Compare commits
41 Commits
3.0.0+107
...
925cb2b423
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
925cb2b423 | ||
|
|
0a2804a404 | ||
|
|
12bbcbf69c | ||
|
|
52ce490725 | ||
| 82067fb3aa | |||
| 007acedf29 | |||
| 8e903ec6c1 | |||
| b55e56c3c4 | |||
| 6f9de431b1 | |||
| a8efd26262 | |||
| e367fc3f5c | |||
| 8a1af120ea | |||
| f03f0181f8 | |||
| 6c7d42c31a | |||
| d6c829c26a | |||
| 666a2dfbf5 | |||
| fd979c3a35 | |||
| 847fc6e864 | |||
| 356b7bf01a | |||
| 450d5ebc81 | |||
| f04285848f | |||
| c4becb0a05 | |||
| d22619396b | |||
| fe8640a6db | |||
| ff475d43dd | |||
| 9e8f6d57df | |||
| 79227a12e2 | |||
| a23dcfe702 | |||
| 243ecb3f71 | |||
| b8dec9f798 | |||
| 536375729f | |||
| 5939a1dc5b | |||
| 9d115a5712 | |||
| f511612a53 | |||
| 180fbcc558 | |||
| 047cb9dc0d | |||
| 786f851a97 | |||
| 4deff5a920 | |||
| 0361f031db | |||
| e90b35f19f | |||
| f2829b2012 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -3,7 +3,7 @@ name: Build Release
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- "*"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@@ -59,6 +59,7 @@ jobs:
|
|||||||
sudo apt-get install -y libnotify-dev
|
sudo apt-get install -y libnotify-dev
|
||||||
sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
sudo apt-get install -y libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||||
sudo apt-get install -y gstreamer-1.0
|
sudo apt-get install -y gstreamer-1.0
|
||||||
|
sudo apt-get install -y libsecret-1-0
|
||||||
- run: flutter pub get
|
- run: flutter pub get
|
||||||
- run: flutter build linux
|
- run: flutter build linux
|
||||||
- name: Archive production artifacts
|
- name: Archive production artifacts
|
||||||
@@ -80,4 +81,4 @@ jobs:
|
|||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build-output-linux-appimage
|
name: build-output-linux-appimage
|
||||||
path: './*.AppImage*'
|
path: "./*.AppImage*"
|
||||||
|
|||||||
@@ -57,6 +57,9 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.google.android.material:material:1.12.0")
|
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 {
|
flutter {
|
||||||
|
|||||||
@@ -46,12 +46,37 @@
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND" />
|
<action android:name="android.intent.action.SEND" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="image/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="image/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="video/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="text/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/*" />
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<data android:mimeType="application/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@@ -70,6 +95,19 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<receiver
|
||||||
|
android:name=".receiver.ReplyReceiver"
|
||||||
|
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
|
<provider
|
||||||
android:name="androidx.core.content.FileProvider"
|
android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="dev.solsynth.solian.provider"
|
android:authorities="dev.solsynth.solian.provider"
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
package dev.solsynth.solian
|
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
|
||||||
import io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
|
||||||
{
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
|
||||||
super.configureFlutterEngine(flutterEngine)
|
|
||||||
// https://github.com/flutter/flutter/issues/153075#issuecomment-2693189362
|
|
||||||
flutterEngine.plugins.add(LegacySharedPreferencesPlugin())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package dev.solsynth.solian
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import io.flutter.plugins.sharedpreferences.LegacySharedPreferencesPlugin
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity()
|
||||||
|
{
|
||||||
|
private val CHANNEL = "dev.solsynth.solian/notifications"
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
// https://github.com/flutter/flutter/issues/153075#issuecomment-2693189362
|
||||||
|
flutterEngine.plugins.add(LegacySharedPreferencesPlugin())
|
||||||
|
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
||||||
|
if (call.method == "initialLink") {
|
||||||
|
val roomId = intent.getStringExtra("room_id")
|
||||||
|
if (roomId != null) {
|
||||||
|
result.success("/rooms/$roomId")
|
||||||
|
} else {
|
||||||
|
result.success(null)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
super.onNewIntent(intent)
|
||||||
|
val roomId = intent.getStringExtra("room_id")
|
||||||
|
if (roomId != null) {
|
||||||
|
MethodChannel(flutterEngine!!.dartExecutor.binaryMessenger, CHANNEL).invokeMethod("newLink", "/rooms/$roomId")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package dev.solsynth.solian.network
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Callback
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
class ApiClient(private val context: Context) {
|
||||||
|
private val client = OkHttpClient()
|
||||||
|
private val sharedPreferences: SharedPreferences = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun sendMessage(roomId: String, message: String, replyTo: String, callback: (Boolean) -> Unit) {
|
||||||
|
val token = sharedPreferences.getString("flutter.token", null)
|
||||||
|
if (token == null) {
|
||||||
|
callback(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val json = JSONObject().apply {
|
||||||
|
put("content", message)
|
||||||
|
put("reply_to", replyTo)
|
||||||
|
}
|
||||||
|
val body = json.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url("https://solian.dev/api/rooms/$roomId/messages")
|
||||||
|
.header("Authorization", "Bearer $token")
|
||||||
|
.post(body)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
callback(response.isSuccessful)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package dev.solsynth.solian.receiver
|
||||||
|
|
||||||
|
import android.app.NotificationManager
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.app.RemoteInput
|
||||||
|
import dev.solsynth.solian.network.ApiClient
|
||||||
|
|
||||||
|
class ReplyReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val remoteInput = RemoteInput.getResultsFromIntent(intent)
|
||||||
|
if (remoteInput != null) {
|
||||||
|
val replyText = remoteInput.getCharSequence("key_text_reply").toString()
|
||||||
|
val roomId = intent.getStringExtra("room_id")
|
||||||
|
val messageId = intent.getStringExtra("message_id")
|
||||||
|
val notificationId = intent.getIntExtra("notification_id", 0)
|
||||||
|
|
||||||
|
if (roomId != null && messageId != null) {
|
||||||
|
ApiClient(context).sendMessage(roomId, replyText, messageId) {
|
||||||
|
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||||
|
notificationManager.cancel(notificationId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -40,31 +40,31 @@ PODS:
|
|||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- DKImagePickerController/PhotoGallery
|
- DKImagePickerController/PhotoGallery
|
||||||
- Flutter
|
- Flutter
|
||||||
- Firebase/CoreOnly (11.13.0):
|
- Firebase/CoreOnly (11.15.0):
|
||||||
- FirebaseCore (~> 11.13.0)
|
- FirebaseCore (~> 11.15.0)
|
||||||
- Firebase/Messaging (11.13.0):
|
- Firebase/Messaging (11.15.0):
|
||||||
- Firebase/CoreOnly
|
- Firebase/CoreOnly
|
||||||
- FirebaseMessaging (~> 11.13.0)
|
- FirebaseMessaging (~> 11.15.0)
|
||||||
- firebase_core (3.14.0):
|
- firebase_core (3.15.0):
|
||||||
- Firebase/CoreOnly (= 11.13.0)
|
- Firebase/CoreOnly (= 11.15.0)
|
||||||
- Flutter
|
- Flutter
|
||||||
- firebase_messaging (15.2.7):
|
- firebase_messaging (15.2.8):
|
||||||
- Firebase/Messaging (= 11.13.0)
|
- Firebase/Messaging (= 11.15.0)
|
||||||
- firebase_core
|
- firebase_core
|
||||||
- Flutter
|
- Flutter
|
||||||
- FirebaseCore (11.13.0):
|
- FirebaseCore (11.15.0):
|
||||||
- FirebaseCoreInternal (~> 11.13.0)
|
- FirebaseCoreInternal (~> 11.15.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/Logger (~> 8.1)
|
- GoogleUtilities/Logger (~> 8.1)
|
||||||
- FirebaseCoreInternal (11.13.0):
|
- FirebaseCoreInternal (11.15.0):
|
||||||
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
- "GoogleUtilities/NSData+zlib (~> 8.1)"
|
||||||
- FirebaseInstallations (11.13.0):
|
- FirebaseInstallations (11.15.0):
|
||||||
- FirebaseCore (~> 11.13.0)
|
- FirebaseCore (~> 11.15.0)
|
||||||
- GoogleUtilities/Environment (~> 8.1)
|
- GoogleUtilities/Environment (~> 8.1)
|
||||||
- GoogleUtilities/UserDefaults (~> 8.1)
|
- GoogleUtilities/UserDefaults (~> 8.1)
|
||||||
- PromisesObjC (~> 2.4)
|
- PromisesObjC (~> 2.4)
|
||||||
- FirebaseMessaging (11.13.0):
|
- FirebaseMessaging (11.15.0):
|
||||||
- FirebaseCore (~> 11.13.0)
|
- FirebaseCore (~> 11.15.0)
|
||||||
- FirebaseInstallations (~> 11.0)
|
- FirebaseInstallations (~> 11.0)
|
||||||
- GoogleDataTransport (~> 10.0)
|
- GoogleDataTransport (~> 10.0)
|
||||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
- GoogleUtilities/AppDelegateSwizzler (~> 8.1)
|
||||||
@@ -80,11 +80,13 @@ PODS:
|
|||||||
- flutter_inappwebview_ios/Core (0.0.1):
|
- flutter_inappwebview_ios/Core (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- OrderedSet (~> 6.0.3)
|
- OrderedSet (~> 6.0.3)
|
||||||
|
- flutter_keyboard_visibility (0.0.1):
|
||||||
|
- Flutter
|
||||||
- flutter_native_splash (2.4.3):
|
- flutter_native_splash (2.4.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_platform_alert (0.0.1):
|
- flutter_platform_alert (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_secure_storage (3.3.1):
|
- flutter_secure_storage (6.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_timezone (0.0.1):
|
- flutter_timezone (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -128,8 +130,8 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- irondash_engine_context (0.0.1):
|
- irondash_engine_context (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Kingfisher (8.3.2)
|
- Kingfisher (8.3.3)
|
||||||
- livekit_client (2.4.8):
|
- livekit_client (2.4.9):
|
||||||
- Flutter
|
- Flutter
|
||||||
- flutter_webrtc
|
- flutter_webrtc
|
||||||
- WebRTC-SDK (= 125.6422.07)
|
- WebRTC-SDK (= 125.6422.07)
|
||||||
@@ -155,6 +157,8 @@ PODS:
|
|||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- pointer_interceptor_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- PromisesObjC (2.4.0)
|
- PromisesObjC (2.4.0)
|
||||||
- receive_sharing_intent (1.8.1):
|
- receive_sharing_intent (1.8.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
@@ -217,6 +221,7 @@ DEPENDENCIES:
|
|||||||
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
|
||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
|
||||||
|
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
|
||||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||||
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
|
- flutter_platform_alert (from `.symlinks/plugins/flutter_platform_alert/ios`)
|
||||||
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
|
||||||
@@ -235,6 +240,7 @@ DEPENDENCIES:
|
|||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
- pasteboard (from `.symlinks/plugins/pasteboard/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
|
- pointer_interceptor_ios (from `.symlinks/plugins/pointer_interceptor_ios/ios`)
|
||||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||||
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
- record_ios (from `.symlinks/plugins/record_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
@@ -286,6 +292,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter
|
:path: Flutter
|
||||||
flutter_inappwebview_ios:
|
flutter_inappwebview_ios:
|
||||||
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
:path: ".symlinks/plugins/flutter_inappwebview_ios/ios"
|
||||||
|
flutter_keyboard_visibility:
|
||||||
|
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
|
||||||
flutter_native_splash:
|
flutter_native_splash:
|
||||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||||
flutter_platform_alert:
|
flutter_platform_alert:
|
||||||
@@ -320,6 +328,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/pasteboard/ios"
|
:path: ".symlinks/plugins/pasteboard/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||||
|
pointer_interceptor_ios:
|
||||||
|
:path: ".symlinks/plugins/pointer_interceptor_ios/ios"
|
||||||
receive_sharing_intent:
|
receive_sharing_intent:
|
||||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||||
record_ios:
|
record_ios:
|
||||||
@@ -351,18 +361,19 @@ SPEC CHECKSUMS:
|
|||||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||||
Firebase: 3435bc66b4d494c2f22c79fd3aae4c1db6662327
|
Firebase: d99ac19b909cd2c548339c2241ecd0d1599ab02e
|
||||||
firebase_core: 700bac7ed92bb754fd70fbf01d72b36ecdd6d450
|
firebase_core: c727a02c560a53f1f1e56e18f16515eb5753c492
|
||||||
firebase_messaging: 860c017fcfbb5e27c163062d1d3135388f3ef954
|
firebase_messaging: 4158969b04b667f5435731ec9d6e453bb58b0c4c
|
||||||
FirebaseCore: c692c7f1c75305ab6aff2b367f25e11d73aa8bd0
|
FirebaseCore: efb3893e5b94f32b86e331e3bd6dadf18b66568e
|
||||||
FirebaseCoreInternal: 29d7b3af4aaf0b8f3ed20b568c13df399b06f68c
|
FirebaseCoreInternal: 9afa45b1159304c963da48addb78275ef701c6b4
|
||||||
FirebaseInstallations: 0ee9074f2c1e86561ace168ee1470dc67aabaf02
|
FirebaseInstallations: 317270fec08a5d418fdbc8429282238cab3ac843
|
||||||
FirebaseMessaging: 195bbdb73e6ca1dbc76cd46e73f3552c084ef6e4
|
FirebaseMessaging: 3b26e2cee503815e01c3701236b020aa9b576f09
|
||||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||||
|
flutter_keyboard_visibility: 4625131e43015dbbe759d9b20daaf77e0e3f6619
|
||||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||||
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
|
flutter_platform_alert: bf3b5fcd4ac14bd637e20527e9c471633071afd3
|
||||||
flutter_secure_storage: 50035aef357c5a8bdd67fd6bc81370d46efc4d16
|
flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13
|
||||||
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
flutter_timezone: 7c838e17ffd4645d261e87037e5bebf6d38fe544
|
||||||
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
flutter_udid: f7c3884e6ec2951efe4f9de082257fc77c4d15e9
|
||||||
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
|
flutter_webrtc: fd0d3bdef8766a0736dbbe2e5b7e85f1f3c52117
|
||||||
@@ -371,8 +382,8 @@ SPEC CHECKSUMS:
|
|||||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||||
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
irondash_engine_context: 8e58ca8e0212ee9d1c7dc6a42121849986c88486
|
||||||
Kingfisher: 0621d0ac0c78fecb19f6dc5303bde2b52abaf2f5
|
Kingfisher: ff82cb91d9266ddb56cbb2f72d32c26f00d3e5be
|
||||||
livekit_client: 9e901890552514206e5ff828903ed271531da264
|
livekit_client: 3f79d79233a5bd13d5b541732624ef959d7c538e
|
||||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||||
@@ -382,6 +393,7 @@ SPEC CHECKSUMS:
|
|||||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||||
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
|
pasteboard: 49088aeb6119d51f976a421db60d8e1ab079b63c
|
||||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||||
|
pointer_interceptor_ios: ec847ef8b0915778bed2b2cef636f4d177fa8eed
|
||||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||||
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
||||||
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
|
record_ios: fee1c924aa4879b882ebca2b4bce6011bcfc3d8b
|
||||||
|
|||||||
@@ -857,7 +857,7 @@
|
|||||||
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -900,7 +900,7 @@
|
|||||||
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -940,7 +940,7 @@
|
|||||||
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
INFOPLIST_FILE = SolianShareExtension/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
INFOPLIST_KEY_CFBundleDisplayName = SolianShareExtension;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -979,7 +979,7 @@
|
|||||||
INFOPLIST_FILE = SolianNotificationService/Info.plist;
|
INFOPLIST_FILE = SolianNotificationService/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
|
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -1021,7 +1021,7 @@
|
|||||||
INFOPLIST_FILE = SolianNotificationService/Info.plist;
|
INFOPLIST_FILE = SolianNotificationService/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
|
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@@ -1060,7 +1060,7 @@
|
|||||||
INFOPLIST_FILE = SolianNotificationService/Info.plist;
|
INFOPLIST_FILE = SolianNotificationService/Info.plist;
|
||||||
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
|
INFOPLIST_KEY_CFBundleDisplayName = SolianNotificationService;
|
||||||
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
INFOPLIST_KEY_NSHumanReadableCopyright = "";
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 18.5;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
|||||||
@@ -11,6 +11,21 @@ import UIKit
|
|||||||
) -> Bool {
|
) -> Bool {
|
||||||
UNUserNotificationCenter.current().delegate = notifyDelegate
|
UNUserNotificationCenter.current().delegate = notifyDelegate
|
||||||
|
|
||||||
|
let replyableMessageCategory = UNNotificationCategory(
|
||||||
|
identifier: "REPLYABLE_MESSAGE",
|
||||||
|
actions: [
|
||||||
|
UNTextInputNotificationAction(
|
||||||
|
identifier: "reply_action",
|
||||||
|
title: "Reply",
|
||||||
|
options: []
|
||||||
|
),
|
||||||
|
],
|
||||||
|
intentIdentifiers: [],
|
||||||
|
options: []
|
||||||
|
)
|
||||||
|
|
||||||
|
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
|
||||||
|
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,14 +10,26 @@ import Alamofire
|
|||||||
|
|
||||||
class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
||||||
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||||
if let textResponse = response as? UNTextInputNotificationResponse {
|
guard let textResponse = response as? UNTextInputNotificationResponse else {
|
||||||
let content = response.notification.request.content
|
completionHandler()
|
||||||
guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var token: String? = UserDefaults.standard.getFlutterToken()
|
let content = response.notification.request.content
|
||||||
if token == nil {
|
|
||||||
|
// Only handle replies for new messages
|
||||||
|
guard let notificationType = content.userInfo["type"] as? String, notificationType == "messages.new" else {
|
||||||
|
completionHandler()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let metadata = content.userInfo["meta"] as? [AnyHashable: Any] else {
|
||||||
|
completionHandler()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
guard let token = UserDefaults.standard.getFlutterToken() else {
|
||||||
|
completionHandler()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,7 +42,7 @@ class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
|||||||
]
|
]
|
||||||
|
|
||||||
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders(
|
AF.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default, headers: HTTPHeaders(
|
||||||
[HTTPHeader(name: "Authorization", value: "AtField \(token!)")]
|
[HTTPHeader(name: "Authorization", value: "AtField \(token)")]
|
||||||
))
|
))
|
||||||
.validate()
|
.validate()
|
||||||
.responseString { response in
|
.responseString { response in
|
||||||
@@ -41,9 +53,8 @@ class NotifyDelegate: UIResponder, UNUserNotificationCenterDelegate {
|
|||||||
print("Failed to send chat reply message: \(error)")
|
print("Failed to send chat reply message: \(error)")
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
// Call completion handler after network request is finished
|
||||||
}
|
|
||||||
|
|
||||||
completionHandler()
|
completionHandler()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,21 +60,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
let pfpIdentifier = meta["pfp"] as? String
|
let pfpIdentifier = meta["pfp"] as? String
|
||||||
|
|
||||||
let replyableMessageCategory = UNNotificationCategory(
|
content.categoryIdentifier = "REPLYABLE_MESSAGE"
|
||||||
identifier: content.categoryIdentifier,
|
|
||||||
actions: [
|
|
||||||
UNTextInputNotificationAction(
|
|
||||||
identifier: "reply_action",
|
|
||||||
title: "Reply",
|
|
||||||
options: []
|
|
||||||
),
|
|
||||||
],
|
|
||||||
intentIdentifiers: [],
|
|
||||||
options: []
|
|
||||||
)
|
|
||||||
|
|
||||||
UNUserNotificationCenter.current().setNotificationCategories([replyableMessageCategory])
|
|
||||||
content.categoryIdentifier = replyableMessageCategory.identifier
|
|
||||||
|
|
||||||
let metaCopy = meta as? [String: Any] ?? [:]
|
let metaCopy = meta as? [String: Any] ?? [:]
|
||||||
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
|
let pfpUrl = pfpIdentifier != nil ? getAttachmentUrl(for: pfpIdentifier!) : nil
|
||||||
|
|||||||
@@ -71,25 +71,32 @@ class MessageRepository {
|
|||||||
bool synced = false,
|
bool synced = false,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
|
// For initial load, fetch latest messages in the background to sync.
|
||||||
|
if (offset == 0 && !synced) {
|
||||||
|
// Not awaiting this is intentional, for a quicker UI response.
|
||||||
|
// The UI should rely on a stream from the database to get updates.
|
||||||
|
_fetchAndCacheMessages(room.id, offset: 0, take: take).catchError((_) {
|
||||||
|
// Best effort, errors will be handled by later fetches.
|
||||||
|
return <LocalChatMessage>[];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
final localMessages = await _getCachedMessages(
|
final localMessages = await _getCachedMessages(
|
||||||
room.id,
|
room.id,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
take: take,
|
take: take,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If it already synced with the remote, skip this
|
// If local cache has messages, return them. This is the common case for scrolling up.
|
||||||
if (offset == 0 && !synced) {
|
|
||||||
// Fetch latest messages
|
|
||||||
_fetchAndCacheMessages(room.id, offset: offset, take: take);
|
|
||||||
|
|
||||||
if (localMessages.isNotEmpty) {
|
if (localMessages.isNotEmpty) {
|
||||||
return localMessages;
|
return localMessages;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
// If local cache is empty, we've probably reached the end of cached history.
|
||||||
|
// Fetch from remote. This will also be hit on first load if cache is empty.
|
||||||
return await _fetchAndCacheMessages(room.id, offset: offset, take: take);
|
return await _fetchAndCacheMessages(room.id, offset: offset, take: take);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If API fails but we have local messages, return them
|
// Final fallback to cache in case of network errors during fetch.
|
||||||
final localMessages = await _getCachedMessages(
|
final localMessages = await _getCachedMessages(
|
||||||
room.id,
|
room.id,
|
||||||
offset: offset,
|
offset: offset,
|
||||||
@@ -117,24 +124,26 @@ class MessageRepository {
|
|||||||
final dbLocalMessages =
|
final dbLocalMessages =
|
||||||
dbMessages.map(_database.companionToMessage).toList();
|
dbMessages.map(_database.companionToMessage).toList();
|
||||||
|
|
||||||
// Combine with pending messages
|
// Combine with pending messages for the first page
|
||||||
|
if (offset == 0) {
|
||||||
final pendingForRoom =
|
final pendingForRoom =
|
||||||
pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
|
pendingMessages.values.where((msg) => msg.roomId == roomId).toList();
|
||||||
|
|
||||||
// Sort by timestamp descending (newest first)
|
|
||||||
final allMessages = [...pendingForRoom, ...dbLocalMessages];
|
final allMessages = [...pendingForRoom, ...dbLocalMessages];
|
||||||
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
allMessages.sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
|
|
||||||
// Apply pagination
|
// Remove duplicates by ID, preserving the order
|
||||||
if (offset >= allMessages.length) {
|
final uniqueMessages = <LocalChatMessage>[];
|
||||||
return [];
|
final seenIds = <String>{};
|
||||||
|
for (final message in allMessages) {
|
||||||
|
if (seenIds.add(message.id)) {
|
||||||
|
uniqueMessages.add(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return uniqueMessages;
|
||||||
}
|
}
|
||||||
|
|
||||||
final end =
|
return dbLocalMessages;
|
||||||
(offset + take) > allMessages.length
|
|
||||||
? allMessages.length
|
|
||||||
: (offset + take);
|
|
||||||
return allMessages.sublist(offset, end);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<LocalChatMessage>> _fetchAndCacheMessages(
|
Future<List<LocalChatMessage>> _fetchAndCacheMessages(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:firebase_core/firebase_core.dart';
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:image_picker_android/image_picker_android.dart';
|
import 'package:image_picker_android/image_picker_android.dart';
|
||||||
@@ -18,7 +19,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart';
|
|||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/screens/tabs.dart';
|
|
||||||
import 'package:island/services/notify.dart';
|
import 'package:island/services/notify.dart';
|
||||||
import 'package:island/services/timezone.dart';
|
import 'package:island/services/timezone.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
@@ -29,6 +30,12 @@ import 'package:image_picker_platform_interface/image_picker_platform_interface.
|
|||||||
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||||
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
|
log('Handling a background message: ${message.messageId}');
|
||||||
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||||
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
@@ -43,6 +50,7 @@ void main() async {
|
|||||||
await Firebase.initializeApp(
|
await Firebase.initializeApp(
|
||||||
options: DefaultFirebaseOptions.currentPlatform,
|
options: DefaultFirebaseOptions.currentPlatform,
|
||||||
);
|
);
|
||||||
|
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
|
||||||
log("[SplashScreen] Firebase is ready!");
|
log("[SplashScreen] Firebase is ready!");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
@@ -125,7 +133,7 @@ void main() async {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final appRouter = AppRouter();
|
// Router will be provided through Riverpod
|
||||||
|
|
||||||
final globalOverlay = GlobalKey<OverlayState>();
|
final globalOverlay = GlobalKey<OverlayState>();
|
||||||
|
|
||||||
@@ -141,7 +149,8 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
var uri = notification.data['action_uri'] as String;
|
var uri = notification.data['action_uri'] as String;
|
||||||
if (uri.startsWith('/')) {
|
if (uri.startsWith('/')) {
|
||||||
// In-app routes
|
// In-app routes
|
||||||
appRouter.pushPath(notification.data['action_uri']);
|
final router = ref.read(routerProvider);
|
||||||
|
router.go(notification.data['action_uri']);
|
||||||
} else {
|
} else {
|
||||||
// External links
|
// External links
|
||||||
launchUrlString(uri);
|
launchUrlString(uri);
|
||||||
@@ -150,17 +159,52 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
Future(() async {
|
const channel = MethodChannel('dev.solsynth.solian/notifications');
|
||||||
RemoteMessage? initialMessage =
|
|
||||||
await FirebaseMessaging.instance.getInitialMessage();
|
Future<void> handleInitialLink() async {
|
||||||
if (initialMessage != null) {
|
final String? link = await channel.invokeMethod('initialLink');
|
||||||
handleMessage(initialMessage);
|
if (link != null) {
|
||||||
|
final router = ref.read(routerProvider);
|
||||||
|
router.go(link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FirebaseMessaging.onMessageOpenedApp.listen(handleMessage);
|
if (!kIsWeb && Platform.isAndroid) {
|
||||||
|
handleInitialLink();
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.setMethodCallHandler((call) async {
|
||||||
|
if (call.method == 'newLink') {
|
||||||
|
final String link = call.arguments;
|
||||||
|
final router = ref.read(routerProvider);
|
||||||
|
router.go(link);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
// When the app is opened from a terminated state.
|
||||||
|
FirebaseMessaging.instance.getInitialMessage().then((message) {
|
||||||
|
if (message != null) {
|
||||||
|
handleMessage(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// When the app is in the background and opened.
|
||||||
|
final onMessageOpenedAppSubscription = FirebaseMessaging
|
||||||
|
.onMessageOpenedApp
|
||||||
|
.listen(handleMessage);
|
||||||
|
|
||||||
|
// When the app is in the foreground.
|
||||||
|
final onMessageSubscription = FirebaseMessaging.onMessage.listen((
|
||||||
|
message,
|
||||||
|
) {
|
||||||
|
log('Foreground message received: ${message.messageId}');
|
||||||
|
handleMessage(message);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () {
|
||||||
|
onMessageOpenedAppSubscription.cancel();
|
||||||
|
onMessageSubscription.cancel();
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@@ -183,20 +227,13 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
return MaterialApp.router(
|
return MaterialApp.router(
|
||||||
theme: theme?.light,
|
theme: theme?.light,
|
||||||
darkTheme: theme?.dark,
|
darkTheme: theme?.dark,
|
||||||
themeMode: ThemeMode.system,
|
themeMode: ThemeMode.system,
|
||||||
routerConfig: appRouter.config(
|
routerConfig: router,
|
||||||
navigatorObservers:
|
|
||||||
() => [
|
|
||||||
TabNavigationObserver(
|
|
||||||
onChange: (route) {
|
|
||||||
ref.read(currentRouteProvider.notifier).state = route;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
supportedLocales: context.supportedLocales,
|
supportedLocales: context.supportedLocales,
|
||||||
localizationsDelegates: [
|
localizationsDelegates: [
|
||||||
...context.localizationDelegates,
|
...context.localizationDelegates,
|
||||||
@@ -210,10 +247,8 @@ class IslandApp extends HookConsumerWidget {
|
|||||||
initialEntries: [
|
initialEntries: [
|
||||||
OverlayEntry(
|
OverlayEntry(
|
||||||
builder:
|
builder:
|
||||||
(_) => WindowScaffold(
|
(_) =>
|
||||||
router: appRouter,
|
WindowScaffold(child: child ?? const SizedBox.shrink()),
|
||||||
child: child ?? const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
34
lib/models/auto_completion.dart
Normal file
34
lib/models/auto_completion.dart
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'auto_completion.freezed.dart';
|
||||||
|
part 'auto_completion.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class AutoCompletionResponse with _$AutoCompletionResponse {
|
||||||
|
const factory AutoCompletionResponse.account({
|
||||||
|
required String type,
|
||||||
|
required List<AutoCompletionItem> items,
|
||||||
|
}) = AutoCompletionAccountResponse;
|
||||||
|
|
||||||
|
const factory AutoCompletionResponse.sticker({
|
||||||
|
required String type,
|
||||||
|
required List<AutoCompletionItem> items,
|
||||||
|
}) = AutoCompletionStickerResponse;
|
||||||
|
|
||||||
|
factory AutoCompletionResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AutoCompletionResponseFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class AutoCompletionItem with _$AutoCompletionItem {
|
||||||
|
const factory AutoCompletionItem({
|
||||||
|
required String id,
|
||||||
|
required String displayName,
|
||||||
|
required String? secondaryText,
|
||||||
|
required String type,
|
||||||
|
required dynamic data,
|
||||||
|
}) = _AutoCompletionItem;
|
||||||
|
|
||||||
|
factory AutoCompletionItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$AutoCompletionItemFromJson(json);
|
||||||
|
}
|
||||||
410
lib/models/auto_completion.freezed.dart
Normal file
410
lib/models/auto_completion.freezed.dart
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'auto_completion.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
AutoCompletionResponse _$AutoCompletionResponseFromJson(
|
||||||
|
Map<String, dynamic> json
|
||||||
|
) {
|
||||||
|
switch (json['runtimeType']) {
|
||||||
|
case 'account':
|
||||||
|
return AutoCompletionAccountResponse.fromJson(
|
||||||
|
json
|
||||||
|
);
|
||||||
|
case 'sticker':
|
||||||
|
return AutoCompletionStickerResponse.fromJson(
|
||||||
|
json
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw CheckedFromJsonException(
|
||||||
|
json,
|
||||||
|
'runtimeType',
|
||||||
|
'AutoCompletionResponse',
|
||||||
|
'Invalid union type "${json['runtimeType']}"!'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$AutoCompletionResponse {
|
||||||
|
|
||||||
|
String get type; List<AutoCompletionItem> get items;
|
||||||
|
/// Create a copy of AutoCompletionResponse
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$AutoCompletionResponseCopyWith<AutoCompletionResponse> get copyWith => _$AutoCompletionResponseCopyWithImpl<AutoCompletionResponse>(this as AutoCompletionResponse, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this AutoCompletionResponse to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionResponse&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.items, items));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(items));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AutoCompletionResponse(type: $type, items: $items)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $AutoCompletionResponseCopyWith<$Res> {
|
||||||
|
factory $AutoCompletionResponseCopyWith(AutoCompletionResponse value, $Res Function(AutoCompletionResponse) _then) = _$AutoCompletionResponseCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String type, List<AutoCompletionItem> items
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$AutoCompletionResponseCopyWithImpl<$Res>
|
||||||
|
implements $AutoCompletionResponseCopyWith<$Res> {
|
||||||
|
_$AutoCompletionResponseCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final AutoCompletionResponse _self;
|
||||||
|
final $Res Function(AutoCompletionResponse) _then;
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionResponse
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? type = null,Object? items = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,items: null == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<AutoCompletionItem>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class AutoCompletionAccountResponse implements AutoCompletionResponse {
|
||||||
|
const AutoCompletionAccountResponse({required this.type, required final List<AutoCompletionItem> items, final String? $type}): _items = items,$type = $type ?? 'account';
|
||||||
|
factory AutoCompletionAccountResponse.fromJson(Map<String, dynamic> json) => _$AutoCompletionAccountResponseFromJson(json);
|
||||||
|
|
||||||
|
@override final String type;
|
||||||
|
final List<AutoCompletionItem> _items;
|
||||||
|
@override List<AutoCompletionItem> get items {
|
||||||
|
if (_items is EqualUnmodifiableListView) return _items;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@JsonKey(name: 'runtimeType')
|
||||||
|
final String $type;
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionResponse
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$AutoCompletionAccountResponseCopyWith<AutoCompletionAccountResponse> get copyWith => _$AutoCompletionAccountResponseCopyWithImpl<AutoCompletionAccountResponse>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$AutoCompletionAccountResponseToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionAccountResponse&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._items, _items));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_items));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AutoCompletionResponse.account(type: $type, items: $items)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $AutoCompletionAccountResponseCopyWith<$Res> implements $AutoCompletionResponseCopyWith<$Res> {
|
||||||
|
factory $AutoCompletionAccountResponseCopyWith(AutoCompletionAccountResponse value, $Res Function(AutoCompletionAccountResponse) _then) = _$AutoCompletionAccountResponseCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String type, List<AutoCompletionItem> items
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$AutoCompletionAccountResponseCopyWithImpl<$Res>
|
||||||
|
implements $AutoCompletionAccountResponseCopyWith<$Res> {
|
||||||
|
_$AutoCompletionAccountResponseCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final AutoCompletionAccountResponse _self;
|
||||||
|
final $Res Function(AutoCompletionAccountResponse) _then;
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionResponse
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? items = null,}) {
|
||||||
|
return _then(AutoCompletionAccountResponse(
|
||||||
|
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<AutoCompletionItem>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class AutoCompletionStickerResponse implements AutoCompletionResponse {
|
||||||
|
const AutoCompletionStickerResponse({required this.type, required final List<AutoCompletionItem> items, final String? $type}): _items = items,$type = $type ?? 'sticker';
|
||||||
|
factory AutoCompletionStickerResponse.fromJson(Map<String, dynamic> json) => _$AutoCompletionStickerResponseFromJson(json);
|
||||||
|
|
||||||
|
@override final String type;
|
||||||
|
final List<AutoCompletionItem> _items;
|
||||||
|
@override List<AutoCompletionItem> get items {
|
||||||
|
if (_items is EqualUnmodifiableListView) return _items;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_items);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@JsonKey(name: 'runtimeType')
|
||||||
|
final String $type;
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionResponse
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$AutoCompletionStickerResponseCopyWith<AutoCompletionStickerResponse> get copyWith => _$AutoCompletionStickerResponseCopyWithImpl<AutoCompletionStickerResponse>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$AutoCompletionStickerResponseToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionStickerResponse&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other._items, _items));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,type,const DeepCollectionEquality().hash(_items));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AutoCompletionResponse.sticker(type: $type, items: $items)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $AutoCompletionStickerResponseCopyWith<$Res> implements $AutoCompletionResponseCopyWith<$Res> {
|
||||||
|
factory $AutoCompletionStickerResponseCopyWith(AutoCompletionStickerResponse value, $Res Function(AutoCompletionStickerResponse) _then) = _$AutoCompletionStickerResponseCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String type, List<AutoCompletionItem> items
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$AutoCompletionStickerResponseCopyWithImpl<$Res>
|
||||||
|
implements $AutoCompletionStickerResponseCopyWith<$Res> {
|
||||||
|
_$AutoCompletionStickerResponseCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final AutoCompletionStickerResponse _self;
|
||||||
|
final $Res Function(AutoCompletionStickerResponse) _then;
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionResponse
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? type = null,Object? items = null,}) {
|
||||||
|
return _then(AutoCompletionStickerResponse(
|
||||||
|
type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,items: null == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<AutoCompletionItem>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$AutoCompletionItem {
|
||||||
|
|
||||||
|
String get id; String get displayName; String? get secondaryText; String get type; dynamic get data;
|
||||||
|
/// Create a copy of AutoCompletionItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$AutoCompletionItemCopyWith<AutoCompletionItem> get copyWith => _$AutoCompletionItemCopyWithImpl<AutoCompletionItem>(this as AutoCompletionItem, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this AutoCompletionItem to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is AutoCompletionItem&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.secondaryText, secondaryText) || other.secondaryText == secondaryText)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,displayName,secondaryText,type,const DeepCollectionEquality().hash(data));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AutoCompletionItem(id: $id, displayName: $displayName, secondaryText: $secondaryText, type: $type, data: $data)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $AutoCompletionItemCopyWith<$Res> {
|
||||||
|
factory $AutoCompletionItemCopyWith(AutoCompletionItem value, $Res Function(AutoCompletionItem) _then) = _$AutoCompletionItemCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String displayName, String? secondaryText, String type, dynamic data
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$AutoCompletionItemCopyWithImpl<$Res>
|
||||||
|
implements $AutoCompletionItemCopyWith<$Res> {
|
||||||
|
_$AutoCompletionItemCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final AutoCompletionItem _self;
|
||||||
|
final $Res Function(AutoCompletionItem) _then;
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? displayName = null,Object? secondaryText = freezed,Object? type = null,Object? data = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,secondaryText: freezed == secondaryText ? _self.secondaryText : secondaryText // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _AutoCompletionItem implements AutoCompletionItem {
|
||||||
|
const _AutoCompletionItem({required this.id, required this.displayName, required this.secondaryText, required this.type, required this.data});
|
||||||
|
factory _AutoCompletionItem.fromJson(Map<String, dynamic> json) => _$AutoCompletionItemFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String displayName;
|
||||||
|
@override final String? secondaryText;
|
||||||
|
@override final String type;
|
||||||
|
@override final dynamic data;
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$AutoCompletionItemCopyWith<_AutoCompletionItem> get copyWith => __$AutoCompletionItemCopyWithImpl<_AutoCompletionItem>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$AutoCompletionItemToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AutoCompletionItem&&(identical(other.id, id) || other.id == id)&&(identical(other.displayName, displayName) || other.displayName == displayName)&&(identical(other.secondaryText, secondaryText) || other.secondaryText == secondaryText)&&(identical(other.type, type) || other.type == type)&&const DeepCollectionEquality().equals(other.data, data));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,displayName,secondaryText,type,const DeepCollectionEquality().hash(data));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'AutoCompletionItem(id: $id, displayName: $displayName, secondaryText: $secondaryText, type: $type, data: $data)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$AutoCompletionItemCopyWith<$Res> implements $AutoCompletionItemCopyWith<$Res> {
|
||||||
|
factory _$AutoCompletionItemCopyWith(_AutoCompletionItem value, $Res Function(_AutoCompletionItem) _then) = __$AutoCompletionItemCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String displayName, String? secondaryText, String type, dynamic data
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$AutoCompletionItemCopyWithImpl<$Res>
|
||||||
|
implements _$AutoCompletionItemCopyWith<$Res> {
|
||||||
|
__$AutoCompletionItemCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _AutoCompletionItem _self;
|
||||||
|
final $Res Function(_AutoCompletionItem) _then;
|
||||||
|
|
||||||
|
/// Create a copy of AutoCompletionItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? displayName = null,Object? secondaryText = freezed,Object? type = null,Object? data = freezed,}) {
|
||||||
|
return _then(_AutoCompletionItem(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,displayName: null == displayName ? _self.displayName : displayName // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,secondaryText: freezed == secondaryText ? _self.secondaryText : secondaryText // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||||
|
as dynamic,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
63
lib/models/auto_completion.g.dart
Normal file
63
lib/models/auto_completion.g.dart
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'auto_completion.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
AutoCompletionAccountResponse _$AutoCompletionAccountResponseFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => AutoCompletionAccountResponse(
|
||||||
|
type: json['type'] as String,
|
||||||
|
items:
|
||||||
|
(json['items'] as List<dynamic>)
|
||||||
|
.map((e) => AutoCompletionItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AutoCompletionAccountResponseToJson(
|
||||||
|
AutoCompletionAccountResponse instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'type': instance.type,
|
||||||
|
'items': instance.items.map((e) => e.toJson()).toList(),
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
||||||
|
|
||||||
|
AutoCompletionStickerResponse _$AutoCompletionStickerResponseFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => AutoCompletionStickerResponse(
|
||||||
|
type: json['type'] as String,
|
||||||
|
items:
|
||||||
|
(json['items'] as List<dynamic>)
|
||||||
|
.map((e) => AutoCompletionItem.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList(),
|
||||||
|
$type: json['runtimeType'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AutoCompletionStickerResponseToJson(
|
||||||
|
AutoCompletionStickerResponse instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'type': instance.type,
|
||||||
|
'items': instance.items.map((e) => e.toJson()).toList(),
|
||||||
|
'runtimeType': instance.$type,
|
||||||
|
};
|
||||||
|
|
||||||
|
_AutoCompletionItem _$AutoCompletionItemFromJson(Map<String, dynamic> json) =>
|
||||||
|
_AutoCompletionItem(
|
||||||
|
id: json['id'] as String,
|
||||||
|
displayName: json['display_name'] as String,
|
||||||
|
secondaryText: json['secondary_text'] as String?,
|
||||||
|
type: json['type'] as String,
|
||||||
|
data: json['data'],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$AutoCompletionItemToJson(_AutoCompletionItem instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'display_name': instance.displayName,
|
||||||
|
'secondary_text': instance.secondaryText,
|
||||||
|
'type': instance.type,
|
||||||
|
'data': instance.data,
|
||||||
|
};
|
||||||
@@ -13,7 +13,8 @@ sealed class SnChatRoom with _$SnChatRoom {
|
|||||||
required String? name,
|
required String? name,
|
||||||
required String? description,
|
required String? description,
|
||||||
required int type,
|
required int type,
|
||||||
required bool isPublic,
|
@Default(false) bool isPublic,
|
||||||
|
@Default(false) bool isCommunity,
|
||||||
required SnCloudFile? picture,
|
required SnCloudFile? picture,
|
||||||
required SnCloudFile? background,
|
required SnCloudFile? background,
|
||||||
required String? realmId,
|
required String? realmId,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnChatRoom {
|
mixin _$SnChatRoom {
|
||||||
|
|
||||||
String get id; String? get name; String? get description; int get type; bool get isPublic; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;
|
String get id; String? get name; String? get description; int get type; bool get isPublic; bool get isCommunity; SnCloudFile? get picture; SnCloudFile? get background; String? get realmId; SnRealm? get realm; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt; List<SnChatMember>? get members;
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -29,16 +29,16 @@ $SnChatRoomCopyWith<SnChatRoom> get copyWith => _$SnChatRoomCopyWithImpl<SnChatR
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other.members, members));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members));
|
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(members));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
|
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ abstract mixin class $SnChatRoomCopyWith<$Res> {
|
|||||||
factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl;
|
factory $SnChatRoomCopyWith(SnChatRoom value, $Res Function(SnChatRoom) _then) = _$SnChatRoomCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
|
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -66,13 +66,14 @@ class _$SnChatRoomCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
|
||||||
return _then(_self.copyWith(
|
return _then(_self.copyWith(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
|
as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -128,14 +129,15 @@ $SnRealmCopyWith<$Res>? get realm {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnChatRoom implements SnChatRoom {
|
class _SnChatRoom implements SnChatRoom {
|
||||||
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, required this.isPublic, required this.picture, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members}): _members = members;
|
const _SnChatRoom({required this.id, required this.name, required this.description, required this.type, this.isPublic = false, this.isCommunity = false, required this.picture, required this.background, required this.realmId, required this.realm, required this.createdAt, required this.updatedAt, required this.deletedAt, required final List<SnChatMember>? members}): _members = members;
|
||||||
factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json);
|
factory _SnChatRoom.fromJson(Map<String, dynamic> json) => _$SnChatRoomFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String? name;
|
@override final String? name;
|
||||||
@override final String? description;
|
@override final String? description;
|
||||||
@override final int type;
|
@override final int type;
|
||||||
@override final bool isPublic;
|
@override@JsonKey() final bool isPublic;
|
||||||
|
@override@JsonKey() final bool isCommunity;
|
||||||
@override final SnCloudFile? picture;
|
@override final SnCloudFile? picture;
|
||||||
@override final SnCloudFile? background;
|
@override final SnCloudFile? background;
|
||||||
@override final String? realmId;
|
@override final String? realmId;
|
||||||
@@ -166,16 +168,16 @@ Map<String, dynamic> toJson() {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnChatRoom&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.type, type) || other.type == type)&&(identical(other.isPublic, isPublic) || other.isPublic == isPublic)&&(identical(other.isCommunity, isCommunity) || other.isCommunity == isCommunity)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.realm, realm) || other.realm == realm)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&const DeepCollectionEquality().equals(other._members, _members));
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members));
|
int get hashCode => Object.hash(runtimeType,id,name,description,type,isPublic,isCommunity,picture,background,realmId,realm,createdAt,updatedAt,deletedAt,const DeepCollectionEquality().hash(_members));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
|
return 'SnChatRoom(id: $id, name: $name, description: $description, type: $type, isPublic: $isPublic, isCommunity: $isCommunity, picture: $picture, background: $background, realmId: $realmId, realm: $realm, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, members: $members)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -186,7 +188,7 @@ abstract mixin class _$SnChatRoomCopyWith<$Res> implements $SnChatRoomCopyWith<$
|
|||||||
factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl;
|
factory _$SnChatRoomCopyWith(_SnChatRoom value, $Res Function(_SnChatRoom) _then) = __$SnChatRoomCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? name, String? description, int type, bool isPublic, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
|
String id, String? name, String? description, int type, bool isPublic, bool isCommunity, SnCloudFile? picture, SnCloudFile? background, String? realmId, SnRealm? realm, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt, List<SnChatMember>? members
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -203,13 +205,14 @@ class __$SnChatRoomCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of SnChatRoom
|
/// Create a copy of SnChatRoom
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = freezed,Object? description = freezed,Object? type = null,Object? isPublic = null,Object? isCommunity = null,Object? picture = freezed,Object? background = freezed,Object? realmId = freezed,Object? realm = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,Object? members = freezed,}) {
|
||||||
return _then(_SnChatRoom(
|
return _then(_SnChatRoom(
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
as String,name: freezed == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
as String?,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
|
as int,isPublic: null == isPublic ? _self.isPublic : isPublic // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,isCommunity: null == isCommunity ? _self.isCommunity : isCommunity // ignore: cast_nullable_to_non_nullable
|
||||||
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
as bool,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
as SnCloudFile?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ _SnChatRoom _$SnChatRoomFromJson(Map<String, dynamic> json) => _SnChatRoom(
|
|||||||
name: json['name'] as String?,
|
name: json['name'] as String?,
|
||||||
description: json['description'] as String?,
|
description: json['description'] as String?,
|
||||||
type: (json['type'] as num).toInt(),
|
type: (json['type'] as num).toInt(),
|
||||||
isPublic: json['is_public'] as bool,
|
isPublic: json['is_public'] as bool? ?? false,
|
||||||
|
isCommunity: json['is_community'] as bool? ?? false,
|
||||||
picture:
|
picture:
|
||||||
json['picture'] == null
|
json['picture'] == null
|
||||||
? null
|
? null
|
||||||
@@ -44,6 +45,7 @@ Map<String, dynamic> _$SnChatRoomToJson(_SnChatRoom instance) =>
|
|||||||
'description': instance.description,
|
'description': instance.description,
|
||||||
'type': instance.type,
|
'type': instance.type,
|
||||||
'is_public': instance.isPublic,
|
'is_public': instance.isPublic,
|
||||||
|
'is_community': instance.isCommunity,
|
||||||
'picture': instance.picture?.toJson(),
|
'picture': instance.picture?.toJson(),
|
||||||
'background': instance.background?.toJson(),
|
'background': instance.background?.toJson(),
|
||||||
'realm_id': instance.realmId,
|
'realm_id': instance.realmId,
|
||||||
|
|||||||
71
lib/models/custom_app.dart
Normal file
71
lib/models/custom_app.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/models/user.dart';
|
||||||
|
|
||||||
|
part 'custom_app.freezed.dart';
|
||||||
|
part 'custom_app.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class CustomApp with _$CustomApp {
|
||||||
|
const factory CustomApp({
|
||||||
|
@Default('') String id,
|
||||||
|
@Default('') String slug,
|
||||||
|
@Default('') String name,
|
||||||
|
String? description,
|
||||||
|
@Default(0) int status,
|
||||||
|
SnCloudFile? picture,
|
||||||
|
SnCloudFile? background,
|
||||||
|
SnVerificationMark? verification,
|
||||||
|
CustomAppOauthConfig? oauthConfig,
|
||||||
|
CustomAppLinks? links,
|
||||||
|
@Default([]) List<CustomAppSecret> secrets,
|
||||||
|
@Default('') String publisherId,
|
||||||
|
}) = _CustomApp;
|
||||||
|
|
||||||
|
factory CustomApp.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CustomAppFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class CustomAppLinks with _$CustomAppLinks {
|
||||||
|
const factory CustomAppLinks({
|
||||||
|
String? homePage,
|
||||||
|
String? privacyPolicy,
|
||||||
|
String? termsOfService,
|
||||||
|
}) = _CustomAppLinks;
|
||||||
|
|
||||||
|
factory CustomAppLinks.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CustomAppLinksFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class CustomAppOauthConfig with _$CustomAppOauthConfig {
|
||||||
|
const factory CustomAppOauthConfig({
|
||||||
|
String? clientUri,
|
||||||
|
@Default([]) List<String> redirectUris,
|
||||||
|
List<String>? postLogoutRedirectUris,
|
||||||
|
@Default(['openid', 'profile', 'email']) List<String> allowedScopes,
|
||||||
|
@Default(['authorization_code', 'refresh_token'])
|
||||||
|
List<String> allowedGrantTypes,
|
||||||
|
@Default(true) bool requirePkce,
|
||||||
|
@Default(false) bool allowOfflineAccess,
|
||||||
|
}) = _CustomAppOauthConfig;
|
||||||
|
|
||||||
|
factory CustomAppOauthConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CustomAppOauthConfigFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class CustomAppSecret with _$CustomAppSecret {
|
||||||
|
const factory CustomAppSecret({
|
||||||
|
@Default('') String id,
|
||||||
|
@Default('') String secret,
|
||||||
|
String? description,
|
||||||
|
DateTime? expiredAt,
|
||||||
|
@Default(false) bool isOidc,
|
||||||
|
@Default('') String appId,
|
||||||
|
}) = _CustomAppSecret;
|
||||||
|
|
||||||
|
factory CustomAppSecret.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CustomAppSecretFromJson(json);
|
||||||
|
}
|
||||||
771
lib/models/custom_app.freezed.dart
Normal file
771
lib/models/custom_app.freezed.dart
Normal file
@@ -0,0 +1,771 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'custom_app.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$CustomApp {
|
||||||
|
|
||||||
|
String get id; String get slug; String get name; String? get description; int get status; SnCloudFile? get picture; SnCloudFile? get background; SnVerificationMark? get verification; CustomAppOauthConfig? get oauthConfig; CustomAppLinks? get links; List<CustomAppSecret> get secrets; String get publisherId;
|
||||||
|
/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppCopyWith<CustomApp> get copyWith => _$CustomAppCopyWithImpl<CustomApp>(this as CustomApp, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this CustomApp to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomApp&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.status, status) || other.status == status)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.oauthConfig, oauthConfig) || other.oauthConfig == oauthConfig)&&(identical(other.links, links) || other.links == links)&&const DeepCollectionEquality().equals(other.secrets, secrets)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,slug,name,description,status,picture,background,verification,oauthConfig,links,const DeepCollectionEquality().hash(secrets),publisherId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomApp(id: $id, slug: $slug, name: $name, description: $description, status: $status, picture: $picture, background: $background, verification: $verification, oauthConfig: $oauthConfig, links: $links, secrets: $secrets, publisherId: $publisherId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $CustomAppCopyWith<$Res> {
|
||||||
|
factory $CustomAppCopyWith(CustomApp value, $Res Function(CustomApp) _then) = _$CustomAppCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String slug, String name, String? description, int status, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, CustomAppOauthConfig? oauthConfig, CustomAppLinks? links, List<CustomAppSecret> secrets, String publisherId
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnVerificationMarkCopyWith<$Res>? get verification;$CustomAppOauthConfigCopyWith<$Res>? get oauthConfig;$CustomAppLinksCopyWith<$Res>? get links;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$CustomAppCopyWithImpl<$Res>
|
||||||
|
implements $CustomAppCopyWith<$Res> {
|
||||||
|
_$CustomAppCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final CustomApp _self;
|
||||||
|
final $Res Function(CustomApp) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? status = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? oauthConfig = freezed,Object? links = freezed,Object? secrets = null,Object? publisherId = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,slug: null == slug ? _self.slug : slug // ignore: cast_nullable_to_non_nullable
|
||||||
|
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?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnVerificationMark?,oauthConfig: freezed == oauthConfig ? _self.oauthConfig : oauthConfig // ignore: cast_nullable_to_non_nullable
|
||||||
|
as CustomAppOauthConfig?,links: freezed == links ? _self.links : links // ignore: cast_nullable_to_non_nullable
|
||||||
|
as CustomAppLinks?,secrets: null == secrets ? _self.secrets : secrets // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<CustomAppSecret>,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
|
if (_self.picture == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||||
|
return _then(_self.copyWith(picture: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get background {
|
||||||
|
if (_self.background == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
|
return _then(_self.copyWith(background: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnVerificationMarkCopyWith<$Res>? get verification {
|
||||||
|
if (_self.verification == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
|
||||||
|
return _then(_self.copyWith(verification: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppOauthConfigCopyWith<$Res>? get oauthConfig {
|
||||||
|
if (_self.oauthConfig == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $CustomAppOauthConfigCopyWith<$Res>(_self.oauthConfig!, (value) {
|
||||||
|
return _then(_self.copyWith(oauthConfig: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppLinksCopyWith<$Res>? get links {
|
||||||
|
if (_self.links == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $CustomAppLinksCopyWith<$Res>(_self.links!, (value) {
|
||||||
|
return _then(_self.copyWith(links: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _CustomApp implements CustomApp {
|
||||||
|
const _CustomApp({this.id = '', this.slug = '', this.name = '', this.description, this.status = 0, this.picture, this.background, this.verification, this.oauthConfig, this.links, final List<CustomAppSecret> secrets = const [], this.publisherId = ''}): _secrets = secrets;
|
||||||
|
factory _CustomApp.fromJson(Map<String, dynamic> json) => _$CustomAppFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey() final String id;
|
||||||
|
@override@JsonKey() final String slug;
|
||||||
|
@override@JsonKey() final String name;
|
||||||
|
@override final String? description;
|
||||||
|
@override@JsonKey() final int status;
|
||||||
|
@override final SnCloudFile? picture;
|
||||||
|
@override final SnCloudFile? background;
|
||||||
|
@override final SnVerificationMark? verification;
|
||||||
|
@override final CustomAppOauthConfig? oauthConfig;
|
||||||
|
@override final CustomAppLinks? links;
|
||||||
|
final List<CustomAppSecret> _secrets;
|
||||||
|
@override@JsonKey() List<CustomAppSecret> get secrets {
|
||||||
|
if (_secrets is EqualUnmodifiableListView) return _secrets;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_secrets);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override@JsonKey() final String publisherId;
|
||||||
|
|
||||||
|
/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$CustomAppCopyWith<_CustomApp> get copyWith => __$CustomAppCopyWithImpl<_CustomApp>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$CustomAppToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomApp&&(identical(other.id, id) || other.id == id)&&(identical(other.slug, slug) || other.slug == slug)&&(identical(other.name, name) || other.name == name)&&(identical(other.description, description) || other.description == description)&&(identical(other.status, status) || other.status == status)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.verification, verification) || other.verification == verification)&&(identical(other.oauthConfig, oauthConfig) || other.oauthConfig == oauthConfig)&&(identical(other.links, links) || other.links == links)&&const DeepCollectionEquality().equals(other._secrets, _secrets)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,slug,name,description,status,picture,background,verification,oauthConfig,links,const DeepCollectionEquality().hash(_secrets),publisherId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomApp(id: $id, slug: $slug, name: $name, description: $description, status: $status, picture: $picture, background: $background, verification: $verification, oauthConfig: $oauthConfig, links: $links, secrets: $secrets, publisherId: $publisherId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$CustomAppCopyWith<$Res> implements $CustomAppCopyWith<$Res> {
|
||||||
|
factory _$CustomAppCopyWith(_CustomApp value, $Res Function(_CustomApp) _then) = __$CustomAppCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String slug, String name, String? description, int status, SnCloudFile? picture, SnCloudFile? background, SnVerificationMark? verification, CustomAppOauthConfig? oauthConfig, CustomAppLinks? links, List<CustomAppSecret> secrets, String publisherId
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnVerificationMarkCopyWith<$Res>? get verification;@override $CustomAppOauthConfigCopyWith<$Res>? get oauthConfig;@override $CustomAppLinksCopyWith<$Res>? get links;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$CustomAppCopyWithImpl<$Res>
|
||||||
|
implements _$CustomAppCopyWith<$Res> {
|
||||||
|
__$CustomAppCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _CustomApp _self;
|
||||||
|
final $Res Function(_CustomApp) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? slug = null,Object? name = null,Object? description = freezed,Object? status = null,Object? picture = freezed,Object? background = freezed,Object? verification = freezed,Object? oauthConfig = freezed,Object? links = freezed,Object? secrets = null,Object? publisherId = null,}) {
|
||||||
|
return _then(_CustomApp(
|
||||||
|
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: 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?,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnVerificationMark?,oauthConfig: freezed == oauthConfig ? _self.oauthConfig : oauthConfig // ignore: cast_nullable_to_non_nullable
|
||||||
|
as CustomAppOauthConfig?,links: freezed == links ? _self.links : links // ignore: cast_nullable_to_non_nullable
|
||||||
|
as CustomAppLinks?,secrets: null == secrets ? _self._secrets : secrets // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<CustomAppSecret>,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
|
if (_self.picture == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||||
|
return _then(_self.copyWith(picture: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get background {
|
||||||
|
if (_self.background == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
|
return _then(_self.copyWith(background: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnVerificationMarkCopyWith<$Res>? get verification {
|
||||||
|
if (_self.verification == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
|
||||||
|
return _then(_self.copyWith(verification: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppOauthConfigCopyWith<$Res>? get oauthConfig {
|
||||||
|
if (_self.oauthConfig == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $CustomAppOauthConfigCopyWith<$Res>(_self.oauthConfig!, (value) {
|
||||||
|
return _then(_self.copyWith(oauthConfig: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of CustomApp
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppLinksCopyWith<$Res>? get links {
|
||||||
|
if (_self.links == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $CustomAppLinksCopyWith<$Res>(_self.links!, (value) {
|
||||||
|
return _then(_self.copyWith(links: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$CustomAppLinks {
|
||||||
|
|
||||||
|
String? get homePage; String? get privacyPolicy; String? get termsOfService;
|
||||||
|
/// Create a copy of CustomAppLinks
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppLinksCopyWith<CustomAppLinks> get copyWith => _$CustomAppLinksCopyWithImpl<CustomAppLinks>(this as CustomAppLinks, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this CustomAppLinks to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppLinks&&(identical(other.homePage, homePage) || other.homePage == homePage)&&(identical(other.privacyPolicy, privacyPolicy) || other.privacyPolicy == privacyPolicy)&&(identical(other.termsOfService, termsOfService) || other.termsOfService == termsOfService));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,homePage,privacyPolicy,termsOfService);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomAppLinks(homePage: $homePage, privacyPolicy: $privacyPolicy, termsOfService: $termsOfService)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $CustomAppLinksCopyWith<$Res> {
|
||||||
|
factory $CustomAppLinksCopyWith(CustomAppLinks value, $Res Function(CustomAppLinks) _then) = _$CustomAppLinksCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String? homePage, String? privacyPolicy, String? termsOfService
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$CustomAppLinksCopyWithImpl<$Res>
|
||||||
|
implements $CustomAppLinksCopyWith<$Res> {
|
||||||
|
_$CustomAppLinksCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final CustomAppLinks _self;
|
||||||
|
final $Res Function(CustomAppLinks) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppLinks
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? homePage = freezed,Object? privacyPolicy = freezed,Object? termsOfService = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
homePage: freezed == homePage ? _self.homePage : homePage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,privacyPolicy: freezed == privacyPolicy ? _self.privacyPolicy : privacyPolicy // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,termsOfService: freezed == termsOfService ? _self.termsOfService : termsOfService // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _CustomAppLinks implements CustomAppLinks {
|
||||||
|
const _CustomAppLinks({this.homePage, this.privacyPolicy, this.termsOfService});
|
||||||
|
factory _CustomAppLinks.fromJson(Map<String, dynamic> json) => _$CustomAppLinksFromJson(json);
|
||||||
|
|
||||||
|
@override final String? homePage;
|
||||||
|
@override final String? privacyPolicy;
|
||||||
|
@override final String? termsOfService;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppLinks
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$CustomAppLinksCopyWith<_CustomAppLinks> get copyWith => __$CustomAppLinksCopyWithImpl<_CustomAppLinks>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$CustomAppLinksToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppLinks&&(identical(other.homePage, homePage) || other.homePage == homePage)&&(identical(other.privacyPolicy, privacyPolicy) || other.privacyPolicy == privacyPolicy)&&(identical(other.termsOfService, termsOfService) || other.termsOfService == termsOfService));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,homePage,privacyPolicy,termsOfService);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomAppLinks(homePage: $homePage, privacyPolicy: $privacyPolicy, termsOfService: $termsOfService)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$CustomAppLinksCopyWith<$Res> implements $CustomAppLinksCopyWith<$Res> {
|
||||||
|
factory _$CustomAppLinksCopyWith(_CustomAppLinks value, $Res Function(_CustomAppLinks) _then) = __$CustomAppLinksCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String? homePage, String? privacyPolicy, String? termsOfService
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$CustomAppLinksCopyWithImpl<$Res>
|
||||||
|
implements _$CustomAppLinksCopyWith<$Res> {
|
||||||
|
__$CustomAppLinksCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _CustomAppLinks _self;
|
||||||
|
final $Res Function(_CustomAppLinks) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppLinks
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? homePage = freezed,Object? privacyPolicy = freezed,Object? termsOfService = freezed,}) {
|
||||||
|
return _then(_CustomAppLinks(
|
||||||
|
homePage: freezed == homePage ? _self.homePage : homePage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,privacyPolicy: freezed == privacyPolicy ? _self.privacyPolicy : privacyPolicy // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,termsOfService: freezed == termsOfService ? _self.termsOfService : termsOfService // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$CustomAppOauthConfig {
|
||||||
|
|
||||||
|
String? get clientUri; List<String> get redirectUris; List<String>? get postLogoutRedirectUris; List<String> get allowedScopes; List<String> get allowedGrantTypes; bool get requirePkce; bool get allowOfflineAccess;
|
||||||
|
/// Create a copy of CustomAppOauthConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppOauthConfigCopyWith<CustomAppOauthConfig> get copyWith => _$CustomAppOauthConfigCopyWithImpl<CustomAppOauthConfig>(this as CustomAppOauthConfig, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this CustomAppOauthConfig to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppOauthConfig&&(identical(other.clientUri, clientUri) || other.clientUri == clientUri)&&const DeepCollectionEquality().equals(other.redirectUris, redirectUris)&&const DeepCollectionEquality().equals(other.postLogoutRedirectUris, postLogoutRedirectUris)&&const DeepCollectionEquality().equals(other.allowedScopes, allowedScopes)&&const DeepCollectionEquality().equals(other.allowedGrantTypes, allowedGrantTypes)&&(identical(other.requirePkce, requirePkce) || other.requirePkce == requirePkce)&&(identical(other.allowOfflineAccess, allowOfflineAccess) || other.allowOfflineAccess == allowOfflineAccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,clientUri,const DeepCollectionEquality().hash(redirectUris),const DeepCollectionEquality().hash(postLogoutRedirectUris),const DeepCollectionEquality().hash(allowedScopes),const DeepCollectionEquality().hash(allowedGrantTypes),requirePkce,allowOfflineAccess);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomAppOauthConfig(clientUri: $clientUri, redirectUris: $redirectUris, postLogoutRedirectUris: $postLogoutRedirectUris, allowedScopes: $allowedScopes, allowedGrantTypes: $allowedGrantTypes, requirePkce: $requirePkce, allowOfflineAccess: $allowOfflineAccess)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $CustomAppOauthConfigCopyWith<$Res> {
|
||||||
|
factory $CustomAppOauthConfigCopyWith(CustomAppOauthConfig value, $Res Function(CustomAppOauthConfig) _then) = _$CustomAppOauthConfigCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String? clientUri, List<String> redirectUris, List<String>? postLogoutRedirectUris, List<String> allowedScopes, List<String> allowedGrantTypes, bool requirePkce, bool allowOfflineAccess
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$CustomAppOauthConfigCopyWithImpl<$Res>
|
||||||
|
implements $CustomAppOauthConfigCopyWith<$Res> {
|
||||||
|
_$CustomAppOauthConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final CustomAppOauthConfig _self;
|
||||||
|
final $Res Function(CustomAppOauthConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppOauthConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? clientUri = freezed,Object? redirectUris = null,Object? postLogoutRedirectUris = freezed,Object? allowedScopes = null,Object? allowedGrantTypes = null,Object? requirePkce = null,Object? allowOfflineAccess = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
clientUri: freezed == clientUri ? _self.clientUri : clientUri // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,redirectUris: null == redirectUris ? _self.redirectUris : redirectUris // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,postLogoutRedirectUris: freezed == postLogoutRedirectUris ? _self.postLogoutRedirectUris : postLogoutRedirectUris // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,allowedScopes: null == allowedScopes ? _self.allowedScopes : allowedScopes // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,allowedGrantTypes: null == allowedGrantTypes ? _self.allowedGrantTypes : allowedGrantTypes // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,requirePkce: null == requirePkce ? _self.requirePkce : requirePkce // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,allowOfflineAccess: null == allowOfflineAccess ? _self.allowOfflineAccess : allowOfflineAccess // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _CustomAppOauthConfig implements CustomAppOauthConfig {
|
||||||
|
const _CustomAppOauthConfig({this.clientUri, final List<String> redirectUris = const [], final List<String>? postLogoutRedirectUris, final List<String> allowedScopes = const ['openid', 'profile', 'email'], final List<String> allowedGrantTypes = const ['authorization_code', 'refresh_token'], this.requirePkce = true, this.allowOfflineAccess = false}): _redirectUris = redirectUris,_postLogoutRedirectUris = postLogoutRedirectUris,_allowedScopes = allowedScopes,_allowedGrantTypes = allowedGrantTypes;
|
||||||
|
factory _CustomAppOauthConfig.fromJson(Map<String, dynamic> json) => _$CustomAppOauthConfigFromJson(json);
|
||||||
|
|
||||||
|
@override final String? clientUri;
|
||||||
|
final List<String> _redirectUris;
|
||||||
|
@override@JsonKey() List<String> get redirectUris {
|
||||||
|
if (_redirectUris is EqualUnmodifiableListView) return _redirectUris;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_redirectUris);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String>? _postLogoutRedirectUris;
|
||||||
|
@override List<String>? get postLogoutRedirectUris {
|
||||||
|
final value = _postLogoutRedirectUris;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_postLogoutRedirectUris is EqualUnmodifiableListView) return _postLogoutRedirectUris;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> _allowedScopes;
|
||||||
|
@override@JsonKey() List<String> get allowedScopes {
|
||||||
|
if (_allowedScopes is EqualUnmodifiableListView) return _allowedScopes;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_allowedScopes);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> _allowedGrantTypes;
|
||||||
|
@override@JsonKey() List<String> get allowedGrantTypes {
|
||||||
|
if (_allowedGrantTypes is EqualUnmodifiableListView) return _allowedGrantTypes;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_allowedGrantTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override@JsonKey() final bool requirePkce;
|
||||||
|
@override@JsonKey() final bool allowOfflineAccess;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppOauthConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$CustomAppOauthConfigCopyWith<_CustomAppOauthConfig> get copyWith => __$CustomAppOauthConfigCopyWithImpl<_CustomAppOauthConfig>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$CustomAppOauthConfigToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppOauthConfig&&(identical(other.clientUri, clientUri) || other.clientUri == clientUri)&&const DeepCollectionEquality().equals(other._redirectUris, _redirectUris)&&const DeepCollectionEquality().equals(other._postLogoutRedirectUris, _postLogoutRedirectUris)&&const DeepCollectionEquality().equals(other._allowedScopes, _allowedScopes)&&const DeepCollectionEquality().equals(other._allowedGrantTypes, _allowedGrantTypes)&&(identical(other.requirePkce, requirePkce) || other.requirePkce == requirePkce)&&(identical(other.allowOfflineAccess, allowOfflineAccess) || other.allowOfflineAccess == allowOfflineAccess));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,clientUri,const DeepCollectionEquality().hash(_redirectUris),const DeepCollectionEquality().hash(_postLogoutRedirectUris),const DeepCollectionEquality().hash(_allowedScopes),const DeepCollectionEquality().hash(_allowedGrantTypes),requirePkce,allowOfflineAccess);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomAppOauthConfig(clientUri: $clientUri, redirectUris: $redirectUris, postLogoutRedirectUris: $postLogoutRedirectUris, allowedScopes: $allowedScopes, allowedGrantTypes: $allowedGrantTypes, requirePkce: $requirePkce, allowOfflineAccess: $allowOfflineAccess)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$CustomAppOauthConfigCopyWith<$Res> implements $CustomAppOauthConfigCopyWith<$Res> {
|
||||||
|
factory _$CustomAppOauthConfigCopyWith(_CustomAppOauthConfig value, $Res Function(_CustomAppOauthConfig) _then) = __$CustomAppOauthConfigCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String? clientUri, List<String> redirectUris, List<String>? postLogoutRedirectUris, List<String> allowedScopes, List<String> allowedGrantTypes, bool requirePkce, bool allowOfflineAccess
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$CustomAppOauthConfigCopyWithImpl<$Res>
|
||||||
|
implements _$CustomAppOauthConfigCopyWith<$Res> {
|
||||||
|
__$CustomAppOauthConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _CustomAppOauthConfig _self;
|
||||||
|
final $Res Function(_CustomAppOauthConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppOauthConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? clientUri = freezed,Object? redirectUris = null,Object? postLogoutRedirectUris = freezed,Object? allowedScopes = null,Object? allowedGrantTypes = null,Object? requirePkce = null,Object? allowOfflineAccess = null,}) {
|
||||||
|
return _then(_CustomAppOauthConfig(
|
||||||
|
clientUri: freezed == clientUri ? _self.clientUri : clientUri // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,redirectUris: null == redirectUris ? _self._redirectUris : redirectUris // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,postLogoutRedirectUris: freezed == postLogoutRedirectUris ? _self._postLogoutRedirectUris : postLogoutRedirectUris // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>?,allowedScopes: null == allowedScopes ? _self._allowedScopes : allowedScopes // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,allowedGrantTypes: null == allowedGrantTypes ? _self._allowedGrantTypes : allowedGrantTypes // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<String>,requirePkce: null == requirePkce ? _self.requirePkce : requirePkce // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,allowOfflineAccess: null == allowOfflineAccess ? _self.allowOfflineAccess : allowOfflineAccess // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$CustomAppSecret {
|
||||||
|
|
||||||
|
String get id; String get secret; String? get description; DateTime? get expiredAt; bool get isOidc; String get appId;
|
||||||
|
/// Create a copy of CustomAppSecret
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$CustomAppSecretCopyWith<CustomAppSecret> get copyWith => _$CustomAppSecretCopyWithImpl<CustomAppSecret>(this as CustomAppSecret, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this CustomAppSecret to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is CustomAppSecret&&(identical(other.id, id) || other.id == id)&&(identical(other.secret, secret) || other.secret == secret)&&(identical(other.description, description) || other.description == description)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.isOidc, isOidc) || other.isOidc == isOidc)&&(identical(other.appId, appId) || other.appId == appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,secret,description,expiredAt,isOidc,appId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomAppSecret(id: $id, secret: $secret, description: $description, expiredAt: $expiredAt, isOidc: $isOidc, appId: $appId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $CustomAppSecretCopyWith<$Res> {
|
||||||
|
factory $CustomAppSecretCopyWith(CustomAppSecret value, $Res Function(CustomAppSecret) _then) = _$CustomAppSecretCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String secret, String? description, DateTime? expiredAt, bool isOidc, String appId
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$CustomAppSecretCopyWithImpl<$Res>
|
||||||
|
implements $CustomAppSecretCopyWith<$Res> {
|
||||||
|
_$CustomAppSecretCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final CustomAppSecret _self;
|
||||||
|
final $Res Function(CustomAppSecret) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppSecret
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? secret = null,Object? description = freezed,Object? expiredAt = freezed,Object? isOidc = null,Object? appId = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,secret: null == secret ? _self.secret : secret // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,isOidc: null == isOidc ? _self.isOidc : isOidc // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,appId: null == appId ? _self.appId : appId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _CustomAppSecret implements CustomAppSecret {
|
||||||
|
const _CustomAppSecret({this.id = '', this.secret = '', this.description, this.expiredAt, this.isOidc = false, this.appId = ''});
|
||||||
|
factory _CustomAppSecret.fromJson(Map<String, dynamic> json) => _$CustomAppSecretFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey() final String id;
|
||||||
|
@override@JsonKey() final String secret;
|
||||||
|
@override final String? description;
|
||||||
|
@override final DateTime? expiredAt;
|
||||||
|
@override@JsonKey() final bool isOidc;
|
||||||
|
@override@JsonKey() final String appId;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppSecret
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$CustomAppSecretCopyWith<_CustomAppSecret> get copyWith => __$CustomAppSecretCopyWithImpl<_CustomAppSecret>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$CustomAppSecretToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CustomAppSecret&&(identical(other.id, id) || other.id == id)&&(identical(other.secret, secret) || other.secret == secret)&&(identical(other.description, description) || other.description == description)&&(identical(other.expiredAt, expiredAt) || other.expiredAt == expiredAt)&&(identical(other.isOidc, isOidc) || other.isOidc == isOidc)&&(identical(other.appId, appId) || other.appId == appId));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,secret,description,expiredAt,isOidc,appId);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CustomAppSecret(id: $id, secret: $secret, description: $description, expiredAt: $expiredAt, isOidc: $isOidc, appId: $appId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$CustomAppSecretCopyWith<$Res> implements $CustomAppSecretCopyWith<$Res> {
|
||||||
|
factory _$CustomAppSecretCopyWith(_CustomAppSecret value, $Res Function(_CustomAppSecret) _then) = __$CustomAppSecretCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String secret, String? description, DateTime? expiredAt, bool isOidc, String appId
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$CustomAppSecretCopyWithImpl<$Res>
|
||||||
|
implements _$CustomAppSecretCopyWith<$Res> {
|
||||||
|
__$CustomAppSecretCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _CustomAppSecret _self;
|
||||||
|
final $Res Function(_CustomAppSecret) _then;
|
||||||
|
|
||||||
|
/// Create a copy of CustomAppSecret
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? secret = null,Object? description = freezed,Object? expiredAt = freezed,Object? isOidc = null,Object? appId = null,}) {
|
||||||
|
return _then(_CustomAppSecret(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,secret: null == secret ? _self.secret : secret // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,description: freezed == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,expiredAt: freezed == expiredAt ? _self.expiredAt : expiredAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,isOidc: null == isOidc ? _self.isOidc : isOidc // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,appId: null == appId ? _self.appId : appId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
137
lib/models/custom_app.g.dart
Normal file
137
lib/models/custom_app.g.dart
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'custom_app.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_CustomApp _$CustomAppFromJson(Map<String, dynamic> json) => _CustomApp(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
slug: json['slug'] as String? ?? '',
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
|
description: json['description'] as String?,
|
||||||
|
status: (json['status'] as num?)?.toInt() ?? 0,
|
||||||
|
picture:
|
||||||
|
json['picture'] == null
|
||||||
|
? null
|
||||||
|
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
|
||||||
|
background:
|
||||||
|
json['background'] == null
|
||||||
|
? null
|
||||||
|
: SnCloudFile.fromJson(json['background'] as Map<String, dynamic>),
|
||||||
|
verification:
|
||||||
|
json['verification'] == null
|
||||||
|
? null
|
||||||
|
: SnVerificationMark.fromJson(
|
||||||
|
json['verification'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
oauthConfig:
|
||||||
|
json['oauth_config'] == null
|
||||||
|
? null
|
||||||
|
: CustomAppOauthConfig.fromJson(
|
||||||
|
json['oauth_config'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
links:
|
||||||
|
json['links'] == null
|
||||||
|
? null
|
||||||
|
: CustomAppLinks.fromJson(json['links'] as Map<String, dynamic>),
|
||||||
|
secrets:
|
||||||
|
(json['secrets'] as List<dynamic>?)
|
||||||
|
?.map((e) => CustomAppSecret.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
publisherId: json['publisher_id'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CustomAppToJson(_CustomApp instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'slug': instance.slug,
|
||||||
|
'name': instance.name,
|
||||||
|
'description': instance.description,
|
||||||
|
'status': instance.status,
|
||||||
|
'picture': instance.picture?.toJson(),
|
||||||
|
'background': instance.background?.toJson(),
|
||||||
|
'verification': instance.verification?.toJson(),
|
||||||
|
'oauth_config': instance.oauthConfig?.toJson(),
|
||||||
|
'links': instance.links?.toJson(),
|
||||||
|
'secrets': instance.secrets.map((e) => e.toJson()).toList(),
|
||||||
|
'publisher_id': instance.publisherId,
|
||||||
|
};
|
||||||
|
|
||||||
|
_CustomAppLinks _$CustomAppLinksFromJson(Map<String, dynamic> json) =>
|
||||||
|
_CustomAppLinks(
|
||||||
|
homePage: json['home_page'] as String?,
|
||||||
|
privacyPolicy: json['privacy_policy'] as String?,
|
||||||
|
termsOfService: json['terms_of_service'] as String?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CustomAppLinksToJson(_CustomAppLinks instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'home_page': instance.homePage,
|
||||||
|
'privacy_policy': instance.privacyPolicy,
|
||||||
|
'terms_of_service': instance.termsOfService,
|
||||||
|
};
|
||||||
|
|
||||||
|
_CustomAppOauthConfig _$CustomAppOauthConfigFromJson(
|
||||||
|
Map<String, dynamic> json,
|
||||||
|
) => _CustomAppOauthConfig(
|
||||||
|
clientUri: json['client_uri'] as String?,
|
||||||
|
redirectUris:
|
||||||
|
(json['redirect_uris'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
postLogoutRedirectUris:
|
||||||
|
(json['post_logout_redirect_uris'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList(),
|
||||||
|
allowedScopes:
|
||||||
|
(json['allowed_scopes'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList() ??
|
||||||
|
const ['openid', 'profile', 'email'],
|
||||||
|
allowedGrantTypes:
|
||||||
|
(json['allowed_grant_types'] as List<dynamic>?)
|
||||||
|
?.map((e) => e as String)
|
||||||
|
.toList() ??
|
||||||
|
const ['authorization_code', 'refresh_token'],
|
||||||
|
requirePkce: json['require_pkce'] as bool? ?? true,
|
||||||
|
allowOfflineAccess: json['allow_offline_access'] as bool? ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CustomAppOauthConfigToJson(
|
||||||
|
_CustomAppOauthConfig instance,
|
||||||
|
) => <String, dynamic>{
|
||||||
|
'client_uri': instance.clientUri,
|
||||||
|
'redirect_uris': instance.redirectUris,
|
||||||
|
'post_logout_redirect_uris': instance.postLogoutRedirectUris,
|
||||||
|
'allowed_scopes': instance.allowedScopes,
|
||||||
|
'allowed_grant_types': instance.allowedGrantTypes,
|
||||||
|
'require_pkce': instance.requirePkce,
|
||||||
|
'allow_offline_access': instance.allowOfflineAccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
_CustomAppSecret _$CustomAppSecretFromJson(Map<String, dynamic> json) =>
|
||||||
|
_CustomAppSecret(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
secret: json['secret'] as String? ?? '',
|
||||||
|
description: json['description'] as String?,
|
||||||
|
expiredAt:
|
||||||
|
json['expired_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['expired_at'] as String),
|
||||||
|
isOidc: json['is_oidc'] as bool? ?? false,
|
||||||
|
appId: json['app_id'] as String? ?? '',
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$CustomAppSecretToJson(_CustomAppSecret instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'secret': instance.secret,
|
||||||
|
'description': instance.description,
|
||||||
|
'expired_at': instance.expiredAt?.toIso8601String(),
|
||||||
|
'is_oidc': instance.isOidc,
|
||||||
|
'app_id': instance.appId,
|
||||||
|
};
|
||||||
14
lib/models/developer.dart
Normal file
14
lib/models/developer.dart
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'developer.freezed.dart';
|
||||||
|
part 'developer.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class DeveloperStats with _$DeveloperStats {
|
||||||
|
const factory DeveloperStats({
|
||||||
|
@Default(0) int totalCustomApps,
|
||||||
|
}) = _DeveloperStats;
|
||||||
|
|
||||||
|
factory DeveloperStats.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DeveloperStatsFromJson(json);
|
||||||
|
}
|
||||||
148
lib/models/developer.freezed.dart
Normal file
148
lib/models/developer.freezed.dart
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'developer.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$DeveloperStats {
|
||||||
|
|
||||||
|
int get totalCustomApps;
|
||||||
|
/// Create a copy of DeveloperStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$DeveloperStatsCopyWith<DeveloperStats> get copyWith => _$DeveloperStatsCopyWithImpl<DeveloperStats>(this as DeveloperStats, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this DeveloperStats to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is DeveloperStats&&(identical(other.totalCustomApps, totalCustomApps) || other.totalCustomApps == totalCustomApps));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,totalCustomApps);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DeveloperStats(totalCustomApps: $totalCustomApps)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $DeveloperStatsCopyWith<$Res> {
|
||||||
|
factory $DeveloperStatsCopyWith(DeveloperStats value, $Res Function(DeveloperStats) _then) = _$DeveloperStatsCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
int totalCustomApps
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$DeveloperStatsCopyWithImpl<$Res>
|
||||||
|
implements $DeveloperStatsCopyWith<$Res> {
|
||||||
|
_$DeveloperStatsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final DeveloperStats _self;
|
||||||
|
final $Res Function(DeveloperStats) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DeveloperStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? totalCustomApps = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
totalCustomApps: null == totalCustomApps ? _self.totalCustomApps : totalCustomApps // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _DeveloperStats implements DeveloperStats {
|
||||||
|
const _DeveloperStats({this.totalCustomApps = 0});
|
||||||
|
factory _DeveloperStats.fromJson(Map<String, dynamic> json) => _$DeveloperStatsFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey() final int totalCustomApps;
|
||||||
|
|
||||||
|
/// Create a copy of DeveloperStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$DeveloperStatsCopyWith<_DeveloperStats> get copyWith => __$DeveloperStatsCopyWithImpl<_DeveloperStats>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$DeveloperStatsToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DeveloperStats&&(identical(other.totalCustomApps, totalCustomApps) || other.totalCustomApps == totalCustomApps));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,totalCustomApps);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'DeveloperStats(totalCustomApps: $totalCustomApps)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$DeveloperStatsCopyWith<$Res> implements $DeveloperStatsCopyWith<$Res> {
|
||||||
|
factory _$DeveloperStatsCopyWith(_DeveloperStats value, $Res Function(_DeveloperStats) _then) = __$DeveloperStatsCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
int totalCustomApps
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$DeveloperStatsCopyWithImpl<$Res>
|
||||||
|
implements _$DeveloperStatsCopyWith<$Res> {
|
||||||
|
__$DeveloperStatsCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _DeveloperStats _self;
|
||||||
|
final $Res Function(_DeveloperStats) _then;
|
||||||
|
|
||||||
|
/// Create a copy of DeveloperStats
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? totalCustomApps = null,}) {
|
||||||
|
return _then(_DeveloperStats(
|
||||||
|
totalCustomApps: null == totalCustomApps ? _self.totalCustomApps : totalCustomApps // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
15
lib/models/developer.g.dart
Normal file
15
lib/models/developer.g.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'developer.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_DeveloperStats _$DeveloperStatsFromJson(Map<String, dynamic> json) =>
|
||||||
|
_DeveloperStats(
|
||||||
|
totalCustomApps: (json['total_custom_apps'] as num?)?.toInt() ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DeveloperStatsToJson(_DeveloperStats instance) =>
|
||||||
|
<String, dynamic>{'total_custom_apps': instance.totalCustomApps};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/user.dart';
|
import 'package:island/models/post_category.dart';
|
||||||
|
import 'package:island/models/post_tag.dart';
|
||||||
|
import 'package:island/models/publisher.dart';
|
||||||
|
|
||||||
part 'post.freezed.dart';
|
part 'post.freezed.dart';
|
||||||
part 'post.g.dart';
|
part 'post.g.dart';
|
||||||
@@ -30,11 +32,11 @@ sealed class SnPost with _$SnPost {
|
|||||||
String? forwardedPostId,
|
String? forwardedPostId,
|
||||||
SnPost? forwardedPost,
|
SnPost? forwardedPost,
|
||||||
@Default([]) List<SnCloudFile> attachments,
|
@Default([]) List<SnCloudFile> attachments,
|
||||||
@Default(SnPublisher()) SnPublisher publisher,
|
required SnPublisher publisher,
|
||||||
@Default({}) Map<String, int> reactionsCount,
|
@Default({}) Map<String, int> reactionsCount,
|
||||||
@Default([]) List<dynamic> reactions,
|
@Default([]) List<dynamic> reactions,
|
||||||
@Default([]) List<dynamic> tags,
|
@Default([]) List<PostTag> tags,
|
||||||
@Default([]) List<dynamic> categories,
|
@Default([]) List<PostCategory> categories,
|
||||||
@Default([]) List<dynamic> collections,
|
@Default([]) List<dynamic> collections,
|
||||||
@Default(null) DateTime? createdAt,
|
@Default(null) DateTime? createdAt,
|
||||||
@Default(null) DateTime? updatedAt,
|
@Default(null) DateTime? updatedAt,
|
||||||
@@ -45,29 +47,6 @@ sealed class SnPost with _$SnPost {
|
|||||||
factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
|
||||||
sealed class SnPublisher with _$SnPublisher {
|
|
||||||
const factory SnPublisher({
|
|
||||||
@Default('') String id,
|
|
||||||
@Default(0) int type,
|
|
||||||
@Default('') String name,
|
|
||||||
@Default('') String nick,
|
|
||||||
@Default('') String bio,
|
|
||||||
SnCloudFile? picture,
|
|
||||||
SnCloudFile? background,
|
|
||||||
SnAccount? account,
|
|
||||||
String? accountId,
|
|
||||||
@Default(null) DateTime? createdAt,
|
|
||||||
@Default(null) DateTime? updatedAt,
|
|
||||||
DateTime? deletedAt,
|
|
||||||
String? realmId,
|
|
||||||
SnVerificationMark? verification,
|
|
||||||
}) = _SnPublisher;
|
|
||||||
|
|
||||||
factory SnPublisher.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$SnPublisherFromJson(json);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class SnPublisherStats with _$SnPublisherStats {
|
sealed class SnPublisherStats with _$SnPublisherStats {
|
||||||
const factory SnPublisherStats({
|
const factory SnPublisherStats({
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPost {
|
mixin _$SnPost {
|
||||||
|
|
||||||
String get id; String? get title; String? get description; String? get language; DateTime? get editedAt; DateTime? get publishedAt; int get visibility; String? get content; int get type; Map<String, dynamic>? get meta; int get viewsUnique; int get viewsTotal; int get upvotes; int get downvotes; int get repliesCount; String? get threadedPostId; SnPost? get threadedPost; String? get repliedPostId; SnPost? get repliedPost; String? get forwardedPostId; SnPost? get forwardedPost; List<SnCloudFile> get attachments; SnPublisher get publisher; Map<String, int> get reactionsCount; List<dynamic> get reactions; List<dynamic> get tags; List<dynamic> get categories; List<dynamic> get collections; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; bool get isTruncated;
|
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;
|
||||||
/// Create a copy of SnPost
|
/// Create a copy of SnPost
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
@@ -49,7 +49,7 @@ abstract mixin class $SnPostCopyWith<$Res> {
|
|||||||
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
factory $SnPostCopyWith(SnPost value, $Res Function(SnPost) _then) = _$SnPostCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -94,8 +94,8 @@ as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher
|
|||||||
as SnPublisher,reactionsCount: null == reactionsCount ? _self.reactionsCount : reactionsCount // 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>,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<dynamic>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
as List<PostTag>,categories: null == categories ? _self.categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,collections: null == collections ? _self.collections : collections // ignore: cast_nullable_to_non_nullable
|
as List<PostCategory>,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 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?,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
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -156,7 +156,7 @@ $SnPublisherCopyWith<$Res> get publisher {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnPost implements SnPost {
|
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 [], this.publisher = const SnPublisher(), final Map<String, int> reactionsCount = const {}, final List<dynamic> reactions = const [], final List<dynamic> tags = const [], final List<dynamic> 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 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;
|
||||||
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
factory _SnPost.fromJson(Map<String, dynamic> json) => _$SnPostFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@@ -195,7 +195,7 @@ class _SnPost implements SnPost {
|
|||||||
return EqualUnmodifiableListView(_attachments);
|
return EqualUnmodifiableListView(_attachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override@JsonKey() final SnPublisher publisher;
|
@override final SnPublisher publisher;
|
||||||
final Map<String, int> _reactionsCount;
|
final Map<String, int> _reactionsCount;
|
||||||
@override@JsonKey() Map<String, int> get reactionsCount {
|
@override@JsonKey() Map<String, int> get reactionsCount {
|
||||||
if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount;
|
if (_reactionsCount is EqualUnmodifiableMapView) return _reactionsCount;
|
||||||
@@ -210,15 +210,15 @@ class _SnPost implements SnPost {
|
|||||||
return EqualUnmodifiableListView(_reactions);
|
return EqualUnmodifiableListView(_reactions);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _tags;
|
final List<PostTag> _tags;
|
||||||
@override@JsonKey() List<dynamic> get tags {
|
@override@JsonKey() List<PostTag> get tags {
|
||||||
if (_tags is EqualUnmodifiableListView) return _tags;
|
if (_tags is EqualUnmodifiableListView) return _tags;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_tags);
|
return EqualUnmodifiableListView(_tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> _categories;
|
final List<PostCategory> _categories;
|
||||||
@override@JsonKey() List<dynamic> get categories {
|
@override@JsonKey() List<PostCategory> get categories {
|
||||||
if (_categories is EqualUnmodifiableListView) return _categories;
|
if (_categories is EqualUnmodifiableListView) return _categories;
|
||||||
// ignore: implicit_dynamic_type
|
// ignore: implicit_dynamic_type
|
||||||
return EqualUnmodifiableListView(_categories);
|
return EqualUnmodifiableListView(_categories);
|
||||||
@@ -269,7 +269,7 @@ abstract mixin class _$SnPostCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
|||||||
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
factory _$SnPostCopyWith(_SnPost value, $Res Function(_SnPost) _then) = __$SnPostCopyWithImpl;
|
||||||
@override @useResult
|
@override @useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String id, String? title, String? description, String? language, DateTime? editedAt, DateTime? publishedAt, int visibility, String? content, int type, Map<String, dynamic>? meta, int viewsUnique, int viewsTotal, int upvotes, int downvotes, int repliesCount, String? threadedPostId, SnPost? threadedPost, String? repliedPostId, SnPost? repliedPost, String? forwardedPostId, SnPost? forwardedPost, List<SnCloudFile> attachments, SnPublisher publisher, Map<String, int> reactionsCount, List<dynamic> reactions, List<dynamic> tags, List<dynamic> categories, List<dynamic> collections, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, bool isTruncated
|
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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@@ -314,8 +314,8 @@ as List<SnCloudFile>,publisher: null == publisher ? _self.publisher : publisher
|
|||||||
as SnPublisher,reactionsCount: null == reactionsCount ? _self._reactionsCount : reactionsCount // 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>,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<dynamic>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
as List<PostTag>,categories: null == categories ? _self._categories : categories // ignore: cast_nullable_to_non_nullable
|
||||||
as List<dynamic>,collections: null == collections ? _self._collections : collections // ignore: cast_nullable_to_non_nullable
|
as List<PostCategory>,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 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?,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
|
as DateTime?,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
@@ -373,274 +373,6 @@ $SnPublisherCopyWith<$Res> get publisher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SnPublisher {
|
|
||||||
|
|
||||||
String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification;
|
|
||||||
/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPublisher>(this as SnPublisher, _$identity);
|
|
||||||
|
|
||||||
/// Serializes this SnPublisher to a JSON map.
|
|
||||||
Map<String, dynamic> toJson();
|
|
||||||
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class $SnPublisherCopyWith<$Res> {
|
|
||||||
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnAccountCopyWith<$Res>? get account;$SnVerificationMarkCopyWith<$Res>? get verification;
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class _$SnPublisherCopyWithImpl<$Res>
|
|
||||||
implements $SnPublisherCopyWith<$Res> {
|
|
||||||
_$SnPublisherCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final SnPublisher _self;
|
|
||||||
final $Res Function(SnPublisher) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
|
||||||
return _then(_self.copyWith(
|
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,createdAt: 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
|
|
||||||
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnVerificationMark?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnCloudFileCopyWith<$Res>? get picture {
|
|
||||||
if (_self.picture == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
|
||||||
return _then(_self.copyWith(picture: value));
|
|
||||||
});
|
|
||||||
}/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnCloudFileCopyWith<$Res>? get background {
|
|
||||||
if (_self.background == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
|
||||||
return _then(_self.copyWith(background: value));
|
|
||||||
});
|
|
||||||
}/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnAccountCopyWith<$Res>? get account {
|
|
||||||
if (_self.account == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
|
||||||
return _then(_self.copyWith(account: value));
|
|
||||||
});
|
|
||||||
}/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnVerificationMarkCopyWith<$Res>? get verification {
|
|
||||||
if (_self.verification == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
|
|
||||||
return _then(_self.copyWith(verification: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
@JsonSerializable()
|
|
||||||
|
|
||||||
class _SnPublisher implements SnPublisher {
|
|
||||||
const _SnPublisher({this.id = '', this.type = 0, this.name = '', this.nick = '', this.bio = '', this.picture, this.background, this.account, this.accountId, this.createdAt = null, this.updatedAt = null, this.deletedAt, this.realmId, this.verification});
|
|
||||||
factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json);
|
|
||||||
|
|
||||||
@override@JsonKey() final String id;
|
|
||||||
@override@JsonKey() final int type;
|
|
||||||
@override@JsonKey() final String name;
|
|
||||||
@override@JsonKey() final String nick;
|
|
||||||
@override@JsonKey() final String bio;
|
|
||||||
@override final SnCloudFile? picture;
|
|
||||||
@override final SnCloudFile? background;
|
|
||||||
@override final SnAccount? account;
|
|
||||||
@override final String? accountId;
|
|
||||||
@override@JsonKey() final DateTime? createdAt;
|
|
||||||
@override@JsonKey() final DateTime? updatedAt;
|
|
||||||
@override final DateTime? deletedAt;
|
|
||||||
@override final String? realmId;
|
|
||||||
@override final SnVerificationMark? verification;
|
|
||||||
|
|
||||||
/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$SnPublisherCopyWith<_SnPublisher> get copyWith => __$SnPublisherCopyWithImpl<_SnPublisher>(this, _$identity);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
return _$SnPublisherToJson(this, );
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification));
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith<$Res> {
|
|
||||||
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
|
|
||||||
@override @useResult
|
|
||||||
$Res call({
|
|
||||||
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnAccountCopyWith<$Res>? get account;@override $SnVerificationMarkCopyWith<$Res>? get verification;
|
|
||||||
|
|
||||||
}
|
|
||||||
/// @nodoc
|
|
||||||
class __$SnPublisherCopyWithImpl<$Res>
|
|
||||||
implements _$SnPublisherCopyWith<$Res> {
|
|
||||||
__$SnPublisherCopyWithImpl(this._self, this._then);
|
|
||||||
|
|
||||||
final _SnPublisher _self;
|
|
||||||
final $Res Function(_SnPublisher) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
|
||||||
return _then(_SnPublisher(
|
|
||||||
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
|
||||||
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,createdAt: 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
|
|
||||||
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SnVerificationMark?,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnCloudFileCopyWith<$Res>? get picture {
|
|
||||||
if (_self.picture == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
|
||||||
return _then(_self.copyWith(picture: value));
|
|
||||||
});
|
|
||||||
}/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnCloudFileCopyWith<$Res>? get background {
|
|
||||||
if (_self.background == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
|
||||||
return _then(_self.copyWith(background: value));
|
|
||||||
});
|
|
||||||
}/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnAccountCopyWith<$Res>? get account {
|
|
||||||
if (_self.account == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
|
||||||
return _then(_self.copyWith(account: value));
|
|
||||||
});
|
|
||||||
}/// Create a copy of SnPublisher
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
$SnVerificationMarkCopyWith<$Res>? get verification {
|
|
||||||
if (_self.verification == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
|
|
||||||
return _then(_self.copyWith(verification: value));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
mixin _$SnPublisherStats {
|
mixin _$SnPublisherStats {
|
||||||
|
|
||||||
|
|||||||
@@ -48,18 +48,23 @@ _SnPost _$SnPostFromJson(Map<String, dynamic> json) => _SnPost(
|
|||||||
?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
|
?.map((e) => SnCloudFile.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
publisher:
|
publisher: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||||
json['publisher'] == null
|
|
||||||
? const SnPublisher()
|
|
||||||
: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
|
||||||
reactionsCount:
|
reactionsCount:
|
||||||
(json['reactions_count'] as Map<String, dynamic>?)?.map(
|
(json['reactions_count'] as Map<String, dynamic>?)?.map(
|
||||||
(k, e) => MapEntry(k, (e as num).toInt()),
|
(k, e) => MapEntry(k, (e as num).toInt()),
|
||||||
) ??
|
) ??
|
||||||
const {},
|
const {},
|
||||||
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
reactions: json['reactions'] as List<dynamic>? ?? const [],
|
||||||
tags: json['tags'] as List<dynamic>? ?? const [],
|
tags:
|
||||||
categories: json['categories'] as List<dynamic>? ?? const [],
|
(json['tags'] as List<dynamic>?)
|
||||||
|
?.map((e) => PostTag.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
categories:
|
||||||
|
(json['categories'] as List<dynamic>?)
|
||||||
|
?.map((e) => PostCategory.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
collections: json['collections'] as List<dynamic>? ?? const [],
|
collections: json['collections'] as List<dynamic>? ?? const [],
|
||||||
createdAt:
|
createdAt:
|
||||||
json['created_at'] == null
|
json['created_at'] == null
|
||||||
@@ -102,8 +107,8 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'publisher': instance.publisher.toJson(),
|
'publisher': instance.publisher.toJson(),
|
||||||
'reactions_count': instance.reactionsCount,
|
'reactions_count': instance.reactionsCount,
|
||||||
'reactions': instance.reactions,
|
'reactions': instance.reactions,
|
||||||
'tags': instance.tags,
|
'tags': instance.tags.map((e) => e.toJson()).toList(),
|
||||||
'categories': instance.categories,
|
'categories': instance.categories.map((e) => e.toJson()).toList(),
|
||||||
'collections': instance.collections,
|
'collections': instance.collections,
|
||||||
'created_at': instance.createdAt?.toIso8601String(),
|
'created_at': instance.createdAt?.toIso8601String(),
|
||||||
'updated_at': instance.updatedAt?.toIso8601String(),
|
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||||
@@ -111,64 +116,6 @@ Map<String, dynamic> _$SnPostToJson(_SnPost instance) => <String, dynamic>{
|
|||||||
'is_truncated': instance.isTruncated,
|
'is_truncated': instance.isTruncated,
|
||||||
};
|
};
|
||||||
|
|
||||||
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
|
||||||
id: json['id'] as String? ?? '',
|
|
||||||
type: (json['type'] as num?)?.toInt() ?? 0,
|
|
||||||
name: json['name'] as String? ?? '',
|
|
||||||
nick: json['nick'] as String? ?? '',
|
|
||||||
bio: json['bio'] as String? ?? '',
|
|
||||||
picture:
|
|
||||||
json['picture'] == null
|
|
||||||
? null
|
|
||||||
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
|
|
||||||
background:
|
|
||||||
json['background'] == null
|
|
||||||
? null
|
|
||||||
: SnCloudFile.fromJson(json['background'] as Map<String, dynamic>),
|
|
||||||
account:
|
|
||||||
json['account'] == null
|
|
||||||
? null
|
|
||||||
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
|
||||||
accountId: json['account_id'] as String?,
|
|
||||||
createdAt:
|
|
||||||
json['created_at'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['created_at'] as String),
|
|
||||||
updatedAt:
|
|
||||||
json['updated_at'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['updated_at'] as String),
|
|
||||||
deletedAt:
|
|
||||||
json['deleted_at'] == null
|
|
||||||
? null
|
|
||||||
: DateTime.parse(json['deleted_at'] as String),
|
|
||||||
realmId: json['realm_id'] as String?,
|
|
||||||
verification:
|
|
||||||
json['verification'] == null
|
|
||||||
? null
|
|
||||||
: SnVerificationMark.fromJson(
|
|
||||||
json['verification'] as Map<String, dynamic>,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'type': instance.type,
|
|
||||||
'name': instance.name,
|
|
||||||
'nick': instance.nick,
|
|
||||||
'bio': instance.bio,
|
|
||||||
'picture': instance.picture?.toJson(),
|
|
||||||
'background': instance.background?.toJson(),
|
|
||||||
'account': instance.account?.toJson(),
|
|
||||||
'account_id': instance.accountId,
|
|
||||||
'created_at': instance.createdAt?.toIso8601String(),
|
|
||||||
'updated_at': instance.updatedAt?.toIso8601String(),
|
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
|
||||||
'realm_id': instance.realmId,
|
|
||||||
'verification': instance.verification?.toJson(),
|
|
||||||
};
|
|
||||||
|
|
||||||
_SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) =>
|
_SnPublisherStats _$SnPublisherStatsFromJson(Map<String, dynamic> json) =>
|
||||||
_SnPublisherStats(
|
_SnPublisherStats(
|
||||||
postsCreated: (json['posts_created'] as num).toInt(),
|
postsCreated: (json['posts_created'] as num).toInt(),
|
||||||
|
|||||||
19
lib/models/post_category.dart
Normal file
19
lib/models/post_category.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
|
||||||
|
part 'post_category.freezed.dart';
|
||||||
|
part 'post_category.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class PostCategory with _$PostCategory {
|
||||||
|
const factory PostCategory({
|
||||||
|
required String id,
|
||||||
|
required String slug,
|
||||||
|
String? name,
|
||||||
|
@Default([]) List<SnPost> posts,
|
||||||
|
}) = _PostCategory;
|
||||||
|
|
||||||
|
factory PostCategory.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PostCategoryFromJson(json);
|
||||||
|
}
|
||||||
163
lib/models/post_category.freezed.dart
Normal file
163
lib/models/post_category.freezed.dart
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'post_category.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$PostCategory {
|
||||||
|
|
||||||
|
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||||
|
/// Create a copy of PostCategory
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Serializes this PostCategory 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostCategory(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $PostCategoryCopyWith<$Res> {
|
||||||
|
factory $PostCategoryCopyWith(PostCategory value, $Res Function(PostCategory) _then) = _$PostCategoryCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$PostCategoryCopyWithImpl<$Res>
|
||||||
|
implements $PostCategoryCopyWith<$Res> {
|
||||||
|
_$PostCategoryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final PostCategory _self;
|
||||||
|
final $Res Function(PostCategory) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PostCategory
|
||||||
|
/// 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(
|
||||||
|
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
|
||||||
|
as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPost>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @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);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String slug;
|
||||||
|
@override final String? name;
|
||||||
|
final List<SnPost> _posts;
|
||||||
|
@override@JsonKey() List<SnPost> get posts {
|
||||||
|
if (_posts is EqualUnmodifiableListView) return _posts;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_posts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of PostCategory
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$PostCategoryToJson(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostCategory(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;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$PostCategoryCopyWithImpl<$Res>
|
||||||
|
implements _$PostCategoryCopyWith<$Res> {
|
||||||
|
__$PostCategoryCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _PostCategory _self;
|
||||||
|
final $Res Function(_PostCategory) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PostCategory
|
||||||
|
/// 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(
|
||||||
|
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
|
||||||
|
as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPost>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
27
lib/models/post_category.g.dart
Normal file
27
lib/models/post_category.g.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'post_category.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_PostCategory _$PostCategoryFromJson(Map<String, dynamic> json) =>
|
||||||
|
_PostCategory(
|
||||||
|
id: json['id'] as String,
|
||||||
|
slug: json['slug'] as String,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
posts:
|
||||||
|
(json['posts'] as List<dynamic>?)
|
||||||
|
?.map((e) => SnPost.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$PostCategoryToJson(_PostCategory instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'slug': instance.slug,
|
||||||
|
'name': instance.name,
|
||||||
|
'posts': instance.posts.map((e) => e.toJson()).toList(),
|
||||||
|
};
|
||||||
19
lib/models/post_tag.dart
Normal file
19
lib/models/post_tag.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
|
||||||
|
part 'post_tag.freezed.dart';
|
||||||
|
part 'post_tag.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class PostTag with _$PostTag {
|
||||||
|
const factory PostTag({
|
||||||
|
required String id,
|
||||||
|
required String slug,
|
||||||
|
String? name,
|
||||||
|
@Default([]) List<SnPost> posts,
|
||||||
|
}) = _PostTag;
|
||||||
|
|
||||||
|
factory PostTag.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$PostTagFromJson(json);
|
||||||
|
}
|
||||||
163
lib/models/post_tag.freezed.dart
Normal file
163
lib/models/post_tag.freezed.dart
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'post_tag.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$PostTag {
|
||||||
|
|
||||||
|
String get id; String get slug; String? get name; List<SnPost> get posts;
|
||||||
|
/// Create a copy of PostTag
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
/// Serializes this PostTag 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(posts));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostTag(id: $id, slug: $slug, name: $name, posts: $posts)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $PostTagCopyWith<$Res> {
|
||||||
|
factory $PostTagCopyWith(PostTag value, $Res Function(PostTag) _then) = _$PostTagCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$PostTagCopyWithImpl<$Res>
|
||||||
|
implements $PostTagCopyWith<$Res> {
|
||||||
|
_$PostTagCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final PostTag _self;
|
||||||
|
final $Res Function(PostTag) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PostTag
|
||||||
|
/// 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(
|
||||||
|
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
|
||||||
|
as String?,posts: null == posts ? _self.posts : posts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPost>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @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);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String slug;
|
||||||
|
@override final String? name;
|
||||||
|
final List<SnPost> _posts;
|
||||||
|
@override@JsonKey() List<SnPost> get posts {
|
||||||
|
if (_posts is EqualUnmodifiableListView) return _posts;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_posts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Create a copy of PostTag
|
||||||
|
/// 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);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$PostTagToJson(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,slug,name,const DeepCollectionEquality().hash(_posts));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'PostTag(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;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String slug, String? name, List<SnPost> posts
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$PostTagCopyWithImpl<$Res>
|
||||||
|
implements _$PostTagCopyWith<$Res> {
|
||||||
|
__$PostTagCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _PostTag _self;
|
||||||
|
final $Res Function(_PostTag) _then;
|
||||||
|
|
||||||
|
/// Create a copy of PostTag
|
||||||
|
/// 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(
|
||||||
|
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
|
||||||
|
as String?,posts: null == posts ? _self._posts : posts // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnPost>,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
25
lib/models/post_tag.g.dart
Normal file
25
lib/models/post_tag.g.dart
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'post_tag.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_PostTag _$PostTagFromJson(Map<String, dynamic> json) => _PostTag(
|
||||||
|
id: json['id'] as String,
|
||||||
|
slug: json['slug'] as String,
|
||||||
|
name: json['name'] as String?,
|
||||||
|
posts:
|
||||||
|
(json['posts'] as List<dynamic>?)
|
||||||
|
?.map((e) => SnPost.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
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(),
|
||||||
|
};
|
||||||
47
lib/models/publisher.dart
Normal file
47
lib/models/publisher.dart
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/models/user.dart';
|
||||||
|
|
||||||
|
part 'publisher.freezed.dart';
|
||||||
|
part 'publisher.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnPublisher with _$SnPublisher {
|
||||||
|
const factory SnPublisher({
|
||||||
|
@Default('') String id,
|
||||||
|
@Default(0) int type,
|
||||||
|
@Default('') String name,
|
||||||
|
@Default('') String nick,
|
||||||
|
@Default('') String bio,
|
||||||
|
SnCloudFile? picture,
|
||||||
|
SnCloudFile? background,
|
||||||
|
SnAccount? account,
|
||||||
|
String? accountId,
|
||||||
|
@Default(null) DateTime? createdAt,
|
||||||
|
@Default(null) DateTime? updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
String? realmId,
|
||||||
|
SnVerificationMark? verification,
|
||||||
|
}) = _SnPublisher;
|
||||||
|
|
||||||
|
factory SnPublisher.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPublisherFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnPublisherMember with _$SnPublisherMember {
|
||||||
|
const factory SnPublisherMember({
|
||||||
|
required String publisherId,
|
||||||
|
required SnPublisher? publisher,
|
||||||
|
required String accountId,
|
||||||
|
required SnAccount? account,
|
||||||
|
required int role,
|
||||||
|
required DateTime? joinedAt,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
required DateTime? deletedAt,
|
||||||
|
}) = _SnPublisherMember;
|
||||||
|
|
||||||
|
factory SnPublisherMember.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnPublisherMemberFromJson(json);
|
||||||
|
}
|
||||||
488
lib/models/publisher.freezed.dart
Normal file
488
lib/models/publisher.freezed.dart
Normal file
@@ -0,0 +1,488 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'publisher.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPublisher {
|
||||||
|
|
||||||
|
String get id; int get type; String get name; String get nick; String get bio; SnCloudFile? get picture; SnCloudFile? get background; SnAccount? get account; String? get accountId; DateTime? get createdAt; DateTime? get updatedAt; DateTime? get deletedAt; String? get realmId; SnVerificationMark? get verification;
|
||||||
|
/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublisherCopyWith<SnPublisher> get copyWith => _$SnPublisherCopyWithImpl<SnPublisher>(this as SnPublisher, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnPublisher to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnPublisherCopyWith<$Res> {
|
||||||
|
factory $SnPublisherCopyWith(SnPublisher value, $Res Function(SnPublisher) _then) = _$SnPublisherCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture;$SnCloudFileCopyWith<$Res>? get background;$SnAccountCopyWith<$Res>? get account;$SnVerificationMarkCopyWith<$Res>? get verification;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPublisherCopyWithImpl<$Res>
|
||||||
|
implements $SnPublisherCopyWith<$Res> {
|
||||||
|
_$SnPublisherCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnPublisher _self;
|
||||||
|
final $Res Function(SnPublisher) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,createdAt: 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
|
||||||
|
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnVerificationMark?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
|
if (_self.picture == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||||
|
return _then(_self.copyWith(picture: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get background {
|
||||||
|
if (_self.background == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
|
return _then(_self.copyWith(background: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<$Res>? get account {
|
||||||
|
if (_self.account == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||||
|
return _then(_self.copyWith(account: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnVerificationMarkCopyWith<$Res>? get verification {
|
||||||
|
if (_self.verification == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
|
||||||
|
return _then(_self.copyWith(verification: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnPublisher implements SnPublisher {
|
||||||
|
const _SnPublisher({this.id = '', this.type = 0, this.name = '', this.nick = '', this.bio = '', this.picture, this.background, this.account, this.accountId, this.createdAt = null, this.updatedAt = null, this.deletedAt, this.realmId, this.verification});
|
||||||
|
factory _SnPublisher.fromJson(Map<String, dynamic> json) => _$SnPublisherFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey() final String id;
|
||||||
|
@override@JsonKey() final int type;
|
||||||
|
@override@JsonKey() final String name;
|
||||||
|
@override@JsonKey() final String nick;
|
||||||
|
@override@JsonKey() final String bio;
|
||||||
|
@override final SnCloudFile? picture;
|
||||||
|
@override final SnCloudFile? background;
|
||||||
|
@override final SnAccount? account;
|
||||||
|
@override final String? accountId;
|
||||||
|
@override@JsonKey() final DateTime? createdAt;
|
||||||
|
@override@JsonKey() final DateTime? updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
@override final String? realmId;
|
||||||
|
@override final SnVerificationMark? verification;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnPublisherCopyWith<_SnPublisher> get copyWith => __$SnPublisherCopyWithImpl<_SnPublisher>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnPublisherToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisher&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.name, name) || other.name == name)&&(identical(other.nick, nick) || other.nick == nick)&&(identical(other.bio, bio) || other.bio == bio)&&(identical(other.picture, picture) || other.picture == picture)&&(identical(other.background, background) || other.background == background)&&(identical(other.account, account) || other.account == account)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt)&&(identical(other.realmId, realmId) || other.realmId == realmId)&&(identical(other.verification, verification) || other.verification == verification));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,type,name,nick,bio,picture,background,account,accountId,createdAt,updatedAt,deletedAt,realmId,verification);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublisher(id: $id, type: $type, name: $name, nick: $nick, bio: $bio, picture: $picture, background: $background, account: $account, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, realmId: $realmId, verification: $verification)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnPublisherCopyWith<$Res> implements $SnPublisherCopyWith<$Res> {
|
||||||
|
factory _$SnPublisherCopyWith(_SnPublisher value, $Res Function(_SnPublisher) _then) = __$SnPublisherCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, int type, String name, String nick, String bio, SnCloudFile? picture, SnCloudFile? background, SnAccount? account, String? accountId, DateTime? createdAt, DateTime? updatedAt, DateTime? deletedAt, String? realmId, SnVerificationMark? verification
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnCloudFileCopyWith<$Res>? get picture;@override $SnCloudFileCopyWith<$Res>? get background;@override $SnAccountCopyWith<$Res>? get account;@override $SnVerificationMarkCopyWith<$Res>? get verification;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnPublisherCopyWithImpl<$Res>
|
||||||
|
implements _$SnPublisherCopyWith<$Res> {
|
||||||
|
__$SnPublisherCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnPublisher _self;
|
||||||
|
final $Res Function(_SnPublisher) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? type = null,Object? name = null,Object? nick = null,Object? bio = null,Object? picture = freezed,Object? background = freezed,Object? account = freezed,Object? accountId = freezed,Object? createdAt = freezed,Object? updatedAt = freezed,Object? deletedAt = freezed,Object? realmId = freezed,Object? verification = freezed,}) {
|
||||||
|
return _then(_SnPublisher(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,nick: null == nick ? _self.nick : nick // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,bio: null == bio ? _self.bio : bio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,picture: freezed == picture ? _self.picture : picture // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,background: freezed == background ? _self.background : background // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnCloudFile?,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount?,accountId: freezed == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,createdAt: 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
|
||||||
|
as DateTime?,realmId: freezed == realmId ? _self.realmId : realmId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,verification: freezed == verification ? _self.verification : verification // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnVerificationMark?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get picture {
|
||||||
|
if (_self.picture == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.picture!, (value) {
|
||||||
|
return _then(_self.copyWith(picture: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFileCopyWith<$Res>? get background {
|
||||||
|
if (_self.background == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnCloudFileCopyWith<$Res>(_self.background!, (value) {
|
||||||
|
return _then(_self.copyWith(background: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<$Res>? get account {
|
||||||
|
if (_self.account == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||||
|
return _then(_self.copyWith(account: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisher
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnVerificationMarkCopyWith<$Res>? get verification {
|
||||||
|
if (_self.verification == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnVerificationMarkCopyWith<$Res>(_self.verification!, (value) {
|
||||||
|
return _then(_self.copyWith(verification: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnPublisherMember {
|
||||||
|
|
||||||
|
String get publisherId; SnPublisher? get publisher; String get accountId; SnAccount? get account; int get role; DateTime? get joinedAt; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublisherMemberCopyWith<SnPublisherMember> get copyWith => _$SnPublisherMemberCopyWithImpl<SnPublisherMember>(this as SnPublisherMember, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnPublisherMember to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnPublisherMember&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,publisherId,publisher,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublisherMember(publisherId: $publisherId, publisher: $publisher, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnPublisherMemberCopyWith<$Res> {
|
||||||
|
factory $SnPublisherMemberCopyWith(SnPublisherMember value, $Res Function(SnPublisherMember) _then) = _$SnPublisherMemberCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String publisherId, SnPublisher? publisher, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnPublisherCopyWith<$Res>? get publisher;$SnAccountCopyWith<$Res>? get account;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnPublisherMemberCopyWithImpl<$Res>
|
||||||
|
implements $SnPublisherMemberCopyWith<$Res> {
|
||||||
|
_$SnPublisherMemberCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnPublisherMember _self;
|
||||||
|
final $Res Function(SnPublisherMember) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? publisherId = null,Object? publisher = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPublisher?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublisherCopyWith<$Res>? get publisher {
|
||||||
|
if (_self.publisher == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) {
|
||||||
|
return _then(_self.copyWith(publisher: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<$Res>? get account {
|
||||||
|
if (_self.account == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||||
|
return _then(_self.copyWith(account: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnPublisherMember implements SnPublisherMember {
|
||||||
|
const _SnPublisherMember({required this.publisherId, required this.publisher, required this.accountId, required this.account, required this.role, required this.joinedAt, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
|
factory _SnPublisherMember.fromJson(Map<String, dynamic> json) => _$SnPublisherMemberFromJson(json);
|
||||||
|
|
||||||
|
@override final String publisherId;
|
||||||
|
@override final SnPublisher? publisher;
|
||||||
|
@override final String accountId;
|
||||||
|
@override final SnAccount? account;
|
||||||
|
@override final int role;
|
||||||
|
@override final DateTime? joinedAt;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnPublisherMemberCopyWith<_SnPublisherMember> get copyWith => __$SnPublisherMemberCopyWithImpl<_SnPublisherMember>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnPublisherMemberToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnPublisherMember&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&(identical(other.publisher, publisher) || other.publisher == publisher)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.role, role) || other.role == role)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt)&&(identical(other.deletedAt, deletedAt) || other.deletedAt == deletedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,publisherId,publisher,accountId,account,role,joinedAt,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnPublisherMember(publisherId: $publisherId, publisher: $publisher, accountId: $accountId, account: $account, role: $role, joinedAt: $joinedAt, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnPublisherMemberCopyWith<$Res> implements $SnPublisherMemberCopyWith<$Res> {
|
||||||
|
factory _$SnPublisherMemberCopyWith(_SnPublisherMember value, $Res Function(_SnPublisherMember) _then) = __$SnPublisherMemberCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String publisherId, SnPublisher? publisher, String accountId, SnAccount? account, int role, DateTime? joinedAt, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnPublisherCopyWith<$Res>? get publisher;@override $SnAccountCopyWith<$Res>? get account;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnPublisherMemberCopyWithImpl<$Res>
|
||||||
|
implements _$SnPublisherMemberCopyWith<$Res> {
|
||||||
|
__$SnPublisherMemberCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnPublisherMember _self;
|
||||||
|
final $Res Function(_SnPublisherMember) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? publisherId = null,Object? publisher = freezed,Object? accountId = null,Object? account = freezed,Object? role = null,Object? joinedAt = freezed,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnPublisherMember(
|
||||||
|
publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,publisher: freezed == publisher ? _self.publisher : publisher // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnPublisher?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnAccount?,role: null == role ? _self.role : role // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,joinedAt: freezed == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnPublisherCopyWith<$Res>? get publisher {
|
||||||
|
if (_self.publisher == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnPublisherCopyWith<$Res>(_self.publisher!, (value) {
|
||||||
|
return _then(_self.copyWith(publisher: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnPublisherMember
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnAccountCopyWith<$Res>? get account {
|
||||||
|
if (_self.account == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnAccountCopyWith<$Res>(_self.account!, (value) {
|
||||||
|
return _then(_self.copyWith(account: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
103
lib/models/publisher.g.dart
Normal file
103
lib/models/publisher.g.dart
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'publisher.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnPublisher _$SnPublisherFromJson(Map<String, dynamic> json) => _SnPublisher(
|
||||||
|
id: json['id'] as String? ?? '',
|
||||||
|
type: (json['type'] as num?)?.toInt() ?? 0,
|
||||||
|
name: json['name'] as String? ?? '',
|
||||||
|
nick: json['nick'] as String? ?? '',
|
||||||
|
bio: json['bio'] as String? ?? '',
|
||||||
|
picture:
|
||||||
|
json['picture'] == null
|
||||||
|
? null
|
||||||
|
: SnCloudFile.fromJson(json['picture'] as Map<String, dynamic>),
|
||||||
|
background:
|
||||||
|
json['background'] == null
|
||||||
|
? null
|
||||||
|
: SnCloudFile.fromJson(json['background'] as Map<String, dynamic>),
|
||||||
|
account:
|
||||||
|
json['account'] == null
|
||||||
|
? null
|
||||||
|
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
|
accountId: json['account_id'] as String?,
|
||||||
|
createdAt:
|
||||||
|
json['created_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt:
|
||||||
|
json['updated_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
realmId: json['realm_id'] as String?,
|
||||||
|
verification:
|
||||||
|
json['verification'] == null
|
||||||
|
? null
|
||||||
|
: SnVerificationMark.fromJson(
|
||||||
|
json['verification'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnPublisherToJson(_SnPublisher instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'type': instance.type,
|
||||||
|
'name': instance.name,
|
||||||
|
'nick': instance.nick,
|
||||||
|
'bio': instance.bio,
|
||||||
|
'picture': instance.picture?.toJson(),
|
||||||
|
'background': instance.background?.toJson(),
|
||||||
|
'account': instance.account?.toJson(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'created_at': instance.createdAt?.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt?.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
'realm_id': instance.realmId,
|
||||||
|
'verification': instance.verification?.toJson(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_SnPublisherMember _$SnPublisherMemberFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnPublisherMember(
|
||||||
|
publisherId: json['publisher_id'] as String,
|
||||||
|
publisher:
|
||||||
|
json['publisher'] == null
|
||||||
|
? null
|
||||||
|
: SnPublisher.fromJson(json['publisher'] as Map<String, dynamic>),
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
account:
|
||||||
|
json['account'] == null
|
||||||
|
? null
|
||||||
|
: SnAccount.fromJson(json['account'] as Map<String, dynamic>),
|
||||||
|
role: (json['role'] as num).toInt(),
|
||||||
|
joinedAt:
|
||||||
|
json['joined_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['joined_at'] as String),
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
deletedAt:
|
||||||
|
json['deleted_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['deleted_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnPublisherMemberToJson(_SnPublisherMember instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'publisher_id': instance.publisherId,
|
||||||
|
'publisher': instance.publisher?.toJson(),
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'account': instance.account?.toJson(),
|
||||||
|
'role': instance.role,
|
||||||
|
'joined_at': instance.joinedAt?.toIso8601String(),
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
@@ -10,8 +10,8 @@ sealed class SnRealm with _$SnRealm {
|
|||||||
const factory SnRealm({
|
const factory SnRealm({
|
||||||
required String id,
|
required String id,
|
||||||
required String slug,
|
required String slug,
|
||||||
required String name,
|
@Default('') String name,
|
||||||
required String description,
|
@Default('') String description,
|
||||||
required String? verifiedAs,
|
required String? verifiedAs,
|
||||||
required DateTime? verifiedAt,
|
required DateTime? verifiedAt,
|
||||||
required bool isCommunity,
|
required bool isCommunity,
|
||||||
|
|||||||
@@ -117,13 +117,13 @@ $SnCloudFileCopyWith<$Res>? get background {
|
|||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
||||||
class _SnRealm implements SnRealm {
|
class _SnRealm implements SnRealm {
|
||||||
const _SnRealm({required this.id, required this.slug, required this.name, required this.description, required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
const _SnRealm({required this.id, required this.slug, this.name = '', this.description = '', required this.verifiedAs, required this.verifiedAt, required this.isCommunity, required this.isPublic, required this.picture, required this.background, required this.accountId, required this.createdAt, required this.updatedAt, required this.deletedAt});
|
||||||
factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json);
|
factory _SnRealm.fromJson(Map<String, dynamic> json) => _$SnRealmFromJson(json);
|
||||||
|
|
||||||
@override final String id;
|
@override final String id;
|
||||||
@override final String slug;
|
@override final String slug;
|
||||||
@override final String name;
|
@override@JsonKey() final String name;
|
||||||
@override final String description;
|
@override@JsonKey() final String description;
|
||||||
@override final String? verifiedAs;
|
@override final String? verifiedAs;
|
||||||
@override final DateTime? verifiedAt;
|
@override final DateTime? verifiedAt;
|
||||||
@override final bool isCommunity;
|
@override final bool isCommunity;
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ part of 'realm.dart';
|
|||||||
_SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm(
|
_SnRealm _$SnRealmFromJson(Map<String, dynamic> json) => _SnRealm(
|
||||||
id: json['id'] as String,
|
id: json['id'] as String,
|
||||||
slug: json['slug'] as String,
|
slug: json['slug'] as String,
|
||||||
name: json['name'] as String,
|
name: json['name'] as String? ?? '',
|
||||||
description: json['description'] as String,
|
description: json['description'] as String? ?? '',
|
||||||
verifiedAs: json['verified_as'] as String?,
|
verifiedAs: json['verified_as'] as String?,
|
||||||
verifiedAt:
|
verifiedAt:
|
||||||
json['verified_at'] == null
|
json['verified_at'] == null
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/publisher.dart';
|
||||||
|
|
||||||
part 'sticker.freezed.dart';
|
part 'sticker.freezed.dart';
|
||||||
part 'sticker.g.dart';
|
part 'sticker.g.dart';
|
||||||
|
|||||||
64
lib/models/webfeed.dart
Normal file
64
lib/models/webfeed.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
import 'package:island/models/embed.dart';
|
||||||
|
|
||||||
|
part 'webfeed.freezed.dart';
|
||||||
|
part 'webfeed.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnWebFeedConfig with _$SnWebFeedConfig {
|
||||||
|
const factory SnWebFeedConfig({@Default(false) bool scrapPage}) =
|
||||||
|
_SnWebFeedConfig;
|
||||||
|
|
||||||
|
factory SnWebFeedConfig.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnWebFeedConfigFromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnWebFeed with _$SnWebFeed {
|
||||||
|
const factory SnWebFeed({
|
||||||
|
required String id,
|
||||||
|
required String url,
|
||||||
|
required String title,
|
||||||
|
String? description,
|
||||||
|
SnScrappedLink? preview,
|
||||||
|
@Default(SnWebFeedConfig()) SnWebFeedConfig config,
|
||||||
|
required String publisherId,
|
||||||
|
@Default([]) List<SnWebArticle> articles,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
}) = _SnWebFeed;
|
||||||
|
|
||||||
|
factory SnWebFeed.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnWebFeedFromJson(json);
|
||||||
|
|
||||||
|
factory SnWebFeed.fromJsonString(String jsonString) =>
|
||||||
|
SnWebFeed.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
|
||||||
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnWebArticle with _$SnWebArticle {
|
||||||
|
const factory SnWebArticle({
|
||||||
|
required String id,
|
||||||
|
required String title,
|
||||||
|
required String url,
|
||||||
|
String? author,
|
||||||
|
Map<String, dynamic>? meta,
|
||||||
|
SnScrappedLink? preview,
|
||||||
|
SnWebFeed? feed,
|
||||||
|
String? content,
|
||||||
|
DateTime? publishedAt,
|
||||||
|
required String feedId,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
DateTime? deletedAt,
|
||||||
|
}) = _SnWebArticle;
|
||||||
|
|
||||||
|
factory SnWebArticle.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnWebArticleFromJson(json);
|
||||||
|
|
||||||
|
factory SnWebArticle.fromJsonString(String jsonString) =>
|
||||||
|
SnWebArticle.fromJson(jsonDecode(jsonString) as Map<String, dynamic>);
|
||||||
|
}
|
||||||
584
lib/models/webfeed.freezed.dart
Normal file
584
lib/models/webfeed.freezed.dart
Normal file
@@ -0,0 +1,584 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'webfeed.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnWebFeedConfig {
|
||||||
|
|
||||||
|
bool get scrapPage;
|
||||||
|
/// Create a copy of SnWebFeedConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebFeedConfigCopyWith<SnWebFeedConfig> get copyWith => _$SnWebFeedConfigCopyWithImpl<SnWebFeedConfig>(this as SnWebFeedConfig, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnWebFeedConfig to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,scrapPage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWebFeedConfig(scrapPage: $scrapPage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnWebFeedConfigCopyWith<$Res> {
|
||||||
|
factory $SnWebFeedConfigCopyWith(SnWebFeedConfig value, $Res Function(SnWebFeedConfig) _then) = _$SnWebFeedConfigCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
bool scrapPage
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnWebFeedConfigCopyWithImpl<$Res>
|
||||||
|
implements $SnWebFeedConfigCopyWith<$Res> {
|
||||||
|
_$SnWebFeedConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnWebFeedConfig _self;
|
||||||
|
final $Res Function(SnWebFeedConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeedConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? scrapPage = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnWebFeedConfig implements SnWebFeedConfig {
|
||||||
|
const _SnWebFeedConfig({this.scrapPage = false});
|
||||||
|
factory _SnWebFeedConfig.fromJson(Map<String, dynamic> json) => _$SnWebFeedConfigFromJson(json);
|
||||||
|
|
||||||
|
@override@JsonKey() final bool scrapPage;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeedConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnWebFeedConfigCopyWith<_SnWebFeedConfig> get copyWith => __$SnWebFeedConfigCopyWithImpl<_SnWebFeedConfig>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnWebFeedConfigToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebFeedConfig&&(identical(other.scrapPage, scrapPage) || other.scrapPage == scrapPage));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,scrapPage);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWebFeedConfig(scrapPage: $scrapPage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnWebFeedConfigCopyWith<$Res> implements $SnWebFeedConfigCopyWith<$Res> {
|
||||||
|
factory _$SnWebFeedConfigCopyWith(_SnWebFeedConfig value, $Res Function(_SnWebFeedConfig) _then) = __$SnWebFeedConfigCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
bool scrapPage
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnWebFeedConfigCopyWithImpl<$Res>
|
||||||
|
implements _$SnWebFeedConfigCopyWith<$Res> {
|
||||||
|
__$SnWebFeedConfigCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnWebFeedConfig _self;
|
||||||
|
final $Res Function(_SnWebFeedConfig) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeedConfig
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? scrapPage = null,}) {
|
||||||
|
return _then(_SnWebFeedConfig(
|
||||||
|
scrapPage: null == scrapPage ? _self.scrapPage : scrapPage // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnWebFeed {
|
||||||
|
|
||||||
|
String get id; String get url; String get title; String? get description; SnScrappedLink? get preview; SnWebFeedConfig get config; String get publisherId; List<SnWebArticle> get articles; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebFeedCopyWith<SnWebFeed> get copyWith => _$SnWebFeedCopyWithImpl<SnWebFeed>(this as SnWebFeed, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnWebFeed to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other.articles, articles)&&(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,url,title,description,preview,config,publisherId,const DeepCollectionEquality().hash(articles),createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnWebFeedCopyWith<$Res> {
|
||||||
|
factory $SnWebFeedCopyWith(SnWebFeed value, $Res Function(SnWebFeed) _then) = _$SnWebFeedCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String url, String title, String? description, SnScrappedLink? preview, SnWebFeedConfig config, String publisherId, List<SnWebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnScrappedLinkCopyWith<$Res>? get preview;$SnWebFeedConfigCopyWith<$Res> get config;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnWebFeedCopyWithImpl<$Res>
|
||||||
|
implements $SnWebFeedCopyWith<$Res> {
|
||||||
|
_$SnWebFeedCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnWebFeed _self;
|
||||||
|
final $Res Function(SnWebFeed) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,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?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,articles: null == articles ? _self.articles : articles // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnWebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnScrappedLinkCopyWith<$Res>? get preview {
|
||||||
|
if (_self.preview == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
|
||||||
|
return _then(_self.copyWith(preview: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebFeedConfigCopyWith<$Res> get config {
|
||||||
|
|
||||||
|
return $SnWebFeedConfigCopyWith<$Res>(_self.config, (value) {
|
||||||
|
return _then(_self.copyWith(config: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnWebFeed implements SnWebFeed {
|
||||||
|
const _SnWebFeed({required this.id, required this.url, required this.title, this.description, this.preview, this.config = const SnWebFeedConfig(), required this.publisherId, final List<SnWebArticle> articles = const [], required this.createdAt, required this.updatedAt, this.deletedAt}): _articles = articles;
|
||||||
|
factory _SnWebFeed.fromJson(Map<String, dynamic> json) => _$SnWebFeedFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String url;
|
||||||
|
@override final String title;
|
||||||
|
@override final String? description;
|
||||||
|
@override final SnScrappedLink? preview;
|
||||||
|
@override@JsonKey() final SnWebFeedConfig config;
|
||||||
|
@override final String publisherId;
|
||||||
|
final List<SnWebArticle> _articles;
|
||||||
|
@override@JsonKey() List<SnWebArticle> get articles {
|
||||||
|
if (_articles is EqualUnmodifiableListView) return _articles;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableListView(_articles);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnWebFeedCopyWith<_SnWebFeed> get copyWith => __$SnWebFeedCopyWithImpl<_SnWebFeed>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnWebFeedToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebFeed&&(identical(other.id, id) || other.id == id)&&(identical(other.url, url) || other.url == url)&&(identical(other.title, title) || other.title == title)&&(identical(other.description, description) || other.description == description)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.config, config) || other.config == config)&&(identical(other.publisherId, publisherId) || other.publisherId == publisherId)&&const DeepCollectionEquality().equals(other._articles, _articles)&&(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,url,title,description,preview,config,publisherId,const DeepCollectionEquality().hash(_articles),createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWebFeed(id: $id, url: $url, title: $title, description: $description, preview: $preview, config: $config, publisherId: $publisherId, articles: $articles, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnWebFeedCopyWith<$Res> implements $SnWebFeedCopyWith<$Res> {
|
||||||
|
factory _$SnWebFeedCopyWith(_SnWebFeed value, $Res Function(_SnWebFeed) _then) = __$SnWebFeedCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String url, String title, String? description, SnScrappedLink? preview, SnWebFeedConfig config, String publisherId, List<SnWebArticle> articles, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $SnWebFeedConfigCopyWith<$Res> get config;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnWebFeedCopyWithImpl<$Res>
|
||||||
|
implements _$SnWebFeedCopyWith<$Res> {
|
||||||
|
__$SnWebFeedCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnWebFeed _self;
|
||||||
|
final $Res Function(_SnWebFeed) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? url = null,Object? title = null,Object? description = freezed,Object? preview = freezed,Object? config = null,Object? publisherId = null,Object? articles = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnWebFeed(
|
||||||
|
id: null == id ? _self.id : id // 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?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnScrappedLink?,config: null == config ? _self.config : config // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWebFeedConfig,publisherId: null == publisherId ? _self.publisherId : publisherId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,articles: null == articles ? _self._articles : articles // ignore: cast_nullable_to_non_nullable
|
||||||
|
as List<SnWebArticle>,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnScrappedLinkCopyWith<$Res>? get preview {
|
||||||
|
if (_self.preview == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
|
||||||
|
return _then(_self.copyWith(preview: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnWebFeed
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebFeedConfigCopyWith<$Res> get config {
|
||||||
|
|
||||||
|
return $SnWebFeedConfigCopyWith<$Res>(_self.config, (value) {
|
||||||
|
return _then(_self.copyWith(config: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnWebArticle {
|
||||||
|
|
||||||
|
String get id; String get title; String get url; String? get author; Map<String, dynamic>? get meta; SnScrappedLink? get preview; SnWebFeed? get feed; String? get content; DateTime? get publishedAt; String get feedId; DateTime get createdAt; DateTime get updatedAt; DateTime? get deletedAt;
|
||||||
|
/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebArticleCopyWith<SnWebArticle> get copyWith => _$SnWebArticleCopyWithImpl<SnWebArticle>(this as SnWebArticle, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnWebArticle to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnWebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other.meta, meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.feed, feed) || other.feed == feed)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(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,title,url,author,const DeepCollectionEquality().hash(meta),preview,feed,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, feed: $feed, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnWebArticleCopyWith<$Res> {
|
||||||
|
factory $SnWebArticleCopyWith(SnWebArticle value, $Res Function(SnWebArticle) _then) = _$SnWebArticleCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, SnWebFeed? feed, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnScrappedLinkCopyWith<$Res>? get preview;$SnWebFeedCopyWith<$Res>? get feed;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnWebArticleCopyWithImpl<$Res>
|
||||||
|
implements $SnWebArticleCopyWith<$Res> {
|
||||||
|
_$SnWebArticleCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnWebArticle _self;
|
||||||
|
final $Res Function(SnWebArticle) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? feed = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,meta: freezed == meta ? _self.meta : meta // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnScrappedLink?,feed: freezed == feed ? _self.feed : feed // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWebFeed?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnScrappedLinkCopyWith<$Res>? get preview {
|
||||||
|
if (_self.preview == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
|
||||||
|
return _then(_self.copyWith(preview: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebFeedCopyWith<$Res>? get feed {
|
||||||
|
if (_self.feed == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWebFeedCopyWith<$Res>(_self.feed!, (value) {
|
||||||
|
return _then(_self.copyWith(feed: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnWebArticle implements SnWebArticle {
|
||||||
|
const _SnWebArticle({required this.id, required this.title, required this.url, this.author, final Map<String, dynamic>? meta, this.preview, this.feed, this.content, this.publishedAt, required this.feedId, required this.createdAt, required this.updatedAt, this.deletedAt}): _meta = meta;
|
||||||
|
factory _SnWebArticle.fromJson(Map<String, dynamic> json) => _$SnWebArticleFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String title;
|
||||||
|
@override final String url;
|
||||||
|
@override final String? author;
|
||||||
|
final Map<String, dynamic>? _meta;
|
||||||
|
@override Map<String, dynamic>? get meta {
|
||||||
|
final value = _meta;
|
||||||
|
if (value == null) return null;
|
||||||
|
if (_meta is EqualUnmodifiableMapView) return _meta;
|
||||||
|
// ignore: implicit_dynamic_type
|
||||||
|
return EqualUnmodifiableMapView(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override final SnScrappedLink? preview;
|
||||||
|
@override final SnWebFeed? feed;
|
||||||
|
@override final String? content;
|
||||||
|
@override final DateTime? publishedAt;
|
||||||
|
@override final String feedId;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
@override final DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnWebArticleCopyWith<_SnWebArticle> get copyWith => __$SnWebArticleCopyWithImpl<_SnWebArticle>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnWebArticleToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnWebArticle&&(identical(other.id, id) || other.id == id)&&(identical(other.title, title) || other.title == title)&&(identical(other.url, url) || other.url == url)&&(identical(other.author, author) || other.author == author)&&const DeepCollectionEquality().equals(other._meta, _meta)&&(identical(other.preview, preview) || other.preview == preview)&&(identical(other.feed, feed) || other.feed == feed)&&(identical(other.content, content) || other.content == content)&&(identical(other.publishedAt, publishedAt) || other.publishedAt == publishedAt)&&(identical(other.feedId, feedId) || other.feedId == feedId)&&(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,title,url,author,const DeepCollectionEquality().hash(_meta),preview,feed,content,publishedAt,feedId,createdAt,updatedAt,deletedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnWebArticle(id: $id, title: $title, url: $url, author: $author, meta: $meta, preview: $preview, feed: $feed, content: $content, publishedAt: $publishedAt, feedId: $feedId, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnWebArticleCopyWith<$Res> implements $SnWebArticleCopyWith<$Res> {
|
||||||
|
factory _$SnWebArticleCopyWith(_SnWebArticle value, $Res Function(_SnWebArticle) _then) = __$SnWebArticleCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String title, String url, String? author, Map<String, dynamic>? meta, SnScrappedLink? preview, SnWebFeed? feed, String? content, DateTime? publishedAt, String feedId, DateTime createdAt, DateTime updatedAt, DateTime? deletedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@override $SnScrappedLinkCopyWith<$Res>? get preview;@override $SnWebFeedCopyWith<$Res>? get feed;
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnWebArticleCopyWithImpl<$Res>
|
||||||
|
implements _$SnWebArticleCopyWith<$Res> {
|
||||||
|
__$SnWebArticleCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnWebArticle _self;
|
||||||
|
final $Res Function(_SnWebArticle) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? title = null,Object? url = null,Object? author = freezed,Object? meta = freezed,Object? preview = freezed,Object? feed = freezed,Object? content = freezed,Object? publishedAt = freezed,Object? feedId = null,Object? createdAt = null,Object? updatedAt = null,Object? deletedAt = freezed,}) {
|
||||||
|
return _then(_SnWebArticle(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,title: null == title ? _self.title : title // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,url: null == url ? _self.url : url // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,author: freezed == author ? _self.author : author // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,meta: freezed == meta ? _self._meta : meta // ignore: cast_nullable_to_non_nullable
|
||||||
|
as Map<String, dynamic>?,preview: freezed == preview ? _self.preview : preview // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnScrappedLink?,feed: freezed == feed ? _self.feed : feed // ignore: cast_nullable_to_non_nullable
|
||||||
|
as SnWebFeed?,content: freezed == content ? _self.content : content // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,publishedAt: freezed == publishedAt ? _self.publishedAt : publishedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,feedId: null == feedId ? _self.feedId : feedId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,deletedAt: freezed == deletedAt ? _self.deletedAt : deletedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime?,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnScrappedLinkCopyWith<$Res>? get preview {
|
||||||
|
if (_self.preview == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnScrappedLinkCopyWith<$Res>(_self.preview!, (value) {
|
||||||
|
return _then(_self.copyWith(preview: value));
|
||||||
|
});
|
||||||
|
}/// Create a copy of SnWebArticle
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnWebFeedCopyWith<$Res>? get feed {
|
||||||
|
if (_self.feed == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $SnWebFeedCopyWith<$Res>(_self.feed!, (value) {
|
||||||
|
return _then(_self.copyWith(feed: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
103
lib/models/webfeed.g.dart
Normal file
103
lib/models/webfeed.g.dart
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'webfeed.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnWebFeedConfig _$SnWebFeedConfigFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnWebFeedConfig(scrapPage: json['scrap_page'] as bool? ?? false);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnWebFeedConfigToJson(_SnWebFeedConfig instance) =>
|
||||||
|
<String, dynamic>{'scrap_page': instance.scrapPage};
|
||||||
|
|
||||||
|
_SnWebFeed _$SnWebFeedFromJson(Map<String, dynamic> json) => _SnWebFeed(
|
||||||
|
id: json['id'] as String,
|
||||||
|
url: json['url'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
description: json['description'] as String?,
|
||||||
|
preview:
|
||||||
|
json['preview'] == null
|
||||||
|
? null
|
||||||
|
: SnScrappedLink.fromJson(json['preview'] as Map<String, dynamic>),
|
||||||
|
config:
|
||||||
|
json['config'] == null
|
||||||
|
? const SnWebFeedConfig()
|
||||||
|
: SnWebFeedConfig.fromJson(json['config'] as Map<String, dynamic>),
|
||||||
|
publisherId: json['publisher_id'] as String,
|
||||||
|
articles:
|
||||||
|
(json['articles'] as List<dynamic>?)
|
||||||
|
?.map((e) => SnWebArticle.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
|
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> _$SnWebFeedToJson(_SnWebFeed instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'url': instance.url,
|
||||||
|
'title': instance.title,
|
||||||
|
'description': instance.description,
|
||||||
|
'preview': instance.preview?.toJson(),
|
||||||
|
'config': instance.config.toJson(),
|
||||||
|
'publisher_id': instance.publisherId,
|
||||||
|
'articles': instance.articles.map((e) => e.toJson()).toList(),
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
|
|
||||||
|
_SnWebArticle _$SnWebArticleFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnWebArticle(
|
||||||
|
id: json['id'] as String,
|
||||||
|
title: json['title'] as String,
|
||||||
|
url: json['url'] as String,
|
||||||
|
author: json['author'] as String?,
|
||||||
|
meta: json['meta'] as Map<String, dynamic>?,
|
||||||
|
preview:
|
||||||
|
json['preview'] == null
|
||||||
|
? null
|
||||||
|
: SnScrappedLink.fromJson(
|
||||||
|
json['preview'] as Map<String, dynamic>,
|
||||||
|
),
|
||||||
|
feed:
|
||||||
|
json['feed'] == null
|
||||||
|
? null
|
||||||
|
: SnWebFeed.fromJson(json['feed'] as Map<String, dynamic>),
|
||||||
|
content: json['content'] as String?,
|
||||||
|
publishedAt:
|
||||||
|
json['published_at'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['published_at'] as String),
|
||||||
|
feedId: json['feed_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> _$SnWebArticleToJson(_SnWebArticle instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'title': instance.title,
|
||||||
|
'url': instance.url,
|
||||||
|
'author': instance.author,
|
||||||
|
'meta': instance.meta,
|
||||||
|
'preview': instance.preview?.toJson(),
|
||||||
|
'feed': instance.feed?.toJson(),
|
||||||
|
'content': instance.content,
|
||||||
|
'published_at': instance.publishedAt?.toIso8601String(),
|
||||||
|
'feed_id': instance.feedId,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
|
};
|
||||||
31
lib/pods/article_detail.dart
Normal file
31
lib/pods/article_detail.dart
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
|
||||||
|
/// Provider that fetches a single article by its ID
|
||||||
|
final articleDetailProvider = FutureProvider.autoDispose.family<SnWebArticle, String>(
|
||||||
|
(ref, articleId) async {
|
||||||
|
final dio = ref.watch(apiClientProvider);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await dio.get<Map<String, dynamic>>(
|
||||||
|
'/feeds/articles/$articleId',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200 && response.data != null) {
|
||||||
|
return SnWebArticle.fromJson(response.data!);
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load article');
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
if (e.response?.statusCode == 404) {
|
||||||
|
throw Exception('Article not found');
|
||||||
|
} else {
|
||||||
|
throw Exception('Failed to load article: ${e.message}');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load article: $e');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
1
lib/pods/article_list.dart
Normal file
1
lib/pods/article_list.dart
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -11,11 +11,6 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
|
|
||||||
UserInfoNotifier(this._ref) : super(const AsyncValue.data(null));
|
UserInfoNotifier(this._ref) : super(const AsyncValue.data(null));
|
||||||
|
|
||||||
Future<String?> getAccessToken() async {
|
|
||||||
final prefs = _ref.read(sharedPreferencesProvider);
|
|
||||||
return prefs.getString(kTokenPairStoreKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> fetchUser() async {
|
Future<void> fetchUser() async {
|
||||||
try {
|
try {
|
||||||
final client = _ref.read(apiClientProvider);
|
final client = _ref.read(apiClientProvider);
|
||||||
@@ -32,7 +27,6 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> {
|
|||||||
state = const AsyncValue.data(null);
|
state = const AsyncValue.data(null);
|
||||||
final prefs = _ref.read(sharedPreferencesProvider);
|
final prefs = _ref.read(sharedPreferencesProvider);
|
||||||
await prefs.remove(kTokenPairStoreKey);
|
await prefs.remove(kTokenPairStoreKey);
|
||||||
_ref.invalidate(userInfoProvider);
|
|
||||||
_ref.invalidate(tokenProvider);
|
_ref.invalidate(tokenProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
123
lib/pods/webfeed.dart
Normal file
123
lib/pods/webfeed.dart
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
|
||||||
|
final webFeedListProvider = FutureProvider.family<List<SnWebFeed>, String>((
|
||||||
|
ref,
|
||||||
|
pubName,
|
||||||
|
) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final response = await client.get('/publishers/$pubName/feeds');
|
||||||
|
return (response.data as List)
|
||||||
|
.map((json) => SnWebFeed.fromJson(json))
|
||||||
|
.toList();
|
||||||
|
});
|
||||||
|
|
||||||
|
class WebFeedNotifier
|
||||||
|
extends
|
||||||
|
AutoDisposeFamilyAsyncNotifier<
|
||||||
|
SnWebFeed,
|
||||||
|
({String pubName, String? feedId})
|
||||||
|
> {
|
||||||
|
@override
|
||||||
|
FutureOr<SnWebFeed> build(({String pubName, String? feedId}) arg) async {
|
||||||
|
if (arg.feedId == null || arg.feedId!.isEmpty) {
|
||||||
|
return SnWebFeed(
|
||||||
|
id: '',
|
||||||
|
url: '',
|
||||||
|
title: '',
|
||||||
|
publisherId: arg.pubName,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final response = await client.get(
|
||||||
|
'/publishers/${arg.pubName}/feeds/${arg.feedId}',
|
||||||
|
);
|
||||||
|
return SnWebFeed.fromJson(response.data);
|
||||||
|
} catch (e) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> saveFeed(SnWebFeed feed) async {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final url = '/publishers/${feed.publisherId}/feeds';
|
||||||
|
|
||||||
|
final response =
|
||||||
|
feed.id.isEmpty
|
||||||
|
? await client.post(url, data: feed.toJson())
|
||||||
|
: await client.patch('$url/${feed.id}', data: feed.toJson());
|
||||||
|
|
||||||
|
state = AsyncValue.data(SnWebFeed.fromJson(response.data));
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
state = AsyncValue.error(error, stackTrace);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteFeed() async {
|
||||||
|
final feedId = arg.feedId;
|
||||||
|
if (feedId == null || feedId.isEmpty) return;
|
||||||
|
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.delete('/publishers/${arg.pubName}/feeds/$feedId');
|
||||||
|
state = AsyncValue.data(
|
||||||
|
SnWebFeed(
|
||||||
|
id: '',
|
||||||
|
url: '',
|
||||||
|
title: '',
|
||||||
|
publisherId: arg.pubName,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
state = AsyncValue.error(error, stackTrace);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> scrapFeed() async {
|
||||||
|
final feedId = arg.feedId;
|
||||||
|
if (feedId == null || feedId.isEmpty) return;
|
||||||
|
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.post(
|
||||||
|
'/publishers/${arg.pubName}/feeds/$feedId/scrap',
|
||||||
|
options: Options(
|
||||||
|
sendTimeout: const Duration(seconds: 60),
|
||||||
|
receiveTimeout: const Duration(seconds: 180),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Reload the feed
|
||||||
|
final response = await client.get(
|
||||||
|
'/publishers/${arg.pubName}/feeds/$feedId',
|
||||||
|
);
|
||||||
|
state = AsyncValue.data(SnWebFeed.fromJson(response.data));
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
state = AsyncValue.error(error, stackTrace);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final webFeedNotifierProvider = AsyncNotifierProvider.autoDispose
|
||||||
|
.family<WebFeedNotifier, SnWebFeed, ({String pubName, String? feedId})>(
|
||||||
|
WebFeedNotifier.new,
|
||||||
|
);
|
||||||
499
lib/route.dart
499
lib/route.dart
@@ -1,98 +1,433 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:island/route.gr.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/screens/about.dart';
|
||||||
|
import 'package:island/screens/developers/apps.dart';
|
||||||
|
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_search.dart';
|
||||||
|
import 'package:island/widgets/app_wrapper.dart';
|
||||||
|
import 'package:island/screens/tabs.dart';
|
||||||
|
import 'package:island/screens/explore.dart';
|
||||||
|
import 'package:island/screens/article_detail_screen.dart';
|
||||||
|
import 'package:island/screens/account.dart';
|
||||||
|
import 'package:island/screens/notification.dart';
|
||||||
|
import 'package:island/screens/wallet.dart';
|
||||||
|
import 'package:island/screens/account/relationship.dart';
|
||||||
|
import 'package:island/screens/account/profile.dart';
|
||||||
|
import 'package:island/screens/account/me/update.dart';
|
||||||
|
import 'package:island/screens/account/leveling.dart';
|
||||||
|
import 'package:island/screens/account/me/settings.dart';
|
||||||
|
import 'package:island/screens/chat/chat.dart';
|
||||||
|
import 'package:island/screens/chat/room.dart';
|
||||||
|
import 'package:island/screens/chat/room_detail.dart';
|
||||||
|
import 'package:island/screens/chat/call.dart';
|
||||||
|
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/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/posts/compose.dart';
|
||||||
|
import 'package:island/screens/posts/post_detail.dart';
|
||||||
|
import 'package:island/screens/posts/pub_profile.dart';
|
||||||
|
import 'package:island/screens/auth/login.dart';
|
||||||
|
import 'package:island/screens/auth/create_account.dart';
|
||||||
|
import 'package:island/screens/settings.dart';
|
||||||
|
import 'package:island/screens/realm/realms.dart';
|
||||||
|
import 'package:island/screens/realm/realm_detail.dart';
|
||||||
|
import 'package:island/screens/account/event_calendar.dart';
|
||||||
|
import 'package:island/screens/discovery/realms.dart';
|
||||||
|
|
||||||
@AutoRouterConfig(replaceInRouteName: 'Screen|Page,Route')
|
// Shell route keys for nested navigation
|
||||||
class AppRouter extends RootStackRouter {
|
final rootNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
@override
|
final _shellNavigatorKey = GlobalKey<NavigatorState>();
|
||||||
RouteType get defaultRouteType => RouteType.adaptive();
|
final _tabsShellKey = GlobalKey<NavigatorState>();
|
||||||
|
|
||||||
@override
|
// Provider for the router
|
||||||
List<AutoRoute> get routes => [
|
final routerProvider = Provider<GoRouter>((ref) {
|
||||||
AutoRoute(path: '/', page: AppWrapper.page, children: _appRoutes),
|
return GoRouter(
|
||||||
];
|
navigatorKey: rootNavigatorKey,
|
||||||
|
initialLocation: '/',
|
||||||
List<AutoRoute> get _appRoutes => [
|
routes: [
|
||||||
|
ShellRoute(
|
||||||
|
navigatorKey: _shellNavigatorKey,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return AppWrapper(child: child);
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
// Standalone routes without bottom navigation
|
// Standalone routes without bottom navigation
|
||||||
AutoRoute(page: PostComposeRoute.page, path: 'posts/compose'),
|
GoRoute(
|
||||||
AutoRoute(page: PostEditRoute.page, path: 'posts/:id/edit'),
|
path: '/posts/compose',
|
||||||
AutoRoute(page: CallRoute.page, path: 'chat/:id/call'),
|
builder:
|
||||||
AutoRoute(page: EventCalanderRoute.page, path: 'account/:name/calendar'),
|
(context, state) => PostComposeScreen(
|
||||||
|
initialState: state.extra as PostComposeInitialState?,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/posts/:id/edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PostEditScreen(id: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/:id/call',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return CallScreen(roomId: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/:name/calendar',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return EventCalanderScreen(name: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ShellRoute(
|
||||||
|
builder:
|
||||||
|
(context, state, child) => CreatorHubShellScreen(child: child),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators',
|
||||||
|
builder: (context, state) => const CreatorHubScreen(),
|
||||||
|
),
|
||||||
|
// Web Feed Routes
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/feeds',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return WebFeedListScreen(pubName: name);
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: 'new',
|
||||||
|
builder: (context, state) {
|
||||||
|
return WebFeedNewScreen(
|
||||||
|
pubName: state.pathParameters['name']!,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: ':feedId',
|
||||||
|
builder: (context, state) {
|
||||||
|
return WebFeedEditScreen(
|
||||||
|
pubName: state.pathParameters['name']!,
|
||||||
|
feedId: state.pathParameters['feedId'],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/posts',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return CreatorPostListScreen(pubName: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/stickers',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return StickersScreen(pubName: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/stickers/new',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return NewStickerPacksScreen(pubName: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/stickers/:packId/edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
return EditStickerPacksScreen(pubName: name, packId: packId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/stickers/:packId',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
return StickerPackDetailScreen(pubName: name, id: packId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/stickers/:packId/new',
|
||||||
|
builder: (context, state) {
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
return NewStickersScreen(packId: packId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/stickers/:packId/:id/edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final packId = state.pathParameters['packId']!;
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return EditStickersScreen(id: id, packId: packId);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/new',
|
||||||
|
builder: (context, state) => const NewPublisherScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/creators/:name/edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return EditPublisherScreen(name: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
ShellRoute(
|
||||||
|
builder:
|
||||||
|
(context, state, child) =>
|
||||||
|
DeveloperHubShellScreen(child: child),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/developers',
|
||||||
|
builder: (context, state) => const DeveloperHubScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/developers/:name/apps',
|
||||||
|
builder:
|
||||||
|
(context, state) => CustomAppsScreen(
|
||||||
|
publisherName: state.pathParameters['name']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/developers/:name/apps/new',
|
||||||
|
builder:
|
||||||
|
(context, state) => NewCustomAppScreen(
|
||||||
|
publisherName: state.pathParameters['name']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/developers/:name/apps/:id',
|
||||||
|
builder:
|
||||||
|
(context, state) => EditAppScreen(
|
||||||
|
publisherName: state.pathParameters['name']!,
|
||||||
|
id: state.pathParameters['id']!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
// Main tabs with bottom navigation and shell routes for desktop layout
|
// Web articles
|
||||||
AutoRoute(
|
GoRoute(
|
||||||
page: TabsRoute.page,
|
path: '/feeds/articles',
|
||||||
path: '',
|
builder: (context, state) => const ArticlesScreen(),
|
||||||
children: [
|
),
|
||||||
AutoRoute(
|
GoRoute(
|
||||||
page: ExploreShellRoute.page,
|
path: '/feeds/articles/:id',
|
||||||
path: '',
|
builder: (context, state) {
|
||||||
children: [
|
final id = state.pathParameters['id']!;
|
||||||
AutoRoute(page: ExploreRoute.page, path: ''),
|
return ArticleDetailScreen(articleId: id);
|
||||||
AutoRoute(page: PostDetailRoute.page, path: 'posts/:id'),
|
},
|
||||||
AutoRoute(
|
),
|
||||||
page: PublisherProfileRoute.page,
|
|
||||||
path: 'publishers/:name',
|
// Auth routes
|
||||||
|
GoRoute(
|
||||||
|
path: '/auth/login',
|
||||||
|
builder: (context, state) => const LoginScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/auth/create-account',
|
||||||
|
builder: (context, state) => const CreateAccountScreen(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Other routes
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings',
|
||||||
|
builder: (context, state) => const SettingsScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/about',
|
||||||
|
builder: (context, state) => const AboutScreen(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Main tabs with TabsScreen shell
|
||||||
|
ShellRoute(
|
||||||
|
navigatorKey: _tabsShellKey,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return TabsScreen(child: child);
|
||||||
|
},
|
||||||
|
routes: [
|
||||||
|
// Explore tab
|
||||||
|
ShellRoute(
|
||||||
|
builder:
|
||||||
|
(context, state, child) => ExploreShellScreen(child: child),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/',
|
||||||
|
builder: (context, state) => const ExploreScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/posts/search',
|
||||||
|
builder: (context, state) => const PostSearchScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/posts/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return PostDetailScreen(id: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/publishers/:name',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return PublisherProfileScreen(name: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/discovery/realms',
|
||||||
|
builder: (context, state) => const DiscoveryRealmsScreen(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(
|
|
||||||
page: AccountShellRoute.page,
|
// Chat tab
|
||||||
path: 'account',
|
ShellRoute(
|
||||||
children: [
|
builder:
|
||||||
AutoRoute(page: AccountRoute.page, path: ''),
|
(context, state, child) => ChatShellScreen(child: child),
|
||||||
AutoRoute(page: NotificationRoute.page, path: 'notifications'),
|
routes: [
|
||||||
AutoRoute(page: WalletRoute.page, path: 'wallet'),
|
GoRoute(
|
||||||
AutoRoute(page: RelationshipRoute.page, path: 'relationships'),
|
path: '/chat',
|
||||||
AutoRoute(page: AccountProfileRoute.page, path: ':name'),
|
builder: (context, state) => const ChatListScreen(),
|
||||||
AutoRoute(page: UpdateProfileRoute.page, path: 'me/update'),
|
),
|
||||||
AutoRoute(page: LevelingRoute.page, path: 'me/leveling'),
|
GoRoute(
|
||||||
AutoRoute(page: AccountSettingsRoute.page, path: 'settings'),
|
path: '/chat/new',
|
||||||
|
builder: (context, state) => const NewChatScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/:id',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return ChatRoomScreen(id: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/:id/edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return EditChatScreen(id: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/chat/:id/detail',
|
||||||
|
builder: (context, state) {
|
||||||
|
final id = state.pathParameters['id']!;
|
||||||
|
return ChatDetailScreen(id: id);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(page: RealmListRoute.page, path: 'realms'),
|
|
||||||
AutoRoute(
|
// Realms tab
|
||||||
page: ChatShellRoute.page,
|
GoRoute(
|
||||||
path: 'chat',
|
path: '/realms',
|
||||||
children: [
|
builder: (context, state) => const RealmListScreen(),
|
||||||
AutoRoute(page: ChatListRoute.page, path: ''),
|
routes: [
|
||||||
AutoRoute(page: ChatRoomRoute.page, path: ':id'),
|
GoRoute(
|
||||||
AutoRoute(page: NewChatRoute.page, path: 'new'),
|
path: 'new',
|
||||||
AutoRoute(page: EditChatRoute.page, path: ':id/edit'),
|
builder: (context, state) => const NewRealmScreen(),
|
||||||
AutoRoute(page: ChatDetailRoute.page, path: ':id/detail'),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: ':slug',
|
||||||
|
builder: (context, state) {
|
||||||
|
final slug = state.pathParameters['slug']!;
|
||||||
|
return RealmDetailScreen(slug: slug);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: ':slug/edit',
|
||||||
|
builder: (context, state) {
|
||||||
|
final slug = state.pathParameters['slug']!;
|
||||||
|
return EditRealmScreen(slug: slug);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
// Account tab
|
||||||
|
ShellRoute(
|
||||||
|
builder:
|
||||||
|
(context, state, child) => AccountShellScreen(child: child),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
path: '/account',
|
||||||
|
builder: (context, state) => const AccountScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/notifications',
|
||||||
|
builder: (context, state) => const NotificationScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/wallet',
|
||||||
|
builder: (context, state) => const WalletScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/relationships',
|
||||||
|
builder: (context, state) => const RelationshipScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/:name',
|
||||||
|
builder: (context, state) {
|
||||||
|
final name = state.pathParameters['name']!;
|
||||||
|
return AccountProfileScreen(name: name);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/me/update',
|
||||||
|
builder: (context, state) => const UpdateProfileScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/me/leveling',
|
||||||
|
builder: (context, state) => const LevelingScreen(),
|
||||||
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/account/settings',
|
||||||
|
builder: (context, state) => const AccountSettingsScreen(),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(
|
|
||||||
page: CreatorHubShellRoute.page,
|
|
||||||
path: 'creators',
|
|
||||||
children: [
|
|
||||||
AutoRoute(page: CreatorHubRoute.page, path: ''),
|
|
||||||
AutoRoute(page: CreatorPostListRoute.page, path: ':name/posts'),
|
|
||||||
AutoRoute(page: StickersRoute.page, path: ':name/stickers'),
|
|
||||||
AutoRoute(page: NewStickerPacksRoute.page, path: ':name/stickers/new'),
|
|
||||||
AutoRoute(
|
|
||||||
page: EditStickerPacksRoute.page,
|
|
||||||
path: ':name/stickers/:packId/edit',
|
|
||||||
),
|
|
||||||
AutoRoute(
|
|
||||||
page: StickerPackDetailRoute.page,
|
|
||||||
path: ':name/stickers/:packId',
|
|
||||||
),
|
|
||||||
AutoRoute(page: NewStickersRoute.page, path: ':name/stickers/new'),
|
|
||||||
AutoRoute(
|
|
||||||
page: EditStickersRoute.page,
|
|
||||||
path: ':name/stickers/:id/edit',
|
|
||||||
),
|
|
||||||
AutoRoute(page: NewPublisherRoute.page, path: 'new'),
|
|
||||||
AutoRoute(page: EditPublisherRoute.page, path: ':name/edit'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
AutoRoute(page: LoginRoute.page, path: 'auth/login'),
|
],
|
||||||
AutoRoute(page: CreateAccountRoute.page, path: 'auth/create-account'),
|
);
|
||||||
AutoRoute(page: SettingsRoute.page, path: 'settings'),
|
});
|
||||||
AutoRoute(page: NewRealmRoute.page, path: 'realms/new'),
|
|
||||||
AutoRoute(page: RealmDetailRoute.page, path: 'realms/:slug'),
|
// Navigation helper functions
|
||||||
AutoRoute(page: EditRealmRoute.page, path: 'realms/:slug/edit'),
|
class AppRouter {
|
||||||
];
|
static GoRouter of(BuildContext context) {
|
||||||
|
return GoRouter.of(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void go(BuildContext context, String path) {
|
||||||
|
context.go(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void push(BuildContext context, String path) {
|
||||||
|
context.push(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pop(BuildContext context) {
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool canPop(BuildContext context) {
|
||||||
|
return context.canPop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1773
lib/route.gr.dart
1773
lib/route.gr.dart
File diff suppressed because it is too large
Load Diff
300
lib/screens/about.dart
Normal file
300
lib/screens/about.dart
Normal file
@@ -0,0 +1,300 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
class AboutScreen extends StatefulWidget {
|
||||||
|
const AboutScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AboutScreen> createState() => _AboutScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AboutScreenState extends State<AboutScreen> {
|
||||||
|
PackageInfo _packageInfo = PackageInfo(
|
||||||
|
appName: 'Island',
|
||||||
|
packageName: 'com.example.island',
|
||||||
|
version: '1.0.0',
|
||||||
|
buildNumber: '1',
|
||||||
|
);
|
||||||
|
bool _isLoading = true;
|
||||||
|
String? _errorMessage;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_initPackageInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initPackageInfo() async {
|
||||||
|
try {
|
||||||
|
final info = await PackageInfo.fromPlatform();
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_packageInfo = info;
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_errorMessage = 'Failed to load package info: $e';
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _launchURL(String url) async {
|
||||||
|
final uri = Uri.parse(url);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('About'), elevation: 0),
|
||||||
|
body:
|
||||||
|
_isLoading
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: _errorMessage != null
|
||||||
|
? Center(child: Text(_errorMessage!))
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
// App Icon and Name
|
||||||
|
CircleAvatar(
|
||||||
|
radius: 50,
|
||||||
|
backgroundColor: theme.colorScheme.primary.withOpacity(
|
||||||
|
0.1,
|
||||||
|
),
|
||||||
|
child: Image.asset(
|
||||||
|
'assets/icons/icon.png',
|
||||||
|
width: 56,
|
||||||
|
height: 56,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(
|
||||||
|
_packageInfo.appName,
|
||||||
|
style: theme.textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Version ${_packageInfo.version} (${_packageInfo.buildNumber})',
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.textTheme.bodySmall?.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// App Info Card
|
||||||
|
_buildSection(
|
||||||
|
context,
|
||||||
|
title: 'App Information',
|
||||||
|
children: [
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.info_outline,
|
||||||
|
label: 'Package Name',
|
||||||
|
value: _packageInfo.packageName,
|
||||||
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.update,
|
||||||
|
label: 'Version',
|
||||||
|
value: _packageInfo.version,
|
||||||
|
),
|
||||||
|
_buildInfoItem(
|
||||||
|
context,
|
||||||
|
icon: Icons.build,
|
||||||
|
label: 'Build Number',
|
||||||
|
value: _packageInfo.buildNumber,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Links Card
|
||||||
|
_buildSection(
|
||||||
|
context,
|
||||||
|
title: 'Links',
|
||||||
|
children: [
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Icons.privacy_tip_outlined,
|
||||||
|
title: 'Privacy Policy',
|
||||||
|
onTap:
|
||||||
|
() => _launchURL(
|
||||||
|
'https://solsynth.dev/terms/privacy-policy',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Icons.description_outlined,
|
||||||
|
title: 'Terms of Service',
|
||||||
|
onTap:
|
||||||
|
() => _launchURL(
|
||||||
|
'https://example.com/terms/basic-law',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Icons.code,
|
||||||
|
title: 'Open Source Licenses',
|
||||||
|
onTap: () {
|
||||||
|
showLicensePage(
|
||||||
|
context: context,
|
||||||
|
applicationName: _packageInfo.appName,
|
||||||
|
applicationVersion:
|
||||||
|
'Version ${_packageInfo.version}',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// Developer Info
|
||||||
|
_buildSection(
|
||||||
|
context,
|
||||||
|
title: 'Developer',
|
||||||
|
children: [
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Icons.email_outlined,
|
||||||
|
title: 'Contact Us',
|
||||||
|
subtitle: 'lily@solsynth.dev',
|
||||||
|
onTap: () => _launchURL('mailto:lily@solsynth.dev'),
|
||||||
|
),
|
||||||
|
_buildListTile(
|
||||||
|
context,
|
||||||
|
icon: Icons.copyright,
|
||||||
|
title: 'License',
|
||||||
|
subtitle:
|
||||||
|
'Copyright reserved © ${DateTime.now().year} Solsynth\nGNU Affero General Public License v3.0',
|
||||||
|
onTap:
|
||||||
|
() => _launchURL(
|
||||||
|
'https://github.com/Solsynth/Solian/blob/v3/LICENSE.txt',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// Copyright
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
'© ${DateTime.now().year} ${_packageInfo.appName}. All rights reserved.',
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSection(
|
||||||
|
BuildContext context, {
|
||||||
|
required String title,
|
||||||
|
required List<Widget> children,
|
||||||
|
}) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(
|
||||||
|
context,
|
||||||
|
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
...children,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoItem(
|
||||||
|
BuildContext context, {
|
||||||
|
required IconData icon,
|
||||||
|
required String label,
|
||||||
|
required String value,
|
||||||
|
}) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(icon, size: 20, color: Theme.of(context).hintColor),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(label, style: Theme.of(context).textTheme.bodySmall),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
SelectableText(
|
||||||
|
value,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (value.startsWith('http') || value.contains('@'))
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.copy, size: 16),
|
||||||
|
onPressed: () {
|
||||||
|
Clipboard.setData(ClipboardData(text: value));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('Copied to clipboard')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
padding: EdgeInsets.zero,
|
||||||
|
constraints: const BoxConstraints(),
|
||||||
|
tooltip: 'Copy to clipboard',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildListTile(
|
||||||
|
BuildContext context, {
|
||||||
|
required IconData icon,
|
||||||
|
required String title,
|
||||||
|
String? subtitle,
|
||||||
|
required VoidCallback onTap,
|
||||||
|
}) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
leading: Icon(icon),
|
||||||
|
title: Text(title),
|
||||||
|
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||||
|
trailing: const Icon(Icons.chevron_right),
|
||||||
|
onTap: onTap,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
minLeadingWidth: 24,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/message.dart';
|
import 'package:island/pods/message.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/notification.dart';
|
import 'package:island/screens/notification.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/account_name.dart';
|
import 'package:island/widgets/account/account_name.dart';
|
||||||
@@ -19,9 +18,9 @@ import 'package:island/widgets/content/cloud_files.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class AccountShellScreen extends HookConsumerWidget {
|
class AccountShellScreen extends HookConsumerWidget {
|
||||||
const AccountShellScreen({super.key});
|
final Widget child;
|
||||||
|
const AccountShellScreen({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -34,17 +33,16 @@ class AccountShellScreen extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Flexible(flex: 2, child: AccountScreen(isAside: true)),
|
Flexible(flex: 2, child: AccountScreen(isAside: true)),
|
||||||
VerticalDivider(width: 1),
|
VerticalDivider(width: 1),
|
||||||
Flexible(flex: 3, child: AutoRouter()),
|
Flexible(flex: 3, child: child),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppBackground(isRoot: true, child: AutoRouter());
|
return AppBackground(isRoot: true, child: child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class AccountScreen extends HookConsumerWidget {
|
class AccountScreen extends HookConsumerWidget {
|
||||||
final bool isAside;
|
final bool isAside;
|
||||||
const AccountScreen({super.key, this.isAside = false});
|
const AccountScreen({super.key, this.isAside = false});
|
||||||
@@ -100,9 +98,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
radius: 24,
|
radius: 24,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(
|
context.push('/account/${user.value!.name}');
|
||||||
AccountProfileRoute(name: user.value!.name),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -147,7 +143,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
progress: user.value!.profile.levelingProgress,
|
progress: user.value!.profile.levelingProgress,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(LevelingRoute());
|
context.push('/account/me/leveling');
|
||||||
},
|
},
|
||||||
).padding(horizontal: 12),
|
).padding(horizontal: 12),
|
||||||
Row(
|
Row(
|
||||||
@@ -165,7 +161,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
).padding(horizontal: 16, vertical: 12),
|
).padding(horizontal: 16, vertical: 12),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(CreatorHubShellRoute());
|
context.push('/creators');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
).height(140),
|
).height(140),
|
||||||
@@ -182,7 +178,9 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
Text('developerPortalDescription').tr(),
|
Text('developerPortalDescription').tr(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 16, vertical: 12),
|
).padding(horizontal: 16, vertical: 12),
|
||||||
onTap: () {},
|
onTap: () {
|
||||||
|
context.push('/developers');
|
||||||
|
},
|
||||||
),
|
),
|
||||||
).height(140),
|
).height(140),
|
||||||
),
|
),
|
||||||
@@ -204,7 +202,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(NotificationRoute());
|
context.push('/account/notifications');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -214,7 +212,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('wallet').tr(),
|
title: Text('wallet').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(WalletRoute());
|
context.push('/account/wallet');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -224,7 +222,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('relationships').tr(),
|
title: Text('relationships').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(RelationshipRoute());
|
context.push('/account/relationship');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1).padding(vertical: 8),
|
const Divider(height: 1).padding(vertical: 8),
|
||||||
@@ -235,7 +233,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('appSettings').tr(),
|
title: Text('appSettings').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(SettingsRoute());
|
context.push('/settings');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -245,7 +243,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('updateYourProfile').tr(),
|
title: Text('updateYourProfile').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(UpdateProfileRoute());
|
context.push('/account/me/update');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
@@ -255,7 +253,7 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
title: Text('accountSettings').tr(),
|
title: Text('accountSettings').tr(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(AccountSettingsRoute());
|
context.push('/account/me/settings');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (kDebugMode) const Divider(height: 1).padding(vertical: 8),
|
if (kDebugMode) const Divider(height: 1).padding(vertical: 8),
|
||||||
@@ -283,6 +281,16 @@ class AccountScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1).padding(vertical: 8),
|
const Divider(height: 1).padding(vertical: 8),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
leading: const Icon(Symbols.info),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
title: Text('about').tr(),
|
||||||
|
onTap: () {
|
||||||
|
context.push('/about');
|
||||||
|
},
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
leading: const Icon(Symbols.logout),
|
leading: const Icon(Symbols.logout),
|
||||||
@@ -320,7 +328,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
child: Card(
|
child: Card(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(CreateAccountRoute());
|
context.push('/auth/create');
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -342,7 +350,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
child: Card(
|
child: Card(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(LoginRoute());
|
context.push('/auth/login');
|
||||||
},
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -361,7 +369,7 @@ class _UnauthorizedAccountScreen extends StatelessWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.router.push(SettingsRoute());
|
context.push('/settings');
|
||||||
},
|
},
|
||||||
child: Text('appSettings').tr(),
|
child: Text('appSettings').tr(),
|
||||||
).center(),
|
).center(),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -12,10 +11,9 @@ import 'package:island/widgets/account/event_calendar.dart';
|
|||||||
import 'package:island/widgets/account/fortune_graph.dart';
|
import 'package:island/widgets/account/fortune_graph.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class EventCalanderScreen extends HookConsumerWidget {
|
class EventCalanderScreen extends HookConsumerWidget {
|
||||||
final String name;
|
final String name;
|
||||||
const EventCalanderScreen({super.key, @PathParam("name") required this.name});
|
const EventCalanderScreen({super.key, required this.name});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@@ -31,7 +30,6 @@ Future<SnWalletSubscription?> accountStellarSubscription(Ref ref) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class LevelingScreen extends HookConsumerWidget {
|
class LevelingScreen extends HookConsumerWidget {
|
||||||
const LevelingScreen({super.key});
|
const LevelingScreen({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:auto_route/annotations.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@@ -51,7 +50,6 @@ Future<List<SnAccountConnection>> accountConnections(Ref ref) async {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class AccountSettingsScreen extends HookConsumerWidget {
|
class AccountSettingsScreen extends HookConsumerWidget {
|
||||||
const AccountSettingsScreen({super.key});
|
const AccountSettingsScreen({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:croppy/croppy.dart' hide cropImage;
|
import 'package:croppy/croppy.dart' hide cropImage;
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@@ -20,7 +19,6 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
|
|
||||||
const kServerSupportedLanguages = {'en-US': 'en-us', 'zh-CN': 'zh-hans'};
|
const kServerSupportedLanguages = {'en-US': 'en-us', 'zh-CN': 'zh-hans'};
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class UpdateProfileScreen extends HookConsumerWidget {
|
class UpdateProfileScreen extends HookConsumerWidget {
|
||||||
const UpdateProfileScreen({super.key});
|
const UpdateProfileScreen({super.key});
|
||||||
|
|
||||||
@@ -343,7 +341,10 @@ class UpdateProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
TextFormField(
|
TextFormField(
|
||||||
decoration: InputDecoration(labelText: 'bio'.tr()),
|
decoration: InputDecoration(
|
||||||
|
labelText: 'bio'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
controller: bioController,
|
controller: bioController,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
@@ -53,6 +53,7 @@ Future<List<SnAccountBadge>> accountBadges(Ref ref, String uname) async {
|
|||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async {
|
Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async {
|
||||||
|
try {
|
||||||
final account = await ref.watch(accountProvider(uname).future);
|
final account = await ref.watch(accountProvider(uname).future);
|
||||||
if (account.profile.background == null) return null;
|
if (account.profile.background == null) return null;
|
||||||
final palette = await PaletteGenerator.fromImageProvider(
|
final palette = await PaletteGenerator.fromImageProvider(
|
||||||
@@ -64,6 +65,9 @@ Future<Color?> accountAppbarForcegroundColor(Ref ref, String uname) async {
|
|||||||
final dominantColor = palette.dominantColor?.color;
|
final dominantColor = palette.dominantColor?.color;
|
||||||
if (dominantColor == null) return null;
|
if (dominantColor == null) return null;
|
||||||
return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
@@ -96,13 +100,9 @@ Future<SnRelationship?> accountRelationship(Ref ref, String uname) async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class AccountProfileScreen extends HookConsumerWidget {
|
class AccountProfileScreen extends HookConsumerWidget {
|
||||||
final String name;
|
final String name;
|
||||||
const AccountProfileScreen({
|
const AccountProfileScreen({super.key, required this.name});
|
||||||
super.key,
|
|
||||||
@PathParam("name") required this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -142,7 +142,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
Future<void> directMessageAction() async {
|
Future<void> directMessageAction() async {
|
||||||
if (!account.hasValue) return;
|
if (!account.hasValue) return;
|
||||||
if (accountChat.value != null) {
|
if (accountChat.value != null) {
|
||||||
context.router.pushPath('/chat/${accountChat.value!.id}');
|
context.push('/chat/${accountChat.value!.id}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
@@ -153,7 +153,7 @@ class AccountProfileScreen extends HookConsumerWidget {
|
|||||||
data: {'related_user_id': account.value!.id},
|
data: {'related_user_id': account.value!.id},
|
||||||
);
|
);
|
||||||
final chat = SnChatRoom.fromJson(resp.data);
|
final chat = SnChatRoom.fromJson(resp.data);
|
||||||
if (context.mounted) context.router.pushPath('/chat/${chat.id}');
|
if (context.mounted) context.push('/chat/${chat.id}');
|
||||||
ref.invalidate(accountDirectChatProvider(name));
|
ref.invalidate(accountDirectChatProvider(name));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ class _AccountBadgesProviderElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$accountAppbarForcegroundColorHash() =>
|
String _$accountAppbarForcegroundColorHash() =>
|
||||||
r'f654a7a5594eda1500906e9ad023c22772257a9b';
|
r'8ee0cae10817b77fb09548a482f5247662b4374c';
|
||||||
|
|
||||||
/// See also [accountAppbarForcegroundColor].
|
/// See also [accountAppbarForcegroundColor].
|
||||||
@ProviderFor(accountAppbarForcegroundColor)
|
@ProviderFor(accountAppbarForcegroundColor)
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -204,7 +203,6 @@ class RelationshipListTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class RelationshipScreen extends HookConsumerWidget {
|
class RelationshipScreen extends HookConsumerWidget {
|
||||||
const RelationshipScreen({super.key});
|
const RelationshipScreen({super.key});
|
||||||
|
|
||||||
@@ -217,6 +215,7 @@ class RelationshipScreen extends HookConsumerWidget {
|
|||||||
Future<void> addFriend() async {
|
Future<void> addFriend() async {
|
||||||
final result = await showModalBottomSheet(
|
final result = await showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => AccountPickerSheet(),
|
builder: (context) => AccountPickerSheet(),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|||||||
105
lib/screens/article_detail_screen.dart
Normal file
105
lib/screens/article_detail_screen.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
|
import 'package:island/pods/article_detail.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/loading_indicator.dart';
|
||||||
|
import 'package:html2md/html2md.dart' as html2md;
|
||||||
|
|
||||||
|
class ArticleDetailScreen extends ConsumerWidget {
|
||||||
|
final String articleId;
|
||||||
|
|
||||||
|
const ArticleDetailScreen({super.key, required this.articleId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final articleAsync = ref.watch(articleDetailProvider(articleId));
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
body: articleAsync.when(
|
||||||
|
data:
|
||||||
|
(article) => AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: const BackButton(),
|
||||||
|
title: Text(article.title),
|
||||||
|
),
|
||||||
|
body: _ArticleDetailContent(article: article),
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: LoadingIndicator()),
|
||||||
|
error:
|
||||||
|
(error, stackTrace) =>
|
||||||
|
Center(child: Text('Failed to load article: $error')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArticleDetailContent extends HookConsumerWidget {
|
||||||
|
final SnWebArticle article;
|
||||||
|
|
||||||
|
const _ArticleDetailContent({required this.article});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final markdownContent = useMemoized(
|
||||||
|
() => html2md.convert(article.content ?? ''),
|
||||||
|
[article],
|
||||||
|
);
|
||||||
|
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
if (article.preview?.imageUrl != null)
|
||||||
|
Image.network(
|
||||||
|
article.preview!.imageUrl!,
|
||||||
|
width: double.infinity,
|
||||||
|
height: 200,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
article.title,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
if (article.feed?.title != null)
|
||||||
|
Text(
|
||||||
|
article.feed!.title,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
if (article.content != null)
|
||||||
|
...MarkdownTextContent.buildGenerator(
|
||||||
|
isDark: Theme.of(context).brightness == Brightness.dark,
|
||||||
|
).buildWidgets(markdownContent)
|
||||||
|
else if (article.preview?.description != null)
|
||||||
|
Text(article.preview!.description!),
|
||||||
|
const Gap(24),
|
||||||
|
FilledButton(
|
||||||
|
onPressed:
|
||||||
|
() => launchUrlString(
|
||||||
|
article.url,
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
|
),
|
||||||
|
child: const Text('Read Full Article'),
|
||||||
|
),
|
||||||
|
Gap(MediaQuery.of(context).padding.bottom),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:email_validator/email_validator.dart';
|
import 'package:email_validator/email_validator.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/account/me/update.dart';
|
import 'package:island/screens/account/me/update.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -16,7 +15,6 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||||||
|
|
||||||
import 'captcha.dart';
|
import 'captcha.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class CreateAccountScreen extends HookConsumerWidget {
|
class CreateAccountScreen extends HookConsumerWidget {
|
||||||
const CreateAccountScreen({super.key});
|
const CreateAccountScreen({super.key});
|
||||||
|
|
||||||
@@ -307,7 +305,7 @@ class _PostCreateModal extends HookConsumerWidget {
|
|||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
context.router.replace(LoginRoute());
|
context.pushReplacement('/auth/login');
|
||||||
},
|
},
|
||||||
child: Text('login'.tr()),
|
child: Text('login'.tr()),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'dart:io';
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:animations/animations.dart';
|
import 'package:animations/animations.dart';
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@@ -43,7 +42,6 @@ final Map<int, (String, String, IconData)> kFactorTypes = {
|
|||||||
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
|
4: ('authFactorPin', 'authFactorPinDescription', Symbols.nest_secure_alarm),
|
||||||
};
|
};
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class LoginScreen extends HookConsumerWidget {
|
class LoginScreen extends HookConsumerWidget {
|
||||||
const LoginScreen({super.key});
|
const LoginScreen({super.key});
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/annotations.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -14,10 +13,9 @@ import 'package:livekit_client/livekit_client.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class CallScreen extends HookConsumerWidget {
|
class CallScreen extends HookConsumerWidget {
|
||||||
final String roomId;
|
final String roomId;
|
||||||
const CallScreen({super.key, @PathParam('id') required this.roomId});
|
const CallScreen({super.key, required this.roomId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:croppy/croppy.dart' hide cropImage;
|
import 'package:croppy/croppy.dart' hide cropImage;
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -15,7 +15,6 @@ import 'package:island/pods/call.dart';
|
|||||||
import 'package:island/pods/chat_summary.dart';
|
import 'package:island/pods/chat_summary.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/realm/realms.dart';
|
import 'package:island/screens/realm/realms.dart';
|
||||||
import 'package:island/services/file.dart';
|
import 'package:island/services/file.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
@@ -173,9 +172,9 @@ Future<List<SnChatRoom>> chatroomsJoined(Ref ref) async {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ChatShellScreen extends HookConsumerWidget {
|
class ChatShellScreen extends HookConsumerWidget {
|
||||||
const ChatShellScreen({super.key});
|
final Widget child;
|
||||||
|
const ChatShellScreen({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -187,18 +186,17 @@ class ChatShellScreen extends HookConsumerWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(flex: 2, child: ChatListScreen(isAside: true)),
|
Flexible(flex: 2, child: ChatListScreen(isAside: true)),
|
||||||
VerticalDivider(width: 1),
|
const VerticalDivider(width: 1),
|
||||||
Flexible(flex: 4, child: AutoRouter()),
|
Flexible(flex: 4, child: child),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppBackground(isRoot: true, child: AutoRouter());
|
return AppBackground(isRoot: true, child: child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ChatListScreen extends HookConsumerWidget {
|
class ChatListScreen extends HookConsumerWidget {
|
||||||
final bool isAside;
|
final bool isAside;
|
||||||
const ChatListScreen({super.key, this.isAside = false});
|
const ChatListScreen({super.key, this.isAside = false});
|
||||||
@@ -229,7 +227,8 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
Future<void> createDirectMessage() async {
|
Future<void> createDirectMessage() async {
|
||||||
final result = await showModalBottomSheet(
|
final result = await showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AccountPickerSheet(),
|
useRootNavigator: true,
|
||||||
|
builder: (context) => const AccountPickerSheet(),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
final client = ref.read(apiClientProvider);
|
final client = ref.read(apiClientProvider);
|
||||||
@@ -244,7 +243,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
extendBody: false, // Prevent conflicts with tabs navigation
|
extendBody: false, // Prevent conflicts with tabs navigation
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('chat').tr(),
|
title: const Text('chat').tr(),
|
||||||
bottom: TabBar(
|
bottom: TabBar(
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
tabs: [
|
tabs: [
|
||||||
@@ -298,7 +297,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => _ChatInvitesSheet(),
|
builder: (context) => const _ChatInvitesSheet(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -309,17 +308,18 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder:
|
builder:
|
||||||
(context) => Column(
|
(context) => Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('createChatRoom').tr(),
|
title: const Text('createChatRoom').tr(),
|
||||||
leading: const Icon(Symbols.add),
|
leading: const Icon(Symbols.add),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
context.pushRoute(NewChatRoute()).then((value) {
|
context.push('/chat/new').then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
}
|
}
|
||||||
@@ -327,7 +327,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text('createDirectMessage').tr(),
|
title: const Text('createDirectMessage').tr(),
|
||||||
leading: const Icon(Symbols.person),
|
leading: const Icon(Symbols.person),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -400,16 +400,7 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
room: item,
|
room: item,
|
||||||
isDirect: item.type == 1,
|
isDirect: item.type == 1,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (context.router.topRoute.name ==
|
context.push('/chat/${item.id}');
|
||||||
ChatRoomRoute.name) {
|
|
||||||
context.router.replace(
|
|
||||||
ChatRoomRoute(id: item.id),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
context.router.push(
|
|
||||||
ChatRoomRoute(id: item.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -443,33 +434,45 @@ class ChatListScreen extends HookConsumerWidget {
|
|||||||
@riverpod
|
@riverpod
|
||||||
Future<SnChatRoom?> chatroom(Ref ref, String? identifier) async {
|
Future<SnChatRoom?> chatroom(Ref ref, String? identifier) async {
|
||||||
if (identifier == null) return null;
|
if (identifier == null) return null;
|
||||||
|
try {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.get('/chat/$identifier');
|
final resp = await client.get('/chat/$identifier');
|
||||||
return SnChatRoom.fromJson(resp.data);
|
return SnChatRoom.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (err is DioException && err.response?.statusCode == 404) {
|
||||||
|
return null; // Chat room not found
|
||||||
|
}
|
||||||
|
rethrow; // Rethrow other errors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async {
|
Future<SnChatMember?> chatroomIdentity(Ref ref, String? identifier) async {
|
||||||
if (identifier == null) return null;
|
if (identifier == null) return null;
|
||||||
|
try {
|
||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
final resp = await client.get('/chat/$identifier/members/me');
|
final resp = await client.get('/chat/$identifier/members/me');
|
||||||
return SnChatMember.fromJson(resp.data);
|
return SnChatMember.fromJson(resp.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (err is DioException && err.response?.statusCode == 404) {
|
||||||
|
return null; // Chat member not found
|
||||||
|
}
|
||||||
|
rethrow; // Rethrow other errors
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class NewChatScreen extends StatelessWidget {
|
class NewChatScreen extends StatelessWidget {
|
||||||
const NewChatScreen({super.key});
|
const NewChatScreen({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return EditChatScreen();
|
return const EditChatScreen();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class EditChatScreen extends HookConsumerWidget {
|
class EditChatScreen extends HookConsumerWidget {
|
||||||
final String? id;
|
final String? id;
|
||||||
const EditChatScreen({super.key, @PathParam("id") this.id});
|
const EditChatScreen({super.key, this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -481,6 +484,8 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
final descriptionController = useTextEditingController();
|
final descriptionController = useTextEditingController();
|
||||||
final picture = useState<SnCloudFile?>(null);
|
final picture = useState<SnCloudFile?>(null);
|
||||||
final background = useState<SnCloudFile?>(null);
|
final background = useState<SnCloudFile?>(null);
|
||||||
|
final isPublic = useState(true);
|
||||||
|
final isCommunity = useState(false);
|
||||||
|
|
||||||
final chat = ref.watch(chatroomProvider(id));
|
final chat = ref.watch(chatroomProvider(id));
|
||||||
|
|
||||||
@@ -493,12 +498,14 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
descriptionController.text = chat.value!.description ?? '';
|
descriptionController.text = chat.value!.description ?? '';
|
||||||
picture.value = chat.value!.picture;
|
picture.value = chat.value!.picture;
|
||||||
background.value = chat.value!.background;
|
background.value = chat.value!.background;
|
||||||
|
isPublic.value = chat.value!.isPublic;
|
||||||
|
isCommunity.value = chat.value!.isCommunity;
|
||||||
currentRealm.value = joinedRealms.value?.firstWhereOrNull(
|
currentRealm.value = joinedRealms.value?.firstWhereOrNull(
|
||||||
(realm) => realm.id == chat.value!.realmId,
|
(realm) => realm.id == chat.value!.realmId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}, [chat]);
|
}, [chat, joinedRealms]);
|
||||||
|
|
||||||
void setPicture(String position) async {
|
void setPicture(String position) async {
|
||||||
showLoadingModal(context);
|
showLoadingModal(context);
|
||||||
@@ -516,9 +523,9 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
image: result,
|
image: result,
|
||||||
allowedAspectRatios: [
|
allowedAspectRatios: [
|
||||||
if (position == 'background')
|
if (position == 'background')
|
||||||
CropAspectRatio(height: 7, width: 16)
|
const CropAspectRatio(height: 7, width: 16)
|
||||||
else
|
else
|
||||||
CropAspectRatio(height: 1, width: 1),
|
const CropAspectRatio(height: 1, width: 1),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
@@ -575,11 +582,13 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
'background_id': background.value?.id,
|
'background_id': background.value?.id,
|
||||||
'picture_id': picture.value?.id,
|
'picture_id': picture.value?.id,
|
||||||
'realm_id': currentRealm.value?.id,
|
'realm_id': currentRealm.value?.id,
|
||||||
|
'is_public': isPublic.value,
|
||||||
|
'is_community': isCommunity.value,
|
||||||
},
|
},
|
||||||
options: Options(method: id == null ? 'POST' : 'PATCH'),
|
options: Options(method: id == null ? 'POST' : 'PATCH'),
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.maybePop(SnChatRoom.fromJson(resp.data));
|
context.pop(SnChatRoom.fromJson(resp.data));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
@@ -660,13 +669,48 @@ class EditChatScreen extends HookConsumerWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: descriptionController,
|
controller: descriptionController,
|
||||||
decoration: const InputDecoration(labelText: 'Description'),
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Description',
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.public),
|
||||||
|
title: Text('publicChat').tr(),
|
||||||
|
subtitle: Text('publicChatDescription').tr(),
|
||||||
|
value: isPublic.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
isPublic.value = value ?? true;
|
||||||
|
},
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
secondary: const Icon(Symbols.travel_explore),
|
||||||
|
title: Text('communityChat').tr(),
|
||||||
|
subtitle: Text('communityChatDescription').tr(),
|
||||||
|
value: isCommunity.value,
|
||||||
|
onChanged: (value) {
|
||||||
|
isCommunity.value = value ?? false;
|
||||||
|
},
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
@@ -767,7 +811,7 @@ class _ChatInvitesSheet extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
if (invite.chatRoom!.type == 1)
|
if (invite.chatRoom!.type == 1)
|
||||||
Badge(
|
Badge(
|
||||||
label: Text('directMessage').tr(),
|
label: const Text('directMessage').tr(),
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
Theme.of(context).colorScheme.primary,
|
Theme.of(context).colorScheme.primary,
|
||||||
textColor:
|
textColor:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ final chatroomsJoinedProvider =
|
|||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChatRoom>>;
|
typedef ChatroomsJoinedRef = AutoDisposeFutureProviderRef<List<SnChatRoom>>;
|
||||||
String _$chatroomHash() => r'dce3c0fc407f178bb7c306a08b9fa545795a9205';
|
String _$chatroomHash() => r'8dac7aaac50932e6dd213039102d43c1cf5f1d4e';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
@@ -164,7 +164,7 @@ class _ChatroomProviderElement
|
|||||||
String? get identifier => (origin as ChatroomProvider).identifier;
|
String? get identifier => (origin as ChatroomProvider).identifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$chatroomIdentityHash() => r'4c349ea4265df7b0498cf26c82dbaabe3d868727';
|
String _$chatroomIdentityHash() => r'ad6ad09b6fc4cf7c4abe146ea97f8e364a3d4fd0';
|
||||||
|
|
||||||
/// See also [chatroomIdentity].
|
/// See also [chatroomIdentity].
|
||||||
@ProviderFor(chatroomIdentity)
|
@ProviderFor(chatroomIdentity)
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
@@ -18,7 +18,6 @@ import 'package:island/pods/config.dart';
|
|||||||
import 'package:island/pods/database.dart';
|
import 'package:island/pods/database.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -288,15 +287,76 @@ class MessagesNotifier extends _$MessagesNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ChatRoomScreen extends HookConsumerWidget {
|
class ChatRoomScreen extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
const ChatRoomScreen({super.key, @PathParam("id") required this.id});
|
const ChatRoomScreen({super.key, required this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final chatRoom = ref.watch(chatroomProvider(id));
|
final chatRoom = ref.watch(chatroomProvider(id));
|
||||||
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
final chatIdentity = ref.watch(chatroomIdentityProvider(id));
|
||||||
|
|
||||||
|
if (chatIdentity.isLoading || chatRoom.isLoading) {
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
body: CircularProgressIndicator().center(),
|
||||||
|
);
|
||||||
|
} else if (chatIdentity.value == null) {
|
||||||
|
// Identity was not found, user was not joined
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(leading: const PageBackButton()),
|
||||||
|
body: Center(
|
||||||
|
child:
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxWidth: 280),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
chatRoom.value?.isCommunity == true
|
||||||
|
? Symbols.person_add
|
||||||
|
: Symbols.person_remove,
|
||||||
|
size: 36,
|
||||||
|
fill: 1,
|
||||||
|
).padding(bottom: 4),
|
||||||
|
Text('chatNotJoined').tr(),
|
||||||
|
if (chatRoom.value?.isCommunity != true)
|
||||||
|
Text(
|
||||||
|
'chatUnableJoin',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).tr().bold()
|
||||||
|
else
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
showLoadingModal(context);
|
||||||
|
final apiClient = ref.read(apiClientProvider);
|
||||||
|
if (chatRoom.value == null) {
|
||||||
|
hideLoadingModal(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await apiClient.post(
|
||||||
|
'/chat/${chatRoom.value!.id}/members/me',
|
||||||
|
);
|
||||||
|
ref.invalidate(chatroomIdentityProvider(id));
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label: Text('chatJoin').tr(),
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
).padding(top: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).center(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
final messages = ref.watch(messagesNotifierProvider(id));
|
final messages = ref.watch(messagesNotifierProvider(id));
|
||||||
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
|
final messagesNotifier = ref.read(messagesNotifierProvider(id).notifier);
|
||||||
final ws = ref.watch(websocketProvider);
|
final ws = ref.watch(websocketProvider);
|
||||||
@@ -431,6 +491,28 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
return () => subscription.cancel();
|
return () => subscription.cancel();
|
||||||
}, [ws, chatRoom]);
|
}, [ws, chatRoom]);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
final wsState = ref.read(websocketStateProvider.notifier);
|
||||||
|
wsState.sendMessage(
|
||||||
|
jsonEncode(
|
||||||
|
WebSocketPacket(
|
||||||
|
type: 'messages.subscribe',
|
||||||
|
data: {'chat_room_id': id},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return () {
|
||||||
|
wsState.sendMessage(
|
||||||
|
jsonEncode(
|
||||||
|
WebSocketPacket(
|
||||||
|
type: 'messages.unsubscribe',
|
||||||
|
data: {'chat_room_id': id},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
Future<void> pickPhotoMedia() async {
|
Future<void> pickPhotoMedia() async {
|
||||||
final result = await ref
|
final result = await ref
|
||||||
.watch(imagePickerProvider)
|
.watch(imagePickerProvider)
|
||||||
@@ -605,7 +687,7 @@ class ChatRoomScreen extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.router.push(ChatDetailRoute(id: id));
|
context.push('/chat/$id/detail');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/chat.dart';
|
import 'package:island/models/chat.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/chat/chat.dart';
|
import 'package:island/screens/chat/chat.dart';
|
||||||
import 'package:island/widgets/account/account_picker.dart';
|
import 'package:island/widgets/account/account_picker.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
@@ -23,10 +22,9 @@ import 'package:styled_widget/styled_widget.dart';
|
|||||||
part 'room_detail.freezed.dart';
|
part 'room_detail.freezed.dart';
|
||||||
part 'room_detail.g.dart';
|
part 'room_detail.g.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ChatDetailScreen extends HookConsumerWidget {
|
class ChatDetailScreen extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
const ChatDetailScreen({super.key, @PathParam("id") required this.id});
|
const ChatDetailScreen({super.key, required this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -391,7 +389,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
|||||||
if ((chatIdentity.value?.role ?? 0) >= 50)
|
if ((chatIdentity.value?.role ?? 0) >= 50)
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.replace(EditChatRoute(id: id));
|
context.pushReplacement('/chat/$id/edit');
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -426,9 +424,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
|||||||
client.delete('/chat/$id');
|
client.delete('/chat/$id');
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.router.popUntil(
|
context.pop();
|
||||||
(route) => route is ChatRoomRoute,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -461,9 +457,7 @@ class _ChatRoomActionMenu extends HookConsumerWidget {
|
|||||||
client.delete('/chat/$id/members/me');
|
client.delete('/chat/$id/members/me');
|
||||||
ref.invalidate(chatroomsJoinedProvider);
|
ref.invalidate(chatroomsJoinedProvider);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.router.popUntil(
|
context.pop();
|
||||||
(route) => route is ChatRoomRoute,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -590,8 +584,8 @@ class _ChatMemberListSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
Future<void> invitePerson() async {
|
Future<void> invitePerson() async {
|
||||||
final result = await showModalBottomSheet(
|
final result = await showModalBottomSheet(
|
||||||
isScrollControlled: true,
|
|
||||||
context: context,
|
context: context,
|
||||||
|
useRootNavigator: true,
|
||||||
builder: (context) => const AccountPickerSheet(),
|
builder: (context) => const AccountPickerSheet(),
|
||||||
);
|
);
|
||||||
if (result == null) return;
|
if (result == null) return;
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/creators/publishers.dart';
|
import 'package:island/screens/creators/publishers.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/services/text.dart';
|
||||||
|
import 'package:island/widgets/account/account_picker.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'hub.g.dart';
|
part 'hub.g.dart';
|
||||||
@@ -27,9 +33,76 @@ Future<SnPublisherStats?> publisherStats(Ref ref, String? uname) async {
|
|||||||
return SnPublisherStats.fromJson(resp.data);
|
return SnPublisherStats.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
@riverpod
|
||||||
|
Future<SnPublisherMember?> publisherIdentity(Ref ref, String uname) async {
|
||||||
|
try {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final response = await apiClient.get('/publishers/$uname/members/me');
|
||||||
|
return SnPublisherMember.fromJson(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
if (err is DioException && err.response?.statusCode == 404) {
|
||||||
|
return null; // No identity found, user is not a member
|
||||||
|
}
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<Map<String, bool>> publisherFeatures(Ref ref, String? uname) async {
|
||||||
|
if (uname == null) return {};
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final response = await apiClient.get('/publishers/$uname/features');
|
||||||
|
return Map<String, bool>.from(response.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<SnPublisherMember>> publisherInvites(Ref ref) async {
|
||||||
|
final client = ref.watch(apiClientProvider);
|
||||||
|
final resp = await client.get('/publishers/invites');
|
||||||
|
return resp.data
|
||||||
|
.map((e) => SnPublisherMember.fromJson(e))
|
||||||
|
.cast<SnPublisherMember>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class PublisherMemberListNotifier extends _$PublisherMemberListNotifier
|
||||||
|
with CursorPagingNotifierMixin<SnPublisherMember> {
|
||||||
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnPublisherMember>> build(String uname) async {
|
||||||
|
return fetch();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnPublisherMember>> fetch({String? cursor}) async {
|
||||||
|
final apiClient = ref.read(apiClientProvider);
|
||||||
|
final offset = cursor != null ? int.parse(cursor) : 0;
|
||||||
|
|
||||||
|
final response = await apiClient.get(
|
||||||
|
'/publishers/$uname/members',
|
||||||
|
queryParameters: {'offset': offset, 'take': _pageSize},
|
||||||
|
);
|
||||||
|
|
||||||
|
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
final List<dynamic> data = response.data;
|
||||||
|
final members = data.map((e) => SnPublisherMember.fromJson(e)).toList();
|
||||||
|
|
||||||
|
final hasMore = offset + members.length < total;
|
||||||
|
final nextCursor = hasMore ? (offset + members.length).toString() : null;
|
||||||
|
|
||||||
|
return CursorPagingData(
|
||||||
|
items: members,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CreatorHubShellScreen extends StatelessWidget {
|
class CreatorHubShellScreen extends StatelessWidget {
|
||||||
const CreatorHubShellScreen({super.key});
|
final Widget child;
|
||||||
|
const CreatorHubShellScreen({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -39,15 +112,14 @@ class CreatorHubShellScreen extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
SizedBox(width: 360, child: const CreatorHubScreen(isAside: true)),
|
SizedBox(width: 360, child: const CreatorHubScreen(isAside: true)),
|
||||||
const VerticalDivider(width: 1),
|
const VerticalDivider(width: 1),
|
||||||
Expanded(child: AutoRouter()),
|
Expanded(child: child),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return AutoRouter();
|
return child;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class CreatorHubScreen extends HookConsumerWidget {
|
class CreatorHubScreen extends HookConsumerWidget {
|
||||||
final bool isAside;
|
final bool isAside;
|
||||||
const CreatorHubScreen({super.key, this.isAside = false});
|
const CreatorHubScreen({super.key, this.isAside = false});
|
||||||
@@ -60,20 +132,19 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final publishers = ref.watch(publishersManagedProvider);
|
final publishers = ref.watch(publishersManagedProvider);
|
||||||
|
final publisherInvites = ref.watch(publisherInvitesProvider);
|
||||||
final currentPublisher = useState<SnPublisher?>(
|
final currentPublisher = useState<SnPublisher?>(
|
||||||
publishers.value?.firstOrNull,
|
publishers.value?.firstOrNull,
|
||||||
);
|
);
|
||||||
|
|
||||||
void updatePublisher() {
|
void updatePublisher() {
|
||||||
context.router
|
context.push('/creators/${currentPublisher.value!.name}/edit').then((
|
||||||
.push(EditPublisherRoute(name: currentPublisher.value!.name))
|
value,
|
||||||
.then((value) async {
|
) async {
|
||||||
if (value == null) return;
|
if (value == null) return;
|
||||||
final data = await ref.refresh(publishersManagedProvider.future);
|
final data = await ref.refresh(publishersManagedProvider.future);
|
||||||
currentPublisher.value =
|
currentPublisher.value =
|
||||||
data
|
data.where((e) => e.id == currentPublisher.value!.id).firstOrNull;
|
||||||
.where((e) => e.id == currentPublisher.value!.id)
|
|
||||||
.firstOrNull;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,12 +193,40 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
publisherStatsProvider(currentPublisher.value?.name),
|
publisherStatsProvider(currentPublisher.value?.name),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final publisherFeatures = ref.watch(
|
||||||
|
publisherFeaturesProvider(currentPublisher.value?.name),
|
||||||
|
);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
noBackground: false,
|
noBackground: false,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: !isWide ? const PageBackButton() : null,
|
leading: !isWide ? const PageBackButton() : null,
|
||||||
title: Text('creatorHub').tr(),
|
title: Text('creatorHub').tr(),
|
||||||
actions: [
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: Badge(
|
||||||
|
label: Text(
|
||||||
|
publisherInvites.when(
|
||||||
|
data: (invites) => invites.length.toString(),
|
||||||
|
error: (_, _) => '0',
|
||||||
|
loading: () => '0',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
isLabelVisible: publisherInvites.when(
|
||||||
|
data: (invites) => invites.isNotEmpty,
|
||||||
|
error: (_, _) => false,
|
||||||
|
loading: () => false,
|
||||||
|
),
|
||||||
|
child: const Icon(Symbols.email),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (_) => const _PublisherInviteSheet(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
DropdownButtonHideUnderline(
|
DropdownButtonHideUnderline(
|
||||||
child: DropdownButton2<SnPublisher>(
|
child: DropdownButton2<SnPublisher>(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
@@ -205,7 +304,7 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
...(publishers.value?.map(
|
...(publishers.value?.map(
|
||||||
(publisher) => ListTile(
|
(publisher) => ListTile(
|
||||||
leading: ProfilePictureWidget(
|
leading: ProfilePictureWidget(
|
||||||
fileId: publisher.picture?.id,
|
file: publisher.picture,
|
||||||
),
|
),
|
||||||
title: Text(publisher.nick),
|
title: Text(publisher.nick),
|
||||||
subtitle: Text('@${publisher.name}'),
|
subtitle: Text('@${publisher.name}'),
|
||||||
@@ -223,7 +322,7 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
subtitle: Text('createPublisherHint').tr(),
|
subtitle: Text('createPublisherHint').tr(),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(NewPublisherRoute()).then((
|
context.push('/creators/publishers/new').then((
|
||||||
value,
|
value,
|
||||||
) {
|
) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -249,10 +348,8 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
horizontal: 24,
|
horizontal: 24,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(
|
context.push(
|
||||||
StickersRoute(
|
'/creators/${currentPublisher.value!.name}/stickers',
|
||||||
pubName: currentPublisher.value!.name,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -265,13 +362,91 @@ class CreatorHubScreen extends HookConsumerWidget {
|
|||||||
horizontal: 24,
|
horizontal: 24,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(
|
context.push(
|
||||||
CreatorPostListRoute(
|
'/creators/${currentPublisher.value!.name}/posts',
|
||||||
pubName: currentPublisher.value!.name,
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
title: Text('publisherMembers').tr(),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
leading: const Icon(Symbols.group),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => _PublisherMemberListSheet(
|
||||||
|
publisherUname:
|
||||||
|
currentPublisher.value!.name,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
title: const Text('Web Feeds').tr(),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
leading: const Icon(Symbols.rss_feed),
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.push(
|
||||||
|
'/creators/${currentPublisher.value!.name}/feeds',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ExpansionTile(
|
||||||
|
title: Text('publisherFeatures').tr(),
|
||||||
|
leading: const Icon(Symbols.flag),
|
||||||
|
tilePadding: EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
minTileHeight: 48,
|
||||||
|
children: [
|
||||||
|
...publisherFeatures.when(
|
||||||
|
data: (data) {
|
||||||
|
return data.entries.map((entry) {
|
||||||
|
final keyPrefix =
|
||||||
|
'publisherFeature${entry.key.capitalizeEachWord()}';
|
||||||
|
return ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
leading: Icon(
|
||||||
|
Symbols.circle,
|
||||||
|
color:
|
||||||
|
entry.value
|
||||||
|
? Colors.green
|
||||||
|
: Colors.red,
|
||||||
|
fill: 1,
|
||||||
|
size: 16,
|
||||||
|
).padding(left: 2, top: 4),
|
||||||
|
title: Text(keyPrefix).tr(),
|
||||||
|
subtitle: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('${keyPrefix}Description').tr(),
|
||||||
|
if (!entry.value)
|
||||||
|
Text(
|
||||||
|
'${keyPrefix}Hint',
|
||||||
|
).tr().bold(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
isThreeLine: true,
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
},
|
||||||
|
error: (_, _) => [],
|
||||||
|
loading: () => [],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
Divider(height: 1).padding(vertical: 8),
|
Divider(height: 1).padding(vertical: 8),
|
||||||
ListTile(
|
ListTile(
|
||||||
minTileHeight: 48,
|
minTileHeight: 48,
|
||||||
@@ -399,3 +574,482 @@ class _PublisherStatsWidget extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PublisherMemberState {
|
||||||
|
final List<SnPublisherMember> members;
|
||||||
|
final bool isLoading;
|
||||||
|
final int total;
|
||||||
|
final String? error;
|
||||||
|
|
||||||
|
const PublisherMemberState({
|
||||||
|
required this.members,
|
||||||
|
required this.isLoading,
|
||||||
|
required this.total,
|
||||||
|
this.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
PublisherMemberState copyWith({
|
||||||
|
List<SnPublisherMember>? members,
|
||||||
|
bool? isLoading,
|
||||||
|
int? total,
|
||||||
|
String? error,
|
||||||
|
}) {
|
||||||
|
return PublisherMemberState(
|
||||||
|
members: members ?? this.members,
|
||||||
|
isLoading: isLoading ?? this.isLoading,
|
||||||
|
total: total ?? this.total,
|
||||||
|
error: error ?? this.error,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final publisherMemberStateProvider = StateNotifierProvider.family<
|
||||||
|
PublisherMemberNotifier,
|
||||||
|
PublisherMemberState,
|
||||||
|
String
|
||||||
|
>((ref, publisherUname) {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
return PublisherMemberNotifier(apiClient, publisherUname);
|
||||||
|
});
|
||||||
|
|
||||||
|
class PublisherMemberNotifier extends StateNotifier<PublisherMemberState> {
|
||||||
|
final String publisherUname;
|
||||||
|
final Dio _apiClient;
|
||||||
|
|
||||||
|
PublisherMemberNotifier(this._apiClient, this.publisherUname)
|
||||||
|
: super(
|
||||||
|
const PublisherMemberState(members: [], isLoading: false, total: 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
Future<void> loadMore({int offset = 0, int take = 20}) async {
|
||||||
|
if (state.isLoading) return;
|
||||||
|
if (state.total > 0 && state.members.length >= state.total) return;
|
||||||
|
|
||||||
|
state = state.copyWith(isLoading: true, error: null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await _apiClient.get(
|
||||||
|
'/publishers/$publisherUname/members',
|
||||||
|
queryParameters: {'offset': offset, 'take': take},
|
||||||
|
);
|
||||||
|
|
||||||
|
final total = int.parse(response.headers.value('X-Total') ?? '0');
|
||||||
|
final List<dynamic> data = response.data;
|
||||||
|
final members = data.map((e) => SnPublisherMember.fromJson(e)).toList();
|
||||||
|
|
||||||
|
state = state.copyWith(
|
||||||
|
members: [...state.members, ...members],
|
||||||
|
total: total,
|
||||||
|
isLoading: false,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
state = state.copyWith(error: e.toString(), isLoading: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
state = const PublisherMemberState(members: [], isLoading: false, total: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherMemberListSheet extends HookConsumerWidget {
|
||||||
|
final String publisherUname;
|
||||||
|
const _PublisherMemberListSheet({required this.publisherUname});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final publisherIdentity = ref.watch(
|
||||||
|
publisherIdentityProvider(publisherUname),
|
||||||
|
);
|
||||||
|
final memberListProvider = publisherMemberListNotifierProvider(
|
||||||
|
publisherUname,
|
||||||
|
);
|
||||||
|
final memberState = ref.watch(publisherMemberStateProvider(publisherUname));
|
||||||
|
final memberNotifier = ref.read(
|
||||||
|
publisherMemberStateProvider(publisherUname).notifier,
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
Future(() {
|
||||||
|
memberNotifier.loadMore();
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
Future<void> invitePerson() async {
|
||||||
|
final result = await showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder: (context) => const AccountPickerSheet(),
|
||||||
|
);
|
||||||
|
if (result == null) return;
|
||||||
|
try {
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
await apiClient.post(
|
||||||
|
'/publishers/$publisherUname/invites',
|
||||||
|
data: {'related_user_id': result.id, 'role': 0},
|
||||||
|
);
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(top: 16, left: 20, right: 16, bottom: 12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'members'.plural(memberState.total),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: -0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.person_add),
|
||||||
|
onPressed: invitePerson,
|
||||||
|
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.refresh),
|
||||||
|
onPressed: () {
|
||||||
|
memberNotifier.reset();
|
||||||
|
memberNotifier.loadMore();
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Expanded(
|
||||||
|
child: PagingHelperView(
|
||||||
|
provider: memberListProvider,
|
||||||
|
futureRefreshable: memberListProvider.future,
|
||||||
|
notifierRefreshable: memberListProvider.notifier,
|
||||||
|
contentBuilder: (data, widgetCount, endItemView) {
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: widgetCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == data.items.length) {
|
||||||
|
return endItemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
final member = data.items[index];
|
||||||
|
return ListTile(
|
||||||
|
contentPadding: EdgeInsets.only(left: 16, right: 12),
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
fileId: member.account!.profile.picture?.id,
|
||||||
|
),
|
||||||
|
title: Row(
|
||||||
|
spacing: 6,
|
||||||
|
children: [
|
||||||
|
Flexible(child: Text(member.account!.nick)),
|
||||||
|
if (member.joinedAt == null)
|
||||||
|
const Icon(Symbols.pending_actions, size: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
member.role >= 100
|
||||||
|
? 'permissionOwner'
|
||||||
|
: member.role >= 50
|
||||||
|
? 'permissionModerator'
|
||||||
|
: 'permissionMember',
|
||||||
|
).tr(),
|
||||||
|
Text('·').bold().padding(horizontal: 6),
|
||||||
|
Expanded(child: Text("@${member.account!.name}")),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if ((publisherIdentity.value?.role ?? 0) >= 50)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.edit),
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
isScrollControlled: true,
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(context) => _PublisherMemberRoleSheet(
|
||||||
|
publisherUname: publisherUname,
|
||||||
|
member: member,
|
||||||
|
),
|
||||||
|
).then((value) {
|
||||||
|
if (value != null) {
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if ((publisherIdentity.value?.role ?? 0) >= 50)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.delete),
|
||||||
|
onPressed: () {
|
||||||
|
showConfirmAlert(
|
||||||
|
'removePublisherMemberHint'.tr(),
|
||||||
|
'removePublisherMember'.tr(),
|
||||||
|
).then((confirm) async {
|
||||||
|
if (confirm != true) return;
|
||||||
|
try {
|
||||||
|
final apiClient = ref.watch(
|
||||||
|
apiClientProvider,
|
||||||
|
);
|
||||||
|
await apiClient.delete(
|
||||||
|
'/publishers/$publisherUname/members/${member.accountId}',
|
||||||
|
);
|
||||||
|
ref.invalidate(memberListProvider);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherMemberRoleSheet extends HookConsumerWidget {
|
||||||
|
final String publisherUname;
|
||||||
|
final SnPublisherMember member;
|
||||||
|
|
||||||
|
const _PublisherMemberRoleSheet({
|
||||||
|
required this.publisherUname,
|
||||||
|
required this.member,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final roleController = useTextEditingController(
|
||||||
|
text: member.role.toString(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
child: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 16,
|
||||||
|
left: 20,
|
||||||
|
right: 16,
|
||||||
|
bottom: 12,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'memberRoleEdit'.tr(args: [member.account!.name]),
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
letterSpacing: -0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
onPressed: () => Navigator.pop(context),
|
||||||
|
style: IconButton.styleFrom(
|
||||||
|
minimumSize: const Size(36, 36),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Divider(height: 1),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Autocomplete<int>(
|
||||||
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
||||||
|
if (textEditingValue.text.isEmpty) {
|
||||||
|
return const [100, 50, 0];
|
||||||
|
}
|
||||||
|
final int? value = int.tryParse(textEditingValue.text);
|
||||||
|
if (value == null) return const [100, 50, 0];
|
||||||
|
return [100, 50, 0].where(
|
||||||
|
(option) =>
|
||||||
|
option.toString().contains(textEditingValue.text),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onSelected: (int selection) {
|
||||||
|
roleController.text = selection.toString();
|
||||||
|
},
|
||||||
|
fieldViewBuilder: (
|
||||||
|
context,
|
||||||
|
controller,
|
||||||
|
focusNode,
|
||||||
|
onFieldSubmitted,
|
||||||
|
) {
|
||||||
|
return TextField(
|
||||||
|
controller: controller,
|
||||||
|
focusNode: focusNode,
|
||||||
|
keyboardType: TextInputType.number,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'memberRole'.tr(),
|
||||||
|
helperText: 'memberRoleHint'.tr(),
|
||||||
|
),
|
||||||
|
onTapOutside: (event) => focusNode.unfocus(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () async {
|
||||||
|
try {
|
||||||
|
final newRole = int.parse(roleController.text);
|
||||||
|
if (newRole < 0 || newRole > 100) {
|
||||||
|
throw 'Role must be between 0 and 100';
|
||||||
|
}
|
||||||
|
|
||||||
|
final apiClient = ref.read(apiClientProvider);
|
||||||
|
await apiClient.patch(
|
||||||
|
'/publishers/$publisherUname/members/${member.accountId}/role',
|
||||||
|
data: newRole,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (context.mounted) Navigator.pop(context, true);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
label: const Text('saveChanges').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(vertical: 16, horizontal: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherInviteSheet extends HookConsumerWidget {
|
||||||
|
const _PublisherInviteSheet();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final invites = ref.watch(publisherInvitesProvider);
|
||||||
|
|
||||||
|
Future<void> acceptInvite(SnPublisherMember invite) async {
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.post(
|
||||||
|
'/publishers/invites/${invite.publisher!.name}/accept',
|
||||||
|
);
|
||||||
|
ref.invalidate(publisherInvitesProvider);
|
||||||
|
ref.invalidate(publishersManagedProvider);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> declineInvite(SnPublisherMember invite) async {
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.post(
|
||||||
|
'/publishers/invites/${invite.publisher!.name}/decline',
|
||||||
|
);
|
||||||
|
ref.invalidate(publisherInvitesProvider);
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: 'invites'.tr(),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.refresh),
|
||||||
|
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||||
|
onPressed: () {
|
||||||
|
ref.invalidate(publisherInvitesProvider);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: invites.when(
|
||||||
|
data:
|
||||||
|
(items) =>
|
||||||
|
items.isEmpty
|
||||||
|
? Center(
|
||||||
|
child:
|
||||||
|
Text(
|
||||||
|
'invitesEmpty',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).tr(),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final invite = items[index];
|
||||||
|
return ListTile(
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
fileId: invite.publisher!.picture?.id,
|
||||||
|
fallbackIcon: Symbols.group,
|
||||||
|
),
|
||||||
|
title: Text(invite.publisher!.nick),
|
||||||
|
subtitle:
|
||||||
|
Text(
|
||||||
|
invite.role >= 100
|
||||||
|
? 'permissionOwner'
|
||||||
|
: invite.role >= 50
|
||||||
|
? 'permissionModerator'
|
||||||
|
: 'permissionMember',
|
||||||
|
).tr(),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.check),
|
||||||
|
onPressed: () => acceptInvite(invite),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.close),
|
||||||
|
onPressed: () => declineInvite(invite),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(error, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry: () => ref.invalidate(publisherInvitesProvider),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -149,5 +149,422 @@ class _PublisherStatsProviderElement
|
|||||||
String? get uname => (origin as PublisherStatsProvider).uname;
|
String? get uname => (origin as PublisherStatsProvider).uname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _$publisherIdentityHash() => r'f7fd986a303a729ca5557022fceb37cd01fa17f3';
|
||||||
|
|
||||||
|
/// See also [publisherIdentity].
|
||||||
|
@ProviderFor(publisherIdentity)
|
||||||
|
const publisherIdentityProvider = PublisherIdentityFamily();
|
||||||
|
|
||||||
|
/// See also [publisherIdentity].
|
||||||
|
class PublisherIdentityFamily extends Family<AsyncValue<SnPublisherMember?>> {
|
||||||
|
/// See also [publisherIdentity].
|
||||||
|
const PublisherIdentityFamily();
|
||||||
|
|
||||||
|
/// See also [publisherIdentity].
|
||||||
|
PublisherIdentityProvider call(String uname) {
|
||||||
|
return PublisherIdentityProvider(uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PublisherIdentityProvider getProviderOverride(
|
||||||
|
covariant PublisherIdentityProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'publisherIdentityProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [publisherIdentity].
|
||||||
|
class PublisherIdentityProvider
|
||||||
|
extends AutoDisposeFutureProvider<SnPublisherMember?> {
|
||||||
|
/// See also [publisherIdentity].
|
||||||
|
PublisherIdentityProvider(String uname)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => publisherIdentity(ref as PublisherIdentityRef, uname),
|
||||||
|
from: publisherIdentityProvider,
|
||||||
|
name: r'publisherIdentityProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$publisherIdentityHash,
|
||||||
|
dependencies: PublisherIdentityFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PublisherIdentityFamily._allTransitiveDependencies,
|
||||||
|
uname: uname,
|
||||||
|
);
|
||||||
|
|
||||||
|
PublisherIdentityProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.uname,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String uname;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<SnPublisherMember?> Function(PublisherIdentityRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PublisherIdentityProvider._internal(
|
||||||
|
(ref) => create(ref as PublisherIdentityRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
uname: uname,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<SnPublisherMember?> createElement() {
|
||||||
|
return _PublisherIdentityProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PublisherIdentityProvider && other.uname == uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, uname.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PublisherIdentityRef on AutoDisposeFutureProviderRef<SnPublisherMember?> {
|
||||||
|
/// The parameter `uname` of this provider.
|
||||||
|
String get uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherIdentityProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<SnPublisherMember?>
|
||||||
|
with PublisherIdentityRef {
|
||||||
|
_PublisherIdentityProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uname => (origin as PublisherIdentityProvider).uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$publisherFeaturesHash() => r'34db65d9a4b6b0c6961733ae79e67f25d5d111d3';
|
||||||
|
|
||||||
|
/// See also [publisherFeatures].
|
||||||
|
@ProviderFor(publisherFeatures)
|
||||||
|
const publisherFeaturesProvider = PublisherFeaturesFamily();
|
||||||
|
|
||||||
|
/// See also [publisherFeatures].
|
||||||
|
class PublisherFeaturesFamily extends Family<AsyncValue<Map<String, bool>>> {
|
||||||
|
/// See also [publisherFeatures].
|
||||||
|
const PublisherFeaturesFamily();
|
||||||
|
|
||||||
|
/// See also [publisherFeatures].
|
||||||
|
PublisherFeaturesProvider call(String? uname) {
|
||||||
|
return PublisherFeaturesProvider(uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PublisherFeaturesProvider getProviderOverride(
|
||||||
|
covariant PublisherFeaturesProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'publisherFeaturesProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [publisherFeatures].
|
||||||
|
class PublisherFeaturesProvider
|
||||||
|
extends AutoDisposeFutureProvider<Map<String, bool>> {
|
||||||
|
/// See also [publisherFeatures].
|
||||||
|
PublisherFeaturesProvider(String? uname)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => publisherFeatures(ref as PublisherFeaturesRef, uname),
|
||||||
|
from: publisherFeaturesProvider,
|
||||||
|
name: r'publisherFeaturesProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$publisherFeaturesHash,
|
||||||
|
dependencies: PublisherFeaturesFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PublisherFeaturesFamily._allTransitiveDependencies,
|
||||||
|
uname: uname,
|
||||||
|
);
|
||||||
|
|
||||||
|
PublisherFeaturesProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.uname,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String? uname;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<Map<String, bool>> Function(PublisherFeaturesRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PublisherFeaturesProvider._internal(
|
||||||
|
(ref) => create(ref as PublisherFeaturesRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
uname: uname,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<Map<String, bool>> createElement() {
|
||||||
|
return _PublisherFeaturesProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PublisherFeaturesProvider && other.uname == uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, uname.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PublisherFeaturesRef on AutoDisposeFutureProviderRef<Map<String, bool>> {
|
||||||
|
/// The parameter `uname` of this provider.
|
||||||
|
String? get uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherFeaturesProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<Map<String, bool>>
|
||||||
|
with PublisherFeaturesRef {
|
||||||
|
_PublisherFeaturesProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get uname => (origin as PublisherFeaturesProvider).uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$publisherInvitesHash() => r'488cd443407895ce11f4edff07cb6ea58f2aa018';
|
||||||
|
|
||||||
|
/// See also [publisherInvites].
|
||||||
|
@ProviderFor(publisherInvites)
|
||||||
|
final publisherInvitesProvider =
|
||||||
|
AutoDisposeFutureProvider<List<SnPublisherMember>>.internal(
|
||||||
|
publisherInvites,
|
||||||
|
name: r'publisherInvitesProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$publisherInvitesHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef PublisherInvitesRef =
|
||||||
|
AutoDisposeFutureProviderRef<List<SnPublisherMember>>;
|
||||||
|
String _$publisherMemberListNotifierHash() =>
|
||||||
|
r'237e8f39c9757a6cbdff817853c697539242ad2a';
|
||||||
|
|
||||||
|
abstract class _$PublisherMemberListNotifier
|
||||||
|
extends
|
||||||
|
BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnPublisherMember>> {
|
||||||
|
late final String uname;
|
||||||
|
|
||||||
|
FutureOr<CursorPagingData<SnPublisherMember>> build(String uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [PublisherMemberListNotifier].
|
||||||
|
@ProviderFor(PublisherMemberListNotifier)
|
||||||
|
const publisherMemberListNotifierProvider = PublisherMemberListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [PublisherMemberListNotifier].
|
||||||
|
class PublisherMemberListNotifierFamily
|
||||||
|
extends Family<AsyncValue<CursorPagingData<SnPublisherMember>>> {
|
||||||
|
/// See also [PublisherMemberListNotifier].
|
||||||
|
const PublisherMemberListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [PublisherMemberListNotifier].
|
||||||
|
PublisherMemberListNotifierProvider call(String uname) {
|
||||||
|
return PublisherMemberListNotifierProvider(uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
PublisherMemberListNotifierProvider getProviderOverride(
|
||||||
|
covariant PublisherMemberListNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'publisherMemberListNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [PublisherMemberListNotifier].
|
||||||
|
class PublisherMemberListNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
PublisherMemberListNotifier,
|
||||||
|
CursorPagingData<SnPublisherMember>
|
||||||
|
> {
|
||||||
|
/// See also [PublisherMemberListNotifier].
|
||||||
|
PublisherMemberListNotifierProvider(String uname)
|
||||||
|
: this._internal(
|
||||||
|
() => PublisherMemberListNotifier()..uname = uname,
|
||||||
|
from: publisherMemberListNotifierProvider,
|
||||||
|
name: r'publisherMemberListNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$publisherMemberListNotifierHash,
|
||||||
|
dependencies: PublisherMemberListNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
PublisherMemberListNotifierFamily._allTransitiveDependencies,
|
||||||
|
uname: uname,
|
||||||
|
);
|
||||||
|
|
||||||
|
PublisherMemberListNotifierProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.uname,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String uname;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<CursorPagingData<SnPublisherMember>> runNotifierBuild(
|
||||||
|
covariant PublisherMemberListNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(PublisherMemberListNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: PublisherMemberListNotifierProvider._internal(
|
||||||
|
() => create()..uname = uname,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
uname: uname,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
PublisherMemberListNotifier,
|
||||||
|
CursorPagingData<SnPublisherMember>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _PublisherMemberListNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is PublisherMemberListNotifierProvider && other.uname == uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, uname.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin PublisherMemberListNotifierRef
|
||||||
|
on
|
||||||
|
AutoDisposeAsyncNotifierProviderRef<
|
||||||
|
CursorPagingData<SnPublisherMember>
|
||||||
|
> {
|
||||||
|
/// The parameter `uname` of this provider.
|
||||||
|
String get uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PublisherMemberListNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
PublisherMemberListNotifier,
|
||||||
|
CursorPagingData<SnPublisherMember>
|
||||||
|
>
|
||||||
|
with PublisherMemberListNotifierRef {
|
||||||
|
_PublisherMemberListNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get uname => (origin as PublisherMemberListNotifierProvider).uname;
|
||||||
|
}
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -8,13 +8,9 @@ import 'package:island/widgets/content/sheet.dart';
|
|||||||
import 'package:island/widgets/post/post_list.dart';
|
import 'package:island/widgets/post/post_list.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class CreatorPostListScreen extends HookConsumerWidget {
|
class CreatorPostListScreen extends HookConsumerWidget {
|
||||||
final String pubName;
|
final String pubName;
|
||||||
const CreatorPostListScreen({
|
const CreatorPostListScreen({super.key, required this.pubName});
|
||||||
super.key,
|
|
||||||
@PathParam('name') required this.pubName,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -34,7 +30,7 @@ class CreatorPostListScreen extends HookConsumerWidget {
|
|||||||
subtitle: Text('Create a regular post'),
|
subtitle: Text('Create a regular post'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
final result = await context.router.pushPath(
|
final result = await context.push(
|
||||||
'/posts/compose?type=0',
|
'/posts/compose?type=0',
|
||||||
);
|
);
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
@@ -48,7 +44,7 @@ class CreatorPostListScreen extends HookConsumerWidget {
|
|||||||
subtitle: Text('Create a detailed article'),
|
subtitle: Text('Create a detailed article'),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
final result = await context.router.pushPath(
|
final result = await context.push(
|
||||||
'/posts/compose?type=1',
|
'/posts/compose?type=1',
|
||||||
);
|
);
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:croppy/croppy.dart' hide cropImage;
|
import 'package:croppy/croppy.dart' hide cropImage;
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:image_picker/image_picker.dart';
|
import 'package:image_picker/image_picker.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/models/realm.dart';
|
import 'package:island/models/realm.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
@@ -44,7 +44,6 @@ Future<SnPublisher?> publisher(Ref ref, String? identifier) async {
|
|||||||
return SnPublisher.fromJson(resp.data);
|
return SnPublisher.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class NewPublisherScreen extends StatelessWidget {
|
class NewPublisherScreen extends StatelessWidget {
|
||||||
const NewPublisherScreen({super.key});
|
const NewPublisherScreen({super.key});
|
||||||
|
|
||||||
@@ -54,10 +53,9 @@ class NewPublisherScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class EditPublisherScreen extends HookConsumerWidget {
|
class EditPublisherScreen extends HookConsumerWidget {
|
||||||
final String? name;
|
final String? name;
|
||||||
const EditPublisherScreen({super.key, @PathParam('id') this.name});
|
const EditPublisherScreen({super.key, this.name});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -177,7 +175,7 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
options: Options(method: name == null ? 'POST' : 'PATCH'),
|
options: Options(method: name == null ? 'POST' : 'PATCH'),
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.maybePop(SnPublisher.fromJson(resp.data));
|
context.pop(SnPublisher.fromJson(resp.data));
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
@@ -272,7 +270,10 @@ class EditPublisherScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
TextFormField(
|
TextFormField(
|
||||||
controller: bioController,
|
controller: bioController,
|
||||||
decoration: InputDecoration(labelText: 'bio'.tr()),
|
decoration: InputDecoration(
|
||||||
|
labelText: 'bio'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
onTapOutside:
|
onTapOutside:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@@ -10,7 +10,6 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/sticker.dart';
|
import 'package:island/models/sticker.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/screens/creators/stickers/stickers.dart';
|
import 'package:island/screens/creators/stickers/stickers.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
@@ -34,14 +33,13 @@ Future<List<SnSticker>> stickerPackContent(Ref ref, String packId) async {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class StickerPackDetailScreen extends HookConsumerWidget {
|
class StickerPackDetailScreen extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
final String pubName;
|
final String pubName;
|
||||||
const StickerPackDetailScreen({
|
const StickerPackDetailScreen({
|
||||||
super.key,
|
super.key,
|
||||||
@PathParam('name') required this.pubName,
|
required this.pubName,
|
||||||
@PathParam('packId') required this.id,
|
required this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -76,7 +74,7 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.add_circle),
|
icon: const Icon(Symbols.add_circle),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AutoRouter.of(context).push(NewStickersRoute(packId: id)).then((
|
context.push('/creators/stickers/$id/new').then((
|
||||||
value,
|
value,
|
||||||
) {
|
) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -175,12 +173,9 @@ class StickerPackDetailScreen extends HookConsumerWidget {
|
|||||||
title: 'edit'.tr(),
|
title: 'edit'.tr(),
|
||||||
image: MenuImage.icon(Symbols.edit),
|
image: MenuImage.icon(Symbols.edit),
|
||||||
callback: () {
|
callback: () {
|
||||||
context.router
|
context
|
||||||
.push(
|
.push(
|
||||||
EditStickersRoute(
|
'/creators/stickers/$id/edit/${sticker.id}',
|
||||||
packId: id,
|
|
||||||
id: sticker.id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.then((value) {
|
.then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -264,8 +259,8 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
|||||||
(context) => [
|
(context) => [
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(
|
context.push(
|
||||||
EditStickerPacksRoute(pubName: pubName, packId: packId),
|
'/creators/$pubName/stickers/$packId/edit',
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -299,7 +294,7 @@ class _StickerPackActionMenu extends HookConsumerWidget {
|
|||||||
final client = ref.watch(apiClientProvider);
|
final client = ref.watch(apiClientProvider);
|
||||||
client.delete('/stickers/$packId');
|
client.delete('/stickers/$packId');
|
||||||
ref.invalidate(stickerPacksNotifierProvider);
|
ref.invalidate(stickerPacksNotifierProvider);
|
||||||
if (context.mounted) context.router.maybePop(true);
|
if (context.mounted) context.pop(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -331,13 +326,9 @@ Future<SnSticker?> stickerPackSticker(
|
|||||||
return SnSticker.fromJson(resp.data);
|
return SnSticker.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class NewStickersScreen extends StatelessWidget {
|
class NewStickersScreen extends StatelessWidget {
|
||||||
final String packId;
|
final String packId;
|
||||||
const NewStickersScreen({
|
const NewStickersScreen({super.key, required this.packId});
|
||||||
super.key,
|
|
||||||
@PathParam('packId') required this.packId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -345,15 +336,10 @@ class NewStickersScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class EditStickersScreen extends HookConsumerWidget {
|
class EditStickersScreen extends HookConsumerWidget {
|
||||||
final String packId;
|
final String packId;
|
||||||
final String? id;
|
final String? id;
|
||||||
const EditStickersScreen({
|
const EditStickersScreen({super.key, required this.packId, required this.id});
|
||||||
super.key,
|
|
||||||
@PathParam("packId") required this.packId,
|
|
||||||
@PathParam("id") required this.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/sticker.dart';
|
import 'package:island/models/sticker.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
@@ -17,10 +16,9 @@ import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
|||||||
|
|
||||||
part 'stickers.g.dart';
|
part 'stickers.g.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class StickersScreen extends HookConsumerWidget {
|
class StickersScreen extends HookConsumerWidget {
|
||||||
final String pubName;
|
final String pubName;
|
||||||
const StickersScreen({super.key, @PathParam("name") required this.pubName});
|
const StickersScreen({super.key, required this.pubName});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -30,7 +28,7 @@ class StickersScreen extends HookConsumerWidget {
|
|||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.router.push(NewStickerPacksRoute(pubName: pubName)).then((
|
context.push('/creators/stickers/new?pubName=pubName').then((
|
||||||
value,
|
value,
|
||||||
) {
|
) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
@@ -73,9 +71,7 @@ class SliverStickerPacksList extends HookConsumerWidget {
|
|||||||
subtitle: Text(sticker.description),
|
subtitle: Text(sticker.description),
|
||||||
trailing: const Icon(Symbols.chevron_right),
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.router.push(
|
context.push('/creators/$pubName/stickers/${sticker.id}');
|
||||||
StickerPackDetailRoute(pubName: pubName, id: sticker.id),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -137,13 +133,9 @@ Future<SnStickerPack?> stickerPack(Ref ref, String? packId) async {
|
|||||||
return SnStickerPack.fromJson(resp.data);
|
return SnStickerPack.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class NewStickerPacksScreen extends HookConsumerWidget {
|
class NewStickerPacksScreen extends HookConsumerWidget {
|
||||||
final String pubName;
|
final String pubName;
|
||||||
const NewStickerPacksScreen({
|
const NewStickerPacksScreen({super.key, required this.pubName});
|
||||||
super.key,
|
|
||||||
@PathParam("name") required this.pubName,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -151,15 +143,10 @@ class NewStickerPacksScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class EditStickerPacksScreen extends HookConsumerWidget {
|
class EditStickerPacksScreen extends HookConsumerWidget {
|
||||||
final String pubName;
|
final String pubName;
|
||||||
final String? packId;
|
final String? packId;
|
||||||
const EditStickerPacksScreen({
|
const EditStickerPacksScreen({super.key, required this.pubName, this.packId});
|
||||||
super.key,
|
|
||||||
@PathParam("name") required this.pubName,
|
|
||||||
@PathParam("packId") this.packId,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -200,7 +187,7 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
context.router.maybePop(SnStickerPack.fromJson(resp.data));
|
context.pop(SnStickerPack.fromJson(resp.data));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorAlert(err);
|
showErrorAlert(err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -241,6 +228,7 @@ class EditStickerPacksScreen extends HookConsumerWidget {
|
|||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: 'description'.tr(),
|
labelText: 'description'.tr(),
|
||||||
border: const UnderlineInputBorder(),
|
border: const UnderlineInputBorder(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
),
|
),
|
||||||
minLines: 3,
|
minLines: 3,
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
|
|||||||
287
lib/screens/creators/webfeed/webfeed_edit.dart
Normal file
287
lib/screens/creators/webfeed/webfeed_edit.dart
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
|
import 'package:island/pods/webfeed.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class WebFeedNewScreen extends StatelessWidget {
|
||||||
|
final String pubName;
|
||||||
|
const WebFeedNewScreen({super.key, required this.pubName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return WebFeedEditScreen(pubName: pubName, feedId: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WebFeedEditScreen extends HookConsumerWidget {
|
||||||
|
final String pubName;
|
||||||
|
final String? feedId;
|
||||||
|
|
||||||
|
const WebFeedEditScreen({super.key, required this.pubName, this.feedId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||||
|
final titleController = useTextEditingController();
|
||||||
|
final urlController = useTextEditingController();
|
||||||
|
final descriptionController = useTextEditingController();
|
||||||
|
final isLoading = useState(false);
|
||||||
|
final isScrapEnabled = useState(false);
|
||||||
|
|
||||||
|
final saveFeed = useCallback(() async {
|
||||||
|
if (!formKey.currentState!.validate()) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
final feed = SnWebFeed(
|
||||||
|
id: feedId ?? '',
|
||||||
|
title: titleController.text,
|
||||||
|
url: urlController.text,
|
||||||
|
description: descriptionController.text,
|
||||||
|
config: SnWebFeedConfig(scrapPage: isScrapEnabled.value),
|
||||||
|
publisherId: pubName,
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
updatedAt: DateTime.now(),
|
||||||
|
deletedAt: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
webFeedNotifierProvider((
|
||||||
|
pubName: pubName,
|
||||||
|
feedId: feedId,
|
||||||
|
)).notifier,
|
||||||
|
)
|
||||||
|
.saveFeed(feed);
|
||||||
|
|
||||||
|
// Refresh the feed list
|
||||||
|
ref.invalidate(webFeedListProvider(pubName));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('Web feed saved successfully');
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}, [pubName, feedId, isScrapEnabled.value, context]);
|
||||||
|
|
||||||
|
final deleteFeed = useCallback(() async {
|
||||||
|
final confirmed = await showConfirmAlert(
|
||||||
|
'Are you sure you want to delete this web feed? This action cannot be undone.',
|
||||||
|
'Delete Web Feed',
|
||||||
|
);
|
||||||
|
if (confirmed != true) return;
|
||||||
|
|
||||||
|
isLoading.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
webFeedNotifierProvider((
|
||||||
|
pubName: pubName,
|
||||||
|
feedId: feedId!,
|
||||||
|
)).notifier,
|
||||||
|
)
|
||||||
|
.deleteFeed();
|
||||||
|
|
||||||
|
ref.invalidate(webFeedListProvider(pubName));
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('Web feed deleted successfully');
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
}, [pubName, feedId, context, ref]);
|
||||||
|
|
||||||
|
final feedAsync = ref.watch(
|
||||||
|
webFeedNotifierProvider((pubName: pubName, feedId: feedId)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return feedAsync.when(
|
||||||
|
loading:
|
||||||
|
() =>
|
||||||
|
const Scaffold(body: Center(child: CircularProgressIndicator())),
|
||||||
|
error:
|
||||||
|
(error, stack) => Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Error')),
|
||||||
|
body: Center(child: Text('Error: $error')),
|
||||||
|
),
|
||||||
|
data: (feed) {
|
||||||
|
// Initialize form fields if they're empty and we have a feed
|
||||||
|
if (titleController.text.isEmpty) {
|
||||||
|
titleController.text = feed.title;
|
||||||
|
urlController.text = feed.url;
|
||||||
|
descriptionController.text = feed.description ?? '';
|
||||||
|
isScrapEnabled.value = feed.config.scrapPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buildForm(
|
||||||
|
context,
|
||||||
|
formKey: formKey,
|
||||||
|
titleController: titleController,
|
||||||
|
urlController: urlController,
|
||||||
|
descriptionController: descriptionController,
|
||||||
|
isScrapEnabled: isScrapEnabled.value,
|
||||||
|
onScrapEnabledChanged: (value) => isScrapEnabled.value = value,
|
||||||
|
onSave: saveFeed,
|
||||||
|
onDelete: deleteFeed,
|
||||||
|
isLoading: isLoading.value,
|
||||||
|
ref: ref,
|
||||||
|
hasFeedId: feedId != null,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildForm(
|
||||||
|
BuildContext context, {
|
||||||
|
required WidgetRef ref,
|
||||||
|
required GlobalKey<FormState> formKey,
|
||||||
|
required TextEditingController titleController,
|
||||||
|
required TextEditingController urlController,
|
||||||
|
required TextEditingController descriptionController,
|
||||||
|
required bool isScrapEnabled,
|
||||||
|
required ValueChanged<bool> onScrapEnabledChanged,
|
||||||
|
required VoidCallback onSave,
|
||||||
|
required VoidCallback onDelete,
|
||||||
|
required bool isLoading,
|
||||||
|
required bool hasFeedId,
|
||||||
|
}) {
|
||||||
|
final scrapNow = useCallback(() async {
|
||||||
|
showLoadingModal(context);
|
||||||
|
try {
|
||||||
|
await ref
|
||||||
|
.read(
|
||||||
|
webFeedNotifierProvider((
|
||||||
|
pubName: pubName,
|
||||||
|
feedId: feedId!,
|
||||||
|
)).notifier,
|
||||||
|
)
|
||||||
|
.scrapFeed();
|
||||||
|
|
||||||
|
if (context.mounted) {
|
||||||
|
showSnackBar('Feed scraping successfully.');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
}
|
||||||
|
}, [pubName, feedId, ref, context]);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(hasFeedId ? 'Edit Web Feed' : 'New Web Feed'),
|
||||||
|
actions: [
|
||||||
|
if (hasFeedId)
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.delete_forever),
|
||||||
|
onPressed: isLoading ? null : onDelete,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Form(
|
||||||
|
key: formKey,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: titleController,
|
||||||
|
decoration: const InputDecoration(labelText: 'Title'),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a title';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: urlController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'URL',
|
||||||
|
hintText: 'https://example.com/feed',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'Please enter a URL';
|
||||||
|
}
|
||||||
|
final uri = Uri.tryParse(value);
|
||||||
|
if (uri == null || !uri.hasAbsolutePath) {
|
||||||
|
return 'Please enter a valid URL';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: descriptionController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Description',
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SwitchListTile(
|
||||||
|
title: const Text('Scrape web page for content'),
|
||||||
|
subtitle: const Text(
|
||||||
|
'When enabled, the system will attempt to extract full content from the web page',
|
||||||
|
),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
value: isScrapEnabled,
|
||||||
|
onChanged: onScrapEnabledChanged,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
if (hasFeedId) ...[
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: isLoading ? null : scrapNow,
|
||||||
|
icon: const Icon(Symbols.refresh),
|
||||||
|
label: const Text('Scrape Now'),
|
||||||
|
).alignment(Alignment.centerRight),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
],
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: isLoading ? null : onSave,
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
label: Text('saveChanges').tr(),
|
||||||
|
).alignment(Alignment.centerRight),
|
||||||
|
],
|
||||||
|
).padding(all: 20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
78
lib/screens/creators/webfeed/webfeed_list.dart
Normal file
78
lib/screens/creators/webfeed/webfeed_list.dart
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:island/pods/webfeed.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/empty_state.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
|
||||||
|
class WebFeedListScreen extends ConsumerWidget {
|
||||||
|
final String pubName;
|
||||||
|
|
||||||
|
const WebFeedListScreen({super.key, required this.pubName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final feedsAsync = ref.watch(webFeedListProvider(pubName));
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: const Text('Web Feeds')),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
child: const Icon(Symbols.add),
|
||||||
|
onPressed: () {
|
||||||
|
context.push('/creators/$pubName/feeds/new');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: feedsAsync.when(
|
||||||
|
data: (feeds) {
|
||||||
|
if (feeds.isEmpty) {
|
||||||
|
return EmptyState(
|
||||||
|
icon: Symbols.rss_feed,
|
||||||
|
title: 'No Web Feeds',
|
||||||
|
description: 'Add a new web feed to get started',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh: () => ref.refresh(webFeedListProvider(pubName).future),
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.only(top: 8),
|
||||||
|
itemCount: feeds.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final feed = feeds[index];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: const Icon(Symbols.rss_feed, size: 32),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
feed.title,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
feed.url,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
context.push('/creators/$pubName/feeds/${feed.id}');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, _) => Center(child: Text('Error: $error')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
162
lib/screens/developers/apps.dart
Normal file
162
lib/screens/developers/apps.dart
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/custom_app.dart';
|
||||||
|
import 'package:island/pods/network.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/response.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
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');
|
||||||
|
return resp.data.map((e) => CustomApp.fromJson(e)).cast<CustomApp>().toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomAppsScreen extends HookConsumerWidget {
|
||||||
|
final String publisherName;
|
||||||
|
const CustomAppsScreen({super.key, required this.publisherName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final apps = ref.watch(customAppsProvider(publisherName));
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text('customApps').tr(),
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
onPressed: () {
|
||||||
|
context.push('/developers/$publisherName/apps/new');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: apps.when(
|
||||||
|
data: (data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return Center(child: Text('noCustomApps').tr());
|
||||||
|
}
|
||||||
|
return RefreshIndicator(
|
||||||
|
onRefresh:
|
||||||
|
() => ref.refresh(customAppsProvider(publisherName).future),
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: EdgeInsets.only(top: 4),
|
||||||
|
itemCount: data.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final app = data[index];
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 150,
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
if (app.background != null)
|
||||||
|
CloudFileWidget(
|
||||||
|
item: app.background!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
).clipRRect(topLeft: 8, topRight: 8),
|
||||||
|
if (app.picture != null)
|
||||||
|
Positioned(
|
||||||
|
left: 16,
|
||||||
|
bottom: 16,
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
fileId: app.picture!.id,
|
||||||
|
radius: 40,
|
||||||
|
fallbackIcon: Symbols.apps,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(app.name),
|
||||||
|
subtitle: Text(
|
||||||
|
app.slug,
|
||||||
|
style: GoogleFonts.robotoMono(fontSize: 12),
|
||||||
|
),
|
||||||
|
contentPadding: EdgeInsets.only(left: 20, right: 12),
|
||||||
|
trailing: PopupMenuButton(
|
||||||
|
itemBuilder:
|
||||||
|
(context) => [
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'edit',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Symbols.edit),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text('edit').tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
value: 'delete',
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Symbols.delete,
|
||||||
|
color: Colors.red,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
'delete',
|
||||||
|
style: TextStyle(color: Colors.red),
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == 'edit') {
|
||||||
|
context.push(
|
||||||
|
'/developers/$publisherName/apps/${app.id}',
|
||||||
|
);
|
||||||
|
} else if (value == 'delete') {
|
||||||
|
showConfirmAlert(
|
||||||
|
'deleteCustomAppHint'.tr(),
|
||||||
|
'deleteCustomApp'.tr(),
|
||||||
|
).then((confirm) {
|
||||||
|
if (confirm) {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
client.delete(
|
||||||
|
'/developers/$publisherName/apps/${app.id}',
|
||||||
|
);
|
||||||
|
ref.invalidate(
|
||||||
|
customAppsProvider(publisherName),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(err, stack) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () => ref.invalidate(customAppsProvider(publisherName)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
151
lib/screens/developers/apps.g.dart
Normal file
151
lib/screens/developers/apps.g.dart
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'apps.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$customAppsHash() => r'1dec11573b9d987c3adbdf4732b3781a6f40172a';
|
||||||
|
|
||||||
|
/// 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 [customApps].
|
||||||
|
@ProviderFor(customApps)
|
||||||
|
const customAppsProvider = CustomAppsFamily();
|
||||||
|
|
||||||
|
/// See also [customApps].
|
||||||
|
class CustomAppsFamily extends Family<AsyncValue<List<CustomApp>>> {
|
||||||
|
/// See also [customApps].
|
||||||
|
const CustomAppsFamily();
|
||||||
|
|
||||||
|
/// See also [customApps].
|
||||||
|
CustomAppsProvider call(String publisherName) {
|
||||||
|
return CustomAppsProvider(publisherName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CustomAppsProvider getProviderOverride(
|
||||||
|
covariant CustomAppsProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.publisherName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'customAppsProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [customApps].
|
||||||
|
class CustomAppsProvider extends AutoDisposeFutureProvider<List<CustomApp>> {
|
||||||
|
/// See also [customApps].
|
||||||
|
CustomAppsProvider(String publisherName)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => customApps(ref as CustomAppsRef, publisherName),
|
||||||
|
from: customAppsProvider,
|
||||||
|
name: r'customAppsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$customAppsHash,
|
||||||
|
dependencies: CustomAppsFamily._dependencies,
|
||||||
|
allTransitiveDependencies: CustomAppsFamily._allTransitiveDependencies,
|
||||||
|
publisherName: publisherName,
|
||||||
|
);
|
||||||
|
|
||||||
|
CustomAppsProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.publisherName,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String publisherName;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<List<CustomApp>> Function(CustomAppsRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: CustomAppsProvider._internal(
|
||||||
|
(ref) => create(ref as CustomAppsRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
publisherName: publisherName,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<List<CustomApp>> createElement() {
|
||||||
|
return _CustomAppsProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is CustomAppsProvider && other.publisherName == publisherName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, publisherName.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin CustomAppsRef on AutoDisposeFutureProviderRef<List<CustomApp>> {
|
||||||
|
/// The parameter `publisherName` of this provider.
|
||||||
|
String get publisherName;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomAppsProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<List<CustomApp>>
|
||||||
|
with CustomAppsRef {
|
||||||
|
_CustomAppsProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get publisherName => (origin as CustomAppsProvider).publisherName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
558
lib/screens/developers/edit_app.dart
Normal file
558
lib/screens/developers/edit_app.dart
Normal file
@@ -0,0 +1,558 @@
|
|||||||
|
import 'package:croppy/croppy.dart' hide cropImage;
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:island/models/custom_app.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/screens/developers/apps.dart';
|
||||||
|
import 'package:island/services/file.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
|
||||||
|
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');
|
||||||
|
return CustomApp.fromJson(resp.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
class EditAppScreen extends HookConsumerWidget {
|
||||||
|
final String publisherName;
|
||||||
|
final String? id;
|
||||||
|
const EditAppScreen({super.key, required this.publisherName, this.id});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isNew = id == null;
|
||||||
|
final app = isNew ? null : ref.watch(customAppProvider(publisherName, id!));
|
||||||
|
|
||||||
|
final formKey = useMemoized(() => GlobalKey<FormState>());
|
||||||
|
|
||||||
|
final submitting = useState(false);
|
||||||
|
|
||||||
|
final nameController = useTextEditingController();
|
||||||
|
final slugController = useTextEditingController();
|
||||||
|
final descriptionController = useTextEditingController();
|
||||||
|
final picture = useState<SnCloudFile?>(null);
|
||||||
|
final background = useState<SnCloudFile?>(null);
|
||||||
|
|
||||||
|
final enableLinks = useState(false); // Only for UI purposes
|
||||||
|
final homePageController = useTextEditingController();
|
||||||
|
final privacyPolicyController = useTextEditingController();
|
||||||
|
final termsController = useTextEditingController();
|
||||||
|
final oauthEnabled = useState(false);
|
||||||
|
final redirectUris = useState<List<String>>([]);
|
||||||
|
final postLogoutUris = useState<List<String>>([]);
|
||||||
|
final allowedScopes = useState<List<String>>([
|
||||||
|
'openid',
|
||||||
|
'profile',
|
||||||
|
'email',
|
||||||
|
]);
|
||||||
|
final allowedGrantTypes = useState<List<String>>([
|
||||||
|
'authorization_code',
|
||||||
|
'refresh_token',
|
||||||
|
]);
|
||||||
|
final requirePkce = useState(true);
|
||||||
|
final allowOfflineAccess = useState(false);
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
if (app?.value != null) {
|
||||||
|
nameController.text = app!.value!.name;
|
||||||
|
slugController.text = app.value!.slug;
|
||||||
|
descriptionController.text = app.value!.description ?? '';
|
||||||
|
picture.value = app.value!.picture;
|
||||||
|
background.value = app.value!.background;
|
||||||
|
homePageController.text = app.value!.links?.homePage ?? '';
|
||||||
|
privacyPolicyController.text = app.value!.links?.privacyPolicy ?? '';
|
||||||
|
termsController.text = app.value!.links?.termsOfService ?? '';
|
||||||
|
if (app.value!.oauthConfig != null) {
|
||||||
|
oauthEnabled.value = true;
|
||||||
|
redirectUris.value = app.value!.oauthConfig!.redirectUris;
|
||||||
|
postLogoutUris.value =
|
||||||
|
app.value!.oauthConfig!.postLogoutRedirectUris ?? [];
|
||||||
|
allowedScopes.value = app.value!.oauthConfig!.allowedScopes;
|
||||||
|
allowedGrantTypes.value = app.value!.oauthConfig!.allowedGrantTypes;
|
||||||
|
requirePkce.value = app.value!.oauthConfig!.requirePkce;
|
||||||
|
allowOfflineAccess.value = app.value!.oauthConfig!.allowOfflineAccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [app]);
|
||||||
|
|
||||||
|
void setPicture(String position) async {
|
||||||
|
showLoadingModal(context);
|
||||||
|
var result = await ref
|
||||||
|
.read(imagePickerProvider)
|
||||||
|
.pickImage(source: ImageSource.gallery);
|
||||||
|
if (result == null) {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
|
hideLoadingModal(context);
|
||||||
|
result = await cropImage(
|
||||||
|
context,
|
||||||
|
image: result,
|
||||||
|
allowedAspectRatios: [
|
||||||
|
if (position == 'background')
|
||||||
|
const CropAspectRatio(height: 7, width: 16)
|
||||||
|
else
|
||||||
|
const CropAspectRatio(height: 1, width: 1),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if (result == null) {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!context.mounted) return;
|
||||||
|
showLoadingModal(context);
|
||||||
|
|
||||||
|
submitting.value = true;
|
||||||
|
try {
|
||||||
|
final baseUrl = ref.watch(serverUrlProvider);
|
||||||
|
final token = await getToken(ref.watch(tokenProvider));
|
||||||
|
if (token == null) throw ArgumentError('Token is null');
|
||||||
|
final cloudFile =
|
||||||
|
await putMediaToCloud(
|
||||||
|
fileData: UniversalFile(
|
||||||
|
data: result,
|
||||||
|
type: UniversalFileType.image,
|
||||||
|
),
|
||||||
|
atk: token,
|
||||||
|
baseUrl: baseUrl,
|
||||||
|
filename: result.name,
|
||||||
|
mimetype: result.mimeType ?? 'image/jpeg',
|
||||||
|
).future;
|
||||||
|
if (cloudFile == null) {
|
||||||
|
throw ArgumentError('Failed to upload the file...');
|
||||||
|
}
|
||||||
|
switch (position) {
|
||||||
|
case 'picture':
|
||||||
|
picture.value = cloudFile;
|
||||||
|
case 'background':
|
||||||
|
background.value = cloudFile;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
} finally {
|
||||||
|
if (context.mounted) hideLoadingModal(context);
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void showAddScopeDialog() {
|
||||||
|
final scopeController = TextEditingController();
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'addScope'.tr(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: scopeController,
|
||||||
|
decoration: InputDecoration(labelText: 'scopeName'.tr()),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: () {
|
||||||
|
if (scopeController.text.isNotEmpty) {
|
||||||
|
allowedScopes.value = [
|
||||||
|
...allowedScopes.value,
|
||||||
|
scopeController.text,
|
||||||
|
];
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
label: Text('add').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showAddRedirectUriDialog() {
|
||||||
|
final uriController = TextEditingController();
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(context) => SheetScaffold(
|
||||||
|
titleText: 'addRedirectUri'.tr(),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: uriController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'redirectUri'.tr(),
|
||||||
|
hintText: 'https://example.com/auth/callback',
|
||||||
|
helperText: 'redirectUriHint'.tr(),
|
||||||
|
helperMaxLines: 3,
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return 'uriRequired'.tr();
|
||||||
|
}
|
||||||
|
final uri = Uri.tryParse(value);
|
||||||
|
if (uri == null || !uri.hasAbsolutePath) {
|
||||||
|
return 'invalidUri'.tr();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onTapOutside:
|
||||||
|
(_) => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
FilledButton.tonalIcon(
|
||||||
|
onPressed: () {
|
||||||
|
if (uriController.text.isNotEmpty) {
|
||||||
|
redirectUris.value = [
|
||||||
|
...redirectUris.value,
|
||||||
|
uriController.text,
|
||||||
|
];
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.add),
|
||||||
|
label: Text('add').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void performAction() async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final data = {
|
||||||
|
'name': nameController.text,
|
||||||
|
'slug': slugController.text,
|
||||||
|
'description': descriptionController.text,
|
||||||
|
'picture_id': picture.value?.id,
|
||||||
|
'background_id': background.value?.id,
|
||||||
|
'links': {
|
||||||
|
'home_page':
|
||||||
|
homePageController.text.isNotEmpty
|
||||||
|
? homePageController.text
|
||||||
|
: null,
|
||||||
|
'privacy_policy':
|
||||||
|
privacyPolicyController.text.isNotEmpty
|
||||||
|
? privacyPolicyController.text
|
||||||
|
: null,
|
||||||
|
'terms_of_service':
|
||||||
|
termsController.text.isNotEmpty ? termsController.text : null,
|
||||||
|
},
|
||||||
|
'oauth_config':
|
||||||
|
oauthEnabled.value
|
||||||
|
? {
|
||||||
|
'redirect_uris': redirectUris.value,
|
||||||
|
'post_logout_redirect_uris':
|
||||||
|
postLogoutUris.value.isNotEmpty
|
||||||
|
? postLogoutUris.value
|
||||||
|
: null,
|
||||||
|
'allowed_scopes': allowedScopes.value,
|
||||||
|
'allowed_grant_types': allowedGrantTypes.value,
|
||||||
|
'require_pkce': requirePkce.value,
|
||||||
|
'allow_offline_access': allowOfflineAccess.value,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
};
|
||||||
|
if (isNew) {
|
||||||
|
await client.post('/developers/$publisherName/apps', data: data);
|
||||||
|
} else {
|
||||||
|
await client.patch('/developers/$publisherName/apps/$id', data: data);
|
||||||
|
}
|
||||||
|
ref.invalidate(customAppsProvider(publisherName));
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(isNew ? 'createCustomApp'.tr() : 'editCustomApp'.tr()),
|
||||||
|
),
|
||||||
|
body:
|
||||||
|
app == null && !isNew
|
||||||
|
? const Center(child: CircularProgressIndicator())
|
||||||
|
: app?.hasError == true && !isNew
|
||||||
|
? ResponseErrorWidget(
|
||||||
|
error: app!.error,
|
||||||
|
onRetry:
|
||||||
|
() => ref.invalidate(customAppProvider(publisherName, id!)),
|
||||||
|
)
|
||||||
|
: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
AspectRatio(
|
||||||
|
aspectRatio: 16 / 7,
|
||||||
|
child: Stack(
|
||||||
|
clipBehavior: Clip.none,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
child: Container(
|
||||||
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.surfaceContainerHigh,
|
||||||
|
child:
|
||||||
|
background.value != null
|
||||||
|
? CloudFileWidget(
|
||||||
|
item: background.value!,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
setPicture('background');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 20,
|
||||||
|
bottom: -32,
|
||||||
|
child: GestureDetector(
|
||||||
|
child: ProfilePictureWidget(
|
||||||
|
fileId: picture.value?.id,
|
||||||
|
radius: 40,
|
||||||
|
fallbackIcon: Symbols.apps,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
setPicture('picture');
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
).padding(bottom: 32),
|
||||||
|
Form(
|
||||||
|
key: formKey,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: nameController,
|
||||||
|
decoration: InputDecoration(labelText: 'name'.tr()),
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: slugController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'slug'.tr(),
|
||||||
|
helperText: 'slugHint'.tr(),
|
||||||
|
),
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: descriptionController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'description'.tr(),
|
||||||
|
alignLabelWithHint: true,
|
||||||
|
),
|
||||||
|
maxLines: 3,
|
||||||
|
onTapOutside:
|
||||||
|
(_) =>
|
||||||
|
FocusManager.instance.primaryFocus
|
||||||
|
?.unfocus(),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
ExpansionPanelList(
|
||||||
|
expansionCallback: (index, isExpanded) {
|
||||||
|
switch (index) {
|
||||||
|
case 0:
|
||||||
|
enableLinks.value = isExpanded;
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
oauthEnabled.value = isExpanded;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder:
|
||||||
|
(context, isExpanded) =>
|
||||||
|
ListTile(title: Text('appLinks').tr()),
|
||||||
|
body: Column(
|
||||||
|
spacing: 16,
|
||||||
|
children: [
|
||||||
|
TextFormField(
|
||||||
|
controller: homePageController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'homePageUrl'.tr(),
|
||||||
|
hintText: 'https://example.com',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: privacyPolicyController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'privacyPolicyUrl'.tr(),
|
||||||
|
hintText: 'https://example.com/privacy',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
controller: termsController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: 'termsOfServiceUrl'.tr(),
|
||||||
|
hintText: 'https://example.com/terms',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.url,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, bottom: 24),
|
||||||
|
isExpanded: enableLinks.value,
|
||||||
|
),
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder:
|
||||||
|
(context, isExpanded) => ListTile(
|
||||||
|
title: Text('oauthConfig').tr(),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('redirectUris'.tr()),
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...redirectUris.value.map(
|
||||||
|
(uri) => ListTile(
|
||||||
|
title: Text(uri),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Symbols.delete,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
redirectUris.value =
|
||||||
|
redirectUris.value
|
||||||
|
.where(
|
||||||
|
(u) => u != uri,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (redirectUris.value.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('addRedirectUri'.tr()),
|
||||||
|
onTap: showAddRedirectUriDialog,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius:
|
||||||
|
BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text('allowedScopes'.tr()),
|
||||||
|
Card(
|
||||||
|
margin: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...allowedScopes.value.map(
|
||||||
|
(scope) => ListTile(
|
||||||
|
title: Text(scope),
|
||||||
|
trailing: IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Symbols.delete,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
allowedScopes.value =
|
||||||
|
allowedScopes.value
|
||||||
|
.where(
|
||||||
|
(s) => s != scope,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (allowedScopes.value.isNotEmpty)
|
||||||
|
const Divider(height: 1),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Symbols.add),
|
||||||
|
title: Text('add').tr(),
|
||||||
|
onTap: showAddScopeDialog,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('requirePkce'.tr()),
|
||||||
|
value: requirePkce.value,
|
||||||
|
onChanged:
|
||||||
|
(value) => requirePkce.value = value,
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text('allowOfflineAccess'.tr()),
|
||||||
|
value: allowOfflineAccess.value,
|
||||||
|
onChanged:
|
||||||
|
(value) =>
|
||||||
|
allowOfflineAccess.value = value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 16, bottom: 24),
|
||||||
|
isExpanded: oauthEnabled.value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: TextButton.icon(
|
||||||
|
onPressed:
|
||||||
|
submitting.value ? null : performAction,
|
||||||
|
label: Text('saveChanges'.tr()),
|
||||||
|
icon: const Icon(Symbols.save),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(all: 24),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
161
lib/screens/developers/edit_app.g.dart
Normal file
161
lib/screens/developers/edit_app.g.dart
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'edit_app.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$customAppHash() => r'aa4d1fb803c47a99cbacf6d91481f4fce3fda457';
|
||||||
|
|
||||||
|
/// 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 [customApp].
|
||||||
|
@ProviderFor(customApp)
|
||||||
|
const customAppProvider = CustomAppFamily();
|
||||||
|
|
||||||
|
/// See also [customApp].
|
||||||
|
class CustomAppFamily extends Family<AsyncValue<CustomApp?>> {
|
||||||
|
/// See also [customApp].
|
||||||
|
const CustomAppFamily();
|
||||||
|
|
||||||
|
/// See also [customApp].
|
||||||
|
CustomAppProvider call(String publisherName, String id) {
|
||||||
|
return CustomAppProvider(publisherName, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CustomAppProvider getProviderOverride(covariant CustomAppProvider provider) {
|
||||||
|
return call(provider.publisherName, provider.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'customAppProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [customApp].
|
||||||
|
class CustomAppProvider extends AutoDisposeFutureProvider<CustomApp?> {
|
||||||
|
/// See also [customApp].
|
||||||
|
CustomAppProvider(String publisherName, String id)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => customApp(ref as CustomAppRef, publisherName, id),
|
||||||
|
from: customAppProvider,
|
||||||
|
name: r'customAppProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$customAppHash,
|
||||||
|
dependencies: CustomAppFamily._dependencies,
|
||||||
|
allTransitiveDependencies: CustomAppFamily._allTransitiveDependencies,
|
||||||
|
publisherName: publisherName,
|
||||||
|
id: id,
|
||||||
|
);
|
||||||
|
|
||||||
|
CustomAppProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.publisherName,
|
||||||
|
required this.id,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String publisherName;
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<CustomApp?> Function(CustomAppRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: CustomAppProvider._internal(
|
||||||
|
(ref) => create(ref as CustomAppRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
publisherName: publisherName,
|
||||||
|
id: id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<CustomApp?> createElement() {
|
||||||
|
return _CustomAppProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is CustomAppProvider &&
|
||||||
|
other.publisherName == publisherName &&
|
||||||
|
other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, publisherName.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, id.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin CustomAppRef on AutoDisposeFutureProviderRef<CustomApp?> {
|
||||||
|
/// The parameter `publisherName` of this provider.
|
||||||
|
String get publisherName;
|
||||||
|
|
||||||
|
/// The parameter `id` of this provider.
|
||||||
|
String get id;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomAppProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<CustomApp?>
|
||||||
|
with CustomAppRef {
|
||||||
|
_CustomAppProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get publisherName => (origin as CustomAppProvider).publisherName;
|
||||||
|
@override
|
||||||
|
String get id => (origin as CustomAppProvider).id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
380
lib/screens/developers/hub.dart
Normal file
380
lib/screens/developers/hub.dart
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/developer.dart';
|
||||||
|
import 'package:island/models/publisher.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/screens/creators/publishers.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/sheet.dart';
|
||||||
|
import 'package:island/widgets/response.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
part 'hub.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<DeveloperStats?> developerStats(Ref ref, String? uname) async {
|
||||||
|
if (uname == null) return null;
|
||||||
|
final apiClient = ref.watch(apiClientProvider);
|
||||||
|
final resp = await apiClient.get('/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('/developers');
|
||||||
|
return resp.data
|
||||||
|
.map((e) => SnPublisher.fromJson(e))
|
||||||
|
.cast<SnPublisher>()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeveloperHubShellScreen extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const DeveloperHubShellScreen({super.key, required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final isWide = isWideScreen(context);
|
||||||
|
if (isWide) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(width: 360, child: const DeveloperHubScreen(isAside: true)),
|
||||||
|
const VerticalDivider(width: 1),
|
||||||
|
Expanded(child: child),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DeveloperHubScreen extends HookConsumerWidget {
|
||||||
|
final bool isAside;
|
||||||
|
const DeveloperHubScreen({super.key, this.isAside = false});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isWide = isWideScreen(context);
|
||||||
|
if (isWide && !isAside) {
|
||||||
|
return Container(color: Theme.of(context).colorScheme.surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
final developers = ref.watch(developersProvider);
|
||||||
|
final currentDeveloper = useState<SnPublisher?>(
|
||||||
|
developers.value?.firstOrNull,
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<DropdownMenuItem<SnPublisher>> developersMenu = developers.when(
|
||||||
|
data:
|
||||||
|
(data) =>
|
||||||
|
data
|
||||||
|
.map(
|
||||||
|
(item) => DropdownMenuItem<SnPublisher>(
|
||||||
|
value: item,
|
||||||
|
child: ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
radius: 16,
|
||||||
|
fileId: item.picture?.id,
|
||||||
|
),
|
||||||
|
title: Text(item.nick),
|
||||||
|
subtitle: Text('@${item.name}'),
|
||||||
|
trailing:
|
||||||
|
currentDeveloper.value?.id == item.id
|
||||||
|
? const Icon(Icons.check)
|
||||||
|
: null,
|
||||||
|
contentPadding: EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
loading: () => [],
|
||||||
|
error: (_, _) => [],
|
||||||
|
);
|
||||||
|
|
||||||
|
final developerStats = ref.watch(
|
||||||
|
developerStatsProvider(currentDeveloper.value?.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
noBackground: false,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: !isWide ? const PageBackButton() : null,
|
||||||
|
title: Text('developerHub').tr(),
|
||||||
|
actions: [
|
||||||
|
DropdownButtonHideUnderline(
|
||||||
|
child: DropdownButton2<SnPublisher>(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
value: currentDeveloper.value,
|
||||||
|
hint: CircleAvatar(
|
||||||
|
radius: 16,
|
||||||
|
child: Icon(
|
||||||
|
Symbols.person,
|
||||||
|
color: Theme.of(
|
||||||
|
context,
|
||||||
|
).colorScheme.onSecondaryContainer.withOpacity(0.9),
|
||||||
|
fill: 1,
|
||||||
|
),
|
||||||
|
).center().padding(right: 8),
|
||||||
|
items: [...developersMenu],
|
||||||
|
onChanged: (value) {
|
||||||
|
currentDeveloper.value = value;
|
||||||
|
},
|
||||||
|
selectedItemBuilder: (context) {
|
||||||
|
return [
|
||||||
|
...developersMenu.map(
|
||||||
|
(e) => ProfilePictureWidget(
|
||||||
|
radius: 16,
|
||||||
|
fileId: e.value?.picture?.id,
|
||||||
|
).center().padding(right: 8),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
buttonStyleData: ButtonStyleData(
|
||||||
|
height: 40,
|
||||||
|
padding: const EdgeInsets.only(left: 14, right: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
dropdownStyleData: DropdownStyleData(
|
||||||
|
width: 320,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
menuItemStyleData: const MenuItemStyleData(
|
||||||
|
height: 64,
|
||||||
|
padding: EdgeInsets.only(left: 14, right: 14),
|
||||||
|
),
|
||||||
|
iconStyleData: IconStyleData(
|
||||||
|
icon: Icon(Icons.arrow_drop_down),
|
||||||
|
iconSize: 19,
|
||||||
|
iconEnabledColor:
|
||||||
|
Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
iconDisabledColor:
|
||||||
|
Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: developerStats.when(
|
||||||
|
data:
|
||||||
|
(stats) => SingleChildScrollView(
|
||||||
|
child:
|
||||||
|
currentDeveloper.value == null
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
const Gap(24),
|
||||||
|
const Icon(Symbols.info, size: 32).padding(bottom: 4),
|
||||||
|
Text(
|
||||||
|
'developerHubUnselectedHint',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).tr(),
|
||||||
|
const Gap(24),
|
||||||
|
const Divider(height: 1),
|
||||||
|
...(developers.value?.map(
|
||||||
|
(developer) => ListTile(
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
file: developer.picture,
|
||||||
|
),
|
||||||
|
title: Text(developer.nick),
|
||||||
|
subtitle: Text('@${developer.name}'),
|
||||||
|
onTap: () {
|
||||||
|
currentDeveloper.value = developer;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
[]),
|
||||||
|
ListTile(
|
||||||
|
leading: const CircleAvatar(
|
||||||
|
child: Icon(Symbols.add),
|
||||||
|
),
|
||||||
|
title: Text('enrollDeveloper').tr(),
|
||||||
|
subtitle: Text('enrollDeveloperHint').tr(),
|
||||||
|
trailing: const Icon(Symbols.chevron_right),
|
||||||
|
onTap: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder:
|
||||||
|
(_) => const _DeveloperEnrollmentSheet(),
|
||||||
|
).then((value) {
|
||||||
|
if (value == true) {
|
||||||
|
ref.invalidate(developersProvider);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: Column(
|
||||||
|
children: [
|
||||||
|
if (stats != null)
|
||||||
|
_DeveloperStatsWidget(
|
||||||
|
stats: stats,
|
||||||
|
).padding(vertical: 12, horizontal: 12),
|
||||||
|
ListTile(
|
||||||
|
minTileHeight: 48,
|
||||||
|
title: Text('customApps').tr(),
|
||||||
|
trailing: Icon(Symbols.chevron_right),
|
||||||
|
leading: const Icon(Symbols.apps),
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 24,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
context.push(
|
||||||
|
'/developers/${currentDeveloper.value!.name}/apps',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(err, stack) => ResponseErrorWidget(
|
||||||
|
error: err,
|
||||||
|
onRetry: () {
|
||||||
|
ref.invalidate(
|
||||||
|
developerStatsProvider(currentDeveloper.value?.name),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeveloperStatsWidget extends StatelessWidget {
|
||||||
|
final DeveloperStats stats;
|
||||||
|
const _DeveloperStatsWidget({required this.stats});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
spacing: 8,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: _buildStatsCard(
|
||||||
|
context,
|
||||||
|
stats.totalCustomApps.toString(),
|
||||||
|
'totalCustomApps',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildStatsCard(
|
||||||
|
BuildContext context,
|
||||||
|
String statValue,
|
||||||
|
String statLabel,
|
||||||
|
) {
|
||||||
|
return Card(
|
||||||
|
margin: EdgeInsets.zero,
|
||||||
|
child: SizedBox(
|
||||||
|
height: 100,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
statValue,
|
||||||
|
style: Theme.of(context).textTheme.headlineMedium,
|
||||||
|
),
|
||||||
|
const Gap(4),
|
||||||
|
Text(
|
||||||
|
statLabel,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
).tr(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeveloperEnrollmentSheet extends HookConsumerWidget {
|
||||||
|
const _DeveloperEnrollmentSheet();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final publishers = ref.watch(publishersManagedProvider);
|
||||||
|
|
||||||
|
Future<void> enroll(SnPublisher publisher) async {
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
await client.post('/developers/${publisher.name}/enroll');
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
showErrorAlert(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SheetScaffold(
|
||||||
|
titleText: 'enrollDeveloper'.tr(),
|
||||||
|
child: publishers.when(
|
||||||
|
data:
|
||||||
|
(items) =>
|
||||||
|
items.isEmpty
|
||||||
|
? Center(
|
||||||
|
child:
|
||||||
|
Text(
|
||||||
|
'noPublishersToEnroll',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
).tr(),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final publisher = items[index];
|
||||||
|
return ListTile(
|
||||||
|
leading: ProfilePictureWidget(
|
||||||
|
fileId: publisher.picture?.id,
|
||||||
|
fallbackIcon: Symbols.group,
|
||||||
|
),
|
||||||
|
title: Text(publisher.nick),
|
||||||
|
subtitle: Text('@${publisher.name}'),
|
||||||
|
onTap: () => enroll(publisher),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error:
|
||||||
|
(error, _) => ResponseErrorWidget(
|
||||||
|
error: error,
|
||||||
|
onRetry: () => ref.invalidate(publishersManagedProvider),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
172
lib/screens/developers/hub.g.dart
Normal file
172
lib/screens/developers/hub.g.dart
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'hub.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$developerStatsHash() => r'783398cbde09c3d956c3e20b02a1cebd1f8ab748';
|
||||||
|
|
||||||
|
/// 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 [developerStats].
|
||||||
|
@ProviderFor(developerStats)
|
||||||
|
const developerStatsProvider = DeveloperStatsFamily();
|
||||||
|
|
||||||
|
/// See also [developerStats].
|
||||||
|
class DeveloperStatsFamily extends Family<AsyncValue<DeveloperStats?>> {
|
||||||
|
/// See also [developerStats].
|
||||||
|
const DeveloperStatsFamily();
|
||||||
|
|
||||||
|
/// See also [developerStats].
|
||||||
|
DeveloperStatsProvider call(String? uname) {
|
||||||
|
return DeveloperStatsProvider(uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
DeveloperStatsProvider getProviderOverride(
|
||||||
|
covariant DeveloperStatsProvider provider,
|
||||||
|
) {
|
||||||
|
return call(provider.uname);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'developerStatsProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [developerStats].
|
||||||
|
class DeveloperStatsProvider
|
||||||
|
extends AutoDisposeFutureProvider<DeveloperStats?> {
|
||||||
|
/// See also [developerStats].
|
||||||
|
DeveloperStatsProvider(String? uname)
|
||||||
|
: this._internal(
|
||||||
|
(ref) => developerStats(ref as DeveloperStatsRef, uname),
|
||||||
|
from: developerStatsProvider,
|
||||||
|
name: r'developerStatsProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$developerStatsHash,
|
||||||
|
dependencies: DeveloperStatsFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
DeveloperStatsFamily._allTransitiveDependencies,
|
||||||
|
uname: uname,
|
||||||
|
);
|
||||||
|
|
||||||
|
DeveloperStatsProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.uname,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String? uname;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(
|
||||||
|
FutureOr<DeveloperStats?> Function(DeveloperStatsRef provider) create,
|
||||||
|
) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: DeveloperStatsProvider._internal(
|
||||||
|
(ref) => create(ref as DeveloperStatsRef),
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
uname: uname,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeFutureProviderElement<DeveloperStats?> createElement() {
|
||||||
|
return _DeveloperStatsProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is DeveloperStatsProvider && other.uname == uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, uname.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin DeveloperStatsRef on AutoDisposeFutureProviderRef<DeveloperStats?> {
|
||||||
|
/// The parameter `uname` of this provider.
|
||||||
|
String? get uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DeveloperStatsProviderElement
|
||||||
|
extends AutoDisposeFutureProviderElement<DeveloperStats?>
|
||||||
|
with DeveloperStatsRef {
|
||||||
|
_DeveloperStatsProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get uname => (origin as DeveloperStatsProvider).uname;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$developersHash() => r'f52639d3c21aafbf235c8ae33f35448baf2989a1';
|
||||||
|
|
||||||
|
/// See also [developers].
|
||||||
|
@ProviderFor(developers)
|
||||||
|
final developersProvider =
|
||||||
|
AutoDisposeFutureProvider<List<SnPublisher>>.internal(
|
||||||
|
developers,
|
||||||
|
name: r'developersProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$developersHash,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
);
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
typedef DevelopersRef = AutoDisposeFutureProviderRef<List<SnPublisher>>;
|
||||||
|
// 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
|
||||||
12
lib/screens/developers/new_app.dart
Normal file
12
lib/screens/developers/new_app.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:island/screens/developers/edit_app.dart';
|
||||||
|
|
||||||
|
class NewCustomAppScreen extends StatelessWidget {
|
||||||
|
final String publisherName;
|
||||||
|
const NewCustomAppScreen({super.key, required this.publisherName});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return EditAppScreen(publisherName: publisherName);
|
||||||
|
}
|
||||||
|
}
|
||||||
142
lib/screens/discovery/articles.dart
Normal file
142
lib/screens/discovery/articles.dart
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/web_article_card.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
|
|
||||||
|
part 'articles.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class ArticlesListNotifier extends _$ArticlesListNotifier
|
||||||
|
with CursorPagingNotifierMixin<SnWebArticle> {
|
||||||
|
static const int _pageSize = 20;
|
||||||
|
|
||||||
|
Map<String, dynamic> _params = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnWebArticle>> build({
|
||||||
|
String? feedId,
|
||||||
|
String? publisherId,
|
||||||
|
}) async {
|
||||||
|
_params = {
|
||||||
|
if (feedId != null) 'feedId': feedId,
|
||||||
|
if (publisherId != null) 'publisherId': publisherId,
|
||||||
|
};
|
||||||
|
return fetch(cursor: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CursorPagingData<SnWebArticle>> fetch({
|
||||||
|
required String? cursor,
|
||||||
|
}) async {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||||
|
|
||||||
|
final queryParams = {'limit': _pageSize, 'offset': offset, ..._params};
|
||||||
|
|
||||||
|
try {
|
||||||
|
final response = await client.get(
|
||||||
|
'/feeds/articles',
|
||||||
|
queryParameters: queryParams,
|
||||||
|
);
|
||||||
|
|
||||||
|
final List<dynamic> data = response.data;
|
||||||
|
final articles =
|
||||||
|
data
|
||||||
|
.map(
|
||||||
|
(json) => SnWebArticle.fromJson(json as Map<String, dynamic>),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final total = int.tryParse(response.headers.value('X-Total') ?? '0') ?? 0;
|
||||||
|
final hasMore = offset + articles.length < total;
|
||||||
|
final nextCursor = hasMore ? (offset + articles.length).toString() : null;
|
||||||
|
|
||||||
|
return CursorPagingData(
|
||||||
|
items: articles,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error fetching articles: $e');
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SliverArticlesList extends ConsumerWidget {
|
||||||
|
final String? feedId;
|
||||||
|
final String? publisherId;
|
||||||
|
final Color? backgroundColor;
|
||||||
|
final EdgeInsets? padding;
|
||||||
|
final Function? onRefresh;
|
||||||
|
|
||||||
|
const SliverArticlesList({
|
||||||
|
super.key,
|
||||||
|
this.feedId,
|
||||||
|
this.publisherId,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.padding,
|
||||||
|
this.onRefresh,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return PagingHelperSliverView(
|
||||||
|
provider: articlesListNotifierProvider(
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
),
|
||||||
|
futureRefreshable:
|
||||||
|
articlesListNotifierProvider(
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
).future,
|
||||||
|
notifierRefreshable:
|
||||||
|
articlesListNotifierProvider(
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
).notifier,
|
||||||
|
contentBuilder:
|
||||||
|
(data, widgetCount, endItemView) => SliverList.builder(
|
||||||
|
itemCount: widgetCount,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index == widgetCount - 1) {
|
||||||
|
return endItemView;
|
||||||
|
}
|
||||||
|
|
||||||
|
final article = data.items[index];
|
||||||
|
return WebArticleCard(article: article, showDetails: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ArticlesScreen extends ConsumerWidget {
|
||||||
|
final String? feedId;
|
||||||
|
final String? publisherId;
|
||||||
|
final String? title;
|
||||||
|
|
||||||
|
const ArticlesScreen({super.key, this.feedId, this.publisherId, this.title});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: Text(title ?? 'Articles')),
|
||||||
|
body: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.only(top: 8, left: 8, right: 8),
|
||||||
|
sliver: SliverArticlesList(
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
206
lib/screens/discovery/articles.g.dart
Normal file
206
lib/screens/discovery/articles.g.dart
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'articles.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// RiverpodGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
String _$articlesListNotifierHash() =>
|
||||||
|
r'924f2344c3bbf0ff7b92fe69e88d3b64a534b538';
|
||||||
|
|
||||||
|
/// 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 _$ArticlesListNotifier
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<CursorPagingData<SnWebArticle>> {
|
||||||
|
late final String? feedId;
|
||||||
|
late final String? publisherId;
|
||||||
|
|
||||||
|
FutureOr<CursorPagingData<SnWebArticle>> build({
|
||||||
|
String? feedId,
|
||||||
|
String? publisherId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [ArticlesListNotifier].
|
||||||
|
@ProviderFor(ArticlesListNotifier)
|
||||||
|
const articlesListNotifierProvider = ArticlesListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [ArticlesListNotifier].
|
||||||
|
class ArticlesListNotifierFamily
|
||||||
|
extends Family<AsyncValue<CursorPagingData<SnWebArticle>>> {
|
||||||
|
/// See also [ArticlesListNotifier].
|
||||||
|
const ArticlesListNotifierFamily();
|
||||||
|
|
||||||
|
/// See also [ArticlesListNotifier].
|
||||||
|
ArticlesListNotifierProvider call({String? feedId, String? publisherId}) {
|
||||||
|
return ArticlesListNotifierProvider(
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
ArticlesListNotifierProvider getProviderOverride(
|
||||||
|
covariant ArticlesListNotifierProvider provider,
|
||||||
|
) {
|
||||||
|
return call(feedId: provider.feedId, publisherId: provider.publisherId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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'articlesListNotifierProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [ArticlesListNotifier].
|
||||||
|
class ArticlesListNotifierProvider
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
ArticlesListNotifier,
|
||||||
|
CursorPagingData<SnWebArticle>
|
||||||
|
> {
|
||||||
|
/// See also [ArticlesListNotifier].
|
||||||
|
ArticlesListNotifierProvider({String? feedId, String? publisherId})
|
||||||
|
: this._internal(
|
||||||
|
() =>
|
||||||
|
ArticlesListNotifier()
|
||||||
|
..feedId = feedId
|
||||||
|
..publisherId = publisherId,
|
||||||
|
from: articlesListNotifierProvider,
|
||||||
|
name: r'articlesListNotifierProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$articlesListNotifierHash,
|
||||||
|
dependencies: ArticlesListNotifierFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
ArticlesListNotifierFamily._allTransitiveDependencies,
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
);
|
||||||
|
|
||||||
|
ArticlesListNotifierProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.feedId,
|
||||||
|
required this.publisherId,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final String? feedId;
|
||||||
|
final String? publisherId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<CursorPagingData<SnWebArticle>> runNotifierBuild(
|
||||||
|
covariant ArticlesListNotifier notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(feedId: feedId, publisherId: publisherId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(ArticlesListNotifier Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: ArticlesListNotifierProvider._internal(
|
||||||
|
() =>
|
||||||
|
create()
|
||||||
|
..feedId = feedId
|
||||||
|
..publisherId = publisherId,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
feedId: feedId,
|
||||||
|
publisherId: publisherId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
ArticlesListNotifier,
|
||||||
|
CursorPagingData<SnWebArticle>
|
||||||
|
>
|
||||||
|
createElement() {
|
||||||
|
return _ArticlesListNotifierProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is ArticlesListNotifierProvider &&
|
||||||
|
other.feedId == feedId &&
|
||||||
|
other.publisherId == publisherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, feedId.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, publisherId.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin ArticlesListNotifierRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<CursorPagingData<SnWebArticle>> {
|
||||||
|
/// The parameter `feedId` of this provider.
|
||||||
|
String? get feedId;
|
||||||
|
|
||||||
|
/// The parameter `publisherId` of this provider.
|
||||||
|
String? get publisherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ArticlesListNotifierProviderElement
|
||||||
|
extends
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<
|
||||||
|
ArticlesListNotifier,
|
||||||
|
CursorPagingData<SnWebArticle>
|
||||||
|
>
|
||||||
|
with ArticlesListNotifierRef {
|
||||||
|
_ArticlesListNotifierProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get feedId => (origin as ArticlesListNotifierProvider).feedId;
|
||||||
|
@override
|
||||||
|
String? get publisherId =>
|
||||||
|
(origin as ArticlesListNotifierProvider).publisherId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
64
lib/screens/discovery/realms.dart
Normal file
64
lib/screens/discovery/realms.dart
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/realm/realm_list.dart';
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
class DiscoveryRealmsScreen extends HookConsumerWidget {
|
||||||
|
const DiscoveryRealmsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
Timer? debounceTimer;
|
||||||
|
final searchController = useTextEditingController();
|
||||||
|
final currentQuery = useState<String?>(null);
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
appBar: AppBar(title: Text('discoverRealms'.tr())),
|
||||||
|
body: Stack(
|
||||||
|
children: [
|
||||||
|
CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverGap(80),
|
||||||
|
SliverRealmList(
|
||||||
|
query: currentQuery.value,
|
||||||
|
key: ValueKey(currentQuery.value),
|
||||||
|
),
|
||||||
|
SliverGap(MediaQuery.of(context).padding.bottom + 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
child: SearchBar(
|
||||||
|
elevation: WidgetStateProperty.all(4),
|
||||||
|
controller: searchController,
|
||||||
|
hintText: 'search'.tr(),
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
padding: WidgetStateProperty.all(
|
||||||
|
const EdgeInsets.symmetric(horizontal: 24),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
if (debounceTimer?.isActive ?? false) {
|
||||||
|
debounceTimer?.cancel();
|
||||||
|
}
|
||||||
|
debounceTimer = Timer(const Duration(milliseconds: 300), () {
|
||||||
|
if (currentQuery.value != value) {
|
||||||
|
currentQuery.value = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,34 +1,39 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/activity.dart';
|
import 'package:island/models/activity.dart';
|
||||||
|
import 'package:island/models/publisher.dart';
|
||||||
|
import 'package:island/models/realm.dart';
|
||||||
|
import 'package:island/models/webfeed.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/route.gr.dart';
|
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
import 'package:island/widgets/check_in.dart';
|
import 'package:island/widgets/check_in.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/tour/tour.dart';
|
|
||||||
import 'package:island/screens/tabs.dart';
|
import 'package:island/screens/tabs.dart';
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/realm/realm_card.dart';
|
||||||
|
import 'package:island/widgets/publisher/publisher_card.dart';
|
||||||
|
import 'package:island/widgets/web_article_card.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'explore.g.dart';
|
part 'explore.g.dart';
|
||||||
|
|
||||||
@RoutePage()
|
class ExploreShellScreen extends HookConsumerWidget {
|
||||||
class ExploreShellScreen extends ConsumerWidget {
|
final Widget child;
|
||||||
const ExploreShellScreen({super.key});
|
const ExploreShellScreen({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isWide = isWideScreen(context);
|
final isWide = MediaQuery.of(context).size.width > 640;
|
||||||
|
|
||||||
if (isWide) {
|
if (isWide) {
|
||||||
return AppBackground(
|
return AppBackground(
|
||||||
@@ -37,17 +42,16 @@ class ExploreShellScreen extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
Flexible(flex: 2, child: ExploreScreen(isAside: true)),
|
Flexible(flex: 2, child: ExploreScreen(isAside: true)),
|
||||||
VerticalDivider(width: 1),
|
VerticalDivider(width: 1),
|
||||||
Flexible(flex: 3, child: AutoRouter()),
|
Flexible(flex: 3, child: child),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AppBackground(isRoot: true, child: AutoRouter());
|
return AppBackground(isRoot: true, child: child);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ExploreScreen extends HookConsumerWidget {
|
class ExploreScreen extends HookConsumerWidget {
|
||||||
final bool isAside;
|
final bool isAside;
|
||||||
const ExploreScreen({super.key, this.isAside = false});
|
const ExploreScreen({super.key, this.isAside = false});
|
||||||
@@ -85,48 +89,93 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
activityListNotifierProvider(currentFilter.value).notifier,
|
activityListNotifierProvider(currentFilter.value).notifier,
|
||||||
);
|
);
|
||||||
|
|
||||||
return TourTriggerWidget(
|
return AppScaffold(
|
||||||
child: AppScaffold(
|
|
||||||
extendBody: false, // Prevent conflicts with tabs navigation
|
extendBody: false, // Prevent conflicts with tabs navigation
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
toolbarHeight: 0,
|
toolbarHeight: 0,
|
||||||
bottom: TabBar(
|
bottom: PreferredSize(
|
||||||
|
preferredSize: const Size.fromHeight(48),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TabBar(
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
|
tabAlignment: TabAlignment.start,
|
||||||
|
isScrollable: true,
|
||||||
|
dividerColor: Colors.transparent,
|
||||||
tabs: [
|
tabs: [
|
||||||
Tab(
|
Tab(
|
||||||
child: Text(
|
icon: Tooltip(
|
||||||
'explore'.tr(),
|
message: 'explore'.tr(),
|
||||||
textAlign: TextAlign.center,
|
child: Icon(
|
||||||
style: TextStyle(
|
Symbols.explore,
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Tab(
|
Tab(
|
||||||
child: Text(
|
icon: Tooltip(
|
||||||
'exploreFilterSubscriptions'.tr(),
|
message: 'exploreFilterSubscriptions'.tr(),
|
||||||
textAlign: TextAlign.center,
|
child: Icon(
|
||||||
style: TextStyle(
|
Symbols.subscriptions,
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Tab(
|
Tab(
|
||||||
child: Text(
|
icon: Tooltip(
|
||||||
'exploreFilterFriends'.tr(),
|
message: 'exploreFilterFriends'.tr(),
|
||||||
textAlign: TextAlign.center,
|
child: Icon(
|
||||||
style: TextStyle(
|
Symbols.people,
|
||||||
color: Theme.of(context).appBarTheme.foregroundColor!,
|
color:
|
||||||
|
Theme.of(
|
||||||
|
context,
|
||||||
|
).appBarTheme.foregroundColor!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.push('/feeds/articles');
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.auto_stories,
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
),
|
||||||
|
tooltip: 'webArticlesStand'.tr(),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
context.push('/posts/search');
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Symbols.search,
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor!,
|
||||||
|
),
|
||||||
|
tooltip: 'search'.tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.padding(horizontal: 8)
|
||||||
|
.border(
|
||||||
|
bottom: 1 / MediaQuery.of(context).devicePixelRatio,
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
heroTag: Key("explore-page-fab"),
|
heroTag: Key("explore-page-fab"),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
context.router.push(PostComposeRoute()).then((value) {
|
context.push('/posts/compose').then((value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
activitiesNotifier.forceRefresh();
|
activitiesNotifier.forceRefresh();
|
||||||
}
|
}
|
||||||
@@ -137,13 +186,13 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
floatingActionButtonLocation: TabbedFabLocation(context),
|
floatingActionButtonLocation: TabbedFabLocation(context),
|
||||||
body: TabBarView(
|
body: TabBarView(
|
||||||
controller: tabController,
|
controller: tabController,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
children: [
|
children: [
|
||||||
_buildActivityList(ref, null),
|
_buildActivityList(ref, null),
|
||||||
_buildActivityList(ref, 'subscriptions'),
|
_buildActivityList(ref, 'subscriptions'),
|
||||||
_buildActivityList(ref, 'friends'),
|
_buildActivityList(ref, 'friends'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,6 +222,70 @@ class ExploreScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _DiscoveryActivityItem extends StatelessWidget {
|
||||||
|
final Map<String, dynamic> data;
|
||||||
|
|
||||||
|
const _DiscoveryActivityItem({required this.data});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
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),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _ActivityListView extends HookConsumerWidget {
|
class _ActivityListView extends HookConsumerWidget {
|
||||||
final CursorPagingData<SnActivity> data;
|
final CursorPagingData<SnActivity> data;
|
||||||
final int widgetCount;
|
final int widgetCount;
|
||||||
@@ -216,10 +329,14 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
itemWidget = PostItem(
|
itemWidget = PostItem(
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
isWideScreen(context) ? Colors.transparent : null,
|
isWideScreen(context) ? Colors.transparent : null,
|
||||||
item: SnPost.fromJson(item.data),
|
item: SnPost.fromJson(item.data!),
|
||||||
padding:
|
padding:
|
||||||
isReply
|
isReply
|
||||||
? EdgeInsets.only(left: 16, right: 16, bottom: 16)
|
? const EdgeInsets.only(
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
bottom: 16,
|
||||||
|
)
|
||||||
: null,
|
: null,
|
||||||
onRefresh: (_) {
|
onRefresh: (_) {
|
||||||
activitiesNotifier.forceRefresh();
|
activitiesNotifier.forceRefresh();
|
||||||
@@ -247,6 +364,9 @@ class _ActivityListView extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'discovery':
|
||||||
|
itemWidget = _DiscoveryActivityItem(data: item.data!);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
itemWidget = const Placeholder();
|
itemWidget = const Placeholder();
|
||||||
}
|
}
|
||||||
@@ -276,6 +396,7 @@ class ActivityListNotifier extends _$ActivityListNotifier
|
|||||||
if (cursor != null) 'cursor': cursor,
|
if (cursor != null) 'cursor': cursor,
|
||||||
'take': take,
|
'take': take,
|
||||||
if (filter != null) 'filter': filter,
|
if (filter != null) 'filter': filter,
|
||||||
|
if (kDebugMode) 'debugInclude': 'realms,publishers,articles',
|
||||||
};
|
};
|
||||||
|
|
||||||
final response = await client.get(
|
final response = await client.get(
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ part of 'explore.dart';
|
|||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$activityListNotifierHash() =>
|
String _$activityListNotifierHash() =>
|
||||||
r'14ec2f211c86e1e64a9a34b142d0e8f78ff6361a';
|
r'98b62fb9b958023d2c9e320af7ec1f1244836f49';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/user.dart';
|
import 'package:island/models/user.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
import 'package:relative_time/relative_time.dart';
|
import 'package:relative_time/relative_time.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
part 'notification.g.dart';
|
part 'notification.g.dart';
|
||||||
|
|
||||||
@@ -107,7 +106,6 @@ class NotificationListNotifier extends _$NotificationListNotifier
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class NotificationScreen extends HookConsumerWidget {
|
class NotificationScreen extends HookConsumerWidget {
|
||||||
const NotificationScreen({super.key});
|
const NotificationScreen({super.key});
|
||||||
|
|
||||||
@@ -181,36 +179,17 @@ class NotificationScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (notification.meta['link'] is String) {
|
if (notification.meta['action_uri'] != null) {
|
||||||
final href = notification.meta['link'];
|
var uri = notification.meta['action_uri'] as String;
|
||||||
final uri = Uri.tryParse(href);
|
if (uri.startsWith('/')) {
|
||||||
if (uri == null) {
|
// In-app routes
|
||||||
showSnackBar(
|
rootNavigatorKey.currentContext?.push(
|
||||||
'brokenLink'.tr(args: []),
|
notification.meta['action_uri'],
|
||||||
action: SnackBarAction(
|
|
||||||
label: 'copyToClipboard'.tr(),
|
|
||||||
onPressed: () {
|
|
||||||
Clipboard.setData(ClipboardData(text: href));
|
|
||||||
clearSnackBar(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
return;
|
} else {
|
||||||
|
// External URLs
|
||||||
|
launchUrlString(uri);
|
||||||
}
|
}
|
||||||
if (uri.scheme == 'solian') {
|
|
||||||
context.router.pushPath(
|
|
||||||
['', uri.host, ...uri.pathSegments].join('/'),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showConfirmAlert(
|
|
||||||
'openLinkConfirmDescription'.tr(args: [href]),
|
|
||||||
'openLinkConfirm'.tr(),
|
|
||||||
).then((value) {
|
|
||||||
if (value) {
|
|
||||||
launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
@@ -16,7 +15,7 @@ import 'package:island/widgets/content/cloud_files.dart';
|
|||||||
import 'package:island/widgets/post/compose_shared.dart';
|
import 'package:island/widgets/post/compose_shared.dart';
|
||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/post/publishers_modal.dart';
|
import 'package:island/widgets/post/publishers_modal.dart';
|
||||||
import 'package:island/screens/posts/detail.dart';
|
import 'package:island/screens/posts/post_detail.dart';
|
||||||
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
import 'package:island/widgets/post/compose_settings_sheet.dart';
|
||||||
import 'package:island/services/compose_storage_db.dart';
|
import 'package:island/services/compose_storage_db.dart';
|
||||||
import 'package:island/widgets/post/draft_manager.dart';
|
import 'package:island/widgets/post/draft_manager.dart';
|
||||||
@@ -40,10 +39,9 @@ sealed class PostComposeInitialState with _$PostComposeInitialState {
|
|||||||
_$PostComposeInitialStateFromJson(json);
|
_$PostComposeInitialStateFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class PostEditScreen extends HookConsumerWidget {
|
class PostEditScreen extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
const PostEditScreen({super.key, @PathParam('id') required this.id});
|
const PostEditScreen({super.key, required this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -66,7 +64,6 @@ class PostEditScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class PostComposeScreen extends HookConsumerWidget {
|
class PostComposeScreen extends HookConsumerWidget {
|
||||||
final SnPost? originalPost;
|
final SnPost? originalPost;
|
||||||
final SnPost? repliedPost;
|
final SnPost? repliedPost;
|
||||||
@@ -78,7 +75,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
this.originalPost,
|
this.originalPost,
|
||||||
this.repliedPost,
|
this.repliedPost,
|
||||||
this.forwardedPost,
|
this.forwardedPost,
|
||||||
@QueryParam('type') this.type,
|
this.type,
|
||||||
this.initialState,
|
this.initialState,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -106,15 +103,32 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
originalPost: originalPost,
|
originalPost: originalPost,
|
||||||
forwardedPost: effectiveForwardedPost,
|
forwardedPost: effectiveForwardedPost,
|
||||||
repliedPost: effectiveRepliedPost,
|
repliedPost: effectiveRepliedPost,
|
||||||
|
postType: 0, // Regular post type
|
||||||
),
|
),
|
||||||
[originalPost, effectiveForwardedPost, effectiveRepliedPost],
|
[originalPost, effectiveForwardedPost, effectiveRepliedPost],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add a listener to the entire state to trigger rebuilds
|
||||||
|
final stateNotifier = useMemoized(
|
||||||
|
() => Listenable.merge([
|
||||||
|
state.titleController,
|
||||||
|
state.descriptionController,
|
||||||
|
state.contentController,
|
||||||
|
state.visibility,
|
||||||
|
state.attachments,
|
||||||
|
state.attachmentProgress,
|
||||||
|
state.currentPublisher,
|
||||||
|
state.submitting,
|
||||||
|
]),
|
||||||
|
[state],
|
||||||
|
);
|
||||||
|
useListenable(stateNotifier);
|
||||||
|
|
||||||
// Start auto-save when component mounts
|
// Start auto-save when component mounts
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
// Only auto-save for new posts, not edits
|
// Only auto-save for new posts, not edits
|
||||||
state.startAutoSave(ref, postType: 0);
|
state.startAutoSave(ref);
|
||||||
}
|
}
|
||||||
return () => state.stopAutoSave();
|
return () => state.stopAutoSave();
|
||||||
}, [state]);
|
}, [state]);
|
||||||
@@ -153,13 +167,18 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
final drafts = ref.read(composeStorageNotifierProvider);
|
final drafts = ref.read(composeStorageNotifierProvider);
|
||||||
if (drafts.isNotEmpty) {
|
if (drafts.isNotEmpty) {
|
||||||
final mostRecentDraft = drafts.values.reduce(
|
final mostRecentDraft = drafts.values.reduce(
|
||||||
(a, b) => (a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0)) ? a : b,
|
(a, b) =>
|
||||||
|
(a.updatedAt ?? DateTime(0)).isAfter(b.updatedAt ?? DateTime(0))
|
||||||
|
? a
|
||||||
|
: b,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only load if the draft has meaningful content
|
// Only load if the draft has meaningful content
|
||||||
if (mostRecentDraft.content?.isNotEmpty == true || mostRecentDraft.title?.isNotEmpty == true) {
|
if (mostRecentDraft.content?.isNotEmpty == true ||
|
||||||
|
mostRecentDraft.title?.isNotEmpty == true) {
|
||||||
state.titleController.text = mostRecentDraft.title ?? '';
|
state.titleController.text = mostRecentDraft.title ?? '';
|
||||||
state.descriptionController.text = mostRecentDraft.description ?? '';
|
state.descriptionController.text =
|
||||||
|
mostRecentDraft.description ?? '';
|
||||||
state.contentController.text = mostRecentDraft.content ?? '';
|
state.contentController.text = mostRecentDraft.content ?? '';
|
||||||
state.visibility.value = mostRecentDraft.visibility;
|
state.visibility.value = mostRecentDraft.visibility;
|
||||||
}
|
}
|
||||||
@@ -187,6 +206,8 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
titleController: state.titleController,
|
titleController: state.titleController,
|
||||||
descriptionController: state.descriptionController,
|
descriptionController: state.descriptionController,
|
||||||
visibility: state.visibility,
|
visibility: state.visibility,
|
||||||
|
tagsController: state.tagsController,
|
||||||
|
categoriesController: state.categoriesController,
|
||||||
onVisibilityChanged: () {
|
onVisibilityChanged: () {
|
||||||
// Trigger rebuild if needed
|
// Trigger rebuild if needed
|
||||||
},
|
},
|
||||||
@@ -206,9 +227,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
itemCount: state.attachments.value.length,
|
itemCount: state.attachments.value.length,
|
||||||
itemBuilder: (context, idx) {
|
itemBuilder: (context, idx) {
|
||||||
return ValueListenableBuilder<Map<int, double>>(
|
final progressMap = state.attachmentProgress.value;
|
||||||
valueListenable: state.attachmentProgress,
|
|
||||||
builder: (context, progressMap, _) {
|
|
||||||
return AttachmentPreview(
|
return AttachmentPreview(
|
||||||
item: state.attachments.value[idx],
|
item: state.attachments.value[idx],
|
||||||
progress: progressMap[idx],
|
progress: progressMap[idx],
|
||||||
@@ -225,8 +244,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildNarrowAttachmentList() {
|
Widget buildNarrowAttachmentList() {
|
||||||
@@ -235,9 +252,8 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
for (var idx = 0; idx < state.attachments.value.length; idx++)
|
for (var idx = 0; idx < state.attachments.value.length; idx++)
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.only(bottom: 8),
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
child: ValueListenableBuilder<Map<int, double>>(
|
child: () {
|
||||||
valueListenable: state.attachmentProgress,
|
final progressMap = state.attachmentProgress.value;
|
||||||
builder: (context, progressMap, _) {
|
|
||||||
return AttachmentPreview(
|
return AttachmentPreview(
|
||||||
item: state.attachments.value[idx],
|
item: state.attachments.value[idx],
|
||||||
progress: progressMap[idx],
|
progress: progressMap[idx],
|
||||||
@@ -253,8 +269,7 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
}(),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -290,7 +305,8 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
state.titleController.text = draft.title ?? '';
|
state.titleController.text = draft.title ?? '';
|
||||||
state.descriptionController.text =
|
state.descriptionController.text =
|
||||||
draft.description ?? '';
|
draft.description ?? '';
|
||||||
state.contentController.text = draft.content ?? '';
|
state.contentController.text =
|
||||||
|
draft.content ?? '';
|
||||||
state.visibility.value = draft.visibility;
|
state.visibility.value = draft.visibility;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -309,12 +325,9 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
onPressed: showSettingsSheet,
|
onPressed: showSettingsSheet,
|
||||||
tooltip: 'postSettings'.tr(),
|
tooltip: 'postSettings'.tr(),
|
||||||
),
|
),
|
||||||
ValueListenableBuilder<bool>(
|
IconButton(
|
||||||
valueListenable: state.submitting,
|
|
||||||
builder: (context, submitting, _) {
|
|
||||||
return IconButton(
|
|
||||||
onPressed:
|
onPressed:
|
||||||
submitting
|
state.submitting.value
|
||||||
? null
|
? null
|
||||||
: () => ComposeLogic.performAction(
|
: () => ComposeLogic.performAction(
|
||||||
ref,
|
ref,
|
||||||
@@ -323,10 +336,9 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
originalPost: originalPost,
|
originalPost: originalPost,
|
||||||
repliedPost: repliedPost,
|
repliedPost: repliedPost,
|
||||||
forwardedPost: forwardedPost,
|
forwardedPost: forwardedPost,
|
||||||
postType: 0, // Regular post type
|
|
||||||
),
|
),
|
||||||
icon:
|
icon:
|
||||||
submitting
|
state.submitting.value
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
@@ -336,12 +348,8 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).center()
|
).center()
|
||||||
: Icon(
|
: Icon(
|
||||||
originalPost != null
|
originalPost != null ? Symbols.edit : Symbols.upload,
|
||||||
? Symbols.edit
|
|
||||||
: Symbols.upload,
|
|
||||||
),
|
),
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
const Gap(8),
|
const Gap(8),
|
||||||
],
|
],
|
||||||
@@ -402,7 +410,6 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
originalPost: originalPost,
|
originalPost: originalPost,
|
||||||
repliedPost: repliedPost,
|
repliedPost: repliedPost,
|
||||||
forwardedPost: forwardedPost,
|
forwardedPost: forwardedPost,
|
||||||
postType: 0, // Regular post type
|
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: state.contentController,
|
controller: state.contentController,
|
||||||
@@ -423,22 +430,17 @@ class PostComposeScreen extends HookConsumerWidget {
|
|||||||
const Gap(8),
|
const Gap(8),
|
||||||
|
|
||||||
// Attachments preview
|
// Attachments preview
|
||||||
ValueListenableBuilder<List<UniversalFile>>(
|
if (state.attachments.value.isNotEmpty)
|
||||||
valueListenable: state.attachments,
|
LayoutBuilder(
|
||||||
builder: (context, attachments, _) {
|
|
||||||
if (attachments.isEmpty) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
return LayoutBuilder(
|
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final isWide = isWideScreen(context);
|
final isWide = isWideScreen(context);
|
||||||
return isWide
|
return isWide
|
||||||
? buildWideAttachmentGrid()
|
? buildWideAttachmentGrid()
|
||||||
: buildNarrowAttachmentList();
|
: buildNarrowAttachmentList();
|
||||||
},
|
},
|
||||||
);
|
)
|
||||||
},
|
else
|
||||||
),
|
const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -13,7 +12,7 @@ import 'package:island/models/post.dart';
|
|||||||
import 'package:island/screens/creators/publishers.dart';
|
import 'package:island/screens/creators/publishers.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/app_scaffold.dart';
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
import 'package:island/screens/posts/detail.dart';
|
import 'package:island/screens/posts/post_detail.dart';
|
||||||
import 'package:island/widgets/content/attachment_preview.dart';
|
import 'package:island/widgets/content/attachment_preview.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/markdown.dart';
|
import 'package:island/widgets/content/markdown.dart';
|
||||||
@@ -26,10 +25,9 @@ import 'package:island/widgets/post/draft_manager.dart';
|
|||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ArticleEditScreen extends HookConsumerWidget {
|
class ArticleEditScreen extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
const ArticleEditScreen({super.key, @PathParam('id') required this.id});
|
const ArticleEditScreen({super.key, required this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -50,7 +48,6 @@ class ArticleEditScreen extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class ArticleComposeScreen extends HookConsumerWidget {
|
class ArticleComposeScreen extends HookConsumerWidget {
|
||||||
final SnPost? originalPost;
|
final SnPost? originalPost;
|
||||||
|
|
||||||
@@ -63,7 +60,10 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
final publishers = ref.watch(publishersManagedProvider);
|
final publishers = ref.watch(publishersManagedProvider);
|
||||||
final state = useMemoized(
|
final state = useMemoized(
|
||||||
() => ComposeLogic.createState(originalPost: originalPost),
|
() => ComposeLogic.createState(
|
||||||
|
originalPost: originalPost,
|
||||||
|
postType: 1, // Article type
|
||||||
|
),
|
||||||
[originalPost],
|
[originalPost],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
// Only auto-save for new articles, not edits
|
// Only auto-save for new articles, not edits
|
||||||
autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
autoSaveTimer = Timer.periodic(const Duration(seconds: 3), (_) {
|
||||||
ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1);
|
ComposeLogic.saveDraftWithoutUpload(ref, state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return () {
|
return () {
|
||||||
@@ -81,7 +81,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
state.stopAutoSave();
|
state.stopAutoSave();
|
||||||
// Save final draft before disposing
|
// Save final draft before disposing
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1);
|
ComposeLogic.saveDraftWithoutUpload(ref, state);
|
||||||
}
|
}
|
||||||
ComposeLogic.dispose(state);
|
ComposeLogic.dispose(state);
|
||||||
autoSaveTimer?.cancel();
|
autoSaveTimer?.cancel();
|
||||||
@@ -143,6 +143,8 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
titleController: state.titleController,
|
titleController: state.titleController,
|
||||||
descriptionController: state.descriptionController,
|
descriptionController: state.descriptionController,
|
||||||
visibility: state.visibility,
|
visibility: state.visibility,
|
||||||
|
tagsController: state.tagsController,
|
||||||
|
categoriesController: state.categoriesController,
|
||||||
onVisibilityChanged: () {
|
onVisibilityChanged: () {
|
||||||
// Trigger rebuild if needed
|
// Trigger rebuild if needed
|
||||||
},
|
},
|
||||||
@@ -363,7 +365,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
return PopScope(
|
return PopScope(
|
||||||
onPopInvoked: (_) {
|
onPopInvoked: (_) {
|
||||||
if (originalPost == null) {
|
if (originalPost == null) {
|
||||||
ComposeLogic.saveDraftWithoutUpload(ref, state, postType: 1);
|
ComposeLogic.saveDraftWithoutUpload(ref, state);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: AppScaffold(
|
child: AppScaffold(
|
||||||
@@ -411,7 +413,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.save),
|
icon: const Icon(Symbols.save),
|
||||||
onPressed: () => ComposeLogic.saveDraft(ref, state, postType: 1),
|
onPressed: () => ComposeLogic.saveDraft(ref, state),
|
||||||
tooltip: 'saveDraft'.tr(),
|
tooltip: 'saveDraft'.tr(),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
@@ -438,7 +440,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
state,
|
state,
|
||||||
context,
|
context,
|
||||||
originalPost: originalPost,
|
originalPost: originalPost,
|
||||||
postType: 1, // Article type
|
|
||||||
),
|
),
|
||||||
icon:
|
icon:
|
||||||
submitting
|
submitting
|
||||||
@@ -531,18 +532,17 @@ class ArticleComposeScreen extends HookConsumerWidget {
|
|||||||
if (isPaste && isModifierPressed) {
|
if (isPaste && isModifierPressed) {
|
||||||
ComposeLogic.handlePaste(state);
|
ComposeLogic.handlePaste(state);
|
||||||
} else if (isSave && isModifierPressed) {
|
} else if (isSave && isModifierPressed) {
|
||||||
ComposeLogic.saveDraft(ref, state, postType: 1);
|
ComposeLogic.saveDraft(ref, state);
|
||||||
|
ComposeLogic.saveDraft(ref, state);
|
||||||
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
|
} else if (isSubmit && isModifierPressed && !state.submitting.value) {
|
||||||
ComposeLogic.performAction(
|
ComposeLogic.performAction(
|
||||||
ref,
|
ref,
|
||||||
state,
|
state,
|
||||||
context,
|
context,
|
||||||
originalPost: originalPost,
|
originalPost: originalPost,
|
||||||
postType: 1, // Article type
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to save article draft
|
// Helper method to save article draft
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -10,10 +9,11 @@ import 'package:island/widgets/app_scaffold.dart';
|
|||||||
import 'package:island/widgets/post/post_item.dart';
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
import 'package:island/widgets/post/post_quick_reply.dart';
|
import 'package:island/widgets/post/post_quick_reply.dart';
|
||||||
import 'package:island/widgets/post/post_replies.dart';
|
import 'package:island/widgets/post/post_replies.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
part 'detail.g.dart';
|
part 'post_detail.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<SnPost?> post(Ref ref, String id) async {
|
Future<SnPost?> post(Ref ref, String id) async {
|
||||||
@@ -22,21 +22,43 @@ Future<SnPost?> post(Ref ref, String id) async {
|
|||||||
return SnPost.fromJson(resp.data);
|
return SnPost.fromJson(resp.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
final postStateProvider = StateNotifierProvider.family<PostState, AsyncValue<SnPost?>, String>(
|
||||||
|
(ref, id) => PostState(ref, id),
|
||||||
|
);
|
||||||
|
|
||||||
|
class PostState extends StateNotifier<AsyncValue<SnPost?>> {
|
||||||
|
final Ref _ref;
|
||||||
|
final String _id;
|
||||||
|
|
||||||
|
PostState(this._ref, this._id) : super(const AsyncValue.loading()) {
|
||||||
|
// Initialize with the initial post data
|
||||||
|
_ref.listen<AsyncValue<SnPost?>>(
|
||||||
|
postProvider(_id),
|
||||||
|
(_, next) => state = next,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updatePost(SnPost? newPost) {
|
||||||
|
if (newPost != null) {
|
||||||
|
state = AsyncData(newPost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class PostDetailScreen extends HookConsumerWidget {
|
class PostDetailScreen extends HookConsumerWidget {
|
||||||
final String id;
|
final String id;
|
||||||
const PostDetailScreen({super.key, @PathParam('id') required this.id});
|
const PostDetailScreen({super.key, required this.id});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final post = ref.watch(postProvider(id));
|
final postState = ref.watch(postStateProvider(id));
|
||||||
final user = ref.watch(userInfoProvider);
|
final user = ref.watch(userInfoProvider);
|
||||||
|
|
||||||
final isWide = isWideScreen(context);
|
final isWide = isWideScreen(context);
|
||||||
|
|
||||||
return AppScaffold(
|
return AppScaffold(
|
||||||
appBar: AppBar(title: const Text('Post')),
|
appBar: AppBar(title: const Text('Post')),
|
||||||
body: post.when(
|
body: postState.when(
|
||||||
data: (post) {
|
data: (post) {
|
||||||
return Stack(
|
return Stack(
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
@@ -51,6 +73,10 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
isOpenable: false,
|
isOpenable: false,
|
||||||
isFullPost: true,
|
isFullPost: true,
|
||||||
backgroundColor: isWide ? Colors.transparent : null,
|
backgroundColor: isWide ? Colors.transparent : null,
|
||||||
|
onUpdate: (newItem) {
|
||||||
|
// Update the local state with the new post data
|
||||||
|
ref.read(postStateProvider(id).notifier).updatePost(newItem);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const Divider(height: 1),
|
const Divider(height: 1),
|
||||||
],
|
],
|
||||||
@@ -67,11 +93,15 @@ class PostDetailScreen extends HookConsumerWidget {
|
|||||||
right: 0,
|
right: 0,
|
||||||
child: Material(
|
child: Material(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
child: PostQuickReply(
|
child: postState.when(
|
||||||
parent: post,
|
data: (post) => PostQuickReply(
|
||||||
|
parent: post!,
|
||||||
onPosted: () {
|
onPosted: () {
|
||||||
ref.invalidate(postRepliesNotifierProvider(id));
|
ref.invalidate(postRepliesNotifierProvider(id));
|
||||||
},
|
},
|
||||||
|
),
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
error: (_, __) => const SizedBox.shrink(),
|
||||||
).padding(
|
).padding(
|
||||||
bottom: MediaQuery.of(context).padding.bottom + 16,
|
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||||
top: 16,
|
top: 16,
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'detail.dart';
|
part of 'post_detail.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
165
lib/screens/posts/post_search.dart
Normal file
165
lib/screens/posts/post_search.dart
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/widgets/post/post_item.dart';
|
||||||
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
|
|
||||||
|
final postSearchNotifierProvider = StateNotifierProvider.autoDispose<
|
||||||
|
PostSearchNotifier,
|
||||||
|
AsyncValue<CursorPagingData<SnPost>>
|
||||||
|
>((ref) => PostSearchNotifier(ref));
|
||||||
|
|
||||||
|
class PostSearchNotifier
|
||||||
|
extends StateNotifier<AsyncValue<CursorPagingData<SnPost>>> {
|
||||||
|
final AutoDisposeRef ref;
|
||||||
|
static const int _pageSize = 20;
|
||||||
|
String _currentQuery = '';
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
PostSearchNotifier(this.ref) : super(const AsyncValue.loading()) {
|
||||||
|
state = const AsyncValue.data(
|
||||||
|
CursorPagingData(items: [], hasMore: false, nextCursor: null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> search(String query) async {
|
||||||
|
if (_isLoading) return;
|
||||||
|
|
||||||
|
_currentQuery = query.trim();
|
||||||
|
if (_currentQuery.isEmpty) {
|
||||||
|
state = AsyncValue.data(
|
||||||
|
CursorPagingData(items: [], hasMore: false, nextCursor: null),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch(cursor: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> fetch({String? cursor}) async {
|
||||||
|
if (_isLoading) return;
|
||||||
|
|
||||||
|
_isLoading = true;
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final offset = cursor == null ? 0 : int.parse(cursor);
|
||||||
|
|
||||||
|
final response = await client.get(
|
||||||
|
'/posts/search',
|
||||||
|
queryParameters: {
|
||||||
|
'query': _currentQuery,
|
||||||
|
'offset': offset,
|
||||||
|
'take': _pageSize,
|
||||||
|
'useVector': true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
final data = response.data as List;
|
||||||
|
final posts = data.map((json) => SnPost.fromJson(json)).toList();
|
||||||
|
final hasMore = posts.length == _pageSize;
|
||||||
|
final nextCursor = hasMore ? (offset + posts.length).toString() : null;
|
||||||
|
|
||||||
|
state = AsyncValue.data(
|
||||||
|
CursorPagingData(
|
||||||
|
items: posts,
|
||||||
|
hasMore: hasMore,
|
||||||
|
nextCursor: nextCursor,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e, stack) {
|
||||||
|
state = AsyncValue.error(e, stack);
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PostSearchScreen extends ConsumerStatefulWidget {
|
||||||
|
const PostSearchScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PostSearchScreen> createState() => _PostSearchScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PostSearchScreenState extends ConsumerState<PostSearchScreen> {
|
||||||
|
final _searchController = TextEditingController();
|
||||||
|
final _debounce = Duration(milliseconds: 500);
|
||||||
|
Timer? _debounceTimer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
_debounceTimer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearchChanged(String query) {
|
||||||
|
if (_debounceTimer?.isActive ?? false) _debounceTimer!.cancel();
|
||||||
|
|
||||||
|
_debounceTimer = Timer(_debounce, () {
|
||||||
|
ref.read(postSearchNotifierProvider.notifier).search(query);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: TextField(
|
||||||
|
controller: _searchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: 'Search posts...',
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintStyle: TextStyle(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context).appBarTheme.foregroundColor,
|
||||||
|
),
|
||||||
|
onChanged: _onSearchChanged,
|
||||||
|
onSubmitted: (value) {
|
||||||
|
ref.read(postSearchNotifierProvider.notifier).search(value);
|
||||||
|
},
|
||||||
|
autofocus: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Consumer(
|
||||||
|
builder: (context, ref, child) {
|
||||||
|
final searchState = ref.watch(postSearchNotifierProvider);
|
||||||
|
|
||||||
|
return searchState.when(
|
||||||
|
data: (data) {
|
||||||
|
if (data.items.isEmpty && _searchController.text.isNotEmpty) {
|
||||||
|
return const Center(child: Text('No results found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: data.items.length + (data.hasMore ? 1 : 0),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
if (index >= data.items.length) {
|
||||||
|
ref
|
||||||
|
.read(postSearchNotifierProvider.notifier)
|
||||||
|
.fetch(cursor: data.nextCursor);
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
final post = data.items[index];
|
||||||
|
return Column(
|
||||||
|
children: [PostItem(item: post), const Divider(height: 1)],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
|
error: (error, stack) => Center(child: Text('Error: $error')),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/post.dart';
|
import 'package:island/models/post.dart';
|
||||||
|
import 'package:island/models/publisher.dart';
|
||||||
import 'package:island/models/user.dart';
|
import 'package:island/models/user.dart';
|
||||||
import 'package:island/pods/config.dart';
|
import 'package:island/pods/config.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
@@ -54,6 +55,7 @@ Future<SnSubscriptionStatus> publisherSubscriptionStatus(
|
|||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async {
|
Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async {
|
||||||
|
try {
|
||||||
final publisher = await ref.watch(publisherProvider(pubName).future);
|
final publisher = await ref.watch(publisherProvider(pubName).future);
|
||||||
if (publisher.background == null) return null;
|
if (publisher.background == null) return null;
|
||||||
final palette = await PaletteGenerator.fromImageProvider(
|
final palette = await PaletteGenerator.fromImageProvider(
|
||||||
@@ -65,15 +67,14 @@ Future<Color?> publisherAppbarForcegroundColor(Ref ref, String pubName) async {
|
|||||||
final dominantColor = palette.dominantColor?.color;
|
final dominantColor = palette.dominantColor?.color;
|
||||||
if (dominantColor == null) return null;
|
if (dominantColor == null) return null;
|
||||||
return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
return dominantColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||||
|
} catch (_) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class PublisherProfileScreen extends HookConsumerWidget {
|
class PublisherProfileScreen extends HookConsumerWidget {
|
||||||
final String name;
|
final String name;
|
||||||
const PublisherProfileScreen({
|
const PublisherProfileScreen({super.key, required this.name});
|
||||||
super.key,
|
|
||||||
@PathParam("name") required this.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -186,7 +187,7 @@ class PublisherProfileScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
context.router.pushPath('/account/${data.name}');
|
context.push('/account/${data.name}');
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
|||||||
@@ -400,7 +400,7 @@ class _PublisherSubscriptionStatusProviderElement
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _$publisherAppbarForcegroundColorHash() =>
|
String _$publisherAppbarForcegroundColorHash() =>
|
||||||
r'3ff2eebb48d3f3af1907052f471e648f5b14b13c';
|
r'd781a806a242aea5c1609ec98c97c52fdd9f7db1';
|
||||||
|
|
||||||
/// See also [publisherAppbarForcegroundColor].
|
/// See also [publisherAppbarForcegroundColor].
|
||||||
@ProviderFor(publisherAppbarForcegroundColor)
|
@ProviderFor(publisherAppbarForcegroundColor)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user