diff --git a/.fvmrc b/.fvmrc index 19e8577e95..d80cf5773b 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.41.6" + "flutter": "3.41.9" } \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..ec4bb386bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9b9fcd935c..8f64efa49e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ .svn/ .swiftpm/ migrate_working_dir/ +Package.resolved # IntelliJ related *.iml @@ -148,7 +149,7 @@ pili_release.json dist -test*.dart +test* ohos/build-profile.json5 diff --git a/.vscode/build_env.dart b/.vscode/build_env.dart index a454a683b7..587be31bc0 100644 --- a/.vscode/build_env.dart +++ b/.vscode/build_env.dart @@ -5,13 +5,15 @@ Future _updatePubspecVersion(int versionCode) async { final pubspecPath = './pubspec.yaml'; final file = File(pubspecPath); if (!await file.exists()) return; - + final content = await file.readAsString(); final lines = content.split('\n'); - + for (var i = 0; i < lines.length; i++) { if (lines[i].startsWith('version:')) { - final match = RegExp(r'version:\s*([\d.]+)(\+[\d]+)?').firstMatch(lines[i]); + final match = RegExp( + r'version:\s*([\d.]+)(\+[\d]+)?', + ).firstMatch(lines[i]); if (match != null) { final versionName = match.group(1)!; lines[i] = 'version: $versionName+$versionCode'; @@ -24,12 +26,12 @@ Future _updatePubspecVersion(int versionCode) async { void main() async { // 手动指定 versionName - const versionName = '2.0.1-ohos-3'; + const versionName = '2.1.0-ohos'; // 通过 git 命令获取 hash 和 code final versionCode = await _getGitCommitCount(); final commitHash = await _getGitCommitHash(); - + await _updatePubspecVersion(versionCode); final env = { @@ -39,7 +41,7 @@ void main() async { 'pili.time': DateTime.now().millisecondsSinceEpoch ~/ 1000, 'pili.hash': commitHash, 'pili.code': versionCode, - 'ENABLE_FLEX_OVERFLOW': false + 'ENABLE_FLEX_OVERFLOW': false, }; File('./.vscode/env.json') ..createSync(recursive: true) diff --git a/analysis_options.yaml b/analysis_options.yaml index 76b0839624..143015c4c5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -12,7 +12,6 @@ include: package:flutter_lints/flutter.yaml analyzer: exclude: - lib/grpc/bilibili/** - # - lib/grpc/google/** # - lib/common/widgets/flutter/** formatter: diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 8128334d62..a101e55c93 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -3,11 +3,20 @@ import org.jetbrains.kotlin.konan.properties.Properties plugins { id("com.android.application") - id("kotlin-android") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } +val agpMajorVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION + .substringBefore('.') + .toInt() +val builtInKotlinProperty = providers.gradleProperty("android.builtInKotlin").orNull +val isBuiltInKotlinEnabled = agpMajorVersion >= 9 && + (builtInKotlinProperty == null || builtInKotlinProperty.toBoolean()) +if (!isBuiltInKotlinEnabled) { + apply(plugin = "org.jetbrains.kotlin.android") +} + android { namespace = "com.example.piliplus" compileSdk = flutter.compileSdkVersion @@ -18,12 +27,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlin { - compilerOptions { - jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) - } - } - defaultConfig { applicationId = "com.example.piliplus" minSdk = flutter.minSdkVersion @@ -51,6 +54,12 @@ android { } } + buildFeatures { + if (project.hasProperty("dev")) { + resValues = true + } + } + buildTypes { all { signingConfig = config ?: signingConfigs["debug"] @@ -82,6 +91,12 @@ android { } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + flutter { source = "../.." } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index d67a772ca4..a74f0afb22 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + @@ -16,8 +17,7 @@ - + @@ -35,56 +35,62 @@ - + android:supportsPictureInPicture="true" + android:theme="@style/LaunchTheme" + android:windowSoftInputMode="adjustResize"> - + + + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> + - - + + + - - - - - - - - + + + + + + + + + @@ -100,36 +106,56 @@ + + - + + + - - - + - + - - - + - + - - - + @@ -147,28 +173,44 @@ - - - - + + + - - + - + - + - + - @@ -177,32 +219,37 @@ + android:theme="@style/Ucrop.CropTheme" /> - - + + - + - - - - - - + + + + + + - + diff --git a/android/app/src/main/java/com/example/piliplus/AndroidHelper.java b/android/app/src/main/java/com/example/piliplus/AndroidHelper.java new file mode 100644 index 0000000000..ad199bc20a --- /dev/null +++ b/android/app/src/main/java/com/example/piliplus/AndroidHelper.java @@ -0,0 +1,272 @@ +package com.example.piliplus; + +import android.app.Activity; +import android.app.PendingIntent; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.app.SearchManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.media.session.PlaybackState; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.provider.Settings; +import android.util.Rational; +import android.view.WindowManager; + +import androidx.annotation.DrawableRes; +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.annotation.RequiresApi; + +import com.github.dart_lang.jni_flutter.JniFlutterPlugin; + +import java.util.ArrayList; +import java.util.Objects; + +@Keep +public final class AndroidHelper { + public static final boolean isFoldable; + + public static final boolean isPipAvailable; + + public static volatile boolean isPipMode = false; + + static { + PackageManager pm = getContext().getPackageManager(); + isFoldable = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_HINGE_ANGLE); + isPipAvailable = pm.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + + private AndroidHelper() { + } + + private static Context getContext() { + return JniFlutterPlugin.getApplicationContext(); + } + + public static int sdkInt() { + return Build.VERSION.SDK_INT; + } + + public static void back() { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + getContext().startActivity(intent); + } + + public static void biliSendCommAntifraud( + int action, long oid, int type, long rpId, long root, long parent, long ctime, @NonNull String commentText, + String pictures, @NonNull String sourceId, long uid, @NonNull String cookie + ) { + Intent intent = new Intent(); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.setComponent(new ComponentName( + "icu.freedomIntrovert.biliSendCommAntifraud", + "icu.freedomIntrovert.biliSendCommAntifraud.ByXposedLaunchedActivity" + )); + intent.putExtra("action", action); + intent.putExtra("oid", oid); + intent.putExtra("type", type); + intent.putExtra("rpid", rpId); + intent.putExtra("root", root); + intent.putExtra("parent", parent); + intent.putExtra("ctime", ctime); + intent.putExtra("comment_text", commentText); + if (pictures != null) { + intent.putExtra("pictures", pictures); + } + intent.putExtra("source_id", sourceId); + intent.putExtra("uid", uid); + ArrayList cookiesList = new ArrayList<>(1); + cookiesList.add(cookie); + intent.putStringArrayListExtra("cookies", cookiesList); + getContext().startActivity(intent); + } + + public static void openLinkVerifySettings() { + Context context = getContext(); + Uri uri = Uri.parse("package:" + context.getPackageName()); + try { + Intent intent; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intent = new Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri); + } else { + intent = new Intent(Intent.ACTION_MAIN, uri); + intent.setClassName( + "com.android.settings", + "com.android.settings.applications.InstalledAppOpenByDefaultActivity" + ); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } catch (Exception ignored) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + context.startActivity(intent); + } + } + + public static boolean openMusic(@NonNull String title, String artist, String album) { + Intent intent = new Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(SearchManager.QUERY, title); + intent.putExtra(MediaStore.EXTRA_MEDIA_TITLE, title); + if (artist != null) { + intent.putExtra(MediaStore.EXTRA_MEDIA_ARTIST, artist); + } + if (album != null) { + intent.putExtra(MediaStore.EXTRA_MEDIA_ALBUM, album); + } + intent.addCategory(Intent.CATEGORY_DEFAULT); + + Context context = getContext(); + PackageManager pm = context.getPackageManager(); + + try { + if (pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) { + context.startActivity(intent); + return true; + } + } catch (Exception ignored) { + } + + try { + intent.setAction(MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH); + if (pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY) != null) { + context.startActivity(intent); + return true; + } + } catch (Exception ignored) { + } + + return false; + } + + public static void enterPip(long engineId, int width, int height, boolean autoEnter, boolean isLive, boolean isPlaying) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Activity activity = JniFlutterPlugin.getActivity(engineId); + assert activity != null; + PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder() + .setAspectRatio(new Rational(width, height)); + setPipActions(activity, builder, isLive, isPlaying); + if (autoEnter) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + builder.setAutoEnterEnabled(true); + activity.setPictureInPictureParams(builder.build()); + } + } else { + activity.enterPictureInPictureMode(builder.build()); + } + } + } + + @RequiresApi(api = Build.VERSION_CODES.O) + public static void updatePipActions(long engineId, boolean isLive, boolean isPlaying) { + Activity activity = JniFlutterPlugin.getActivity(engineId); + assert activity != null; + PictureInPictureParams.Builder builder = new PictureInPictureParams.Builder(); + setPipActions(activity, builder, isLive, isPlaying); + activity.setPictureInPictureParams(builder.build()); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private static void setPipActions(Activity activity, PictureInPictureParams.Builder builder, boolean isLive, boolean isPlaying) { + ComponentName mbrComponent = MediaHelper.getMediaButtonReceiverComponent(activity); + if (mbrComponent == null) return; + ArrayList actionList = new ArrayList<>(3); + if (!isLive) { + actionList.add(getRemoteAction(mbrComponent, activity, R.drawable.ic_player_rewind_10s, "ACTION_REWIND", (int) PlaybackState.ACTION_REWIND)); + } + if (isPlaying) { + actionList.add(getRemoteAction(mbrComponent, activity, R.drawable.ic_player_pause, "ACTION_PAUSE", (int) PlaybackState.ACTION_PAUSE)); + } else { + actionList.add(getRemoteAction(mbrComponent, activity, R.drawable.ic_player_play, "ACTION_PLAY", (int) PlaybackState.ACTION_PLAY)); + } + if (!isLive) { + actionList.add(getRemoteAction(mbrComponent, activity, R.drawable.ic_player_fast_forward_10s, "ACTION_FAST_FORWARD", (int) PlaybackState.ACTION_FAST_FORWARD)); + } + builder.setActions(actionList); + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private static RemoteAction getRemoteAction(@NonNull ComponentName mbrComponent, Activity activity, @DrawableRes int resId, String title, int action) { + return new RemoteAction( + Icon.createWithResource(activity, resId), + title, + title, + Objects.requireNonNull(MediaHelper.buildMediaButtonPendingIntent(activity, mbrComponent, action)) + ); + } + + public static void disableAutoEnterPip(long engineId) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + Activity activity = JniFlutterPlugin.getActivity(engineId); + if (activity != null) { + activity.setPictureInPictureParams(new PictureInPictureParams.Builder() + .setAutoEnterEnabled(false) + .build() + ); + } + } + } + + public static int[] maxScreenSize() { + Context context = getContext(); + WindowManager wm = context.getSystemService(WindowManager.class); + try { + float density = context.getResources().getDisplayMetrics().density; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + Rect maxBounds = wm.getMaximumWindowMetrics().getBounds(); + return new int[]{Math.round(maxBounds.width() / density), Math.round(maxBounds.height() / density)}; + } else { + Point realSize = new Point(); + wm.getDefaultDisplay().getRealSize(realSize); + return new int[]{Math.round(realSize.x / density), Math.round(realSize.y / density)}; + } + } catch (Exception ignored) { + return null; + } + } + + public static void createShortcut(@NonNull String id, @NonNull String uri, @NonNull String label, @NonNull String icon) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + Context context = getContext(); + ShortcutManager shortcutManager = context.getSystemService(ShortcutManager.class); + if (shortcutManager != null && shortcutManager.isRequestPinShortcutSupported()) { + Bitmap bitmap = BitmapFactory.decodeFile(icon); + ShortcutInfo shortcut = new ShortcutInfo.Builder(context, id) + .setShortLabel(label) + .setIcon(Icon.createWithAdaptiveBitmap(bitmap)) + .setIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(uri))) + .build(); + // TODO: WorkerThread + Intent pinIntent = shortcutManager.createShortcutResultIntent(shortcut); + PendingIntent pendingIntent = PendingIntent.getBroadcast( + context, 0, pinIntent, PendingIntent.FLAG_IMMUTABLE + ); + shortcutManager.requestPinShortcut(shortcut, pendingIntent.getIntentSender()); + } + } + } + + @Keep + public static final class ToDart { + public static volatile Runnable onUserLeaveHint; + public static Runnable onConfigurationChanged; + + private ToDart() { + } + } +} diff --git a/android/app/src/main/java/com/example/piliplus/MediaHelper.java b/android/app/src/main/java/com/example/piliplus/MediaHelper.java new file mode 100644 index 0000000000..a3befb3e99 --- /dev/null +++ b/android/app/src/main/java/com/example/piliplus/MediaHelper.java @@ -0,0 +1,83 @@ +/* + * Copyright 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.piliplus; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.media.session.PlaybackState; +import android.os.Build; +import android.util.Log; +import android.view.KeyEvent; + +import java.util.List; + +class MediaHelper { + private static final String TAG = "MediaButtonReceiver"; + + static PendingIntent buildMediaButtonPendingIntent(Context context, ComponentName mbrComponent, int action) { + if (mbrComponent == null) { + Log.w(TAG, "The component name of media button receiver should be provided."); + return null; + } + int keyCode = PlaybackStateCompat_toKeyCode(action); + if (keyCode == KeyEvent.KEYCODE_UNKNOWN) { + Log.w(TAG, + "Cannot build a media button pending intent with the given action: " + action); + return null; + } + Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); + intent.setComponent(mbrComponent); + intent.putExtra(Intent.EXTRA_KEY_EVENT, new KeyEvent(KeyEvent.ACTION_DOWN, keyCode)); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + return PendingIntent.getBroadcast(context, keyCode, intent, + Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0); + } + + private static int PlaybackStateCompat_toKeyCode(int action) { + return switch (action) { + case (int) PlaybackState.ACTION_STOP -> KeyEvent.KEYCODE_MEDIA_STOP; + case (int) PlaybackState.ACTION_PAUSE -> KeyEvent.KEYCODE_MEDIA_PAUSE; + case (int) PlaybackState.ACTION_PLAY -> KeyEvent.KEYCODE_MEDIA_PLAY; + case (int) PlaybackState.ACTION_REWIND -> KeyEvent.KEYCODE_MEDIA_REWIND; + case (int) PlaybackState.ACTION_SKIP_TO_PREVIOUS -> KeyEvent.KEYCODE_MEDIA_PREVIOUS; + case (int) PlaybackState.ACTION_SKIP_TO_NEXT -> KeyEvent.KEYCODE_MEDIA_NEXT; + case (int) PlaybackState.ACTION_FAST_FORWARD -> KeyEvent.KEYCODE_MEDIA_FAST_FORWARD; + case (int) PlaybackState.ACTION_PLAY_PAUSE -> KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE; + default -> KeyEvent.KEYCODE_UNKNOWN; + }; + } + + static ComponentName getMediaButtonReceiverComponent(Context context) { + Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); + queryIntent.setPackage(context.getPackageName()); + PackageManager pm = context.getPackageManager(); + List resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0); + if (resolveInfos.size() == 1) { + ResolveInfo resolveInfo = resolveInfos.get(0); + return new ComponentName(resolveInfo.activityInfo.packageName, + resolveInfo.activityInfo.name); + } else if (resolveInfos.size() > 1) { + Log.w(TAG, "More than one BroadcastReceiver that handles " + + Intent.ACTION_MEDIA_BUTTON + " was found, returning null."); + } + return null; + } +} diff --git a/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt b/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt index 892f84aa45..d3b27cc9e6 100644 --- a/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt +++ b/android/app/src/main/kotlin/com/example/piliplus/MainActivity.kt @@ -1,149 +1,18 @@ package com.example.piliplus -import android.app.PictureInPictureParams -import android.app.SearchManager -import android.content.ComponentName import android.content.Intent -import android.content.pm.PackageManager import android.content.res.Configuration import android.os.Build import android.os.Bundle -import android.provider.MediaStore -import android.provider.Settings import android.view.WindowManager.LayoutParams -import androidx.core.net.toUri import com.ryanheise.audioservice.AudioServiceActivity -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodChannel -import kotlin.system.exitProcess class MainActivity : AudioServiceActivity() { - private lateinit var methodChannel: MethodChannel - - override fun configureFlutterEngine(flutterEngine: FlutterEngine) { - super.configureFlutterEngine(flutterEngine) - - methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "PiliPlus") - methodChannel.setMethodCallHandler { call, result -> - when (call.method) { - "back" -> back(); - "biliSendCommAntifraud" -> { - try { - val action = call.argument("action") ?: 0 - val oid = call.argument("oid") ?: 0L - val type = call.argument("type") ?: 0 - val rpid = call.argument("rpid") ?: 0L - val root = call.argument("root") ?: 0L - val parent = call.argument("parent") ?: 0L - val ctime = call.argument("ctime") ?: 0L - val commentText = call.argument("comment_text") ?: "" - val pictures = call.argument("pictures") - val sourceId = call.argument("source_id") ?: "" - val uid = call.argument("uid") ?: 0L - val cookies = call.argument>("cookies") ?: emptyList() - - val intent = Intent().apply { - component = ComponentName( - "icu.freedomIntrovert.biliSendCommAntifraud", - "icu.freedomIntrovert.biliSendCommAntifraud.ByXposedLaunchedActivity" - ) - putExtra("action", action) - putExtra("oid", oid.toLong()) - putExtra("type", type) - putExtra("rpid", rpid.toLong()) - putExtra("root", root.toLong()) - putExtra("parent", parent.toLong()) - putExtra("ctime", ctime.toLong()) - putExtra("comment_text", commentText) - if (pictures != null) - putExtra("pictures", pictures) - putExtra("source_id", sourceId) - putExtra("uid", uid.toLong()) - putStringArrayListExtra("cookies", ArrayList(cookies)) - } - startActivity(intent) - } catch (_: Exception) { - } - } - - "linkVerifySettings" -> { - val uri = ("package:" + context.packageName).toUri() - try { - val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - Intent(Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri) - } else { - Intent("android.intent.action.MAIN", uri).setClassName( - "com.android.settings", - "com.android.settings.applications.InstalledAppOpenByDefaultActivity" - ) - } - context.startActivity(intent) - } catch (_: Throwable) { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri) - context.startActivity(intent) - } - } - - "music" -> { - val title = call.argument("title") - val intent = Intent(MediaStore.INTENT_ACTION_MEDIA_SEARCH).apply { - putExtra(SearchManager.QUERY, title) - putExtra(MediaStore.EXTRA_MEDIA_TITLE, title) - call.argument("artist") - ?.let { putExtra(MediaStore.EXTRA_MEDIA_ARTIST, it) } - call.argument("album") - ?.let { putExtra(MediaStore.EXTRA_MEDIA_ALBUM, it) } - - addCategory(Intent.CATEGORY_DEFAULT) - } - try { - if (packageManager.resolveActivity( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ) != null - ) { - startActivity(intent) - result.success(true) - return@setMethodCallHandler - } - } catch (_: Throwable) { - } - try { - intent.action = MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH - if (packageManager.resolveActivity( - intent, - PackageManager.MATCH_DEFAULT_ONLY - ) != null - ) { - startActivity(intent) - result.success(true) - return@setMethodCallHandler - } - } catch (_: Throwable) { - } - result.success(false) - } - - "setPipAutoEnterEnabled" -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val params = PictureInPictureParams.Builder() - .setAutoEnterEnabled(call.argument("autoEnable") ?: false) - .build() - setPictureInPictureParams(params) - } - } - - else -> result.notImplemented() - } - } - } - - private fun back() { - val intent = Intent(Intent.ACTION_MAIN).apply { - addCategory(Intent.CATEGORY_HOME) - flags = Intent.FLAG_ACTIVITY_NEW_TASK + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + if (AndroidHelper.isFoldable) { + AndroidHelper.ToDart.onConfigurationChanged?.run() } - startActivity(intent) } override fun onCreate(savedInstanceState: Bundle?) { @@ -157,23 +26,15 @@ class MainActivity : AudioServiceActivity() { override fun onDestroy() { stopService(Intent(this, com.ryanheise.audioservice.AudioService::class.java)) super.onDestroy() - android.os.Process.killProcess(android.os.Process.myPid()) - exitProcess(0) } override fun onUserLeaveHint() { super.onUserLeaveHint() - methodChannel.invokeMethod("onUserLeaveHint", null) + AndroidHelper.ToDart.onUserLeaveHint?.run() } - override fun onPictureInPictureModeChanged( - isInPictureInPictureMode: Boolean, - newConfig: Configuration? - ) { + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?) { super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) - MethodChannel( - flutterEngine!!.dartExecutor.binaryMessenger, - "floating" - ).invokeMethod("onPipChanged", isInPictureInPictureMode) + AndroidHelper.isPipMode = isInPictureInPictureMode } } diff --git a/android/app/src/main/res/drawable/ic_baseline_forward_10_24.xml b/android/app/src/main/res/drawable/ic_baseline_forward_10_24.xml deleted file mode 100644 index f6a6c06497..0000000000 --- a/android/app/src/main/res/drawable/ic_baseline_forward_10_24.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_baseline_replay_10_24.xml b/android/app/src/main/res/drawable/ic_baseline_replay_10_24.xml deleted file mode 100644 index 06db412b17..0000000000 --- a/android/app/src/main/res/drawable/ic_baseline_replay_10_24.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/android/app/src/main/res/drawable/ic_notification_icon.xml b/android/app/src/main/res/drawable/ic_notification_icon.xml index 697e5cbdaa..ed3c75aaed 100644 --- a/android/app/src/main/res/drawable/ic_notification_icon.xml +++ b/android/app/src/main/res/drawable/ic_notification_icon.xml @@ -6,7 +6,6 @@ android:viewportHeight="108.0"> diff --git a/android/app/src/main/res/drawable/ic_player_fast_forward_10s.xml b/android/app/src/main/res/drawable/ic_player_fast_forward_10s.xml new file mode 100644 index 0000000000..7afb5605ee --- /dev/null +++ b/android/app/src/main/res/drawable/ic_player_fast_forward_10s.xml @@ -0,0 +1,11 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_player_pause.xml b/android/app/src/main/res/drawable/ic_player_pause.xml new file mode 100644 index 0000000000..e5a6faaadf --- /dev/null +++ b/android/app/src/main/res/drawable/ic_player_pause.xml @@ -0,0 +1,11 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_player_play.xml b/android/app/src/main/res/drawable/ic_player_play.xml new file mode 100644 index 0000000000..e185c82f6a --- /dev/null +++ b/android/app/src/main/res/drawable/ic_player_play.xml @@ -0,0 +1,10 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_player_rewind_10s.xml b/android/app/src/main/res/drawable/ic_player_rewind_10s.xml new file mode 100644 index 0000000000..55cdc09fff --- /dev/null +++ b/android/app/src/main/res/drawable/ic_player_rewind_10s.xml @@ -0,0 +1,11 @@ + + + + diff --git a/android/app/src/main/res/drawable/ic_shortcut_download.png b/android/app/src/main/res/drawable/ic_shortcut_download.png new file mode 100644 index 0000000000..13827bdebc Binary files /dev/null and b/android/app/src/main/res/drawable/ic_shortcut_download.png differ diff --git a/android/app/src/main/res/drawable/ic_shortcut_search.png b/android/app/src/main/res/drawable/ic_shortcut_search.png new file mode 100644 index 0000000000..9c7fcf5df2 Binary files /dev/null and b/android/app/src/main/res/drawable/ic_shortcut_search.png differ diff --git a/android/app/src/main/res/values/string.xml b/android/app/src/main/res/values/string.xml index 7827e9219b..cf338c2303 100644 --- a/android/app/src/main/res/values/string.xml +++ b/android/app/src/main/res/values/string.xml @@ -1,3 +1,5 @@ PiliPlus + 搜索 + 离线视频 \ No newline at end of file diff --git a/android/app/src/main/res/xml-v25/shortcuts.xml b/android/app/src/main/res/xml-v25/shortcuts.xml new file mode 100644 index 0000000000..8d83c72a85 --- /dev/null +++ b/android/app/src/main/res/xml-v25/shortcuts.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/android/build.gradle.kts b/android/build.gradle.kts index be844ccc8e..59b31be82c 100644 --- a/android/build.gradle.kts +++ b/android/build.gradle.kts @@ -43,14 +43,14 @@ subprojects { val pluginCompileSdk = pluginCompileSdkStr ?.removePrefix("android-") ?.toIntOrNull() - if (pluginCompileSdk != null && pluginCompileSdk < 31) { + if (pluginCompileSdk != null && pluginCompileSdk < 36) { project.logger.error( "Warning: Overriding compileSdk version in Flutter plugin: ${project.name} " + - "from $pluginCompileSdk to 31 (to work around https://issuetracker.google.com/issues/199180389).\n" + + "from $pluginCompileSdk to 36 (to work around https://issuetracker.google.com/issues/199180389).\n" + "If there is not a new version of ${project.name}, consider filing an issue against ${project.name} " + "to increase their compileSdk to the latest (otherwise try updating to the latest version)." ) - androidExtension.setCompileSdkVersion(31) + androidExtension.setCompileSdkVersion(36) } } diff --git a/android/gradle.properties b/android/gradle.properties index f3dfe33489..2f0b4caddd 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,5 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true -android.enableJetifier=true \ No newline at end of file +android.enableJetifier=true +android.builtInKotlin=false +android.newDsl=false diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 82282519db..497e99979a 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index 8de0af82b9..c21f0c5b49 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -19,8 +19,8 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.12.1" apply false - id("org.jetbrains.kotlin.android") version "2.2.20" apply false + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false } include(":app") diff --git a/assets/fonts/custom_icon.ttf b/assets/fonts/custom_icon.ttf index 88e7f64fce..4d41785795 100644 Binary files a/assets/fonts/custom_icon.ttf and b/assets/fonts/custom_icon.ttf differ diff --git a/assets/images/big-vip.png b/assets/images/big-vip.png deleted file mode 100644 index bb00915466..0000000000 Binary files a/assets/images/big-vip.png and /dev/null differ diff --git a/assets/images/big-vip.svg b/assets/images/big-vip.svg new file mode 100644 index 0000000000..089eeeae41 --- /dev/null +++ b/assets/images/big-vip.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/dm.svg b/assets/images/dm.svg deleted file mode 100644 index 2690acd204..0000000000 --- a/assets/images/dm.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/images/dm_gray.png b/assets/images/dm_gray.png deleted file mode 100644 index 438cffc023..0000000000 Binary files a/assets/images/dm_gray.png and /dev/null differ diff --git a/assets/images/dm_white.png b/assets/images/dm_white.png deleted file mode 100644 index 71fd28f94e..0000000000 Binary files a/assets/images/dm_white.png and /dev/null differ diff --git a/assets/images/logo/logo_3.png b/assets/images/logo/logo_3.png deleted file mode 100644 index 6a204d29f4..0000000000 Binary files a/assets/images/logo/logo_3.png and /dev/null differ diff --git a/assets/images/lv/lv0.png b/assets/images/lv/lv0.png deleted file mode 100644 index f9ed49b620..0000000000 Binary files a/assets/images/lv/lv0.png and /dev/null differ diff --git a/assets/images/lv/lv1.png b/assets/images/lv/lv1.png deleted file mode 100644 index ad8b70ce0a..0000000000 Binary files a/assets/images/lv/lv1.png and /dev/null differ diff --git a/assets/images/lv/lv2.png b/assets/images/lv/lv2.png deleted file mode 100644 index c369f5ada8..0000000000 Binary files a/assets/images/lv/lv2.png and /dev/null differ diff --git a/assets/images/lv/lv3.png b/assets/images/lv/lv3.png deleted file mode 100644 index 80a0db3fad..0000000000 Binary files a/assets/images/lv/lv3.png and /dev/null differ diff --git a/assets/images/lv/lv4.png b/assets/images/lv/lv4.png deleted file mode 100644 index 5441967b20..0000000000 Binary files a/assets/images/lv/lv4.png and /dev/null differ diff --git a/assets/images/lv/lv5.png b/assets/images/lv/lv5.png deleted file mode 100644 index dedd1309c0..0000000000 Binary files a/assets/images/lv/lv5.png and /dev/null differ diff --git a/assets/images/lv/lv6.png b/assets/images/lv/lv6.png deleted file mode 100644 index 399e585a70..0000000000 Binary files a/assets/images/lv/lv6.png and /dev/null differ diff --git a/assets/images/lv/lv6_s.png b/assets/images/lv/lv6_s.png deleted file mode 100644 index 4b867500c7..0000000000 Binary files a/assets/images/lv/lv6_s.png and /dev/null differ diff --git a/assets/images/play.png b/assets/images/play.png deleted file mode 100644 index b5d0299c58..0000000000 Binary files a/assets/images/play.png and /dev/null differ diff --git a/assets/images/play.svg b/assets/images/play.svg deleted file mode 100644 index 0032f069f6..0000000000 --- a/assets/images/play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/tv.svg b/assets/images/tv.svg deleted file mode 100644 index fdb077b169..0000000000 --- a/assets/images/tv.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/up.svg b/assets/images/up.svg deleted file mode 100644 index c63989c590..0000000000 --- a/assets/images/up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/up_gray.png b/assets/images/up_gray.png deleted file mode 100644 index c6d7f4ab7e..0000000000 Binary files a/assets/images/up_gray.png and /dev/null differ diff --git a/assets/images/video/danmu_close.svg b/assets/images/video/danmu_close.svg deleted file mode 100644 index 9f48027b05..0000000000 --- a/assets/images/video/danmu_close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/images/video/danmu_open.svg b/assets/images/video/danmu_open.svg deleted file mode 100644 index 24e8d7a99c..0000000000 --- a/assets/images/video/danmu_open.svg +++ /dev/null @@ -1 +0,0 @@ -Layer 1 \ No newline at end of file diff --git a/assets/images/view.svg b/assets/images/view.svg deleted file mode 100644 index 88fe609c7b..0000000000 --- a/assets/images/view.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/assets/images/view_gray.png b/assets/images/view_gray.png deleted file mode 100644 index fe2b34825a..0000000000 Binary files a/assets/images/view_gray.png and /dev/null differ diff --git a/assets/images/view_white.png b/assets/images/view_white.png deleted file mode 100644 index d97b0e937d..0000000000 Binary files a/assets/images/view_white.png and /dev/null differ diff --git a/ios/Podfile b/ios/Podfile index a1de6446bb..29e3d9b8b1 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '13.0' +platform :ios, '14.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fdadabedd7..ebeafc0c84 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,8 +6,6 @@ PODS: - FlutterMacOS - audio_session (0.0.1): - Flutter - - auto_orientation (0.0.1): - - Flutter - battery_plus (1.0.0): - Flutter - chat_bottom_container (0.0.1): @@ -16,40 +14,9 @@ PODS: - Flutter - device_info_plus (0.0.1): - Flutter - - DKImagePickerController/Core (4.3.9): - - DKImagePickerController/ImageDataManager - - DKImagePickerController/Resource - - DKImagePickerController/ImageDataManager (4.3.9) - - DKImagePickerController/PhotoGallery (4.3.9): - - DKImagePickerController/Core - - DKPhotoGallery - - DKImagePickerController/Resource (4.3.9) - - DKPhotoGallery (0.0.19): - - DKPhotoGallery/Core (= 0.0.19) - - DKPhotoGallery/Model (= 0.0.19) - - DKPhotoGallery/Preview (= 0.0.19) - - DKPhotoGallery/Resource (= 0.0.19) - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Core (0.0.19): - - DKPhotoGallery/Model - - DKPhotoGallery/Preview - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Model (0.0.19): - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Preview (0.0.19): - - DKPhotoGallery/Model - - DKPhotoGallery/Resource - - SDWebImage - - SwiftyGif - - DKPhotoGallery/Resource (0.0.19): - - SDWebImage - - SwiftyGif - file_picker (0.0.1): - - DKImagePickerController/PhotoGallery - Flutter + - FlutterMacOS - Flutter (1.0.0) - flutter_inappwebview_ios (0.0.1): - Flutter @@ -69,10 +36,10 @@ PODS: - gt3_flutter_plugin (0.0.9): - Flutter - GT3Captcha-iOS - - GT3Captcha-iOS (0.15.8.3) + - GT3Captcha-iOS (0.15.9) - image_cropper (0.0.5): - Flutter - - TOCropViewController (~> 3.1.1) + - TOCropViewController (~> 3.1.2) - image_picker_ios (0.0.1): - Flutter - live_photo_maker (0.0.3): @@ -83,6 +50,8 @@ PODS: - Flutter - media_kit_video (0.0.1): - Flutter + - native_device_orientation (0.0.1): + - Flutter - OrderedSet (6.0.3) - package_info_plus (0.4.5): - Flutter @@ -92,9 +61,6 @@ PODS: - Flutter - screen_brightness_ios (0.1.0): - Flutter - - SDWebImage (5.21.3): - - SDWebImage/Core (= 5.21.3) - - SDWebImage/Core (5.21.3) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -103,8 +69,7 @@ PODS: - sqflite_darwin (0.0.4): - Flutter - FlutterMacOS - - SwiftyGif (5.4.5) - - TOCropViewController (3.1.1) + - TOCropViewController (3.1.2) - url_launcher_ios (0.0.1): - Flutter - wakelock_plus (0.0.1): @@ -114,12 +79,11 @@ DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - audio_service (from `.symlinks/plugins/audio_service/darwin`) - audio_session (from `.symlinks/plugins/audio_session/ios`) - - auto_orientation (from `.symlinks/plugins/auto_orientation/ios`) - battery_plus (from `.symlinks/plugins/battery_plus/ios`) - chat_bottom_container (from `.symlinks/plugins/chat_bottom_container/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - - file_picker (from `.symlinks/plugins/file_picker/ios`) + - file_picker (from `.symlinks/plugins/file_picker/darwin`) - Flutter (from `Flutter`) - flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`) - flutter_mailer (from `.symlinks/plugins/flutter_mailer/ios`) @@ -133,6 +97,7 @@ DEPENDENCIES: - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) + - native_device_orientation (from `.symlinks/plugins/native_device_orientation/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - saver_gallery (from `.symlinks/plugins/saver_gallery/ios`) @@ -145,12 +110,8 @@ DEPENDENCIES: SPEC REPOS: trunk: - - DKImagePickerController - - DKPhotoGallery - GT3Captcha-iOS - OrderedSet - - SDWebImage - - SwiftyGif - TOCropViewController EXTERNAL SOURCES: @@ -160,8 +121,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/audio_service/darwin" audio_session: :path: ".symlinks/plugins/audio_session/ios" - auto_orientation: - :path: ".symlinks/plugins/auto_orientation/ios" battery_plus: :path: ".symlinks/plugins/battery_plus/ios" chat_bottom_container: @@ -171,7 +130,7 @@ EXTERNAL SOURCES: device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" file_picker: - :path: ".symlinks/plugins/file_picker/ios" + :path: ".symlinks/plugins/file_picker/darwin" Flutter: :path: Flutter flutter_inappwebview_ios: @@ -198,6 +157,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/media_kit_native_event_loop/ios" media_kit_video: :path: ".symlinks/plugins/media_kit_video/ios" + native_device_orientation: + :path: ".symlinks/plugins/native_device_orientation/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" permission_handler_apple: @@ -221,14 +182,11 @@ SPEC CHECKSUMS: app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8 audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0 - auto_orientation: a1600c9ed72e6e96982fb4e1214463343342432a battery_plus: b42253f6d2dde71712f8c36fef456d99121c5977 chat_bottom_container: f1eb8323db77a87db50f361142c679f11e892d1b connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe - DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c - DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60 - file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be + file_picker: 70164d9778c42c47218d6cd79ce435de0856b11a Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99 flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4 @@ -236,27 +194,26 @@ SPEC CHECKSUMS: flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1 gt3_flutter_plugin: 37090e5fa66ff2a52939eb9d208fc36fa49d36e5 - GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6 - image_cropper: e405d3e44183f8e8edbec2e49b01ff9c819c7ac8 + GT3Captcha-iOS: aeb6fed2e8594099821430a89208679e5a55b740 + image_cropper: fca51f94982730acae168c4b5d691e0f11aeb259 image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326 live_photo_maker: 29280ca88323bd5a33aafd00d98624d5cf522176 media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854 media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474 + native_device_orientation: e3580675687d5034770da198f6839ebf2122ef94 OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94 package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490 screen_brightness_ios: 9953fd7da5bd480f1a93990daeec2eb42d4f3b52 - SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0 - SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 - TOCropViewController: 9002a9b12d8104d7478cdc306d80f0efea7fe2c5 + TOCropViewController: a916930c465b5d9445a74d95e0c0da931771b4df url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556 -PODFILE CHECKSUM: f62db4fb414ebdecb264109948f76dfef35fdc3d +PODFILE CHECKSUM: 5e755568c318fde60f7b59d132a4ba634d53bf27 COCOAPODS: 1.16.2 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index e316c85e16..eba4fe1dbe 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -360,7 +360,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -438,7 +438,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -487,7 +487,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/lib/common/assets.dart b/lib/common/assets.dart index 97996d3640..ed429dc808 100644 --- a/lib/common/assets.dart +++ b/lib/common/assets.dart @@ -6,14 +6,14 @@ abstract final class Assets { static const logoIco = 'assets/images/logo/ico/app_icon.ico'; static const logoLarge = 'assets/images/logo/desktop/logo_large.png'; - static const vipIcon = 'assets/images/big-vip.png'; + static const vipIcon = 'assets/images/big-vip.svg'; static const avatarPlaceHolder = 'assets/images/noface.jpeg'; static const loading = 'assets/images/loading.png'; static const buffering = 'assets/images/loading.webp'; - static const play = 'assets/images/play.png'; static const topicHeader = 'assets/images/topic-header-bg.png'; static const trendingBanner = 'assets/images/trending_banner.png'; static const ai = 'assets/images/ai.png'; + static const error = 'assets/images/error.svg'; static const livingChart = 'assets/images/live.gif'; static const livingStatic = 'assets/images/live.png'; diff --git a/lib/common/skeleton/fav_pgc_item.dart b/lib/common/skeleton/fav_pgc_item.dart index 2704b3ee00..e139d70c48 100644 --- a/lib/common/skeleton/fav_pgc_item.dart +++ b/lib/common/skeleton/fav_pgc_item.dart @@ -1,7 +1,6 @@ import 'package:PiliPlus/common/skeleton/skeleton.dart'; import 'package:PiliPlus/common/style.dart'; -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; -import 'package:flutter/material.dart' hide LayoutBuilder; +import 'package:flutter/material.dart'; class FavPgcItemSkeleton extends StatelessWidget { const FavPgcItemSkeleton({super.key}); diff --git a/lib/common/skeleton/skeleton.dart b/lib/common/skeleton/skeleton.dart index a2bcf8c75d..d8b75fd810 100644 --- a/lib/common/skeleton/skeleton.dart +++ b/lib/common/skeleton/skeleton.dart @@ -1,188 +1,62 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; -class Skeleton extends StatelessWidget { +class Skeleton extends StatefulWidget { final Widget child; - const Skeleton({ - required this.child, - super.key, - }); - - @override - Widget build(BuildContext context) { - final color = Theme.of(context).colorScheme.surface.withAlpha(10); - final shimmerGradient = LinearGradient( - colors: [ - Colors.transparent, - color, - color, - Colors.transparent, - ], - stops: const [ - 0.1, - 0.3, - 0.5, - 0.7, - ], - begin: const Alignment(-1.0, -0.3), - end: const Alignment(1.0, 0.9), - tileMode: TileMode.clamp, - ); - return Shimmer( - linearGradient: shimmerGradient, - child: ShimmerLoading( - isLoading: true, - child: child, - ), - ); - } -} - -class Shimmer extends StatefulWidget { - static ShimmerState? of(BuildContext context) { - return context.findAncestorStateOfType(); - } - - const Shimmer({ - super.key, - required this.linearGradient, - this.child, - }); - - final LinearGradient linearGradient; - final Widget? child; + const Skeleton({super.key, required this.child}); @override - ShimmerState createState() => ShimmerState(); + State createState() => _SkeletonState(); } -class ShimmerState extends State with SingleTickerProviderStateMixin { - late AnimationController _shimmerController; +class _SkeletonState extends State + with SingleTickerProviderStateMixin { + late final AnimationController _controller; + late Color color; + final matrix = Matrix4.identity(); @override void initState() { super.initState(); - _shimmerController = AnimationController.unbounded(vsync: this) - ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)); + _controller = AnimationController.unbounded(vsync: this) + ..repeat(min: -0.5, max: 1.5, period: const Duration(milliseconds: 1000)) + ..addListener(_setState); } @override void dispose() { - _shimmerController.dispose(); + _controller.dispose(); super.dispose(); } - LinearGradient get gradient => LinearGradient( - colors: widget.linearGradient.colors, - stops: widget.linearGradient.stops, - begin: widget.linearGradient.begin, - end: widget.linearGradient.end, - transform: _SlidingGradientTransform( - slidePercent: _shimmerController.value, - ), - ); - - bool get isSized => - (context.findRenderObject() as RenderBox?)?.hasSize ?? false; - - Size get size => (context.findRenderObject() as RenderBox).size; - - Offset getDescendantOffset({ - required RenderBox descendant, - Offset offset = Offset.zero, - }) { - final shimmerBox = context.findRenderObject() as RenderBox; - return descendant.localToGlobal(offset, ancestor: shimmerBox); - } - - Listenable get shimmerChanges => _shimmerController; - - @override - Widget build(BuildContext context) { - return widget.child ?? const SizedBox.shrink(); - } -} - -class _SlidingGradientTransform extends GradientTransform { - const _SlidingGradientTransform({ - required this.slidePercent, - }); - - final double slidePercent; - - @override - Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { - return Matrix4.translationValues(bounds.width * slidePercent, 0.0, 0.0); + void _setState() { + setState(() {}); } -} - -class ShimmerLoading extends StatefulWidget { - const ShimmerLoading({ - super.key, - required this.isLoading, - required this.child, - }); - - final bool isLoading; - final Widget child; - - @override - State createState() => _ShimmerLoadingState(); -} - -class _ShimmerLoadingState extends State { - Listenable? _shimmerChanges; @override void didChangeDependencies() { super.didChangeDependencies(); - if (_shimmerChanges != null) { - _shimmerChanges!.removeListener(_onShimmerChange); - } - _shimmerChanges = Shimmer.of(context)?.shimmerChanges; - if (_shimmerChanges != null) { - _shimmerChanges!.addListener(_onShimmerChange); - } - } - - @override - void dispose() { - _shimmerChanges?.removeListener(_onShimmerChange); - super.dispose(); - } - - void _onShimmerChange() { - if (widget.isLoading) { - setState(() {}); - } + color = ColorScheme.of(context).surface.withAlpha(10); } @override Widget build(BuildContext context) { - if (!widget.isLoading) { - return widget.child; - } - - final shimmer = Shimmer.of(context)!; - if (!shimmer.isSized) { - return const SizedBox.shrink(); - } - final shimmerSize = shimmer.size; - final gradient = shimmer.gradient; - final offsetWithinShimmer = shimmer.getDescendantOffset( - descendant: context.findRenderObject() as RenderBox, - ); - + final colors = [Colors.transparent, color, color, Colors.transparent]; return ShaderMask( blendMode: BlendMode.srcATop, - shaderCallback: (bounds) { - return gradient.createShader( - Rect.fromLTWH( - -offsetWithinShimmer.dx, - -offsetWithinShimmer.dy, - shimmerSize.width, - shimmerSize.height, - ), + shaderCallback: (Rect bounds) { + final width = bounds.width; + final height = bounds.height; + matrix[12] = width * _controller.value; + return ui.Gradient.linear( + Offset(0, 0.35 * height), + Offset(width, 0.95 * height), + colors, + const [0.1, 0.3, 0.5, 0.7], + TileMode.clamp, + matrix.storage, ); }, child: widget.child, diff --git a/lib/common/skeleton/space_opus.dart b/lib/common/skeleton/space_opus.dart index 2d34fea8e1..e62e15c423 100644 --- a/lib/common/skeleton/space_opus.dart +++ b/lib/common/skeleton/space_opus.dart @@ -1,7 +1,6 @@ import 'package:PiliPlus/common/skeleton/skeleton.dart'; -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart' hide LayoutBuilder; +import 'package:flutter/material.dart'; class SpaceOpusSkeleton extends StatelessWidget { const SpaceOpusSkeleton({super.key}); diff --git a/lib/common/style.dart b/lib/common/style.dart index a50f36f798..09b83ffa51 100644 --- a/lib/common/style.dart +++ b/lib/common/style.dart @@ -9,13 +9,8 @@ abstract final class Style { static const aspectRatio = 16 / 10; static const aspectRatio16x9 = 16 / 9; static const imgMaxRatio = 2.6; - static const bottomSheetRadius = BorderRadius.vertical( - top: Radius.circular(18), - ); - static const dialogFixedConstraints = BoxConstraints( - minWidth: 420, - maxWidth: 420, - ); + static const bottomSheetRadius = BorderRadius.vertical(top: .circular(18)); + static const dialogFixedConstraints = BoxConstraints.tightFor(width: 420); static const topBarHeight = 52.0; static const buttonStyle = ButtonStyle( visualDensity: VisualDensity(horizontal: -2, vertical: -1.25), diff --git a/lib/common/widgets/animated_height.dart b/lib/common/widgets/animated_height.dart new file mode 100644 index 0000000000..ab653e9f48 --- /dev/null +++ b/lib/common/widgets/animated_height.dart @@ -0,0 +1,250 @@ +import 'package:PiliPlus/utils/extension/num_ext.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' + show ClipRectLayer, LayerHandle, RenderAnimatedSize, RenderProxyBox; + +typedef Heights = ({double from, double to}); + +/// ref [AnimatedSize] +class AnimatedHeight extends StatefulWidget { + const AnimatedHeight({ + super.key, + required this.child, + this.curve = Curves.linear, + required this.duration, + this.reverseDuration, + this.clipBehavior = .hardEdge, + required this.expand, + }); + + final Widget child; + final Curve curve; + final Duration duration; + final Duration? reverseDuration; + final Clip clipBehavior; + final bool expand; + + @override + State createState() => _AnimatedHeightState(); +} + +class _AnimatedHeightState extends State + with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return _AnimatedHeight( + curve: widget.curve, + duration: widget.duration, + reverseDuration: widget.reverseDuration, + vsync: this, + clipBehavior: widget.clipBehavior, + expand: widget.expand, + child: widget.child, + ); + } +} + +class _AnimatedHeight extends SingleChildRenderObjectWidget { + const _AnimatedHeight({ + required Widget super.child, + this.curve = Curves.linear, + required this.duration, + this.reverseDuration, + required this.vsync, + this.clipBehavior = .hardEdge, + required this.expand, + }); + + final Curve curve; + final Duration duration; + final Duration? reverseDuration; + final TickerProvider vsync; + final Clip clipBehavior; + final bool expand; + + @override + RenderAnimatedHeight createRenderObject(BuildContext context) { + return RenderAnimatedHeight( + duration: duration, + reverseDuration: reverseDuration, + curve: curve, + vsync: vsync, + clipBehavior: clipBehavior, + expand: expand, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderAnimatedHeight renderObject, + ) { + renderObject + ..duration = duration + ..reverseDuration = reverseDuration + ..curve = curve + ..vsync = vsync + ..clipBehavior = clipBehavior + ..expand = expand; + } +} + +/// ref [RenderAnimatedSize] +class RenderAnimatedHeight extends RenderProxyBox { + RenderAnimatedHeight({ + required TickerProvider vsync, + required Duration duration, + Duration? reverseDuration, + Curve curve = Curves.linear, + Clip clipBehavior = .hardEdge, + required bool expand, + }) : _vsync = vsync, + _curve = curve, + _clipBehavior = clipBehavior, + _expand = expand { + _controller = + AnimationController( + vsync: vsync, + value: expand ? 1.0 : 0.0, + duration: duration, + reverseDuration: reverseDuration, + )..addListener(() { + if (_controller.value != _lastValue) { + markNeedsLayout(); + } + }); + } + + bool _expand; + bool get expand => _expand; + set expand(bool value) { + if (_expand == value) return; + _expand = value; + _lastValue = 0.0; + _controller.forward(from: 0); + } + + late final AnimationController _controller; + bool get isAnimating => _controller.isAnimating; + bool get _isInvisible => !isAnimating && !expand; + + double? _lastValue; + Heights? _heights; + + Duration get duration => _controller.duration!; + set duration(Duration value) { + if (value == _controller.duration) { + return; + } + _controller.duration = value; + } + + Duration? get reverseDuration => _controller.reverseDuration; + set reverseDuration(Duration? value) { + if (value == _controller.reverseDuration) { + return; + } + _controller.reverseDuration = value; + } + + Curve _curve; + Curve get curve => _curve; + set curve(Curve value) { + if (value == _curve) { + return; + } + _curve = value; + } + + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = .hardEdge; + set clipBehavior(Clip value) { + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + } + } + + TickerProvider get vsync => _vsync; + TickerProvider _vsync; + set vsync(TickerProvider value) { + if (value == _vsync) { + return; + } + _vsync = value; + _controller.resync(vsync); + } + + @override + void detach() { + _controller.stop(); + super.detach(); + } + + @override + void performLayout() { + final BoxConstraints constraints = this.constraints; + + if (_isInvisible) { + _heights = const (from: 0, to: 0); + child!.layout(constraints); + size = constraints.constrain(.zero); + return; + } + + _lastValue = _controller.value; + + final childSize = (child!..layout(constraints, parentUsesSize: true)).size; + + final Size animatedSize; + + if (isAnimating && _heights != null) { + final to = expand ? childSize.height : 0.0; + if (_heights!.to != to) { + _heights = (from: size.height, to: to); + } + animatedSize = Size( + childSize.width, + curve.transform(_controller.value).lerp(_heights!.from, _heights!.to), + ); + } else { + animatedSize = childSize; + _heights = (from: childSize.height, to: childSize.height); + } + + size = constraints.constrain(animatedSize); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_isInvisible) { + _clipRectLayer.layer = null; + return; + } + + if (isAnimating && clipBehavior != .none) { + final Rect rect = Offset.zero & size; + _clipRectLayer.layer = context.pushClipRect( + needsCompositing, + offset, + rect, + super.paint, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + } else { + _clipRectLayer.layer = null; + super.paint(context, offset); + } + } + + final LayerHandle _clipRectLayer = + LayerHandle(); + + @override + void dispose() { + _clipRectLayer.layer = null; + _controller.dispose(); + super.dispose(); + } +} diff --git a/lib/common/widgets/animated_multi_height.dart b/lib/common/widgets/animated_multi_height.dart new file mode 100644 index 0000000000..57e57d5e7a --- /dev/null +++ b/lib/common/widgets/animated_multi_height.dart @@ -0,0 +1,264 @@ +import 'package:PiliPlus/common/widgets/animated_height.dart' show Heights; +import 'package:PiliPlus/utils/extension/num_ext.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' + show ClipRectLayer, LayerHandle, PipelineOwner, RenderProxyBox; + +class AnimatedMultiHeight extends StatefulWidget { + const AnimatedMultiHeight({ + super.key, + required Widget this.child, + this.curve = Curves.linear, + required this.duration, + this.reverseDuration, + this.clipBehavior = .hardEdge, + this.onEnd, + required this.expand, + }); + + final Widget? child; + final Curve curve; + final Duration duration; + final Duration? reverseDuration; + final Clip clipBehavior; + final VoidCallback? onEnd; + final bool expand; + + @override + State createState() => _AnimatedMultiHeightState(); +} + +class _AnimatedMultiHeightState extends State + with SingleTickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return _AnimatedMultiHeight( + curve: widget.curve, + duration: widget.duration, + reverseDuration: widget.reverseDuration, + vsync: this, + clipBehavior: widget.clipBehavior, + onEnd: widget.onEnd, + expand: widget.expand, + child: widget.child, + ); + } +} + +class _AnimatedMultiHeight extends SingleChildRenderObjectWidget { + const _AnimatedMultiHeight({ + super.child, + this.curve = Curves.linear, + required this.duration, + this.reverseDuration, + required this.vsync, + this.clipBehavior = .hardEdge, + this.onEnd, + required this.expand, + }); + + final Curve curve; + final Duration duration; + final Duration? reverseDuration; + final TickerProvider vsync; + final Clip clipBehavior; + final VoidCallback? onEnd; + final bool expand; + + @override + RenderAnimatedMultiHeight createRenderObject(BuildContext context) { + return RenderAnimatedMultiHeight( + duration: duration, + reverseDuration: reverseDuration, + curve: curve, + vsync: vsync, + clipBehavior: clipBehavior, + onEnd: onEnd, + expand: expand, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderAnimatedMultiHeight renderObject, + ) { + renderObject + ..duration = duration + ..reverseDuration = reverseDuration + ..curve = curve + ..vsync = vsync + ..clipBehavior = clipBehavior + ..onEnd = onEnd + ..expand = expand; + } +} + +class RenderAnimatedMultiHeight extends RenderProxyBox { + RenderAnimatedMultiHeight({ + required TickerProvider vsync, + required Duration duration, + Duration? reverseDuration, + Curve curve = Curves.linear, + Clip clipBehavior = .hardEdge, + VoidCallback? onEnd, + required bool expand, + }) : _curve = curve, + _clipBehavior = clipBehavior, + _onEnd = onEnd, + _expand = expand, + _vsync = vsync { + _controller = + AnimationController( + vsync: vsync, + value: _expand ? 1.0 : 0.0, + duration: duration, + reverseDuration: reverseDuration, + )..addListener(() { + if (_controller.value != _lastValue) { + markNeedsLayout(); + } + }); + } + + bool _expand; + bool get expand => _expand; + set expand(bool value) { + if (_expand == value) return; + _expand = value; + _lastValue = 0.0; + _controller.forward(from: 0.0); + } + + late final AnimationController _controller; + bool get isAnimating => _controller.isAnimating; + + double? _lastValue; + Heights? _heights; + + Duration get duration => _controller.duration!; + set duration(Duration value) { + if (value == _controller.duration) { + return; + } + _controller.duration = value; + } + + Duration? get reverseDuration => _controller.reverseDuration; + set reverseDuration(Duration? value) { + if (value == _controller.reverseDuration) { + return; + } + _controller.reverseDuration = value; + } + + Curve _curve; + Curve get curve => _curve; + set curve(Curve value) { + if (value == _curve) { + return; + } + _curve = value; + } + + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = .hardEdge; + set clipBehavior(Clip value) { + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + } + } + + TickerProvider get vsync => _vsync; + TickerProvider _vsync; + set vsync(TickerProvider value) { + if (value == _vsync) { + return; + } + _vsync = value; + _controller.resync(vsync); + } + + VoidCallback? get onEnd => _onEnd; + VoidCallback? _onEnd; + set onEnd(VoidCallback? value) { + if (value == _onEnd) { + return; + } + _onEnd = value; + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _controller.addStatusListener(_animationStatusListener); + } + + @override + void detach() { + _controller + ..stop() + ..removeStatusListener(_animationStatusListener); + super.detach(); + } + + @override + void performLayout() { + _lastValue = _controller.value; + + final BoxConstraints constraints = this.constraints; + final childSize = (child!..layout(constraints, parentUsesSize: true)).size; + + final Size animatedSize; + if (isAnimating && _heights != null) { + final to = childSize.height; + if (_heights!.to != to) { + _heights = (from: size.height, to: to); + } + animatedSize = Size( + childSize.width, + curve.transform(_controller.value).lerp(_heights!.from, _heights!.to), + ); + } else { + animatedSize = childSize; + _heights = (from: childSize.height, to: childSize.height); + } + + size = constraints.constrain(animatedSize); + } + + void _animationStatusListener(AnimationStatus status) { + if (status.isCompleted) { + _onEnd?.call(); + } + } + + @override + void paint(PaintingContext context, Offset offset) { + if (isAnimating && clipBehavior != .none) { + final Rect rect = Offset.zero & size; + _clipRectLayer.layer = context.pushClipRect( + needsCompositing, + offset, + rect, + super.paint, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + } else { + _clipRectLayer.layer = null; + super.paint(context, offset); + } + } + + final LayerHandle _clipRectLayer = + LayerHandle(); + + @override + void dispose() { + _clipRectLayer.layer = null; + _controller.dispose(); + super.dispose(); + } +} diff --git a/lib/common/widgets/avatars.dart b/lib/common/widgets/avatars.dart index bad97921f9..e94e7186c4 100644 --- a/lib/common/widgets/avatars.dart +++ b/lib/common/widgets/avatars.dart @@ -1,15 +1,16 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/model_owner.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; Widget avatars({ required ColorScheme colorScheme, required Iterable users, + double gap = 6.0, }) { - const gap = 6.0; const size = 22.0; const padding = 0.8; - const offset = size - gap; + final offset = size - gap; const imgSize = size - 2 * padding; if (users.length == 1) { return NetworkImgLayer( @@ -27,20 +28,20 @@ Widget avatars({ height: size, width: offset * users.length + gap, child: Stack( - clipBehavior: Clip.none, - children: users.indexed - .map( - (e) => Positioned( + clipBehavior: .none, + children: users + .mapIndexed( + (i, e) => Positioned( top: 0, bottom: 0, width: size, - left: e.$1 * offset, + left: i * offset, child: DecoratedBox( decoration: decoration, child: Padding( padding: const .all(padding), child: NetworkImgLayer( - src: e.$2.face, + src: e.face, width: imgSize, height: imgSize, type: .avatar, diff --git a/lib/common/widgets/badge.dart b/lib/common/widgets/badge.dart index 63f3a3027c..826ee0fcd2 100644 --- a/lib/common/widgets/badge.dart +++ b/lib/common/widgets/badge.dart @@ -82,26 +82,21 @@ class PBadge extends StatelessWidget { color = theme.onSurfaceVariant; } - late EdgeInsets paddingStyle = const EdgeInsets.symmetric( - vertical: 2, - horizontal: 3, - ); - BorderRadius br = size == PBadgeSize.small - ? const BorderRadius.all(Radius.circular(3)) - : const BorderRadius.all(Radius.circular(4)); + late EdgeInsets paddingStyle = const .symmetric(vertical: 2, horizontal: 3); + final BorderRadius br = size == .small + ? const .all(.circular(3)) + : const .all(.circular(4)); Widget content = Container( padding: padding ?? paddingStyle, decoration: BoxDecoration( borderRadius: br, color: bgColor, - border: Border.all(color: borderColor), + border: .all(color: borderColor), ), child: Text( text!, - textScaler: textScaleFactor != null - ? TextScaler.linear(textScaleFactor!) - : null, + textScaler: textScaleFactor != null ? .linear(textScaleFactor!) : null, style: TextStyle( height: 1, fontSize: fontSize, diff --git a/lib/common/widgets/button/toolbar_icon_button.dart b/lib/common/widgets/button/toolbar_icon_button.dart index e9c78cc82b..ae033e8076 100644 --- a/lib/common/widgets/button/toolbar_icon_button.dart +++ b/lib/common/widgets/button/toolbar_icon_button.dart @@ -16,7 +16,7 @@ class ToolbarIconButton extends StatelessWidget { @override Widget build(BuildContext context) { - final ThemeData theme = Theme.of(context); + final colorScheme = ColorScheme.of(context); return SizedBox( width: 36, height: 36, @@ -24,14 +24,14 @@ class ToolbarIconButton extends StatelessWidget { tooltip: tooltip, onPressed: onPressed, icon: icon, - highlightColor: theme.colorScheme.secondaryContainer, + highlightColor: colorScheme.secondaryContainer, color: selected - ? theme.colorScheme.onSecondaryContainer - : theme.colorScheme.outline, + ? colorScheme.onSecondaryContainer + : colorScheme.outline, style: ButtonStyle( padding: const WidgetStatePropertyAll(EdgeInsets.zero), backgroundColor: WidgetStatePropertyAll( - selected ? theme.colorScheme.secondaryContainer : null, + selected ? colorScheme.secondaryContainer : null, ), ), ), diff --git a/lib/common/widgets/cached_layout_builder.dart b/lib/common/widgets/cached_layout_builder.dart index 9e2a410448..0883d4e34a 100644 --- a/lib/common/widgets/cached_layout_builder.dart +++ b/lib/common/widgets/cached_layout_builder.dart @@ -1,6 +1,4 @@ -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; -import 'package:flutter/material.dart' hide LayoutBuilder; -import 'package:flutter/widgets.dart' hide LayoutBuilder; +import 'package:flutter/widgets.dart'; /// 带缓存的 LayoutBuilder,避免父组件 rebuild 时产生不必要的子树重建。 /// @@ -14,7 +12,7 @@ class CachedLayoutBuilder extends StatefulWidget { }); final Widget Function(BuildContext context, BoxConstraints constraints) - builder; + builder; @override State createState() => _CachedLayoutBuilderState(); diff --git a/lib/common/widgets/custom_icon.dart b/lib/common/widgets/custom_icon.dart index 69d1c8931f..29c9072bfa 100644 --- a/lib/common/widgets/custom_icon.dart +++ b/lib/common/widgets/custom_icon.dart @@ -1,35 +1,42 @@ // ignore_for_file: constant_identifier_names -import 'package:flutter/widgets.dart'; +import 'package:flutter/widgets.dart' show IconData; -class CustomIcons { - static const IconData coin = _CustomIconData(0xe800); - static const IconData dm_off = _CustomIconData(0xe801); - static const IconData dm_on = _CustomIconData(0xe802); - static const IconData dm_settings = _CustomIconData(0xe803); - static const IconData dyn = _CustomIconData(0xe804); - static const IconData fav = _CustomIconData(0xe805); - static const IconData live_reserve = _CustomIconData(0xe806); - static const IconData player_dm_tip_back = _CustomIconData(0xe807); - static const IconData player_dm_tip_copy = _CustomIconData(0xe808); - static const IconData player_dm_tip_like = _CustomIconData(0xe809); - static const IconData player_dm_tip_like_solid = _CustomIconData(0xe80a); - static const IconData player_dm_tip_recall = _CustomIconData(0xe80b); - static const IconData share = _CustomIconData(0xe80c); - static const IconData share_line = _CustomIconData(0xe80d); - static const IconData share_node = _CustomIconData(0xe80e); - static const IconData star_favorite_line = _CustomIconData(0xe80f); - static const IconData star_favorite_solid = _CustomIconData(0xe810); - static const IconData thumbs_down = _CustomIconData(0xe811); - static const IconData thumbs_down_outline = _CustomIconData(0xe812); - static const IconData thumbs_up = _CustomIconData(0xe813); - static const IconData thumbs_up_fill = _CustomIconData(0xe814); - static const IconData thumbs_up_line = _CustomIconData(0xe815); - static const IconData thumbs_up_outline = _CustomIconData(0xe816); - static const IconData topic_tag = _CustomIconData(0xe817); - static const IconData watch_later = _CustomIconData(0xe818); -} +// dart format off +abstract final class CustomIcons { + static const _kFontFam = 'custom_icon'; -class _CustomIconData extends IconData { - const _CustomIconData(super.codePoint) : super(fontFamily: 'custom_icon'); -} + static const IconData ai_circle = IconData(0xe800, fontFamily: _kFontFam); + static const IconData dm_off = IconData(0xe801, fontFamily: _kFontFam); + static const IconData dm_on = IconData(0xe802, fontFamily: _kFontFam); + static const IconData dm_settings = IconData(0xe803, fontFamily: _kFontFam); + static const IconData download = IconData(0xe804, fontFamily: _kFontFam); + static const IconData flip_rotate_90 = IconData(0xe805, fontFamily: _kFontFam); + static const IconData folderDownloadOutline = IconData(0xe806, fontFamily: _kFontFam); + static const IconData history = IconData(0xe807, fontFamily: _kFontFam); + static const IconData identifier_circle = IconData(0xe808, fontFamily: _kFontFam); + static const IconData live_reserve = IconData(0xe809, fontFamily: _kFontFam); + static const IconData motion_photos_on = IconData(0xe80a, fontFamily: _kFontFam); + static const IconData motion_photos_on_outlined = IconData(0xe80b, fontFamily: _kFontFam); + static const IconData open_in_full_rotate_45 = IconData(0xe80c, fontFamily: _kFontFam); + static const IconData player_dm_tip_back = IconData(0xe80d, fontFamily: _kFontFam); + static const IconData player_dm_tip_copy = IconData(0xe80e, fontFamily: _kFontFam); + static const IconData player_dm_tip_like = IconData(0xe80f, fontFamily: _kFontFam); + static const IconData player_dm_tip_like_solid = IconData(0xe810, fontFamily: _kFontFam); + static const IconData player_dm_tip_recall = IconData(0xe811, fontFamily: _kFontFam); + static const IconData repeat_rounded_rotate_90 = IconData(0xe812, fontFamily: _kFontFam); + static const IconData replay_rounded = IconData(0xe813, fontFamily: _kFontFam); + static const IconData share_node = IconData(0xe814, fontFamily: _kFontFam); + static const IconData shield_play_arrow = IconData(0xe815, fontFamily: _kFontFam); + static const IconData shield_published = IconData(0xe816, fontFamily: _kFontFam); + static const IconData shield_reply = IconData(0xe817, fontFamily: _kFontFam); + static const IconData shopping_bag_not_interested = IconData(0xe818, fontFamily: _kFontFam); + static const IconData splitscreen_rotate_90 = IconData(0xe819, fontFamily: _kFontFam); + static const IconData star_favorite_line = IconData(0xe81a, fontFamily: _kFontFam); + static const IconData star_favorite_solid = IconData(0xe81b, fontFamily: _kFontFam); + static const IconData subscriptions_outlined = IconData(0xe81c, fontFamily: _kFontFam); + static const IconData topic_tag = IconData(0xe81d, fontFamily: _kFontFam); + static const IconData touch_app_rotate_270 = IconData(0xe81e, fontFamily: _kFontFam); + static const IconData view_headline_rotate_90 = IconData(0xe81f, fontFamily: _kFontFam); + static const IconData watch_later_outlined = IconData(0xe820, fontFamily: _kFontFam); +} \ No newline at end of file diff --git a/lib/common/widgets/custom_toast.dart b/lib/common/widgets/custom_toast.dart index edc416fb84..2546455f07 100644 --- a/lib/common/widgets/custom_toast.dart +++ b/lib/common/widgets/custom_toast.dart @@ -2,7 +2,7 @@ import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; class CustomToast extends StatelessWidget { - const CustomToast({super.key, required this.msg}); + const CustomToast(this.msg, {super.key}); final String msg; @@ -12,13 +12,13 @@ class CustomToast extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = ColorScheme.of(context); return Container( - margin: EdgeInsets.only( + margin: .only( bottom: MediaQuery.viewPaddingOf(context).bottom + 30, ), - padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 10), + padding: const .symmetric(horizontal: 17, vertical: 10), decoration: BoxDecoration( color: colorScheme.primaryContainer.withValues(alpha: toastOpacity), - borderRadius: const BorderRadius.all(Radius.circular(20)), + borderRadius: const .all(.circular(20)), ), child: Text( msg, @@ -32,7 +32,7 @@ class CustomToast extends StatelessWidget { } class LoadingWidget extends StatelessWidget { - const LoadingWidget({super.key, required this.msg}); + const LoadingWidget(this.msg, {super.key}); ///loading msg final String msg; @@ -42,14 +42,14 @@ class LoadingWidget extends StatelessWidget { final theme = Theme.of(context); final onSurfaceVariant = theme.colorScheme.onSurfaceVariant; return Container( - padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20), + padding: const .symmetric(horizontal: 30, vertical: 20), decoration: BoxDecoration( color: theme.dialogTheme.backgroundColor, - borderRadius: const BorderRadius.all(Radius.circular(15)), + borderRadius: const .all(.circular(15)), ), child: Column( spacing: 20, - mainAxisSize: MainAxisSize.min, + mainAxisSize: .min, children: [ //loading animation CircularProgressIndicator( @@ -63,3 +63,34 @@ class LoadingWidget extends StatelessWidget { ); } } + +class NotifyWarning extends StatelessWidget { + const NotifyWarning(this.msg, {super.key}); + + final String msg; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final onSurfaceVariant = theme.colorScheme.onSurfaceVariant; + return Container( + decoration: BoxDecoration( + borderRadius: const .all(.circular(8)), + color: theme.dialogTheme.backgroundColor, + ), + padding: const .symmetric(horizontal: 20, vertical: 10), + child: Column( + spacing: 5, + mainAxisSize: .min, + children: [ + Icon( + Icons.warning_amber_rounded, + size: 22, + color: onSurfaceVariant, + ), + Text(msg, style: TextStyle(color: onSurfaceVariant)), + ], + ), + ); + } +} diff --git a/lib/common/widgets/dialog/dialog.dart b/lib/common/widgets/dialog/dialog.dart index 9d9ff7b227..db4b717f51 100644 --- a/lib/common/widgets/dialog/dialog.dart +++ b/lib/common/widgets/dialog/dialog.dart @@ -64,42 +64,39 @@ void showPgcFollowDialog({ showDialog( context: context, - builder: (context) => AlertDialog( + builder: (context) => SimpleDialog( clipBehavior: Clip.hardEdge, contentPadding: const EdgeInsets.symmetric(vertical: 12), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ...const [ - (followStatus: 3, title: '看过'), - (followStatus: 2, title: '在看'), - (followStatus: 1, title: '想看'), - ].map( - (item) => statusItem( - enabled: followStatus != item.followStatus, - text: item.title, - onTap: () { - Get.back(); - onUpdateStatus(item.followStatus); - }, - ), - ), - ListTile( - dense: true, - title: Padding( - padding: const EdgeInsets.only(left: 10), - child: Text( - '取消$type', - style: const TextStyle(fontSize: 14), - ), - ), + children: [ + ...const [ + (followStatus: 3, title: '看过'), + (followStatus: 2, title: '在看'), + (followStatus: 1, title: '想看'), + ].map( + (item) => statusItem( + enabled: followStatus != item.followStatus, + text: item.title, onTap: () { Get.back(); - onUpdateStatus(-1); + onUpdateStatus(item.followStatus); }, ), - ], - ), + ), + ListTile( + dense: true, + title: Padding( + padding: const EdgeInsets.only(left: 10), + child: Text( + '取消$type', + style: const TextStyle(fontSize: 14), + ), + ), + onTap: () { + Get.back(); + onUpdateStatus(-1); + }, + ), + ], ), ); } diff --git a/lib/common/widgets/dialog/export_import.dart b/lib/common/widgets/dialog/export_import.dart index e708ab81ec..c8f4967d61 100644 --- a/lib/common/widgets/dialog/export_import.dart +++ b/lib/common/widgets/dialog/export_import.dart @@ -1,9 +1,10 @@ import 'dart:async' show FutureOr; import 'dart:convert' show utf8, jsonDecode; -import 'dart:io' show File; import 'package:PiliPlus/common/style.dart'; -import 'package:PiliPlus/utils/extension/context_ext.dart'; +import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; +import 'package:PiliPlus/utils/extension/theme_ext.dart'; +import 'package:PiliPlus/utils/storage_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:file_picker_ohos/file_picker_ohos.dart'; import 'package:flutter/material.dart'; @@ -28,7 +29,7 @@ void exportToLocalFile({ required ValueGetter localFileName, }) { final res = utf8.encode(onExport()); - Utils.saveBytes2File( + StorageUtils.saveBytes2File( name: 'piliplus_${localFileName()}_' '${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json', @@ -45,93 +46,61 @@ Future importFromClipBoard( bool showConfirmDialog = true, }) async { final data = await Clipboard.getData('text/plain'); - if (data?.text?.isNotEmpty != true) { - SmartDialog.showToast('剪贴板无数据'); - return; - } - if (!context.mounted) return; - final text = data!.text!; - late final T json; - late final String formatText; - try { - json = jsonDecode(text); - formatText = Utils.jsonEncoder.convert(json); - } catch (e) { - SmartDialog.showToast('解析json失败:$e'); - return; - } - bool? executeImport; - if (showConfirmDialog) { - final highlight = Highlight()..registerLanguage('json', langJson); - final result = highlight.highlight( - code: formatText, - language: 'json', - ); - late TextSpanRenderer renderer; - bool? isDarkMode; - executeImport = await showDialog( - context: context, - builder: (context) { - final isDark = context.isDarkMode; - if (isDark != isDarkMode) { - isDarkMode = isDark; - renderer = TextSpanRenderer( - const TextStyle(), - isDark ? githubDarkTheme : githubTheme, - ); - result.render(renderer); - } - return AlertDialog( - title: Text('是否导入如下$title?'), - content: SingleChildScrollView( - child: Text.rich(renderer.span!), - ), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () => Get.back(result: true), - child: const Text('确定'), - ), - ], - ); - }, - ); - } else { - executeImport = true; - } - if (executeImport ?? false) { + if (data?.text case final text? when (text.isNotEmpty)) { + if (!context.mounted) return; + final T json; + final String formatText; try { - await onImport(json); - SmartDialog.showToast('导入成功'); + json = jsonDecode(text); + formatText = Utils.jsonEncoder.convert(json); } catch (e) { - SmartDialog.showToast('导入失败:$e'); + SmartDialog.showToast('解析json失败:$e'); + return; } - } -} - -Future importFromLocalFile({ - required FutureOr Function(T json) onImport, -}) async { - final result = await FilePicker.platform.pickFiles(); - if (result != null) { - final path = result.files.first.path; - if (path != null) { - final data = await File(path).readAsString(); - late final T json; - try { - json = jsonDecode(data); - } catch (e) { - SmartDialog.showToast('解析json失败:$e'); - return; - } + bool? executeImport; + if (showConfirmDialog) { + final highlight = Highlight()..registerLanguage('json', langJson); + final result = highlight.highlight( + code: formatText, + language: 'json', + ); + late TextSpanRenderer renderer; + bool? isDarkMode; + executeImport = await showDialog( + context: context, + builder: (context) { + final colorScheme = ColorScheme.of(context); + final isDark = colorScheme.isDark; + if (isDark != isDarkMode) { + isDarkMode = isDark; + renderer = TextSpanRenderer( + null, + isDark ? githubDarkTheme : githubTheme, + ); + result.render(renderer); + } + return AlertDialog( + title: Text('是否导入如下$title?'), + content: SingleChildScrollView( + child: Text.rich(renderer.span!), + ), + actions: [ + TextButton( + onPressed: Get.back, + child: Text('取消', style: TextStyle(color: colorScheme.outline)), + ), + TextButton( + onPressed: () => Get.back(result: true), + child: const Text('确定'), + ), + ], + ); + }, + ); + } else { + executeImport = true; + } + if (executeImport ?? false) { try { await onImport(json); SmartDialog.showToast('导入成功'); @@ -139,6 +108,35 @@ Future importFromLocalFile({ SmartDialog.showToast('导入失败:$e'); } } + } else { + SmartDialog.showToast('剪贴板无数据'); + return; + } +} + +Future importFromLocalFile({ + required FutureOr Function(T json) onImport, +}) async { + // 鸿蒙适配 fork 仅提供 FilePicker.platform 实例方法 + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: const ['json', 'txt'], + ); + if (result?.files.firstOrNull case final file?) { + final data = await file.xFile.readAsString(); + final T json; + try { + json = jsonDecode(data); + } catch (e) { + SmartDialog.showToast('解析json失败:$e'); + return; + } + try { + await onImport(json); + SmartDialog.showToast('导入成功'); + } catch (e) { + SmartDialog.showToast('导入失败:$e'); + } } } @@ -171,7 +169,6 @@ void importFromInput( json = jsonDecode(value!) as T; return null; } catch (e) { - if (e is FormatException) {} return '解析json失败:$e'; } }, @@ -182,7 +179,7 @@ void importFromInput( child: Text( '取消', style: TextStyle( - color: Theme.of(context).colorScheme.outline, + color: ColorScheme.of(context).outline, ), ), ), @@ -219,21 +216,19 @@ Future showImportExportDialog( builder: (context) { const style = TextStyle(fontSize: 15); return SimpleDialog( - clipBehavior: Clip.hardEdge, + clipBehavior: .hardEdge, title: Text('导入/导出$title'), children: [ - ListTile( - dense: true, - title: const Text('导出至剪贴板', style: style), - onTap: () { + DialogOption( + child: const Text('导出至剪贴板', style: style), + onPressed: () { Get.back(); exportToClipBoard(onExport: onExport); }, ), - ListTile( - dense: true, - title: const Text('导出文件至本地', style: style), - onTap: () { + DialogOption( + child: const Text('导出文件至本地', style: style), + onPressed: () { Get.back(); exportToLocalFile(onExport: onExport, localFileName: localFileName); }, @@ -242,18 +237,16 @@ Future showImportExportDialog( height: 1, color: ColorScheme.of(context).outline.withValues(alpha: 0.1), ), - ListTile( - dense: true, - title: const Text('输入', style: style), - onTap: () { + DialogOption( + child: const Text('输入', style: style), + onPressed: () { Get.back(); importFromInput(context, title: title, onImport: onImport); }, ), - ListTile( - dense: true, - title: const Text('从剪贴板导入', style: style), - onTap: () { + DialogOption( + child: const Text('从剪贴板导入', style: style), + onPressed: () { Get.back(); importFromClipBoard( context, @@ -263,10 +256,9 @@ Future showImportExportDialog( ); }, ), - ListTile( - dense: true, - title: const Text('从本地文件导入', style: style), - onTap: () { + DialogOption( + child: const Text('从本地文件导入', style: style), + onPressed: () { Get.back(); importFromLocalFile(onImport: onImport); }, diff --git a/lib/common/widgets/dialog/simple_dialog_option.dart b/lib/common/widgets/dialog/simple_dialog_option.dart new file mode 100644 index 0000000000..26a8d907e7 --- /dev/null +++ b/lib/common/widgets/dialog/simple_dialog_option.dart @@ -0,0 +1,29 @@ +import 'package:PiliPlus/utils/platform_utils.dart'; +import 'package:flutter/material.dart'; + +final EdgeInsets _padding = PlatformUtils.isMobile + ? const .symmetric(horizontal: 16, vertical: 14) + : const .symmetric(horizontal: 16, vertical: 10); + +class DialogOption extends StatelessWidget { + const DialogOption({ + super.key, + this.onPressed, + this.child, + }); + + final VoidCallback? onPressed; + + final Widget? child; + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onPressed, + child: Padding( + padding: _padding, + child: child, + ), + ); + } +} diff --git a/lib/common/widgets/draggable_sheet/dyn.dart b/lib/common/widgets/draggable_sheet/dyn.dart new file mode 100644 index 0000000000..f1f81f07ab --- /dev/null +++ b/lib/common/widgets/draggable_sheet/dyn.dart @@ -0,0 +1,91 @@ +part of 'package:PiliPlus/common/widgets/flutter/draggable_scrollable_sheet.dart'; + +class DynDraggableScrollableSheet extends DraggableScrollableSheet { + const DynDraggableScrollableSheet({ + super.key, + super.initialChildSize, + super.minChildSize, + super.maxChildSize, + super.expand, + super.snap, + super.snapSizes, + super.snapAnimationDuration, + super.controller, + super.shouldCloseOnMinExtent, + required super.builder, + }); + + @override + State createState() => + _DynDraggableScrollableSheetState(); +} + +class _DynDraggableScrollableSheetState extends _DraggableScrollableSheetState { + @override + void initState() { + super.initState(); + _extent = _DraggableSheetExtent( + minSize: widget.minChildSize, + maxSize: widget.maxChildSize, + snap: widget.snap, + snapSizes: _impliedSnapSizes(), + snapAnimationDuration: widget.snapAnimationDuration, + initialSize: widget.initialChildSize, + shouldCloseOnMinExtent: widget.shouldCloseOnMinExtent, + ); + _scrollController = _DynDraggableScrollableSheetScrollController( + extent: _extent, + ); + widget.controller?._attach(_scrollController); + } +} + +class _DynDraggableScrollableSheetScrollController + extends _DraggableScrollableSheetScrollController { + _DynDraggableScrollableSheetScrollController({ + required super.extent, + }); + + @override + _DraggableScrollableSheetScrollPosition createScrollPosition( + ScrollPhysics physics, + ScrollContext context, + ScrollPosition? oldPosition, + ) { + return _DynDraggableScrollableSheetScrollPosition( + physics: physics.applyTo(const AlwaysScrollableScrollPhysics()), + context: context, + oldPosition: oldPosition, + getExtent: () => extent, + ); + } +} + +class _DynDraggableScrollableSheetScrollPosition + extends _DraggableScrollableSheetScrollPosition { + _DynDraggableScrollableSheetScrollPosition({ + required super.physics, + required super.context, + super.oldPosition, + required super.getExtent, + }); + + bool _isAtTop = true; + + @override + bool get listShouldScroll => !_isAtTop || super.listShouldScroll; + + @override + void applyUserOffset(double delta) { + if (_isAtTop && pixels > 0) { + _isAtTop = false; + } + super.applyUserOffset(delta); + } + + @override + Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { + _isAtTop = pixels == 0; + return super.drag(details, dragCancelCallback); + } +} diff --git a/lib/common/widgets/draggable_sheet/topic.dart b/lib/common/widgets/draggable_sheet/topic.dart new file mode 100644 index 0000000000..9e627b3b2d --- /dev/null +++ b/lib/common/widgets/draggable_sheet/topic.dart @@ -0,0 +1,74 @@ +part of 'package:PiliPlus/common/widgets/flutter/draggable_scrollable_sheet.dart'; + +class TopicDraggableScrollableSheet extends DraggableScrollableSheet { + const TopicDraggableScrollableSheet({ + super.key, + super.initialChildSize, + super.minChildSize, + super.maxChildSize, + super.expand, + super.snap, + super.snapSizes, + super.snapAnimationDuration, + super.controller, + super.shouldCloseOnMinExtent, + required super.builder, + this.initialScrollOffset = 0.0, + }); + + final double initialScrollOffset; + + @override + State createState() => + _TopicDraggableScrollableSheetState(); +} + +class _TopicDraggableScrollableSheetState + extends _DraggableScrollableSheetState { + @override + void initState() { + super.initState(); + _extent = _DraggableSheetExtent( + minSize: widget.minChildSize, + maxSize: widget.maxChildSize, + snap: widget.snap, + snapSizes: _impliedSnapSizes(), + snapAnimationDuration: widget.snapAnimationDuration, + initialSize: widget.initialChildSize, + shouldCloseOnMinExtent: widget.shouldCloseOnMinExtent, + ); + _scrollController = _TopicDraggableScrollableSheetScrollController( + extent: _extent, + initialScrollOffset: + (widget as TopicDraggableScrollableSheet).initialScrollOffset, + ); + widget.controller?._attach(_scrollController); + } +} + +class _TopicDraggableScrollableSheetScrollController + extends _DraggableScrollableSheetScrollController { + _TopicDraggableScrollableSheetScrollController({ + required super.extent, + double initialScrollOffset = 0.0, + }) : _initialScrollOffset = initialScrollOffset; + + @override + double get initialScrollOffset => _initialScrollOffset; + final double _initialScrollOffset; + + @override + _DraggableScrollableSheetScrollPosition createScrollPosition( + ScrollPhysics physics, + ScrollContext context, + ScrollPosition? oldPosition, + ) { + return _DraggableScrollableSheetScrollPosition( + physics: physics.applyTo(const AlwaysScrollableScrollPhysics()), + context: context, + oldPosition: oldPosition, + getExtent: () => extent, + initialPixels: _initialScrollOffset, + ); + } +} diff --git a/lib/common/widgets/expandable.dart b/lib/common/widgets/expandable.dart new file mode 100644 index 0000000000..93e9472744 --- /dev/null +++ b/lib/common/widgets/expandable.dart @@ -0,0 +1,140 @@ +import 'package:PiliPlus/common/widgets/animated_multi_height.dart'; +import 'package:flutter/material.dart'; + +class ExpandablePanel extends StatelessWidget { + final bool expand; + + final Widget collapsed; + + final Widget expanded; + + const ExpandablePanel({ + super.key, + required this.expand, + required this.collapsed, + required this.expanded, + }); + + @override + Widget build(BuildContext context) { + return _AnimatedCross( + alignment: .topLeft, + firstChild: collapsed, + secondChild: expanded, + sizeCurve: Curves.linear, + crossFadeState: expand ? .showSecond : .showFirst, + duration: const Duration(milliseconds: 300), + ); + } +} + +/// ref [AnimatedCrossFade] +class _AnimatedCross extends StatefulWidget { + const _AnimatedCross({ + required this.firstChild, + required this.secondChild, + this.sizeCurve = Curves.linear, + this.alignment = Alignment.topCenter, + required this.crossFadeState, + required this.duration, + }); + + final Widget firstChild; + + final Widget secondChild; + + final CrossFadeState crossFadeState; + + final Duration duration; + + final Curve sizeCurve; + + final AlignmentGeometry alignment; + + @override + State<_AnimatedCross> createState() => _AnimatedCrossState(); +} + +class _AnimatedCrossState extends State<_AnimatedCross> { + late bool _showFirst; + AnimationStatus? _status; + + @override + void initState() { + super.initState(); + switch (widget.crossFadeState) { + case .showFirst: + _showFirst = true; + case .showSecond: + _showFirst = false; + } + } + + @override + void didUpdateWidget(_AnimatedCross oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.crossFadeState != oldWidget.crossFadeState) { + switch (widget.crossFadeState) { + case .showFirst: + _status = .reverse; + case .showSecond: + _status = .forward; + } + _showFirst = false; + } + } + + void _onEnd() { + if (_status == .reverse) { + _showFirst = true; + } + setState(() {}); + } + + Widget get firstChild => _widgetBuilder(_showFirst, widget.firstChild); + + Widget get secondChild => _widgetBuilder(!_showFirst, widget.secondChild); + + static Widget _widgetBuilder(bool visible, Widget child) => + Opacity(opacity: visible ? 1.0 : 0.0, child: child); + + @override + Widget build(BuildContext context) { + const Key kFirstChildKey = ValueKey(.showFirst); + const Key kSecondChildKey = ValueKey(.showSecond); + + final bool expand; + final Key topKey; + Widget topChild; + final Key bottomKey; + Widget bottomChild; + + switch (widget.crossFadeState) { + case .showFirst: + expand = false; + topKey = kFirstChildKey; + topChild = firstChild; + bottomKey = kSecondChildKey; + bottomChild = secondChild; + case .showSecond: + expand = true; + topKey = kSecondChildKey; + topChild = secondChild; + bottomKey = kFirstChildKey; + bottomChild = firstChild; + } + + return AnimatedMultiHeight( + duration: widget.duration, + curve: widget.sizeCurve, + onEnd: _onEnd, + expand: expand, + child: AnimatedCrossFade.defaultLayoutBuilder( + topChild, + topKey, + bottomChild, + bottomKey, + ), + ); + } +} diff --git a/lib/common/widgets/extra_hittest_stack.dart b/lib/common/widgets/extra_hittest_stack.dart new file mode 100644 index 0000000000..47cadf5caa --- /dev/null +++ b/lib/common/widgets/extra_hittest_stack.dart @@ -0,0 +1,94 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' + show RenderStack, BoxHitTestResult, BoxHitTestEntry; + +class ExtraHitTestStack extends Stack { + const ExtraHitTestStack({ + super.key, + super.alignment, + super.textDirection, + super.fit, + super.clipBehavior, + super.children, + }); + + @override + RenderExtraHitTestStack createRenderObject(BuildContext context) { + return RenderExtraHitTestStack( + alignment: alignment, + textDirection: textDirection ?? Directionality.maybeOf(context), + fit: fit, + clipBehavior: clipBehavior, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderExtraHitTestStack renderObject, + ) { + renderObject + ..alignment = alignment + ..textDirection = textDirection ?? Directionality.maybeOf(context) + ..fit = fit + ..clipBehavior = clipBehavior; + } +} + +class RenderExtraHitTestStack extends RenderStack { + RenderExtraHitTestStack({ + super.children, + super.alignment, + super.textDirection, + super.fit, + super.clipBehavior, + }); + + @override + bool hitTest(BoxHitTestResult result, {required Offset position}) { + assert(() { + if (!hasSize) { + if (debugNeedsLayout) { + throw FlutterError.fromParts([ + ErrorSummary( + 'Cannot hit test a render box that has never been laid out.', + ), + describeForError( + 'The hitTest() method was called on this RenderBox', + ), + ErrorDescription( + "Unfortunately, this object's geometry is not known at this time, " + 'probably because it has never been laid out. ' + 'This means it cannot be accurately hit-tested.', + ), + ErrorHint( + 'If you are trying ' + 'to perform a hit test during the layout phase itself, make sure ' + "you only hit test nodes that have completed layout (e.g. the node's " + 'children, after their layout() method has been called).', + ), + ]); + } + throw FlutterError.fromParts([ + ErrorSummary('Cannot hit test a render box with no size.'), + describeForError('The hitTest() method was called on this RenderBox'), + ErrorDescription( + 'Although this node is not marked as needing layout, ' + 'its size is not set.', + ), + ErrorHint( + 'A RenderBox object must have an ' + 'explicit size before it can be hit-tested. Make sure ' + 'that the RenderBox in question sets its size during layout.', + ), + ]); + } + return true; + }()); + if (hitTestChildren(result, position: position) || hitTestSelf(position)) { + result.add(BoxHitTestEntry(this, position)); + return true; + } + return false; + } +} diff --git a/lib/common/widgets/floating_navigation_bar.dart b/lib/common/widgets/floating_navigation_bar.dart new file mode 100644 index 0000000000..46838ca14e --- /dev/null +++ b/lib/common/widgets/floating_navigation_bar.dart @@ -0,0 +1,777 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:PiliPlus/utils/extension/theme_ext.dart'; +import 'package:flutter/material.dart'; + +const double _kMaxLabelTextScaleFactor = 1.3; + +const _kNavigationHeight = 64.0; +const _kIndicatorHeight = _kNavigationHeight - 2 * _kIndicatorPaddingInt; +const _kIndicatorWidth = 86.0; +const _kIndicatorPaddingInt = 4.0; +const _kIndicatorPadding = EdgeInsets.all(_kIndicatorPaddingInt); +const _kBorderRadius = BorderRadius.all(.circular(_kNavigationHeight / 2)); +const _kNavigationShape = RoundedSuperellipseBorder( + borderRadius: _kBorderRadius, +); + +/// ref [NavigationBar] +class FloatingNavigationBar extends StatelessWidget { + // ignore: prefer_const_constructors_in_immutables + FloatingNavigationBar({ + super.key, + this.animationDuration = const Duration(milliseconds: 500), + this.selectedIndex = 0, + required this.destinations, + this.onDestinationSelected, + this.backgroundColor, + this.elevation, + this.shadowColor, + this.surfaceTintColor, + this.indicatorColor, + this.indicatorShape, + this.labelBehavior, + this.overlayColor, + this.labelTextStyle, + this.labelPadding, + this.bottomPadding = 8.0, + }) : assert(destinations.length >= 2), + assert(0 <= selectedIndex && selectedIndex < destinations.length); + + final Duration animationDuration; + final int selectedIndex; + final List destinations; + final ValueChanged? onDestinationSelected; + final Color? backgroundColor; + final double? elevation; + final Color? shadowColor; + final Color? surfaceTintColor; + final Color? indicatorColor; + final ShapeBorder? indicatorShape; + final NavigationDestinationLabelBehavior? labelBehavior; + final WidgetStateProperty? overlayColor; + final WidgetStateProperty? labelTextStyle; + final EdgeInsetsGeometry? labelPadding; + final double bottomPadding; + + VoidCallback _handleTap(int index) { + return onDestinationSelected != null + ? () => onDestinationSelected!(index) + : () {}; + } + + @override + Widget build(BuildContext context) { + final defaults = _NavigationBarDefaultsM3(context); + + final navigationBarTheme = NavigationBarTheme.of(context); + final effectiveLabelBehavior = + labelBehavior ?? + navigationBarTheme.labelBehavior ?? + defaults.labelBehavior!; + + final padding = MediaQuery.viewPaddingOf(context); + + return UnconstrainedBox( + child: Padding( + padding: .fromLTRB( + padding.left, + 0, + padding.right, + bottomPadding + padding.bottom, + ), + child: SizedBox( + height: _kNavigationHeight, + width: destinations.length * _kIndicatorWidth, + child: DecoratedBox( + decoration: ShapeDecoration( + color: ElevationOverlay.applySurfaceTint( + backgroundColor ?? + navigationBarTheme.backgroundColor ?? + defaults.backgroundColor!, + surfaceTintColor ?? + navigationBarTheme.surfaceTintColor ?? + defaults.surfaceTintColor, + elevation ?? + navigationBarTheme.elevation ?? + defaults.elevation!, + ), + shape: RoundedSuperellipseBorder( + side: defaults.borderSide, + borderRadius: _kBorderRadius, + ), + ), + child: Padding( + padding: _kIndicatorPadding, + child: Row( + crossAxisAlignment: .stretch, + children: [ + for (int i = 0; i < destinations.length; i++) + Expanded( + child: _SelectableAnimatedBuilder( + duration: animationDuration, + isSelected: i == selectedIndex, + builder: (context, animation) { + return _NavigationDestinationInfo( + index: i, + selectedIndex: selectedIndex, + totalNumberOfDestinations: destinations.length, + selectedAnimation: animation, + labelBehavior: effectiveLabelBehavior, + indicatorColor: indicatorColor, + indicatorShape: indicatorShape, + overlayColor: overlayColor, + onTap: _handleTap(i), + labelTextStyle: labelTextStyle, + labelPadding: labelPadding, + child: destinations[i], + ); + }, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +class FloatingNavigationDestination extends StatelessWidget { + const FloatingNavigationDestination({ + super.key, + required this.icon, + this.selectedIcon, + required this.label, + this.tooltip, + this.enabled = true, + }); + + final Widget icon; + + final Widget? selectedIcon; + + final String label; + + final String? tooltip; + + final bool enabled; + + @override + Widget build(BuildContext context) { + final info = _NavigationDestinationInfo.of(context); + const selectedState = {WidgetState.selected}; + const unselectedState = {}; + const disabledState = {WidgetState.disabled}; + + final navigationBarTheme = NavigationBarTheme.of(context); + final defaults = _NavigationBarDefaultsM3(context); + final animation = info.selectedAnimation; + + return Stack( + alignment: .center, + clipBehavior: .none, + children: [ + NavigationIndicator( + animation: animation, + color: + info.indicatorColor ?? + navigationBarTheme.indicatorColor ?? + defaults.indicatorColor!, + ), + _NavigationDestinationBuilder( + label: label, + tooltip: tooltip, + enabled: enabled, + buildIcon: (context) { + final IconThemeData selectedIconTheme = + navigationBarTheme.iconTheme?.resolve(selectedState) ?? + defaults.iconTheme!.resolve(selectedState)!; + final IconThemeData unselectedIconTheme = + navigationBarTheme.iconTheme?.resolve(unselectedState) ?? + defaults.iconTheme!.resolve(unselectedState)!; + final IconThemeData disabledIconTheme = + navigationBarTheme.iconTheme?.resolve(disabledState) ?? + defaults.iconTheme!.resolve(disabledState)!; + + final Widget selectedIconWidget = IconTheme.merge( + data: enabled ? selectedIconTheme : disabledIconTheme, + child: selectedIcon ?? icon, + ); + final Widget unselectedIconWidget = IconTheme.merge( + data: enabled ? unselectedIconTheme : disabledIconTheme, + child: icon, + ); + return _StatusTransitionWidgetBuilder( + animation: animation, + builder: (context, child) { + return animation.isForwardOrCompleted + ? selectedIconWidget + : unselectedIconWidget; + }, + ); + }, + buildLabel: (context) { + final TextStyle? effectiveSelectedLabelTextStyle = + info.labelTextStyle?.resolve(selectedState) ?? + navigationBarTheme.labelTextStyle?.resolve(selectedState) ?? + defaults.labelTextStyle!.resolve(selectedState); + final TextStyle? effectiveUnselectedLabelTextStyle = + info.labelTextStyle?.resolve(unselectedState) ?? + navigationBarTheme.labelTextStyle?.resolve(unselectedState) ?? + defaults.labelTextStyle!.resolve(unselectedState); + final TextStyle? effectiveDisabledLabelTextStyle = + info.labelTextStyle?.resolve(disabledState) ?? + navigationBarTheme.labelTextStyle?.resolve(disabledState) ?? + defaults.labelTextStyle!.resolve(disabledState); + final EdgeInsetsGeometry labelPadding = + info.labelPadding ?? + navigationBarTheme.labelPadding ?? + defaults.labelPadding!; + + final textStyle = enabled + ? animation.isForwardOrCompleted + ? effectiveSelectedLabelTextStyle + : effectiveUnselectedLabelTextStyle + : effectiveDisabledLabelTextStyle; + + return Padding( + padding: labelPadding, + child: MediaQuery.withClampedTextScaling( + maxScaleFactor: _kMaxLabelTextScaleFactor, + child: Text(label, style: textStyle), + ), + ); + }, + ), + ], + ); + } +} + +class _NavigationDestinationBuilder extends StatefulWidget { + const _NavigationDestinationBuilder({ + required this.buildIcon, + required this.buildLabel, + required this.label, + this.tooltip, + this.enabled = true, + }); + + final WidgetBuilder buildIcon; + + final WidgetBuilder buildLabel; + + final String label; + + final String? tooltip; + + final bool enabled; + + @override + State<_NavigationDestinationBuilder> createState() => + _NavigationDestinationBuilderState(); +} + +class _NavigationDestinationBuilderState + extends State<_NavigationDestinationBuilder> { + final GlobalKey iconKey = GlobalKey(); + + @override + Widget build(BuildContext context) { + final info = _NavigationDestinationInfo.of(context); + + final child = GestureDetector( + behavior: .opaque, + onTap: widget.enabled ? info.onTap : null, + child: _NavigationBarDestinationLayout( + icon: widget.buildIcon(context), + iconKey: iconKey, + label: widget.buildLabel(context), + ), + ); + if (info.labelBehavior == .alwaysShow) { + return child; + } + return _NavigationBarDestinationTooltip( + message: widget.tooltip ?? widget.label, + child: child, + ); + } +} + +class _NavigationDestinationInfo extends InheritedWidget { + const _NavigationDestinationInfo({ + required this.index, + required this.selectedIndex, + required this.totalNumberOfDestinations, + required this.selectedAnimation, + required this.labelBehavior, + required this.indicatorColor, + required this.indicatorShape, + required this.overlayColor, + required this.onTap, + this.labelTextStyle, + this.labelPadding, + required super.child, + }); + + final int index; + + final int selectedIndex; + + final int totalNumberOfDestinations; + + final Animation selectedAnimation; + + final NavigationDestinationLabelBehavior labelBehavior; + + final Color? indicatorColor; + + final ShapeBorder? indicatorShape; + + final WidgetStateProperty? overlayColor; + + final VoidCallback onTap; + + final WidgetStateProperty? labelTextStyle; + + final EdgeInsetsGeometry? labelPadding; + + static _NavigationDestinationInfo of(BuildContext context) { + final _NavigationDestinationInfo? result = context + .dependOnInheritedWidgetOfExactType<_NavigationDestinationInfo>(); + assert( + result != null, + 'Navigation destinations need a _NavigationDestinationInfo parent, ' + 'which is usually provided by NavigationBar.', + ); + return result!; + } + + @override + bool updateShouldNotify(_NavigationDestinationInfo oldWidget) { + return index != oldWidget.index || + totalNumberOfDestinations != oldWidget.totalNumberOfDestinations || + selectedAnimation != oldWidget.selectedAnimation || + labelBehavior != oldWidget.labelBehavior || + onTap != oldWidget.onTap; + } +} + +class NavigationIndicator extends StatelessWidget { + const NavigationIndicator({ + super.key, + required this.animation, + this.color, + this.width = _kIndicatorWidth, + this.height = _kIndicatorHeight, + }); + + final Animation animation; + + final Color? color; + + final double width; + + final double height; + + static final _anim = Tween( + begin: .5, + end: 1.0, + ).chain(CurveTween(curve: Curves.easeInOutCubicEmphasized)); + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: animation, + builder: (context, child) { + final double scale = animation.isDismissed + ? 0.0 + : _anim.evaluate(animation); + + return Transform( + alignment: Alignment.center, + transform: Matrix4.diagonal3Values(scale, 1.0, 1.0), + child: child, + ); + }, + + child: _StatusTransitionWidgetBuilder( + animation: animation, + builder: (context, child) { + return _SelectableAnimatedBuilder( + isSelected: animation.isForwardOrCompleted, + duration: const Duration(milliseconds: 100), + alwaysDoFullAnimation: true, + builder: (context, fadeAnimation) { + return FadeTransition( + opacity: fadeAnimation, + child: DecoratedBox( + decoration: ShapeDecoration( + shape: _kNavigationShape, + color: color ?? Theme.of(context).colorScheme.secondary, + ), + child: const SizedBox( + width: _kIndicatorWidth, + height: _kIndicatorHeight, + ), + ), + ); + }, + ); + }, + ), + ); + } +} + +class _NavigationBarDestinationLayout extends StatelessWidget { + const _NavigationBarDestinationLayout({ + required this.icon, + required this.iconKey, + required this.label, + }); + + final Widget icon; + + final GlobalKey iconKey; + + final Widget label; + + @override + Widget build(BuildContext context) { + return _DestinationLayoutAnimationBuilder( + builder: (context, animation) { + return CustomMultiChildLayout( + delegate: _NavigationDestinationLayoutDelegate(animation: animation), + children: [ + LayoutId( + id: _NavigationDestinationLayoutDelegate.iconId, + child: KeyedSubtree(key: iconKey, child: icon), + ), + LayoutId( + id: _NavigationDestinationLayoutDelegate.labelId, + child: FadeTransition( + alwaysIncludeSemantics: true, + opacity: animation, + child: label, + ), + ), + ], + ); + }, + ); + } +} + +class _DestinationLayoutAnimationBuilder extends StatelessWidget { + const _DestinationLayoutAnimationBuilder({required this.builder}); + + final Widget Function(BuildContext, Animation) builder; + + @override + Widget build(BuildContext context) { + final info = _NavigationDestinationInfo.of(context); + switch (info.labelBehavior) { + case NavigationDestinationLabelBehavior.alwaysShow: + return builder(context, kAlwaysCompleteAnimation); + case NavigationDestinationLabelBehavior.alwaysHide: + return builder(context, kAlwaysDismissedAnimation); + case NavigationDestinationLabelBehavior.onlyShowSelected: + return _CurvedAnimationBuilder( + animation: info.selectedAnimation, + curve: Curves.easeInOutCubicEmphasized, + reverseCurve: Curves.easeInOutCubicEmphasized.flipped, + builder: builder, + ); + } + } +} + +class _NavigationBarDestinationTooltip extends StatelessWidget { + const _NavigationBarDestinationTooltip({ + required this.message, + required this.child, + }); + + final String message; + + final Widget child; + + @override + Widget build(BuildContext context) { + return Tooltip( + message: message, + verticalOffset: 34, + excludeFromSemantics: true, + preferBelow: false, + child: child, + ); + } +} + +class _NavigationDestinationLayoutDelegate extends MultiChildLayoutDelegate { + _NavigationDestinationLayoutDelegate({required this.animation}) + : super(relayout: animation); + + final Animation animation; + + static const int iconId = 1; + + static const int labelId = 2; + + @override + void performLayout(Size size) { + double halfWidth(Size size) => size.width / 2; + double halfHeight(Size size) => size.height / 2; + + final Size iconSize = layoutChild(iconId, BoxConstraints.loose(size)); + final Size labelSize = layoutChild(labelId, BoxConstraints.loose(size)); + + final double yPositionOffset = Tween( + begin: halfHeight(iconSize), + + end: halfHeight(iconSize) + halfHeight(labelSize), + ).transform(animation.value); + final double iconYPosition = halfHeight(size) - yPositionOffset; + + positionChild( + iconId, + Offset( + halfWidth(size) - halfWidth(iconSize), + iconYPosition, + ), + ); + + positionChild( + labelId, + Offset( + halfWidth(size) - halfWidth(labelSize), + + iconYPosition + iconSize.height, + ), + ); + } + + @override + bool shouldRelayout(_NavigationDestinationLayoutDelegate oldDelegate) { + return oldDelegate.animation != animation; + } +} + +class _StatusTransitionWidgetBuilder extends StatusTransitionWidget { + const _StatusTransitionWidgetBuilder({ + required super.animation, + required this.builder, + // ignore: unused_element_parameter + this.child, + }); + + final TransitionBuilder builder; + + final Widget? child; + + @override + Widget build(BuildContext context) => builder(context, child); +} + +class _SelectableAnimatedBuilder extends StatefulWidget { + const _SelectableAnimatedBuilder({ + required this.isSelected, + this.duration = const Duration(milliseconds: 200), + this.alwaysDoFullAnimation = false, + required this.builder, + }); + + final bool isSelected; + + final Duration duration; + + final bool alwaysDoFullAnimation; + + final Widget Function(BuildContext, Animation) builder; + + @override + _SelectableAnimatedBuilderState createState() => + _SelectableAnimatedBuilderState(); +} + +class _SelectableAnimatedBuilderState extends State<_SelectableAnimatedBuilder> + with SingleTickerProviderStateMixin { + late AnimationController _controller; + + @override + void initState() { + super.initState(); + _controller = AnimationController(vsync: this); + _controller.duration = widget.duration; + _controller.value = widget.isSelected ? 1.0 : 0.0; + } + + @override + void didUpdateWidget(_SelectableAnimatedBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.duration != widget.duration) { + _controller.duration = widget.duration; + } + if (oldWidget.isSelected != widget.isSelected) { + if (widget.isSelected) { + _controller.forward(from: widget.alwaysDoFullAnimation ? 0 : null); + } else { + _controller.reverse(from: widget.alwaysDoFullAnimation ? 1 : null); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.builder(context, _controller); + } +} + +class _CurvedAnimationBuilder extends StatefulWidget { + const _CurvedAnimationBuilder({ + required this.animation, + required this.curve, + required this.reverseCurve, + required this.builder, + }); + + final Animation animation; + final Curve curve; + final Curve reverseCurve; + final Widget Function(BuildContext, Animation) builder; + + @override + _CurvedAnimationBuilderState createState() => _CurvedAnimationBuilderState(); +} + +class _CurvedAnimationBuilderState extends State<_CurvedAnimationBuilder> { + late AnimationStatus _animationDirection; + AnimationStatus? _preservedDirection; + + @override + void initState() { + super.initState(); + _animationDirection = widget.animation.status; + _updateStatus(widget.animation.status); + widget.animation.addStatusListener(_updateStatus); + } + + @override + void dispose() { + widget.animation.removeStatusListener(_updateStatus); + super.dispose(); + } + + void _updateStatus(AnimationStatus status) { + if (_animationDirection != status) { + setState(() { + _animationDirection = status; + }); + } + switch (status) { + case AnimationStatus.forward || AnimationStatus.reverse + when _preservedDirection != null: + break; + case AnimationStatus.forward || AnimationStatus.reverse: + setState(() { + _preservedDirection = status; + }); + case AnimationStatus.completed || AnimationStatus.dismissed: + setState(() { + _preservedDirection = null; + }); + } + } + + @override + Widget build(BuildContext context) { + final shouldUseForwardCurve = + (_preservedDirection ?? _animationDirection) != AnimationStatus.reverse; + + final Animation curvedAnimation = CurveTween( + curve: shouldUseForwardCurve ? widget.curve : widget.reverseCurve, + ).animate(widget.animation); + + return widget.builder(context, curvedAnimation); + } +} + +const _indicatorDark = Color(0x15FFFFFF); +const _indicatorLight = Color(0x10000000); + +class _NavigationBarDefaultsM3 extends NavigationBarThemeData { + _NavigationBarDefaultsM3(this.context) + : super( + height: _kNavigationHeight, + elevation: 3.0, + labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, + ); + + final BuildContext context; + late final _colors = Theme.of(context).colorScheme; + late final _textTheme = Theme.of(context).textTheme; + + BorderSide get borderSide => _colors.isDark + ? const BorderSide(color: Color(0x08FFFFFF)) + : const BorderSide(color: Color(0x08000000)); + + @override + Color? get backgroundColor => _colors.surfaceContainer; + + @override + Color? get shadowColor => Colors.transparent; + + @override + Color? get surfaceTintColor => Colors.transparent; + + @override + WidgetStateProperty? get iconTheme { + return WidgetStateProperty.resolveWith((Set states) { + return IconThemeData( + size: 24.0, + color: states.contains(WidgetState.disabled) + ? _colors.onSurfaceVariant.withValues(alpha: 0.38) + : states.contains(WidgetState.selected) + ? _colors.onSecondaryContainer + : _colors.onSurfaceVariant, + ); + }); + } + + @override + Color? get indicatorColor => + _colors.isDark ? _indicatorDark : _indicatorLight; + + @override + ShapeBorder? get indicatorShape => const StadiumBorder(); + + @override + WidgetStateProperty? get labelTextStyle { + return WidgetStateProperty.resolveWith((Set states) { + final TextStyle style = _textTheme.labelMedium!; + return style.apply( + color: states.contains(WidgetState.disabled) + ? _colors.onSurfaceVariant.withValues(alpha: 0.38) + : states.contains(WidgetState.selected) + ? _colors.onSurface + : _colors.onSurfaceVariant, + ); + }); + } + + @override + EdgeInsetsGeometry? get labelPadding => const EdgeInsets.only(top: 2); +} diff --git a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart b/lib/common/widgets/flutter/draggable_scrollable_sheet.dart similarity index 92% rename from lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart rename to lib/common/widgets/flutter/draggable_scrollable_sheet.dart index e121715fc7..a020b227b1 100644 --- a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_dyn.dart +++ b/lib/common/widgets/flutter/draggable_scrollable_sheet.dart @@ -2,27 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: uri_does_not_exist_in_doc_import, depend_on_referenced_packages - -/// @docImport 'package:flutter/material.dart'; -/// @docImport 'package:flutter_test/flutter_test.dart'; -/// -/// @docImport 'primary_scroll_controller.dart'; -/// @docImport 'scroll_configuration.dart'; -/// @docImport 'scroll_view.dart'; -/// @docImport 'scrollable.dart'; -/// @docImport 'single_child_scroll_view.dart'; -/// @docImport 'viewport.dart'; -library; +// ignore_for_file: prefer_initializing_formals import 'dart:math' as math; -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' - hide DraggableScrollableSheet, LayoutBuilder; +import 'package:flutter/material.dart' hide DraggableScrollableSheet; + +part 'package:PiliPlus/common/widgets/draggable_sheet/dyn.dart'; +part 'package:PiliPlus/common/widgets/draggable_sheet/topic.dart'; /// Controls a [DraggableScrollableSheet]. /// @@ -730,9 +720,7 @@ class _DraggableScrollableSheetState extends State { /// [_DraggableScrollableSheetScrollController] as the primary controller for /// descendants. class _DraggableScrollableSheetScrollController extends ScrollController { - _DraggableScrollableSheetScrollController({ - required this.extent, - }); + _DraggableScrollableSheetScrollController({required this.extent}); _DraggableSheetExtent extent; VoidCallback? onPositionDetached; @@ -807,6 +795,7 @@ class _DraggableScrollableSheetScrollPosition required super.context, super.oldPosition, required this.getExtent, + super.initialPixels, }); VoidCallback? _dragCancelCallback; @@ -817,8 +806,6 @@ class _DraggableScrollableSheetScrollPosition _DraggableSheetExtent get extent => getExtent(); - bool _isAtTop = true; - @override void absorb(ScrollPosition other) { super.absorb(other); @@ -846,9 +833,7 @@ class _DraggableScrollableSheetScrollPosition @override void applyUserOffset(double delta) { - if (!_isAtTop) { - super.applyUserOffset(delta); - } else if (!listShouldScroll && + if (!listShouldScroll && (!(extent.isAtMin || extent.isAtMax) || (extent.isAtMin && delta < 0) || (extent.isAtMax && delta > 0))) { @@ -883,10 +868,6 @@ class _DraggableScrollableSheetScrollPosition @override void goBallistic(double velocity) { - if (!_isAtTop) { - super.goBallistic(velocity); - return; - } if ((velocity == 0.0 && !_shouldSnap()) || (velocity < 0.0 && listShouldScroll) || (velocity > 0.0 && extent.isAtMax)) { @@ -964,71 +945,12 @@ class _DraggableScrollableSheetScrollPosition @override Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { - _isAtTop = pixels == 0; // Save this so we can call it later if we have to [goBallistic] on our own. _dragCancelCallback = dragCancelCallback; return super.drag(details, dragCancelCallback); } } -/// A widget that can notify a descendent [DraggableScrollableSheet] that it -/// should reset its position to the initial state. -/// -/// The [Scaffold] uses this widget to notify a persistent bottom sheet that -/// the user has tapped back if the sheet has started to cover more of the body -/// than when at its initial position. This is important for users of assistive -/// technology, where dragging may be difficult to communicate. -/// -/// This is just a wrapper on top of [DraggableScrollableController]. It is -/// primarily useful for controlling a sheet in a part of the widget tree that -/// the current code does not control (e.g. library code trying to affect a sheet -/// in library users' code). Generally, it's easier to control the sheet -/// directly by creating a controller and passing the controller to the sheet in -/// its constructor (see [DraggableScrollableSheet.controller]). -class DraggableScrollableActuator extends StatefulWidget { - /// Creates a widget that can notify descendent [DraggableScrollableSheet]s - /// to reset to their initial position. - /// - /// The [child] parameter is required. - const DraggableScrollableActuator({super.key, required this.child}); - - /// This child's [DraggableScrollableSheet] descendant will be reset when the - /// [reset] method is applied to a context that includes it. - final Widget child; - - /// Notifies any descendant [DraggableScrollableSheet] that it should reset - /// to its initial position. - /// - /// Returns `true` if a [DraggableScrollableActuator] is available and - /// some [DraggableScrollableSheet] is listening for updates, `false` - /// otherwise. - static bool reset(BuildContext context) { - final _InheritedResetNotifier? notifier = context - .dependOnInheritedWidgetOfExactType<_InheritedResetNotifier>(); - return notifier?._sendReset() ?? false; - } - - @override - State createState() => - _DraggableScrollableActuatorState(); -} - -class _DraggableScrollableActuatorState - extends State { - final _ResetNotifier _notifier = _ResetNotifier(); - - @override - Widget build(BuildContext context) { - return _InheritedResetNotifier(notifier: _notifier, child: widget.child); - } - - @override - void dispose() { - _notifier.dispose(); - super.dispose(); - } -} - /// A [ChangeNotifier] to use with [_InheritedResetNotifier] to notify /// descendants that they should reset to initial state. class _ResetNotifier extends ChangeNotifier { @@ -1064,6 +986,7 @@ class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> { required _ResetNotifier super.notifier, }); + // ignore: unused_element bool _sendReset() => notifier!.sendReset(); /// Specifies whether the [DraggableScrollableSheet] should reset to its diff --git a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart b/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart deleted file mode 100644 index 2c25288fc0..0000000000 --- a/lib/common/widgets/flutter/draggable_sheet/draggable_scrollable_sheet_topic.dart +++ /dev/null @@ -1,1178 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -// ignore_for_file: uri_does_not_exist_in_doc_import, depend_on_referenced_packages - -/// @docImport 'package:flutter/material.dart'; -/// @docImport 'package:flutter_test/flutter_test.dart'; -/// -/// @docImport 'primary_scroll_controller.dart'; -/// @docImport 'scroll_configuration.dart'; -/// @docImport 'scroll_view.dart'; -/// @docImport 'scrollable.dart'; -/// @docImport 'single_child_scroll_view.dart'; -/// @docImport 'viewport.dart'; -library; - -import 'dart:math' as math; - -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' - hide DraggableScrollableSheet, LayoutBuilder; - -/// Controls a [DraggableScrollableSheet]. -/// -/// Draggable scrollable controllers are typically stored as member variables in -/// [State] objects and are reused in each [State.build]. Controllers can only -/// be used to control one sheet at a time. A controller can be reused with a -/// new sheet if the previous sheet has been disposed. -/// -/// The controller's methods cannot be used until after the controller has been -/// passed into a [DraggableScrollableSheet] and the sheet has run initState. -/// -/// A [DraggableScrollableController] is a [Listenable]. It notifies its -/// listeners whenever an attached sheet changes sizes. It does not notify its -/// listeners when a sheet is first attached or when an attached sheet's -/// parameters change without affecting the sheet's current size. It does not -/// fire when [pixels] changes without [size] changing. For example, if the -/// constraints provided to an attached sheet change. -class DraggableScrollableController extends ChangeNotifier { - /// Creates a controller for [DraggableScrollableSheet]. - DraggableScrollableController() { - if (kFlutterMemoryAllocationsEnabled) { - ChangeNotifier.maybeDispatchObjectCreation(this); - } - } - - _DraggableScrollableSheetScrollController? _attachedController; - final Set _animationControllers = - {}; - - /// Get the current size (as a fraction of the parent height) of the attached sheet. - double get size { - _assertAttached(); - return _attachedController!.extent.currentSize; - } - - /// Get the current pixel height of the attached sheet. - double get pixels { - _assertAttached(); - return _attachedController!.extent.currentPixels; - } - - /// Convert a sheet's size (fractional value of parent container height) to pixels. - double sizeToPixels(double size) { - _assertAttached(); - return _attachedController!.extent.sizeToPixels(size); - } - - /// Returns Whether any [DraggableScrollableController] objects have attached themselves to the - /// [DraggableScrollableSheet]. - /// - /// If this is false, then members that interact with the [ScrollPosition], - /// such as [sizeToPixels], [size], [animateTo], and [jumpTo], must not be - /// called. - bool get isAttached => - _attachedController != null && _attachedController!.hasClients; - - /// Convert a sheet's pixel height to size (fractional value of parent container height). - double pixelsToSize(double pixels) { - _assertAttached(); - return _attachedController!.extent.pixelsToSize(pixels); - } - - /// Animates the attached sheet from its current size to the given [size], a - /// fractional value of the parent container's height. - /// - /// Any active sheet animation is canceled. If the sheet's internal scrollable - /// is currently animating (e.g. responding to a user fling), that animation is - /// canceled as well. - /// - /// An animation will be interrupted whenever the user attempts to scroll - /// manually, whenever another activity is started, or when the sheet hits its - /// max or min size (e.g. if you animate to 1 but the max size is .8, the - /// animation will stop playing when it reaches .8). - /// - /// The duration must not be zero. To jump to a particular value without an - /// animation, use [jumpTo]. - /// - /// The sheet will not snap after calling [animateTo] even if [DraggableScrollableSheet.snap] - /// is true. Snapping only occurs after user drags. - /// - /// When calling [animateTo] in widget tests, `await`ing the returned - /// [Future] may cause the test to hang and timeout. Instead, use - /// [WidgetTester.pumpAndSettle]. - Future animateTo( - double size, { - required Duration duration, - required Curve curve, - }) async { - _assertAttached(); - assert(size >= 0 && size <= 1); - assert(duration != Duration.zero); - final animationController = AnimationController.unbounded( - vsync: _attachedController!.position.context.vsync, - value: _attachedController!.extent.currentSize, - ); - _animationControllers.add(animationController); - _attachedController!.position.goIdle(); - // This disables any snapping until the next user interaction with the sheet. - _attachedController!.extent.hasDragged = false; - _attachedController!.extent.hasChanged = true; - _attachedController!.extent.startActivity( - onCanceled: () { - // Don't stop the controller if it's already finished and may have been disposed. - if (animationController.isAnimating) { - animationController.stop(); - } - }, - ); - animationController.addListener(() { - _attachedController!.extent.updateSize( - animationController.value, - _attachedController!.position.context.notificationContext!, - ); - }); - await animationController.animateTo( - clampDouble( - size, - _attachedController!.extent.minSize, - _attachedController!.extent.maxSize, - ), - duration: duration, - curve: curve, - ); - } - - /// Jumps the attached sheet from its current size to the given [size], a - /// fractional value of the parent container's height. - /// - /// If [size] is outside of a the attached sheet's min or max child size, - /// [jumpTo] will jump the sheet to the nearest valid size instead. - /// - /// Any active sheet animation is canceled. If the sheet's inner scrollable - /// is currently animating (e.g. responding to a user fling), that animation is - /// canceled as well. - /// - /// The sheet will not snap after calling [jumpTo] even if [DraggableScrollableSheet.snap] - /// is true. Snapping only occurs after user drags. - void jumpTo(double size) { - _assertAttached(); - assert(size >= 0 && size <= 1); - // Call start activity to interrupt any other playing activities. - _attachedController!.extent.startActivity(onCanceled: () {}); - _attachedController!.position.goIdle(); - _attachedController!.extent.hasDragged = false; - _attachedController!.extent.hasChanged = true; - _attachedController!.extent.updateSize( - size, - _attachedController!.position.context.notificationContext!, - ); - } - - /// Reset the attached sheet to its initial size (see: [DraggableScrollableSheet.initialChildSize]). - void reset() { - _assertAttached(); - _attachedController!.reset(); - } - - void _assertAttached() { - assert( - isAttached, - 'DraggableScrollableController is not attached to a sheet. A DraggableScrollableController ' - 'must be used in a DraggableScrollableSheet before any of its methods are called.', - ); - } - - void _attach(_DraggableScrollableSheetScrollController scrollController) { - assert( - _attachedController == null, - 'Draggable scrollable controller is already attached to a sheet.', - ); - _attachedController = scrollController; - _attachedController!.extent._currentSize.addListener(notifyListeners); - _attachedController!.onPositionDetached = _disposeAnimationControllers; - } - - void _onExtentReplaced(_DraggableSheetExtent previousExtent) { - // When the extent has been replaced, the old extent is already disposed and - // the controller will point to a new extent. We have to add our listener to - // the new extent. - _attachedController!.extent._currentSize.addListener(notifyListeners); - if (previousExtent.currentSize != _attachedController!.extent.currentSize) { - // The listener won't fire for a change in size between two extent - // objects so we have to fire it manually here. - notifyListeners(); - } - } - - void _detach({bool disposeExtent = false}) { - if (disposeExtent) { - _attachedController?.extent.dispose(); - } else { - _attachedController?.extent._currentSize.removeListener(notifyListeners); - } - _disposeAnimationControllers(); - _attachedController = null; - } - - void _disposeAnimationControllers() { - for (final AnimationController animationController - in _animationControllers) { - animationController.dispose(); - } - _animationControllers.clear(); - } -} - -/// A container for a [Scrollable] that responds to drag gestures by resizing -/// the scrollable until a limit is reached, and then scrolling. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=Hgw819mL_78} -/// -/// This widget can be dragged along the vertical axis between its -/// [minChildSize], which defaults to `0.25` and [maxChildSize], which defaults -/// to `1.0`. These sizes are percentages of the height of the parent container. -/// -/// The widget coordinates resizing and scrolling of the widget returned by -/// builder as the user drags along the horizontal axis. -/// -/// The widget will initially be displayed at its initialChildSize which -/// defaults to `0.5`, meaning half the height of its parent. Dragging will work -/// between the range of minChildSize and maxChildSize (as percentages of the -/// parent container's height) as long as the builder creates a widget which -/// uses the provided [ScrollController]. If the widget created by the -/// [ScrollableWidgetBuilder] does not use the provided [ScrollController], the -/// sheet will remain at the initialChildSize. -/// -/// By default, the widget will stay at whatever size the user drags it to. To -/// make the widget snap to specific sizes whenever they lift their finger -/// during a drag, set [snap] to `true`. The sheet will snap between -/// [minChildSize] and [maxChildSize]. Use [snapSizes] to add more sizes for -/// the sheet to snap between. -/// -/// The snapping effect is only applied on user drags. Programmatically -/// manipulating the sheet size via [DraggableScrollableController.animateTo] or -/// [DraggableScrollableController.jumpTo] will ignore [snap] and [snapSizes]. -/// -/// By default, the widget will expand its non-occupied area to fill available -/// space in the parent. If this is not desired, e.g. because the parent wants -/// to position sheet based on the space it is taking, the [expand] property -/// may be set to false. -/// -/// {@tool dartpad} -/// -/// This is a sample widget which shows a [ListView] that has 25 [ListTile]s. -/// It starts out as taking up half the body of the [Scaffold], and can be -/// dragged up to the full height of the scaffold or down to 25% of the height -/// of the scaffold. Upon reaching full height, the list contents will be -/// scrolled up or down, until they reach the top of the list again and the user -/// drags the sheet back down. -/// -/// On desktop and web running on desktop platforms, dragging to scroll with a mouse is disabled by default -/// to align with the natural behavior found in other desktop applications. -/// -/// This behavior is dictated by the [ScrollBehavior], and can be changed by adding -/// [PointerDeviceKind.mouse] to [ScrollBehavior.dragDevices]. -/// For more info on this, please refer to https://docs.flutter.dev/release/breaking-changes/default-scroll-behavior-drag -/// -/// Alternatively, this example illustrates how to add a drag handle for desktop applications. -/// -/// ** See code in examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart ** -/// {@end-tool} -class DraggableScrollableSheet extends StatefulWidget { - /// Creates a widget that can be dragged and scrolled in a single gesture. - const DraggableScrollableSheet({ - super.key, - this.initialChildSize = 0.5, - this.minChildSize = 0.25, - this.maxChildSize = 1.0, - this.expand = true, - this.snap = false, - this.snapSizes, - this.snapAnimationDuration, - this.controller, - this.shouldCloseOnMinExtent = true, - this.initialScrollOffset = 0, - required this.builder, - }) : assert(minChildSize >= 0.0), - assert(maxChildSize <= 1.0), - assert(minChildSize <= initialChildSize), - assert(initialChildSize <= maxChildSize), - assert( - snapAnimationDuration == null || snapAnimationDuration > Duration.zero, - ); - - final double initialScrollOffset; - - /// The initial fractional value of the parent container's height to use when - /// displaying the widget. - /// - /// Rebuilding the sheet with a new [initialChildSize] will only move - /// the sheet to the new value if the sheet has not yet been dragged since it - /// was first built or since the last call to [DraggableScrollableActuator.reset]. - /// - /// The default value is `0.5`. - final double initialChildSize; - - /// The minimum fractional value of the parent container's height to use when - /// displaying the widget. - /// - /// The default value is `0.25`. - final double minChildSize; - - /// The maximum fractional value of the parent container's height to use when - /// displaying the widget. - /// - /// The default value is `1.0`. - final double maxChildSize; - - /// Whether the widget should expand to fill the available space in its parent - /// or not. - /// - /// In most cases, this should be true. However, in the case of a parent - /// widget that will position this one based on its desired size (such as a - /// [Center]), this should be set to false. - /// - /// The default value is true. - final bool expand; - - /// Whether the widget should snap between [snapSizes] when the user lifts - /// their finger during a drag. - /// - /// If the user's finger was still moving when they lifted it, the widget will - /// snap to the next snap size (see [snapSizes]) in the direction of the drag. - /// If their finger was still, the widget will snap to the nearest snap size. - /// - /// Snapping is not applied when the sheet is programmatically moved by - /// calling [DraggableScrollableController.animateTo] or [DraggableScrollableController.jumpTo]. - /// - /// Rebuilding the sheet with snap newly enabled will immediately trigger a - /// snap unless the sheet has not yet been dragged away from - /// [initialChildSize] since first being built or since the last call to - /// [DraggableScrollableActuator.reset]. - final bool snap; - - /// A list of target sizes that the widget should snap to. - /// - /// Snap sizes are fractional values of the parent container's height. They - /// must be listed in increasing order and be between [minChildSize] and - /// [maxChildSize]. - /// - /// The [minChildSize] and [maxChildSize] are implicitly included in snap - /// sizes and do not need to be specified here. For example, `snapSizes = [.5]` - /// will result in a sheet that snaps between [minChildSize], `.5`, and - /// [maxChildSize]. - /// - /// Any modifications to the [snapSizes] list will not take effect until the - /// `build` function containing this widget is run again. - /// - /// Rebuilding with a modified or new list will trigger a snap unless the - /// sheet has not yet been dragged away from [initialChildSize] since first - /// being built or since the last call to [DraggableScrollableActuator.reset]. - final List? snapSizes; - - /// Defines a duration for the snap animations. - /// - /// If it's not set, then the animation duration is the distance to the snap - /// target divided by the velocity of the widget. - final Duration? snapAnimationDuration; - - /// A controller that can be used to programmatically control this sheet. - final DraggableScrollableController? controller; - - /// Whether the sheet, when dragged (or flung) to its minimum size, should - /// cause its parent sheet to close. - /// - /// Set on emitted [DraggableScrollableNotification]s. It is up to parent - /// classes to properly read and handle this value. - final bool shouldCloseOnMinExtent; - - /// The builder that creates a child to display in this widget, which will - /// use the provided [ScrollController] to enable dragging and scrolling - /// of the contents. - final ScrollableWidgetBuilder builder; - - @override - State createState() => - _DraggableScrollableSheetState(); -} - -/// Manages state between [_DraggableScrollableSheetState], -/// [_DraggableScrollableSheetScrollController], and -/// [_DraggableScrollableSheetScrollPosition]. -/// -/// The State knows the pixels available along the axis the widget wants to -/// scroll, but expects to get a fraction of those pixels to render the sheet. -/// -/// The ScrollPosition knows the number of pixels a user wants to move the sheet. -/// -/// The [currentSize] will never be null. -/// The [availablePixels] will never be null, but may be `double.infinity`. -class _DraggableSheetExtent { - _DraggableSheetExtent({ - required this.minSize, - required this.maxSize, - required this.snap, - required this.snapSizes, - required this.initialSize, - this.snapAnimationDuration, - ValueNotifier? currentSize, - bool? hasDragged, - bool? hasChanged, - this.shouldCloseOnMinExtent = true, - }) : assert(minSize >= 0), - assert(maxSize <= 1), - assert(minSize <= initialSize), - assert(initialSize <= maxSize), - _currentSize = currentSize ?? ValueNotifier(initialSize), - availablePixels = double.infinity, - hasDragged = hasDragged ?? false, - hasChanged = hasChanged ?? false { - assert(debugMaybeDispatchCreated('widgets', '_DraggableSheetExtent', this)); - } - - VoidCallback? _cancelActivity; - - final double minSize; - final double maxSize; - final bool snap; - final List snapSizes; - final Duration? snapAnimationDuration; - final double initialSize; - final bool shouldCloseOnMinExtent; - final ValueNotifier _currentSize; - double availablePixels; - - // Used to disable snapping until the user has dragged on the sheet. - bool hasDragged; - - // Used to determine if the sheet should move to a new initial size when it - // changes. - // We need both `hasChanged` and `hasDragged` to achieve the following - // behavior: - // 1. The sheet should only snap following user drags (as opposed to - // programmatic sheet changes). See docs for `animateTo` and `jumpTo`. - // 2. The sheet should move to a new initial child size on rebuild iff the - // sheet has not changed, either by drag or programmatic control. See - // docs for `initialChildSize`. - bool hasChanged; - - bool get isAtMin => minSize >= _currentSize.value; - bool get isAtMax => maxSize <= _currentSize.value; - - double get currentSize => _currentSize.value; - double get currentPixels => sizeToPixels(_currentSize.value); - - List get pixelSnapSizes => snapSizes.map(sizeToPixels).toList(); - - /// Start an activity that affects the sheet and register a cancel call back - /// that will be called if another activity starts. - /// - /// The `onCanceled` callback will get called even if the subsequent activity - /// started after this one finished, so `onCanceled` must be safe to call at - /// any time. - void startActivity({required VoidCallback onCanceled}) { - _cancelActivity?.call(); - _cancelActivity = onCanceled; - } - - /// The scroll position gets inputs in terms of pixels, but the size is - /// expected to be expressed as a number between 0..1. - /// - /// This should only be called to respond to a user drag. To update the - /// size in response to a programmatic call, use [updateSize] directly. - void addPixelDelta(double delta, BuildContext context) { - // Stop any playing sheet animations. - _cancelActivity?.call(); - _cancelActivity = null; - // The user has interacted with the sheet, set `hasDragged` to true so that - // we'll snap if applicable. - hasDragged = true; - hasChanged = true; - if (availablePixels == 0) { - return; - } - updateSize(currentSize + pixelsToSize(delta), context); - } - - /// Set the size to the new value. [newSize] should be a number between - /// [minSize] and [maxSize]. - /// - /// This can be triggered by a programmatic (e.g. controller triggered) change - /// or a user drag. - void updateSize(double newSize, BuildContext context) { - final double clampedSize = clampDouble(newSize, minSize, maxSize); - if (_currentSize.value == clampedSize) { - return; - } - _currentSize.value = clampedSize; - DraggableScrollableNotification( - minExtent: minSize, - maxExtent: maxSize, - extent: currentSize, - initialExtent: initialSize, - context: context, - shouldCloseOnMinExtent: shouldCloseOnMinExtent, - ).dispatch(context); - } - - double pixelsToSize(double pixels) { - return pixels / availablePixels * maxSize; - } - - double sizeToPixels(double size) { - return size / maxSize * availablePixels; - } - - void dispose() { - assert(debugMaybeDispatchDisposed(this)); - _currentSize.dispose(); - } - - _DraggableSheetExtent copyWith({ - required double minSize, - required double maxSize, - required bool snap, - required List snapSizes, - required double initialSize, - required Duration? snapAnimationDuration, - required bool shouldCloseOnMinExtent, - }) { - return _DraggableSheetExtent( - minSize: minSize, - maxSize: maxSize, - snap: snap, - snapSizes: snapSizes, - snapAnimationDuration: snapAnimationDuration, - initialSize: initialSize, - // Set the current size to the possibly updated initial size if the sheet - // hasn't changed yet. - currentSize: ValueNotifier( - hasChanged - ? clampDouble(_currentSize.value, minSize, maxSize) - : initialSize, - ), - hasDragged: hasDragged, - hasChanged: hasChanged, - shouldCloseOnMinExtent: shouldCloseOnMinExtent, - ); - } -} - -class _DraggableScrollableSheetState extends State { - late _DraggableScrollableSheetScrollController _scrollController; - late _DraggableSheetExtent _extent; - - @override - void initState() { - super.initState(); - _extent = _DraggableSheetExtent( - minSize: widget.minChildSize, - maxSize: widget.maxChildSize, - snap: widget.snap, - snapSizes: _impliedSnapSizes(), - snapAnimationDuration: widget.snapAnimationDuration, - initialSize: widget.initialChildSize, - shouldCloseOnMinExtent: widget.shouldCloseOnMinExtent, - ); - _scrollController = _DraggableScrollableSheetScrollController( - extent: _extent, - initialScrollOffset: widget.initialScrollOffset, - ); - widget.controller?._attach(_scrollController); - } - - List _impliedSnapSizes() { - for (var index = 0; index < (widget.snapSizes?.length ?? 0); index += 1) { - final double snapSize = widget.snapSizes![index]; - assert( - snapSize >= widget.minChildSize && snapSize <= widget.maxChildSize, - '${_snapSizeErrorMessage(index)}\nSnap sizes must be between `minChildSize` and `maxChildSize`. ', - ); - assert( - index == 0 || snapSize > widget.snapSizes![index - 1], - '${_snapSizeErrorMessage(index)}\nSnap sizes must be in ascending order. ', - ); - } - // Ensure the snap sizes start and end with the min and max child sizes. - if (widget.snapSizes == null || widget.snapSizes!.isEmpty) { - return [widget.minChildSize, widget.maxChildSize]; - } - return [ - if (widget.snapSizes!.first != widget.minChildSize) widget.minChildSize, - ...widget.snapSizes!, - if (widget.snapSizes!.last != widget.maxChildSize) widget.maxChildSize, - ]; - } - - @override - void didUpdateWidget(covariant DraggableScrollableSheet oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.controller != oldWidget.controller) { - oldWidget.controller?._detach(); - widget.controller?._attach(_scrollController); - } - _replaceExtent(oldWidget); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - if (_InheritedResetNotifier.shouldReset(context)) { - _scrollController.reset(); - } - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: _extent._currentSize, - builder: (BuildContext context, double currentSize, Widget? child) => - LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - _extent.availablePixels = - widget.maxChildSize * constraints.biggest.height; - final Widget sheet = FractionallySizedBox( - heightFactor: currentSize, - alignment: Alignment.bottomCenter, - child: child, - ); - return widget.expand ? SizedBox.expand(child: sheet) : sheet; - }, - ), - child: widget.builder(context, _scrollController), - ); - } - - @override - void dispose() { - if (widget.controller == null) { - _extent.dispose(); - } else { - widget.controller!._detach(disposeExtent: true); - } - _scrollController.dispose(); - super.dispose(); - } - - void _replaceExtent(covariant DraggableScrollableSheet oldWidget) { - final _DraggableSheetExtent previousExtent = _extent; - _extent = previousExtent.copyWith( - minSize: widget.minChildSize, - maxSize: widget.maxChildSize, - snap: widget.snap, - snapSizes: _impliedSnapSizes(), - snapAnimationDuration: widget.snapAnimationDuration, - initialSize: widget.initialChildSize, - shouldCloseOnMinExtent: widget.shouldCloseOnMinExtent, - ); - // Modify the existing scroll controller instead of replacing it so that - // developers listening to the controller do not have to rebuild their listeners. - _scrollController.extent = _extent; - // If an external facing controller was provided, let it know that the - // extent has been replaced. - widget.controller?._onExtentReplaced(previousExtent); - previousExtent.dispose(); - if (widget.snap && - (widget.snap != oldWidget.snap || - widget.snapSizes != oldWidget.snapSizes) && - _scrollController.hasClients) { - // Trigger a snap in case snap or snapSizes has changed and there is a - // scroll position currently attached. We put this in a post frame - // callback so that `build` can update `_extent.availablePixels` before - // this runs-we can't use the previous extent's available pixels as it may - // have changed when the widget was updated. - WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { - for ( - var index = 0; - index < _scrollController.positions.length; - index++ - ) { - final position = - _scrollController.positions.elementAt(index) - as _DraggableScrollableSheetScrollPosition; - position.goBallistic(0); - } - }, debugLabel: 'DraggableScrollableSheet.snap'); - } - } - - String _snapSizeErrorMessage(int invalidIndex) { - final List snapSizesWithIndicator = widget.snapSizes! - .asMap() - .keys - .map((int index) { - final snapSizeString = widget.snapSizes![index].toString(); - if (index == invalidIndex) { - return '>>> $snapSizeString <<<'; - } - return snapSizeString; - }) - .toList(); - return "Invalid snapSize '${widget.snapSizes![invalidIndex]}' at index $invalidIndex of:\n" - ' $snapSizesWithIndicator'; - } -} - -/// A [ScrollController] suitable for use in a [ScrollableWidgetBuilder] created -/// by a [DraggableScrollableSheet]. -/// -/// If a [DraggableScrollableSheet] contains content that is exceeds the height -/// of its container, this controller will allow the sheet to both be dragged to -/// fill the container and then scroll the child content. -/// -/// See also: -/// -/// * [_DraggableScrollableSheetScrollPosition], which manages the positioning logic for -/// this controller. -/// * [PrimaryScrollController], which can be used to establish a -/// [_DraggableScrollableSheetScrollController] as the primary controller for -/// descendants. -class _DraggableScrollableSheetScrollController extends ScrollController { - _DraggableScrollableSheetScrollController({ - required this.extent, - double initialScrollOffset = 0.0, - }) : _initialScrollOffset = initialScrollOffset; - - _DraggableSheetExtent extent; - VoidCallback? onPositionDetached; - - @override - double get initialScrollOffset => _initialScrollOffset; - final double _initialScrollOffset; - - @override - _DraggableScrollableSheetScrollPosition createScrollPosition( - ScrollPhysics physics, - ScrollContext context, - ScrollPosition? oldPosition, - ) { - return _DraggableScrollableSheetScrollPosition( - physics: physics.applyTo(const AlwaysScrollableScrollPhysics()), - context: context, - oldPosition: oldPosition, - getExtent: () => extent, - initialPixels: _initialScrollOffset, - ); - } - - @override - void debugFillDescription(List description) { - super.debugFillDescription(description); - description.add('extent: $extent'); - } - - @override - _DraggableScrollableSheetScrollPosition get position => - super.position as _DraggableScrollableSheetScrollPosition; - - void reset() { - extent._cancelActivity?.call(); - extent.hasDragged = false; - extent.hasChanged = false; - // jumpTo can result in trying to replace semantics during build. - // Just animate really fast. - // Avoid doing it at all if the offset is already 0.0. - if (offset != 0.0) { - animateTo( - 0.0, - duration: const Duration(milliseconds: 1), - curve: Curves.linear, - ); - } - extent.updateSize( - extent.initialSize, - position.context.notificationContext!, - ); - } - - @override - void detach(ScrollPosition position) { - onPositionDetached?.call(); - super.detach(position); - } -} - -/// A scroll position that manages scroll activities for -/// [_DraggableScrollableSheetScrollController]. -/// -/// This class is a concrete subclass of [ScrollPosition] logic that handles a -/// single [ScrollContext], such as a [Scrollable]. An instance of this class -/// manages [ScrollActivity] instances, which changes the -/// [_DraggableSheetExtent.currentSize] or visible content offset in the -/// [Scrollable]'s [Viewport] -/// -/// See also: -/// -/// * [_DraggableScrollableSheetScrollController], which uses this as its [ScrollPosition]. -class _DraggableScrollableSheetScrollPosition - extends ScrollPositionWithSingleContext { - _DraggableScrollableSheetScrollPosition({ - required super.physics, - required super.context, - super.oldPosition, - required this.getExtent, - super.initialPixels, - }); - - VoidCallback? _dragCancelCallback; - final _DraggableSheetExtent Function() getExtent; - final Set _ballisticControllers = - {}; - bool get listShouldScroll => pixels > 0.0 && extent.isAtMax; - - _DraggableSheetExtent get extent => getExtent(); - - @override - void absorb(ScrollPosition other) { - super.absorb(other); - assert(_dragCancelCallback == null); - - if (other is! _DraggableScrollableSheetScrollPosition) { - return; - } - - if (other._dragCancelCallback != null) { - _dragCancelCallback = other._dragCancelCallback; - other._dragCancelCallback = null; - } - } - - @override - void beginActivity(ScrollActivity? newActivity) { - // Cancel the running ballistic simulations - for (final AnimationController ballisticController - in _ballisticControllers) { - ballisticController.stop(); - } - super.beginActivity(newActivity); - } - - @override - void applyUserOffset(double delta) { - if (!listShouldScroll && - (!(extent.isAtMin || extent.isAtMax) || - (extent.isAtMin && delta < 0) || - (extent.isAtMax && delta > 0))) { - extent.addPixelDelta(-delta, context.notificationContext!); - } else { - super.applyUserOffset(delta); - } - } - - // Checks if the sheet's current size is close to a snap size, returning the - // snap size if so; returns null otherwise. - double? _getCurrentSnapSize() { - return extent.snapSizes.firstWhereOrNull((double snapSize) { - return (extent.currentSize - snapSize).abs() <= - extent.pixelsToSize(physics.toleranceFor(this).distance); - }); - } - - bool _isAtSnapSize() => _getCurrentSnapSize() != null; - - bool _shouldSnap() => extent.snap && extent.hasDragged && !_isAtSnapSize(); - - @override - void dispose() { - for (final AnimationController ballisticController - in _ballisticControllers) { - ballisticController.dispose(); - } - _ballisticControllers.clear(); - super.dispose(); - } - - @override - void goBallistic(double velocity) { - if ((velocity == 0.0 && !_shouldSnap()) || - (velocity < 0.0 && listShouldScroll) || - (velocity > 0.0 && extent.isAtMax)) { - super.goBallistic(velocity); - return; - } - // Scrollable expects that we will dispose of its current _dragCancelCallback - _dragCancelCallback?.call(); - _dragCancelCallback = null; - - late final Simulation simulation; - if (extent.snap) { - // Snap is enabled, simulate snapping instead of clamping scroll. - simulation = _SnappingSimulation( - position: extent.currentPixels, - initialVelocity: velocity, - pixelSnapSize: extent.pixelSnapSizes, - snapAnimationDuration: extent.snapAnimationDuration, - tolerance: physics.toleranceFor(this), - ); - } else { - // The iOS bouncing simulation just isn't right here - once we delegate - // the ballistic back to the ScrollView, it will use the right simulation. - simulation = ClampingScrollSimulation( - // Run the simulation in terms of pixels, not extent. - position: extent.currentPixels, - velocity: velocity, - tolerance: physics.toleranceFor(this), - ); - } - - final ballisticController = AnimationController.unbounded( - debugLabel: objectRuntimeType(this, '_DraggableScrollableSheetPosition'), - vsync: context.vsync, - ); - _ballisticControllers.add(ballisticController); - - double lastPosition = extent.currentPixels; - void tick() { - final double delta = ballisticController.value - lastPosition; - lastPosition = ballisticController.value; - extent.addPixelDelta(delta, context.notificationContext!); - if ((velocity > 0 && extent.isAtMax) || - (velocity < 0 && extent.isAtMin)) { - // Make sure we pass along enough velocity to keep scrolling - otherwise - // we just "bounce" off the top making it look like the list doesn't - // have more to scroll. - velocity = - ballisticController.velocity + - (physics.toleranceFor(this).velocity * - ballisticController.velocity.sign); - super.goBallistic(velocity); - ballisticController.stop(); - } else if (ballisticController.isCompleted) { - // Update the extent value after the snap animation completes to - // avoid rounding errors that could prevent the sheet from closing when - // it reaches minSize. - final double? snapSize = _getCurrentSnapSize(); - if (snapSize != null) { - extent.updateSize(snapSize, context.notificationContext!); - } - super.goBallistic(0); - } - } - - ballisticController - ..addListener(tick) - ..animateWith(simulation).whenCompleteOrCancel(() { - if (_ballisticControllers.contains(ballisticController)) { - _ballisticControllers.remove(ballisticController); - ballisticController.dispose(); - } - }); - } - - @override - Drag drag(DragStartDetails details, VoidCallback dragCancelCallback) { - // Save this so we can call it later if we have to [goBallistic] on our own. - _dragCancelCallback = dragCancelCallback; - return super.drag(details, dragCancelCallback); - } -} - -/// A widget that can notify a descendent [DraggableScrollableSheet] that it -/// should reset its position to the initial state. -/// -/// The [Scaffold] uses this widget to notify a persistent bottom sheet that -/// the user has tapped back if the sheet has started to cover more of the body -/// than when at its initial position. This is important for users of assistive -/// technology, where dragging may be difficult to communicate. -/// -/// This is just a wrapper on top of [DraggableScrollableController]. It is -/// primarily useful for controlling a sheet in a part of the widget tree that -/// the current code does not control (e.g. library code trying to affect a sheet -/// in library users' code). Generally, it's easier to control the sheet -/// directly by creating a controller and passing the controller to the sheet in -/// its constructor (see [DraggableScrollableSheet.controller]). -class DraggableScrollableActuator extends StatefulWidget { - /// Creates a widget that can notify descendent [DraggableScrollableSheet]s - /// to reset to their initial position. - /// - /// The [child] parameter is required. - const DraggableScrollableActuator({super.key, required this.child}); - - /// This child's [DraggableScrollableSheet] descendant will be reset when the - /// [reset] method is applied to a context that includes it. - final Widget child; - - /// Notifies any descendant [DraggableScrollableSheet] that it should reset - /// to its initial position. - /// - /// Returns `true` if a [DraggableScrollableActuator] is available and - /// some [DraggableScrollableSheet] is listening for updates, `false` - /// otherwise. - static bool reset(BuildContext context) { - final _InheritedResetNotifier? notifier = context - .dependOnInheritedWidgetOfExactType<_InheritedResetNotifier>(); - return notifier?._sendReset() ?? false; - } - - @override - State createState() => - _DraggableScrollableActuatorState(); -} - -class _DraggableScrollableActuatorState - extends State { - final _ResetNotifier _notifier = _ResetNotifier(); - - @override - Widget build(BuildContext context) { - return _InheritedResetNotifier(notifier: _notifier, child: widget.child); - } - - @override - void dispose() { - _notifier.dispose(); - super.dispose(); - } -} - -/// A [ChangeNotifier] to use with [_InheritedResetNotifier] to notify -/// descendants that they should reset to initial state. -class _ResetNotifier extends ChangeNotifier { - _ResetNotifier() { - if (kFlutterMemoryAllocationsEnabled) { - ChangeNotifier.maybeDispatchObjectCreation(this); - } - } - - /// Whether someone called [sendReset] or not. - /// - /// This flag should be reset after checking it. - bool _wasCalled = false; - - /// Fires a reset notification to descendants. - /// - /// Returns false if there are no listeners. - bool sendReset() { - if (!hasListeners) { - return false; - } - _wasCalled = true; - notifyListeners(); - return true; - } -} - -class _InheritedResetNotifier extends InheritedNotifier<_ResetNotifier> { - /// Creates an [InheritedNotifier] that the [DraggableScrollableSheet] will - /// listen to for an indication that it should reset itself back to [DraggableScrollableSheet.initialChildSize]. - const _InheritedResetNotifier({ - required super.child, - required _ResetNotifier super.notifier, - }); - - bool _sendReset() => notifier!.sendReset(); - - /// Specifies whether the [DraggableScrollableSheet] should reset to its - /// initial position. - /// - /// Returns true if the notifier requested a reset, false otherwise. - static bool shouldReset(BuildContext context) { - final InheritedWidget? widget = context - .dependOnInheritedWidgetOfExactType<_InheritedResetNotifier>(); - if (widget == null) { - return false; - } - assert(widget is _InheritedResetNotifier); - final inheritedNotifier = widget as _InheritedResetNotifier; - final bool wasCalled = inheritedNotifier.notifier!._wasCalled; - inheritedNotifier.notifier!._wasCalled = false; - return wasCalled; - } -} - -class _SnappingSimulation extends Simulation { - _SnappingSimulation({ - required this.position, - required double initialVelocity, - required List pixelSnapSize, - Duration? snapAnimationDuration, - super.tolerance, - }) { - _pixelSnapSize = _getSnapSize(initialVelocity, pixelSnapSize); - - if (snapAnimationDuration != null && - snapAnimationDuration.inMilliseconds > 0) { - velocity = - (_pixelSnapSize - position) * - 1000 / - snapAnimationDuration.inMilliseconds; - } - // Check the direction of the target instead of the sign of the velocity because - // we may snap in the opposite direction of velocity if velocity is very low. - else if (_pixelSnapSize < position) { - velocity = math.min(-minimumSpeed, initialVelocity); - } else { - velocity = math.max(minimumSpeed, initialVelocity); - } - } - - final double position; - late final double velocity; - - // A minimum speed to snap at. Used to ensure that the snapping animation - // does not play too slowly. - static const double minimumSpeed = 1600.0; - - late final double _pixelSnapSize; - - @override - double dx(double time) { - if (isDone(time)) { - return 0; - } - return velocity; - } - - @override - bool isDone(double time) { - return x(time) == _pixelSnapSize; - } - - @override - double x(double time) { - final double newPosition = position + velocity * time; - if ((velocity >= 0 && newPosition > _pixelSnapSize) || - (velocity < 0 && newPosition < _pixelSnapSize)) { - // We're passed the snap size, return it instead. - return _pixelSnapSize; - } - return newPosition; - } - - // Find the two closest snap sizes to the position. If the velocity is - // non-zero, select the size in the velocity's direction. Otherwise, - // the nearest snap size. - double _getSnapSize(double initialVelocity, List pixelSnapSizes) { - final int indexOfNextSize = pixelSnapSizes.indexWhere( - (double size) => size >= position, - ); - if (indexOfNextSize == 0) { - return pixelSnapSizes.first; - } - final double nextSize = pixelSnapSizes[indexOfNextSize]; - // If already snapped - keep this as target size - if (nextSize == position) { - return nextSize; - } - final double previousSize = pixelSnapSizes[indexOfNextSize - 1]; - if (initialVelocity.abs() <= tolerance.velocity) { - // If velocity is zero, snap to the nearest snap size with the minimum velocity. - if (position - previousSize < nextSize - position) { - return previousSize; - } else { - return nextSize; - } - } - // Snap forward or backward depending on current velocity. - if (initialVelocity < 0.0) { - return pixelSnapSizes[indexOfNextSize - 1]; - } - return pixelSnapSizes[indexOfNextSize]; - } -} diff --git a/lib/common/widgets/flutter/layout_builder.dart b/lib/common/widgets/flutter/layout_builder.dart deleted file mode 100644 index af7a7a7352..0000000000 --- a/lib/common/widgets/flutter/layout_builder.dart +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/foundation.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; - -/// An abstract superclass for widgets that defer their building until layout. -/// -/// Similar to the [Builder] widget except that the implementation calls the [builder] -/// function at layout time and provides the [LayoutInfoType] that is required to -/// configure the child widget subtree. -/// -/// This is useful when the child widget tree relies on information that are only -/// available during layout, and doesn't depend on the child's intrinsic size. -/// -/// The [LayoutInfoType] should typically be immutable. The equality of the -/// [LayoutInfoType] type is used by the implementation to avoid unnecessary -/// rebuilds: if the new [LayoutInfoType] computed during layout is the same as -/// (defined by `LayoutInfoType.==`) the previous [LayoutInfoType], the -/// implementation will try to avoid calling the [builder] again unless -/// [updateShouldRebuild] returns true. The corresponding [RenderObject] produced -/// by this widget retains the most up-to-date [LayoutInfoType] for this purpose, -/// which may keep a [LayoutInfoType] object in memory until the widget is removed -/// from the tree. -/// -/// Subclasses must return a [RenderObject] that mixes in [RenderAbstractLayoutBuilderMixin]. -abstract class AbstractLayoutBuilder - extends RenderObjectWidget { - /// Creates a widget that defers its building until layout. - const AbstractLayoutBuilder({super.key}); - - /// Called at layout time to construct the widget tree. - /// - /// The builder must not return null. - Widget Function(BuildContext context, LayoutInfoType layoutInfo) get builder; - - @override - RenderObjectElement createElement() => - _LayoutBuilderElement(this); - - /// Whether [builder] needs to be called again even if the layout constraints - /// are the same. - /// - /// When this widget's configuration is updated, the [builder] callback most - /// likely needs to be called to build this widget's child. However, - /// subclasses may provide ways in which the widget can be updated without - /// needing to rebuild the child. Such subclasses can use this method to tell - /// the framework when the child widget should be rebuilt. - /// - /// When this method is called by the framework, the newly configured widget - /// is asked if it requires a rebuild, and it is passed the old widget as a - /// parameter. - /// - /// See also: - /// - /// * [State.setState] and [State.didUpdateWidget], which talk about widget - /// configuration changes and how they're triggered. - /// * [Element.update], the method that actually updates the widget's - /// configuration. - @protected - bool updateShouldRebuild( - covariant AbstractLayoutBuilder oldWidget, - ) => true; - - @override - RenderAbstractLayoutBuilderMixin - createRenderObject( - BuildContext context, - ); - - // updateRenderObject is redundant with the logic in the LayoutBuilderElement below. -} - -/// A specialized [AbstractLayoutBuilder] whose widget subtree depends on the -/// incoming [ConstraintType] that will be imposed on the widget. -/// -/// {@template flutter.widgets.ConstrainedLayoutBuilder} -/// The [builder] function is called in the following situations: -/// -/// * The first time the widget is laid out. -/// * When the parent widget passes different layout constraints. -/// * When the parent widget updates this widget and [updateShouldRebuild] returns `true`. -/// * When the dependencies that the [builder] function subscribes to change. -/// -/// The [builder] function is _not_ called during layout if the parent passes -/// the same constraints repeatedly. -/// -/// In the event that an ancestor skips the layout of this subtree so the -/// constraints become outdated, the `builder` rebuilds with the last known -/// constraints. -/// {@endtemplate} -abstract class ConstrainedLayoutBuilder - extends AbstractLayoutBuilder { - /// Creates a widget that defers its building until layout. - const ConstrainedLayoutBuilder({super.key, required this.builder}); - - @override - final Widget Function(BuildContext context, ConstraintType constraints) - builder; -} - -class _LayoutBuilderElement extends RenderObjectElement { - _LayoutBuilderElement(AbstractLayoutBuilder super.widget); - - @override - RenderAbstractLayoutBuilderMixin - get renderObject => - super.renderObject - as RenderAbstractLayoutBuilderMixin; - - Element? _child; - - // @override - // BuildScope get buildScope => _buildScope; - - // late final BuildScope _buildScope = BuildScope( - // scheduleRebuild: _scheduleRebuild, - // ); - - // To schedule a rebuild, markNeedsLayout needs to be called on this Element's - // render object (as the rebuilding is done in its performLayout call). However, - // the render tree should typically be kept clean during the postFrameCallbacks - // and the idle phase, so the layout data can be safely read. - // bool _deferredCallbackScheduled = false; - // void _scheduleRebuild() { - // if (_deferredCallbackScheduled) { - // return; - // } - - // final bool deferMarkNeedsLayout = - // switch (SchedulerBinding.instance.schedulerPhase) { - // SchedulerPhase.idle || SchedulerPhase.postFrameCallbacks => true, - // SchedulerPhase.transientCallbacks || - // SchedulerPhase.midFrameMicrotasks || - // SchedulerPhase.persistentCallbacks => false, - // }; - // if (!deferMarkNeedsLayout) { - // renderObject.scheduleLayoutCallback(); - // return; - // } - // _deferredCallbackScheduled = true; - // SchedulerBinding.instance.scheduleFrameCallback(_frameCallback); - // } - - // void _frameCallback(Duration timestamp) { - // _deferredCallbackScheduled = false; - // // This method is only called when the render tree is stable, if the Element - // // is deactivated it will never be reincorporated back to the tree. - // if (mounted) { - // renderObject.scheduleLayoutCallback(); - // } - // } - - @override - void visitChildren(ElementVisitor visitor) { - if (_child != null) { - visitor(_child!); - } - } - - @override - void forgetChild(Element child) { - assert(child == _child); - _child = null; - super.forgetChild(child); - } - - @override - void mount(Element? parent, Object? newSlot) { - super.mount(parent, newSlot); // Creates the renderObject. - renderObject._updateCallback(_rebuildWithConstraints); - } - - @override - void update(AbstractLayoutBuilder newWidget) { - assert(widget != newWidget); - final oldWidget = widget as AbstractLayoutBuilder; - super.update(newWidget); - assert(widget == newWidget); - - renderObject._updateCallback(_rebuildWithConstraints); - if (newWidget.updateShouldRebuild(oldWidget)) { - _needsBuild = true; - renderObject.scheduleLayoutCallback(); - } - } - - @override - void markNeedsBuild() { - // Calling super.markNeedsBuild is not needed. This Element does not need - // to performRebuild since this call already does what performRebuild does, - // So the element is clean as soon as this method returns and does not have - // to be added to the dirty list or marked as dirty. - renderObject.scheduleLayoutCallback(); - _needsBuild = true; - } - - @override - void performRebuild() { - // This gets called if markNeedsBuild() is called on us. - // That might happen if, e.g., our builder uses Inherited widgets. - - // Force the callback to be called, even if the layout constraints are the - // same. This is because that callback may depend on the updated widget - // configuration, or an inherited widget. - renderObject.scheduleLayoutCallback(); - _needsBuild = true; - super - .performRebuild(); // Calls widget.updateRenderObject (a no-op in this case). - } - - @override - void unmount() { - renderObject._callback = null; - super.unmount(); - } - - // The LayoutInfoType that was used to invoke the layout callback with last time, - // during layout. The `_previousLayoutInfo` value is compared to the new one - // to determine whether [LayoutBuilderBase.builder] needs to be called. - LayoutInfoType? _previousLayoutInfo; - bool _needsBuild = true; - - void _rebuildWithConstraints(Constraints _) { - final LayoutInfoType layoutInfo = renderObject.layoutInfo; - @pragma('vm:notify-debugger-on-exception') - void updateChildCallback() { - Widget built; - try { - assert(layoutInfo == renderObject.layoutInfo); - built = (widget as AbstractLayoutBuilder).builder( - this, - layoutInfo, - ); - debugWidgetBuilderValue(widget, built); - } catch (e, stack) { - built = ErrorWidget.builder( - _reportException( - ErrorDescription('building $widget'), - e, - stack, - informationCollector: () => [ - if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)), - ], - ), - ); - } - try { - _child = updateChild(_child, built, null); - assert(_child != null); - } catch (e, stack) { - built = ErrorWidget.builder( - _reportException( - ErrorDescription('building $widget'), - e, - stack, - informationCollector: () => [ - if (kDebugMode) DiagnosticsDebugCreator(DebugCreator(this)), - ], - ), - ); - _child = updateChild(null, built, slot); - } finally { - _needsBuild = false; - _previousLayoutInfo = layoutInfo; - } - } - - final VoidCallback? callback = - _needsBuild || (layoutInfo != _previousLayoutInfo) - ? updateChildCallback - : null; - owner!.buildScope(this, callback); - } - - @override - void insertRenderObjectChild(RenderObject child, Object? slot) { - final RenderObjectWithChildMixin renderObject = - this.renderObject; - assert(slot == null); - assert(renderObject.debugValidateChild(child)); - renderObject.child = child; - assert(renderObject == this.renderObject); - } - - @override - void moveRenderObjectChild( - RenderObject child, - Object? oldSlot, - Object? newSlot, - ) { - assert(false); - } - - @override - void removeRenderObjectChild(RenderObject child, Object? slot) { - final RenderAbstractLayoutBuilderMixin - renderObject = this.renderObject; - assert(renderObject.child == child); - renderObject.child = null; - assert(renderObject == this.renderObject); - } -} - -/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with -/// the the same `LayoutInfoType`. -/// -/// Provides a [layoutCallback] implementation which, if needed, invokes -/// [AbstractLayoutBuilder]'s builder callback. -/// -/// Implementers can override the [layoutInfo] implementation with a value -/// that is safe to access in [layoutCallback], which is called in -/// [performLayout]. The default [layoutInfo] returns the incoming -/// [Constraints]. -/// -/// This mixin replaces [RenderConstrainedLayoutBuilder]. -mixin RenderAbstractLayoutBuilderMixin< - LayoutInfoType, - ChildType extends RenderObject -> - on - RenderObjectWithChildMixin, - RenderObjectWithLayoutCallbackMixin { - LayoutCallback? _callback; - - /// Change the layout callback. - void _updateCallback(LayoutCallback value) { - if (value == _callback) { - return; - } - _callback = value; - scheduleLayoutCallback(); - } - - /// Invokes the builder callback supplied via [AbstractLayoutBuilder] and - /// rebuilds the [AbstractLayoutBuilder]'s widget tree, if needed. - /// - /// No further work will be done if [layoutInfo] has not changed since the last - /// time this method was called, and [AbstractLayoutBuilder.updateShouldRebuild] - /// returned `false` when the widget was rebuilt. - /// - /// This method should typically be called as soon as possible in the class's - /// [performLayout] implementation, before any layout work is done. - @visibleForOverriding - @override - void layoutCallback() => _callback!(constraints); - - /// The information to invoke the [AbstractLayoutBuilder.builder] callback with. - /// - /// This is typically the information that are only made available in - /// [performLayout], which is inaccessible for regular [Builder] widget, - /// such as the incoming [Constraints], which are the default value. - @protected - LayoutInfoType get layoutInfo => constraints as LayoutInfoType; -} - -/// Generic mixin for [RenderObject]s created by an [AbstractLayoutBuilder] with -/// the the same `LayoutInfoType`. -/// -/// Use [RenderAbstractLayoutBuilderMixin] instead, which replaces this mixin. -typedef RenderConstrainedLayoutBuilder< - LayoutInfoType, - ChildType extends RenderObject -> = RenderAbstractLayoutBuilderMixin; - -/// Builds a widget tree that can depend on the parent widget's size. -/// -/// Similar to the [Builder] widget except that the framework calls the [builder] -/// function at layout time and provides the parent widget's constraints. This -/// is useful when the parent constrains the child's size and doesn't depend on -/// the child's intrinsic size. The [LayoutBuilder]'s final size will match its -/// child's size. -/// -/// {@macro flutter.widgets.ConstrainedLayoutBuilder} -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=IYDVcriKjsw} -/// -/// If the child should be smaller than the parent, consider wrapping the child -/// in an [Align] widget. If the child might want to be bigger, consider -/// wrapping it in a [SingleChildScrollView] or [OverflowBox]. -/// -/// {@tool dartpad} -/// This example uses a [LayoutBuilder] to build a different widget depending on the available width. Resize the -/// DartPad window to see [LayoutBuilder] in action! -/// -/// ** See code in examples/api/lib/widgets/layout_builder/layout_builder.0.dart ** -/// {@end-tool} -/// -/// See also: -/// -/// * [SliverLayoutBuilder], the sliver counterpart of this widget. -/// * [Builder], which calls a `builder` function at build time. -/// * [StatefulBuilder], which passes its `builder` function a `setState` callback. -/// * [CustomSingleChildLayout], which positions its child during layout. -/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/). -class LayoutBuilder extends ConstrainedLayoutBuilder { - /// Creates a widget that defers its building until layout. - const LayoutBuilder({super.key, required super.builder}); - - @override - RenderAbstractLayoutBuilderMixin - createRenderObject( - BuildContext context, - ) => _RenderLayoutBuilder(); -} - -class _RenderLayoutBuilder extends RenderBox - with - RenderObjectWithChildMixin, - RenderObjectWithLayoutCallbackMixin, - RenderAbstractLayoutBuilderMixin { - @override - double computeMinIntrinsicWidth(double height) { - assert(_debugThrowIfNotCheckingIntrinsics()); - return 0.0; - } - - @override - double computeMaxIntrinsicWidth(double height) { - assert(_debugThrowIfNotCheckingIntrinsics()); - return 0.0; - } - - @override - double computeMinIntrinsicHeight(double width) { - assert(_debugThrowIfNotCheckingIntrinsics()); - return 0.0; - } - - @override - double computeMaxIntrinsicHeight(double width) { - assert(_debugThrowIfNotCheckingIntrinsics()); - return 0.0; - } - - @override - Size computeDryLayout(BoxConstraints constraints) { - assert( - debugCannotComputeDryLayout( - reason: - 'Calculating the dry layout would require running the layout callback ' - 'speculatively, which might mutate the live render object tree.', - ), - ); - return Size.zero; - } - - @override - double? computeDryBaseline( - BoxConstraints constraints, - TextBaseline baseline, - ) { - assert( - debugCannotComputeDryLayout( - reason: - 'Calculating the dry baseline would require running the layout callback ' - 'speculatively, which might mutate the live render object tree.', - ), - ); - return null; - } - - @override - void performLayout() { - final BoxConstraints constraints = this.constraints; - runLayoutCallback(); - if (child != null) { - child!.layout(constraints, parentUsesSize: true); - size = constraints.constrain(child!.size); - } else { - size = constraints.biggest; - } - } - - @override - double? computeDistanceToActualBaseline(TextBaseline baseline) { - return child?.getDistanceToActualBaseline(baseline) ?? - super.computeDistanceToActualBaseline(baseline); - } - - @override - bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { - return child?.hitTest(result, position: position) ?? false; - } - - @override - void paint(PaintingContext context, Offset offset) { - if (child != null) { - context.paintChild(child!, offset); - } - } - - bool _debugThrowIfNotCheckingIntrinsics() { - assert(() { - if (!RenderObject.debugCheckingIntrinsics) { - throw FlutterError( - 'LayoutBuilder does not support returning intrinsic dimensions.\n' - 'Calculating the intrinsic dimensions would require running the layout ' - 'callback speculatively, which might mutate the live render object tree.', - ); - } - return true; - }()); - - return true; - } -} - -FlutterErrorDetails _reportException( - DiagnosticsNode context, - Object exception, - StackTrace stack, { - InformationCollector? informationCollector, -}) { - final details = FlutterErrorDetails( - exception: exception, - stack: stack, - library: 'widgets library', - context: context, - informationCollector: informationCollector, - ); - FlutterError.reportError(details); - return details; -} diff --git a/lib/common/widgets/flutter/list_tile.dart b/lib/common/widgets/flutter/list_tile.dart index 048fe14005..79e1aece0f 100644 --- a/lib/common/widgets/flutter/list_tile.dart +++ b/lib/common/widgets/flutter/list_tile.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: uri_does_not_exist_in_doc_import +// ignore_for_file: prefer_initializing_formals, uri_does_not_exist_in_doc_import /// @docImport 'card.dart'; /// @docImport 'checkbox.dart'; @@ -742,19 +742,6 @@ class ListTile extends StatelessWidget { return dense ?? tileTheme.dense ?? theme.listTileTheme.dense ?? false; } - Color _tileBackgroundColor( - ThemeData theme, - ListTileThemeData tileTheme, - ListTileThemeData defaults, - ) { - final Color? color = selected - ? selectedTileColor ?? - tileTheme.selectedTileColor ?? - theme.listTileTheme.selectedTileColor - : tileColor ?? tileTheme.tileColor ?? theme.listTileTheme.tileColor; - return color ?? defaults.tileColor!; - } - @override Widget build(BuildContext context) { assert(debugCheckHasMaterial(context)); @@ -769,6 +756,25 @@ class ListTile extends StatelessWidget { final ListTileThemeData defaults = theme.useMaterial3 ? _LisTileDefaultsM3(context) : _LisTileDefaultsM2(context, listTileStyle); + + final Color backgroundColor = + tileColor ?? + tileTheme.tileColor ?? + theme.listTileTheme.tileColor ?? + defaults.tileColor!; + final Color selectedBackgroundColor = + selectedTileColor ?? + tileTheme.selectedTileColor ?? + theme.listTileTheme.selectedTileColor ?? + defaults.tileColor!; + final effectiveTileColor = selected + ? selectedBackgroundColor + : backgroundColor; + final bool hasOpaqueBackground = + backgroundColor.alpha > 0 || selectedBackgroundColor.alpha > 0; + if (onTap != null || onLongPress != null || hasOpaqueBackground) { + assert(_debugCheckBackgroundIsHidden(context)); + } final Set states = { if (!enabled) WidgetState.disabled, if (selected) WidgetState.selected, @@ -1015,7 +1021,7 @@ class ListTile extends StatelessWidget { child: Ink( decoration: ShapeDecoration( shape: shape ?? tileTheme.shape ?? const Border(), - color: _tileBackgroundColor(theme, tileTheme, defaults), + color: effectiveTileColor, ), child: child, ), @@ -1189,6 +1195,68 @@ class ListTile extends StatelessWidget { ), ); } + + bool _debugCheckBackgroundIsHidden(BuildContext context) { + assert(() { + final Widget? intermediateWidget = _findIntermediateWidget(context); + if (intermediateWidget != null) { + FlutterError.reportError( + FlutterErrorDetails( + exception: FlutterError.fromParts([ + ErrorSummary( + 'ListTile background color or ink splashes may be invisible.', + ), + ErrorDescription( + 'The ListTile is wrapped in a ${intermediateWidget.runtimeType} that has a background color. ' + 'Because ListTile paints its background and ink splashes on the nearest Material ancestor, ' + 'this ${intermediateWidget.runtimeType} will hide those effects.', + ), + ErrorHint( + 'To fix this, wrap the ListTile in its own Material widget, ' + 'or remove the background color from the intermediate ${intermediateWidget.runtimeType}.', + ), + ]), + informationCollector: () => [ + DiagnosticsProperty( + 'ListTile', + this, + expandableValue: true, + ), + DiagnosticsProperty( + '${intermediateWidget.runtimeType}', + intermediateWidget, + expandableValue: true, + ), + ], + ), + ); + } + return true; + }()); + return true; + } + + Widget? _findIntermediateWidget(BuildContext context) { + Widget? intermediateWidget; + (context as Element).visitAncestorElements((Element ancestor) { + if (ancestor.widget is Material) { + return false; + } + final Widget widget = ancestor.widget; + final Color? color = switch (widget) { + ColoredBox(:final Color color) => color, + DecoratedBox(decoration: BoxDecoration(:final Color? color)) => color, + DecoratedBox(decoration: ShapeDecoration(:final Color? color)) => color, + _ => null, + }; + if (color != null && color.a > 0) { + intermediateWidget = widget; + return false; + } + return true; + }); + return intermediateWidget; + } } class _IndividualOverrides extends WidgetStateProperty { diff --git a/lib/common/widgets/flutter/page/scrollable.dart b/lib/common/widgets/flutter/page/scrollable.dart index f1876e5f3b..1abd487d0a 100644 --- a/lib/common/widgets/flutter/page/scrollable.dart +++ b/lib/common/widgets/flutter/page/scrollable.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + import 'dart:async'; import 'dart:math' as math; @@ -948,9 +950,6 @@ class ScrollableState void _receivedPointerSignal(PointerSignalEvent event) { if (event is PointerScrollEvent && _position != null) { if (_physics != null && !_physics!.shouldAcceptUserOffset(position)) { - // The handler won't use the `event`, so allow the platform to trigger - // any default native actions. - event.respond(allowPlatformDefault: true); return; } final double delta = _pointerSignalEventDelta(event); @@ -965,9 +964,6 @@ class ScrollableState ); return; } - // The `event` won't result in a scroll, so allow the platform to trigger - // any default native actions. - event.respond(allowPlatformDefault: true); } else if (event is PointerScrollInertiaCancelEvent) { position.pointerScroll(0); // Don't use the pointer signal resolver, all hit-tested scrollables should stop. @@ -976,12 +972,16 @@ class ScrollableState void _handlePointerScroll(PointerEvent event) { assert(event is PointerScrollEvent); - final double delta = _pointerSignalEventDelta(event as PointerScrollEvent); + final scrollEvent = event as PointerScrollEvent; + final double delta = _pointerSignalEventDelta(scrollEvent); final double targetScrollOffset = _targetScrollOffsetForPointerScroll( delta, ); if (delta != 0.0 && targetScrollOffset != position.pixels) { position.pointerScroll(delta); + // Tell engine this scrollable handled the event. + // This prevents parent page from scrolling when nested scrollables exist. + scrollEvent.respond(allowPlatformDefault: false); } } diff --git a/lib/common/widgets/flutter/page/scrollable_helpers.dart b/lib/common/widgets/flutter/page/scrollable_helpers.dart index 53a2b2bb75..b68809be38 100644 --- a/lib/common/widgets/flutter/page/scrollable_helpers.dart +++ b/lib/common/widgets/flutter/page/scrollable_helpers.dart @@ -2,13 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: dangling_library_doc_comments, prefer_initializing_formals + /// @docImport 'package:flutter/material.dart'; /// /// @docImport 'overscroll_indicator.dart'; /// @docImport 'viewport.dart'; -// ignore_for_file: dangling_library_doc_comments - import 'dart:math' as math; import 'package:PiliPlus/common/widgets/flutter/page/scrollable.dart'; diff --git a/lib/common/widgets/flutter/page/tabs.dart b/lib/common/widgets/flutter/page/tabs.dart index 24be4d0845..cd71c0ee4b 100644 --- a/lib/common/widgets/flutter/page/tabs.dart +++ b/lib/common/widgets/flutter/page/tabs.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart'; import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/gestures.dart' diff --git a/lib/common/widgets/flutter/pop_scope.dart b/lib/common/widgets/flutter/pop_scope.dart index 33cffe2e1c..ec0010f628 100644 --- a/lib/common/widgets/flutter/pop_scope.dart +++ b/lib/common/widgets/flutter/pop_scope.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + import 'package:flutter/material.dart' hide PopScope; import 'package:get/get_core/src/get_main.dart'; import 'package:get/get_navigation/src/extension_navigation.dart'; diff --git a/lib/common/widgets/flutter/popup_menu.dart b/lib/common/widgets/flutter/popup_menu.dart index 041c6f7e1e..4e5e82ca42 100644 --- a/lib/common/widgets/flutter/popup_menu.dart +++ b/lib/common/widgets/flutter/popup_menu.dart @@ -2,9 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -library; +// ignore_for_file: prefer_initializing_formals -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide PopupMenuItem; class CustomPopupMenuItem extends PopupMenuEntry { const CustomPopupMenuItem({ @@ -114,7 +114,7 @@ class _CustomPopupMenuDividerState extends State { // dart format off class _PopupMenuDefaultsM3 extends PopupMenuThemeData { _PopupMenuDefaultsM3(this.context) - : super(elevation: 3.0); + : super(elevation: 3.0); final BuildContext context; late final ThemeData _theme = Theme.of(context); @@ -123,8 +123,8 @@ class _PopupMenuDefaultsM3 extends PopupMenuThemeData { @override WidgetStateProperty? get labelTextStyle { return WidgetStateProperty.resolveWith((Set states) { - // TODO(quncheng): Update this hard-coded value to use the latest tokens. - final TextStyle style = _textTheme.labelLarge!; + // TODO(quncheng): Update this hard-coded value to use the latest tokens. + final TextStyle style = _textTheme.labelLarge!; if (states.contains(WidgetState.disabled)) { return style.apply(color: _colors.onSurface.withValues(alpha: 0.38)); } diff --git a/lib/common/widgets/flutter/selectable_text/selectable_region.dart b/lib/common/widgets/flutter/selectable_text/selectable_region.dart deleted file mode 100644 index 0ca45fc00e..0000000000 --- a/lib/common/widgets/flutter/selectable_text/selectable_region.dart +++ /dev/null @@ -1,2220 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:io'; -import 'dart:math'; - -import 'package:PiliPlus/common/widgets/flutter/selectable_text/tap_and_drag.dart'; -import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart' - hide - BaseTapAndDragGestureRecognizer, - TapAndHorizontalDragGestureRecognizer, - TapAndPanGestureRecognizer; -import 'package:flutter/material.dart' hide SelectableRegion; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; -import 'package:os_type/os_type.dart'; -import 'package:vector_math/vector_math_64.dart'; - -// Examples can assume: -// late GlobalKey key; - -const Set _kLongPressSelectionDevices = { - PointerDeviceKind.touch, - PointerDeviceKind.stylus, - PointerDeviceKind.invertedStylus, -}; - -/// A widget that introduces an area for user selections. -/// -/// Flutter widgets are not selectable by default. Wrapping a widget subtree -/// with a [SelectableRegion] widget enables selection within that subtree (for -/// example, [Text] widgets automatically look for selectable regions to enable -/// selection). The wrapped subtree can be selected by users using mouse or -/// touch gestures, e.g. users can select widgets by holding the mouse -/// left-click and dragging across widgets, or they can use long press gestures -/// to select words on touch devices. -/// -/// A [SelectableRegion] widget requires configuration; in particular specific -/// [selectionControls] must be provided. -/// -/// The [SelectionArea] widget from the [material] library configures a -/// [SelectableRegion] in a platform-specific manner (e.g. using a Material -/// toolbar on Android, a Cupertino toolbar on iOS), and it may therefore be -/// simpler to use that widget rather than using [SelectableRegion] directly. -/// -/// ## An overview of the selection system. -/// -/// Every [Selectable] under the [SelectableRegion] can be selected. They form a -/// selection tree structure to handle the selection. -/// -/// The [SelectableRegion] is a wrapper over [SelectionContainer]. It listens to -/// user gestures and sends corresponding [SelectionEvent]s to the -/// [SelectionContainer] it creates. -/// -/// A [SelectionContainer] is a single [Selectable] that handles -/// [SelectionEvent]s on behalf of child [Selectable]s in the subtree. It -/// creates a [SelectionRegistrarScope] with its [SelectionContainer.delegate] -/// to collect child [Selectable]s and sends the [SelectionEvent]s it receives -/// from the parent [SelectionRegistrar] to the appropriate child [Selectable]s. -/// It creates an abstraction for the parent [SelectionRegistrar] as if it is -/// interacting with a single [Selectable]. -/// -/// The [SelectionContainer] created by [SelectableRegion] is the root node of a -/// selection tree. Each non-leaf node in the tree is a [SelectionContainer], -/// and the leaf node is a leaf widget whose render object implements -/// [Selectable]. They are connected through [SelectionRegistrarScope]s created -/// by [SelectionContainer]s. -/// -/// Both [SelectionContainer]s and the leaf [Selectable]s need to register -/// themselves to the [SelectionRegistrar] from the -/// [SelectionContainer.maybeOf] if they want to participate in the -/// selection. -/// -/// An example selection tree will look like: -/// -/// {@tool snippet} -/// -/// ```dart -/// MaterialApp( -/// home: SelectableRegion( -/// selectionControls: materialTextSelectionControls, -/// child: Scaffold( -/// appBar: AppBar(title: const Text('Flutter Code Sample')), -/// body: ListView( -/// children: const [ -/// Text('Item 0', style: TextStyle(fontSize: 50.0)), -/// Text('Item 1', style: TextStyle(fontSize: 50.0)), -/// ], -/// ), -/// ), -/// ), -/// ) -/// ``` -/// {@end-tool} -/// -/// -/// SelectionContainer -/// (SelectableRegion) -/// / \ -/// / \ -/// / \ -/// Selectable \ -/// ("Flutter Code Sample") \ -/// \ -/// SelectionContainer -/// (ListView) -/// / \ -/// / \ -/// / \ -/// Selectable Selectable -/// ("Item 0") ("Item 1") -/// -/// -/// ## Making a widget selectable -/// -/// Some leaf widgets, such as [Text], have all of the selection logic wired up -/// automatically and can be selected as long as they are under a -/// [SelectableRegion]. -/// -/// To make a custom selectable widget, its render object needs to mix in -/// [Selectable] and implement the required APIs to handle [SelectionEvent]s -/// as well as paint appropriate selection highlights. -/// -/// The render object also needs to register itself to a [SelectionRegistrar]. -/// For the most cases, one can use [SelectionRegistrant] to auto-register -/// itself with the register returned from [SelectionContainer.maybeOf] as -/// seen in the example below. -/// -/// {@tool dartpad} -/// This sample demonstrates how to create an adapter widget that makes any -/// child widget selectable. -/// -/// ** See code in examples/api/lib/material/selectable_region/selectable_region.0.dart ** -/// {@end-tool} -/// -/// ## Complex layout -/// -/// By default, the screen order is used as the selection order. If a group of -/// [Selectable]s needs to select differently, consider wrapping them with a -/// [SelectionContainer] to customize its selection behavior. -/// -/// {@tool dartpad} -/// This sample demonstrates how to create a [SelectionContainer] that only -/// allows selecting everything or nothing with no partial selection. -/// -/// ** See code in examples/api/lib/material/selection_container/selection_container.0.dart ** -/// {@end-tool} -/// -/// In the case where a group of widgets should be excluded from selection under -/// a [SelectableRegion], consider wrapping that group of widgets using -/// [SelectionContainer.disabled]. -/// -/// {@tool dartpad} -/// This sample demonstrates how to disable selection for a Text in a Column. -/// -/// ** See code in examples/api/lib/material/selection_container/selection_container_disabled.0.dart ** -/// {@end-tool} -/// -/// To create a separate selection system from its parent selection area, -/// wrap part of the subtree with another [SelectableRegion]. The selection of the -/// child selection area can not extend past its subtree, and the selection of -/// the parent selection area can not extend inside the child selection area. -/// -/// ## Selection status -/// -/// A [SelectableRegion]s [SelectableRegionSelectionStatus] is used to indicate whether -/// the [SelectableRegion] is actively changing the selection, or has finalized it. For -/// example, during a mouse click + drag, the [SelectableRegionSelectionStatus] will be -/// set to [SelectableRegionSelectionStatus.changing], and when the mouse click is released -/// the status will be set to [SelectableRegionSelectionStatus.finalized]. -/// -/// The default value of [SelectableRegion]s selection status -/// is [SelectableRegionSelectionStatus.finalized]. -/// -/// To access the [SelectableRegionSelectionStatus] of a parent [SelectableRegion] -/// use [SelectableRegionSelectionStatusScope.maybeOf] and retrieve the value from -/// the [ValueListenable]. -/// -/// One can also listen for changes to the [SelectableRegionSelectionStatus] by -/// adding a listener to the [ValueListenable] retrieved from [SelectableRegionSelectionStatusScope.maybeOf] -/// through [ValueListenable.addListener]. In Stateful widgets this is typically -/// done in [State.didChangeDependencies]. Remove the listener when no longer -/// needed, typically in your Stateful widgets [State.dispose] method through -/// [ValueListenable.removeListener]. -/// -/// ## Tests -/// -/// In a test, a region can be selected either by faking drag events (e.g. using -/// [WidgetTester.dragFrom]) or by sending intents to a widget inside the region -/// that has been given a [GlobalKey], e.g.: -/// -/// ```dart -/// Actions.invoke(key.currentContext!, const SelectAllTextIntent(SelectionChangedCause.keyboard)); -/// ``` -/// -/// See also: -/// -/// * [SelectionArea], which creates a [SelectableRegion] with -/// platform-adaptive selection controls. -/// * [SelectableText], which enables selection on a single run of text. -/// * [SelectionHandler], which contains APIs to handle selection events from the -/// [SelectableRegion]. -/// * [Selectable], which provides API to participate in the selection system. -/// * [SelectionRegistrar], which [Selectable] needs to subscribe to receive -/// selection events. -/// * [SelectionContainer], which collects selectable widgets in the subtree -/// and provides api to dispatch selection event to the collected widget. -/// * [SelectionListener], which enables accessing the [SelectionDetails] of -/// the selectable subtree it wraps. -class SelectableRegion extends StatefulWidget { - /// Create a new [SelectableRegion] widget. - /// - /// The [selectionControls] are used for building the selection handles and - /// toolbar for mobile devices. - const SelectableRegion({ - super.key, - this.contextMenuBuilder, - this.focusNode, - this.magnifierConfiguration = TextMagnifierConfiguration.disabled, - this.onSelectionChanged, - required this.selectionControls, - required this.child, - }); - - /// The configuration for the magnifier used with selections in this region. - /// - /// By default, [SelectableRegion]'s [TextMagnifierConfiguration] is disabled. - /// For a version of [SelectableRegion] that adapts automatically to the - /// current platform, consider [SelectionArea]. - /// - /// {@macro flutter.widgets.magnifier.intro} - final TextMagnifierConfiguration magnifierConfiguration; - - /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode? focusNode; - - /// The child widget this selection area applies to. - /// - /// {@macro flutter.widgets.ProxyWidget.child} - final Widget child; - - /// {@macro flutter.widgets.EditableText.contextMenuBuilder} - final SelectableRegionContextMenuBuilder? contextMenuBuilder; - - /// The delegate to build the selection handles and toolbar for mobile - /// devices. - /// - /// The [emptyTextSelectionControls] global variable provides a default - /// [TextSelectionControls] implementation with no controls. - final TextSelectionControls selectionControls; - - /// Called when the selected content changes. - final ValueChanged? onSelectionChanged; - - /// Returns the [ContextMenuButtonItem]s representing the buttons in this - /// platform's default selection menu. - /// - /// For example, [SelectableRegion] uses this to generate the default buttons - /// for its context menu. - /// - /// See also: - /// - /// * [SelectableRegionState.contextMenuButtonItems], which gives the - /// [ContextMenuButtonItem]s for a specific SelectableRegion. - /// * [EditableText.getEditableButtonItems], which performs a similar role but - /// for content that is both selectable and editable. - /// * [AdaptiveTextSelectionToolbar], which builds the toolbar itself, and can - /// take a list of [ContextMenuButtonItem]s with - /// [AdaptiveTextSelectionToolbar.buttonItems]. - /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the button - /// Widgets for the current platform given [ContextMenuButtonItem]s. - static List getSelectableButtonItems({ - required final SelectionGeometry selectionGeometry, - required final VoidCallback onCopy, - required final VoidCallback onSelectAll, - required final VoidCallback? onShare, - }) { - final canCopy = selectionGeometry.status == SelectionStatus.uncollapsed; - final bool canSelectAll = selectionGeometry.hasContent; - // The share button is not supported on the web. - final bool platformCanShare = - !kIsWeb && - switch (defaultTargetPlatform) { - TargetPlatform.android => - selectionGeometry.status == SelectionStatus.uncollapsed, - TargetPlatform.macOS || - TargetPlatform.fuchsia || - TargetPlatform.linux || - TargetPlatform.windows => false, - // TODO(bleroux): the share button should be shown on iOS but the share - // functionality requires some changes on the engine side because, on iPad, - // it needs an anchor for the popup. - // See: https://github.com/flutter/flutter/issues/141775. - TargetPlatform.iOS => false, - _ => selectionGeometry.status == SelectionStatus.uncollapsed, - }; - final bool canShare = onShare != null && platformCanShare; - - // On Android, the share button is before the select all button. - final showShareBeforeSelectAll = - defaultTargetPlatform == TargetPlatform.android; - - // Determine which buttons will appear so that the order and total number is - // known. A button's position in the menu can slightly affect its - // appearance. - return [ - if (canCopy) - ContextMenuButtonItem( - onPressed: onCopy, - type: ContextMenuButtonType.copy, - ), - if (canShare && showShareBeforeSelectAll) - ContextMenuButtonItem( - onPressed: onShare, - type: ContextMenuButtonType.share, - ), - if (canSelectAll) - ContextMenuButtonItem( - onPressed: onSelectAll, - type: ContextMenuButtonType.selectAll, - ), - if (canShare && !showShareBeforeSelectAll) - ContextMenuButtonItem( - onPressed: onShare, - type: ContextMenuButtonType.share, - ), - ]; - } - - @override - State createState() => SelectableRegionState(); -} - -/// State for a [SelectableRegion]. -class SelectableRegionState extends State - with TextSelectionDelegate - implements SelectionRegistrar { - late final Map> _actions = >{ - SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)), - CopySelectionTextIntent: _makeOverridable(_CopySelectionAction(this)), - ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable( - _GranularlyExtendSelectionAction< - ExtendSelectionToNextWordBoundaryOrCaretLocationIntent - >( - this, - granularity: TextGranularity.word, - ), - ), - ExpandSelectionToDocumentBoundaryIntent: _makeOverridable( - _GranularlyExtendSelectionAction( - this, - granularity: TextGranularity.document, - ), - ), - ExpandSelectionToLineBreakIntent: _makeOverridable( - _GranularlyExtendSelectionAction( - this, - granularity: TextGranularity.line, - ), - ), - ExtendSelectionByCharacterIntent: _makeOverridable( - _GranularlyExtendCaretSelectionAction( - this, - granularity: TextGranularity.character, - ), - ), - ExtendSelectionToNextWordBoundaryIntent: _makeOverridable( - _GranularlyExtendCaretSelectionAction< - ExtendSelectionToNextWordBoundaryIntent - >( - this, - granularity: TextGranularity.word, - ), - ), - ExtendSelectionToLineBreakIntent: _makeOverridable( - _GranularlyExtendCaretSelectionAction( - this, - granularity: TextGranularity.line, - ), - ), - ExtendSelectionVerticallyToAdjacentLineIntent: _makeOverridable( - _DirectionallyExtendCaretSelectionAction< - ExtendSelectionVerticallyToAdjacentLineIntent - >(this), - ), - ExtendSelectionToDocumentBoundaryIntent: _makeOverridable( - _GranularlyExtendCaretSelectionAction< - ExtendSelectionToDocumentBoundaryIntent - >( - this, - granularity: TextGranularity.document, - ), - ), - }; - - final Map _gestureRecognizers = - {}; - SelectionOverlay? _selectionOverlay; - final LayerLink _startHandleLayerLink = LayerLink(); - final LayerLink _endHandleLayerLink = LayerLink(); - final LayerLink _toolbarLayerLink = LayerLink(); - final StaticSelectionContainerDelegate _selectionDelegate = - StaticSelectionContainerDelegate(); - // there should only ever be one selectable, which is the SelectionContainer. - Selectable? _selectable; - - bool get _hasSelectionOverlayGeometry => - _selectionDelegate.value.startSelectionPoint != null || - _selectionDelegate.value.endSelectionPoint != null; - - Orientation? _lastOrientation; - SelectedContent? _lastSelectedContent; - - /// Whether the native browser context menu is enabled. - // TODO(Renzo-Olivares): Re-enable web context menu for Android - // and iOS when https://github.com/flutter/flutter/issues/177123 - // is resolved. - bool get _webContextMenuEnabled => - kIsWeb && - BrowserContextMenu.enabled && - defaultTargetPlatform != TargetPlatform.android && - defaultTargetPlatform != TargetPlatform.iOS; - - /// The [SelectionOverlay] that is currently visible on the screen. - /// - /// Can be null if there is no visible [SelectionOverlay]. - @visibleForTesting - SelectionOverlay? get selectionOverlay => _selectionOverlay; - - /// The text processing service used to retrieve the native text processing actions. - final ProcessTextService _processTextService = DefaultProcessTextService(); - - /// The list of native text processing actions provided by the engine. - final List _processTextActions = []; - - // The focus node to use if the widget didn't supply one. - FocusNode? _localFocusNode; - FocusNode get _focusNode => - widget.focusNode ?? - (_localFocusNode ??= FocusNode(debugLabel: 'SelectableRegion')); - - /// Notifies its listeners when the selection state in this [SelectableRegion] changes. - final _SelectableRegionSelectionStatusNotifier _selectionStatusNotifier = - _SelectableRegionSelectionStatusNotifier._(); - - @protected - @override - void initState() { - super.initState(); - _focusNode.addListener(_handleFocusChanged); - _initMouseGestureRecognizer(); - _initTouchGestureRecognizer(); - // Right clicks. - _gestureRecognizers[TapGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(debugOwner: this), - (TapGestureRecognizer instance) { - instance.onSecondaryTapDown = _handleRightClickDown; - }, - ); - _initProcessTextActions(); - } - - /// Query the engine to initialize the list of text processing actions to show - /// in the text selection toolbar. - Future _initProcessTextActions() async { - _processTextActions - ..clear() - ..addAll(await _processTextService.queryTextActions()); - } - - @protected - @override - void didChangeDependencies() { - super.didChangeDependencies(); - switch (defaultTargetPlatform) { - case TargetPlatform.android: - case TargetPlatform.iOS: - break; - case TargetPlatform.fuchsia: - case TargetPlatform.linux: - case TargetPlatform.macOS: - case TargetPlatform.windows: - return; - default: - if (OS.isHarmony && PlatformUtils.isDesktop) return; - break; - } - - // Hide the text selection toolbar on mobile when orientation changes. - final Orientation orientation = MediaQuery.orientationOf(context); - if (_lastOrientation == null) { - _lastOrientation = orientation; - return; - } - if (orientation != _lastOrientation) { - _lastOrientation = orientation; - hideToolbar(defaultTargetPlatform == TargetPlatform.android); - } - } - - @protected - @override - void didUpdateWidget(SelectableRegion oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.focusNode != oldWidget.focusNode) { - if (oldWidget.focusNode == null && widget.focusNode != null) { - _localFocusNode?.removeListener(_handleFocusChanged); - _localFocusNode?.dispose(); - _localFocusNode = null; - } else if (widget.focusNode == null && oldWidget.focusNode != null) { - oldWidget.focusNode!.removeListener(_handleFocusChanged); - } - _focusNode.addListener(_handleFocusChanged); - if (_focusNode.hasFocus != oldWidget.focusNode?.hasFocus) { - _handleFocusChanged(); - } - } - } - - Action _makeOverridable(Action defaultAction) { - return Action.overridable( - context: context, - defaultAction: defaultAction, - ); - } - - void _handleFocusChanged() { - if (!_focusNode.hasFocus) { - if (_webContextMenuEnabled) { - PlatformSelectableRegionContextMenu.detach(_selectionDelegate); - } - if (SchedulerBinding.instance.lifecycleState == - AppLifecycleState.resumed) { - // We should only clear the selection when this SelectableRegion loses - // focus while the application is currently running. It is possible - // that the application is not currently running, for example on desktop - // platforms, clicking on a different window switches the focus to - // the new window causing the Flutter application to go inactive. In this - // case we want to retain the selection so it remains when we return to - // the Flutter application. - clearSelection(); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } - } - if (_webContextMenuEnabled) { - PlatformSelectableRegionContextMenu.attach(_selectionDelegate); - } - } - - void _updateSelectionStatus() { - final SelectionGeometry geometry = _selectionDelegate.value; - final TextSelection selection = switch (geometry.status) { - SelectionStatus.uncollapsed || SelectionStatus.collapsed => - const TextSelection(baseOffset: 0, extentOffset: 1), - SelectionStatus.none => const TextSelection.collapsed(offset: 1), - }; - textEditingValue = TextEditingValue(text: '__', selection: selection); - if (_hasSelectionOverlayGeometry) { - _updateSelectionOverlay(); - } else { - _selectionOverlay?.dispose(); - _selectionOverlay = null; - } - } - - // gestures. - - /// Whether the Shift key was pressed when the most recent [PointerDownEvent] - /// was tracked by the [BaseTapAndDragGestureRecognizer]. - bool _isShiftPressed = false; - - // The position of the most recent secondary tap down event on this - // SelectableRegion. - Offset? _lastSecondaryTapDownPosition; - - // The device kind for the pointer of the most recent tap down event on this - // SelectableRegion. - PointerDeviceKind? _lastPointerDeviceKind; - - static bool _isPrecisePointerDevice(PointerDeviceKind pointerDeviceKind) { - switch (pointerDeviceKind) { - case PointerDeviceKind.mouse: - return true; - case PointerDeviceKind.trackpad: - case PointerDeviceKind.stylus: - case PointerDeviceKind.invertedStylus: - case PointerDeviceKind.touch: - case PointerDeviceKind.unknown: - return false; - } - } - - void _finalizeSelectableRegionStatus() { - if (_selectionStatusNotifier.value != - SelectableRegionSelectionStatus.changing) { - // Don't finalize the selection again if it is not currently changing. - return; - } - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.finalized; - } - - // Converts the details.consecutiveTapCount from a TapAndDrag*Details object, - // which can grow to be infinitely large, to a value between 1 and the supported - // max consecutive tap count. The value that the raw count is converted to is - // based on the default observed behavior on the native platforms. - // - // This method should be used in all instances when details.consecutiveTapCount - // would be used. - int _getEffectiveConsecutiveTapCount(int rawCount) { - var maxConsecutiveTap = 3; - if (PlatformUtils.isMobile) { - if (_lastPointerDeviceKind != null && - _lastPointerDeviceKind != PointerDeviceKind.mouse) { - // When the pointer device kind is not precise like a mouse, native - // Android resets the tap count at 2. For example, this is so the - // selection can collapse on the third tap. - maxConsecutiveTap = 2; - } - // From observation, these platforms reset their tap count to 0 when - // the number of consecutive taps exceeds the max consecutive tap supported. - // For example on native Android, when going past a triple click, - // on the fourth click the selection is moved to the precise click - // position, on the fifth click the word at the position is selected, and - // on the sixth click the paragraph at the position is selected. - return rawCount <= maxConsecutiveTap - ? rawCount - : (rawCount % maxConsecutiveTap == 0 - ? maxConsecutiveTap - : rawCount % maxConsecutiveTap); - } else { - if (defaultTargetPlatform == TargetPlatform.linux) { - // From observation, these platforms reset their tap count to 0 when - // the number of consecutive taps exceeds the max consecutive tap supported. - // For example on Debian Linux with GTK, when going past a triple click, - // on the fourth click the selection is moved to the precise click - // position, on the fifth click the word at the position is selected, and - // on the sixth click the paragraph at the position is selected. - return rawCount <= maxConsecutiveTap - ? rawCount - : (rawCount % maxConsecutiveTap == 0 - ? maxConsecutiveTap - : rawCount % maxConsecutiveTap); - } - // From observation, these platforms hold their tap count at the max - // consecutive tap supported. For example on macOS, when going past a triple - // click, the selection should be retained at the paragraph that was first - // selected on triple click. - return min(rawCount, maxConsecutiveTap); - } - } - - void _initMouseGestureRecognizer() { - _gestureRecognizers[TapAndPanGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => TapAndPanGestureRecognizer( - debugOwner: this, - supportedDevices: {PointerDeviceKind.mouse}, - ), - (TapAndPanGestureRecognizer instance) { - instance - ..onTapTrackStart = _onTapTrackStart - ..onTapTrackReset = _onTapTrackReset - ..onTapDown = _startNewMouseSelectionGesture - ..onTapUp = _handleMouseTapUp - ..onDragStart = _handleMouseDragStart - ..onDragUpdate = _handleMouseDragUpdate - ..onDragEnd = _handleMouseDragEnd - ..onCancel = clearSelection - ..dragStartBehavior = DragStartBehavior.down; - }, - ); - } - - void _onTapTrackStart() { - _isShiftPressed = HardwareKeyboard.instance.logicalKeysPressed.intersection( - { - LogicalKeyboardKey.shiftLeft, - LogicalKeyboardKey.shiftRight, - }, - ).isNotEmpty; - } - - void _onTapTrackReset() { - _isShiftPressed = false; - } - - void _initTouchGestureRecognizer() { - // A [TapAndHorizontalDragGestureRecognizer] is used on non-precise pointer devices - // like PointerDeviceKind.touch so [SelectableRegion] gestures do not conflict with - // ancestor Scrollable gestures in common scenarios like a vertically scrolling list view. - _gestureRecognizers[TapAndHorizontalDragGestureRecognizer] = - GestureRecognizerFactoryWithHandlers< - TapAndHorizontalDragGestureRecognizer - >( - () => TapAndHorizontalDragGestureRecognizer( - debugOwner: this, - supportedDevices: PointerDeviceKind.values.where(( - PointerDeviceKind device, - ) { - return device != PointerDeviceKind.mouse; - }).toSet(), - ), - (TapAndHorizontalDragGestureRecognizer instance) { - instance - // iOS does not provide a device specific touch slop - // unlike Android (~8.0), so the touch slop for a [Scrollable] - // always default to kTouchSlop which is 18.0. When - // [SelectableRegion] is the child of a horizontal - // scrollable that means the [SelectableRegion] will - // always win the gesture arena when competing with - // the ancestor scrollable because they both have - // the same touch slop threshold and the child receives - // the [PointerEvent] first. To avoid this conflict - // and ensure a smooth scrolling experience, on - // iOS the [TapAndHorizontalDragGestureRecognizer] - // will wait for all other gestures to lose before - // declaring victory. - ..eagerVictoryOnDrag = defaultTargetPlatform != TargetPlatform.iOS - ..onTapDown = _startNewMouseSelectionGesture - ..onTapUp = _handleMouseTapUp - ..onDragStart = _handleMouseDragStart - ..onDragUpdate = _handleMouseDragUpdate - ..onDragEnd = _handleMouseDragEnd - ..onCancel = clearSelection - ..dragStartBehavior = DragStartBehavior.down; - }, - ); - _gestureRecognizers[LongPressGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => LongPressGestureRecognizer( - debugOwner: this, - supportedDevices: _kLongPressSelectionDevices, - ), - (LongPressGestureRecognizer instance) { - instance - ..onLongPressStart = _handleTouchLongPressStart - ..onLongPressMoveUpdate = _handleTouchLongPressMoveUpdate - ..onLongPressEnd = _handleTouchLongPressEnd; - }, - ); - } - - Offset? _doubleTapOffset; - void _startNewMouseSelectionGesture(TapDragDownDetails details) { - _lastPointerDeviceKind = details.kind; - switch (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount)) { - case 1: - _focusNode.requestFocus(); - if (PlatformUtils.isMobile) { - // On mobile platforms the selection is set on tap up for the first - // tap. - break; - } else { - hideToolbar(); - // It is impossible to extend the selection when the shift key is - // pressed and the start of the selection has not been initialized. - // In this case we fallback on collapsing the selection to first - // initialize the selection. - final bool isShiftPressedValid = - _isShiftPressed && - _selectionDelegate.value.startSelectionPoint != null; - if (isShiftPressedValid) { - _selectEndTo(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - break; - } - clearSelection(); - _collapseSelectionAt(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - case 2: - switch (defaultTargetPlatform) { - case TargetPlatform.iOS: - if (kIsWeb && - details.kind != null && - !_isPrecisePointerDevice(details.kind!)) { - // Double tap on iOS web triggers when a drag begins after the double tap. - _doubleTapOffset = details.globalPosition; - break; - } - _selectWordAt(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - if (details.kind != null && - !_isPrecisePointerDevice(details.kind!)) { - _showHandles(); - } - default: - _selectWordAt(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - case 3: - if (PlatformUtils.isMobile) { - if (details.kind != null && _isPrecisePointerDevice(details.kind!)) { - // Triple tap on static text is only supported on mobile - // platforms using a precise pointer device. - _selectParagraphAt(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } else { - _selectParagraphAt(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - } - } - _updateSelectedContentIfNeeded(); - } - - void _handleMouseDragStart(TapDragStartDetails details) { - switch (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount)) { - case 1: - if (details.kind != null && !_isPrecisePointerDevice(details.kind!)) { - // Drag to select is only enabled with a precise pointer device. - return; - } - _selectStartTo(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - _updateSelectedContentIfNeeded(); - } - - void _handleMouseDragUpdate(TapDragUpdateDetails details) { - switch (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount)) { - case 1: - if (details.kind != null && !_isPrecisePointerDevice(details.kind!)) { - // Drag to select is only enabled with a precise pointer device. - return; - } - _selectEndTo(offset: details.globalPosition, continuous: true); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - case 2: - if (PlatformUtils.isMobile) { - if (Platform.isAndroid || Platform.isFuchsia || OS.isHarmony) { - // Double tap + drag is only supported on Android when using a precise - // pointer device or when not on the web. - if (!kIsWeb || - details.kind != null && - _isPrecisePointerDevice(details.kind!)) { - _selectEndTo( - offset: details.globalPosition, - continuous: true, - textGranularity: TextGranularity.word, - ); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - } else if (Platform.isIOS) { - if (kIsWeb && - details.kind != null && - !_isPrecisePointerDevice(details.kind!) && - _doubleTapOffset != null) { - // On iOS web a double tap does not select the word at the position, - // until the drag has begun. - _selectWordAt(offset: _doubleTapOffset!); - _doubleTapOffset = null; - } - _selectEndTo( - offset: details.globalPosition, - continuous: true, - textGranularity: TextGranularity.word, - ); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - if (details.kind != null && - !_isPrecisePointerDevice(details.kind!)) { - _showHandles(); - } - } - } else { - _selectEndTo( - offset: details.globalPosition, - continuous: true, - textGranularity: TextGranularity.word, - ); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - case 3: - if (PlatformUtils.isMobile) { - // Triple tap + drag is only supported on mobile devices when using - // a precise pointer device. - if (details.kind != null && _isPrecisePointerDevice(details.kind!)) { - _selectEndTo( - offset: details.globalPosition, - continuous: true, - textGranularity: TextGranularity.paragraph, - ); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - } else { - _selectEndTo( - offset: details.globalPosition, - continuous: true, - textGranularity: TextGranularity.paragraph, - ); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - } - } - _updateSelectedContentIfNeeded(); - } - - void _handleMouseDragEnd(TapDragEndDetails details) { - assert(_lastPointerDeviceKind != null); - final bool isPointerPrecise = _isPrecisePointerDevice( - _lastPointerDeviceKind!, - ); - // On mobile platforms like android, fuchsia, and iOS, a drag gesture will - // only show the selection overlay when the drag has finished and the pointer - // device kind is not precise, for example at the end of a double tap + drag - // to select on native iOS. - final bool shouldShowSelectionOverlayOnMobile = !isPointerPrecise; - - if (PlatformUtils.isMobile) { - if (Platform.isAndroid || Platform.isFuchsia || OS.isHarmony) { - if (shouldShowSelectionOverlayOnMobile) { - _showHandles(); - _showToolbar(); - } - } else if (Platform.isIOS) { - if (shouldShowSelectionOverlayOnMobile) { - _showToolbar(); - } - } - } - _finalizeSelection(); - _updateSelectedContentIfNeeded(); - _finalizeSelectableRegionStatus(); - } - - void _handleMouseTapUp(TapDragUpDetails details) { - if (defaultTargetPlatform == TargetPlatform.iOS && - _positionIsOnActiveSelection(globalPosition: details.globalPosition)) { - // On iOS when the tap occurs on the previous selection, instead of - // moving the selection, the context menu will be toggled. - final bool toolbarIsVisible = - _selectionOverlay?.toolbarIsVisible ?? false; - if (toolbarIsVisible) { - hideToolbar(false); - } else { - _showToolbar(); - } - return; - } - switch (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount)) { - case 1: - if (PlatformUtils.isMobile) { - hideToolbar(); - _collapseSelectionAt(offset: details.globalPosition); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - // On desktop platforms the selection is set on tap down. - } - case 2: - final bool isPointerPrecise = _isPrecisePointerDevice(details.kind); - if (PlatformUtils.isMobile) { - if (Platform.isAndroid || Platform.isFuchsia || OS.isHarmony) { - if (!isPointerPrecise) { - // On Android, a double tap will only show the selection overlay after - // the following tap up when the pointer device kind is not precise. - _showHandles(); - _showToolbar(); - } - } else if (Platform.isIOS) { - if (!isPointerPrecise) { - if (kIsWeb) { - // Double tap on iOS web only triggers when a drag begins after the double tap. - break; - } - // On iOS, a double tap will only show the selection toolbar after - // the following tap up when the pointer device kind is not precise. - _showToolbar(); - } - } - } - } - _finalizeSelectableRegionStatus(); - _updateSelectedContentIfNeeded(); - } - - void _updateSelectedContentIfNeeded() { - if (widget.onSelectionChanged == null) { - return; - } - final SelectedContent? content = _selectable?.getSelectedContent(); - if (_lastSelectedContent?.plainText != content?.plainText) { - _lastSelectedContent = content; - widget.onSelectionChanged!.call(_lastSelectedContent); - } - } - - void _handleTouchLongPressStart(LongPressStartDetails details) { - HapticFeedback.selectionClick(); - _focusNode.requestFocus(); - _selectWordAt(offset: details.globalPosition); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - // Platforms besides Android will show the text selection handles when - // the long press is initiated. Android shows the text selection handles when - // the long press has ended, usually after a pointer up event is received. - if (defaultTargetPlatform != TargetPlatform.android) { - _showHandles(); - } - _updateSelectedContentIfNeeded(); - } - - void _handleTouchLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - _selectEndTo( - offset: details.globalPosition, - textGranularity: TextGranularity.word, - ); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - _updateSelectedContentIfNeeded(); - } - - void _handleTouchLongPressEnd(LongPressEndDetails details) { - _finalizeSelection(); - _updateSelectedContentIfNeeded(); - _finalizeSelectableRegionStatus(); - _showToolbar(); - if (defaultTargetPlatform == TargetPlatform.android) { - _showHandles(); - } - } - - bool _positionIsOnActiveSelection({required Offset globalPosition}) { - for (final Rect selectionRect in _selectionDelegate.value.selectionRects) { - final Matrix4 transform = _selectable!.getTransformTo(null); - final Rect globalRect = MatrixUtils.transformRect( - transform, - selectionRect, - ); - if (globalRect.contains(globalPosition)) { - return true; - } - } - return false; - } - - void _handleRightClickDown(TapDownDetails details) { - final Offset? previousSecondaryTapDownPosition = - _lastSecondaryTapDownPosition; - final bool toolbarIsVisible = _selectionOverlay?.toolbarIsVisible ?? false; - _lastSecondaryTapDownPosition = details.globalPosition; - _focusNode.requestFocus(); - switch (defaultTargetPlatform) { - case TargetPlatform.iOS: - _selectWordAt(offset: _lastSecondaryTapDownPosition!); - case TargetPlatform.macOS: - if (previousSecondaryTapDownPosition == _lastSecondaryTapDownPosition && - toolbarIsVisible) { - hideToolbar(); - return; - } - _selectWordAt(offset: _lastSecondaryTapDownPosition!); - case TargetPlatform.linux: - if (toolbarIsVisible) { - hideToolbar(); - return; - } - // If _lastSecondaryTapDownPosition is within the current selection then - // keep the current selection, if not then collapse it. - final bool lastSecondaryTapDownPositionWasOnActiveSelection = - _positionIsOnActiveSelection( - globalPosition: details.globalPosition, - ); - if (!lastSecondaryTapDownPositionWasOnActiveSelection) { - _collapseSelectionAt(offset: _lastSecondaryTapDownPosition!); - } - case _: - if ([ - TargetPlatform.android, - TargetPlatform.fuchsia, - TargetPlatform.windows, - ].contains(defaultTargetPlatform) || - OS.isHarmony) { - // If _lastSecondaryTapDownPosition is within the current selection then - // keep the current selection, if not then collapse it. - final bool lastSecondaryTapDownPositionWasOnActiveSelection = - _positionIsOnActiveSelection( - globalPosition: details.globalPosition, - ); - if (lastSecondaryTapDownPositionWasOnActiveSelection) { - // Restore _lastSecondaryTapDownPosition since it may be cleared if a user - // accesses contextMenuAnchors. - _lastSecondaryTapDownPosition = details.globalPosition; - _showHandles(); - _showToolbar(location: _lastSecondaryTapDownPosition); - _updateSelectedContentIfNeeded(); - return; - } - _collapseSelectionAt(offset: _lastSecondaryTapDownPosition!); - } - } - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - // Restore _lastSecondaryTapDownPosition since it may be cleared if a user - // accesses contextMenuAnchors. - _lastSecondaryTapDownPosition = details.globalPosition; - _showHandles(); - _showToolbar(location: _lastSecondaryTapDownPosition); - _updateSelectedContentIfNeeded(); - } - - // Selection update helper methods. - - Offset? _selectionEndPosition; - bool get _userDraggingSelectionEnd => _selectionEndPosition != null; - bool _scheduledSelectionEndEdgeUpdate = false; - - /// Sends end [SelectionEdgeUpdateEvent] to the selectable subtree. - /// - /// If the selectable subtree returns a [SelectionResult.pending], this method - /// continues to send [SelectionEdgeUpdateEvent]s every frame until the result - /// is not pending or users end their gestures. - void _triggerSelectionEndEdgeUpdate({TextGranularity? textGranularity}) { - // This method can be called when the drag is not in progress. This can - // happen if the child scrollable returns SelectionResult.pending, and - // the selection area scheduled a selection update for the next frame, but - // the drag is lifted before the scheduled selection update is run. - if (_scheduledSelectionEndEdgeUpdate || !_userDraggingSelectionEnd) { - return; - } - if (_selectable?.dispatchSelectionEvent( - SelectionEdgeUpdateEvent.forEnd( - globalPosition: _selectionEndPosition!, - granularity: textGranularity, - ), - ) == - SelectionResult.pending) { - _scheduledSelectionEndEdgeUpdate = true; - SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { - if (!_scheduledSelectionEndEdgeUpdate) { - return; - } - _scheduledSelectionEndEdgeUpdate = false; - _triggerSelectionEndEdgeUpdate(textGranularity: textGranularity); - }, debugLabel: 'SelectableRegion.endEdgeUpdate'); - return; - } - } - - void _onAnyDragEnd(DragEndDetails details) { - final bool draggingHandles = - _selectionOverlay != null && - (_selectionOverlay!.isDraggingStartHandle || - _selectionOverlay!.isDraggingEndHandle); - if (!draggingHandles) { - _selectionOverlay!.hideMagnifier(); - _showToolbar(); - } - _finalizeSelection(); - _updateSelectedContentIfNeeded(); - _finalizeSelectableRegionStatus(); - } - - void _stopSelectionEndEdgeUpdate() { - _scheduledSelectionEndEdgeUpdate = false; - _selectionEndPosition = null; - } - - Offset? _selectionStartPosition; - bool get _userDraggingSelectionStart => _selectionStartPosition != null; - bool _scheduledSelectionStartEdgeUpdate = false; - - /// Sends start [SelectionEdgeUpdateEvent] to the selectable subtree. - /// - /// If the selectable subtree returns a [SelectionResult.pending], this method - /// continues to send [SelectionEdgeUpdateEvent]s every frame until the result - /// is not pending or users end their gestures. - void _triggerSelectionStartEdgeUpdate({TextGranularity? textGranularity}) { - // This method can be called when the drag is not in progress. This can - // happen if the child scrollable returns SelectionResult.pending, and - // the selection area scheduled a selection update for the next frame, but - // the drag is lifted before the scheduled selection update is run. - if (_scheduledSelectionStartEdgeUpdate || !_userDraggingSelectionStart) { - return; - } - if (_selectable?.dispatchSelectionEvent( - SelectionEdgeUpdateEvent.forStart( - globalPosition: _selectionStartPosition!, - granularity: textGranularity, - ), - ) == - SelectionResult.pending) { - _scheduledSelectionStartEdgeUpdate = true; - SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) { - if (!_scheduledSelectionStartEdgeUpdate) { - return; - } - _scheduledSelectionStartEdgeUpdate = false; - _triggerSelectionStartEdgeUpdate(textGranularity: textGranularity); - }, debugLabel: 'SelectableRegion.startEdgeUpdate'); - return; - } - } - - void _stopSelectionStartEdgeUpdate() { - _scheduledSelectionStartEdgeUpdate = false; - _selectionEndPosition = null; - } - - // SelectionOverlay helper methods. - - late Offset _selectionStartHandleDragPosition; - late Offset _selectionEndHandleDragPosition; - - void _handleSelectionStartHandleDragStart(DragStartDetails details) { - assert(_selectionDelegate.value.startSelectionPoint != null); - - final Offset localPosition = - _selectionDelegate.value.startSelectionPoint!.localPosition; - final Matrix4 globalTransform = _selectable!.getTransformTo(null); - _selectionStartHandleDragPosition = MatrixUtils.transformPoint( - globalTransform, - localPosition, - ); - - _selectionOverlay!.showMagnifier( - _buildInfoForMagnifier( - details.globalPosition, - _selectionDelegate.value.startSelectionPoint!, - ), - ); - _updateSelectedContentIfNeeded(); - } - - void _handleSelectionStartHandleDragUpdate(DragUpdateDetails details) { - _selectionStartHandleDragPosition = - _selectionStartHandleDragPosition + details.delta; - // The value corresponds to the paint origin of the selection handle. - // Offset it to the center of the line to make it feel more natural. - _selectionStartPosition = - _selectionStartHandleDragPosition - - Offset(0, _selectionDelegate.value.startSelectionPoint!.lineHeight / 2); - _triggerSelectionStartEdgeUpdate(); - - _selectionOverlay!.updateMagnifier( - _buildInfoForMagnifier( - details.globalPosition, - _selectionDelegate.value.startSelectionPoint!, - ), - ); - _updateSelectedContentIfNeeded(); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - } - - void _handleSelectionEndHandleDragStart(DragStartDetails details) { - assert(_selectionDelegate.value.endSelectionPoint != null); - final Offset localPosition = - _selectionDelegate.value.endSelectionPoint!.localPosition; - final Matrix4 globalTransform = _selectable!.getTransformTo(null); - _selectionEndHandleDragPosition = MatrixUtils.transformPoint( - globalTransform, - localPosition, - ); - - _selectionOverlay!.showMagnifier( - _buildInfoForMagnifier( - details.globalPosition, - _selectionDelegate.value.endSelectionPoint!, - ), - ); - _updateSelectedContentIfNeeded(); - } - - void _handleSelectionEndHandleDragUpdate(DragUpdateDetails details) { - _selectionEndHandleDragPosition = - _selectionEndHandleDragPosition + details.delta; - // The value corresponds to the paint origin of the selection handle. - // Offset it to the center of the line to make it feel more natural. - _selectionEndPosition = - _selectionEndHandleDragPosition - - Offset(0, _selectionDelegate.value.endSelectionPoint!.lineHeight / 2); - _triggerSelectionEndEdgeUpdate(); - - _selectionOverlay!.updateMagnifier( - _buildInfoForMagnifier( - details.globalPosition, - _selectionDelegate.value.endSelectionPoint!, - ), - ); - _updateSelectedContentIfNeeded(); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - } - - MagnifierInfo _buildInfoForMagnifier( - Offset globalGesturePosition, - SelectionPoint selectionPoint, - ) { - final Vector3 globalTransform = _selectable! - .getTransformTo(null) - .getTranslation(); - final globalTransformAsOffset = Offset( - globalTransform.x, - globalTransform.y, - ); - final Offset globalSelectionPointPosition = - selectionPoint.localPosition + globalTransformAsOffset; - final caretRect = Rect.fromLTWH( - globalSelectionPointPosition.dx, - globalSelectionPointPosition.dy - selectionPoint.lineHeight, - 0, - selectionPoint.lineHeight, - ); - - return MagnifierInfo( - globalGesturePosition: globalGesturePosition, - caretRect: caretRect, - fieldBounds: globalTransformAsOffset & _selectable!.size, - currentLineBoundaries: globalTransformAsOffset & _selectable!.size, - ); - } - - void _createSelectionOverlay() { - assert(_hasSelectionOverlayGeometry); - if (_selectionOverlay != null) { - return; - } - final SelectionPoint? start = _selectionDelegate.value.startSelectionPoint; - final SelectionPoint? end = _selectionDelegate.value.endSelectionPoint; - _selectionOverlay = SelectionOverlay( - context: context, - debugRequiredFor: widget, - startHandleType: start?.handleType ?? TextSelectionHandleType.collapsed, - lineHeightAtStart: start?.lineHeight ?? end!.lineHeight, - onStartHandleDragStart: _handleSelectionStartHandleDragStart, - onStartHandleDragUpdate: _handleSelectionStartHandleDragUpdate, - onStartHandleDragEnd: _onAnyDragEnd, - endHandleType: end?.handleType ?? TextSelectionHandleType.collapsed, - lineHeightAtEnd: end?.lineHeight ?? start!.lineHeight, - onEndHandleDragStart: _handleSelectionEndHandleDragStart, - onEndHandleDragUpdate: _handleSelectionEndHandleDragUpdate, - onEndHandleDragEnd: _onAnyDragEnd, - selectionEndpoints: selectionEndpoints, - selectionControls: widget.selectionControls, - selectionDelegate: this, - clipboardStatus: null, - startHandleLayerLink: _startHandleLayerLink, - endHandleLayerLink: _endHandleLayerLink, - toolbarLayerLink: _toolbarLayerLink, - magnifierConfiguration: widget.magnifierConfiguration, - ); - } - - void _updateSelectionOverlay() { - if (_selectionOverlay == null) { - return; - } - assert(_hasSelectionOverlayGeometry); - final SelectionPoint? start = _selectionDelegate.value.startSelectionPoint; - final SelectionPoint? end = _selectionDelegate.value.endSelectionPoint; - _selectionOverlay! - ..startHandleType = start?.handleType ?? TextSelectionHandleType.left - ..lineHeightAtStart = start?.lineHeight ?? end!.lineHeight - ..endHandleType = end?.handleType ?? TextSelectionHandleType.right - ..lineHeightAtEnd = end?.lineHeight ?? start!.lineHeight - ..selectionEndpoints = selectionEndpoints; - } - - /// Shows the selection handles. - /// - /// Returns true if the handles are shown, false if the handles can't be - /// shown. - bool _showHandles() { - if (_selectionOverlay != null) { - _selectionOverlay!.showHandles(); - return true; - } - - if (!_hasSelectionOverlayGeometry) { - return false; - } - - _createSelectionOverlay(); - _selectionOverlay!.showHandles(); - return true; - } - - /// Shows the text selection toolbar. - /// - /// If the parameter `location` is set, the toolbar will be shown at the - /// location. Otherwise, the toolbar location will be calculated based on the - /// handles' locations. The `location` is in the coordinates system of the - /// [Overlay]. - /// - /// Returns true if the toolbar is shown, false if the toolbar can't be shown. - bool _showToolbar({Offset? location}) { - if (!_hasSelectionOverlayGeometry && _selectionOverlay == null) { - return false; - } - - // Web is using native dom elements to enable clipboard functionality of the - // context menu: copy, paste, select, cut. It might also provide additional - // functionality depending on the browser (such as translate). Due to this, - // we should not show a Flutter toolbar for the editable text elements - // unless the browser's context menu is explicitly disabled. - if (_webContextMenuEnabled) { - return false; - } - - if (_selectionOverlay == null) { - _createSelectionOverlay(); - } - - _selectionOverlay!.toolbarLocation = location; - // TODO(Renzo-Olivares): Remove the logic below that does a runtimeType - // check for TextSelectionHandleControls when TextSelectionHandleControls - // is fully removed, see: https://github.com/flutter/flutter/pull/124262. - if (widget.selectionControls is! TextSelectionHandleControls) { - _selectionOverlay!.showToolbar(); - return true; - } - - _selectionOverlay!.hideToolbar(); - - _selectionOverlay!.showToolbar( - context: context, - contextMenuBuilder: (BuildContext context) { - return widget.contextMenuBuilder!(context, this); - }, - ); - return true; - } - - /// Sets or updates selection end edge to the `offset` location. - /// - /// A selection always contains a select start edge and selection end edge. - /// They can be created by calling both [_selectStartTo] and [_selectEndTo], or - /// use other selection APIs, such as [_selectWordAt] or [selectAll]. - /// - /// This method sets or updates the selection end edge by sending - /// [SelectionEdgeUpdateEvent]s to the child [Selectable]s. - /// - /// If `continuous` is set to true and the update causes scrolling, the - /// method will continue sending the same [SelectionEdgeUpdateEvent]s to the - /// child [Selectable]s every frame until the scrolling finishes or a - /// [_finalizeSelection] is called. - /// - /// The `continuous` argument defaults to false. - /// - /// The `offset` is in global coordinates. - /// - /// Provide the `textGranularity` if the selection should not move by the default - /// [TextGranularity.character]. Only [TextGranularity.character] and - /// [TextGranularity.word] are currently supported. - /// - /// See also: - /// * [_selectStartTo], which sets or updates selection start edge. - /// * [_finalizeSelection], which stops the `continuous` updates. - /// * [clearSelection], which clears the ongoing selection. - /// * [_selectWordAt], which selects a whole word at the location. - /// * [_selectParagraphAt], which selects an entire paragraph at the location. - /// * [_collapseSelectionAt], which collapses the selection at the location. - /// * [selectAll], which selects the entire content. - void _selectEndTo({ - required Offset offset, - bool continuous = false, - TextGranularity? textGranularity, - }) { - if (!continuous) { - _selectable?.dispatchSelectionEvent( - SelectionEdgeUpdateEvent.forEnd( - globalPosition: offset, - granularity: textGranularity, - ), - ); - return; - } - if (_selectionEndPosition != offset) { - _selectionEndPosition = offset; - _triggerSelectionEndEdgeUpdate(textGranularity: textGranularity); - } - } - - /// Sets or updates selection start edge to the `offset` location. - /// - /// A selection always contains a select start edge and selection end edge. - /// They can be created by calling both [_selectStartTo] and [_selectEndTo], or - /// use other selection APIs, such as [_selectWordAt] or [selectAll]. - /// - /// This method sets or updates the selection start edge by sending - /// [SelectionEdgeUpdateEvent]s to the child [Selectable]s. - /// - /// If `continuous` is set to true and the update causes scrolling, the - /// method will continue sending the same [SelectionEdgeUpdateEvent]s to the - /// child [Selectable]s every frame until the scrolling finishes or a - /// [_finalizeSelection] is called. - /// - /// The `continuous` argument defaults to false. - /// - /// The `offset` is in global coordinates. - /// - /// Provide the `textGranularity` if the selection should not move by the default - /// [TextGranularity.character]. Only [TextGranularity.character] and - /// [TextGranularity.word] are currently supported. - /// - /// See also: - /// * [_selectEndTo], which sets or updates selection end edge. - /// * [_finalizeSelection], which stops the `continuous` updates. - /// * [clearSelection], which clears the ongoing selection. - /// * [_selectWordAt], which selects a whole word at the location. - /// * [_selectParagraphAt], which selects an entire paragraph at the location. - /// * [_collapseSelectionAt], which collapses the selection at the location. - /// * [selectAll], which selects the entire content. - void _selectStartTo({ - required Offset offset, - bool continuous = false, - TextGranularity? textGranularity, - }) { - if (!continuous) { - _selectable?.dispatchSelectionEvent( - SelectionEdgeUpdateEvent.forStart( - globalPosition: offset, - granularity: textGranularity, - ), - ); - return; - } - if (_selectionStartPosition != offset) { - _selectionStartPosition = offset; - _triggerSelectionStartEdgeUpdate(textGranularity: textGranularity); - } - } - - /// Collapses the selection at the given `offset` location. - /// - /// The `offset` is in global coordinates. - /// - /// See also: - /// * [_selectStartTo], which sets or updates selection start edge. - /// * [_selectEndTo], which sets or updates selection end edge. - /// * [_finalizeSelection], which stops the `continuous` updates. - /// * [clearSelection], which clears the ongoing selection. - /// * [_selectWordAt], which selects a whole word at the location. - /// * [_selectParagraphAt], which selects an entire paragraph at the location. - /// * [selectAll], which selects the entire content. - void _collapseSelectionAt({required Offset offset}) { - // There may be other selection ongoing. - _finalizeSelection(); - _selectStartTo(offset: offset); - _selectEndTo(offset: offset); - } - - /// Selects a whole word at the `offset` location. - /// - /// The `offset` is in global coordinates. - /// - /// If the whole word is already in the current selection, selection won't - /// change. One call [clearSelection] first if the selection needs to be - /// updated even if the word is already covered by the current selection. - /// - /// One can also use [_selectEndTo] or [_selectStartTo] to adjust the selection - /// edges after calling this method. - /// - /// See also: - /// * [_selectStartTo], which sets or updates selection start edge. - /// * [_selectEndTo], which sets or updates selection end edge. - /// * [_finalizeSelection], which stops the `continuous` updates. - /// * [clearSelection], which clears the ongoing selection. - /// * [_collapseSelectionAt], which collapses the selection at the location. - /// * [_selectParagraphAt], which selects an entire paragraph at the location. - /// * [selectAll], which selects the entire content. - void _selectWordAt({required Offset offset}) { - // There may be other selection ongoing. - _finalizeSelection(); - _selectable?.dispatchSelectionEvent( - SelectWordSelectionEvent(globalPosition: offset), - ); - } - - /// Selects the entire paragraph at the `offset` location. - /// - /// The `offset` is in global coordinates. - /// - /// If the paragraph is already in the current selection, selection won't - /// change. One call [clearSelection] first if the selection needs to be - /// updated even if the paragraph is already covered by the current selection. - /// - /// One can also use [_selectEndTo] or [_selectStartTo] to adjust the selection - /// edges after calling this method. - /// - /// See also: - /// * [_selectStartTo], which sets or updates selection start edge. - /// * [_selectEndTo], which sets or updates selection end edge. - /// * [_finalizeSelection], which stops the `continuous` updates. - /// * [clearSelection], which clear the ongoing selection. - /// * [_selectWordAt], which selects a whole word at the location. - /// * [selectAll], which selects the entire content. - void _selectParagraphAt({required Offset offset}) { - // There may be other selection ongoing. - _finalizeSelection(); - _selectable?.dispatchSelectionEvent( - SelectParagraphSelectionEvent(globalPosition: offset), - ); - } - - /// Stops any ongoing selection updates. - /// - /// This method is different from [clearSelection] that it does not remove - /// the current selection. It only stops the continuous updates. - /// - /// A continuous update can happen as result of calling [_selectStartTo] or - /// [_selectEndTo] with `continuous` sets to true which causes a [Selectable] - /// to scroll. Calling this method will stop the update as well as the - /// scrolling. - void _finalizeSelection() { - _stopSelectionEndEdgeUpdate(); - _stopSelectionStartEdgeUpdate(); - } - - /// Removes the ongoing selection for this [SelectableRegion]. - void clearSelection() { - _finalizeSelection(); - _directionalHorizontalBaseline = null; - _adjustingSelectionEnd = null; - _selectable?.dispatchSelectionEvent(const ClearSelectionEvent()); - _updateSelectedContentIfNeeded(); - } - - Future _copy() async { - final SelectedContent? data = _selectable?.getSelectedContent(); - if (data == null) { - return; - } - await Clipboard.setData(ClipboardData(text: data.plainText)); - } - - Future _share() async { - final SelectedContent? data = _selectable?.getSelectedContent(); - if (data == null) { - return; - } - await SystemChannels.platform.invokeMethod('Share.invoke', data.plainText); - } - - /// {@macro flutter.widgets.EditableText.getAnchors} - /// - /// See also: - /// - /// * [contextMenuButtonItems], which provides the [ContextMenuButtonItem]s - /// for the default context menu buttons. - TextSelectionToolbarAnchors get contextMenuAnchors { - if (_lastSecondaryTapDownPosition != null) { - final anchors = TextSelectionToolbarAnchors( - primaryAnchor: _lastSecondaryTapDownPosition!, - ); - // Clear the state of _lastSecondaryTapDownPosition after use since a user may - // access contextMenuAnchors and receive invalid anchors for their context menu. - _lastSecondaryTapDownPosition = null; - return anchors; - } - final renderBox = context.findRenderObject()! as RenderBox; - return TextSelectionToolbarAnchors.fromSelection( - renderBox: renderBox, - startGlyphHeight: startGlyphHeight, - endGlyphHeight: endGlyphHeight, - selectionEndpoints: selectionEndpoints, - ); - } - - bool? _adjustingSelectionEnd; - bool _determineIsAdjustingSelectionEnd(bool forward) { - if (_adjustingSelectionEnd != null) { - return _adjustingSelectionEnd!; - } - final bool isReversed; - final SelectionPoint start = _selectionDelegate.value.startSelectionPoint!; - final SelectionPoint end = _selectionDelegate.value.endSelectionPoint!; - if (start.localPosition.dy > end.localPosition.dy) { - isReversed = true; - } else if (start.localPosition.dy < end.localPosition.dy) { - isReversed = false; - } else { - isReversed = start.localPosition.dx > end.localPosition.dx; - } - // Always move the selection edge that increases the selection range. - return _adjustingSelectionEnd = forward != isReversed; - } - - void _granularlyExtendSelection(TextGranularity granularity, bool forward) { - _directionalHorizontalBaseline = null; - if (!_selectionDelegate.value.hasSelection) { - return; - } - _selectable?.dispatchSelectionEvent( - GranularlyExtendSelectionEvent( - forward: forward, - isEnd: _determineIsAdjustingSelectionEnd(forward), - granularity: granularity, - ), - ); - _updateSelectedContentIfNeeded(); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } - - double? _directionalHorizontalBaseline; - - void _directionallyExtendSelection(bool forward) { - if (!_selectionDelegate.value.hasSelection) { - return; - } - final bool adjustingSelectionExtend = _determineIsAdjustingSelectionEnd( - forward, - ); - final SelectionPoint baseLinePoint = adjustingSelectionExtend - ? _selectionDelegate.value.endSelectionPoint! - : _selectionDelegate.value.startSelectionPoint!; - _directionalHorizontalBaseline ??= baseLinePoint.localPosition.dx; - final Offset globalSelectionPointOffset = MatrixUtils.transformPoint( - context.findRenderObject()!.getTransformTo(null), - Offset(_directionalHorizontalBaseline!, 0), - ); - _selectable?.dispatchSelectionEvent( - DirectionallyExtendSelectionEvent( - isEnd: _adjustingSelectionEnd!, - direction: forward - ? SelectionExtendDirection.nextLine - : SelectionExtendDirection.previousLine, - dx: globalSelectionPointOffset.dx, - ), - ); - _updateSelectedContentIfNeeded(); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } - - // [TextSelectionDelegate] overrides. - - /// Returns the [ContextMenuButtonItem]s representing the buttons in this - /// platform's default selection menu. - /// - /// See also: - /// - /// * [SelectableRegion.getSelectableButtonItems], which performs a similar role, - /// but for any selectable text, not just specifically SelectableRegion. - /// * [EditableTextState.contextMenuButtonItems], which performs a similar role - /// but for content that is not just selectable but also editable. - /// * [contextMenuAnchors], which provides the anchor points for the default - /// context menu. - /// * [AdaptiveTextSelectionToolbar], which builds the toolbar itself, and can - /// take a list of [ContextMenuButtonItem]s with - /// [AdaptiveTextSelectionToolbar.buttonItems]. - /// * [AdaptiveTextSelectionToolbar.getAdaptiveButtons], which builds the - /// button Widgets for the current platform given [ContextMenuButtonItem]s. - List get contextMenuButtonItems { - return SelectableRegion.getSelectableButtonItems( - selectionGeometry: _selectionDelegate.value, - onCopy: () { - _copy(); - - // On Android copy should clear the selection. - if (PlatformUtils.isMobile) { - if (Platform.isAndroid || Platform.isFuchsia || OS.isHarmony) { - clearSelection(); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } else if (Platform.isIOS) { - hideToolbar(false); - } - } else { - hideToolbar(); - } - }, - onSelectAll: () { - if (PlatformUtils.isMobile) { - selectAll(SelectionChangedCause.toolbar); - } else { - selectAll(); - hideToolbar(); - } - }, - onShare: () { - _share(); - - // On Android, share should clear the selection. - if (PlatformUtils.isMobile) { - if (Platform.isAndroid || Platform.isFuchsia || OS.isHarmony) { - clearSelection(); - _selectionStatusNotifier.value = - SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } else if (Platform.isIOS) { - hideToolbar(false); - } - } else { - hideToolbar(); - } - }, - )..addAll(_textProcessingActionButtonItems); - } - - List get _textProcessingActionButtonItems { - final buttonItems = []; - final SelectedContent? data = _selectable?.getSelectedContent(); - if (data == null) { - return buttonItems; - } - - for (final ProcessTextAction action in _processTextActions) { - buttonItems.add( - ContextMenuButtonItem( - label: action.label, - onPressed: () async { - final String selectedText = data.plainText; - if (selectedText.isNotEmpty) { - await _processTextService.processTextAction( - action.id, - selectedText, - true, - ); - hideToolbar(); - } - }, - ), - ); - } - return buttonItems; - } - - /// The line height at the start of the current selection. - double get startGlyphHeight { - return _selectionDelegate.value.startSelectionPoint!.lineHeight; - } - - /// The line height at the end of the current selection. - double get endGlyphHeight { - return _selectionDelegate.value.endSelectionPoint!.lineHeight; - } - - /// Returns the local coordinates of the endpoints of the current selection. - List get selectionEndpoints { - final SelectionPoint? start = _selectionDelegate.value.startSelectionPoint; - final SelectionPoint? end = _selectionDelegate.value.endSelectionPoint; - late List points; - final Offset startLocalPosition = - start?.localPosition ?? end!.localPosition; - final Offset endLocalPosition = end?.localPosition ?? start!.localPosition; - if (startLocalPosition.dy > endLocalPosition.dy) { - points = [ - TextSelectionPoint(endLocalPosition, TextDirection.ltr), - TextSelectionPoint(startLocalPosition, TextDirection.ltr), - ]; - } else { - points = [ - TextSelectionPoint(startLocalPosition, TextDirection.ltr), - TextSelectionPoint(endLocalPosition, TextDirection.ltr), - ]; - } - return points; - } - - // [TextSelectionDelegate] overrides. - // TODO(justinmc): After deprecations have been removed, remove - // TextSelectionDelegate from this class. - // https://github.com/flutter/flutter/issues/111213 - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - bool get cutEnabled => false; - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - bool get pasteEnabled => false; - - @override - void hideToolbar([bool hideHandles = true]) { - _selectionOverlay?.hideToolbar(); - if (hideHandles) { - _selectionOverlay?.hideHandles(); - } - } - - @override - void selectAll([SelectionChangedCause? cause]) { - clearSelection(); - _selectable?.dispatchSelectionEvent(const SelectAllSelectionEvent()); - if (cause == SelectionChangedCause.toolbar) { - _showToolbar(); - _showHandles(); - } - _updateSelectedContentIfNeeded(); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - void copySelection(SelectionChangedCause cause) { - _copy(); - clearSelection(); - _selectionStatusNotifier.value = SelectableRegionSelectionStatus.changing; - _finalizeSelectableRegionStatus(); - } - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - TextEditingValue textEditingValue = const TextEditingValue(text: '_'); - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - void bringIntoView(TextPosition position) { - /* SelectableRegion must be in view at this point. */ - } - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - void cutSelection(SelectionChangedCause cause) { - assert(false); - } - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - void userUpdateTextEditingValue( - TextEditingValue value, - SelectionChangedCause cause, - ) { - /* SelectableRegion maintains its own state */ - } - - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - @override - Future pasteText(SelectionChangedCause cause) async { - assert(false); - } - - // [SelectionRegistrar] override. - - @override - void add(Selectable selectable) { - assert(_selectable == null); - _selectable = selectable; - _selectable!.addListener(_updateSelectionStatus); - _selectable!.pushHandleLayers(_startHandleLayerLink, _endHandleLayerLink); - } - - @override - void remove(Selectable selectable) { - assert(_selectable == selectable); - _selectable!.removeListener(_updateSelectionStatus); - _selectable!.pushHandleLayers(null, null); - _selectable = null; - } - - @protected - @override - void dispose() { - _selectable?.removeListener(_updateSelectionStatus); - _selectable?.pushHandleLayers(null, null); - _selectionDelegate.dispose(); - _selectionStatusNotifier.dispose(); - // In case dispose was triggered before gesture end, remove the magnifier - // so it doesn't remain stuck in the overlay forever. - _selectionOverlay?.hideMagnifier(); - _selectionOverlay?.dispose(); - _selectionOverlay = null; - widget.focusNode?.removeListener(_handleFocusChanged); - _localFocusNode?.removeListener(_handleFocusChanged); - _localFocusNode?.dispose(); - super.dispose(); - } - - @protected - @override - Widget build(BuildContext context) { - assert(debugCheckHasOverlay(context)); - Widget result = SelectableRegionSelectionStatusScope._( - selectionStatusNotifier: _selectionStatusNotifier, - child: SelectionContainer( - registrar: this, - delegate: _selectionDelegate, - child: widget.child, - ), - ); - if (_webContextMenuEnabled) { - result = PlatformSelectableRegionContextMenu(child: result); - } - return CompositedTransformTarget( - link: _toolbarLayerLink, - child: RawGestureDetector( - gestures: _gestureRecognizers, - behavior: HitTestBehavior.translucent, - excludeFromSemantics: true, - child: Actions( - actions: _actions, - child: Focus.withExternalFocusNode( - includeSemantics: false, - focusNode: _focusNode, - child: result, - ), - ), - ), - ); - } -} - -/// An action that does not override any [Action.overridable] in the subtree. -/// -/// If this action is invoked by an [Action.overridable], it will immediately -/// invoke the [Action.overridable] and do nothing else. Otherwise, it will call -/// [invokeAction]. -abstract class _NonOverrideAction extends ContextAction { - Object? invokeAction(T intent, [BuildContext? context]); - - @override - Object? invoke(T intent, [BuildContext? context]) { - if (callingAction != null) { - return callingAction!.invoke(intent); - } - return invokeAction(intent, context); - } -} - -class _SelectAllAction extends _NonOverrideAction { - _SelectAllAction(this.state); - - final SelectableRegionState state; - - @override - void invokeAction(SelectAllTextIntent intent, [BuildContext? context]) { - state.selectAll(SelectionChangedCause.keyboard); - } -} - -class _CopySelectionAction extends _NonOverrideAction { - _CopySelectionAction(this.state); - - final SelectableRegionState state; - - @override - void invokeAction(CopySelectionTextIntent intent, [BuildContext? context]) { - state._copy(); - } -} - -class _GranularlyExtendSelectionAction - extends _NonOverrideAction { - _GranularlyExtendSelectionAction(this.state, {required this.granularity}); - - final SelectableRegionState state; - final TextGranularity granularity; - - @override - void invokeAction(T intent, [BuildContext? context]) { - state._granularlyExtendSelection(granularity, intent.forward); - } -} - -class _GranularlyExtendCaretSelectionAction< - T extends DirectionalCaretMovementIntent -> - extends _NonOverrideAction { - _GranularlyExtendCaretSelectionAction( - this.state, { - required this.granularity, - }); - - final SelectableRegionState state; - final TextGranularity granularity; - - @override - void invokeAction(T intent, [BuildContext? context]) { - if (intent.collapseSelection) { - // Selectable region never collapses selection. - return; - } - state._granularlyExtendSelection(granularity, intent.forward); - } -} - -class _DirectionallyExtendCaretSelectionAction< - T extends DirectionalCaretMovementIntent -> - extends _NonOverrideAction { - _DirectionallyExtendCaretSelectionAction(this.state); - - final SelectableRegionState state; - - @override - void invokeAction(T intent, [BuildContext? context]) { - if (intent.collapseSelection) { - // Selectable region never collapses selection. - return; - } - state._directionallyExtendSelection(intent.forward); - } -} - -/// Signature for a widget builder that builds a context menu for the given -/// [SelectableRegionState]. -/// -/// See also: -/// -/// * [EditableTextContextMenuBuilder], which performs the same role for -/// [EditableText]. -typedef SelectableRegionContextMenuBuilder = - Widget Function( - BuildContext context, - SelectableRegionState selectableRegionState, - ); - -/// Notifies its listeners when the [SelectableRegion] that created this object -/// is changing or finalizes its selection. -/// -/// To access the [_SelectableRegionSelectionStatusNotifier] from the nearest [SelectableRegion] -/// ancestor, use [SelectableRegionSelectionStatusScope.maybeOf]. -final class _SelectableRegionSelectionStatusNotifier extends ChangeNotifier - implements ValueListenable { - _SelectableRegionSelectionStatusNotifier._(); - - SelectableRegionSelectionStatus _selectableRegionSelectionStatus = - SelectableRegionSelectionStatus.finalized; - - /// The current value of the [SelectableRegionSelectionStatus] of the [SelectableRegion] - /// that owns this object. - /// - /// Defaults to [SelectableRegionSelectionStatus.finalized]. - @override - SelectableRegionSelectionStatus get value => _selectableRegionSelectionStatus; - - /// Sets the [SelectableRegionSelectionStatus] for the [SelectableRegion] that - /// owns this object. - /// - /// Listeners are notified even if the value did not change. - @protected - set value(SelectableRegionSelectionStatus newStatus) { - assert( - newStatus == SelectableRegionSelectionStatus.finalized && - value == SelectableRegionSelectionStatus.changing || - newStatus == SelectableRegionSelectionStatus.changing, - 'Attempting to finalize the selection when it is already finalized.', - ); - _selectableRegionSelectionStatus = newStatus; - notifyListeners(); - } -} - -/// Notifies its listeners when the selection under a [SelectableRegion] or -/// [SelectionArea] is being changed or finalized. -/// -/// Use [SelectableRegionSelectionStatusScope.maybeOf], to access the [ValueListenable] of type -/// [SelectableRegionSelectionStatus] under a [SelectableRegion]. Its listeners -/// will be called even when the value of the [SelectableRegionSelectionStatus] -/// does not change. -final class SelectableRegionSelectionStatusScope extends InheritedWidget { - const SelectableRegionSelectionStatusScope._({ - required this.selectionStatusNotifier, - required super.child, - }); - - /// Tracks updates to the [SelectableRegionSelectionStatus] of the owning - /// [SelectableRegion]. - /// - /// Listeners will be called even when the value of the [SelectableRegionSelectionStatus] - /// does not change. The selection under the [SelectableRegion] still may have changed. - final ValueListenable - selectionStatusNotifier; - - /// The closest instance of this class that encloses the given context. - /// - /// If there is no enclosing [SelectableRegion] or [SelectionArea] widget, then null is - /// returned. - /// - /// Calling this method will create a dependency on the closest - /// [SelectableRegionSelectionStatusScope] in the [context], if there is one. - static ValueListenable? maybeOf( - BuildContext context, - ) { - return context - .dependOnInheritedWidgetOfExactType< - SelectableRegionSelectionStatusScope - >() - ?.selectionStatusNotifier; - } - - @override - bool updateShouldNotify(SelectableRegionSelectionStatusScope oldWidget) { - return selectionStatusNotifier != oldWidget.selectionStatusNotifier; - } -} diff --git a/lib/common/widgets/flutter/selectable_text/selectable_text.dart b/lib/common/widgets/flutter/selectable_text/selectable_text.dart deleted file mode 100644 index b4ec9a9329..0000000000 --- a/lib/common/widgets/flutter/selectable_text/selectable_text.dart +++ /dev/null @@ -1,898 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle; - -import 'package:PiliPlus/common/widgets/flutter/selectable_text/text_selection.dart'; -import 'package:flutter/cupertino.dart' - hide TextSelectionGestureDetectorBuilder; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' - hide SelectableText, TextSelectionGestureDetectorBuilder; -import 'package:flutter/rendering.dart'; -import 'package:flutter/scheduler.dart'; - -class _TextSpanEditingController extends TextEditingController { - _TextSpanEditingController({required TextSpan textSpan}) - : _textSpan = textSpan, - super(text: textSpan.toPlainText(includeSemanticsLabels: false)); - - final TextSpan _textSpan; - - @override - TextSpan buildTextSpan({ - required BuildContext context, - TextStyle? style, - required bool withComposing, - }) { - // This does not care about composing. - return TextSpan(style: style, children: [_textSpan]); - } - - @override - set text(String? newText) { - // This should never be reached. - throw UnimplementedError(); - } -} - -class _SelectableTextSelectionGestureDetectorBuilder - extends CustomTextSelectionGestureDetectorBuilder { - _SelectableTextSelectionGestureDetectorBuilder({ - required _SelectableTextState state, - }) : _state = state, - super(delegate: state); - - final _SelectableTextState _state; - - @override - void onSingleTapUp(TapDragUpDetails details) { - if (!delegate.selectionEnabled) { - return; - } - super.onSingleTapUp(details); - _state.widget.onTap?.call(); - } -} - -/// A run of selectable text with a single style. -/// -/// Consider using [SelectionArea] or [SelectableRegion] instead, which enable -/// selection on a widget subtree, including but not limited to [Text] widgets. -/// -/// The [SelectableText] widget displays a string of text with a single style. -/// The string might break across multiple lines or might all be displayed on -/// the same line depending on the layout constraints. -/// -/// {@youtube 560 315 https://www.youtube.com/watch?v=ZSU3ZXOs6hc} -/// -/// The [style] argument is optional. When omitted, the text will use the style -/// from the closest enclosing [DefaultTextStyle]. If the given style's -/// [TextStyle.inherit] property is true (the default), the given style will -/// be merged with the closest enclosing [DefaultTextStyle]. This merging -/// behavior is useful, for example, to make the text bold while using the -/// default font family and size. -/// -/// {@macro flutter.material.textfield.wantKeepAlive} -/// -/// {@tool snippet} -/// -/// ```dart -/// const SelectableText( -/// 'Hello! How are you?', -/// textAlign: TextAlign.center, -/// style: TextStyle(fontWeight: FontWeight.bold), -/// ) -/// ``` -/// {@end-tool} -/// -/// Using the [SelectableText.rich] constructor, the [SelectableText] widget can -/// display a paragraph with differently styled [TextSpan]s. The sample -/// that follows displays "Hello beautiful world" with different styles -/// for each word. -/// -/// {@tool snippet} -/// -/// ```dart -/// const SelectableText.rich( -/// TextSpan( -/// text: 'Hello', // default text style -/// children: [ -/// TextSpan(text: ' beautiful ', style: TextStyle(fontStyle: FontStyle.italic)), -/// TextSpan(text: 'world', style: TextStyle(fontWeight: FontWeight.bold)), -/// ], -/// ), -/// ) -/// ``` -/// {@end-tool} -/// -/// ## Interactivity -/// -/// To make [SelectableText] react to touch events, use callback [onTap] to achieve -/// the desired behavior. -/// -/// ## Scrolling Considerations -/// -/// If this [SelectableText] is not a descendant of [Scaffold] and is being used -/// within a [Scrollable] or nested [Scrollable]s, consider placing a -/// [ScrollNotificationObserver] above the root [Scrollable] that contains this -/// [SelectableText] to ensure proper scroll coordination for [SelectableText] -/// and its components like [TextSelectionOverlay]. -/// -/// See also: -/// -/// * [Text], which is the non selectable version of this widget. -/// * [TextField], which is the editable version of this widget. -/// * [SelectionArea], which enables the selection of multiple [Text] widgets -/// and of other widgets. -class SelectableText extends StatefulWidget { - /// Creates a selectable text widget. - /// - /// If the [style] argument is null, the text will use the style from the - /// closest enclosing [DefaultTextStyle]. - /// - - /// If the [showCursor], [autofocus], [dragStartBehavior], - /// [selectionHeightStyle], [selectionWidthStyle] and [data] arguments are - /// specified, the [maxLines] argument must be greater than zero. - const SelectableText( - String this.data, { - super.key, - this.focusNode, - this.style, - this.strutStyle, - this.textAlign, - this.textDirection, - @Deprecated( - 'Use textScaler instead. ' - 'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. ' - 'This feature was deprecated after v3.12.0-2.0.pre.', - ) - this.textScaleFactor, - this.textScaler, - this.showCursor = false, - this.autofocus = false, - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - this.toolbarOptions, - this.minLines, - this.maxLines, - this.cursorWidth = 2.0, - this.cursorHeight, - this.cursorRadius, - this.cursorColor, - this.selectionColor, - this.selectionHeightStyle, - this.selectionWidthStyle, - this.dragStartBehavior = DragStartBehavior.start, - this.enableInteractiveSelection = true, - this.selectionControls, - this.onTap, - this.scrollPhysics, - this.scrollBehavior, - this.semanticsLabel, - this.textHeightBehavior, - this.textWidthBasis, - this.onSelectionChanged, - this.contextMenuBuilder = _defaultContextMenuBuilder, - this.magnifierConfiguration, - }) : assert(maxLines == null || maxLines > 0), - assert(minLines == null || minLines > 0), - assert( - (maxLines == null) || (minLines == null) || (maxLines >= minLines), - "minLines can't be greater than maxLines", - ), - assert( - textScaler == null || textScaleFactor == null, - 'textScaleFactor is deprecated and cannot be specified when textScaler is specified.', - ), - textSpan = null; - - /// Creates a selectable text widget with a [TextSpan]. - /// - /// The [TextSpan.children] attribute of the [textSpan] parameter must only - /// contain [TextSpan]s. Other types of [InlineSpan] are not allowed. - const SelectableText.rich( - TextSpan this.textSpan, { - super.key, - this.focusNode, - this.style, - this.strutStyle, - this.textAlign, - this.textDirection, - @Deprecated( - 'Use textScaler instead. ' - 'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. ' - 'This feature was deprecated after v3.12.0-2.0.pre.', - ) - this.textScaleFactor, - this.textScaler, - this.showCursor = false, - this.autofocus = false, - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - this.toolbarOptions, - this.minLines, - this.maxLines, - this.cursorWidth = 2.0, - this.cursorHeight, - this.cursorRadius, - this.cursorColor, - this.selectionColor, - this.selectionHeightStyle, - this.selectionWidthStyle, - this.dragStartBehavior = DragStartBehavior.start, - this.enableInteractiveSelection = true, - this.selectionControls, - this.onTap, - this.scrollPhysics, - this.scrollBehavior, - this.semanticsLabel, - this.textHeightBehavior, - this.textWidthBasis, - this.onSelectionChanged, - this.contextMenuBuilder = _defaultContextMenuBuilder, - this.magnifierConfiguration, - }) : assert(maxLines == null || maxLines > 0), - assert(minLines == null || minLines > 0), - assert( - (maxLines == null) || (minLines == null) || (maxLines >= minLines), - "minLines can't be greater than maxLines", - ), - assert( - textScaler == null || textScaleFactor == null, - 'textScaleFactor is deprecated and cannot be specified when textScaler is specified.', - ), - data = null; - - /// The text to display. - /// - /// This will be null if a [textSpan] is provided instead. - final String? data; - - /// The text to display as a [TextSpan]. - /// - /// This will be null if [data] is provided instead. - final TextSpan? textSpan; - - /// Defines the focus for this widget. - /// - /// Text is only selectable when widget is focused. - /// - /// The [focusNode] is a long-lived object that's typically managed by a - /// [StatefulWidget] parent. See [FocusNode] for more information. - /// - /// To give the focus to this widget, provide a [focusNode] and then - /// use the current [FocusScope] to request the focus: - /// - /// ```dart - /// FocusScope.of(context).requestFocus(myFocusNode); - /// ``` - /// - /// This happens automatically when the widget is tapped. - /// - /// To be notified when the widget gains or loses the focus, add a listener - /// to the [focusNode]: - /// - /// ```dart - /// myFocusNode.addListener(() { print(myFocusNode.hasFocus); }); - /// ``` - /// - /// If null, this widget will create its own [FocusNode] with - /// [FocusNode.skipTraversal] parameter set to `true`, which causes the widget - /// to be skipped over during focus traversal. - final FocusNode? focusNode; - - /// The style to use for the text. - /// - /// If null, defaults [DefaultTextStyle] of context. - final TextStyle? style; - - /// {@macro flutter.widgets.editableText.strutStyle} - final StrutStyle? strutStyle; - - /// {@macro flutter.widgets.editableText.textAlign} - final TextAlign? textAlign; - - /// {@macro flutter.widgets.editableText.textDirection} - final TextDirection? textDirection; - - /// {@macro flutter.widgets.editableText.textScaleFactor} - @Deprecated( - 'Use textScaler instead. ' - 'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. ' - 'This feature was deprecated after v3.12.0-2.0.pre.', - ) - final double? textScaleFactor; - - /// {@macro flutter.painting.textPainter.textScaler} - final TextScaler? textScaler; - - /// {@macro flutter.widgets.editableText.autofocus} - final bool autofocus; - - /// {@macro flutter.widgets.editableText.minLines} - final int? minLines; - - /// {@macro flutter.widgets.editableText.maxLines} - final int? maxLines; - - /// {@macro flutter.widgets.editableText.showCursor} - final bool showCursor; - - /// {@macro flutter.widgets.editableText.cursorWidth} - final double cursorWidth; - - /// {@macro flutter.widgets.editableText.cursorHeight} - final double? cursorHeight; - - /// {@macro flutter.widgets.editableText.cursorRadius} - final Radius? cursorRadius; - - /// The color of the cursor. - /// - /// The cursor indicates the current text insertion point. - /// - /// If null then [DefaultSelectionStyle.cursorColor] is used. If that is also - /// null and [ThemeData.platform] is [TargetPlatform.iOS] or - /// [TargetPlatform.macOS], then [CupertinoThemeData.primaryColor] is used. - /// Otherwise [ColorScheme.primary] of [ThemeData.colorScheme] is used. - final Color? cursorColor; - - /// The color to use when painting the selection. - /// - /// If this property is null, this widget gets the selection color from the - /// inherited [DefaultSelectionStyle] (if any); if none, the selection - /// color is derived from the [CupertinoThemeData.primaryColor] on - /// Apple platforms and [ColorScheme.primary] of [ThemeData.colorScheme] on - /// other platforms. - final Color? selectionColor; - - /// Controls how tall the selection highlight boxes are computed to be. - /// - /// See [ui.BoxHeightStyle] for details on available styles. - final ui.BoxHeightStyle? selectionHeightStyle; - - /// Controls how wide the selection highlight boxes are computed to be. - /// - /// See [ui.BoxWidthStyle] for details on available styles. - final ui.BoxWidthStyle? selectionWidthStyle; - - /// {@macro flutter.widgets.editableText.enableInteractiveSelection} - final bool enableInteractiveSelection; - - /// {@macro flutter.widgets.editableText.selectionControls} - final TextSelectionControls? selectionControls; - - /// {@macro flutter.widgets.scrollable.dragStartBehavior} - final DragStartBehavior dragStartBehavior; - - /// Configuration of toolbar options. - /// - /// Paste and cut will be disabled regardless. - /// - /// If not set, select all and copy will be enabled by default. - @Deprecated( - 'Use `contextMenuBuilder` instead. ' - 'This feature was deprecated after v3.3.0-0.5.pre.', - ) - final ToolbarOptions? toolbarOptions; - - /// {@macro flutter.widgets.editableText.selectionEnabled} - bool get selectionEnabled => enableInteractiveSelection; - - /// Called when the user taps on this selectable text. - /// - /// The selectable text builds a [GestureDetector] to handle input events like tap, - /// to trigger focus requests, to move the caret, adjust the selection, etc. - /// Handling some of those events by wrapping the selectable text with a competing - /// GestureDetector is problematic. - /// - /// To unconditionally handle taps, without interfering with the selectable text's - /// internal gesture detector, provide this callback. - /// - /// To be notified when the text field gains or loses the focus, provide a - /// [focusNode] and add a listener to that. - /// - /// To listen to arbitrary pointer events without competing with the - /// selectable text's internal gesture detector, use a [Listener]. - final GestureTapCallback? onTap; - - /// {@macro flutter.widgets.editableText.scrollPhysics} - final ScrollPhysics? scrollPhysics; - - /// {@macro flutter.widgets.editableText.scrollBehavior} - final ScrollBehavior? scrollBehavior; - - /// {@macro flutter.widgets.Text.semanticsLabel} - final String? semanticsLabel; - - /// {@macro dart.ui.textHeightBehavior} - final TextHeightBehavior? textHeightBehavior; - - /// {@macro flutter.painting.textPainter.textWidthBasis} - final TextWidthBasis? textWidthBasis; - - /// {@macro flutter.widgets.editableText.onSelectionChanged} - final SelectionChangedCallback? onSelectionChanged; - - /// {@macro flutter.widgets.EditableText.contextMenuBuilder} - final EditableTextContextMenuBuilder? contextMenuBuilder; - - static Widget _defaultContextMenuBuilder( - BuildContext context, - EditableTextState editableTextState, - ) { - return AdaptiveTextSelectionToolbar.editableText( - editableTextState: editableTextState, - ); - } - - /// The configuration for the magnifier used when the text is selected. - /// - /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] - /// on Android, and builds nothing on all other platforms. To suppress the - /// magnifier, consider passing [TextMagnifierConfiguration.disabled]. - /// - /// {@macro flutter.widgets.magnifier.intro} - final TextMagnifierConfiguration? magnifierConfiguration; - - @override - State createState() => _SelectableTextState(); - - @override - void debugFillProperties(DiagnosticPropertiesBuilder properties) { - super.debugFillProperties(properties); - properties - ..add( - DiagnosticsProperty('data', data, defaultValue: null), - ) - ..add( - DiagnosticsProperty( - 'semanticsLabel', - semanticsLabel, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty( - 'focusNode', - focusNode, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty('style', style, defaultValue: null), - ) - ..add( - DiagnosticsProperty('autofocus', autofocus, defaultValue: false), - ) - ..add( - DiagnosticsProperty( - 'showCursor', - showCursor, - defaultValue: false, - ), - ) - ..add(IntProperty('minLines', minLines, defaultValue: null)) - ..add(IntProperty('maxLines', maxLines, defaultValue: null)) - ..add( - EnumProperty('textAlign', textAlign, defaultValue: null), - ) - ..add( - EnumProperty( - 'textDirection', - textDirection, - defaultValue: null, - ), - ) - ..add( - DoubleProperty('textScaleFactor', textScaleFactor, defaultValue: null), - ) - ..add( - DiagnosticsProperty( - 'textScaler', - textScaler, - defaultValue: null, - ), - ) - ..add( - DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0), - ) - ..add( - DoubleProperty('cursorHeight', cursorHeight, defaultValue: null), - ) - ..add( - DiagnosticsProperty( - 'cursorRadius', - cursorRadius, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty( - 'cursorColor', - cursorColor, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty( - 'selectionColor', - selectionColor, - defaultValue: null, - ), - ) - ..add( - FlagProperty( - 'selectionEnabled', - value: selectionEnabled, - defaultValue: true, - ifFalse: 'selection disabled', - ), - ) - ..add( - DiagnosticsProperty( - 'selectionControls', - selectionControls, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty( - 'scrollPhysics', - scrollPhysics, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty( - 'scrollBehavior', - scrollBehavior, - defaultValue: null, - ), - ) - ..add( - DiagnosticsProperty( - 'textHeightBehavior', - textHeightBehavior, - defaultValue: null, - ), - ); - } -} - -class _SelectableTextState extends State - implements TextSelectionGestureDetectorBuilderDelegate { - EditableTextState? get _editableText => editableTextKey.currentState; - - late _TextSpanEditingController _controller; - - FocusNode? _focusNode; - FocusNode get _effectiveFocusNode => - widget.focusNode ?? (_focusNode ??= FocusNode(skipTraversal: true)); - - bool _showSelectionHandles = false; - - late _SelectableTextSelectionGestureDetectorBuilder - _selectionGestureDetectorBuilder; - - // API for TextSelectionGestureDetectorBuilderDelegate. - @override - late bool forcePressEnabled; - - @override - final GlobalKey editableTextKey = - GlobalKey(); - - @override - bool get selectionEnabled => widget.selectionEnabled; - // End of API for TextSelectionGestureDetectorBuilderDelegate. - - @override - void initState() { - super.initState(); - _selectionGestureDetectorBuilder = - _SelectableTextSelectionGestureDetectorBuilder(state: this); - _controller = _TextSpanEditingController( - textSpan: widget.textSpan ?? TextSpan(text: widget.data), - ); - _controller.addListener(_onControllerChanged); - _effectiveFocusNode.addListener(_handleFocusChanged); - } - - @override - void didUpdateWidget(SelectableText oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.data != oldWidget.data || - widget.textSpan != oldWidget.textSpan) { - _controller - ..removeListener(_onControllerChanged) - ..dispose(); - _controller = _TextSpanEditingController( - textSpan: widget.textSpan ?? TextSpan(text: widget.data), - ); - _controller.addListener(_onControllerChanged); - } - if (widget.focusNode != oldWidget.focusNode) { - (oldWidget.focusNode ?? _focusNode)?.removeListener(_handleFocusChanged); - (widget.focusNode ?? _focusNode)?.addListener(_handleFocusChanged); - } - if (_effectiveFocusNode.hasFocus && _controller.selection.isCollapsed) { - _showSelectionHandles = false; - } else { - _showSelectionHandles = true; - } - } - - @override - void dispose() { - _effectiveFocusNode.removeListener(_handleFocusChanged); - _focusNode?.dispose(); - _controller.dispose(); - super.dispose(); - } - - void _onControllerChanged() { - final bool showSelectionHandles = - !_effectiveFocusNode.hasFocus || !_controller.selection.isCollapsed; - if (showSelectionHandles == _showSelectionHandles) { - return; - } - setState(() { - _showSelectionHandles = showSelectionHandles; - }); - } - - void _handleFocusChanged() { - if (!_effectiveFocusNode.hasFocus && - SchedulerBinding.instance.lifecycleState == AppLifecycleState.resumed) { - // We should only clear the selection when this SelectableText loses - // focus while the application is currently running. It is possible - // that the application is not currently running, for example on desktop - // platforms, clicking on a different window switches the focus to - // the new window causing the Flutter application to go inactive. In this - // case we want to retain the selection so it remains when we return to - // the Flutter application. - _controller.value = TextEditingValue(text: _controller.value.text); - } - } - - void _handleSelectionChanged( - TextSelection selection, - SelectionChangedCause? cause, - ) { - final bool willShowSelectionHandles = _shouldShowSelectionHandles(cause); - if (willShowSelectionHandles != _showSelectionHandles) { - setState(() { - _showSelectionHandles = willShowSelectionHandles; - }); - } - - widget.onSelectionChanged?.call(selection, cause); - - switch (Theme.of(context).platform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - if (cause == SelectionChangedCause.longPress) { - _editableText?.bringIntoView(selection.base); - } - return; - case _: - // Do nothing. - } - } - - /// Toggle the toolbar when a selection handle is tapped. - void _handleSelectionHandleTapped() { - if (_controller.selection.isCollapsed) { - _editableText!.toggleToolbar(); - } - } - - bool _shouldShowSelectionHandles(SelectionChangedCause? cause) { - // When the text field is activated by something that doesn't trigger the - // selection overlay, we shouldn't show the handles either. - if (!_selectionGestureDetectorBuilder.shouldShowSelectionToolbar) { - return false; - } - - if (_controller.selection.isCollapsed) { - return false; - } - - if (cause == SelectionChangedCause.keyboard) { - return false; - } - - if (cause == SelectionChangedCause.longPress) { - return true; - } - - if (_controller.text.isNotEmpty) { - return true; - } - - return false; - } - - @override - Widget build(BuildContext context) { - // TODO(garyq): Assert to block WidgetSpans from being used here are removed, - // but we still do not yet have nice handling of things like carets, clipboard, - // and other features. We should add proper support. Currently, caret handling - // is blocked on SkParagraph switch and https://github.com/flutter/engine/pull/27010 - // should be landed in SkParagraph after the switch is complete. - assert(debugCheckHasMediaQuery(context)); - assert(debugCheckHasDirectionality(context)); - assert( - !(widget.style != null && - !widget.style!.inherit && - (widget.style!.fontSize == null || - widget.style!.textBaseline == null)), - 'inherit false style must supply fontSize and textBaseline', - ); - - final ThemeData theme = Theme.of(context); - final DefaultSelectionStyle selectionStyle = DefaultSelectionStyle.of( - context, - ); - final FocusNode focusNode = _effectiveFocusNode; - - TextSelectionControls? textSelectionControls = widget.selectionControls; - final bool paintCursorAboveText; - final bool cursorOpacityAnimates; - Offset? cursorOffset; - final Color cursorColor; - final Color selectionColor; - Radius? cursorRadius = widget.cursorRadius; - - switch (theme.platform) { - case TargetPlatform.iOS: - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); - forcePressEnabled = true; - textSelectionControls ??= cupertinoTextSelectionHandleControls; - paintCursorAboveText = true; - cursorOpacityAnimates = true; - cursorColor = - widget.cursorColor ?? - selectionStyle.cursorColor ?? - cupertinoTheme.primaryColor; - selectionColor = - selectionStyle.selectionColor ?? - cupertinoTheme.primaryColor.withValues(alpha: 0.40); - cursorRadius ??= const Radius.circular(2.0); - cursorOffset = Offset( - iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), - 0, - ); - - case TargetPlatform.macOS: - final CupertinoThemeData cupertinoTheme = CupertinoTheme.of(context); - forcePressEnabled = false; - textSelectionControls ??= cupertinoDesktopTextSelectionHandleControls; - paintCursorAboveText = true; - cursorOpacityAnimates = true; - cursorColor = - widget.cursorColor ?? - selectionStyle.cursorColor ?? - cupertinoTheme.primaryColor; - selectionColor = - selectionStyle.selectionColor ?? - cupertinoTheme.primaryColor.withValues(alpha: 0.40); - cursorRadius ??= const Radius.circular(2.0); - cursorOffset = Offset( - iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), - 0, - ); - - case TargetPlatform.linux: - case TargetPlatform.windows: - forcePressEnabled = false; - textSelectionControls ??= desktopTextSelectionHandleControls; - paintCursorAboveText = false; - cursorOpacityAnimates = false; - cursorColor = - widget.cursorColor ?? - selectionStyle.cursorColor ?? - theme.colorScheme.primary; - selectionColor = - selectionStyle.selectionColor ?? - theme.colorScheme.primary.withValues(alpha: 0.40); - case _: // Android, Fuchsia, 鸿蒙 - forcePressEnabled = false; - textSelectionControls ??= materialTextSelectionHandleControls; - paintCursorAboveText = false; - cursorOpacityAnimates = false; - cursorColor = - widget.cursorColor ?? - selectionStyle.cursorColor ?? - theme.colorScheme.primary; - selectionColor = - selectionStyle.selectionColor ?? - theme.colorScheme.primary.withValues(alpha: 0.40); - } - - final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(context); - TextStyle? effectiveTextStyle = widget.style; - if (effectiveTextStyle == null || effectiveTextStyle.inherit) { - effectiveTextStyle = defaultTextStyle.style.merge( - widget.style ?? _controller._textSpan.style, - ); - } - final TextScaler? effectiveScaler = - widget.textScaler ?? - switch (widget.textScaleFactor) { - null => null, - final double textScaleFactor => TextScaler.linear(textScaleFactor), - }; - final Widget child = RepaintBoundary( - child: EditableText( - key: editableTextKey, - style: effectiveTextStyle, - readOnly: true, - toolbarOptions: widget.toolbarOptions, - textWidthBasis: - widget.textWidthBasis ?? defaultTextStyle.textWidthBasis, - textHeightBehavior: - widget.textHeightBehavior ?? defaultTextStyle.textHeightBehavior, - showSelectionHandles: _showSelectionHandles, - showCursor: widget.showCursor, - controller: _controller, - focusNode: focusNode, - strutStyle: widget.strutStyle ?? const StrutStyle(), - textAlign: - widget.textAlign ?? defaultTextStyle.textAlign ?? TextAlign.start, - textDirection: widget.textDirection, - textScaler: effectiveScaler, - autofocus: widget.autofocus, - forceLine: false, - minLines: widget.minLines, - maxLines: widget.maxLines ?? defaultTextStyle.maxLines, - selectionColor: widget.selectionColor ?? selectionColor, - selectionControls: widget.selectionEnabled - ? textSelectionControls - : null, - onSelectionChanged: _handleSelectionChanged, - onSelectionHandleTapped: _handleSelectionHandleTapped, - rendererIgnoresPointer: true, - cursorWidth: widget.cursorWidth, - cursorHeight: widget.cursorHeight, - cursorRadius: cursorRadius, - cursorColor: cursorColor, - selectionHeightStyle: widget.selectionHeightStyle, - selectionWidthStyle: widget.selectionWidthStyle, - cursorOpacityAnimates: cursorOpacityAnimates, - cursorOffset: cursorOffset, - paintCursorAboveText: paintCursorAboveText, - backgroundCursorColor: CupertinoColors.inactiveGray, - enableInteractiveSelection: widget.enableInteractiveSelection, - magnifierConfiguration: - widget.magnifierConfiguration ?? - TextMagnifier.adaptiveMagnifierConfiguration, - dragStartBehavior: widget.dragStartBehavior, - scrollPhysics: widget.scrollPhysics, - scrollBehavior: widget.scrollBehavior, - autofillHints: null, - contextMenuBuilder: widget.contextMenuBuilder, - ), - ); - - return Semantics( - label: widget.semanticsLabel, - excludeSemantics: widget.semanticsLabel != null, - onLongPress: () { - _effectiveFocusNode.requestFocus(); - }, - child: _selectionGestureDetectorBuilder.buildGestureDetector( - behavior: HitTestBehavior.translucent, - child: child, - ), - ); - } -} diff --git a/lib/common/widgets/flutter/selectable_text/selection_area.dart b/lib/common/widgets/flutter/selectable_text/selection_area.dart deleted file mode 100644 index 9c4a233028..0000000000 --- a/lib/common/widgets/flutter/selectable_text/selection_area.dart +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:PiliPlus/common/widgets/flutter/selectable_text/selectable_region.dart'; -import 'package:flutter/cupertino.dart' - hide - SelectableRegion, - SelectableRegionState, - SelectableRegionContextMenuBuilder; -import 'package:flutter/material.dart' - hide - SelectionArea, - SelectableRegion, - SelectableRegionState, - SelectableRegionContextMenuBuilder; -import 'package:flutter/rendering.dart'; - -/// A widget that introduces an area for user selections with adaptive selection -/// controls. -/// -/// This widget creates a [SelectableRegion] with platform-adaptive selection -/// controls. -/// -/// Flutter widgets are not selectable by default. To enable selection for -/// a specific screen, consider wrapping the body of the [Route] with a -/// [SelectionArea]. -/// -/// The [SelectionArea] widget must have a [Localizations] ancestor that -/// contains a [MaterialLocalizations] delegate; using the [MaterialApp] widget -/// ensures that such an ancestor is present. -/// -/// {@tool dartpad} -/// This example shows how to make a screen selectable. -/// -/// ** See code in examples/api/lib/material/selection_area/selection_area.0.dart ** -/// {@end-tool} -/// -/// See also: -/// -/// * [SelectableRegion], which provides an overview of the selection system. -/// * [SelectableText], which enables selection on a single run of text. -/// * [SelectionListener], which enables accessing the [SelectionDetails] of -/// the selectable subtree it wraps. -class SelectionArea extends StatefulWidget { - /// Creates a [SelectionArea]. - /// - /// If [selectionControls] is null, a platform specific one is used. - const SelectionArea({ - super.key, - this.focusNode, - this.selectionControls, - this.contextMenuBuilder = _defaultContextMenuBuilder, - this.magnifierConfiguration, - this.onSelectionChanged, - required this.child, - }); - - /// The configuration for the magnifier in the selection region. - /// - /// By default, builds a [CupertinoTextMagnifier] on iOS and [TextMagnifier] - /// on Android, and builds nothing on all other platforms. To suppress the - /// magnifier, consider passing [TextMagnifierConfiguration.disabled]. - /// - /// {@macro flutter.widgets.magnifier.intro} - final TextMagnifierConfiguration? magnifierConfiguration; - - /// {@macro flutter.widgets.Focus.focusNode} - final FocusNode? focusNode; - - /// The delegate to build the selection handles and toolbar. - /// - /// If it is null, the platform specific selection control is used. - final TextSelectionControls? selectionControls; - - /// {@macro flutter.widgets.EditableText.contextMenuBuilder} - /// - /// If not provided, will build a default menu based on the ambient - /// [ThemeData.platform]. - /// - /// {@tool dartpad} - /// This example shows how to build a custom context menu for any selected - /// content in a SelectionArea. - /// - /// ** See code in examples/api/lib/material/context_menu/selectable_region_toolbar_builder.0.dart ** - /// {@end-tool} - /// - /// See also: - /// - /// * [AdaptiveTextSelectionToolbar], which is built by default. - final SelectableRegionContextMenuBuilder? contextMenuBuilder; - - /// Called when the selected content changes. - final ValueChanged? onSelectionChanged; - - /// The child widget this selection area applies to. - /// - /// {@macro flutter.widgets.ProxyWidget.child} - final Widget child; - - static Widget _defaultContextMenuBuilder( - BuildContext context, - SelectableRegionState selectableRegionState, - ) => AdaptiveTextSelectionToolbar.buttonItems( - buttonItems: selectableRegionState.contextMenuButtonItems, - anchors: selectableRegionState.contextMenuAnchors, - ); - - @override - State createState() => SelectionAreaState(); -} - -/// State for a [SelectionArea]. -class SelectionAreaState extends State { - final GlobalKey _selectableRegionKey = - GlobalKey(); - - /// The [State] of the [SelectableRegion] for which this [SelectionArea] wraps. - SelectableRegionState get selectableRegion => - _selectableRegionKey.currentState!; - - @protected - @override - Widget build(BuildContext context) { - assert(debugCheckHasMaterialLocalizations(context)); - final TextSelectionControls controls = - widget.selectionControls ?? - switch (Theme.of(context).platform) { - TargetPlatform.linux || - TargetPlatform.windows => desktopTextSelectionHandleControls, - TargetPlatform.iOS => cupertinoTextSelectionHandleControls, - TargetPlatform.macOS => cupertinoDesktopTextSelectionHandleControls, - _ => materialTextSelectionHandleControls, // android, fuchsia, 鸿蒙 - }; - return SelectableRegion( - key: _selectableRegionKey, - selectionControls: controls, - focusNode: widget.focusNode, - contextMenuBuilder: widget.contextMenuBuilder, - magnifierConfiguration: - widget.magnifierConfiguration ?? - TextMagnifier.adaptiveMagnifierConfiguration, - onSelectionChanged: widget.onSelectionChanged, - child: widget.child, - ); - } -} diff --git a/lib/common/widgets/flutter/selectable_text/tap_and_drag.dart b/lib/common/widgets/flutter/selectable_text/tap_and_drag.dart deleted file mode 100644 index 7884f7469a..0000000000 --- a/lib/common/widgets/flutter/selectable_text/tap_and_drag.dart +++ /dev/null @@ -1,1125 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart' - hide TapAndHorizontalDragGestureRecognizer; - -// Examples can assume: -// void setState(VoidCallback fn) { } -// late String _last; - -double _getGlobalDistance(PointerEvent event, OffsetPair? originPosition) { - assert(originPosition != null); - final Offset offset = event.position - originPosition!.global; - return offset.distance; -} - -// The possible states of a [BaseTapAndDragGestureRecognizer]. -// -// The recognizer advances from [ready] to [possible] when it starts tracking -// a pointer in [BaseTapAndDragGestureRecognizer.addAllowedPointer]. Where it advances -// from there depends on the sequence of pointer events that is tracked by the -// recognizer, following the initial [PointerDownEvent]: -// -// * If a [PointerUpEvent] has not been tracked, the recognizer stays in the [possible] -// state as long as it continues to track a pointer. -// * If a [PointerMoveEvent] is tracked that has moved a sufficient global distance -// from the initial [PointerDownEvent] and it came before a [PointerUpEvent], then -// this recognizer moves from the [possible] state to [accepted]. -// * If a [PointerUpEvent] is tracked before the pointer has moved a sufficient global -// distance to be considered a drag, then this recognizer moves from the [possible] -// state to [ready]. -// * If a [PointerCancelEvent] is tracked then this recognizer moves from its current -// state to [ready]. -// -// Once the recognizer has stopped tracking any remaining pointers, the recognizer -// returns to the [ready] state. -enum _DragState { - // The recognizer is ready to start recognizing a drag. - ready, - - // The sequence of pointer events seen thus far is consistent with a drag but - // it has not been accepted definitively. - possible, - - // The sequence of pointer events has been accepted definitively as a drag. - accepted, -} - -// A mixin for [OneSequenceGestureRecognizer] that tracks the number of taps -// that occur in a series of [PointerEvent]s and the most recent set of -// [LogicalKeyboardKey]s pressed on the most recent tap down. -// -// A tap is tracked as part of a series of taps if: -// -// 1. The elapsed time between when a [PointerUpEvent] and the subsequent -// [PointerDownEvent] does not exceed [kDoubleTapTimeout]. -// 2. The delta between the position tapped in the global coordinate system -// and the position that was tapped previously must be less than or equal -// to [kDoubleTapSlop]. -// -// This mixin's state, i.e. the series of taps being tracked is reset when -// a tap is tracked that does not meet any of the specifications stated above. -mixin _TapStatusTrackerMixin on OneSequenceGestureRecognizer { - // Public state available to [OneSequenceGestureRecognizer]. - - // The [PointerDownEvent] that was most recently tracked in [addAllowedPointer]. - // - // This value will be null if a [PointerDownEvent] has not been tracked yet in - // [addAllowedPointer] or the timer between two taps has elapsed. - // - // This value is only reset when the timer between a [PointerUpEvent] and the - // [PointerDownEvent] times out or when a new [PointerDownEvent] is tracked in - // [addAllowedPointer]. - PointerDownEvent? get currentDown => _down; - - // The [PointerUpEvent] that was most recently tracked in [handleEvent]. - // - // This value will be null if a [PointerUpEvent] has not been tracked yet in - // [handleEvent] or the timer between two taps has elapsed. - // - // This value is only reset when the timer between a [PointerUpEvent] and the - // [PointerDownEvent] times out or when a new [PointerDownEvent] is tracked in - // [addAllowedPointer]. - PointerUpEvent? get currentUp => _up; - - // The number of consecutive taps that the most recently tracked [PointerDownEvent] - // in [currentDown] represents. - // - // This value defaults to zero, meaning a tap series is not currently being tracked. - // - // When this value is greater than zero it means [addAllowedPointer] has run - // and at least one [PointerDownEvent] belongs to the current series of taps - // being tracked. - // - // [addAllowedPointer] will either increment this value by `1` or set the value to `1` - // depending if the new [PointerDownEvent] is determined to be in the same series as the - // tap that preceded it. If too much time has elapsed between two taps, the recognizer has lost - // in the arena, the gesture has been cancelled, or the recognizer is being disposed then - // this value will be set to `0`, and a new series will begin. - int get consecutiveTapCount => _consecutiveTapCount; - - // The upper limit for the [consecutiveTapCount]. When this limit is reached - // all tap related state is reset and a new tap series is tracked. - // - // If this value is null, [consecutiveTapCount] can grow infinitely large. - int? get maxConsecutiveTap; - - // Private tap state tracked. - PointerDownEvent? _down; - PointerUpEvent? _up; - int _consecutiveTapCount = 0; - - OffsetPair? _originPosition; - int? _previousButtons; - - // For timing taps. - Timer? _consecutiveTapTimer; - Offset? _lastTapOffset; - - /// {@macro flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackStart} - VoidCallback? onTapTrackStart; - - /// {@macro flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackReset} - VoidCallback? onTapTrackReset; - - // When tracking a tap, the [consecutiveTapCount] is incremented if the given tap - // falls under the tolerance specifications and reset to 1 if not. - @override - void addAllowedPointer(PointerDownEvent event) { - super.addAllowedPointer(event); - if (_consecutiveTapTimer != null && !_consecutiveTapTimer!.isActive) { - _tapTrackerReset(); - } - if (maxConsecutiveTap == _consecutiveTapCount) { - _tapTrackerReset(); - } - _up = null; - if (_down != null && !_representsSameSeries(event)) { - // The given tap does not match the specifications of the series of taps being tracked, - // reset the tap count and related state. - _consecutiveTapCount = 1; - } else { - _consecutiveTapCount += 1; - } - _consecutiveTapTimerStop(); - // `_down` must be assigned in this method instead of [handleEvent], - // because [acceptGesture] might be called before [handleEvent], - // which may rely on `_down` to initiate a callback. - _trackTap(event); - } - - @override - void handleEvent(PointerEvent event) { - if (event is PointerMoveEvent) { - final double computedSlop = computeHitSlop(event.kind, gestureSettings); - final bool isSlopPastTolerance = - _getGlobalDistance(event, _originPosition) > computedSlop; - - if (isSlopPastTolerance) { - _consecutiveTapTimerStop(); - _previousButtons = null; - _lastTapOffset = null; - } - } else if (event is PointerUpEvent) { - _up = event; - if (_down != null) { - _consecutiveTapTimerStop(); - _consecutiveTapTimerStart(); - } - } else if (event is PointerCancelEvent) { - _tapTrackerReset(); - } - } - - @override - void rejectGesture(int pointer) { - _tapTrackerReset(); - } - - @override - void dispose() { - _tapTrackerReset(); - super.dispose(); - } - - void _trackTap(PointerDownEvent event) { - _down = event; - _previousButtons = event.buttons; - _lastTapOffset = event.position; - _originPosition = OffsetPair( - local: event.localPosition, - global: event.position, - ); - onTapTrackStart?.call(); - } - - bool _hasSameButton(int buttons) { - assert(_previousButtons != null); - if (buttons == _previousButtons!) { - return true; - } else { - return false; - } - } - - bool _isWithinConsecutiveTapTolerance(Offset secondTapOffset) { - if (_lastTapOffset == null) { - return false; - } - - final Offset difference = secondTapOffset - _lastTapOffset!; - return difference.distance <= kDoubleTapSlop; - } - - bool _representsSameSeries(PointerDownEvent event) { - return _consecutiveTapTimer != null && - _isWithinConsecutiveTapTolerance(event.position) && - _hasSameButton(event.buttons); - } - - void _consecutiveTapTimerStart() { - _consecutiveTapTimer ??= Timer( - kDoubleTapTimeout, - _consecutiveTapTimerTimeout, - ); - } - - void _consecutiveTapTimerStop() { - if (_consecutiveTapTimer != null) { - _consecutiveTapTimer!.cancel(); - _consecutiveTapTimer = null; - } - } - - void _consecutiveTapTimerTimeout() { - // The consecutive tap timer may time out before a tap down/tap up event is - // fired. In this case we should not reset the tap tracker state immediately. - // Instead we should reset the tap tracker on the next call to [addAllowedPointer], - // if the timer is no longer active. - } - - void _tapTrackerReset() { - // The timer has timed out, i.e. the time between a [PointerUpEvent] and the subsequent - // [PointerDownEvent] exceeded the duration of [kDoubleTapTimeout], so the tap belonging - // to the [PointerDownEvent] cannot be considered part of the same tap series as the - // previous [PointerUpEvent]. - _consecutiveTapTimerStop(); - _previousButtons = null; - _originPosition = null; - _lastTapOffset = null; - _consecutiveTapCount = 0; - _down = null; - _up = null; - onTapTrackReset?.call(); - } -} - -/// A base class for gesture recognizers that recognize taps and movements. -/// -/// Takes on the responsibilities of [TapGestureRecognizer] and -/// [DragGestureRecognizer] in one [GestureRecognizer]. -/// -/// ### Gesture arena behavior -/// -/// [BaseTapAndDragGestureRecognizer] competes on the pointer events of -/// [kPrimaryButton] only when it has at least one non-null `onTap*` -/// or `onDrag*` callback. -/// -/// It will declare defeat if it determines that a gesture is not a -/// tap (e.g. if the pointer is dragged too far while it's contacting the -/// screen) or a drag (e.g. if the pointer was not dragged far enough to -/// be considered a drag. -/// -/// This recognizer will not immediately declare victory for every tap that it -/// recognizes, but it declares victory for every drag. -/// -/// The recognizer will declare victory when all other recognizer's in -/// the arena have lost, if the timer of [kPressTimeout] elapses and a tap -/// series greater than 1 is being tracked, or until the pointer has moved -/// a sufficient global distance from the origin to be considered a drag. -/// -/// If this recognizer loses the arena (either by declaring defeat or by -/// another recognizer declaring victory) while the pointer is contacting the -/// screen, it will fire [onCancel] instead of [onTapUp] or [onDragEnd]. -/// -/// ### When competing with `TapGestureRecognizer` and `DragGestureRecognizer` -/// -/// Similar to [TapGestureRecognizer] and [DragGestureRecognizer], -/// [BaseTapAndDragGestureRecognizer] will not aggressively declare victory when -/// it detects a tap, so when it is competing with those gesture recognizers and -/// others it has a chance of losing. Similarly, when `eagerVictoryOnDrag` is set -/// to `false`, this recognizer will not aggressively declare victory when it -/// detects a drag. By default, `eagerVictoryOnDrag` is set to `true`, so this -/// recognizer will aggressively declare victory when it detects a drag. -/// -/// When competing against [TapGestureRecognizer], if the pointer does not move past the tap -/// tolerance, then the recognizer that entered the arena first will win. In this case the -/// gesture detected is a tap. If the pointer does travel past the tap tolerance then this -/// recognizer will be declared winner by default. The gesture detected in this case is a drag. -/// -/// When competing against [DragGestureRecognizer], if the pointer does not move a sufficient -/// global distance to be considered a drag, the recognizers will tie in the arena. If the -/// pointer does travel enough distance then the recognizer that entered the arena -/// first will win. The gesture detected in this case is a drag. -/// -/// {@tool dartpad} -/// This example shows how to use the [TapAndPanGestureRecognizer] along with a -/// [RawGestureDetector] to scale a Widget. -/// -/// ** See code in examples/api/lib/gestures/tap_and_drag/tap_and_drag.0.dart ** -/// {@end-tool} -/// -/// {@tool snippet} -/// -/// This example shows how to hook up [TapAndPanGestureRecognizer]s' to nested -/// [RawGestureDetector]s'. It assumes that the code is being used inside a [State] -/// object with a `_last` field that is then displayed as the child of the gesture detector. -/// -/// In this example, if the pointer has moved past the drag threshold, then the -/// the first [TapAndPanGestureRecognizer] instance to receive the [PointerEvent] -/// will win the arena because the recognizer will immediately declare victory. -/// -/// The first one to receive the event in the example will depend on where on both -/// containers the pointer lands first. If your pointer begins in the overlapping -/// area of both containers, then the inner-most widget will receive the event first. -/// If your pointer begins in the yellow container then it will be the first to -/// receive the event. -/// -/// If the pointer has not moved past the drag threshold, then the first recognizer -/// to enter the arena will win (i.e. they both tie and the gesture arena will call -/// [GestureArenaManager.sweep] so the first member of the arena will win). -/// -/// ```dart -/// RawGestureDetector( -/// gestures: { -/// TapAndPanGestureRecognizer: GestureRecognizerFactoryWithHandlers( -/// () => TapAndPanGestureRecognizer(), -/// (TapAndPanGestureRecognizer instance) { -/// instance -/// ..onTapDown = (TapDragDownDetails details) { setState(() { _last = 'down_a'; }); } -/// ..onDragStart = (TapDragStartDetails details) { setState(() { _last = 'drag_start_a'; }); } -/// ..onDragUpdate = (TapDragUpdateDetails details) { setState(() { _last = 'drag_update_a'; }); } -/// ..onDragEnd = (TapDragEndDetails details) { setState(() { _last = 'drag_end_a'; }); } -/// ..onTapUp = (TapDragUpDetails details) { setState(() { _last = 'up_a'; }); } -/// ..onCancel = () { setState(() { _last = 'cancel_a'; }); }; -/// }, -/// ), -/// }, -/// child: Container( -/// width: 300.0, -/// height: 300.0, -/// color: Colors.yellow, -/// alignment: Alignment.center, -/// child: RawGestureDetector( -/// gestures: { -/// TapAndPanGestureRecognizer: GestureRecognizerFactoryWithHandlers( -/// () => TapAndPanGestureRecognizer(), -/// (TapAndPanGestureRecognizer instance) { -/// instance -/// ..onTapDown = (TapDragDownDetails details) { setState(() { _last = 'down_b'; }); } -/// ..onDragStart = (TapDragStartDetails details) { setState(() { _last = 'drag_start_b'; }); } -/// ..onDragUpdate = (TapDragUpdateDetails details) { setState(() { _last = 'drag_update_b'; }); } -/// ..onDragEnd = (TapDragEndDetails details) { setState(() { _last = 'drag_end_b'; }); } -/// ..onTapUp = (TapDragUpDetails details) { setState(() { _last = 'up_b'; }); } -/// ..onCancel = () { setState(() { _last = 'cancel_b'; }); }; -/// }, -/// ), -/// }, -/// child: Container( -/// width: 150.0, -/// height: 150.0, -/// color: Colors.blue, -/// child: Text(_last), -/// ), -/// ), -/// ), -/// ) -/// ``` -/// {@end-tool} -sealed class BaseTapAndDragGestureRecognizer - extends OneSequenceGestureRecognizer - with _TapStatusTrackerMixin { - /// Creates a tap and drag gesture recognizer. - /// - /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} - BaseTapAndDragGestureRecognizer({ - super.debugOwner, - super.supportedDevices, - super.allowedButtonsFilter, - this.eagerVictoryOnDrag = true, - }) : _deadline = kPressTimeout, - dragStartBehavior = DragStartBehavior.start; - - /// Configure the behavior of offsets passed to [onDragStart]. - /// - /// If set to [DragStartBehavior.start], the [onDragStart] callback will be called - /// with the position of the pointer at the time this gesture recognizer won - /// the arena. If [DragStartBehavior.down], [onDragStart] will be called with - /// the position of the first detected down event for the pointer. When there - /// are no other gestures competing with this gesture in the arena, there's - /// no difference in behavior between the two settings. - /// - /// For more information about the gesture arena: - /// https://flutter.dev/to/gesture-disambiguation - /// - /// By default, the drag start behavior is [DragStartBehavior.start]. - /// - /// See also: - /// - /// * [DragGestureRecognizer.dragStartBehavior], which includes more details and an example. - DragStartBehavior dragStartBehavior; - - /// The frequency at which the [onDragUpdate] callback is called. - /// - /// The value defaults to null, meaning there is no delay for [onDragUpdate] callback. - Duration? dragUpdateThrottleFrequency; - - /// An upper bound for the amount of taps that can belong to one tap series. - /// - /// When this limit is reached the series of taps being tracked by this - /// recognizer will be reset. - @override - int? maxConsecutiveTap; - - /// Whether this recognizer eagerly declares victory when it has detected - /// a drag. - /// - /// When this value is `false`, this recognizer will wait until it is the last - /// recognizer in the gesture arena before declaring victory on a drag. - /// - /// Defaults to `true`. - bool eagerVictoryOnDrag; - - /// {@macro flutter.gestures.tap.TapGestureRecognizer.onTapDown} - /// - /// This triggers after the down event, once a short timeout ([kPressTimeout]) has - /// elapsed, or once the gestures has won the arena, whichever comes first. - /// - /// The position of the pointer is provided in the callback's `details` - /// argument, which is a [TapDragDownDetails] object. - /// - /// {@template flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.tapStatusTrackerData} - /// The number of consecutive taps, and the keys that were pressed on tap down - /// are also provided in the callback's `details` argument. - /// {@endtemplate} - /// - /// See also: - /// - /// * [kPrimaryButton], the button this callback responds to. - /// * [TapDragDownDetails], which is passed as an argument to this callback. - GestureTapDragDownCallback? onTapDown; - - /// {@macro flutter.gestures.tap.TapGestureRecognizer.onTapUp} - /// - /// This triggers on the up event, if the recognizer wins the arena with it - /// or has previously won. - /// - /// The position of the pointer is provided in the callback's `details` - /// argument, which is a [TapDragUpDetails] object. - /// - /// {@macro flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.tapStatusTrackerData} - /// - /// See also: - /// - /// * [kPrimaryButton], the button this callback responds to. - /// * [TapDragUpDetails], which is passed as an argument to this callback. - GestureTapDragUpCallback? onTapUp; - - /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onStart} - /// - /// The position of the pointer is provided in the callback's `details` - /// argument, which is a [TapDragStartDetails] object. The [dragStartBehavior] - /// determines this position. - /// - /// {@macro flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.tapStatusTrackerData} - /// - /// See also: - /// - /// * [kPrimaryButton], the button this callback responds to. - /// * [TapDragStartDetails], which is passed as an argument to this callback. - GestureTapDragStartCallback? onDragStart; - - /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onUpdate} - /// - /// The distance traveled by the pointer since the last update is provided in - /// the callback's `details` argument, which is a [TapDragUpdateDetails] object. - /// - /// {@macro flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.tapStatusTrackerData} - /// - /// See also: - /// - /// * [kPrimaryButton], the button this callback responds to. - /// * [TapDragUpdateDetails], which is passed as an argument to this callback. - GestureTapDragUpdateCallback? onDragUpdate; - - /// {@macro flutter.gestures.monodrag.DragGestureRecognizer.onEnd} - /// - /// The velocity is provided in the callback's `details` argument, which is a - /// [TapDragEndDetails] object. - /// - /// {@macro flutter.gestures.selectionrecognizers.BaseTapAndDragGestureRecognizer.tapStatusTrackerData} - /// - /// See also: - /// - /// * [kPrimaryButton], the button this callback responds to. - /// * [TapDragEndDetails], which is passed as an argument to this callback. - GestureTapDragEndCallback? onDragEnd; - - /// The pointer that previously triggered [onTapDown] did not complete. - /// - /// This is called when a [PointerCancelEvent] is tracked when the [onTapDown] callback - /// was previously called. - /// - /// It may also be called if a [PointerUpEvent] is tracked after the pointer has moved - /// past the tap tolerance but not past the drag tolerance, and the recognizer has not - /// yet won the arena. - /// - /// See also: - /// - /// * [kPrimaryButton], the button this callback responds to. - GestureCancelCallback? onCancel; - - // Tap related state. - bool _pastSlopTolerance = false; - bool _sentTapDown = false; - bool _wonArenaForPrimaryPointer = false; - - // Primary pointer being tracked by this recognizer. - int? _primaryPointer; - Timer? _deadlineTimer; - // The recognizer will call [onTapDown] after this amount of time has elapsed - // since starting to track the primary pointer. - // - // [onTapDown] will not be called if the primary pointer is - // accepted, rejected, or all pointers are up or canceled before [_deadline]. - final Duration _deadline; - - // Drag related state. - _DragState _dragState = _DragState.ready; - PointerEvent? _start; - late OffsetPair _initialPosition; - late OffsetPair _currentPosition; - // late double _globalDistanceMoved; - late double _globalDistanceMovedAllAxes; - - // For drag update throttle. - TapDragUpdateDetails? _lastDragUpdateDetails; - Timer? _dragUpdateThrottleTimer; - - final Set _acceptedActivePointers = {}; - - // Offset _getDeltaForDetails(Offset delta); - // double? _getPrimaryValueFromOffset(Offset value); - bool _hasSufficientGlobalDistanceToAccept( - PointerDeviceKind pointerDeviceKind, - ); - - // Drag updates may require throttling to avoid excessive updating, such as for text layouts in text - // fields. The frequency of invocations is controlled by the [dragUpdateThrottleFrequency]. - // - // Once the drag gesture ends, any pending drag update will be fired - // immediately. See [_checkDragEnd]. - void _handleDragUpdateThrottled() { - assert(_lastDragUpdateDetails != null); - if (onDragUpdate != null) { - invokeCallback( - 'onDragUpdate', - () => onDragUpdate!(_lastDragUpdateDetails!), - ); - } - _dragUpdateThrottleTimer = null; - _lastDragUpdateDetails = null; - } - - @override - bool isPointerAllowed(PointerEvent event) { - if (_primaryPointer == null) { - switch (event.buttons) { - case kPrimaryButton: - if (onTapDown == null && - onDragStart == null && - onDragUpdate == null && - onDragEnd == null && - onTapUp == null && - onCancel == null) { - return false; - } - default: - return false; - } - } else { - if (event.pointer != _primaryPointer) { - return false; - } - } - - return super.isPointerAllowed(event as PointerDownEvent); - } - - @override - void addAllowedPointer(PointerDownEvent event) { - if (_dragState == _DragState.ready) { - super.addAllowedPointer(event); - _primaryPointer = event.pointer; - // _globalDistanceMoved = 0.0; - _globalDistanceMovedAllAxes = 0.0; - _dragState = _DragState.possible; - _initialPosition = OffsetPair( - global: event.position, - local: event.localPosition, - ); - _currentPosition = _initialPosition; - _deadlineTimer = Timer( - _deadline, - () => _didExceedDeadlineWithEvent(event), - ); - } - } - - @override - void handleNonAllowedPointer(PointerDownEvent event) { - // There can be multiple drags simultaneously. Their effects are combined. - if (event.buttons != kPrimaryButton) { - if (!_wonArenaForPrimaryPointer) { - super.handleNonAllowedPointer(event); - } - } - } - - @override - void acceptGesture(int pointer) { - if (pointer != _primaryPointer) { - return; - } - - _stopDeadlineTimer(); - - assert(!_acceptedActivePointers.contains(pointer)); - _acceptedActivePointers.add(pointer); - - // Called when this recognizer is accepted by the [GestureArena]. - if (currentDown != null) { - _checkTapDown(currentDown!); - } - - _wonArenaForPrimaryPointer = true; - - // resolve(GestureDisposition.accepted) will be called when the [PointerMoveEvent] - // has moved a sufficient global distance to be considered a drag and - // `eagerVictoryOnDrag` is set to `true`. - if (_start != null && eagerVictoryOnDrag) { - assert(_dragState == _DragState.accepted); - assert(currentUp == null); - _acceptDrag(_start!); - } - - // This recognizer will wait until it is the last one in the gesture arena - // before accepting a drag when `eagerVictoryOnDrag` is set to `false`. - if (_start != null && !eagerVictoryOnDrag) { - assert(_dragState == _DragState.possible); - assert(currentUp == null); - _dragState = _DragState.accepted; - _acceptDrag(_start!); - } - - if (currentUp != null) { - _checkTapUp(currentUp!); - } - } - - @override - void didStopTrackingLastPointer(int pointer) { - switch (_dragState) { - case _DragState.ready: - _checkCancel(); - resolve(GestureDisposition.rejected); - - case _DragState.possible: - if (_pastSlopTolerance) { - // This means the pointer was not accepted as a tap. - if (_wonArenaForPrimaryPointer) { - // If the recognizer has already won the arena for the primary pointer being tracked - // but the pointer has exceeded the tap tolerance, then the pointer is accepted as a - // drag gesture. - if (currentDown != null) { - if (!_acceptedActivePointers.remove(pointer)) { - resolvePointer(pointer, GestureDisposition.rejected); - } - _dragState = _DragState.accepted; - _acceptDrag(currentDown!); - _checkDragEnd(); - } - } else { - _checkCancel(); - resolve(GestureDisposition.rejected); - } - } else { - // The pointer is accepted as a tap. - if (currentUp != null) { - _checkTapUp(currentUp!); - } - } - - case _DragState.accepted: - // For the case when the pointer has been accepted as a drag. - // Meaning [_checkTapDown] and [_checkDragStart] have already ran. - _checkDragEnd(); - } - - _stopDeadlineTimer(); - _start = null; - _dragState = _DragState.ready; - _pastSlopTolerance = false; - } - - @override - void handleEvent(PointerEvent event) { - if (event.pointer != _primaryPointer) { - return; - } - super.handleEvent(event); - if (event is PointerMoveEvent) { - // Receiving a [PointerMoveEvent], does not automatically mean the pointer - // being tracked is doing a drag gesture. There is some drift that can happen - // between the initial [PointerDownEvent] and subsequent [PointerMoveEvent]s. - // Accessing [_pastSlopTolerance] lets us know if our tap has moved past the - // acceptable tolerance. If the pointer does not move past this tolerance than - // it is not considered a drag. - // - // To be recognized as a drag, the [PointerMoveEvent] must also have moved - // a sufficient global distance from the initial [PointerDownEvent] to be - // accepted as a drag. This logic is handled in [_hasSufficientGlobalDistanceToAccept]. - // - // The recognizer will also detect the gesture as a drag when the pointer - // has been accepted and it has moved past the [slopTolerance] but has not moved - // a sufficient global distance from the initial position to be considered a drag. - // In this case since the gesture cannot be a tap, it defaults to a drag. - final double computedSlop = computeHitSlop(event.kind, gestureSettings); - _pastSlopTolerance = - _pastSlopTolerance || - _getGlobalDistance(event, _initialPosition) > computedSlop; - - if (_dragState == _DragState.accepted) { - _currentPosition = OffsetPair.fromEventPosition(event); - _checkDragUpdate(event); - } else if (_dragState == _DragState.possible) { - if (_start == null) { - // Only check for a drag if the start of a drag was not already identified. - _checkDrag(event); - } - - // This can occur when the recognizer is accepted before a [PointerMoveEvent] has been - // received that moves the pointer a sufficient global distance to be considered a drag. - if (_start != null && _wonArenaForPrimaryPointer) { - _dragState = _DragState.accepted; - _acceptDrag(_start!); - } - } - } else if (event is PointerUpEvent) { - if (_dragState == _DragState.possible) { - // The drag has not been accepted before a [PointerUpEvent], therefore the recognizer - // attempts to recognize a tap. - stopTrackingIfPointerNoLongerDown(event); - } else if (_dragState == _DragState.accepted) { - _giveUpPointer(event.pointer); - } - } else if (event is PointerCancelEvent) { - _dragState = _DragState.ready; - _giveUpPointer(event.pointer); - } - } - - @override - void rejectGesture(int pointer) { - if (pointer != _primaryPointer) { - return; - } - super.rejectGesture(pointer); - - _stopDeadlineTimer(); - _giveUpPointer(pointer); - _resetTaps(); - _resetDragUpdateThrottle(); - } - - @override - void dispose() { - _stopDeadlineTimer(); - _resetDragUpdateThrottle(); - super.dispose(); - } - - @override - String get debugDescription => 'tap_and_drag'; - - void _acceptDrag(PointerEvent event) { - assert(_dragState == _DragState.accepted); - - if (!_wonArenaForPrimaryPointer) { - return; - } - - if (dragStartBehavior == DragStartBehavior.start) { - _initialPosition += OffsetPair( - global: event.delta, - local: event.localDelta, - ); - _currentPosition = _initialPosition; - } - _checkDragStart(event); - final Offset localDelta = event.localDelta; - if (localDelta != Offset.zero) { - _currentPosition = OffsetPair.fromEventPosition(event); - final Offset correctedLocalPosition = _initialPosition.local + localDelta; - final Matrix4? localToGlobalTransform = event.transform == null - ? null - : Matrix4.tryInvert(event.transform!); - final Offset globalUpdateDelta = PointerEvent.transformDeltaViaPositions( - transform: localToGlobalTransform, - untransformedDelta: localDelta, - untransformedEndPosition: correctedLocalPosition, - ); - final updateDelta = OffsetPair( - local: localDelta, - global: globalUpdateDelta, - ); - // Only adds delta for down behaviour - _checkDragUpdate(event, corrected: _initialPosition + updateDelta); - } - } - - void _checkDrag(PointerMoveEvent event) { - final Matrix4? localToGlobalTransform = event.transform == null - ? null - : Matrix4.tryInvert(event.transform!); - // final Offset movedLocally = _getDeltaForDetails(event.localDelta); - // _globalDistanceMoved += - // PointerEvent.transformDeltaViaPositions( - // transform: localToGlobalTransform, - // untransformedDelta: movedLocally, - // untransformedEndPosition: event.localPosition, - // ).distance * - // (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign; - _globalDistanceMovedAllAxes += - PointerEvent.transformDeltaViaPositions( - transform: localToGlobalTransform, - untransformedDelta: event.localDelta, - untransformedEndPosition: event.localPosition, - ).distance * - 1.sign; - if (_hasSufficientGlobalDistanceToAccept(event.kind) || - (_wonArenaForPrimaryPointer && - _globalDistanceMovedAllAxes.abs() > - computePanSlop(event.kind, gestureSettings))) { - _start = event; - if (eagerVictoryOnDrag) { - _dragState = _DragState.accepted; - if (!_wonArenaForPrimaryPointer) { - resolve(GestureDisposition.accepted); - } - } - } - } - - void _checkTapDown(PointerDownEvent event) { - if (_sentTapDown) { - return; - } - - final details = TapDragDownDetails( - globalPosition: event.position, - localPosition: event.localPosition, - kind: getKindForPointer(event.pointer), - consecutiveTapCount: consecutiveTapCount, - ); - - if (onTapDown != null) { - invokeCallback('onTapDown', () => onTapDown!(details)); - } - - _sentTapDown = true; - } - - void _checkTapUp(PointerUpEvent event) { - if (!_wonArenaForPrimaryPointer) { - return; - } - - final upDetails = TapDragUpDetails( - kind: event.kind, - globalPosition: event.position, - localPosition: event.localPosition, - consecutiveTapCount: consecutiveTapCount, - ); - - if (onTapUp != null) { - invokeCallback('onTapUp', () => onTapUp!(upDetails)); - } - - _resetTaps(); - if (!_acceptedActivePointers.remove(event.pointer)) { - resolvePointer(event.pointer, GestureDisposition.rejected); - } - } - - void _checkDragStart(PointerEvent event) { - if (onDragStart != null) { - final details = TapDragStartDetails( - sourceTimeStamp: event.timeStamp, - globalPosition: _initialPosition.global, - localPosition: _initialPosition.local, - kind: getKindForPointer(event.pointer), - consecutiveTapCount: consecutiveTapCount, - ); - - invokeCallback('onDragStart', () => onDragStart!(details)); - } - - _start = null; - } - - void _checkDragUpdate(PointerEvent event, {OffsetPair? corrected}) { - final Offset globalPosition = corrected?.global ?? event.position; - final Offset localPosition = corrected?.local ?? event.localPosition; - - final details = TapDragUpdateDetails( - sourceTimeStamp: event.timeStamp, - delta: event.localDelta, - globalPosition: globalPosition, - kind: getKindForPointer(event.pointer), - localPosition: localPosition, - offsetFromOrigin: globalPosition - _initialPosition.global, - localOffsetFromOrigin: localPosition - _initialPosition.local, - consecutiveTapCount: consecutiveTapCount, - ); - - if (dragUpdateThrottleFrequency != null) { - _lastDragUpdateDetails = details; - // Only schedule a new timer if there's not one pending. - _dragUpdateThrottleTimer ??= Timer( - dragUpdateThrottleFrequency!, - _handleDragUpdateThrottled, - ); - } else { - if (onDragUpdate != null) { - invokeCallback('onDragUpdate', () => onDragUpdate!(details)); - } - } - } - - void _checkDragEnd() { - final Offset globalPosition = _currentPosition.global; - final Offset localPosition = _currentPosition.local; - - if (_dragUpdateThrottleTimer != null) { - // If there's already an update scheduled, trigger it immediately and - // cancel the timer. - _dragUpdateThrottleTimer!.cancel(); - _handleDragUpdateThrottled(); - } - - final endDetails = TapDragEndDetails( - globalPosition: globalPosition, - localPosition: localPosition, - primaryVelocity: 0.0, - consecutiveTapCount: consecutiveTapCount, - ); - - if (onDragEnd != null) { - invokeCallback('onDragEnd', () => onDragEnd!(endDetails)); - } - - _resetTaps(); - _resetDragUpdateThrottle(); - } - - void _checkCancel() { - if (!_sentTapDown) { - // Do not fire tap cancel if [onTapDown] was never called. - return; - } - if (onCancel != null) { - invokeCallback('onCancel', onCancel!); - } - _resetDragUpdateThrottle(); - _resetTaps(); - } - - void _didExceedDeadlineWithEvent(PointerDownEvent event) { - _didExceedDeadline(); - } - - void _didExceedDeadline() { - if (currentDown != null) { - _checkTapDown(currentDown!); - - if (consecutiveTapCount > 1) { - // If our consecutive tap count is greater than 1, i.e. is a double tap or greater, - // then this recognizer declares victory to prevent the [LongPressGestureRecognizer] - // from declaring itself the winner if a double tap is held for too long. - resolve(GestureDisposition.accepted); - } - } - } - - void _giveUpPointer(int pointer) { - stopTrackingPointer(pointer); - // If the pointer was never accepted, then it is rejected since this recognizer is no longer - // interested in winning the gesture arena for it. - if (!_acceptedActivePointers.remove(pointer)) { - resolvePointer(pointer, GestureDisposition.rejected); - } - } - - void _resetTaps() { - _sentTapDown = false; - _wonArenaForPrimaryPointer = false; - _primaryPointer = null; - } - - void _resetDragUpdateThrottle() { - if (dragUpdateThrottleFrequency == null) { - return; - } - _lastDragUpdateDetails = null; - if (_dragUpdateThrottleTimer != null) { - _dragUpdateThrottleTimer!.cancel(); - _dragUpdateThrottleTimer = null; - } - } - - void _stopDeadlineTimer() { - if (_deadlineTimer != null) { - _deadlineTimer!.cancel(); - _deadlineTimer = null; - } - } -} - -/// Recognizes taps along with movement in the horizontal direction. -/// -/// Before this recognizer has won the arena for the primary pointer being tracked, -/// it will only accept a drag on the horizontal axis. If a drag is detected after -/// this recognizer has won the arena then it will accept a drag on any axis. -/// -/// See also: -/// -/// * [BaseTapAndDragGestureRecognizer], for the class that provides the main -/// implementation details of this recognizer. -/// * [TapAndPanGestureRecognizer], for a similar recognizer that accepts a drag -/// on any axis regardless if the recognizer has won the arena for the primary -/// pointer being tracked. -/// * [HorizontalDragGestureRecognizer], for a similar recognizer that only recognizes -/// horizontal movement. -class TapAndHorizontalDragGestureRecognizer - extends BaseTapAndDragGestureRecognizer { - /// Create a gesture recognizer for interactions in the horizontal axis. - /// - /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} - TapAndHorizontalDragGestureRecognizer({ - super.debugOwner, - super.supportedDevices, - }); - - @override - bool _hasSufficientGlobalDistanceToAccept( - PointerDeviceKind pointerDeviceKind, - ) { - return false; - // return _globalDistanceMoved.abs() > - // computeHitSlop(pointerDeviceKind, gestureSettings); - } - - // @override - // Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0); - - // @override - // double _getPrimaryValueFromOffset(Offset value) => value.dx; - - @override - String get debugDescription => 'tap and horizontal drag'; -} - -/// {@template flutter.gestures.selectionrecognizers.TapAndPanGestureRecognizer} -/// Recognizes taps along with both horizontal and vertical movement. -/// -/// This recognizer will accept a drag on any axis, regardless if it has won the -/// arena for the primary pointer being tracked. -/// -/// See also: -/// -/// * [BaseTapAndDragGestureRecognizer], for the class that provides the main -/// implementation details of this recognizer. -/// * [TapAndHorizontalDragGestureRecognizer], for a similar recognizer that -/// only accepts horizontal drags before it has won the arena for the primary -/// pointer being tracked. -/// * [PanGestureRecognizer], for a similar recognizer that only recognizes -/// movement. -/// {@endtemplate} -class TapAndPanGestureRecognizer extends BaseTapAndDragGestureRecognizer { - /// Create a gesture recognizer for interactions on a plane. - TapAndPanGestureRecognizer({super.debugOwner, super.supportedDevices}); - - @override - bool _hasSufficientGlobalDistanceToAccept( - PointerDeviceKind pointerDeviceKind, - ) { - return true; - // return _globalDistanceMoved.abs() > - // computePanSlop(pointerDeviceKind, gestureSettings); - } - - // @override - // Offset _getDeltaForDetails(Offset delta) => delta; - - // @override - // double? _getPrimaryValueFromOffset(Offset value) => null; - - @override - String get debugDescription => 'tap and pan'; -} diff --git a/lib/common/widgets/flutter/selectable_text/text_selection.dart b/lib/common/widgets/flutter/selectable_text/text_selection.dart deleted file mode 100644 index 68494305af..0000000000 --- a/lib/common/widgets/flutter/selectable_text/text_selection.dart +++ /dev/null @@ -1,415 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:math' as math; - -import 'package:PiliPlus/common/widgets/flutter/selectable_text/tap_and_drag.dart'; -import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/gestures.dart' - hide - BaseTapAndDragGestureRecognizer, - TapAndHorizontalDragGestureRecognizer, - TapAndPanGestureRecognizer; -import 'package:flutter/material.dart' hide TextSelectionGestureDetector; - -class CustomTextSelectionGestureDetectorBuilder - extends TextSelectionGestureDetectorBuilder { - CustomTextSelectionGestureDetectorBuilder({required super.delegate}); - - @override - Widget buildGestureDetector({ - Key? key, - HitTestBehavior? behavior, - required Widget child, - }) { - return TextSelectionGestureDetector( - key: key, - onTapTrackStart: onTapTrackStart, - onTapTrackReset: onTapTrackReset, - onTapDown: onTapDown, - onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null, - onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null, - onSecondaryTap: onSecondaryTap, - onSecondaryTapDown: onSecondaryTapDown, - onSingleTapUp: onSingleTapUp, - onSingleTapCancel: onSingleTapCancel, - onUserTap: onUserTap, - onSingleLongTapStart: onSingleLongTapStart, - onSingleLongTapMoveUpdate: onSingleLongTapMoveUpdate, - onSingleLongTapEnd: onSingleLongTapEnd, - onSingleLongTapCancel: onSingleLongTapCancel, - onDoubleTapDown: onDoubleTapDown, - onTripleTapDown: onTripleTapDown, - onDragSelectionStart: onDragSelectionStart, - onDragSelectionUpdate: onDragSelectionUpdate, - onDragSelectionEnd: onDragSelectionEnd, - onUserTapAlwaysCalled: onUserTapAlwaysCalled, - behavior: behavior, - child: child, - ); - } -} - -/// A gesture detector to respond to non-exclusive event chains for a text field. -/// -/// An ordinary [GestureDetector] configured to handle events like tap and -/// double tap will only recognize one or the other. This widget detects both: -/// the first tap and then any subsequent taps that occurs within a time limit -/// after the first. -/// -/// See also: -/// -/// * [TextField], a Material text field which uses this gesture detector. -/// * [CupertinoTextField], a Cupertino text field which uses this gesture -/// detector. -class TextSelectionGestureDetector extends StatefulWidget { - /// Create a [TextSelectionGestureDetector]. - /// - /// Multiple callbacks can be called for one sequence of input gesture. - const TextSelectionGestureDetector({ - super.key, - this.onTapTrackStart, - this.onTapTrackReset, - this.onTapDown, - this.onForcePressStart, - this.onForcePressEnd, - this.onSecondaryTap, - this.onSecondaryTapDown, - this.onSingleTapUp, - this.onSingleTapCancel, - this.onUserTap, - this.onSingleLongTapStart, - this.onSingleLongTapMoveUpdate, - this.onSingleLongTapEnd, - this.onSingleLongTapCancel, - this.onDoubleTapDown, - this.onTripleTapDown, - this.onDragSelectionStart, - this.onDragSelectionUpdate, - this.onDragSelectionEnd, - this.onUserTapAlwaysCalled = false, - this.behavior, - required this.child, - }); - - /// {@template flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackStart} - /// Callback used to indicate that a tap tracking has started upon - /// a [PointerDownEvent]. - /// {@endtemplate} - final VoidCallback? onTapTrackStart; - - /// {@template flutter.gestures.selectionrecognizers.TextSelectionGestureDetector.onTapTrackReset} - /// Callback used to indicate that a tap tracking has been reset which - /// happens on the next [PointerDownEvent] after the timer between two taps - /// elapses, the recognizer loses the arena, the gesture is cancelled or - /// the recognizer is disposed of. - /// {@endtemplate} - final VoidCallback? onTapTrackReset; - - /// Called for every tap down including every tap down that's part of a - /// double click or a long press, except touches that include enough movement - /// to not qualify as taps (e.g. pans and flings). - final GestureTapDragDownCallback? onTapDown; - - /// Called when a pointer has tapped down and the force of the pointer has - /// just become greater than [ForcePressGestureRecognizer.startPressure]. - final GestureForcePressStartCallback? onForcePressStart; - - /// Called when a pointer that had previously triggered [onForcePressStart] is - /// lifted off the screen. - final GestureForcePressEndCallback? onForcePressEnd; - - /// Called for a tap event with the secondary mouse button. - final GestureTapCallback? onSecondaryTap; - - /// Called for a tap down event with the secondary mouse button. - final GestureTapDownCallback? onSecondaryTapDown; - - /// Called for the first tap in a series of taps, consecutive taps do not call - /// this method. - /// - /// For example, if the detector was configured with [onTapDown] and - /// [onDoubleTapDown], three quick taps would be recognized as a single tap - /// down, followed by a tap up, then a double tap down, followed by a single tap down. - final GestureTapDragUpCallback? onSingleTapUp; - - /// Called for each touch that becomes recognized as a gesture that is not a - /// short tap, such as a long tap or drag. It is called at the moment when - /// another gesture from the touch is recognized. - final GestureCancelCallback? onSingleTapCancel; - - /// Called for the first tap in a series of taps when [onUserTapAlwaysCalled] is - /// disabled, which is the default behavior. - /// - /// When [onUserTapAlwaysCalled] is enabled, this is called for every tap, - /// including consecutive taps. - final GestureTapCallback? onUserTap; - - /// Called for a single long tap that's sustained for longer than - /// [kLongPressTimeout] but not necessarily lifted. Not called for a - /// double-tap-hold, which calls [onDoubleTapDown] instead. - final GestureLongPressStartCallback? onSingleLongTapStart; - - /// Called after [onSingleLongTapStart] when the pointer is dragged. - final GestureLongPressMoveUpdateCallback? onSingleLongTapMoveUpdate; - - /// Called after [onSingleLongTapStart] when the pointer is lifted. - final GestureLongPressEndCallback? onSingleLongTapEnd; - - /// Called after [onSingleLongTapStart] when the pointer is canceled. - final GestureLongPressCancelCallback? onSingleLongTapCancel; - - /// Called after a momentary hold or a short tap that is close in space and - /// time (within [kDoubleTapTimeout]) to a previous short tap. - final GestureTapDragDownCallback? onDoubleTapDown; - - /// Called after a momentary hold or a short tap that is close in space and - /// time (within [kDoubleTapTimeout]) to a previous double-tap. - final GestureTapDragDownCallback? onTripleTapDown; - - /// Called when a mouse starts dragging to select text. - final GestureTapDragStartCallback? onDragSelectionStart; - - /// Called repeatedly as a mouse moves while dragging. - final GestureTapDragUpdateCallback? onDragSelectionUpdate; - - /// Called when a mouse that was previously dragging is released. - final GestureTapDragEndCallback? onDragSelectionEnd; - - /// Whether [onUserTap] will be called for all taps including consecutive taps. - /// - /// Defaults to false, so [onUserTap] is only called for each distinct tap. - final bool onUserTapAlwaysCalled; - - /// How this gesture detector should behave during hit testing. - /// - /// This defaults to [HitTestBehavior.deferToChild]. - final HitTestBehavior? behavior; - - /// Child below this widget. - final Widget child; - - @override - State createState() => _TextSelectionGestureDetectorState(); -} - -class _TextSelectionGestureDetectorState - extends State { - // Converts the details.consecutiveTapCount from a TapAndDrag*Details object, - // which can grow to be infinitely large, to a value between 1 and 3. The value - // that the raw count is converted to is based on the default observed behavior - // on the native platforms. - // - // This method should be used in all instances when details.consecutiveTapCount - // would be used. - static int _getEffectiveConsecutiveTapCount(int rawCount) { - switch (defaultTargetPlatform) { - case TargetPlatform.iOS: - case TargetPlatform.macOS: - // From observation, these platform's either hold their tap count at 3. - // For example on macOS, when going past a triple click, the selection - // should be retained at the paragraph that was first selected on triple - // click. - return math.min(rawCount, 3); - case TargetPlatform.windows: - // From observation, this platform's consecutive tap actions alternate - // between double click and triple click actions. For example, after a - // triple click has selected a paragraph, on the next click the word at - // the clicked position will be selected, and on the next click the - // paragraph at the position is selected. - return rawCount < 2 ? rawCount : 2 + rawCount % 2; - case _: //TargetPlatform.android:TargetPlatform.fuchsia:TargetPlatform.linux:鸿蒙 - // From observation, these platform's reset their tap count to 0 when - // the number of consecutive taps exceeds 3. For example on Debian Linux - // with GTK, when going past a triple click, on the fourth click the - // selection is moved to the precise click position, on the fifth click - // the word at the position is selected, and on the sixth click the - // paragraph at the position is selected. - return rawCount <= 3 - ? rawCount - : (rawCount % 3 == 0 ? 3 : rawCount % 3); - } - } - - void _handleTapTrackStart() { - widget.onTapTrackStart?.call(); - } - - void _handleTapTrackReset() { - widget.onTapTrackReset?.call(); - } - - // The down handler is force-run on success of a single tap and optimistically - // run before a long press success. - void _handleTapDown(TapDragDownDetails details) { - widget.onTapDown?.call(details); - // This isn't detected as a double tap gesture in the gesture recognizer - // because it's 2 single taps, each of which may do different things depending - // on whether it's a single tap, the first tap of a double tap, the second - // tap held down, a clean double tap etc. - if (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 2) { - return widget.onDoubleTapDown?.call(details); - } - - if (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 3) { - return widget.onTripleTapDown?.call(details); - } - } - - void _handleTapUp(TapDragUpDetails details) { - if (_getEffectiveConsecutiveTapCount(details.consecutiveTapCount) == 1) { - widget.onSingleTapUp?.call(details); - widget.onUserTap?.call(); - } else if (widget.onUserTapAlwaysCalled) { - widget.onUserTap?.call(); - } - } - - void _handleTapCancel() { - widget.onSingleTapCancel?.call(); - } - - void _handleDragStart(TapDragStartDetails details) { - widget.onDragSelectionStart?.call(details); - } - - void _handleDragUpdate(TapDragUpdateDetails details) { - widget.onDragSelectionUpdate?.call(details); - } - - void _handleDragEnd(TapDragEndDetails details) { - widget.onDragSelectionEnd?.call(details); - } - - void _forcePressStarted(ForcePressDetails details) { - widget.onForcePressStart?.call(details); - } - - void _forcePressEnded(ForcePressDetails details) { - widget.onForcePressEnd?.call(details); - } - - void _handleLongPressStart(LongPressStartDetails details) { - widget.onSingleLongTapStart?.call(details); - } - - void _handleLongPressMoveUpdate(LongPressMoveUpdateDetails details) { - widget.onSingleLongTapMoveUpdate?.call(details); - } - - void _handleLongPressEnd(LongPressEndDetails details) { - widget.onSingleLongTapEnd?.call(details); - } - - void _handleLongPressCancel() { - widget.onSingleLongTapCancel?.call(); - } - - @override - Widget build(BuildContext context) { - final gestures = {}; - - gestures[TapGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => TapGestureRecognizer(debugOwner: this), - (TapGestureRecognizer instance) { - instance - ..onSecondaryTap = widget.onSecondaryTap - ..onSecondaryTapDown = widget.onSecondaryTapDown; - }, - ); - - if (widget.onSingleLongTapStart != null || - widget.onSingleLongTapMoveUpdate != null || - widget.onSingleLongTapEnd != null || - widget.onSingleLongTapCancel != null) { - gestures[LongPressGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => LongPressGestureRecognizer( - debugOwner: this, - supportedDevices: {PointerDeviceKind.touch}, - ), - (LongPressGestureRecognizer instance) { - instance - ..onLongPressStart = _handleLongPressStart - ..onLongPressMoveUpdate = _handleLongPressMoveUpdate - ..onLongPressEnd = _handleLongPressEnd - ..onLongPressCancel = _handleLongPressCancel; - }, - ); - } - - if (widget.onDragSelectionStart != null || - widget.onDragSelectionUpdate != null || - widget.onDragSelectionEnd != null) { - if (PlatformUtils.isMobile) { - gestures[TapAndHorizontalDragGestureRecognizer] = - GestureRecognizerFactoryWithHandlers< - TapAndHorizontalDragGestureRecognizer - >( - () => TapAndHorizontalDragGestureRecognizer(debugOwner: this), - (TapAndHorizontalDragGestureRecognizer instance) { - instance - // Text selection should start from the position of the first pointer - // down event. - ..dragStartBehavior = DragStartBehavior.down - ..eagerVictoryOnDrag = - defaultTargetPlatform != TargetPlatform.iOS - ..onTapTrackStart = _handleTapTrackStart - ..onTapTrackReset = _handleTapTrackReset - ..onTapDown = _handleTapDown - ..onDragStart = _handleDragStart - ..onDragUpdate = _handleDragUpdate - ..onDragEnd = _handleDragEnd - ..onTapUp = _handleTapUp - ..onCancel = _handleTapCancel; - }, - ); - } else { - gestures[TapAndPanGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => TapAndPanGestureRecognizer(debugOwner: this), - (TapAndPanGestureRecognizer instance) { - instance - // Text selection should start from the position of the first pointer - // down event. - ..dragStartBehavior = DragStartBehavior.down - ..onTapTrackStart = _handleTapTrackStart - ..onTapTrackReset = _handleTapTrackReset - ..onTapDown = _handleTapDown - ..onDragStart = _handleDragStart - ..onDragUpdate = _handleDragUpdate - ..onDragEnd = _handleDragEnd - ..onTapUp = _handleTapUp - ..onCancel = _handleTapCancel; - }, - ); - } - } - - if (widget.onForcePressStart != null || widget.onForcePressEnd != null) { - gestures[ForcePressGestureRecognizer] = - GestureRecognizerFactoryWithHandlers( - () => ForcePressGestureRecognizer(debugOwner: this), - (ForcePressGestureRecognizer instance) { - instance - ..onStart = widget.onForcePressStart != null - ? _forcePressStarted - : null - ..onEnd = widget.onForcePressEnd != null - ? _forcePressEnded - : null; - }, - ); - } - - return RawGestureDetector( - gestures: gestures, - excludeFromSemantics: true, - behavior: widget.behavior, - child: widget.child, - ); - } -} diff --git a/lib/common/widgets/flutter/sliver_layout_builder.dart b/lib/common/widgets/flutter/sliver_layout_builder.dart deleted file mode 100644 index f3f35b773b..0000000000 --- a/lib/common/widgets/flutter/sliver_layout_builder.dart +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2014 The Flutter Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart' - hide - ConstrainedLayoutBuilder, - LayoutBuilder, - RenderConstrainedLayoutBuilder; - -/// Builds a sliver widget tree that can depend on its own [SliverConstraints]. -/// -/// Similar to the [LayoutBuilder] widget except its builder should return a sliver -/// widget, and [SliverLayoutBuilder] is itself a sliver. The framework calls the -/// [builder] function at layout time and provides the current [SliverConstraints]. -/// The [SliverLayoutBuilder]'s final [SliverGeometry] will match the [SliverGeometry] -/// of its child. -/// -/// {@macro flutter.widgets.ConstrainedLayoutBuilder} -/// -/// See also: -/// -/// * [LayoutBuilder], the non-sliver version of this widget. -class SliverLayoutBuilder extends ConstrainedLayoutBuilder { - /// Creates a sliver widget that defers its building until layout. - const SliverLayoutBuilder({super.key, required super.builder}); - - @override - RenderConstrainedLayoutBuilder - createRenderObject( - BuildContext context, - ) => _RenderSliverLayoutBuilder(); -} - -class _RenderSliverLayoutBuilder extends RenderSliver - with - RenderObjectWithChildMixin, - RenderObjectWithLayoutCallbackMixin, - RenderConstrainedLayoutBuilder { - @override - double childMainAxisPosition(RenderObject child) { - assert(child == this.child); - return 0; - } - - @override - void performLayout() { - runLayoutCallback(); - child?.layout(constraints, parentUsesSize: true); - geometry = child?.geometry ?? SliverGeometry.zero; - } - - @override - void applyPaintTransform(RenderObject child, Matrix4 transform) { - assert(child == this.child); - // child's offset is always (0, 0), transform.translate(0, 0) does not mutate the transform. - } - - @override - void paint(PaintingContext context, Offset offset) { - // This renderObject does not introduce additional offset to child's position. - if (child?.geometry?.visible ?? false) { - context.paintChild(child!, offset); - } - } - - @override - bool hitTestChildren( - SliverHitTestResult result, { - required double mainAxisPosition, - required double crossAxisPosition, - }) { - return child != null && - child!.geometry!.hitTestExtent > 0 && - child!.hitTest( - result, - mainAxisPosition: mainAxisPosition, - crossAxisPosition: crossAxisPosition, - ); - } -} diff --git a/lib/common/widgets/flutter/text/paragraph.dart b/lib/common/widgets/flutter/text/paragraph.dart index 3f61fe5813..1fdfff5f1f 100644 --- a/lib/common/widgets/flutter/text/paragraph.dart +++ b/lib/common/widgets/flutter/text/paragraph.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: uri_does_not_exist_in_doc_import +// ignore_for_file: prefer_initializing_formals, uri_does_not_exist_in_doc_import /// @docImport 'package:flutter/widgets.dart'; /// @@ -47,6 +47,7 @@ const String _kEllipsis = '\u2026'; class _UnspecifiedTextScaler extends TextScaler { const _UnspecifiedTextScaler(); + @override Never get textScaleFactor => throw UnimplementedError(); @@ -130,6 +131,7 @@ class RenderParagraph extends RenderBox // TODO(abarth): Make computing the min/max intrinsic width/height a // non-destructive operation. TextPainter? _textIntrinsicsCache; + TextPainter get _textIntrinsics { return (_textIntrinsicsCache ??= TextPainter()) ..text = _textPainter.text @@ -224,6 +226,7 @@ class RenderParagraph extends RenderBox /// The [SelectionRegistrar] this paragraph will be, or is, registered to. SelectionRegistrar? get registrar => _registrar; SelectionRegistrar? _registrar; + set registrar(SelectionRegistrar? value) { if (value == _registrar) { return; @@ -325,6 +328,7 @@ class RenderParagraph extends RenderBox /// How the text should be aligned horizontally. TextAlign get textAlign => _textPainter.textAlign; + set textAlign(TextAlign value) { if (_textPainter.textAlign == value) { return; @@ -345,6 +349,7 @@ class RenderParagraph extends RenderBox /// context, the English phrase will be on the right and the Hebrew phrase on /// its left. TextDirection get textDirection => _textPainter.textDirection!; + set textDirection(TextDirection value) { if (_textPainter.textDirection == value) { return; @@ -362,6 +367,7 @@ class RenderParagraph extends RenderBox /// effects. bool get softWrap => _softWrap; bool _softWrap; + set softWrap(bool value) { if (_softWrap == value) { return; @@ -373,6 +379,7 @@ class RenderParagraph extends RenderBox /// How visual overflow should be handled. TextOverflow get overflow => _overflow; TextOverflow _overflow; + set overflow(TextOverflow value) { if (_overflow == value) { return; @@ -395,6 +402,7 @@ class RenderParagraph extends RenderBox 'This feature was deprecated after v3.12.0-2.0.pre.', ) double get textScaleFactor => _textPainter.textScaleFactor; + @Deprecated( 'Use textScaler instead. ' 'Use of textScaleFactor was deprecated in preparation for the upcoming nonlinear text scaling support. ' @@ -406,6 +414,7 @@ class RenderParagraph extends RenderBox /// {@macro flutter.painting.textPainter.textScaler} TextScaler get textScaler => _textPainter.textScaler; + set textScaler(TextScaler value) { if (_textPainter.textScaler == value) { return; @@ -469,6 +478,7 @@ class RenderParagraph extends RenderBox /// {@macro flutter.painting.textPainter.textWidthBasis} TextWidthBasis get textWidthBasis => _textPainter.textWidthBasis; + set textWidthBasis(TextWidthBasis value) { if (_textPainter.textWidthBasis == value) { return; @@ -481,6 +491,7 @@ class RenderParagraph extends RenderBox /// {@macro dart.ui.textHeightBehavior} ui.TextHeightBehavior? get textHeightBehavior => _textPainter.textHeightBehavior; + set textHeightBehavior(ui.TextHeightBehavior? value) { if (_textPainter.textHeightBehavior == value) { return; @@ -495,6 +506,7 @@ class RenderParagraph extends RenderBox /// Ignored if the text is not selectable (e.g. if [registrar] is null). Color? get selectionColor => _selectionColor; Color? _selectionColor; + set selectionColor(Color? value) { if (_selectionColor == value) { return; @@ -1371,6 +1383,7 @@ class _SelectableFragment @override SelectionGeometry get value => _selectionGeometry; late SelectionGeometry _selectionGeometry; + void _updateSelectionGeometry() { final SelectionGeometry newValue = _getSelectionGeometry(); @@ -2329,6 +2342,7 @@ class _SelectableFragment PlaceholderSpan.placeholderCodeUnit, ); static final int _placeholderLength = _placeholderCharacter.length; + // This method handles updating the start edge by a text boundary that may // not be contained within this selectable fragment. It is possible // that a boundary spans multiple selectable fragments when the text contains @@ -3703,12 +3717,13 @@ class _SelectableFragment } List? _cachedBoundingBoxes; + @override List get boundingBoxes { if (_cachedBoundingBoxes == null) { final List boxes = paragraph.getBoxesForSelection( TextSelection(baseOffset: range.start, extentOffset: range.end), - boxHeightStyle: ui.BoxHeightStyle.max, + boxHeightStyle: .max, ); if (boxes.isNotEmpty) { _cachedBoundingBoxes = []; @@ -3730,10 +3745,12 @@ class _SelectableFragment } Rect? _cachedRect; + Rect get _rect { if (_cachedRect == null) { final List boxes = paragraph.getBoxesForSelection( TextSelection(baseOffset: range.start, extentOffset: range.end), + boxHeightStyle: .max, ); if (boxes.isNotEmpty) { Rect result = boxes.first.toRect(); diff --git a/lib/common/widgets/flutter/text/rich_text.dart b/lib/common/widgets/flutter/text/rich_text.dart index a8195b2622..e050783c3f 100644 --- a/lib/common/widgets/flutter/text/rich_text.dart +++ b/lib/common/widgets/flutter/text/rich_text.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + import 'dart:ui' as ui show TextHeightBehavior; import 'package:PiliPlus/common/widgets/flutter/text/paragraph.dart'; diff --git a/lib/common/widgets/flutter/text/text.dart b/lib/common/widgets/flutter/text/text.dart index c58b97ff0d..846adba89d 100644 --- a/lib/common/widgets/flutter/text/text.dart +++ b/lib/common/widgets/flutter/text/text.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: uri_does_not_exist_in_doc_import +// ignore_for_file: prefer_initializing_formals, uri_does_not_exist_in_doc_import /// @docImport 'package:flutter/gestures.dart'; /// @docImport 'package:flutter/material.dart'; diff --git a/lib/common/widgets/flutter/text_field/adaptive_text_selection_toolbar.dart b/lib/common/widgets/flutter/text_field/adaptive_text_selection_toolbar.dart index 8c1d9c54fe..5eae149ddc 100644 --- a/lib/common/widgets/flutter/text_field/adaptive_text_selection_toolbar.dart +++ b/lib/common/widgets/flutter/text_field/adaptive_text_selection_toolbar.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -// ignore_for_file: uri_does_not_exist_in_doc_import +// ignore_for_file: prefer_initializing_formals, uri_does_not_exist_in_doc_import /// @docImport 'selectable_text.dart'; /// @docImport 'selection_area.dart'; @@ -11,7 +11,8 @@ library; import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'; import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState; -import 'package:flutter/material.dart' hide EditableText, EditableTextState; +import 'package:flutter/material.dart' + hide EditableText, EditableTextState, AdaptiveTextSelectionToolbar; import 'package:flutter/rendering.dart'; /// The default context menu for text selection for the current platform. diff --git a/lib/common/widgets/flutter/text_field/cupertino/adaptive_text_selection_toolbar.dart b/lib/common/widgets/flutter/text_field/cupertino/adaptive_text_selection_toolbar.dart index 5af3ab88f7..8e21b8e3ee 100644 --- a/lib/common/widgets/flutter/text_field/cupertino/adaptive_text_selection_toolbar.dart +++ b/lib/common/widgets/flutter/text_field/cupertino/adaptive_text_selection_toolbar.dart @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + /// @docImport 'package:flutter/material.dart'; library; import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'; -import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState; +import 'package:flutter/cupertino.dart' + hide EditableText, EditableTextState, CupertinoAdaptiveTextSelectionToolbar; import 'package:flutter/foundation.dart' show defaultTargetPlatform; import 'package:flutter/rendering.dart'; diff --git a/lib/common/widgets/flutter/text_field/cupertino/spell_check_suggestions_toolbar.dart b/lib/common/widgets/flutter/text_field/cupertino/spell_check_suggestions_toolbar.dart index 429b5bdb5b..1d688428ca 100644 --- a/lib/common/widgets/flutter/text_field/cupertino/spell_check_suggestions_toolbar.dart +++ b/lib/common/widgets/flutter/text_field/cupertino/spell_check_suggestions_toolbar.dart @@ -2,11 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + /// @docImport 'package:flutter/material.dart'; library; import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'; -import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState; +import 'package:flutter/cupertino.dart' + hide EditableText, EditableTextState, CupertinoSpellCheckSuggestionsToolbar; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart' show SelectionChangedCause, SuggestionSpan; diff --git a/lib/common/widgets/flutter/text_field/editable.dart b/lib/common/widgets/flutter/text_field/editable.dart index 1c66b5710f..83a21d0a2c 100644 --- a/lib/common/widgets/flutter/text_field/editable.dart +++ b/lib/common/widgets/flutter/text_field/editable.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + /// @docImport 'package:flutter/cupertino.dart'; library; @@ -19,7 +21,7 @@ import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; import 'package:characters/characters.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/rendering.dart'; +import 'package:flutter/rendering.dart' hide RenderEditable; import 'package:flutter/services.dart'; const double _kCaretGap = 1.0; // pixels diff --git a/lib/common/widgets/flutter/text_field/spell_check.dart b/lib/common/widgets/flutter/text_field/spell_check.dart index 649a811514..b1ea2aa20a 100644 --- a/lib/common/widgets/flutter/text_field/spell_check.dart +++ b/lib/common/widgets/flutter/text_field/spell_check.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + /// @docImport 'editable_text.dart'; library; diff --git a/lib/common/widgets/flutter/text_field/spell_check_suggestions_toolbar.dart b/lib/common/widgets/flutter/text_field/spell_check_suggestions_toolbar.dart index 549e93149c..5692cc8773 100644 --- a/lib/common/widgets/flutter/text_field/spell_check_suggestions_toolbar.dart +++ b/lib/common/widgets/flutter/text_field/spell_check_suggestions_toolbar.dart @@ -2,11 +2,17 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + import 'package:PiliPlus/common/widgets/flutter/text_field/adaptive_text_selection_toolbar.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'; import 'package:flutter/cupertino.dart' hide EditableText, EditableTextState; import 'package:flutter/material.dart' - hide EditableText, EditableTextState, AdaptiveTextSelectionToolbar; + hide + EditableText, + EditableTextState, + AdaptiveTextSelectionToolbar, + SpellCheckSuggestionsToolbar; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart' show SelectionChangedCause, SuggestionSpan; diff --git a/lib/common/widgets/flutter/text_field/system_context_menu.dart b/lib/common/widgets/flutter/text_field/system_context_menu.dart index ed682f985c..ce4de4ea4a 100644 --- a/lib/common/widgets/flutter/text_field/system_context_menu.dart +++ b/lib/common/widgets/flutter/text_field/system_context_menu.dart @@ -2,12 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + /// @docImport 'package:flutter/material.dart'; library; import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'; import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart' hide EditableText, EditableTextState; +import 'package:flutter/material.dart' + hide EditableText, EditableTextState, SystemContextMenu; import 'package:flutter/services.dart'; /// Displays the system context menu on top of the Flutter view. diff --git a/lib/common/widgets/flutter/text_field/text_selection.dart b/lib/common/widgets/flutter/text_field/text_selection.dart index 41f861d91b..0f8b10d005 100644 --- a/lib/common/widgets/flutter/text_field/text_selection.dart +++ b/lib/common/widgets/flutter/text_field/text_selection.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + /// @docImport 'package:flutter/cupertino.dart'; /// @docImport 'package:flutter/material.dart'; library; @@ -13,7 +15,12 @@ import 'package:PiliPlus/common/widgets/flutter/text_field/editable.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/editable_text.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide EditableText, EditableTextState; +import 'package:flutter/material.dart' + hide + EditableText, + EditableTextState, + TextSelectionOverlay, + TextSelectionGestureDetectorBuilder; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; @@ -560,9 +567,9 @@ class TextSelectionGestureDetectorBuilder { .getPositionForPoint( details.globalPosition, ); - final bool isAffinityTheSame = + final isAffinityTheSame = textPosition.affinity == previousSelection.affinity; - final bool wordAtCursorIndexIsMisspelled = + final wordAtCursorIndexIsMisspelled = editableText.findSuggestionSpanAtCursorIndex( textPosition.offset, ) != @@ -663,7 +670,7 @@ class TextSelectionGestureDetectorBuilder { cause: SelectionChangedCause.longPress, ); // Show the floating cursor. - final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint( + final cursorPoint = RawFloatingCursorPoint( state: FloatingCursorDragState.Start, startLocation: ( renderEditable.globalToLocal(details.globalPosition), @@ -700,7 +707,7 @@ class TextSelectionGestureDetectorBuilder { return; } // Adjust the drag start offset for possible viewport offset changes. - final Offset editableOffset = renderEditable.maxLines == 1 + final editableOffset = renderEditable.maxLines == 1 ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) : Offset(0.0, renderEditable.offset.pixels - _dragStartViewportOffset); final Offset scrollableOffset = switch (axisDirectionToAxis( @@ -743,7 +750,7 @@ class TextSelectionGestureDetectorBuilder { cause: SelectionChangedCause.longPress, ); // Update the floating cursor. - final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint( + final cursorPoint = RawFloatingCursorPoint( state: FloatingCursorDragState.Update, offset: details.offsetFromOrigin, ); @@ -866,7 +873,7 @@ class TextSelectionGestureDetectorBuilder { delegate.selectionEnabled && editableText.textEditingValue.selection.isCollapsed) { // Update the floating cursor. - final RawFloatingCursorPoint cursorPoint = RawFloatingCursorPoint( + final cursorPoint = RawFloatingCursorPoint( state: FloatingCursorDragState.End, ); editableText.updateFloatingCursor(cursorPoint); @@ -953,7 +960,7 @@ class TextSelectionGestureDetectorBuilder { : _moveToTextBoundary(toPosition, boundary); final bool isFromBoundaryBeforeToBoundary = fromRange.start < toRange.end; - final TextSelection newSelection = isFromBoundaryBeforeToBoundary + final newSelection = isFromBoundaryBeforeToBoundary ? TextSelection(baseOffset: fromRange.start, extentOffset: toRange.end) : TextSelection(baseOffset: fromRange.end, extentOffset: toRange.start); @@ -1151,7 +1158,7 @@ class TextSelectionGestureDetectorBuilder { if (!_isShiftPressed) { // Adjust the drag start offset for possible viewport offset changes. - final Offset editableOffset = renderEditable.maxLines == 1 + final editableOffset = renderEditable.maxLines == 1 ? Offset(renderEditable.offset.pixels - _dragStartViewportOffset, 0.0) : Offset( 0.0, @@ -2232,21 +2239,21 @@ class TextSelectionOverlay { final TextSelection lineAtOffset = renderEditable.getLineAtOffset( currentTextPosition, ); - final TextPosition positionAtEndOfLine = TextPosition( + final positionAtEndOfLine = TextPosition( offset: lineAtOffset.extentOffset, affinity: TextAffinity.upstream, ); // Default affinity is downstream. - final TextPosition positionAtBeginningOfLine = TextPosition( + final positionAtBeginningOfLine = TextPosition( offset: lineAtOffset.baseOffset, ); - final Rect localLineBoundaries = Rect.fromPoints( + final localLineBoundaries = Rect.fromPoints( renderEditable.getLocalRectForCaret(positionAtBeginningOfLine).topCenter, renderEditable.getLocalRectForCaret(positionAtEndOfLine).bottomCenter, ); - final RenderBox? overlay = + final overlay = Overlay.of(context, rootOverlay: true).context.findRenderObject() as RenderBox?; final Matrix4 transformToOverlay = renderEditable.getTransformTo(overlay); @@ -2356,7 +2363,7 @@ class TextSelectionOverlay { /// line height is used, and the return value is in local coordinates as well. double _getHandleDy(double dragDy, double handleDy) { final double distanceDragged = dragDy - handleDy; - final int dragDirection = distanceDragged < 0.0 ? -1 : 1; + final dragDirection = distanceDragged < 0.0 ? -1 : 1; final int linesDragged = dragDirection * (distanceDragged.abs() / renderObject.preferredLineHeight).floor(); @@ -2382,7 +2389,7 @@ class TextSelectionOverlay { .localToGlobal(Offset(0.0, nextEndHandleDragPositionLocal)) .dy; - final Offset handleTargetGlobal = Offset( + final handleTargetGlobal = Offset( details.globalPosition.dx, _endHandleDragPosition + _endHandleDragTarget, ); @@ -2409,9 +2416,7 @@ class TextSelectionOverlay { ), ); - final TextSelection currentSelection = TextSelection.fromPosition( - position, - ); + final currentSelection = TextSelection.fromPosition(position); _handleSelectionHandleChanged(currentSelection); return; } @@ -2442,9 +2447,7 @@ class TextSelectionOverlay { ), ); - final TextSelection currentSelection = TextSelection.fromPosition( - position, - ); + final currentSelection = TextSelection.fromPosition(position); _handleSelectionHandleChanged(currentSelection); return; } @@ -2532,7 +2535,7 @@ class TextSelectionOverlay { _startHandleDragPosition = renderObject .localToGlobal(Offset(0.0, nextStartHandleDragPositionLocal)) .dy; - final Offset handleTargetGlobal = Offset( + final handleTargetGlobal = Offset( details.globalPosition.dx, _startHandleDragPosition + _startHandleDragTarget, ); @@ -2558,9 +2561,7 @@ class TextSelectionOverlay { ), ); - final TextSelection currentSelection = TextSelection.fromPosition( - position, - ); + final currentSelection = TextSelection.fromPosition(position); _handleSelectionHandleChanged(currentSelection); return; } @@ -2591,9 +2592,7 @@ class TextSelectionOverlay { ), ); - final TextSelection currentSelection = TextSelection.fromPosition( - position, - ); + final currentSelection = TextSelection.fromPosition(position); _handleSelectionHandleChanged(currentSelection); return; } diff --git a/lib/common/widgets/flutter/vertical_slider.dart b/lib/common/widgets/flutter/vertical_slider.dart new file mode 100644 index 0000000000..5fe5c064a0 --- /dev/null +++ b/lib/common/widgets/flutter/vertical_slider.dart @@ -0,0 +1,2683 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: prefer_initializing_formals + +import 'dart:async'; +import 'dart:math' as math; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart' hide Slider; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart' show timeDilation; +import 'package:flutter/services.dart'; + +enum _SliderType { material, adaptive } + +/// A Material Design slider. +/// +/// Used to select from a range of values. +/// +/// {@youtube 560 315 https://www.youtube.com/watch?v=ufb4gIPDmEs} +/// +/// {@tool dartpad} +/// This example showcases non-discrete and discrete [VerticalSlider]s. +/// The [VerticalSlider]s will show the updated ![Material 3 Design appearance](https://m3.material.io/components/sliders/overview) +/// when setting the [VerticalSlider.year2023] flag to false. +/// +/// ** See code in examples/api/lib/material/slider/slider.0.dart ** +/// {@end-tool} +/// +/// {@tool dartpad} +/// This example shows a [VerticalSlider] widget using the [VerticalSlider.secondaryTrackValue] +/// to show a secondary track in the slider. +/// +/// ** See code in examples/api/lib/material/slider/slider.1.dart ** +/// {@end-tool} +/// +/// A slider can be used to select from either a continuous or a discrete set of +/// values. The default is to use a continuous range of values from [min] to +/// [max]. To use discrete values, use a non-null value for [divisions], which +/// indicates the number of discrete intervals. For example, if [min] is 0.0 and +/// [max] is 50.0 and [divisions] is 5, then the slider can take on the +/// discrete values 0.0, 10.0, 20.0, 30.0, 40.0, and 50.0. +/// +/// The terms for the parts of a slider are: +/// +/// * The "thumb", which is a shape that slides horizontally when the user +/// drags it. +/// * The "track", which is the line that the slider thumb slides along. +/// * The "value indicator", which is a shape that pops up when the user +/// is dragging the thumb to indicate the value being selected. +/// * The "active" side of the slider is the side between the thumb and the +/// minimum value. +/// * The "inactive" side of the slider is the side between the thumb and the +/// maximum value. +/// +/// The slider will be disabled if [onChanged] is null or if the range given by +/// [min]..[max] is empty (i.e. if [min] is equal to [max]). +/// +/// The slider widget itself does not maintain any state. Instead, when the state +/// of the slider changes, the widget calls the [onChanged] callback. Most +/// widgets that use a slider will listen for the [onChanged] callback and +/// rebuild the slider with a new [value] to update the visual appearance of the +/// slider. To know when the value starts to change, or when it is done +/// changing, set the optional callbacks [onChangeStart] and/or [onChangeEnd]. +/// +/// By default, a slider will be as wide as possible, centered vertically. When +/// given unbounded constraints, it will attempt to make the track 144 pixels +/// wide (with margins on each side) and will shrink-wrap vertically. +/// +/// Requires one of its ancestors to be a [Material] widget. +/// +/// Requires one of its ancestors to be a [MediaQuery] widget. Typically, these +/// are introduced by the [MaterialApp] or [WidgetsApp] widget at the top of +/// your application widget tree. +/// +/// To determine how it should be displayed (e.g. colors, thumb shape, etc.), +/// a slider uses the [SliderThemeData] available from either a [SliderTheme] +/// widget or the [ThemeData.sliderTheme] a [Theme] widget above it in the +/// widget tree. You can also override some of the colors with the [activeColor] +/// and [inactiveColor] properties, although more fine-grained control of the +/// look is achieved using a [SliderThemeData]. +/// +/// See also: +/// +/// * [SliderTheme] and [SliderThemeData] for information about controlling +/// the visual appearance of the slider. +/// * [Radio], for selecting among a set of explicit values. +/// * [Checkbox] and [Switch], for toggling a particular value on or off. +/// * +/// * [MediaQuery], from which the text scale factor is obtained. +class VerticalSlider extends StatefulWidget { + /// Creates a Material Design slider. + /// + /// The slider itself does not maintain any state. Instead, when the state of + /// the slider changes, the widget calls the [onChanged] callback. Most + /// widgets that use a slider will listen for the [onChanged] callback and + /// rebuild the slider with a new [value] to update the visual appearance of + /// the slider. + /// + /// * [value] determines currently selected value for this slider. + /// * [onChanged] is called while the user is selecting a new value for the + /// slider. + /// * [onChangeStart] is called when the user starts to select a new value for + /// the slider. + /// * [onChangeEnd] is called when the user is done selecting a new value for + /// the slider. + /// + /// You can override some of the colors with the [activeColor] and + /// [inactiveColor] properties, although more fine-grained control of the + /// appearance is achieved using a [SliderThemeData]. + const VerticalSlider({ + super.key, + required this.value, + this.secondaryTrackValue, + required this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.min = 0.0, + this.max = 1.0, + this.divisions, + this.label, + this.activeColor, + this.inactiveColor, + this.secondaryActiveColor, + this.thumbColor, + this.overlayColor, + this.mouseCursor, + this.semanticFormatterCallback, + this.focusNode, + this.autofocus = false, + this.allowedInteraction, + this.padding, + this.showValueIndicator, + @Deprecated( + 'Set this flag to false to opt into the 2024 slider appearance. Defaults to true. ' + 'In the future, this flag will default to false. Use SliderThemeData to customize individual properties. ' + 'This feature was deprecated after v3.27.0-0.2.pre.', + ) + this.year2023, + }) : _sliderType = _SliderType.material, + assert(min <= max), + assert( + value >= min && value <= max, + 'Value $value is not between minimum $min and maximum $max', + ), + assert( + secondaryTrackValue == null || + (secondaryTrackValue >= min && secondaryTrackValue <= max), + 'SecondaryValue $secondaryTrackValue is not between $min and $max', + ), + assert(divisions == null || divisions > 0); + + /// Creates an adaptive [VerticalSlider] based on the target platform, following + /// Material design's + /// [Cross-platform guidelines](https://material.io/design/platform-guidance/cross-platform-adaptation.html). + /// + /// Creates a [CupertinoSlider] if the target platform is iOS or macOS, creates a + /// Material Design slider otherwise. + /// + /// If a [CupertinoSlider] is created, the following parameters are ignored: + /// [secondaryTrackValue], [label], [inactiveColor], [secondaryActiveColor], + /// [semanticFormatterCallback], [showValueIndicator]. + /// + /// The target platform is based on the current [Theme]: [ThemeData.platform]. + const VerticalSlider.adaptive({ + super.key, + required this.value, + this.secondaryTrackValue, + required this.onChanged, + this.onChangeStart, + this.onChangeEnd, + this.min = 0.0, + this.max = 1.0, + this.divisions, + this.label, + this.mouseCursor, + this.activeColor, + this.inactiveColor, + this.secondaryActiveColor, + this.thumbColor, + this.overlayColor, + this.semanticFormatterCallback, + this.focusNode, + this.autofocus = false, + this.allowedInteraction, + this.showValueIndicator, + @Deprecated( + 'Set this flag to false to opt into the 2024 slider appearance. Defaults to true. ' + 'In the future, this flag will default to false. Use SliderThemeData to customize individual properties. ' + 'This feature was deprecated after v3.27.0-0.1.pre.', + ) + this.year2023, + }) : _sliderType = _SliderType.adaptive, + padding = null, + assert(min <= max), + assert( + value >= min && value <= max, + 'Value $value is not between minimum $min and maximum $max', + ), + assert( + secondaryTrackValue == null || + (secondaryTrackValue >= min && secondaryTrackValue <= max), + 'SecondaryValue $secondaryTrackValue is not between $min and $max', + ), + assert(divisions == null || divisions > 0); + + /// The currently selected value for this slider. + /// + /// The slider's thumb is drawn at a position that corresponds to this value. + final double value; + + /// The secondary track value for this slider. + /// + /// If not null, a secondary track using [VerticalSlider.secondaryActiveColor] color + /// is drawn between the thumb and this value, over the inactive track. + /// + /// If less than [VerticalSlider.value], then the secondary track is not shown. + /// + /// It can be ideal for media scenarios such as showing the buffering progress + /// while the [VerticalSlider.value] shows the play progress. + final double? secondaryTrackValue; + + /// Called during a drag when the user is selecting a new value for the slider + /// by dragging. + /// + /// The slider passes the new value to the callback but does not actually + /// change state until the parent widget rebuilds the slider with the new + /// value. + /// + /// If null, the slider will be displayed as disabled. + /// + /// The callback provided to onChanged should update the state of the parent + /// [StatefulWidget] using the [State.setState] method, so that the parent + /// gets rebuilt; for example: + /// + /// {@tool snippet} + /// + /// ```dart + /// Slider( + /// value: _duelCommandment.toDouble(), + /// min: 1.0, + /// max: 10.0, + /// divisions: 10, + /// label: '$_duelCommandment', + /// onChanged: (double newValue) { + /// setState(() { + /// _duelCommandment = newValue.round(); + /// }); + /// }, + /// ) + /// ``` + /// {@end-tool} + /// + /// See also: + /// + /// * [onChangeStart] for a callback that is called when the user starts + /// changing the value. + /// * [onChangeEnd] for a callback that is called when the user stops + /// changing the value. + final ValueChanged? onChanged; + + /// Called when the user starts selecting a new value for the slider. + /// + /// This callback shouldn't be used to update the slider [value] (use + /// [onChanged] for that), but rather to be notified when the user has started + /// selecting a new value by starting a drag or with a tap. + /// + /// The value passed will be the last [value] that the slider had before the + /// change began. + /// + /// {@tool snippet} + /// + /// ```dart + /// Slider( + /// value: _duelCommandment.toDouble(), + /// min: 1.0, + /// max: 10.0, + /// divisions: 10, + /// label: '$_duelCommandment', + /// onChanged: (double newValue) { + /// setState(() { + /// _duelCommandment = newValue.round(); + /// }); + /// }, + /// onChangeStart: (double startValue) { + /// print('Started change at $startValue'); + /// }, + /// ) + /// ``` + /// {@end-tool} + /// + /// See also: + /// + /// * [onChangeEnd] for a callback that is called when the value change is + /// complete. + final ValueChanged? onChangeStart; + + /// Called when the user is done selecting a new value for the slider. + /// + /// This callback shouldn't be used to update the slider [value] (use + /// [onChanged] for that), but rather to know when the user has completed + /// selecting a new [value] by ending a drag or a click. + /// + /// {@tool snippet} + /// + /// ```dart + /// Slider( + /// value: _duelCommandment.toDouble(), + /// min: 1.0, + /// max: 10.0, + /// divisions: 10, + /// label: '$_duelCommandment', + /// onChanged: (double newValue) { + /// setState(() { + /// _duelCommandment = newValue.round(); + /// }); + /// }, + /// onChangeEnd: (double newValue) { + /// print('Ended change on $newValue'); + /// }, + /// ) + /// ``` + /// {@end-tool} + /// + /// See also: + /// + /// * [onChangeStart] for a callback that is called when a value change + /// begins. + final ValueChanged? onChangeEnd; + + /// The minimum value the user can select. + /// + /// Defaults to 0.0. Must be less than or equal to [max]. + /// + /// If the [max] is equal to the [min], then the slider is disabled. + final double min; + + /// The maximum value the user can select. + /// + /// Defaults to 1.0. Must be greater than or equal to [min]. + /// + /// If the [max] is equal to the [min], then the slider is disabled. + final double max; + + /// The number of discrete divisions. + /// + /// Typically used with [label] to show the current discrete value. + /// + /// If null, the slider is continuous. + final int? divisions; + + /// A label to show above the slider when the slider is active and + /// [SliderThemeData.showValueIndicator] is satisfied. + /// + /// It is used to display the value of a discrete slider, and it is displayed + /// as part of the value indicator shape. + /// + /// The label is rendered using the active [ThemeData]'s [TextTheme.bodyLarge] + /// text style, with the theme data's [ColorScheme.onPrimary] color. The + /// label's text style can be overridden with + /// [SliderThemeData.valueIndicatorTextStyle]. + /// + /// If null, then the value indicator will not be displayed. + /// + /// Ignored if this slider is created with [Slider.adaptive]. + /// + /// See also: + /// + /// * [SliderComponentShape] for how to create a custom value indicator + /// shape. + final String? label; + + /// The color to use for the portion of the slider track that is active. + /// + /// The "active" side of the slider is the side between the thumb and the + /// minimum value. + /// + /// If null, [SliderThemeData.activeTrackColor] of the ambient + /// [SliderTheme] is used. If that is null, [ColorScheme.primary] of the + /// surrounding [ThemeData] is used. + /// + /// Using a [SliderTheme] gives much more fine-grained control over the + /// appearance of various components of the slider. + final Color? activeColor; + + /// The color for the inactive portion of the slider track. + /// + /// The "inactive" side of the slider is the side between the thumb and the + /// maximum value. + /// + /// If null, [SliderThemeData.inactiveTrackColor] of the ambient [SliderTheme] + /// is used. If [VerticalSlider.year2023] is false and [ThemeData.useMaterial3] is true, + /// then [ColorScheme.secondaryContainer] is used and if [ThemeData.useMaterial3] + /// is false, [ColorScheme.primary] with an opacity of 0.24 is used. Otherwise, + /// [ColorScheme.surfaceContainerHighest] is used. + /// + /// Using a [SliderTheme] gives much more fine-grained control over the + /// appearance of various components of the slider. + /// + /// Ignored if this slider is created with [Slider.adaptive]. + final Color? inactiveColor; + + /// The color to use for the portion of the slider track between the thumb and + /// the [VerticalSlider.secondaryTrackValue]. + /// + /// Defaults to the [SliderThemeData.secondaryActiveTrackColor] of the current + /// [SliderTheme]. + /// + /// If that is also null, defaults to [ColorScheme.primary] with an + /// opacity of 0.54. + /// + /// Using a [SliderTheme] gives much more fine-grained control over the + /// appearance of various components of the slider. + /// + /// Ignored if this slider is created with [Slider.adaptive]. + final Color? secondaryActiveColor; + + /// The color of the thumb. + /// + /// If this color is null, [VerticalSlider] will use [activeColor], If [activeColor] + /// is also null, [VerticalSlider] will use [SliderThemeData.thumbColor]. + /// + /// If that is also null, defaults to [ColorScheme.primary]. + /// + /// * [CupertinoSlider] will have a white thumb + /// (like the native default iOS slider). + final Color? thumbColor; + + /// The highlight color that's typically used to indicate that + /// the slider thumb is focused, hovered, or dragged. + /// + /// If this property is null, [VerticalSlider] will use [activeColor] with + /// an opacity of 0.12, If null, [SliderThemeData.overlayColor] + /// will be used. + /// + /// If that is also null, If [ThemeData.useMaterial3] is true, + /// Slider will use [ColorScheme.primary] with an opacity of 0.08 when + /// slider thumb is hovered and with an opacity of 0.1 when slider thumb + /// is focused or dragged, If [ThemeData.useMaterial3] is false, defaults + /// to [ColorScheme.primary] with an opacity of 0.12. + final WidgetStateProperty? overlayColor; + + /// {@template flutter.material.slider.mouseCursor} + /// The cursor for a mouse pointer when it enters or is hovering over the + /// widget. + /// + /// If [mouseCursor] is a [WidgetStateMouseCursor], + /// [WidgetStateProperty.resolve] is used for the following [WidgetState]s: + /// + /// * [WidgetState.dragged]. + /// * [WidgetState.hovered]. + /// * [WidgetState.focused]. + /// * [WidgetState.disabled]. + /// {@endtemplate} + /// + /// If null, then the value of [SliderThemeData.mouseCursor] is used. If that + /// is also null, then [WidgetStateMouseCursor.clickable] is used. + final MouseCursor? mouseCursor; + + /// The callback used to create a semantic value from a slider value. + /// + /// Defaults to formatting values as a percentage. + /// + /// This is used by accessibility frameworks like TalkBack on Android to + /// inform users what the currently selected value is with more context. + /// + /// {@tool snippet} + /// + /// In the example below, a slider for currency values is configured to + /// announce a value with a currency label. + /// + /// ```dart + /// Slider( + /// value: _dollars.toDouble(), + /// min: 20.0, + /// max: 330.0, + /// label: '$_dollars dollars', + /// onChanged: (double newValue) { + /// setState(() { + /// _dollars = newValue.round(); + /// }); + /// }, + /// semanticFormatterCallback: (double newValue) { + /// return '${newValue.round()} dollars'; + /// } + /// ) + /// ``` + /// {@end-tool} + /// + /// Ignored if this slider is created with [Slider.adaptive] + final SemanticFormatterCallback? semanticFormatterCallback; + + /// {@macro flutter.widgets.Focus.focusNode} + final FocusNode? focusNode; + + /// {@macro flutter.widgets.Focus.autofocus} + final bool autofocus; + + /// Allowed way for the user to interact with the [VerticalSlider]. + /// + /// For example, if this is set to [SliderInteraction.tapOnly], the user can + /// interact with the slider only by tapping anywhere on the track. Sliding + /// will have no effect. + /// + /// Defaults to [SliderInteraction.tapAndSlide]. + final SliderInteraction? allowedInteraction; + + /// Determines the padding around the [VerticalSlider]. + /// + /// If specified, this padding overrides the default vertical padding of + /// the [VerticalSlider], defaults to the height of the overlay shape, and the + /// horizontal padding, defaults to the width of the thumb shape or + /// overlay shape, whichever is larger. + final EdgeInsetsGeometry? padding; + + /// Determines the conditions under which the value indicator is shown. + /// + /// If [VerticalSlider.showValueIndicator] is null then the + /// ambient [SliderThemeData.showValueIndicator] is used. If that is also + /// null, defaults to [ShowValueIndicator.onlyForDiscrete]. + final ShowValueIndicator? showValueIndicator; + + /// When true, the [VerticalSlider] will use the 2023 Material Design 3 appearance. + /// Defaults to true. + /// + /// If this is set to false, the [VerticalSlider] will use the latest Material Design 3 + /// appearance, which was introduced in December 2023. + /// + /// If [ThemeData.useMaterial3] is false, then this property is ignored. + @Deprecated( + 'Set this flag to false to opt into the 2024 slider appearance. Defaults to true. ' + 'In the future, this flag will default to false. Use SliderThemeData to customize individual properties. ' + 'This feature was deprecated after v3.27.0-0.1.pre.', + ) + final bool? year2023; + + final _SliderType _sliderType; + + @override + State createState() => _VerticalSliderState(); + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DoubleProperty('value', value)) + ..add(DoubleProperty('secondaryTrackValue', secondaryTrackValue)) + ..add( + ObjectFlagProperty>( + 'onChanged', + onChanged, + ifNull: 'disabled', + ), + ) + ..add( + ObjectFlagProperty>.has( + 'onChangeStart', + onChangeStart, + ), + ) + ..add( + ObjectFlagProperty>.has( + 'onChangeEnd', + onChangeEnd, + ), + ) + ..add(DoubleProperty('min', min)) + ..add(DoubleProperty('max', max)) + ..add(IntProperty('divisions', divisions)) + ..add(StringProperty('label', label)) + ..add(ColorProperty('activeColor', activeColor)) + ..add(ColorProperty('inactiveColor', inactiveColor)) + ..add(ColorProperty('secondaryActiveColor', secondaryActiveColor)) + ..add( + ObjectFlagProperty>.has( + 'semanticFormatterCallback', + semanticFormatterCallback, + ), + ) + ..add(ObjectFlagProperty.has('focusNode', focusNode)) + ..add( + FlagProperty('autofocus', value: autofocus, ifTrue: 'autofocus'), + ); + } +} + +class _VerticalSliderState extends State + with TickerProviderStateMixin { + static const Duration enableAnimationDuration = Duration(milliseconds: 75); + static const Duration valueIndicatorAnimationDuration = Duration( + milliseconds: 100, + ); + + // Animation controller that is run when the overlay (a.k.a radial reaction) + // is shown in response to user interaction. + late AnimationController overlayController; + // Animation controller that is run when the value indicator is being shown + // or hidden. + late AnimationController valueIndicatorController; + // Animation controller that is run when enabling/disabling the slider. + late AnimationController enableController; + // Animation controller that is run when transitioning between one value + // and the next on a discrete slider. + late AnimationController positionController; + Timer? interactionTimer; + + final GlobalKey _renderObjectKey = GlobalKey(); + + // Keyboard mapping for a focused slider. + static const Map + _traditionalNavShortcutMap = { + SingleActivator(LogicalKeyboardKey.arrowUp): _AdjustSliderIntent.up(), + SingleActivator(LogicalKeyboardKey.arrowDown): _AdjustSliderIntent.down(), + SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(), + SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(), + }; + + // Keyboard mapping for a focused slider when using directional navigation. + // The vertical inputs are not handled to allow navigating out of the slider. + static const Map + _directionalNavShortcutMap = { + SingleActivator(LogicalKeyboardKey.arrowLeft): _AdjustSliderIntent.left(), + SingleActivator(LogicalKeyboardKey.arrowRight): _AdjustSliderIntent.right(), + }; + + // Action mapping for a focused slider. + late Map> _actionMap; + + bool get _enabled => widget.onChanged != null; + // Value Indicator Animation that appears on the Overlay. + PaintValueIndicator? paintValueIndicator; + + bool _dragging = false; + + // For discrete sliders, _handleChanged might receive the same value + // multiple times. To avoid calling widget.onChanged repeatedly, the + // value from _handleChanged is temporarily saved here. + double? _currentChangedValue; + + FocusNode? _focusNode; + FocusNode get focusNode => widget.focusNode ?? _focusNode!; + + // Always keep the ValueIndicator visible on the Overlay; otherwise, it cannot be updated during the build phase. + final OverlayPortalController _valueIndicatorOverlayPortalController = + OverlayPortalController( + debugLabel: 'Slider ValueIndicator', + )..show(); + + @override + void initState() { + super.initState(); + overlayController = AnimationController( + duration: kRadialReactionDuration, + vsync: this, + ); + valueIndicatorController = AnimationController( + duration: valueIndicatorAnimationDuration, + vsync: this, + ); + enableController = AnimationController( + duration: enableAnimationDuration, + vsync: this, + ); + positionController = AnimationController( + duration: Duration.zero, + vsync: this, + ); + enableController.value = widget.onChanged != null ? 1.0 : 0.0; + positionController.value = _convert(widget.value); + _actionMap = >{ + _AdjustSliderIntent: CallbackAction<_AdjustSliderIntent>( + onInvoke: _actionHandler, + ), + }; + if (widget.focusNode == null) { + // Only create a new node if the widget doesn't have one. + _focusNode ??= FocusNode(); + } + } + + @override + void dispose() { + interactionTimer?.cancel(); + overlayController.dispose(); + valueIndicatorController.dispose(); + enableController.dispose(); + positionController.dispose(); + _focusNode?.dispose(); + super.dispose(); + } + + void _handleChanged(double value) { + assert(widget.onChanged != null); + final double lerpValue = _lerp(value); + if (_currentChangedValue != lerpValue) { + _currentChangedValue = lerpValue; + if (_currentChangedValue != widget.value) { + widget.onChanged!(_currentChangedValue!); + } + } + } + + void _handleDragStart(double value) { + setState(() { + _dragging = true; + }); + widget.onChangeStart?.call(_lerp(value)); + } + + void _handleDragEnd(double value) { + setState(() { + _dragging = false; + }); + _currentChangedValue = null; + widget.onChangeEnd?.call(_lerp(value)); + } + + void _actionHandler(_AdjustSliderIntent intent) { + final TextDirection directionality = Directionality.of( + _renderObjectKey.currentContext!, + ); + final bool shouldIncrease = switch (intent.type) { + _SliderAdjustmentType.up => true, + _SliderAdjustmentType.down => false, + _SliderAdjustmentType.left => directionality == TextDirection.rtl, + _SliderAdjustmentType.right => directionality == TextDirection.ltr, + }; + + final slider = + _renderObjectKey.currentContext!.findRenderObject()! as _RenderSlider; + return shouldIncrease ? slider.increaseAction() : slider.decreaseAction(); + } + + bool _focused = false; + void _handleFocusHighlightChanged(bool focused) { + if (focused != _focused) { + setState(() { + _focused = focused; + }); + } + } + + bool _hovering = false; + void _handleHoverChanged(bool hovering) { + if (hovering != _hovering) { + setState(() { + _hovering = hovering; + }); + } + } + + // Returns a number between min and max, proportional to value, which must + // be between 0.0 and 1.0. + double _lerp(double value) { + assert(value >= 0.0); + assert(value <= 1.0); + return value * (widget.max - widget.min) + widget.min; + } + + double _discretize(double value) { + assert(widget.divisions != null); + assert(value >= 0.0 && value <= 1.0); + + final int divisions = widget.divisions!; + return (value * divisions).round() / divisions; + } + + double _convert(double value) { + double ret = _unlerp(value); + if (widget.divisions != null) { + ret = _discretize(ret); + } + return ret; + } + + // Returns a number between 0.0 and 1.0, given a value between min and max. + double _unlerp(double value) { + assert(value <= widget.max); + assert(value >= widget.min); + return widget.max > widget.min + ? (value - widget.min) / (widget.max - widget.min) + : 0.0; + } + + @override + Widget build(BuildContext context) { + assert(debugCheckHasMaterial(context)); + assert(debugCheckHasMediaQuery(context)); + + switch (widget._sliderType) { + case _SliderType.material: + return _buildMaterialSlider(context); + + case _SliderType.adaptive: + { + final ThemeData theme = Theme.of(context); + switch (theme.platform) { + case TargetPlatform.ohos: + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + return _buildMaterialSlider(context); + case TargetPlatform.iOS: + case TargetPlatform.macOS: + return _buildCupertinoSlider(context); + } + } + } + } + + Widget _buildMaterialSlider(BuildContext context) { + final ThemeData theme = Theme.of(context); + SliderThemeData sliderTheme = SliderTheme.of(context); + // ignore: deprecated_member_use + final bool year2023 = widget.year2023 ?? sliderTheme.year2023 ?? true; + final SliderThemeData defaults = switch (theme.useMaterial3) { + true => + year2023 + ? _SliderDefaultsM3Year2023(context) + : _SliderDefaultsM3(context), + false => _SliderDefaultsM2(context), + }; + + // If the widget has active or inactive colors specified, then we plug them + // in to the slider theme as best we can. If the developer wants more + // control than that, then they need to use a SliderTheme. The default + // colors come from the ThemeData.colorScheme. These colors, along with + // the default shapes and text styles are aligned to the Material + // Guidelines. + + const ShowValueIndicator defaultShowValueIndicator = + ShowValueIndicator.onlyForDiscrete; + const SliderInteraction defaultAllowedInteraction = + SliderInteraction.tapAndSlide; + + final states = { + if (!_enabled) WidgetState.disabled, + if (_hovering) WidgetState.hovered, + if (_focused) WidgetState.focused, + if (_dragging) WidgetState.dragged, + }; + + // The value indicator's color is not the same as the thumb and active track + // (which can be defined by activeColor) if the + // RectangularSliderValueIndicatorShape is used. In all other cases, the + // value indicator is assumed to be the same as the active color. + final SliderComponentShape valueIndicatorShape = + sliderTheme.valueIndicatorShape ?? defaults.valueIndicatorShape!; + final Color valueIndicatorColor; + if (valueIndicatorShape is RectangularSliderValueIndicatorShape) { + valueIndicatorColor = + sliderTheme.valueIndicatorColor ?? + Color.alphaBlend( + theme.colorScheme.onSurface.withValues(alpha: 0.60), + theme.colorScheme.surface.withValues(alpha: 0.90), + ); + } else { + valueIndicatorColor = + widget.activeColor ?? + sliderTheme.valueIndicatorColor ?? + defaults.valueIndicatorColor!; + } + + Color? effectiveOverlayColor() { + return widget.overlayColor?.resolve(states) ?? + widget.activeColor?.withValues(alpha: 0.12) ?? + WidgetStateProperty.resolveAs( + sliderTheme.overlayColor, + states, + ) ?? + WidgetStateProperty.resolveAs(defaults.overlayColor, states); + } + + TextStyle valueIndicatorTextStyle = + sliderTheme.valueIndicatorTextStyle ?? + defaults.valueIndicatorTextStyle!; + if (MediaQuery.boldTextOf(context)) { + valueIndicatorTextStyle = valueIndicatorTextStyle.merge( + const TextStyle(fontWeight: FontWeight.bold), + ); + } + + sliderTheme = sliderTheme.copyWith( + trackHeight: sliderTheme.trackHeight ?? defaults.trackHeight, + activeTrackColor: + widget.activeColor ?? + sliderTheme.activeTrackColor ?? + defaults.activeTrackColor, + inactiveTrackColor: + widget.inactiveColor ?? + sliderTheme.inactiveTrackColor ?? + defaults.inactiveTrackColor, + secondaryActiveTrackColor: + widget.secondaryActiveColor ?? + sliderTheme.secondaryActiveTrackColor ?? + defaults.secondaryActiveTrackColor, + disabledActiveTrackColor: + sliderTheme.disabledActiveTrackColor ?? + defaults.disabledActiveTrackColor, + disabledInactiveTrackColor: + sliderTheme.disabledInactiveTrackColor ?? + defaults.disabledInactiveTrackColor, + disabledSecondaryActiveTrackColor: + sliderTheme.disabledSecondaryActiveTrackColor ?? + defaults.disabledSecondaryActiveTrackColor, + activeTickMarkColor: + widget.inactiveColor ?? + sliderTheme.activeTickMarkColor ?? + defaults.activeTickMarkColor, + inactiveTickMarkColor: + widget.activeColor ?? + sliderTheme.inactiveTickMarkColor ?? + defaults.inactiveTickMarkColor, + disabledActiveTickMarkColor: + sliderTheme.disabledActiveTickMarkColor ?? + defaults.disabledActiveTickMarkColor, + disabledInactiveTickMarkColor: + sliderTheme.disabledInactiveTickMarkColor ?? + defaults.disabledInactiveTickMarkColor, + thumbColor: + widget.thumbColor ?? + widget.activeColor ?? + sliderTheme.thumbColor ?? + defaults.thumbColor, + disabledThumbColor: + sliderTheme.disabledThumbColor ?? defaults.disabledThumbColor, + overlayColor: effectiveOverlayColor(), + valueIndicatorColor: valueIndicatorColor, + trackShape: sliderTheme.trackShape ?? defaults.trackShape, + tickMarkShape: sliderTheme.tickMarkShape ?? defaults.tickMarkShape, + thumbShape: sliderTheme.thumbShape ?? defaults.thumbShape, + overlayShape: sliderTheme.overlayShape ?? defaults.overlayShape, + valueIndicatorShape: valueIndicatorShape, + showValueIndicator: + widget.showValueIndicator ?? + sliderTheme.showValueIndicator ?? + defaultShowValueIndicator, + valueIndicatorTextStyle: valueIndicatorTextStyle, + padding: widget.padding ?? sliderTheme.padding, + thumbSize: sliderTheme.thumbSize ?? defaults.thumbSize, + trackGap: sliderTheme.trackGap ?? defaults.trackGap, + ); + final MouseCursor effectiveMouseCursor = + WidgetStateProperty.resolveAs( + widget.mouseCursor, + states, + ) ?? + sliderTheme.mouseCursor?.resolve(states) ?? + WidgetStateMouseCursor.clickable.resolve(states); + final SliderInteraction effectiveAllowedInteraction = + widget.allowedInteraction ?? + sliderTheme.allowedInteraction ?? + defaultAllowedInteraction; + + // This size is used as the max bounds for the painting of the value + // indicators It must be kept in sync with the function with the same name + // in range_slider.dart. + Size screenSize() => MediaQuery.sizeOf(context); + + VoidCallback? handleDidGainAccessibilityFocus; + switch (theme.platform) { + case TargetPlatform.ohos: + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.iOS: + case TargetPlatform.linux: + case TargetPlatform.macOS: + break; + case TargetPlatform.windows: + handleDidGainAccessibilityFocus = () { + // Automatically activate the slider when it receives a11y focus. + if (!focusNode.hasFocus && focusNode.canRequestFocus) { + focusNode.requestFocus(); + } + }; + } + + final Map shortcutMap = + switch (MediaQuery.navigationModeOf( + context, + )) { + NavigationMode.directional => _directionalNavShortcutMap, + NavigationMode.traditional => _traditionalNavShortcutMap, + }; + + final double fontSize = + sliderTheme.valueIndicatorTextStyle?.fontSize ?? kDefaultFontSize; + final double fontSizeToScale = fontSize == 0.0 + ? kDefaultFontSize + : fontSize; + final TextScaler textScaler = theme.useMaterial3 + // TODO(tahatesser): This is an eye-balled value. + // This needs to be updated when accessibility + // guidelines are available on the material specs page + // https://m3.material.io/components/sliders/accessibility. + ? MediaQuery.textScalerOf(context).clamp(maxScaleFactor: 1.3) + : MediaQuery.textScalerOf(context); + final double effectiveTextScale = + textScaler.scale(fontSizeToScale) / fontSizeToScale; + + Widget result = CompositedTransformTarget( + link: _layerLink, + child: _SliderRenderObjectWidget( + key: _renderObjectKey, + value: _convert(widget.value), + secondaryTrackValue: (widget.secondaryTrackValue != null) + ? _convert(widget.secondaryTrackValue!) + : null, + divisions: widget.divisions, + label: widget.label, + sliderTheme: sliderTheme, + textScaleFactor: effectiveTextScale, + screenSize: screenSize(), + onChanged: (widget.onChanged != null) && (widget.max > widget.min) + ? _handleChanged + : null, + onChangeStart: _handleDragStart, + onChangeEnd: _handleDragEnd, + state: this, + semanticFormatterCallback: widget.semanticFormatterCallback, + onDidGainAccessibilityFocus: handleDidGainAccessibilityFocus, + hasFocus: _focused, + hovering: _hovering, + allowedInteraction: effectiveAllowedInteraction, + ), + ); + + final EdgeInsetsGeometry? padding = widget.padding ?? sliderTheme.padding; + if (padding != null) { + result = Padding(padding: padding, child: result); + } + result = OverlayPortal( + controller: _valueIndicatorOverlayPortalController, + overlayChildBuilder: (BuildContext context) { + return _buildValueIndicator(sliderTheme.showValueIndicator!); + }, + child: result, + ); + + return FocusableActionDetector( + actions: _actionMap, + shortcuts: shortcutMap, + focusNode: focusNode, + autofocus: widget.autofocus, + enabled: _enabled, + onShowFocusHighlight: _handleFocusHighlightChanged, + onShowHoverHighlight: _handleHoverChanged, + mouseCursor: effectiveMouseCursor, + includeFocusSemantics: false, + child: result, + ); + } + + Widget _buildCupertinoSlider(BuildContext context) { + // The render box of a slider has a fixed height but takes up the available + // width. Wrapping the [CupertinoSlider] in this manner will help maintain + // the same size. + return SizedBox( + width: double.infinity, + child: CupertinoSlider( + value: widget.value, + onChanged: widget.onChanged, + onChangeStart: widget.onChangeStart, + onChangeEnd: widget.onChangeEnd, + min: widget.min, + max: widget.max, + divisions: widget.divisions, + activeColor: widget.activeColor, + thumbColor: widget.thumbColor ?? CupertinoColors.white, + ), + ); + } + + final LayerLink _layerLink = LayerLink(); + Widget _buildValueIndicator(ShowValueIndicator showValueIndicator) { + final Widget valueIndicator = CompositedTransformFollower( + link: _layerLink, + child: _ValueIndicatorRenderObjectWidget(state: this), + ); + return switch (showValueIndicator) { + ShowValueIndicator.never => const SizedBox.shrink(), + ShowValueIndicator.onlyForDiscrete => + widget.divisions != null ? valueIndicator : const SizedBox.shrink(), + ShowValueIndicator.onlyForContinuous => + widget.divisions == null ? valueIndicator : const SizedBox.shrink(), + ShowValueIndicator.alwaysVisible || + // ignore: deprecated_member_use + ShowValueIndicator.always || + ShowValueIndicator.onDrag => valueIndicator, + }; + } +} + +class _SliderRenderObjectWidget extends LeafRenderObjectWidget { + const _SliderRenderObjectWidget({ + super.key, + required this.value, + required this.secondaryTrackValue, + required this.divisions, + required this.label, + required this.sliderTheme, + required this.textScaleFactor, + required this.screenSize, + required this.onChanged, + required this.onChangeStart, + required this.onChangeEnd, + required this.state, + required this.semanticFormatterCallback, + required this.onDidGainAccessibilityFocus, + required this.hasFocus, + required this.hovering, + required this.allowedInteraction, + }); + + final double value; + final double? secondaryTrackValue; + final int? divisions; + final String? label; + final SliderThemeData sliderTheme; + final double textScaleFactor; + final Size screenSize; + final ValueChanged? onChanged; + final ValueChanged? onChangeStart; + final ValueChanged? onChangeEnd; + final SemanticFormatterCallback? semanticFormatterCallback; + final VoidCallback? onDidGainAccessibilityFocus; + final _VerticalSliderState state; + final bool hasFocus; + final bool hovering; + final SliderInteraction allowedInteraction; + + @override + _RenderSlider createRenderObject(BuildContext context) { + return _RenderSlider( + value: value, + secondaryTrackValue: secondaryTrackValue, + divisions: divisions, + label: label, + sliderTheme: sliderTheme, + textScaleFactor: textScaleFactor, + screenSize: screenSize, + onChanged: onChanged, + onChangeStart: onChangeStart, + onChangeEnd: onChangeEnd, + state: state, + textDirection: Directionality.of(context), + semanticFormatterCallback: semanticFormatterCallback, + onDidGainAccessibilityFocus: onDidGainAccessibilityFocus, + platform: Theme.of(context).platform, + hasFocus: hasFocus, + hovering: hovering, + gestureSettings: MediaQuery.gestureSettingsOf(context), + allowedInteraction: allowedInteraction, + ); + } + + @override + void updateRenderObject(BuildContext context, _RenderSlider renderObject) { + renderObject + // We should update the `divisions` ahead of `value`, because the `value` + // setter dependent on the `divisions`. + ..divisions = divisions + ..value = value + ..secondaryTrackValue = secondaryTrackValue + ..label = label + ..sliderTheme = sliderTheme + ..textScaleFactor = textScaleFactor + ..screenSize = screenSize + ..onChanged = onChanged + ..onChangeStart = onChangeStart + ..onChangeEnd = onChangeEnd + ..textDirection = Directionality.of(context) + ..semanticFormatterCallback = semanticFormatterCallback + ..onDidGainAccessibilityFocus = onDidGainAccessibilityFocus + ..platform = Theme.of(context).platform + ..hasFocus = hasFocus + ..hovering = hovering + ..gestureSettings = MediaQuery.gestureSettingsOf(context) + ..allowedInteraction = allowedInteraction; + // Ticker provider cannot change since there's a 1:1 relationship between + // the _SliderRenderObjectWidget object and the _SliderState object. + } +} + +class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { + _RenderSlider({ + required double value, + required double? secondaryTrackValue, + required int? divisions, + required String? label, + required SliderThemeData sliderTheme, + required double textScaleFactor, + required Size screenSize, + required TargetPlatform platform, + required ValueChanged? onChanged, + required SemanticFormatterCallback? semanticFormatterCallback, + required this.onDidGainAccessibilityFocus, + required this.onChangeStart, + required this.onChangeEnd, + required _VerticalSliderState state, + required TextDirection textDirection, + required bool hasFocus, + required bool hovering, + required DeviceGestureSettings gestureSettings, + required SliderInteraction allowedInteraction, + }) : assert(value >= 0.0 && value <= 1.0), + assert( + secondaryTrackValue == null || + (secondaryTrackValue >= 0.0 && secondaryTrackValue <= 1.0), + ), + _platform = platform, + _semanticFormatterCallback = semanticFormatterCallback, + _label = label, + _value = value, + _secondaryTrackValue = secondaryTrackValue, + _divisions = divisions, + _sliderTheme = sliderTheme, + _textScaleFactor = textScaleFactor, + _screenSize = screenSize, + _onChanged = onChanged, + _state = state, + _textDirection = textDirection, + _hasFocus = hasFocus, + _hovering = hovering, + _allowedInteraction = allowedInteraction { + _updateLabelPainter(); + final team = GestureArenaTeam(); + _drag = VerticalDragGestureRecognizer() + ..team = team + ..onStart = _handleDragStart + ..onUpdate = _handleDragUpdate + ..onEnd = _handleDragEnd + ..onCancel = _endInteraction + ..gestureSettings = gestureSettings; + _tap = TapGestureRecognizer() + ..team = team + ..onTapDown = _handleTapDown + ..onTapUp = _handleTapUp + ..gestureSettings = gestureSettings; + _overlayAnimation = CurvedAnimation( + parent: _state.overlayController, + curve: Curves.fastOutSlowIn, + ); + _valueIndicatorAnimation = CurvedAnimation( + parent: _state.valueIndicatorController, + curve: Curves.fastOutSlowIn, + ); + _enableAnimation = CurvedAnimation( + parent: _state.enableController, + curve: Curves.easeInOut, + ); + } + static const Duration _positionAnimationDuration = Duration(milliseconds: 75); + static const Duration _minimumInteractionTime = Duration(milliseconds: 500); + + // This value is the touch target, 48, multiplied by 3. + static const double _minPreferredTrackWidth = 144.0; + + // Compute the largest width and height needed to paint the slider shapes, + // other than the track shape. It is assumed that these shapes are vertically + // centered on the track. + double get _maxSliderPartWidth => + _sliderPartSizes.map((Size size) => size.width).reduce(math.max); + double get _maxSliderPartHeight => + _sliderPartSizes.map((Size size) => size.height).reduce(math.max); + double get _thumbSizeHeight => _sliderTheme.thumbShape! + .getPreferredSize(isInteractive, isDiscrete) + .height; + double get _overlayHeight => _sliderTheme.overlayShape! + .getPreferredSize(isInteractive, isDiscrete) + .height; + List get _sliderPartSizes => [ + Size( + _sliderTheme.overlayShape! + .getPreferredSize(isInteractive, isDiscrete) + .width, + _sliderTheme.padding != null ? _thumbSizeHeight : _overlayHeight, + ), + _sliderTheme.thumbShape!.getPreferredSize(isInteractive, isDiscrete), + _sliderTheme.tickMarkShape!.getPreferredSize( + isEnabled: isInteractive, + sliderTheme: sliderTheme, + ), + ]; + double get _minPreferredTrackHeight => _sliderTheme.trackHeight!; + + final _VerticalSliderState _state; + late CurvedAnimation _overlayAnimation; + late CurvedAnimation _valueIndicatorAnimation; + late CurvedAnimation _enableAnimation; + final TextPainter _labelPainter = TextPainter(); + late VerticalDragGestureRecognizer _drag; + late TapGestureRecognizer _tap; + bool _active = false; + VoidCallback? onDidGainAccessibilityFocus; + double _currentDragValue = 0.0; + Rect? overlayRect; + + // This rect is used in gesture calculations, where the gesture coordinates + // are relative to the sliders origin. Therefore, the offset is passed as + // (0,0). + Rect get _trackRect => _sliderTheme.trackShape!.getPreferredRect( + parentBox: this, + sliderTheme: _sliderTheme, + isDiscrete: false, + ); + + bool get isInteractive => onChanged != null; + + bool get isDiscrete => false; // divisions != null && divisions! > 0; + + double get value => _value; + double _value; + set value(double newValue) { + assert(newValue >= 0.0 && newValue <= 1.0); + final double convertedValue = isDiscrete ? _discretize(newValue) : newValue; + if (convertedValue == _value) { + return; + } + _value = convertedValue; + if (isDiscrete) { + // Reset the duration to match the distance that we're traveling, so that + // whatever the distance, we still do it in _positionAnimationDuration, + // and if we get re-targeted in the middle, it still takes that long to + // get to the new location. + final double distance = (_value - _state.positionController.value).abs(); + _state.positionController.duration = distance != 0.0 + ? _positionAnimationDuration * (1.0 / distance) + : Duration.zero; + _state.positionController.animateTo( + convertedValue, + curve: Curves.easeInOut, + ); + } else { + _state.positionController.value = convertedValue; + } + markNeedsSemanticsUpdate(); + } + + double? get secondaryTrackValue => _secondaryTrackValue; + double? _secondaryTrackValue; + set secondaryTrackValue(double? newValue) { + assert(newValue == null || (newValue >= 0.0 && newValue <= 1.0)); + if (newValue == _secondaryTrackValue) { + return; + } + _secondaryTrackValue = newValue; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + DeviceGestureSettings? get gestureSettings => _drag.gestureSettings; + set gestureSettings(DeviceGestureSettings? gestureSettings) { + _drag.gestureSettings = gestureSettings; + _tap.gestureSettings = gestureSettings; + } + + TargetPlatform _platform; + TargetPlatform get platform => _platform; + set platform(TargetPlatform value) { + if (_platform == value) { + return; + } + _platform = value; + markNeedsSemanticsUpdate(); + } + + SemanticFormatterCallback? _semanticFormatterCallback; + SemanticFormatterCallback? get semanticFormatterCallback => + _semanticFormatterCallback; + set semanticFormatterCallback(SemanticFormatterCallback? value) { + if (_semanticFormatterCallback == value) { + return; + } + _semanticFormatterCallback = value; + markNeedsSemanticsUpdate(); + } + + int? get divisions => _divisions; + int? _divisions; + set divisions(int? value) { + if (value == _divisions) { + return; + } + _divisions = value; + markNeedsPaint(); + } + + String? get label => _label; + String? _label; + set label(String? value) { + if (value == _label) { + return; + } + _label = value; + _updateLabelPainter(); + } + + SliderThemeData get sliderTheme => _sliderTheme; + SliderThemeData _sliderTheme; + set sliderTheme(SliderThemeData value) { + if (value == _sliderTheme) { + return; + } + _sliderTheme = value; + _updateLabelPainter(); + } + + double get textScaleFactor => _textScaleFactor; + double _textScaleFactor; + set textScaleFactor(double value) { + if (value == _textScaleFactor) { + return; + } + _textScaleFactor = value; + _updateLabelPainter(); + } + + Size get screenSize => _screenSize; + Size _screenSize; + set screenSize(Size value) { + if (value == _screenSize) { + return; + } + _screenSize = value; + markNeedsPaint(); + } + + ValueChanged? get onChanged => _onChanged; + ValueChanged? _onChanged; + set onChanged(ValueChanged? value) { + if (value == _onChanged) { + return; + } + final bool wasInteractive = isInteractive; + _onChanged = value; + if (wasInteractive != isInteractive) { + if (isInteractive) { + _state.enableController.forward(); + } else { + _state.enableController.reverse(); + } + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + ValueChanged? onChangeStart; + ValueChanged? onChangeEnd; + + TextDirection get textDirection => _textDirection; + TextDirection _textDirection; + set textDirection(TextDirection value) { + if (value == _textDirection) { + return; + } + _textDirection = value; + _updateLabelPainter(); + } + + /// True if this slider has the input focus. + bool get hasFocus => _hasFocus; + bool _hasFocus; + set hasFocus(bool value) { + if (value == _hasFocus) { + return; + } + _hasFocus = value; + _updateForFocus(_hasFocus); + markNeedsSemanticsUpdate(); + } + + /// True if this slider is being hovered over by a pointer. + bool get hovering => _hovering; + bool _hovering; + set hovering(bool value) { + if (value == _hovering) { + return; + } + _hovering = value; + _updateForHover(_hovering); + } + + /// True if the slider is interactive and the slider thumb is being + /// hovered over by a pointer. + bool _hoveringThumb = false; + bool get hoveringThumb => _hoveringThumb; + set hoveringThumb(bool value) { + if (value == _hoveringThumb) { + return; + } + _hoveringThumb = value; + _updateForHover(_hovering); + } + + SliderInteraction _allowedInteraction; + SliderInteraction get allowedInteraction => _allowedInteraction; + set allowedInteraction(SliderInteraction value) { + if (value == _allowedInteraction) { + return; + } + _allowedInteraction = value; + markNeedsSemanticsUpdate(); + } + + void _updateForFocus(bool focused) { + if (focused) { + _state.overlayController.forward(); + if (shouldShowValueIndicatorWhenDragged) { + _state.valueIndicatorController.forward(); + } + } else { + _state.overlayController.reverse(); + if (shouldShowValueIndicatorWhenDragged) { + _state.valueIndicatorController.reverse(); + } + } + } + + void _updateForHover(bool hovered) { + // Only show overlay when pointer is hovering the thumb. + if (hovered && hoveringThumb) { + _state.overlayController.forward(); + } else { + // Only remove overlay when Slider is inactive and unfocused. + if (!_active && !hasFocus) { + _state.overlayController.reverse(); + } + } + } + + bool get shouldAlwaysShowValueIndicator => + _sliderTheme.showValueIndicator == ShowValueIndicator.alwaysVisible; + bool get shouldShowValueIndicatorWhenDragged => + switch (_sliderTheme.showValueIndicator!) { + ShowValueIndicator.onlyForDiscrete => isDiscrete, + ShowValueIndicator.onlyForContinuous => !isDiscrete, + // ignore: deprecated_member_use + ShowValueIndicator.always || ShowValueIndicator.onDrag => true, + ShowValueIndicator.never || ShowValueIndicator.alwaysVisible => false, + }; + + double get _adjustmentUnit { + switch (_platform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + // Matches iOS implementation of material slider. + return 0.1; + case TargetPlatform.ohos: + case TargetPlatform.android: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Matches Android implementation of material slider. + return 0.05; + } + } + + void _updateLabelPainter() { + if (label != null) { + _labelPainter + ..text = TextSpan( + style: _sliderTheme.valueIndicatorTextStyle, + text: label, + ) + ..textDirection = textDirection + // ignore: deprecated_member_use + ..textScaleFactor = textScaleFactor + ..layout(); + } else { + _labelPainter.text = null; + } + // Changing the textDirection can result in the layout changing, because the + // bidi algorithm might line up the glyphs differently which can result in + // different ligatures, different shapes, etc. So we always markNeedsLayout. + markNeedsLayout(); + } + + @override + void systemFontsDidChange() { + super.systemFontsDidChange(); + _labelPainter.markNeedsLayout(); + _updateLabelPainter(); + } + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _overlayAnimation.addListener(markNeedsPaint); + _valueIndicatorAnimation.addListener(markNeedsPaint); + _enableAnimation.addListener(markNeedsPaint); + _state.positionController.addListener(markNeedsPaint); + } + + @override + void detach() { + _overlayAnimation.removeListener(markNeedsPaint); + _valueIndicatorAnimation.removeListener(markNeedsPaint); + _enableAnimation.removeListener(markNeedsPaint); + _state.positionController.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void dispose() { + _drag.dispose(); + _tap.dispose(); + _labelPainter.dispose(); + _enableAnimation.dispose(); + _valueIndicatorAnimation.dispose(); + _overlayAnimation.dispose(); + super.dispose(); + } + + double _getValueFromVisualPosition(double visualPosition) { + return switch (textDirection) { + TextDirection.rtl => 1.0 - visualPosition, + TextDirection.ltr => visualPosition, + }; + } + + double _getValueFromGlobalPosition(Offset globalPosition) { + final double visualPosition = + (_trackRect.bottom - globalToLocal(globalPosition).dy) / + _trackRect.height; + return _getValueFromVisualPosition(visualPosition); + } + + double _discretize(double value) { + double result = clampDouble(value, 0.0, 1.0); + if (isDiscrete) { + result = (result * divisions!).round() / divisions!; + } + return result; + } + + void _startInteraction(Offset globalPosition) { + if (!_state.mounted) { + return; + } + if (!_active && isInteractive) { + switch (allowedInteraction) { + case SliderInteraction.tapAndSlide: + case SliderInteraction.tapOnly: + _active = true; + _currentDragValue = _getValueFromGlobalPosition(globalPosition); + case SliderInteraction.slideThumb: + if (_isPointerOnOverlay(globalPosition)) { + _active = true; + _currentDragValue = value; + } + case SliderInteraction.slideOnly: + _active = true; + _currentDragValue = value; + } + + if (_active) { + // We supply the *current* value as the start location, so that if we have + // a tap, it consists of a call to onChangeStart with the previous value and + // a call to onChangeEnd with the new value. + onChangeStart?.call(_discretize(value)); + onChanged!(_discretize(_currentDragValue)); + _state.overlayController.forward(); + if (shouldShowValueIndicatorWhenDragged) { + _state.valueIndicatorController.forward(); + _state.interactionTimer?.cancel(); + _state.interactionTimer = Timer( + _minimumInteractionTime * timeDilation, + () { + _state.interactionTimer = null; + if (!_active && _state.valueIndicatorController.isCompleted) { + _state.valueIndicatorController.reverse(); + } + }, + ); + } + } + } + } + + void _endInteraction() { + if (!_state.mounted) { + return; + } + + if (_active && _state.mounted) { + onChangeEnd?.call(_discretize(_currentDragValue)); + _active = false; + _currentDragValue = 0.0; + _state.overlayController.reverse(); + if (shouldShowValueIndicatorWhenDragged && + _state.interactionTimer == null) { + _state.valueIndicatorController.reverse(); + } + } + } + + void _handleDragStart(DragStartDetails details) { + _startInteraction(details.globalPosition); + } + + void _handleDragUpdate(DragUpdateDetails details) { + if (!_state.mounted) { + return; + } + + switch (allowedInteraction) { + case SliderInteraction.tapAndSlide: + case SliderInteraction.slideOnly: + case SliderInteraction.slideThumb: + if (_active && isInteractive) { + final double valueDelta = details.primaryDelta! / _trackRect.height; + _currentDragValue -= valueDelta; + onChanged!(_discretize(_currentDragValue)); + } + case SliderInteraction.tapOnly: + // cannot slide (drag) as its tapOnly. + break; + } + } + + void _handleDragEnd(DragEndDetails details) { + _endInteraction(); + } + + void _handleTapDown(TapDownDetails details) { + _startInteraction(details.globalPosition); + } + + void _handleTapUp(TapUpDetails details) { + _endInteraction(); + } + + bool _isPointerOnOverlay(Offset globalPosition) { + return overlayRect!.contains(globalToLocal(globalPosition)); + } + + @override + bool hitTestSelf(Offset position) => true; + + @override + void handleEvent(PointerEvent event, BoxHitTestEntry entry) { + if (!_state.mounted) { + return; + } + assert(debugHandleEvent(event, entry)); + if (event is PointerDownEvent && isInteractive) { + // We need to add the drag first so that it has priority. + _drag.addPointer(event); + _tap.addPointer(event); + } + if (isInteractive && overlayRect != null) { + hoveringThumb = overlayRect!.contains(event.localPosition); + } + } + + @override + double computeMinIntrinsicWidth(double height) => + _minPreferredTrackWidth + _maxSliderPartWidth; + + @override + double computeMaxIntrinsicWidth(double height) => + _minPreferredTrackWidth + _maxSliderPartWidth; + + @override + double computeMinIntrinsicHeight(double width) => + math.max(_minPreferredTrackHeight, _maxSliderPartHeight); + + @override + double computeMaxIntrinsicHeight(double width) => + math.max(_minPreferredTrackHeight, _maxSliderPartHeight); + + @override + bool get sizedByParent => true; + + @override + Size computeDryLayout(BoxConstraints constraints) { + return Size( + constraints.hasBoundedWidth + ? constraints.maxWidth + : _minPreferredTrackWidth + _maxSliderPartWidth, + constraints.hasBoundedHeight + ? constraints.maxHeight + : math.max(_minPreferredTrackHeight, _maxSliderPartHeight), + ); + } + + @override + void paint(PaintingContext context, Offset offset) { + final double controllerValue = _state.positionController.value; + + // The visual position is the position of the thumb from 0 to 1 from left + // to right. In left to right, this is the same as the value, but it is + // reversed for right to left text. + final ( + double visualPosition, + double? secondaryVisualPosition, + ) = switch (textDirection) { + TextDirection.rtl when _secondaryTrackValue == null => ( + 1.0 - controllerValue, + null, + ), + TextDirection.rtl => (1.0 - controllerValue, 1.0 - _secondaryTrackValue!), + TextDirection.ltr => (controllerValue, _secondaryTrackValue), + }; + + final Rect trackRect = _sliderTheme.trackShape!.getPreferredRect( + parentBox: this, + offset: offset, + sliderTheme: _sliderTheme, + isDiscrete: isDiscrete, + ); + + final Offset thumbCenter = _calcThumbCenter( + trackRect: trackRect, + visualPosition: visualPosition, + ); + + if (isInteractive) { + final Size overlaySize = sliderTheme.overlayShape!.getPreferredSize( + isInteractive, + false, + ); + overlayRect = Rect.fromCircle( + center: thumbCenter, + radius: overlaySize.width / 2.0, + ); + } + final Offset? secondaryOffset = (secondaryVisualPosition != null) + ? Offset( + trackRect.left + secondaryVisualPosition * trackRect.width, + trackRect.center.dy, + ) + : null; + + // If [Slider.year2023] is false, the thumb uses handle thumb shape and gapped track shape. + // The handle width and track gap are adjusted when the thumb is pressed. + double? thumbWidth = _sliderTheme.thumbSize + ?.resolve({}) + ?.width; + final double? thumbHeight = _sliderTheme.thumbSize + ?.resolve({}) + ?.height; + double? trackGap = _sliderTheme.trackGap; + final double? pressedThumbWidth = _sliderTheme.thumbSize?.resolve( + { + WidgetState.pressed, + }, + )?.width; + final double delta; + if (_active && + thumbWidth != null && + pressedThumbWidth != null && + trackGap != null) { + delta = thumbWidth - pressedThumbWidth; + if (thumbWidth > 0.0) { + thumbWidth = pressedThumbWidth; + } + if (trackGap > 0.0) { + trackGap = trackGap - delta / 2; + } + } + + _sliderTheme.trackShape!.paint( + context, + offset, + parentBox: this, + sliderTheme: _sliderTheme.copyWith(trackGap: trackGap), + enableAnimation: _enableAnimation, + textDirection: _textDirection, + thumbCenter: thumbCenter, + secondaryOffset: secondaryOffset, + isDiscrete: isDiscrete, + isEnabled: isInteractive, + ); + + if (!_overlayAnimation.isDismissed) { + _sliderTheme.overlayShape!.paint( + context, + thumbCenter, + activationAnimation: _overlayAnimation, + enableAnimation: _enableAnimation, + isDiscrete: isDiscrete, + labelPainter: _labelPainter, + parentBox: this, + sliderTheme: _sliderTheme, + textDirection: _textDirection, + value: _value, + textScaleFactor: _textScaleFactor, + sizeWithOverflow: screenSize.isEmpty ? size : screenSize, + ); + } + + if (isDiscrete) { + final double tickMarkWidth = _sliderTheme.tickMarkShape! + .getPreferredSize(isEnabled: isInteractive, sliderTheme: _sliderTheme) + .width; + final double discreteTrackPadding = trackRect.height; + final double adjustedTrackWidth = trackRect.width - discreteTrackPadding; + // If the tick marks would be too dense, don't bother painting them. + if (adjustedTrackWidth / divisions! >= 3.0 * tickMarkWidth) { + final double dy = trackRect.center.dy; + for (var i = 0; i <= divisions!; i++) { + final double value = i / divisions!; + // The ticks are mapped to be within the track, so the tick mark width + // must be subtracted from the track width. + final double dx = + trackRect.left + + value * adjustedTrackWidth + + discreteTrackPadding / 2; + final tickMarkOffset = Offset(dx, dy); + _sliderTheme.tickMarkShape!.paint( + context, + tickMarkOffset, + parentBox: this, + sliderTheme: _sliderTheme, + enableAnimation: _enableAnimation, + textDirection: _textDirection, + thumbCenter: thumbCenter, + isEnabled: isInteractive, + ); + } + } + } + + if (isInteractive && + label != null && + ((shouldShowValueIndicatorWhenDragged && + !_valueIndicatorAnimation.isDismissed) || + shouldAlwaysShowValueIndicator)) { + _state.paintValueIndicator = (PaintingContext context, Offset offset) { + if (attached && _labelPainter.text != null) { + _sliderTheme.valueIndicatorShape?.paint( + context, + offset + thumbCenter, + activationAnimation: shouldAlwaysShowValueIndicator + ? const AlwaysStoppedAnimation(1) + : _valueIndicatorAnimation, + enableAnimation: shouldAlwaysShowValueIndicator + ? const AlwaysStoppedAnimation(1) + : _enableAnimation, + isDiscrete: isDiscrete, + labelPainter: _labelPainter, + parentBox: this, + sliderTheme: _sliderTheme, + textDirection: _textDirection, + value: _value, + textScaleFactor: textScaleFactor, + sizeWithOverflow: screenSize.isEmpty ? size : screenSize, + ); + } + }; + } else { + _state.paintValueIndicator = null; + } + + _sliderTheme.thumbShape!.paint( + context, + thumbCenter, + activationAnimation: _overlayAnimation, + enableAnimation: _enableAnimation, + isDiscrete: isDiscrete, + labelPainter: _labelPainter, + parentBox: this, + sliderTheme: thumbWidth != null && thumbHeight != null + ? _sliderTheme.copyWith( + thumbSize: WidgetStatePropertyAll( + Size(thumbWidth, thumbHeight), + ), + ) + : _sliderTheme, + textDirection: _textDirection, + value: _value, + textScaleFactor: textScaleFactor, + sizeWithOverflow: screenSize.isEmpty ? size : screenSize, + ); + } + + /// Calculates the local coordinate center of the [Slider] thumb given its + /// physical placement on the track from 0.0 (left) to 1.0 (right). + /// + /// The [visualPosition] is provided by the caller so semantics can use the + /// raw logical value while paint can use the smoothly animated value. + Offset _calcThumbCenter({ + required Rect trackRect, + required double visualPosition, + }) { + final double padding = _sliderTheme.trackShape!.isRounded + ? trackRect.width + : 0.0; + final double thumbPosition = isDiscrete + ? trackRect.left + + visualPosition * (trackRect.width - padding) + + padding / 2 + : trackRect.bottom - visualPosition * trackRect.height; + // Apply padding to trackRect.left and trackRect.right if the track height is + // greater than the thumb radius to ensure the thumb is drawn within the track. + final Size thumbPreferredSize = _sliderTheme.thumbShape!.getPreferredSize( + isInteractive, + isDiscrete, + ); + final double thumbPadding = padding > thumbPreferredSize.width / 2 + ? padding / 2 + : 0; + return Offset( + trackRect.center.dx, + clampDouble( + thumbPosition, + trackRect.top + thumbPadding, + trackRect.bottom - thumbPadding, + ), + ); + } + + Offset get _semanticThumbCenter { + final double visualPosition = switch (textDirection) { + TextDirection.rtl => 1.0 - _value, + TextDirection.ltr => _value, + }; + return _calcThumbCenter( + trackRect: _trackRect, + visualPosition: visualPosition, + ); + } + + @override + void assembleSemanticsNode( + SemanticsNode node, + SemanticsConfiguration config, + Iterable children, + ) { + node + ..rect = Rect.fromCenter( + center: _semanticThumbCenter, + width: kMinInteractiveDimension, + height: kMinInteractiveDimension, + ) + ..updateWith(config: config); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + + // The Slider widget has its own Focus widget. + // We mark the Focus widget with "includeFocusSemantics: false" + // and we want that semantics node to collect the semantics information here + // so that it's all in the same node. + config + ..isSemanticBoundary = true + ..isEnabled = isInteractive; + if (label != null) { + config.label = label!; + } + config + ..isSlider = true + ..isFocusable = isInteractive + ..isFocused = hasFocus; + + if (onDidGainAccessibilityFocus != null) { + config.onDidGainAccessibilityFocus = onDidGainAccessibilityFocus; + } + config.textDirection = textDirection; + if (isInteractive) { + config + ..onIncrease = increaseAction + ..onDecrease = decreaseAction + ..onFocus = onFocusAction; + } + + if (semanticFormatterCallback != null) { + config + ..value = semanticFormatterCallback!(_state._lerp(value)) + ..increasedValue = semanticFormatterCallback!( + _state._lerp(clampDouble(value + _semanticActionUnit, 0.0, 1.0)), + ) + ..decreasedValue = semanticFormatterCallback!( + _state._lerp(clampDouble(value - _semanticActionUnit, 0.0, 1.0)), + ); + } else { + config + ..value = '${(value * 100).round()}%' + ..increasedValue = + '${(clampDouble(value + _semanticActionUnit, 0.0, 1.0) * 100).round()}%' + ..decreasedValue = + '${(clampDouble(value - _semanticActionUnit, 0.0, 1.0) * 100).round()}%'; + } + } + + double get _semanticActionUnit => + divisions != null ? 1.0 / divisions! : _adjustmentUnit; + + void onFocusAction() { + if (isInteractive) { + if (!_state.mounted) { + return; + } + if (!hasFocus) { + _state.focusNode.requestFocus(); + } + } + } + + void increaseAction() { + if (isInteractive) { + onChangeStart!(currentValue); + final double increase = increaseValue(); + onChanged!(increase); + onChangeEnd!(increase); + if (!_state.mounted) { + return; + } + } + } + + void decreaseAction() { + if (isInteractive) { + onChangeStart!(currentValue); + final double decrease = decreaseValue(); + onChanged!(decrease); + onChangeEnd!(decrease); + if (!_state.mounted) { + return; + } + } + } + + double get currentValue { + return clampDouble(value, 0.0, 1.0); + } + + double increaseValue() { + return clampDouble(value + _semanticActionUnit, 0.0, 1.0); + } + + double decreaseValue() { + return clampDouble(value - _semanticActionUnit, 0.0, 1.0); + } +} + +class _AdjustSliderIntent extends Intent { + const _AdjustSliderIntent({required this.type}); + + const _AdjustSliderIntent.right() : type = _SliderAdjustmentType.right; + + const _AdjustSliderIntent.left() : type = _SliderAdjustmentType.left; + + const _AdjustSliderIntent.up() : type = _SliderAdjustmentType.up; + + const _AdjustSliderIntent.down() : type = _SliderAdjustmentType.down; + + final _SliderAdjustmentType type; +} + +enum _SliderAdjustmentType { right, left, up, down } + +class _ValueIndicatorRenderObjectWidget extends LeafRenderObjectWidget { + const _ValueIndicatorRenderObjectWidget({required this.state}); + + final _VerticalSliderState state; + + @override + _RenderValueIndicator createRenderObject(BuildContext context) { + return _RenderValueIndicator(state: state); + } + + @override + void updateRenderObject( + BuildContext context, + _RenderValueIndicator renderObject, + ) { + renderObject._state = state; + } +} + +class _RenderValueIndicator extends RenderBox + with RelayoutWhenSystemFontsChangeMixin { + _RenderValueIndicator({required _VerticalSliderState state}) + : _state = state { + _valueIndicatorAnimation = CurvedAnimation( + parent: _state.valueIndicatorController, + curve: Curves.fastOutSlowIn, + ); + } + late CurvedAnimation _valueIndicatorAnimation; + _VerticalSliderState _state; + + @override + bool get sizedByParent => true; + + @override + void attach(PipelineOwner owner) { + super.attach(owner); + _valueIndicatorAnimation.addListener(markNeedsPaint); + _state.positionController.addListener(markNeedsPaint); + } + + @override + void detach() { + _valueIndicatorAnimation.removeListener(markNeedsPaint); + _state.positionController.removeListener(markNeedsPaint); + super.detach(); + } + + @override + void paint(PaintingContext context, Offset offset) { + _state.paintValueIndicator?.call(context, offset); + } + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.smallest; + } + + @override + void dispose() { + _valueIndicatorAnimation.dispose(); + super.dispose(); + } +} + +class _SliderDefaultsM2 extends SliderThemeData { + _SliderDefaultsM2(this.context) : super(trackHeight: 4.0); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + late final SliderThemeData sliderTheme = SliderTheme.of(context); + + @override + Color? get activeTrackColor => _colors.primary; + + @override + Color? get inactiveTrackColor => _colors.primary.withValues(alpha: 0.24); + + @override + Color? get secondaryActiveTrackColor => + _colors.primary.withValues(alpha: 0.54); + + @override + Color? get disabledActiveTrackColor => + _colors.onSurface.withValues(alpha: 0.32); + + @override + Color? get disabledInactiveTrackColor => + _colors.onSurface.withValues(alpha: 0.12); + + @override + Color? get disabledSecondaryActiveTrackColor => + _colors.onSurface.withValues(alpha: 0.12); + + @override + Color? get activeTickMarkColor => _colors.onPrimary.withValues(alpha: 0.54); + + @override + Color? get inactiveTickMarkColor => _colors.primary.withValues(alpha: 0.54); + + @override + Color? get disabledActiveTickMarkColor => + _colors.onPrimary.withValues(alpha: 0.12); + + @override + Color? get disabledInactiveTickMarkColor => + _colors.onSurface.withValues(alpha: 0.12); + + @override + Color? get thumbColor => _colors.primary; + + @override + Color? get disabledThumbColor => Color.alphaBlend( + _colors.onSurface.withValues(alpha: .38), + _colors.surface, + ); + + @override + Color? get overlayColor => _colors.primary.withValues(alpha: 0.12); + + @override + TextStyle? get valueIndicatorTextStyle => + Theme.of(context).textTheme.bodyLarge!.copyWith(color: _colors.onPrimary); + + @override + Color? get valueIndicatorColor { + if (sliderTheme.valueIndicatorShape + is RoundedRectSliderValueIndicatorShape) { + return _colors.inverseSurface; + } + return _colors.primary; + } + + @override + SliderComponentShape? get valueIndicatorShape => + const RectangularSliderValueIndicatorShape(); + + @override + SliderComponentShape? get thumbShape => const RoundSliderThumbShape(); + + @override + SliderTrackShape? get trackShape => const RoundedRectSliderTrackShape(); + + @override + SliderComponentShape? get overlayShape => const RoundSliderOverlayShape(); + + @override + SliderTickMarkShape? get tickMarkShape => const RoundSliderTickMarkShape(); +} + +class _SliderDefaultsM3Year2023 extends SliderThemeData { + _SliderDefaultsM3Year2023(this.context) : super(trackHeight: 4.0); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => _colors.primary; + + @override + Color? get inactiveTrackColor => _colors.surfaceContainerHighest; + + @override + Color? get secondaryActiveTrackColor => + _colors.primary.withValues(alpha: 0.54); + + @override + Color? get disabledActiveTrackColor => + _colors.onSurface.withValues(alpha: 0.38); + + @override + Color? get disabledInactiveTrackColor => + _colors.onSurface.withValues(alpha: 0.12); + + @override + Color? get disabledSecondaryActiveTrackColor => + _colors.onSurface.withValues(alpha: 0.12); + + @override + Color? get activeTickMarkColor => _colors.onPrimary.withValues(alpha: 0.38); + + @override + Color? get inactiveTickMarkColor => + _colors.onSurfaceVariant.withValues(alpha: 0.38); + + @override + Color? get disabledActiveTickMarkColor => + _colors.onSurface.withValues(alpha: 0.38); + + @override + Color? get disabledInactiveTickMarkColor => + _colors.onSurface.withValues(alpha: 0.38); + + @override + Color? get thumbColor => _colors.primary; + + @override + Color? get disabledThumbColor => Color.alphaBlend( + _colors.onSurface.withValues(alpha: 0.38), + _colors.surface, + ); + + @override + Color? get overlayColor => + WidgetStateColor.resolveWith((Set states) { + if (states.contains(WidgetState.dragged)) { + return _colors.primary.withValues(alpha: 0.1); + } + if (states.contains(WidgetState.hovered)) { + return _colors.primary.withValues(alpha: 0.08); + } + if (states.contains(WidgetState.focused)) { + return _colors.primary.withValues(alpha: 0.1); + } + + return Colors.transparent; + }); + + @override + TextStyle? get valueIndicatorTextStyle => Theme.of( + context, + ).textTheme.labelMedium!.copyWith(color: _colors.onPrimary); + + @override + Color? get valueIndicatorColor => _colors.primary; + + @override + SliderComponentShape? get valueIndicatorShape => + const DropSliderValueIndicatorShape(); + + @override + SliderComponentShape? get thumbShape => const RoundSliderThumbShape(); + + @override + SliderTrackShape? get trackShape => const RoundedRectSliderTrackShape(); + + @override + SliderComponentShape? get overlayShape => const RoundSliderOverlayShape(); + + @override + SliderTickMarkShape? get tickMarkShape => const RoundSliderTickMarkShape(); +} + +// BEGIN GENERATED TOKEN PROPERTIES - Slider + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +// dart format off +class _SliderDefaultsM3 extends SliderThemeData { + _SliderDefaultsM3(this.context) + : super(trackHeight: 16.0); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + + @override + Color? get activeTrackColor => _colors.primary; + + @override + Color? get inactiveTrackColor => _colors.secondaryContainer; + + @override + Color? get secondaryActiveTrackColor => _colors.primary.withValues(alpha: 0.54); + + @override + Color? get disabledActiveTrackColor => _colors.onSurface.withValues(alpha: 0.38); + + @override + Color? get disabledInactiveTrackColor => _colors.onSurface.withValues(alpha: 0.12); + + @override + Color? get disabledSecondaryActiveTrackColor => _colors.onSurface.withValues(alpha: 0.38); + + @override + Color? get activeTickMarkColor => _colors.onPrimary.withValues(alpha: 1.0); + + @override + Color? get inactiveTickMarkColor => _colors.onSecondaryContainer.withValues(alpha: 1.0); + + @override + Color? get disabledActiveTickMarkColor => _colors.onInverseSurface; + + @override + Color? get disabledInactiveTickMarkColor => _colors.onSurface; + + @override + Color? get thumbColor => _colors.primary; + + @override + Color? get disabledThumbColor => _colors.onSurface.withValues(alpha: 0.38); + + @override + Color? get overlayColor => WidgetStateColor.resolveWith((Set states) { + if (states.contains(WidgetState.dragged)) { + return _colors.primary.withValues(alpha: 0.1); + } + if (states.contains(WidgetState.hovered)) { + return _colors.primary.withValues(alpha: 0.08); + } + if (states.contains(WidgetState.focused)) { + return _colors.primary.withValues(alpha: 0.1); + } + + return Colors.transparent; + }); + + @override + TextStyle? get valueIndicatorTextStyle => Theme.of(context).textTheme.labelLarge!.copyWith( + color: _colors.onInverseSurface, + ); + + @override + Color? get valueIndicatorColor => _colors.inverseSurface; + + @override + SliderComponentShape? get valueIndicatorShape => const RoundedRectSliderValueIndicatorShape(); + + @override + SliderComponentShape? get thumbShape => const HandleThumbShape(); + + @override + SliderTrackShape? get trackShape => const GappedSliderTrackShape(); + + @override + SliderComponentShape? get overlayShape => const RoundSliderOverlayShape(); + + @override + SliderTickMarkShape? get tickMarkShape => const RoundSliderTickMarkShape(tickMarkRadius: 4.0 / 2); + + @override + WidgetStateProperty? get thumbSize { + return WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.disabled)) { + return const Size(4.0, 44.0); + } + if (states.contains(WidgetState.hovered)) { + return const Size(4.0, 44.0); + } + if (states.contains(WidgetState.focused)) { + return const Size(2.0, 44.0); + } + if (states.contains(WidgetState.pressed)) { + return const Size(2.0, 44.0); + } + return const Size(4.0, 44.0); + }); + } + + @override + double? get trackGap => 6.0; +} +// dart format on + +// END GENERATED TOKEN PROPERTIES - Slider + +class RoundedRectSliderTrackShape extends SliderTrackShape { + /// Create a slider track that draws two rectangles with rounded outer edges. + const RoundedRectSliderTrackShape(); + + @override + Rect getPreferredRect({ + required RenderBox parentBox, + Offset offset = Offset.zero, + required SliderThemeData sliderTheme, + bool isEnabled = false, + bool isDiscrete = false, + }) { + // final double thumbHeight = sliderTheme.thumbShape! + // .getPreferredSize(isEnabled, isDiscrete) + // .height; + final double overlayHight = sliderTheme.overlayShape! + .getPreferredSize(isEnabled, isDiscrete) + .height; + double trackWidth = sliderTheme.trackHeight!; + assert(overlayHight >= 0); + assert(trackWidth >= 0); + + // If the track colors are transparent, then override only the track height + // to maintain overall Slider width. + if (sliderTheme.activeTrackColor == Colors.transparent && + sliderTheme.inactiveTrackColor == Colors.transparent) { + trackWidth = 0; + } + + final double trackLeft = + offset.dx + (parentBox.size.width - trackWidth) / 2; + final double trackTop = offset.dy + 10; + // padding + // (sliderTheme.padding == null + // ? math.max(overlayHight / 2, thumbHeight / 2) + // : 0); + final double trackRight = trackLeft + trackWidth; + final double trackBottom = trackTop + parentBox.size.height - 20; + // (sliderTheme.padding == null ? math.max(thumbHeight, overlayHight) : 0); + // If the parentBox's size less than slider's size the trackRight will be less than trackLeft, so switch them. + return Rect.fromLTRB( + trackLeft, + math.min(trackTop, trackBottom), + trackRight, + math.max(trackTop, trackBottom), + ); + } + + @override + void paint( + PaintingContext context, + Offset offset, { + required RenderBox parentBox, + required SliderThemeData sliderTheme, + required Animation enableAnimation, + required TextDirection textDirection, + required Offset thumbCenter, + Offset? secondaryOffset, + bool isDiscrete = false, + bool isEnabled = false, + double additionalActiveTrackHeight = 2, + }) { + assert(sliderTheme.disabledActiveTrackColor != null); + assert(sliderTheme.disabledInactiveTrackColor != null); + assert(sliderTheme.activeTrackColor != null); + assert(sliderTheme.inactiveTrackColor != null); + assert(sliderTheme.thumbShape != null); + // If the slider [SliderThemeData.trackHeight] is less than or equal to 0, + // then it makes no difference whether the track is painted or not, + // therefore the painting can be a no-op. + if (sliderTheme.trackHeight == null || sliderTheme.trackHeight! <= 0) { + return; + } + + // Assign the track segment paints, which are leading: active and + // trailing: inactive. + final activeTrackColorTween = ColorTween( + begin: sliderTheme.disabledActiveTrackColor, + end: sliderTheme.activeTrackColor, + ); + final inactiveTrackColorTween = ColorTween( + begin: sliderTheme.disabledInactiveTrackColor, + end: sliderTheme.inactiveTrackColor, + ); + final activePaint = Paint() + ..color = activeTrackColorTween.evaluate(enableAnimation)!; + final inactivePaint = Paint() + ..color = inactiveTrackColorTween.evaluate(enableAnimation)!; + final (Paint leftTrackPaint, Paint rightTrackPaint) = ( + activePaint, + inactivePaint, + ); + + final Rect trackRect = getPreferredRect( + parentBox: parentBox, + offset: offset, + sliderTheme: sliderTheme, + isEnabled: isEnabled, + isDiscrete: isDiscrete, + ); + final trackRadius = Radius.circular(trackRect.height / 2); + final activeTrackRadius = Radius.circular( + (trackRect.height + additionalActiveTrackHeight) / 2, + ); + + final bool drawInactiveTrack = + thumbCenter.dy > (trackRect.top - (sliderTheme.trackHeight! / 2)); + if (drawInactiveTrack) { + // Draw the inactive track segment. + context.canvas.drawRRect( + RRect.fromLTRBR( + trackRect.left, + trackRect.top, + trackRect.right, + thumbCenter.dy - (sliderTheme.trackHeight! / 2), + trackRadius, + ), + rightTrackPaint, + ); + } + final bool drawActiveTrack = + thumbCenter.dy < (trackRect.bottom - (sliderTheme.trackHeight! / 2)); + if (drawActiveTrack) { + // Draw the active track segment. + context.canvas.drawRRect( + RRect.fromLTRBR( + trackRect.left, + thumbCenter.dy + (sliderTheme.trackHeight! / 2), + trackRect.right, + trackRect.bottom, + activeTrackRadius, + ), + leftTrackPaint, + ); + } + + // final bool showSecondaryTrack = + // (secondaryOffset != null) && (secondaryOffset.dx > thumbCenter.dx); + + // if (showSecondaryTrack) { + // final secondaryTrackColorTween = ColorTween( + // begin: sliderTheme.disabledSecondaryActiveTrackColor, + // end: sliderTheme.secondaryActiveTrackColor, + // ); + // final secondaryTrackPaint = Paint() + // ..color = secondaryTrackColorTween.evaluate(enableAnimation)!; + + // context.canvas.drawRRect( + // RRect.fromLTRBAndCorners( + // thumbCenter.dx, + // trackRect.top, + // secondaryOffset.dx, + // trackRect.bottom, + // topRight: trackRadius, + // bottomRight: trackRadius, + // ), + // secondaryTrackPaint, + // ); + // } + } + + @override + bool get isRounded => true; +} diff --git a/lib/common/widgets/flutter/vertical_tabs.dart b/lib/common/widgets/flutter/vertical_tabs.dart index 70ad399456..bc0a5814ed 100644 --- a/lib/common/widgets/flutter/vertical_tabs.dart +++ b/lib/common/widgets/flutter/vertical_tabs.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// ignore_for_file: prefer_initializing_formals + import 'dart:math' as math; import 'dart:ui' show SemanticsRole, lerpDouble; diff --git a/lib/common/widgets/fractionally_sized_box.dart b/lib/common/widgets/fractionally_sized_box.dart new file mode 100644 index 0000000000..9eadfcd85e --- /dev/null +++ b/lib/common/widgets/fractionally_sized_box.dart @@ -0,0 +1,115 @@ +import 'dart:math' as math; + +import 'package:flutter/rendering.dart' show RenderFractionallySizedOverflowBox; +import 'package:flutter/widgets.dart'; + +class CustomFractionallySizedBox extends FractionallySizedBox { + const CustomFractionallySizedBox({ + super.key, + super.alignment, + required double super.widthFactor, + required double super.heightFactor, + required this.maxWidth, + super.child, + }); + + final double maxWidth; + + @override + RenderFractionallySizedOverflowBox createRenderObject(BuildContext context) { + return CustomRenderFractionallySizedOverflowBox( + alignment: alignment, + widthFactor: widthFactor, + heightFactor: heightFactor, + textDirection: Directionality.maybeOf(context), + maxWidth: maxWidth, + ); + } +} + +class CustomRenderFractionallySizedOverflowBox + extends RenderFractionallySizedOverflowBox { + CustomRenderFractionallySizedOverflowBox({ + super.child, + super.widthFactor, + super.heightFactor, + super.alignment, + super.textDirection, + required double maxWidth, + }) : _maxWidth = maxWidth; + + final double _maxWidth; + + BoxConstraints _getInnerConstraints(BoxConstraints constraints) { + double minWidth = constraints.minWidth; + double maxWidth = constraints.maxWidth; + if (widthFactor != null) { + double width = maxWidth * widthFactor!; + if (maxWidth > constraints.maxHeight) { + width = math.min(_maxWidth, width); + } + minWidth = width; + maxWidth = width; + } + double minHeight = constraints.minHeight; + double maxHeight = constraints.maxHeight; + if (heightFactor != null) { + final double height = maxHeight * heightFactor!; + minHeight = height; + maxHeight = height; + } + return BoxConstraints( + minWidth: minWidth, + maxWidth: maxWidth, + minHeight: minHeight, + maxHeight: maxHeight, + ); + } + + @override + @protected + Size computeDryLayout(covariant BoxConstraints constraints) { + if (child != null) { + final Size childSize = child!.getDryLayout( + _getInnerConstraints(constraints), + ); + return constraints.constrain(childSize); + } + return constraints.constrain( + _getInnerConstraints(constraints).constrain(Size.zero), + ); + } + + @override + double? computeDryBaseline( + covariant BoxConstraints constraints, + TextBaseline baseline, + ) { + final RenderBox? child = this.child; + if (child == null) { + return null; + } + final BoxConstraints childConstraints = _getInnerConstraints(constraints); + final double? result = child.getDryBaseline(childConstraints, baseline); + if (result == null) { + return null; + } + final Size childSize = child.getDryLayout(childConstraints); + final Size size = getDryLayout(constraints); + return result + + resolvedAlignment.alongOffset(size - childSize as Offset).dy; + } + + @override + void performLayout() { + if (child != null) { + child!.layout(_getInnerConstraints(constraints), parentUsesSize: true); + size = constraints.constrain(child!.size); + alignChild(); + } else { + size = constraints.constrain( + _getInnerConstraints(constraints).constrain(Size.zero), + ); + } + } +} diff --git a/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart b/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart index 5a90e67a6e..a70ef23f57 100644 --- a/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart +++ b/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/utils/storage_pref.dart'; +import 'package:flutter/foundation.dart' show PlatformDispatcher; import 'package:flutter/gestures.dart'; mixin InitialPositionMixin on GestureRecognizer { @@ -50,22 +51,32 @@ bool _computeHitSlop( Offset lastPosition, ) { switch (kind) { - case PointerDeviceKind.mouse: + case .mouse: return globalDistanceMoved > kPrecisePointerHitSlop; - case PointerDeviceKind.stylus: - case PointerDeviceKind.invertedStylus: - case PointerDeviceKind.unknown: - case PointerDeviceKind.touch: + case .stylus: + case .invertedStylus: + case .unknown: + case .touch: return globalDistanceMoved > settings.touchSlop! && - _calc(initialPosition!, lastPosition); - case PointerDeviceKind.trackpad: + _calcAngle(initialPosition!, lastPosition); + case .trackpad: return globalDistanceMoved > settings.touchSlop!; } } -bool _calc(Offset initialPosition, Offset lastPosition) { +bool _calcAngle(Offset initialPosition, Offset lastPosition) { final offset = lastPosition - initialPosition; // 判定:只要水平位移 > 垂直位移即算横滑(原为 dx > 3·dy,过于苛刻)。 // 约 45° 以内的滑动都会被判定为横向,让简介/评论区左右切换更容易触发。 return offset.dx.abs() > offset.dy.abs(); } + +final deviceTouchSlop = _calcDeviceTouchSlop(); + +double _calcDeviceTouchSlop() { + final view = PlatformDispatcher.instance.views.first; + final physicalTouchSlop = view.gestureSettings.physicalTouchSlop; + return physicalTouchSlop == null + ? kTouchSlop + : physicalTouchSlop / view.devicePixelRatio; +} diff --git a/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart b/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart index f4170ab941..c7e7fc12be 100644 --- a/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart +++ b/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart @@ -1,87 +1,94 @@ import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; -import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:flutter/gestures.dart'; -mixin ImageGestureRecognizerMixin on GestureRecognizer { +class ImageHorizontalDragGestureRecognizer + extends CustomHorizontalDragGestureRecognizer { + ImageHorizontalDragGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + }); + int? _pointer; + bool _reset = true; + bool _hasAcceptedOrRejected = false; + + bool isAtLeftEdge = false; + bool isAtRightEdge = false; + @override void addPointer(PointerDownEvent event, {bool isPointerAllowed = true}) { if (_pointer == event.pointer) { return; } + if (!_reset && + _pointer != event.pointer && + isPointerAllowed && + !_hasAcceptedOrRejected) { + rejectGesture(_pointer!); + _pointer = event.pointer; + return; + } _pointer = event.pointer; if (isPointerAllowed) { super.addPointer(event); } } -} - -class ImageHorizontalDragGestureRecognizer - extends CustomHorizontalDragGestureRecognizer - with ImageGestureRecognizerMixin { - ImageHorizontalDragGestureRecognizer({ - super.debugOwner, - super.supportedDevices, - super.allowedButtonsFilter, - }); - - static final double _touchSlop = PlatformUtils.isDesktop - ? kPrecisePointerHitSlop - : 3.0; @override - DeviceGestureSettings get gestureSettings => _gestureSettings; - final _gestureSettings = DeviceGestureSettings(touchSlop: _touchSlop); - - bool isAtLeftEdge = false; - bool isAtRightEdge = false; + void addAllowedPointer(PointerDownEvent event) { + _reset = false; + super.addAllowedPointer(event); + } void setAtBothEdges() { isAtLeftEdge = isAtRightEdge = true; } bool _isEdgeAllowed(double dx) { - if ((initialPosition!.dx - dx).abs() < _touchSlop) return true; if (isAtLeftEdge) { if (isAtRightEdge) { - return _hasAcceptedOrChecked = true; + return true; } - _hasAcceptedOrChecked = true; return initialPosition!.dx < dx; } else if (isAtRightEdge) { - _hasAcceptedOrChecked = true; return initialPosition!.dx > dx; } return true; } @override - void handleEvent(PointerEvent event) { - if (!_hasAcceptedOrChecked && - event is PointerMoveEvent && - _pointer == event.pointer) { - if (!_isEdgeAllowed(event.position.dx)) { - rejectGesture(event.pointer); - return; - } - } - super.handleEvent(event); + void acceptGesture(int pointer) { + _hasAcceptedOrRejected = true; + super.acceptGesture(pointer); } - bool _hasAcceptedOrChecked = false; - @override - void acceptGesture(int pointer) { - _hasAcceptedOrChecked = true; - super.acceptGesture(pointer); + void rejectGesture(int pointer) { + _hasAcceptedOrRejected = true; + super.rejectGesture(pointer); } @override void stopTrackingPointer(int pointer) { - _hasAcceptedOrChecked = false; + _reset = true; + _hasAcceptedOrRejected = false; + isAtLeftEdge = false; isAtRightEdge = false; super.stopTrackingPointer(pointer); } + + @override + bool hasSufficientGlobalDistanceToAccept( + PointerDeviceKind pointerDeviceKind, + double? deviceTouchSlop, + ) { + return super.hasSufficientGlobalDistanceToAccept( + pointerDeviceKind, + deviceTouchSlop, + ) && + _isEdgeAllowed(lastPosition.global.dx); + } } diff --git a/lib/common/widgets/gesture/immediate_tap_gesture_recognizer.dart b/lib/common/widgets/gesture/immediate_tap_gesture_recognizer.dart index 1ddf8f128b..44936dcd9e 100644 --- a/lib/common/widgets/gesture/immediate_tap_gesture_recognizer.dart +++ b/lib/common/widgets/gesture/immediate_tap_gesture_recognizer.dart @@ -150,6 +150,14 @@ class ImmediateTapGestureRecognizer extends OneSequenceGestureRecognizer { _initialPosition = null; } + @override + void resolve(GestureDisposition disposition) { + if (_wonArena && disposition == GestureDisposition.rejected) { + _cancelGesture('spontaneous'); + } + super.resolve(disposition); + } + @override String get debugDescription => 'immediate tap'; diff --git a/lib/common/widgets/gesture/mouse_interactive_viewer.dart b/lib/common/widgets/gesture/mouse_interactive_viewer.dart index 38b98c90b4..07ae957522 100644 --- a/lib/common/widgets/gesture/mouse_interactive_viewer.dart +++ b/lib/common/widgets/gesture/mouse_interactive_viewer.dart @@ -16,29 +16,30 @@ import 'package:vector_math/vector_math_64.dart' show Quad, Vector3; class MouseInteractiveViewer extends StatefulWidget { const MouseInteractiveViewer({ super.key, - this.clipBehavior = Clip.hardEdge, - this.panAxis = PanAxis.free, - this.boundaryMargin = EdgeInsets.zero, + this.clipBehavior = .hardEdge, + this.panAxis = .free, + this.boundaryMargin = .zero, this.constrained = true, this.maxScale = 2.5, this.minScale = 0.8, this.interactionEndFrictionCoefficient = _kDrag, - this.pointerSignalFallback, + required this.pointerSignalFallback, this.onPointerPanZoomUpdate, this.onPointerPanZoomEnd, - this.onPointerDown, - this.onInteractionEnd, - this.onInteractionStart, - this.onInteractionUpdate, + required this.onPointerDown, + required this.onPanEnd, + required this.onPanStart, + required this.onPanUpdate, + required this.onScaleUpdate, this.panEnabled = true, this.scaleEnabled = true, this.scaleFactor = kDefaultMouseScrollToScaleFactor, - this.transformationController, + required this.transformationController, this.alignment, this.trackpadScrollCausesScale = false, required this.childKey, required this.child, - required this.onTranslate, + required this.scaleGestureRecognizer, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), assert(maxScale > 0), @@ -57,16 +58,17 @@ class MouseInteractiveViewer extends StatefulWidget { final double maxScale; final double minScale; final double interactionEndFrictionCoefficient; - final PointerSignalEventListener? pointerSignalFallback; + final PointerSignalEventListener pointerSignalFallback; final PointerPanZoomUpdateEventListener? onPointerPanZoomUpdate; final PointerPanZoomEndEventListener? onPointerPanZoomEnd; - final PointerDownEventListener? onPointerDown; - final GestureScaleEndCallback? onInteractionEnd; - final GestureScaleStartCallback? onInteractionStart; - final GestureScaleUpdateCallback? onInteractionUpdate; - final TransformationController? transformationController; + final PointerDownEventListener onPointerDown; + final GestureScaleEndCallback onPanEnd; + final GestureScaleStartCallback onPanStart; + final GestureScaleUpdateCallback onPanUpdate; + final ValueChanged onScaleUpdate; + final TransformationController transformationController; final GlobalKey childKey; - final VoidCallback onTranslate; + final ScaleGestureRecognizer scaleGestureRecognizer; static const double _kDrag = 0.0000135; @@ -76,8 +78,7 @@ class MouseInteractiveViewer extends StatefulWidget { class _MouseInteractiveViewerState extends State with TickerProviderStateMixin { - late TransformationController _transformer = - widget.transformationController ?? TransformationController(); + late TransformationController _transformer; final GlobalKey _parentKey = GlobalKey(); Animation? _animation; @@ -94,7 +95,9 @@ class _MouseInteractiveViewerState extends State static final gestureSettings = DeviceGestureSettings( // 鸿蒙平台使用更小的 touchSlop 以便在与 NestedScrollView 的手势竞争中更快识别 - touchSlop: Platform.isIOS ? 9 : (Platform.operatingSystem == 'ohos' ? 1 : 4), + touchSlop: Platform.isIOS + ? 9 + : (Platform.operatingSystem == 'ohos' ? 1 : 4), ); late final ScaleGestureRecognizer _scaleGestureRecognizer; @@ -235,6 +238,9 @@ class _MouseInteractiveViewerState extends State widget.minScale, widget.maxScale, ); + + widget.onScaleUpdate(clampedTotalScale); + final double clampedScale = clampedTotalScale / currentScale; return matrix.clone() ..scaleByDouble(clampedScale, clampedScale, clampedScale, 1); @@ -271,10 +277,15 @@ class _MouseInteractiveViewerState extends State } } + bool _isSinglePointer = false; + // Handle the start of a gesture. All of pan, scale, and rotate are handled // with GestureDetector's scale gesture. void _onScaleStart(ScaleStartDetails details) { - widget.onInteractionStart?.call(details); + if (_isSinglePointer = details.pointerCount == 1) { + widget.onPanStart(details); + return; + } if (_controller.isAnimating) { _controller @@ -301,6 +312,11 @@ class _MouseInteractiveViewerState extends State // Handle an update to an ongoing gesture. All of pan, scale, and rotate are // handled with GestureDetector's scale gesture. void _onScaleUpdate(ScaleUpdateDetails details) { + if (_isSinglePointer) { + widget.onPanUpdate(details); + return; + } + final double scale = _transformer.value.getMaxScaleOnAxis(); _scaleAnimationFocalPoint = details.localFocalPoint; final Offset focalPointScene = _transformer.toScene( @@ -317,7 +333,6 @@ class _MouseInteractiveViewerState extends State _gestureType ??= _getGestureType(details); } if (!_gestureIsSupported(_gestureType)) { - widget.onInteractionUpdate?.call(details); return; } @@ -357,7 +372,6 @@ class _MouseInteractiveViewerState extends State case _GestureType.rotate: if (details.rotation == 0.0) { - widget.onInteractionUpdate?.call(details); return; } final double desiredRotation = _rotationStart! + details.rotation; @@ -374,7 +388,6 @@ class _MouseInteractiveViewerState extends State // In an effort to keep the behavior similar whether or not scaleEnabled // is true, these gestures are thrown away. if (details.scale != 1.0) { - widget.onInteractionUpdate?.call(details); return; } _currentAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene); @@ -388,13 +401,16 @@ class _MouseInteractiveViewerState extends State ); _referenceFocalPoint = _transformer.toScene(details.localFocalPoint); } - widget.onInteractionUpdate?.call(details); } // Handle the end of a gesture of _GestureType. All of pan, scale, and rotate // are handled with GestureDetector's scale gesture. void _onScaleEnd(ScaleEndDetails details) { - widget.onInteractionEnd?.call(details); + if (_isSinglePointer) { + widget.onPanEnd(details); + return; + } + _scaleStart = null; _rotationStart = null; _referenceFocalPoint = null; @@ -482,10 +498,6 @@ class _MouseInteractiveViewerState extends State final double scaleChange; if (event is PointerScrollEvent) { if (event.kind == PointerDeviceKind.trackpad) { - widget.onInteractionStart?.call( - ScaleStartDetails(focalPoint: global, localFocalPoint: local), - ); - final Offset localDelta = PointerEvent.transformDeltaViaPositions( untransformedEndPosition: global + event.scrollDelta, untransformedDelta: event.scrollDelta, @@ -502,14 +514,6 @@ class _MouseInteractiveViewerState extends State newFocalPointScene - focalPointScene, ); - widget.onInteractionUpdate?.call( - ScaleUpdateDetails( - focalPoint: global - event.scrollDelta, - localFocalPoint: local - localDelta, - focalPointDelta: -localDelta, - ), - ); - widget.onInteractionEnd?.call(ScaleEndDetails()); return; } _handlePointerScrollEvent(event); @@ -519,19 +523,8 @@ class _MouseInteractiveViewerState extends State } else { return; } - widget.onInteractionStart?.call( - ScaleStartDetails(focalPoint: global, localFocalPoint: local), - ); if (!_gestureIsSupported(_GestureType.scale)) { - widget.onInteractionUpdate?.call( - ScaleUpdateDetails( - focalPoint: global, - localFocalPoint: local, - scale: scaleChange, - ), - ); - widget.onInteractionEnd?.call(ScaleEndDetails()); return; } @@ -545,22 +538,12 @@ class _MouseInteractiveViewerState extends State _transformer.value, focalPointSceneScaled - focalPointScene, ); - - widget.onInteractionUpdate?.call( - ScaleUpdateDetails( - focalPoint: global, - localFocalPoint: local, - scale: scaleChange, - ), - ); - widget.onInteractionEnd?.call(ScaleEndDetails()); } void _handlePointerScrollEvent(PointerScrollEvent event) { - final Offset local = event.localPosition; - final Offset global = event.position; - if (_gestureIsSupported(_GestureType.scale)) { + final Offset local = event.localPosition; + final Offset global = event.position; if (HardwareKeyboard.instance.isControlPressed) { _handleMouseWheelScale(event, local, global); return; @@ -570,16 +553,8 @@ class _MouseInteractiveViewerState extends State _handleMouseWheelPanAsScale(event, local, global, shift); return; } - widget.pointerSignalFallback?.call(event); + widget.pointerSignalFallback(event); } - widget.onInteractionUpdate?.call( - ScaleUpdateDetails( - focalPoint: global, - localFocalPoint: local, - scale: math.exp(-event.scrollDelta.dy / widget.scaleFactor), - ), - ); - widget.onInteractionEnd?.call(ScaleEndDetails()); } void _handleMouseWheelScale( @@ -598,15 +573,6 @@ class _MouseInteractiveViewerState extends State _transformer.value, focalPointSceneScaled - focalPointScene, ); - - widget.onInteractionUpdate?.call( - ScaleUpdateDetails( - focalPoint: global, - localFocalPoint: local, - scale: scaleChange, - ), - ); - widget.onInteractionEnd?.call(ScaleEndDetails()); } void _handleMouseWheelPanAsScale( @@ -626,8 +592,6 @@ class _MouseInteractiveViewerState extends State _transformer.value, newFocalPointScene - focalPointScene, ); - - widget.onTranslate(); } void _handleInertiaAnimation() { @@ -676,29 +640,18 @@ class _MouseInteractiveViewerState extends State setState(() {}); } - void _onPointerDown(PointerDownEvent event) { - widget.onPointerDown?.call(event); - _scaleGestureRecognizer.addPointer(event); - } - @override void initState() { super.initState(); - _scaleGestureRecognizer = - ScaleGestureRecognizer( - debugOwner: this, - dragStartBehavior: .start, - allowedButtonsFilter: (buttons) => buttons == kPrimaryButton, - trackpadScrollToScaleFactor: Offset(0, -1 / widget.scaleFactor), - trackpadScrollCausesScale: widget.trackpadScrollCausesScale, - ) - ..gestureSettings = gestureSettings - ..onStart = _onScaleStart - ..onUpdate = _onScaleUpdate - ..onEnd = _onScaleEnd; + _scaleGestureRecognizer = widget.scaleGestureRecognizer + ..gestureSettings = gestureSettings + ..onStart = _onScaleStart + ..onUpdate = _onScaleUpdate + ..onEnd = _onScaleEnd; _controller = AnimationController(vsync: this); _scaleController = AnimationController(vsync: this); + _transformer = widget.transformationController; _transformer.addListener(_handleTransformation); } @@ -706,28 +659,20 @@ class _MouseInteractiveViewerState extends State void didUpdateWidget(MouseInteractiveViewer oldWidget) { super.didUpdateWidget(oldWidget); - final TransformationController? newController = - widget.transformationController; + final newController = widget.transformationController; if (newController == oldWidget.transformationController) { return; } _transformer.removeListener(_handleTransformation); - if (oldWidget.transformationController == null) { - _transformer.dispose(); - } - _transformer = newController ?? TransformationController(); + _transformer = newController; _transformer.addListener(_handleTransformation); } @override void dispose() { - _scaleGestureRecognizer.dispose(); _controller.dispose(); _scaleController.dispose(); _transformer.removeListener(_handleTransformation); - if (widget.transformationController == null) { - _transformer.dispose(); - } super.dispose(); } @@ -739,7 +684,7 @@ class _MouseInteractiveViewerState extends State key: _parentKey, behavior: HitTestBehavior.opaque, onPointerSignal: _receivedPointerSignal, - onPointerDown: _onPointerDown, + onPointerDown: widget.onPointerDown, onPointerPanZoomStart: _scaleGestureRecognizer.addPointerPanZoom, onPointerPanZoomUpdate: widget.onPointerPanZoomUpdate, onPointerPanZoomEnd: widget.onPointerPanZoomEnd, diff --git a/lib/common/widgets/gesture/player_gesture_recognizer.dart b/lib/common/widgets/gesture/player_gesture_recognizer.dart new file mode 100644 index 0000000000..3aa14a8ec9 --- /dev/null +++ b/lib/common/widgets/gesture/player_gesture_recognizer.dart @@ -0,0 +1,28 @@ +import 'package:flutter/gestures.dart' + show ScaleGestureRecognizer, RecognizerCallback, GestureRecognizer; + +mixin PlayerGestureMixin on GestureRecognizer { + bool isPosAllowed = true; + + @override + T? invokeCallback( + String name, + RecognizerCallback callback, { + String Function()? debugReport, + }) { + if (!isPosAllowed) return null; + return super.invokeCallback(name, callback, debugReport: debugReport); + } +} + +class PlayerScaleGestureRecognizer extends ScaleGestureRecognizer + with PlayerGestureMixin { + PlayerScaleGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + super.dragStartBehavior, + super.trackpadScrollCausesScale, + super.trackpadScrollToScaleFactor, + }); +} diff --git a/lib/common/widgets/image/cached_network_svg_image.dart b/lib/common/widgets/image/cached_network_svg_image.dart index 8d2bf2e924..06555baf90 100644 --- a/lib/common/widgets/image/cached_network_svg_image.dart +++ b/lib/common/widgets/image/cached_network_svg_image.dart @@ -2,8 +2,8 @@ import 'dart:developer'; +import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/cache_manager_ext.dart'; -import 'package:cached_network_image_ce/cached_network_image.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -11,11 +11,11 @@ import 'package:flutter_svg/flutter_svg.dart'; class CachedNetworkSVGImage extends StatefulWidget { CachedNetworkSVGImage( - String url, { + this._url, { Key? key, String? cacheKey, Widget? placeholder, - Widget? errorWidget, + WidgetBuilder? errorBuilder, double? width, double? height, Map? headers, @@ -28,11 +28,9 @@ class CachedNetworkSVGImage extends StatefulWidget { SvgTheme theme = const SvgTheme(), ColorFilter? colorFilter, WidgetBuilder? placeholderBuilder, - BaseCacheManager? cacheManager, - }) : _url = url, - _cacheKey = cacheKey, + }) : _cacheKey = cacheKey, _placeholder = placeholder, - _errorWidget = errorWidget, + _errorBuilder = errorBuilder, _width = width, _height = height, _headers = headers, @@ -45,13 +43,12 @@ class CachedNetworkSVGImage extends StatefulWidget { _theme = theme, _colorFilter = colorFilter, _placeholderBuilder = placeholderBuilder, - _cacheManager = cacheManager ?? DefaultCacheManager(), - super(key: key ?? ValueKey(cacheKey ?? url)); + super(key: key ?? ValueKey(cacheKey ?? _url)); final String _url; final String? _cacheKey; final Widget? _placeholder; - final Widget? _errorWidget; + final WidgetBuilder? _errorBuilder; final double? _width; final double? _height; final Map? _headers; @@ -64,7 +61,6 @@ class CachedNetworkSVGImage extends StatefulWidget { final SvgTheme _theme; final ColorFilter? _colorFilter; final WidgetBuilder? _placeholderBuilder; - final BaseCacheManager _cacheManager; @override State createState() => _CachedNetworkSVGImageState(); @@ -80,9 +76,7 @@ class _CachedNetworkSVGImageState extends State { double? height; late TextScaler textScaler; - static final _sizeRegExp = RegExp( - r'height="([\d\.]+)([c-x]{2})?"', - ); + static final _sizeRegExp = RegExp(r'height="([\d\.]+)([c-x]{2})?"'); @override void initState() { @@ -101,7 +95,7 @@ class _CachedNetworkSVGImageState extends State { Future _loadImage() async { try { - final file = await widget._cacheManager.getSingleFile( + final file = await CacheManager.manager.getSingleFile( widget._url, key: _cacheKey, headers: widget._headers ?? const {}, @@ -173,7 +167,8 @@ class _CachedNetworkSVGImageState extends State { Widget _buildPlaceholderWidget() => Center(child: widget._placeholder); - Widget _buildErrorWidget() => Center(child: widget._errorWidget); + Widget _buildErrorWidget() => + Center(child: widget._errorBuilder?.call(context)); Widget? _buildSVGImage() { if (_svgString == null) { diff --git a/lib/common/widgets/image_grid/image_grid_builder.dart b/lib/common/widgets/image_grid/image_grid_builder.dart index ddd65f9fe7..2b5aea68ef 100644 --- a/lib/common/widgets/image_grid/image_grid_builder.dart +++ b/lib/common/widgets/image_grid/image_grid_builder.dart @@ -37,7 +37,9 @@ import 'package:flutter/rendering.dart' BoxHitTestEntry, ContainerParentDataMixin, InformationCollector, - DiagnosticsDebugCreator; + DiagnosticsDebugCreator, + RenderObjectVisitor, + SemanticsConfiguration; /// ref [LayoutBuilder] @@ -251,6 +253,23 @@ class RenderImageGrid extends RenderBox super.dispose(); } + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + RenderBox? child = firstChild; + while (child != null) { + visitor(child); + child = (child.parentData as MultiChildLayoutParentData).nextSibling; + } + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config + ..explicitChildNodes = true + ..isSemanticBoundary = true; + } + @override bool get isRepaintBoundary => true; // gif repaint } @@ -497,7 +516,7 @@ class ImageGridRenderObjectElement extends RenderObjectElement { final height = img.height; final ratioWH = width / height; final ratioHW = height / width; - imageWidth = ratioWH > 1.5 + imageWidth = ratioWH > 1.45 ? maxWidth : (ratioWH >= 1 || (height > width && ratioHW < 1.5)) ? 2 * imageWidth diff --git a/lib/common/widgets/image_grid/image_grid_view.dart b/lib/common/widgets/image_grid/image_grid_view.dart index b3a49f072f..de226dc99c 100644 --- a/lib/common/widgets/image_grid/image_grid_view.dart +++ b/lib/common/widgets/image_grid/image_grid_view.dart @@ -22,7 +22,6 @@ import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_grid/image_grid_builder.dart'; -import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; @@ -54,7 +53,8 @@ class ImageModel { bool? _isLongPic; bool? _isLivePhoto; - bool get isLongPic => _isLongPic ??= (height / width) > Style.imgMaxRatio; + bool get isLongPic => + _isLongPic ??= (height / width) > Style.imgMaxRatio && width > 100; bool get isLivePhoto => _isLivePhoto ??= enableLivePhoto && liveUrl?.isNotEmpty == true; @@ -208,9 +208,9 @@ class ImageGridView extends StatelessWidget { width: width, height: height, decoration: BoxDecoration( - color: Theme.of( + color: ColorScheme.of( context, - ).colorScheme.onInverseSurface.withValues(alpha: 0.4), + ).onInverseSurface.withValues(alpha: 0.4), ), child: Image.asset( Assets.loading, @@ -220,6 +220,7 @@ class ImageGridView extends StatelessWidget { ), ); return List.generate(picArr.length, (index) { + void onTap() => _onTap(context, index); final item = picArr[index]; final borderRadius = _borderRadius( info.column, @@ -240,30 +241,21 @@ class ImageGridView extends StatelessWidget { getPlaceHolder: () => placeHolder, ), if (item.isLivePhoto) - const PBadge( - text: 'Live', - right: 8, - bottom: 8, - type: PBadgeType.gray, - ) + const PBadge(text: 'Live', right: 8, bottom: 8, type: .gray) else if (item.isLongPic) - const PBadge( - text: '长图', - right: 8, - bottom: 8, - ), + const PBadge(text: '长图', right: 8, bottom: 8), ], ); if (!item.isLongPic) { - child = Hero( - tag: '${item.url}$hashCode', - child: child, - ); + child = Hero(tag: '${item.url}$hashCode', child: child); } - return LayoutId( - id: index, + child = Semantics( + label: '图片,第 ${index + 1} 张,共 ${picArr.length} 张', + button: true, + onTap: onTap, child: child, ); + return LayoutId(id: index, child: child); }); }, ), diff --git a/lib/common/widgets/image_viewer/gallery_viewer.dart b/lib/common/widgets/image_viewer/gallery_viewer.dart index 1117cc8d08..2c42522c3a 100644 --- a/lib/common/widgets/image_viewer/gallery_viewer.dart +++ b/lib/common/widgets/image_viewer/gallery_viewer.dart @@ -18,17 +18,21 @@ import 'dart:io' show File, Platform; import 'package:PiliPlus/common/widgets/colored_box_transition.dart'; -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; +import 'package:PiliPlus/common/widgets/dialog/simple_dialog_option.dart'; import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart'; import 'package:PiliPlus/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/image.dart'; import 'package:PiliPlus/common/widgets/image_viewer/loading_indicator.dart'; import 'package:PiliPlus/common/widgets/image_viewer/viewer.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/main.dart' show tmpPadding; import 'package:PiliPlus/models/common/image_preview_type.dart'; +import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart'; +import 'package:PiliPlus/utils/device_utils.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/string_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; +import 'package:PiliPlus/utils/max_screen_size.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; @@ -36,7 +40,7 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image_ce/cached_network_image.dart'; import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart' hide Image, PageView, LayoutBuilder; +import 'package:flutter/material.dart' hide Image, PageView; import 'package:flutter/services.dart' show HapticFeedback; import 'package:get/get.dart'; import 'package:media_kit/media_kit.dart'; @@ -76,6 +80,7 @@ class _GalleryViewerState extends State late final int _quality; late final RxInt _currIndex; GlobalKey? _key; + EdgeInsets? _padding; late bool _hasInit = false; Player? _player; @@ -170,6 +175,44 @@ class _GalleryViewerState extends State ); } + late final bool _hideSystemBar; + + void _initHideSystemBar() { + if (Platform.isAndroid) { + if (showSystemBar_) { + final size = DeviceUtils.size; + _hideSystemBar = !MaxScreenSize.isWindowMode( + width: size.width, + height: size.height, + ); + } else { + _hideSystemBar = false; + } + } else if (Platform.isIOS) { + _hideSystemBar = showSystemBar_; + } else { + _hideSystemBar = false; + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_padding == null) { + final padding = MediaQuery.viewPaddingOf(context); + _padding = padding; + _initHideSystemBar(); + if (_hideSystemBar) { + tmpPadding = padding; + hideSystemBar()!.whenComplete( + () => WidgetsBinding.instance.addPostFrameCallback( + (_) => tmpPadding = null, + ), + ); + } + } + } + Matrix4 _onTransform(double val) { final scale = val.lerp(1.0, 0.25); @@ -259,6 +302,9 @@ class _GalleryViewerState extends State } Future.delayed(const Duration(milliseconds: 200), _currIndex.close); super.dispose(); + if (_hideSystemBar) { + showSystemBar(); + } } void _onPointerDown(PointerDownEvent event) { @@ -311,9 +357,7 @@ class _GalleryViewerState extends State right: 0, child: IgnorePointer( child: Container( - padding: - MediaQuery.viewPaddingOf(context) + - const EdgeInsets.fromLTRB(12, 8, 20, 8), + padding: _padding! + const EdgeInsets.fromLTRB(12, 8, 20, 8), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, @@ -491,76 +535,67 @@ class _GalleryViewerState extends State HapticFeedback.mediumImpact(); showDialog( context: context, - builder: (context) => AlertDialog( + builder: (context) => SimpleDialog( clipBehavior: Clip.hardEdge, contentPadding: const EdgeInsets.symmetric(vertical: 12), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (PlatformUtils.isMobile) - ListTile( - onTap: () { - Get.back(); - ImageUtils.onShareImg(item.url); - }, - dense: true, - title: const Text('分享', style: TextStyle(fontSize: 14)), - ), - ListTile( - onTap: () { + children: [ + if (PlatformUtils.isMobile) + DialogOption( + onPressed: () { Get.back(); - Utils.copyText(item.url); + ImageUtils.onShareImg(item.url); }, - dense: true, - title: const Text('复制链接', style: TextStyle(fontSize: 14)), + child: const Text('分享', style: TextStyle(fontSize: 14)), ), - ListTile( - onTap: () { + DialogOption( + onPressed: () { + Get.back(); + Utils.copyText(item.url); + }, + child: const Text('复制链接', style: TextStyle(fontSize: 14)), + ), + DialogOption( + onPressed: () { + Get.back(); + ImageUtils.downloadImg([item.url]); + }, + child: const Text('保存图片', style: TextStyle(fontSize: 14)), + ), + if (PlatformUtils.isDesktop) + DialogOption( + onPressed: () { + Get.back(); + PageUtils.launchURL(item.url); + }, + child: const Text('网页打开', style: TextStyle(fontSize: 14)), + ) + else if (widget.sources.length > 1) + DialogOption( + onPressed: () { Get.back(); - ImageUtils.downloadImg([item.url]); + ImageUtils.downloadImg( + widget.sources.map((item) => item.url).toList(), + ); }, - dense: true, - title: const Text('保存图片', style: TextStyle(fontSize: 14)), + child: const Text('保存全部图片', style: TextStyle(fontSize: 14)), ), - if (PlatformUtils.isDesktop) - ListTile( - onTap: () { - Get.back(); - PageUtils.launchURL(item.url); - }, - dense: true, - title: const Text('网页打开', style: TextStyle(fontSize: 14)), - ) - else if (widget.sources.length > 1) - ListTile( - onTap: () { - Get.back(); - ImageUtils.downloadImg( - widget.sources.map((item) => item.url).toList(), - ); - }, - dense: true, - title: const Text('保存全部图片', style: TextStyle(fontSize: 14)), - ), - if (item.sourceType == SourceType.livePhoto) - ListTile( - onTap: () { - Get.back(); - ImageUtils.downloadLivePhoto( - url: item.url, - liveUrl: item.liveUrl!, - width: item.width!, - height: item.height!, - ); - }, - dense: true, - title: Text( - '保存${Platform.isIOS ? ' Live Photo' : '视频'}', - style: const TextStyle(fontSize: 14), - ), + if (item.sourceType == SourceType.livePhoto) + DialogOption( + onPressed: () { + Get.back(); + ImageUtils.downloadLivePhoto( + url: item.url, + liveUrl: item.liveUrl!, + width: item.width!, + height: item.height!, + ); + }, + child: Text( + '保存${Platform.isIOS ? ' Live Photo' : '视频'}', + style: const TextStyle(fontSize: 14), ), - ], - ), + ), + ], ), ); } diff --git a/lib/common/widgets/image_viewer/image.dart b/lib/common/widgets/image_viewer/image.dart index 61a5e0fc2c..c0c981bf42 100644 --- a/lib/common/widgets/image_viewer/image.dart +++ b/lib/common/widgets/image_viewer/image.dart @@ -390,9 +390,7 @@ class _ImageState extends State with WidgetsBindingObserver { void didChangeDependencies() { _resolveImage(); - _isPaused = - !TickerMode.valuesOf(context).enabled || - (MediaQuery.maybeDisableAnimationsOf(context) ?? false); + _isPaused = !TickerMode.valuesOf(context).enabled; if (_isPaused && _frameNumber != null) { _stopListeningToStream(keepStreamAlive: true); diff --git a/lib/common/widgets/loading_widget/http_error.dart b/lib/common/widgets/loading_widget/http_error.dart index 7b2a7a7b67..8d7805dbb7 100644 --- a/lib/common/widgets/loading_widget/http_error.dart +++ b/lib/common/widgets/loading_widget/http_error.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/assets.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -26,10 +27,7 @@ class HttpError extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ const SizedBox(height: 40), - SvgPicture.asset( - "assets/images/error.svg", - height: 200, - ), + SvgPicture.asset(Assets.error, height: 200), const SizedBox(height: 30), Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 5), diff --git a/lib/common/widgets/marquee.dart b/lib/common/widgets/marquee.dart index 31d9b7002e..bdf334a832 100644 --- a/lib/common/widgets/marquee.dart +++ b/lib/common/widgets/marquee.dart @@ -6,6 +6,7 @@ import 'package:flutter/scheduler.dart'; class MarqueeText extends StatelessWidget { final String text; final TextStyle? style; + final StrutStyle? strutStyle; final double spacing; final double velocity; final ContextSingleTicker? provider; @@ -14,6 +15,7 @@ class MarqueeText extends StatelessWidget { this.text, { super.key, this.style, + this.strutStyle, this.spacing = 0, this.velocity = 25, this.provider, @@ -28,6 +30,7 @@ class MarqueeText extends StatelessWidget { child: Text( text, style: style, + strutStyle: strutStyle, maxLines: 1, textDirection: TextDirection.ltr, ), @@ -119,10 +122,10 @@ abstract class MarqueeRender extends RenderBox required double spacing, required this.clipBehavior, required ContextSingleTicker provider, - }) : _ticker = provider, - _spacing = spacing, + }) : _direction = direction, _velocity = velocity, - _direction = direction, + _ticker = provider, + _spacing = spacing, assert(spacing.isFinite && !spacing.isNaN); Clip clipBehavior; diff --git a/lib/common/widgets/pendant_avatar.dart b/lib/common/widgets/pendant_avatar.dart index f24def1cd1..db6bfef496 100644 --- a/lib/common/widgets/pendant_avatar.dart +++ b/lib/common/widgets/pendant_avatar.dart @@ -1,12 +1,13 @@ import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/common/widgets/extra_hittest_stack.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/avatar_badge_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; -import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; class PendantAvatar extends StatelessWidget { const PendantAvatar( @@ -60,7 +61,7 @@ class PendantAvatar extends StatelessWidget { top: -0.375 * size + pendentOffset / 2, child: IgnorePointer( child: NetworkImgLayer( - type: ImageType .emote, + type: ImageType.emote, width: pendantSize, height: pendantSize, src: pendantImage, @@ -82,7 +83,7 @@ class PendantAvatar extends StatelessWidget { child: avatar, ); } - Widget child = Stack( + Widget child = ExtraHitTestStack( clipBehavior: .none, alignment: .center, children: [ @@ -141,12 +142,11 @@ class PendantAvatar extends StatelessWidget { Widget _buildBadge(BuildContext context, ColorScheme colorScheme) { final child = switch (badgeType) { - .vip => Image.asset( + .vip => SvgPicture.asset( Assets.vipIcon, width: badgeSize, height: badgeSize, - cacheWidth: badgeSize.cacheSize(context), - semanticLabel: badgeType.desc, + semanticsLabel: badgeType.desc, ), _ => Icon( Icons.offline_bolt, diff --git a/lib/common/widgets/progress_bar/audio_video_progress_bar.dart b/lib/common/widgets/progress_bar/audio_video_progress_bar.dart index 665d8fd6ff..c542b2fa37 100644 --- a/lib/common/widgets/progress_bar/audio_video_progress_bar.dart +++ b/lib/common/widgets/progress_bar/audio_video_progress_bar.dart @@ -29,7 +29,7 @@ class ProgressBar extends LeafRenderObjectWidget { super.key, required this.progress, required this.total, - this.buffered = .zero, + this.buffered = 0, this.onSeek, this.onDragStart, this.onDragUpdate, @@ -48,16 +48,19 @@ class ProgressBar extends LeafRenderObjectWidget { /// The elapsed playing time of the media. /// /// This should not be greater than the [total] time. - final Duration progress; + /// seconds + final int progress; /// The total duration of the media. - final Duration total; + /// seconds + final int total; /// The currently buffered content of the media. /// /// This is useful for streamed content. If you are playing a local file /// then you can leave this out. - final Duration buffered; + /// seconds + final int buffered; /// A callback when user moves the thumb. /// @@ -70,7 +73,7 @@ class ProgressBar extends LeafRenderObjectWidget { /// If you want continuous duration updates as the user moves the thumb, /// see [onDragUpdate], where the provided [ThumbDragDetails] has a /// `timeStamp` with the seek duration on it. - final ValueChanged? onSeek; + final OnSeek? onSeek; /// A callback when the user starts to move the thumb. /// @@ -224,7 +227,7 @@ class ProgressBar extends LeafRenderObjectWidget { ..add(StringProperty('total', total.toString())) ..add(StringProperty('buffered', buffered.toString())) ..add( - ObjectFlagProperty>( + ObjectFlagProperty( 'onSeek', onSeek, ifNull: 'unimplemented', @@ -271,6 +274,8 @@ class ProgressBar extends LeafRenderObjectWidget { } } +typedef OnSeek = void Function(int milliseconds); + /// The callback signature for when the thumb begins a horizontal drag. typedef ThumbDragStartCallback = void Function(ThumbDragDetails details); @@ -281,13 +286,13 @@ typedef ThumbDragUpdateCallback = void Function(ThumbDragDetails details); /// Data to pass back on drag callback events class ThumbDragDetails { const ThumbDragDetails({ - this.timeStamp = Duration.zero, + this.seconds = 0, this.globalPosition = Offset.zero, this.localPosition = Offset.zero, }); /// The duration position of the thumb on the progress bar - final Duration timeStamp; + final int seconds; /// The global position of the drag event moving the thumb on the progress bar. final Offset globalPosition; @@ -298,7 +303,7 @@ class ThumbDragDetails { @override String toString() => '${objectRuntimeType(this, 'ThumbDragDetails')}(' - 'time: $timeStamp, ' + 'time: $seconds, ' 'global: $globalPosition, ' 'local: $localPosition)'; } @@ -320,10 +325,10 @@ class _EagerHorizontalDragGestureRecognizer class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { RenderProgressBar({ - required Duration progress, - required Duration total, - required Duration buffered, - ValueChanged? onSeek, + required int progress, + required int total, + required int buffered, + OnSeek? onSeek, ThumbDragStartCallback? onDragStart, ThumbDragUpdateCallback? onDragUpdate, VoidCallback? onDragEnd, @@ -336,22 +341,23 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { required Color thumbGlowColor, double thumbGlowRadius = 30.0, bool thumbCanPaintOutsideBar = true, - }) : _total = total, + }) : _progress = progress, + _total = total, _buffered = buffered, _onSeek = onSeek, - _onDragStartUserCallback = onDragStart, - _onDragUpdateUserCallback = onDragUpdate, - _onDragEndUserCallback = onDragEnd, _barHeight = barHeight, _baseBarColor = baseBarColor, _progressBarColor = progressBarColor, _bufferedBarColor = bufferedBarColor, - _thumbRadius = thumbRadius, _thumbColor = thumbColor, _thumbGlowColor = thumbGlowColor, + _thumbCanPaintOutsideBar = thumbCanPaintOutsideBar, + _onDragStartUserCallback = onDragStart, + _onDragUpdateUserCallback = onDragUpdate, + _onDragEndUserCallback = onDragEnd, + _thumbRadius = thumbRadius, _thumbGlowRadius = thumbGlowRadius, _paintThumbGlow = thumbGlowRadius > thumbRadius, - _thumbCanPaintOutsideBar = thumbCanPaintOutsideBar, _hitTestSelf = onDragStart != null { if (onDragStart != null) { _drag = _EagerHorizontalDragGestureRecognizer() @@ -361,7 +367,6 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { ..onCancel = _finishDrag; } if (!_userIsDraggingThumb) { - _progress = progress; _thumbValue = _proportionOfTotal(_progress); } } @@ -387,14 +392,11 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { bool _userIsDraggingThumb = false; void _onDragStart(DragStartDetails details) { - if (onDragStart == null) { - return; - } _userIsDraggingThumb = true; _updateThumbPosition(details.localPosition); onDragStart?.call( ThumbDragDetails( - timeStamp: _currentThumbDuration(), + seconds: _currentThumbDuration(), globalPosition: details.globalPosition, localPosition: details.localPosition, ), @@ -402,13 +404,10 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { } void _onDragUpdate(DragUpdateDetails details) { - if (onDragUpdate == null) { - return; - } _updateThumbPosition(details.localPosition); onDragUpdate?.call( ThumbDragDetails( - timeStamp: _currentThumbDuration(), + seconds: _currentThumbDuration(), globalPosition: details.globalPosition, localPosition: details.localPosition, ), @@ -416,11 +415,8 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { } void _onDragEnd(DragEndDetails details) { - if (onSeek == null) { - return; - } onDragEnd?.call(); - onSeek?.call(_currentThumbDuration()); + onSeek?.call(_currentThumbDurationInMilliseconds()); _finishDrag(); } @@ -429,9 +425,12 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - Duration _currentThumbDuration() { - final thumbMilliseconds = _thumbValue * total.inMilliseconds; - return Duration(milliseconds: thumbMilliseconds.round()); + int _currentThumbDuration() { + return (_thumbValue * total).round(); + } + + int _currentThumbDurationInMilliseconds() { + return (_thumbValue * total * 1000).round(); } // This needs to stay in sync with the layout. This could be a potential @@ -448,7 +447,7 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { double barEnd = size.width - barCapRadius; final barWidth = barEnd - barStart; final position = (dx - barStart).clamp(0.0, barWidth); - _thumbValue = (position / barWidth); + _thumbValue = position / barWidth; _progress = _currentThumbDuration(); markNeedsPaint(); } @@ -456,9 +455,9 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { /// The play location of the media. /// /// This is used to update the thumb value and the left time label. - Duration get progress => _progress; - Duration _progress = Duration.zero; - set progress(Duration value) { + int get progress => _progress; + int _progress; + set progress(int value) { final clamp = _clampDuration(value); if (_progress == clamp) { return; @@ -471,10 +470,10 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { } /// The total time length of the media. - Duration get total => _total; - Duration _total; - set total(Duration value) { - final clamp = (value.isNegative) ? Duration.zero : value; + int get total => _total; + int _total; + set total(int value) { + final clamp = (value.isNegative) ? 0 : value; if (_total == clamp) { return; } @@ -486,9 +485,9 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { } /// The buffered length of the media when streaming. - Duration get buffered => _buffered; - Duration _buffered; - set buffered(Duration value) { + int get buffered => _buffered; + int _buffered; + set buffered(int value) { final clamp = _clampDuration(value); if (_buffered == clamp) { return; @@ -497,16 +496,16 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { markNeedsPaint(); } - Duration _clampDuration(Duration value) { - if (value.isNegative) return Duration.zero; + int _clampDuration(int value) { + if (value.isNegative) return 0; if (value.compareTo(_total) > 0) return _total; return value; } /// A callback for the audio duration position to where the thumb was moved. - ValueChanged? get onSeek => _onSeek; - ValueChanged? _onSeek; - set onSeek(ValueChanged? value) { + OnSeek? get onSeek => _onSeek; + OnSeek? _onSeek; + set onSeek(OnSeek? value) { if (value == _onSeek) { return; } @@ -726,7 +725,7 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { _drawBar( canvas: canvas, availableSize: localSize, - widthProportion: _proportionOfTotal(_progress), + widthProportion: _thumbValue, color: progressBarColor, ); } @@ -744,8 +743,9 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { final capRadius = _barHeight / 2; final adjustedWidth = availableSize.width - barHeight; final dx = widthProportion * adjustedWidth + capRadius; - final startPoint = Offset(capRadius, availableSize.height / 2); - final endPoint = Offset(dx, availableSize.height / 2); + final dy = availableSize.height / 2; + final startPoint = Offset(capRadius, dy); + final endPoint = Offset(dx, dy); canvas.drawLine(startPoint, endPoint, baseBarPaint); } @@ -765,11 +765,11 @@ class RenderProgressBar extends RenderBox implements MouseTrackerAnnotation { canvas.drawCircle(center, thumbRadius, thumbPaint); } - double _proportionOfTotal(Duration duration) { - if (total.inMilliseconds == 0) { + double _proportionOfTotal(int duration) { + if (total == 0) { return 0.0; } - return (duration.inMilliseconds / total.inMilliseconds).clamp(0.0, 1.0); + return (duration / total).clamp(0.0, 1.0); } @override diff --git a/lib/common/widgets/progress_bar/segment_progress_bar.dart b/lib/common/widgets/progress_bar/segment_progress_bar.dart index 123040714c..26d8691ece 100644 --- a/lib/common/widgets/progress_bar/segment_progress_bar.dart +++ b/lib/common/widgets/progress_bar/segment_progress_bar.dart @@ -207,13 +207,7 @@ class RenderViewPointProgressBar ), ), ) - ..pushStyle( - ui.TextStyle( - color: Colors.white, - fontSize: size, - height: 1, - ), - ) + ..pushStyle(.new(color: Colors.white, fontSize: size, height: 1)) ..addText(title); return builder.build() ..layout(const ui.ParagraphConstraints(width: double.infinity)); diff --git a/lib/common/widgets/progress_bar/video_progress_indicator.dart b/lib/common/widgets/progress_bar/video_progress_indicator.dart index 6bd3d49959..0057eb41ae 100644 --- a/lib/common/widgets/progress_bar/video_progress_indicator.dart +++ b/lib/common/widgets/progress_bar/video_progress_indicator.dart @@ -25,7 +25,7 @@ class VideoProgressIndicator extends LeafRenderObjectWidget { this.radius = 10, this.height = 4, required this.progress, - }) : assert(progress >= 0 && progress <= 1); + }); final Color color; final Color backgroundColor; @@ -136,9 +136,9 @@ class RenderProgressBar extends RenderBox { bottomRight: radius, ); - if (progress == 0) { + if (progress <= 0) { canvas.drawRRect(rrect, paint..color = _backgroundColor); - } else if (progress == 1) { + } else if (progress >= 1) { canvas.drawRRect(rrect, paint..color = _color); } else { final w = size.width * progress; diff --git a/lib/common/widgets/reorder_mixin.dart b/lib/common/widgets/reorder_mixin.dart new file mode 100644 index 0000000000..6adf29a2cf --- /dev/null +++ b/lib/common/widgets/reorder_mixin.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; + +mixin ReorderMixin on State { + late ColorScheme scheme; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + scheme = ColorScheme.of(context); + } + + Widget proxyDecorator(Widget child, _, _) { + return ColoredBox( + color: scheme.onInverseSurface, + child: child, + ); + } +} diff --git a/lib/common/widgets/select_mask.dart b/lib/common/widgets/select_mask.dart index 5c964bc870..b6ce4e926c 100644 --- a/lib/common/widgets/select_mask.dart +++ b/lib/common/widgets/select_mask.dart @@ -2,7 +2,7 @@ import 'package:PiliPlus/common/style.dart'; import 'package:flutter/material.dart'; Widget selectMask( - ThemeData theme, + ColorScheme colorScheme, bool checked, { BorderRadiusGeometry borderRadius = Style.mdRadius, }) { @@ -23,12 +23,12 @@ Widget selectMask( width: 34, height: 34, decoration: BoxDecoration( - color: theme.colorScheme.surface.withValues(alpha: 0.8), + color: colorScheme.surface.withValues(alpha: 0.8), shape: BoxShape.circle, ), child: Icon( Icons.done_all_outlined, - color: theme.colorScheme.primary, + color: colorScheme.primary, semanticLabel: '取消选择', ), ), diff --git a/lib/common/widgets/flutter/selectable_text/text.dart b/lib/common/widgets/selectable_text.dart similarity index 74% rename from lib/common/widgets/flutter/selectable_text/text.dart rename to lib/common/widgets/selectable_text.dart index 24be9f726f..235abda4f3 100644 --- a/lib/common/widgets/flutter/selectable_text/text.dart +++ b/lib/common/widgets/selectable_text.dart @@ -1,7 +1,5 @@ -import 'package:PiliPlus/common/widgets/flutter/selectable_text/selectable_text.dart'; -import 'package:PiliPlus/common/widgets/flutter/selectable_text/selection_area.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart' hide SelectableText, SelectionArea; +import 'package:flutter/material.dart'; Widget selectableText( String text, { diff --git a/lib/common/widgets/self_sized_horizontal_list.dart b/lib/common/widgets/self_sized_horizontal_list.dart index dea354ea8d..777267adb8 100644 --- a/lib/common/widgets/self_sized_horizontal_list.dart +++ b/lib/common/widgets/self_sized_horizontal_list.dart @@ -28,6 +28,9 @@ class _SelfSizedHorizontalListState extends State { @override Widget build(BuildContext context) { if (_height == null) { + if (widget.itemCount == 0) { + return const SizedBox.shrink(); + } return OnlyLayoutWidget( onPerformLayout: (Size size) { if (!mounted) return; diff --git a/lib/common/widgets/sliver/sliver_floating_header.dart b/lib/common/widgets/sliver/sliver_floating_header.dart index 56835cac5c..5b87b9e8e2 100644 --- a/lib/common/widgets/sliver/sliver_floating_header.dart +++ b/lib/common/widgets/sliver/sliver_floating_header.dart @@ -20,13 +20,78 @@ import 'dart:math' as math; import 'package:flutter/foundation.dart' show clampDouble; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart' - show RenderSliverSingleBoxAdapter, SliverGeometry; + show RenderSliverSingleBoxAdapter, SliverGeometry, ScrollDirection; /// ref [SliverFloatingHeader] -class SliverFloatingHeaderWidget extends SingleChildRenderObjectWidget { +class SliverFloatingHeaderWidget extends StatelessWidget { const SliverFloatingHeaderWidget({ super.key, + required this.child, + required this.backgroundColor, + }); + + final Widget child; + final Color backgroundColor; + + @override + Widget build(BuildContext context) { + return _SliverFloatingHeaderWidget( + backgroundColor: backgroundColor, + child: _SliverFloatingHeaderScroll(child: child), + ); + } +} + +class _SliverFloatingHeaderScroll extends StatefulWidget { + const _SliverFloatingHeaderScroll({required this.child}); + + final Widget child; + + @override + State<_SliverFloatingHeaderScroll> createState() => + _SliverFloatingHeaderScrollState(); +} + +class _SliverFloatingHeaderScrollState + extends State<_SliverFloatingHeaderScroll> { + ScrollPosition? _position; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (_position != null) { + _position!.isScrollingNotifier.removeListener(_isScrollingListener); + } + _position = Scrollable.maybeOf(context)?.position; + if (_position != null) { + _position!.isScrollingNotifier.addListener(_isScrollingListener); + } + } + + @override + void dispose() { + if (_position != null) { + _position!.isScrollingNotifier.removeListener(_isScrollingListener); + } + super.dispose(); + } + + void _isScrollingListener() { + assert(_position != null); + if (_position!.isScrollingNotifier.value) { + final RenderSliverFloatingHeader? renderer = context + .findAncestorRenderObjectOfType(); + renderer?.updateScrollStartDirection(_position!.userScrollDirection); + } + } + + @override + Widget build(BuildContext context) => widget.child; +} + +class _SliverFloatingHeaderWidget extends SingleChildRenderObjectWidget { + const _SliverFloatingHeaderWidget({ required Widget super.child, required this.backgroundColor, }); @@ -70,6 +135,12 @@ class RenderSliverFloatingHeader extends RenderSliverSingleBoxAdapter { effectiveScrollOffset < child!.size.height); } + ScrollDirection? _lastStartedScrollDirection; + + void updateScrollStartDirection(ScrollDirection direction) { + _lastStartedScrollDirection = direction; + } + @override void performLayout() { if (!floatingHeaderNeedsToBeUpdated) { @@ -78,7 +149,8 @@ class RenderSliverFloatingHeader extends RenderSliverSingleBoxAdapter { double delta = lastScrollOffset! - constraints.scrollOffset; // > 0 when the header is growing - if (constraints.userScrollDirection == .forward) { + if (constraints.userScrollDirection == .forward || + _lastStartedScrollDirection == .forward) { final childExtent = child!.size.height; if (effectiveScrollOffset > childExtent) { effectiveScrollOffset = diff --git a/lib/common/widgets/sliver/sliver_to_box_adapter.dart b/lib/common/widgets/sliver/sliver_to_box_adapter.dart new file mode 100644 index 0000000000..14b5b5b162 --- /dev/null +++ b/lib/common/widgets/sliver/sliver_to_box_adapter.dart @@ -0,0 +1,85 @@ +import 'package:flutter/rendering.dart' show RenderSliverToBoxAdapter; +import 'package:flutter/widgets.dart'; + +class SliverToBoxWithOffsetAdapter extends SliverToBoxAdapter { + const SliverToBoxWithOffsetAdapter({ + super.key, + required this.offset, + required this.onVisibilityChanged, + super.child, + }); + + final double offset; + final ValueChanged onVisibilityChanged; + + @override + RenderSliverToBoxWithOffsetAdapter createRenderObject(BuildContext context) => + RenderSliverToBoxWithOffsetAdapter( + offset: offset, + onVisibilityChanged: onVisibilityChanged, + ); +} + +class RenderSliverToBoxWithOffsetAdapter extends RenderSliverToBoxAdapter { + RenderSliverToBoxWithOffsetAdapter({ + required this.offset, + required this.onVisibilityChanged, + super.child, + }); + + bool? _visible; + final double offset; + final ValueChanged onVisibilityChanged; + + @override + void performLayout() { + final visible = constraints.scrollOffset > offset; + if (_visible != visible) { + _visible = visible; + WidgetsBinding.instance.addPostFrameCallback( + (_) => onVisibilityChanged(visible), + ); + } + super.performLayout(); + } +} + +class SliverToBoxWithVisibilityAdapter extends SliverToBoxAdapter { + const SliverToBoxWithVisibilityAdapter({ + super.key, + required this.onVisibilityChanged, + super.child, + }); + + final ValueChanged onVisibilityChanged; + + @override + RenderSliverToBoxWithVisibilityAdapter createRenderObject( + BuildContext context, + ) => RenderSliverToBoxWithVisibilityAdapter( + onVisibilityChanged: onVisibilityChanged, + ); +} + +class RenderSliverToBoxWithVisibilityAdapter extends RenderSliverToBoxAdapter { + RenderSliverToBoxWithVisibilityAdapter({ + required this.onVisibilityChanged, + super.child, + }); + + final ValueChanged onVisibilityChanged; + + bool? _visible; + + @override + void performLayout() { + super.performLayout(); + final visible = geometry!.visible; + if (_visible != visible) { + _visible = visible; + WidgetsBinding.instance.addPostFrameCallback( + (_) => onVisibilityChanged(!visible), + ); + } + } +} diff --git a/lib/common/widgets/sliver/trending_header.dart b/lib/common/widgets/sliver/trending_header.dart new file mode 100644 index 0000000000..3640182ce6 --- /dev/null +++ b/lib/common/widgets/sliver/trending_header.dart @@ -0,0 +1,55 @@ +import 'package:flutter/foundation.dart' show clampDouble; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart' show RenderSliverToBoxAdapter; + +class TrendingHeader extends SliverToBoxAdapter { + const TrendingHeader({ + super.key, + required this.offset, + required this.onScrollRatioChanged, + required super.child, + }); + + final double offset; + final ValueChanged onScrollRatioChanged; + + @override + RenderSliverToBoxAdapter createRenderObject(BuildContext context) { + return RenderTrendingHeader( + offset: offset, + onScrollRatioChanged: onScrollRatioChanged, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderTrendingHeader renderObject, + ) { + renderObject.offset = offset; + } +} + +class RenderTrendingHeader extends RenderSliverToBoxAdapter { + RenderTrendingHeader({ + required this.offset, + required this.onScrollRatioChanged, + }); + + double offset; + double? _scrollRatio; + final ValueChanged onScrollRatioChanged; + + @override + void performLayout() { + super.performLayout(); + final scrollOffset = constraints.scrollOffset; + final scrollRatio = clampDouble(scrollOffset / offset, 0.0, 1.0); + if (_scrollRatio != scrollRatio) { + _scrollRatio = scrollRatio; + WidgetsBinding.instance.addPostFrameCallback((_) { + onScrollRatioChanged(scrollRatio); + }); + } + } +} diff --git a/lib/common/widgets/sliver/video_header.dart b/lib/common/widgets/sliver/video_header.dart new file mode 100644 index 0000000000..35f5f11d94 --- /dev/null +++ b/lib/common/widgets/sliver/video_header.dart @@ -0,0 +1,68 @@ +import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_dynamic_header.dart'; +import 'package:PiliPlus/utils/extension/num_ext.dart'; +import 'package:flutter/foundation.dart' show clampDouble; +import 'package:flutter/material.dart'; + +class VideoHeader extends SliverPinnedDynamicHeader { + const VideoHeader({ + super.key, + required super.minExtent, + required super.maxExtent, + required this.minVideoHeight, + required this.onScrollRatioChanged, + required super.child, + }); + + final double minVideoHeight; + final ValueChanged onScrollRatioChanged; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderVideoHeader( + minExtent: minExtent, + maxExtent: maxExtent, + minVideoHeight: minVideoHeight, + onScrollRatioChanged: onScrollRatioChanged, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderVideoHeader renderObject, + ) { + super.updateRenderObject(context, renderObject); + renderObject.minVideoHeight = minVideoHeight; + } +} + +class RenderVideoHeader extends RenderSliverPinnedDynamicHeader { + RenderVideoHeader({ + required super.minExtent, + required super.maxExtent, + required this.minVideoHeight, + required this.onScrollRatioChanged, + }); + + double? _scrollRatio; + double minVideoHeight; + final ValueChanged onScrollRatioChanged; + + @override + void performLayout() { + super.performLayout(); + final scrollOffset = constraints.scrollOffset; + final offset = scrollOffset - (maxExtent - minVideoHeight); + final scrollRatio = clampDouble( + offset.toPrecision(2) / (minVideoHeight - kToolbarHeight).toPrecision(2), + 0.0, + 1.0, + ); + if (_scrollRatio != scrollRatio) { + _scrollRatio = scrollRatio; + WidgetsBinding.instance.addPostFrameCallback((_) { + onScrollRatioChanged(scrollRatio); + }); + } + } +} diff --git a/lib/common/widgets/sliver_wrap.dart b/lib/common/widgets/sliver_wrap.dart index 492a8b8fd4..28bf220a24 100644 --- a/lib/common/widgets/sliver_wrap.dart +++ b/lib/common/widgets/sliver_wrap.dart @@ -67,9 +67,9 @@ class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor { required double mainAxisExtent, double spacing = 0.0, double runSpacing = 0.0, - }) : _mainAxisExtent = mainAxisExtent, - _spacing = spacing, - _runSpacing = runSpacing { + }) : _spacing = spacing, + _runSpacing = runSpacing, + _mainAxisExtent = mainAxisExtent { assert(mainAxisExtent > 0.0 && mainAxisExtent.isFinite); } diff --git a/lib/common/widgets/svg/level_icon.dart b/lib/common/widgets/svg/level_icon.dart new file mode 100644 index 0000000000..71a17746ab --- /dev/null +++ b/lib/common/widgets/svg/level_icon.dart @@ -0,0 +1,293 @@ +// dart format width=120 +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +class UserLevel extends LeafRenderObjectWidget { + const UserLevel( + this.level, { + super.key, + this.height = 11, + this.flash = false, + }); + + final double height; + final int level; + final bool flash; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderLevel(height, level, flash); + } + + @override + void updateRenderObject( + BuildContext context, + RenderLevel renderObject, + ) { + renderObject + ..height = height + ..level = level + ..flash = flash; + } +} + +class RenderLevel extends RenderBox { + RenderLevel(this._height, this._level, this._flash); + + double _height; + set height(double value) { + if (_height == value) return; + _height = value; + markNeedsLayout(); + } + + int _level; + set level(int value) { + if (_level == value) return; + _level = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + + bool _flash; + set flash(bool value) { + if (_flash == value) return; + _flash = value; + markNeedsLayout(); + } + + @override + Size computeDryLayout(covariant BoxConstraints constraints) { + return constraints.constrainSizeAndAttemptToPreserveAspectRatio( + Size( + (_flash ? LevelCanvas._extendR : LevelCanvas._totalR) * _height / LevelCanvas._totalB, + _height, + ), + ); + } + + @override + void performLayout() { + size = computeDryLayout(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + final paint = Paint()..color = lookupBackgroundColor(_level); + LevelCanvas(context.canvas) + ..save() + ..translate(offset.dx, offset.dy) + ..scale(size.height / LevelCanvas._totalB) + ..drawLevelBack(paint, bolt: _flash) + ..drawLevelLv() + ..drawLEDigit(_level, paint..color = Colors.white) + ..restore(); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.label = '${_flash ? "硬核" : ""}$_level级'; + } + + static Color lookupBackgroundColor(int level) { + return switch (level) { + 0 || 1 => const Color(0xFFC0C0C0), + 2 => const Color(0xFF8BD29B), + 3 => const Color(0xFF7BCDEF), + 4 => const Color(0xFFFEBB8B), + 5 => const Color(0xFFEE672A), + _ => const Color(0xFFF04C49), + }; + } +} + +extension type LevelCanvas(Canvas _) implements Canvas { + // ========== 布局常量 ========== + static const _r = Radius.circular(20); + + static const double _left = 629; + static const double _right = 877; + static const double _colW = 68; // 竖段宽度 + static const double _lColR = _left + _colW; // 697 + static const double _rColL = _right - _colW; // 810 + + // 三条横线的边界 + static const double _rowH = 68; + static const double _rowSp = 146; + static const double _topY = 55; + static const double _topYB = _topY + _rowH; // 123 + static const double _midY = _topY + _rowSp; // 201 + static const double _midYB = _midY + _rowH; // 269 + static const double _botY = _midY + _rowSp; // 347 + static const double _botYB = _botY + _rowH; // 415 + + // 竖段拼接用的中心线 + static const double _midMid = (_midY + _midYB) / 2; // 235 + + static final _boltIcon = + (ParagraphBuilder( + ParagraphStyle( + fontSize: 460, + fontFamily: Icons.bolt_rounded.fontFamily, + height: 1, + fontWeight: FontWeight.w900, + textDirection: TextDirection.ltr, + ), + )..addText(.fromCharCode(Icons.bolt_rounded.codePoint))).build() + ..layout(const ParagraphConstraints(width: double.infinity)); + void drawBolt() => drawParagraph(_boltIcon, const Offset(840, 5)); + + void _draw1(Paint paint) { + drawRRect(const .fromLTRBXY(673, _botY, 833, _botYB, 20, 20), paint); + drawRRect(.fromLTRBAndCorners(673, _topY, 787, _topYB, topLeft: _r, bottomLeft: _r, topRight: _r), paint); + drawRect(const .fromLTRB(719, _topYB, 787, _botY), paint); + } + + void drawLEDigit(int digit, Paint paint) { + if (digit == 1) return _draw1(paint); + final bits = switch (digit) { + 0 => 0x7E, + 2 => 0x6D, + 3 => 0x79, + 4 => 0x33, + 5 => 0x5B, + 6 => 0x5F, + 7 => 0x70, + 8 => 0x7F, + 9 => 0x7B, + // _ => throw ArgumentError('Unsupported digit: $digit'), + _ => 0x4F, // `E` + }; + + _drawSegments( + bits & 0x40 != 0, + bits & 0x20 != 0, + bits & 0x10 != 0, + bits & 0x08 != 0, + bits & 0x04 != 0, + bits & 0x02 != 0, + bits & 0x01 != 0, + paint, + ); + } + + void _drawSegments(bool a, bool b, bool c, bool d, bool e, bool f, bool g, Paint paint) { + // 横段 + if (a) { + _drawRRect(_left, _topY, _right, _topYB, _r, _r, f ? .zero : _r, b ? .zero : _r, paint); + } + if (g) { + _drawRRect(_left, _midY, _right, _midYB, f ? .zero : _r, b ? .zero : _r, e ? .zero : _r, c ? .zero : _r, paint); + } + if (d) { + _drawRRect(_left, _botY, _right, _botYB, e ? .zero : _r, c ? .zero : _r, _r, _r, paint); + } + + // 竖段 + // 左上竖段 f + if (f) { + final top = (a ? _topYB : _topY) - 1; // 有上横则齐底,否则到顶 + final bottom = (g ? _midY : (e ? _midMid : _midYB)) + 1; + final rTop = a ? Radius.zero : _r; + final rBot = g || e ? Radius.zero : _r; + _drawRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, paint); + } + + // 右上竖段 b + if (b) { + final top = (a ? _topYB : _topY) - 1; + final bottom = (g ? _midY : (c ? _midMid : _midYB)) + 1; + final rTop = a ? Radius.zero : _r; + final rBot = g || c ? Radius.zero : _r; + _drawRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, paint); + } + + // 左下竖段 e + if (e) { + final top = (g ? _midYB : (f ? _midMid : _midY)) - 1; + final bottom = (d ? _botY : _botYB) + 1; + final rTop = g || f ? Radius.zero : _r; + final rBot = d ? Radius.zero : _r; + _drawRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, paint); + } + + // 右下竖段 c + if (c) { + final top = (g ? _midYB : (b ? _midMid : _midY)) - 1; + final bottom = (d ? _botY : _botYB) + 1; + final rTop = g || b ? Radius.zero : _r; + final rBot = d ? Radius.zero : _r; + _drawRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, paint); + } + } + + /// 绘制圆角矩形,四角全零时退化为矩形 + void _drawRRect(double l, double t, double r, double b, Radius tl, Radius tr, Radius bl, Radius br, Paint paint) { + if (tl == .zero && tr == .zero && bl == .zero && br == .zero) { + drawRect(.fromLTRB(l, t, r, b), paint); + } else { + drawRRect(.fromLTRBAndCorners(l, t, r, b, topLeft: tl, topRight: tr, bottomLeft: bl, bottomRight: br), paint); + } + } + + static final _lvPicture = () { + final recorder = PictureRecorder(); + final paint = Paint()..color = Colors.white; + final canvas = Canvas(recorder); + + const double vLeft = 296; + const double lvTop = 106; + const double llr = 123; + const double vtb = 282; + + canvas + // L + ..drawRRect(.fromLTRBAndCorners(56, lvTop, llr, _botYB, topLeft: _r, topRight: _r, bottomLeft: _r), paint) + ..drawRRect(.fromLTRBAndCorners(llr - 1, _botY, 256, _botYB, topRight: _r, bottomRight: _r), paint) + // V + ..drawRRect(.fromLTRBAndCorners(vLeft, lvTop, 363, vtb + 1, topLeft: _r, topRight: _r), paint) + ..drawRRect(.fromLTRBAndCorners(476, lvTop, 543, vtb + 1, topLeft: _r, topRight: _r), paint) + ..drawPath( + Path() + ..moveTo(vLeft, vtb) + ..lineTo(vLeft, 292) + ..arcToPoint(const Offset(300, 313), radius: const .circular(50), clockwise: false) + ..lineTo(395, 408) + ..arcToPoint(const Offset(444, 408), radius: const .circular(50), clockwise: false) + ..lineTo(539, 313) + ..arcToPoint(const Offset(543, 292), radius: const .circular(50), clockwise: false) + ..lineTo(543, vtb) + ..lineTo(476, vtb) + ..lineTo(419.5, 340) + ..lineTo(363, vtb) + ..close(), + paint, + ); + return recorder.endRecording(); + }(); + + void drawLevelLv() => drawPicture(_lvPicture); + + static const double _totalR = 930; + static const double _extendR = 1250; + static const double _totalB = 466; + + void drawLevelBack(Paint paint, {bool bolt = false}) { + const radius = Radius.circular(27); + final double right = bolt ? _extendR : _totalR; + const double blockTop = 48; + drawRRect( + RRect.fromLTRBAndCorners(0, blockTop, right, _totalB, topLeft: radius, bottomLeft: radius, bottomRight: radius), + paint, + ); + drawRRect( + RRect.fromLTRBAndCorners(576, 0, right, blockTop + 1, topLeft: radius, topRight: radius), + paint, + ); + + if (bolt) drawBolt(); + } +} diff --git a/lib/common/widgets/svg/play_icon.dart b/lib/common/widgets/svg/play_icon.dart new file mode 100644 index 0000000000..2b64a09580 --- /dev/null +++ b/lib/common/widgets/svg/play_icon.dart @@ -0,0 +1,218 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter/semantics.dart'; + +class PlayIcon extends LeafRenderObjectWidget { + const PlayIcon({super.key, this.size = 60}); + + final double size; + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderPlay(size); + } + + @override + void updateRenderObject(BuildContext context, RenderPlay renderObject) { + renderObject.imgSize = size; + } +} + +class RenderPlay extends RenderBox { + RenderPlay(this._imgSize); + + double _imgSize; + set imgSize(double value) { + if (_imgSize == value) return; + _imgSize = value; + markNeedsLayout(); + } + + @override + Size computeDryLayout(covariant BoxConstraints constraints) { + return constraints.constrainDimensions(_imgSize, _imgSize); + } + + @override + void performLayout() { + size = computeDryLayout(constraints); + } + + @override + void paint(PaintingContext context, Offset offset) { + final canvas = context.canvas; + final size = this.size.shortestSide; + if (offset != .zero || size != 60) { + canvas.save(); + if (offset != .zero) canvas.translate(offset.dx, offset.dy); + if (size != 60) { + canvas.scale(size / 60); + } + } + canvas.drawPicture(_picture); + if (offset != .zero || size != 60) { + canvas.restore(); + } + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + super.describeSemanticsConfiguration(config); + config.label = '播放'; + } + + /// [SvgPicture] can not parse mask filter + /// fom i0.hdslb.com/bfs/static/player/img/play.svg + /// scale size from 80 to 60 + static final _picture = () { + final rec = PictureRecorder(); + final canvas = Canvas(rec); + final path = Path() + ..moveTo(41.576, 7.318) + ..cubicTo(41.244, 5.892, 39.91, 4.886, 38.41, 5.011) + ..cubicTo(38.068, 5.039, 37.813, 5.13, 37.59, 5.245) + ..cubicTo(37.37, 5.361, 37.187, 5.506, 37.034, 5.672) + ..cubicTo(36.957, 5.754, 36.891, 5.844, 36.824, 5.934) + ..lineTo(36.622, 6.203) + ..lineTo(36.222, 6.743) + ..cubicTo(35.694, 7.467, 35.178, 8.2, 34.678, 8.945) + ..cubicTo(34.179, 9.69, 33.694, 10.445, 33.231, 11.217) + ..cubicTo(33.092, 11.449, 32.954, 11.683, 32.819, 11.917) + ..cubicTo(32.258, 11.909, 31.697, 11.902, 31.137, 11.898) + ..cubicTo(29.094, 11.884, 27.051, 11.891, 25.008, 11.926) + ..cubicTo(24.871, 11.688, 24.732, 11.452, 24.591, 11.217) + ..cubicTo(24.128, 10.445, 23.643, 9.69, 23.144, 8.945) + ..cubicTo(22.645, 8.2, 22.129, 7.467, 21.6, 6.743) + ..lineTo(21.2, 6.203) + ..lineTo(20.998, 5.934) + ..cubicTo(20.931, 5.844, 20.865, 5.754, 20.788, 5.672) + ..cubicTo(20.635, 5.506, 20.452, 5.361, 20.232, 5.245) + ..cubicTo(20.009, 5.13, 19.754, 5.039, 19.412, 5.011) + ..cubicTo(17.956, 4.888, 16.59, 5.85, 16.246, 7.318) + ..cubicTo(16.168, 7.652, 16.176, 7.924, 16.217, 8.172) + ..cubicTo(16.26, 8.418, 16.34, 8.636, 16.451, 8.833) + ..cubicTo(16.506, 8.931, 16.571, 9.023, 16.635, 9.114) + ..lineTo(16.829, 9.389) + ..lineTo(17.219, 9.936) + ..cubicTo(17.743, 10.663, 18.281, 11.381, 18.834, 12.086) + ..cubicTo(18.845, 12.099, 18.855, 12.112, 18.865, 12.124) + ..cubicTo(18.025, 12.164, 17.184, 12.209, 16.344, 12.26) + ..cubicTo(15.523, 12.311, 14.701, 12.365, 13.88, 12.428) + ..lineTo(12.648, 12.525) + ..lineTo(12.032, 12.577) + ..lineTo(11.68, 12.616) + ..cubicTo(11.562, 12.63, 11.445, 12.651, 11.328, 12.668) + ..cubicTo(10.39, 12.827, 9.477, 13.141, 8.641, 13.595) + ..cubicTo(7.804, 14.049, 7.043, 14.641, 6.399, 15.34) + ..cubicTo(5.754, 16.04, 5.224, 16.845, 4.837, 17.716) + ..cubicTo(4.45, 18.586, 4.208, 19.521, 4.12, 20.467) + ..cubicTo(3.808, 23.756, 3.603, 27.055, 3.529, 30.365) + ..cubicTo(3.453, 33.676, 3.53, 36.99, 3.722, 40.289) + ..cubicTo(3.77, 41.114, 3.825, 41.939, 3.887, 42.763) + ..lineTo(3.986, 43.998) + ..lineTo(4.039, 44.616) + ..lineTo(4.046, 44.693) + ..lineTo(4.056, 44.782) + ..lineTo(4.075, 44.961) + ..cubicTo(4.087, 45.08, 4.107, 45.198, 4.126, 45.317) + ..cubicTo(4.278, 46.264, 4.586, 47.189, 5.037, 48.037) + ..cubicTo(5.486, 48.887, 6.078, 49.66, 6.777, 50.319) + ..cubicTo(7.475, 50.978, 8.283, 51.522, 9.16, 51.921) + ..cubicTo(10.035, 52.319, 10.978, 52.575, 11.935, 52.664) + ..cubicTo(11.998, 52.672, 12.047, 52.675, 12.098, 52.68) + ..lineTo(12.252, 52.693) + ..lineTo(12.56, 52.72) + ..lineTo(13.176, 52.771) + ..lineTo(14.408, 52.868) + ..cubicTo(15.23, 52.927, 16.052, 52.985, 16.874, 53.033) + ..cubicTo(23.449, 53.424, 30.03, 53.502, 36.609, 53.259) + ..cubicTo(38.254, 53.199, 39.898, 53.118, 41.542, 53.016) + ..cubicTo(42.364, 52.963, 43.186, 52.908, 44.008, 52.843) + ..lineTo(45.241, 52.743) + ..lineTo(45.857, 52.689) + ..lineTo(46.214, 52.65) + ..cubicTo(46.334, 52.635, 46.452, 52.614, 46.571, 52.596) + ..cubicTo(47.52, 52.432, 48.443, 52.112, 49.288, 51.649) + ..cubicTo(50.134, 51.188, 50.902, 50.586, 51.553, 49.878) + ..cubicTo(52.204, 49.17, 52.739, 48.353, 53.127, 47.471) + ..cubicTo(53.321, 47.03, 53.479, 46.573, 53.598, 46.107) + ..cubicTo(53.631, 45.991, 53.656, 45.873, 53.681, 45.755) + ..lineTo(53.719, 45.579) + ..lineTo(53.749, 45.401) + ..cubicTo(53.77, 45.283, 53.79, 45.164, 53.803, 45.045) + ..cubicTo(53.818, 44.927, 53.834, 44.8, 53.843, 44.704) + ..cubicTo(54.179, 41.414, 54.402, 38.111, 54.476, 34.794) + ..cubicTo(54.553, 31.475, 54.442, 28.153, 54.205, 24.853) + ..cubicTo(54.145, 24.028, 54.078, 23.204, 54.002, 22.38) + ..lineTo(53.884, 21.145) + ..lineTo(53.82, 20.528) + ..lineTo(53.804, 20.374) + ..lineTo(53.794, 20.29) + ..lineTo(53.782, 20.201) + ..cubicTo(53.766, 20.083, 53.754, 19.964, 53.731, 19.846) + ..cubicTo(53.578, 18.901, 53.266, 17.979, 52.813, 17.136) + ..cubicTo(52.362, 16.291, 51.771, 15.522, 51.073, 14.869) + ..cubicTo(50.375, 14.215, 49.57, 13.677, 48.698, 13.284) + ..cubicTo(47.827, 12.89, 46.89, 12.64, 45.94, 12.552) + ..lineTo(45.854, 12.544) + ..lineTo(45.777, 12.537) + ..lineTo(45.623, 12.524) + ..lineTo(45.315, 12.499) + ..lineTo(44.698, 12.449) + ..lineTo(43.466, 12.357) + ..cubicTo(42.644, 12.3, 41.822, 12.247, 41.0, 12.202) + ..cubicTo(40.326, 12.164, 39.651, 12.131, 38.977, 12.1) + ..cubicTo(38.98, 12.096, 38.984, 12.091, 38.988, 12.086) + ..cubicTo(39.542, 11.381, 40.079, 10.663, 40.603, 9.936) + ..lineTo(40.994, 9.389) + ..lineTo(41.187, 9.114) + ..cubicTo(41.252, 9.023, 41.316, 8.931, 41.371, 8.833) + ..cubicTo(41.482, 8.636, 41.563, 8.418, 41.605, 8.172) + ..cubicTo(41.646, 7.924, 41.654, 7.652, 41.576, 7.318) + ..close() + ..moveTo(21.283, 26.038) + ..cubicTo(21.321, 25.666, 21.427, 25.305, 21.597, 24.973) + ..cubicTo(22.351, 23.498, 24.158, 22.913, 25.634, 23.667) + ..lineTo(26.683, 24.211) + ..cubicTo(28.428, 25.126, 30.148, 26.088, 31.842, 27.097) + ..cubicTo(34.726, 28.814, 34.726, 28.814, 37.376, 30.628) + ..cubicTo(37.694, 30.846, 37.967, 31.123, 38.18, 31.444) + ..cubicTo(39.096, 32.824, 38.72, 34.686, 37.34, 35.603) + ..lineTo(36.265, 36.309) + ..cubicTo(34.823, 37.245, 33.349, 38.161, 31.842, 39.058) + ..cubicTo(28.87, 40.828, 28.87, 40.828, 25.698, 42.513) + ..cubicTo(25.352, 42.697, 24.973, 42.811, 24.583, 42.849) + ..cubicTo(22.934, 43.01, 21.466, 41.805, 21.305, 40.156) + ..lineTo(21.221, 39.247) + ..cubicTo(21.04, 37.126, 20.949, 35.005, 20.949, 32.884) + ..cubicTo(20.949, 29.361, 20.949, 29.361, 21.283, 26.038) + ..close(); + + final paint = Paint() + ..color = Colors.black.withValues(alpha: 0.3 * 0.8) + ..maskFilter = const .blur(.normal, 1.0); + + // feOffset dy="2" + canvas + ..save() + ..translate(0, 2) + ..drawPath(path, paint) + ..restore(); + + // dy=0, blur=3.5 + paint + ..color = Colors.black.withValues(alpha: 0.2 * 0.8) + ..maskFilter = const .blur(.normal, 3.5); + + canvas.drawPath(path, paint); + + paint + ..color = Colors.white.withValues(alpha: 0.8) + ..maskFilter = null; + + canvas.drawPath(path, paint); + + return rec.endRecording(); + }(); +} diff --git a/lib/common/widgets/translucent_column.dart b/lib/common/widgets/translucent_column.dart new file mode 100644 index 0000000000..989f7d0b2b --- /dev/null +++ b/lib/common/widgets/translucent_column.dart @@ -0,0 +1,132 @@ +/* + * This file is part of PiliPlus + * + * PiliPlus is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * PiliPlus is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with PiliPlus. If not, see . + */ + +import 'package:PiliPlus/common/widgets/animated_height.dart' + show RenderAnimatedHeight; +import 'package:flutter/rendering.dart' + show RenderProxyBox, BoxHitTestResult, RenderFlex, FlexParentData; +import 'package:flutter/widgets.dart'; + +class TranslucentColumn extends Flex { + const TranslucentColumn({ + super.key, + super.mainAxisAlignment, + super.mainAxisSize, + super.crossAxisAlignment, + super.textDirection, + super.verticalDirection, + super.textBaseline, + super.spacing, + super.children, + }) : super(direction: Axis.vertical); + + @override + RenderTranslucentColumn createRenderObject(BuildContext context) { + return RenderTranslucentColumn( + direction: direction, + mainAxisAlignment: mainAxisAlignment, + mainAxisSize: mainAxisSize, + crossAxisAlignment: crossAxisAlignment, + textDirection: getEffectiveTextDirection(context), + verticalDirection: verticalDirection, + textBaseline: textBaseline, + clipBehavior: clipBehavior, + spacing: spacing, + ); + } + + @override + void updateRenderObject( + BuildContext context, + RenderTranslucentColumn renderObject, + ) { + renderObject + ..direction = direction + ..mainAxisAlignment = mainAxisAlignment + ..mainAxisSize = mainAxisSize + ..crossAxisAlignment = crossAxisAlignment + ..textDirection = getEffectiveTextDirection(context) + ..verticalDirection = verticalDirection + ..textBaseline = textBaseline + ..clipBehavior = clipBehavior + ..spacing = spacing; + } +} + +class RenderTranslucentColumn extends RenderFlex { + RenderTranslucentColumn({ + super.children, + super.direction, + super.mainAxisSize, + super.mainAxisAlignment, + super.crossAxisAlignment, + super.textDirection, + super.verticalDirection, + super.textBaseline, + super.clipBehavior, + super.spacing, + }); + + @override + bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { + RenderBox? child = lastChild; + final width = size.width; + while (child != null) { + final childParentData = child.parentData! as FlexParentData; + final bool isHit = result.addWithPaintOffset( + offset: childParentData.offset, + position: position, + hitTest: (BoxHitTestResult result, Offset transformed) { + assert(transformed == position - childParentData.offset); + if (transformed.dx >= 0.0 && + transformed.dx < width && + transformed.dy >= 0.0 && + transformed.dy < child!.size.height) { + final hit = child.hitTest(result, position: transformed); + if (child is RenderAnimatedHeight) { + return hit; + } + if (hit) { + return true; + } + if (child is RenderNoTranslucentArea) { + return false; + } + return true; + } + return false; + }, + ); + if (isHit) { + return true; + } + child = childParentData.previousSibling; + } + return false; + } +} + +class NoTranslucentArea extends SingleChildRenderObjectWidget { + const NoTranslucentArea({super.key, required Widget super.child}); + + @override + RenderObject createRenderObject(BuildContext context) { + return RenderNoTranslucentArea(); + } +} + +class RenderNoTranslucentArea extends RenderProxyBox {} diff --git a/lib/common/widgets/video_card/video_card_h.dart b/lib/common/widgets/video_card/video_card_h.dart index 82c1dc5026..16da9c1c73 100644 --- a/lib/common/widgets/video_card/video_card_h.dart +++ b/lib/common/widgets/video_card/video_card_h.dart @@ -1,23 +1,19 @@ import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/progress_bar/video_progress_indicator.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/video_popup_menu.dart'; import 'package:PiliPlus/http/search.dart'; -import 'package:PiliPlus/models/common/badge_type.dart'; -import 'package:PiliPlus/models/common/stat_type.dart'; -import 'package:PiliPlus/models/model_hot_video_item.dart'; -import 'package:PiliPlus/models/model_video.dart'; -import 'package:PiliPlus/models/search/result.dart'; +import 'package:PiliPlus/models/horizontal_video_model.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:flutter/material.dart' hide LayoutBuilder; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:flutter/material.dart'; // 视频卡片 - 水平布局 class VideoCardH extends StatelessWidget { @@ -28,47 +24,23 @@ class VideoCardH extends StatelessWidget { this.onViewLater, this.onRemove, }); - final BaseVideoItemModel videoItem; + final HorizontalVideoModel videoItem; final VoidCallback? onTap; final ValueChanged? onViewLater; final VoidCallback? onRemove; @override Widget build(BuildContext context) { - String type = 'video'; - String? badge; - if (videoItem case final SearchVideoItemModel item) { - final typeOrNull = item.type; - if (typeOrNull != null && typeOrNull.isNotEmpty) { - type = typeOrNull; - if (type == 'ketang') { - badge = '课堂'; - } else if (type == 'live_room') { - badge = '直播'; - } - } - if (item.isUnionVideo == 1) { - badge = '合作'; - } - } else if (videoItem case final HotVideoItemModel item) { - if (item.isCharging == true) { - badge = '充电专属'; - } else if (item.isCooperation == 1) { - badge = '合作'; - } else { - badge = item.pgcLabel; - } - } void onLongPress() => imageSaveDialog( bvid: videoItem.bvid, title: videoItem.title, cover: videoItem.cover, ); - final colorScheme = ColorScheme.of(context); + final theme = Theme.of(context); return Material( - type: MaterialType.transparency, + type: .transparency, child: Stack( - clipBehavior: Clip.none, + clipBehavior: .none, children: [ InkWell( onLongPress: onLongPress, @@ -76,70 +48,65 @@ class VideoCardH extends StatelessWidget { onTap: onTap ?? () async { - if (type == 'ketang') { - PageUtils.viewPugv(seasonId: videoItem.aid); + if (videoItem.isPugv ?? false) { + PageUtils.viewPugv(seasonId: videoItem.seasonId); return; - } else if (type == 'live_room') { - if (videoItem case final SearchVideoItemModel item) { - int? roomId = item.id; - if (roomId != null) { - PageUtils.toLiveRoom(roomId); - } - } else { - SmartDialog.showToast( - 'err: live_room : ${videoItem.runtimeType}', - ); + } + + if (videoItem.isLive ?? false) { + if (videoItem.roomId case final roomId?) { + PageUtils.toLiveRoom(roomId); } return; } - if (videoItem case final HotVideoItemModel item) { - if (item.redirectUrl?.isNotEmpty == true && - PageUtils.viewPgcFromUri(item.redirectUrl!)) { - return; - } + + if (videoItem.redirectUrl?.isNotEmpty == true && + PageUtils.viewPgcFromUri(videoItem.redirectUrl!)) { + return; } - try { - final int? cid = - videoItem.cid ?? - await SearchHttp.ab2c( + int? cid = videoItem.cid; + Dimension? dimension = videoItem.dimension; + if (cid == null) { + if (await SearchHttp.ab2cWithDimension( aid: videoItem.aid, bvid: videoItem.bvid, - ); - if (cid != null) { - PageUtils.toVideoPage( - bvid: videoItem.bvid, - cid: cid, - cover: videoItem.cover, - title: videoItem.title, - ); + ) + case final res?) { + cid = res.cid; + dimension = res.dimension; } - } catch (err) { - SmartDialog.showToast(err.toString()); + } + if (cid != null) { + PageUtils.toVideoPage( + bvid: videoItem.bvid, + cid: cid, + cover: videoItem.cover, + title: videoItem.title, + dimension: dimension, + ); } }, child: Padding( - padding: const EdgeInsets.symmetric( + padding: const .symmetric( horizontal: Style.safeSpace, vertical: 5, ), child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + crossAxisAlignment: .start, + children: [ AspectRatio( aspectRatio: Style.aspectRatio, child: _CoverBuilderH( cover: videoItem.cover, - badge: badge, + badge: videoItem.badge, duration: videoItem.duration, - progress: videoItem is HotVideoItemModel - ? (videoItem as HotVideoItemModel).progress - : null, - colorScheme: colorScheme, + progress: videoItem.progress, + colorScheme: theme.colorScheme, ), ), const SizedBox(width: 10), - content(context), + content(theme), ], ), ), @@ -160,51 +127,49 @@ class VideoCardH extends StatelessWidget { ); } - Widget content(BuildContext context) { - final theme = Theme.of(context); + Widget content(ThemeData theme) { String pubdate = DateFormatUtils.dateFormat(videoItem.pubdate!); if (pubdate != '') pubdate += ' '; return Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: .start, children: [ - if (videoItem case final SearchVideoItemModel item) ...[ - if (item.titleList?.isNotEmpty == true) - Expanded( - child: Text.rich( - overflow: TextOverflow.ellipsis, - maxLines: 2, - TextSpan( - children: item.titleList! - .map( - (e) => TextSpan( - text: e.text, - style: TextStyle( - fontSize: theme.textTheme.bodyMedium!.fontSize, - height: 1.42, - letterSpacing: 0.3, - color: e.isEm - ? theme.colorScheme.primary - : theme.colorScheme.onSurface, - ), + if (videoItem.titleList?.isNotEmpty == true) + Expanded( + child: Text.rich( + overflow: .ellipsis, + maxLines: 2, + TextSpan( + children: videoItem.titleList! + .map( + (e) => TextSpan( + text: e.text, + style: TextStyle( + fontSize: theme.textTheme.bodyMedium!.fontSize, + height: 1.42, + letterSpacing: 0.3, + color: e.isEm + ? theme.colorScheme.primary + : theme.colorScheme.onSurface, ), - ) - .toList(), - ), + ), + ) + .toList(), ), ), - ] else + ) + else Expanded( child: Text( videoItem.title, - textAlign: TextAlign.start, + textAlign: .start, style: TextStyle( fontSize: theme.textTheme.bodyMedium!.fontSize, height: 1.42, letterSpacing: 0.3, ), maxLines: 2, - overflow: TextOverflow.ellipsis, + overflow: .ellipsis, ), ), Text( @@ -214,7 +179,7 @@ class VideoCardH extends StatelessWidget { fontSize: 12, height: 1, color: theme.colorScheme.outline, - overflow: TextOverflow.clip, + overflow: .clip, ), ), const SizedBox(height: 3), @@ -222,11 +187,11 @@ class VideoCardH extends StatelessWidget { spacing: 8, children: [ StatWidget( - type: StatType.play, + type: .play, value: videoItem.stat.view, ), StatWidget( - type: StatType.danmaku, + type: .danmaku, value: videoItem.stat.danmu, ), ], diff --git a/lib/common/widgets/video_card/video_card_v.dart b/lib/common/widgets/video_card/video_card_v.dart index 14446af03d..e986653ea8 100644 --- a/lib/common/widgets/video_card/video_card_v.dart +++ b/lib/common/widgets/video_card/video_card_v.dart @@ -1,22 +1,23 @@ import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; -import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/video_popup_menu.dart'; import 'package:PiliPlus/http/search.dart'; -import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; +import 'package:PiliPlus/models/home/rcmd/result.dart'; import 'package:PiliPlus/models/model_rec_video_item.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; +import 'package:PiliPlus/utils/extension/dimension_ext.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; -import 'package:PiliPlus/utils/utils.dart'; -import 'package:flutter/material.dart' hide LayoutBuilder; +import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:intl/intl.dart'; @@ -31,17 +32,28 @@ class VideoCardV extends StatelessWidget { this.onRemove, }); - Future onPushDetail(String heroTag) async { - String? goto = videoItem.goto; - switch (goto) { + Future onPushDetail() async { + switch (videoItem.goto) { case 'bangumi': PageUtils.viewPgc(epId: videoItem.param!); break; case 'av': - String bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid!); - int? cid = - videoItem.cid ?? - await SearchHttp.ab2c(aid: videoItem.aid, bvid: bvid); + var bvid = videoItem.bvid ?? IdUtils.av2bv(videoItem.aid!); + var cid = videoItem.cid; + bool isVertical = false; + Dimension? dimension; + if (videoItem is RcmdVideoItemAppModel) { + if (videoItem.uri case final uri?) { + isVertical = uri.isVerticalFromUri; + } + } + if (cid == null) { + if (await SearchHttp.ab2cWithDimension(aid: videoItem.aid, bvid: bvid) + case final res?) { + cid = res.cid; + dimension = res.dimension; + } + } if (cid != null) { PageUtils.toVideoPage( aid: videoItem.aid, @@ -49,6 +61,8 @@ class VideoCardV extends StatelessWidget { cid: cid, cover: videoItem.cover, title: videoItem.title, + isVertical: isVertical, + dimension: dimension, ); } break; @@ -74,14 +88,13 @@ class VideoCardV extends StatelessWidget { cover: videoItem.cover, bvid: videoItem.bvid, ); - final theme = Theme.of(context); return Stack( clipBehavior: Clip.none, children: [ Card( clipBehavior: Clip.hardEdge, child: InkWell( - onTap: () => onPushDetail(Utils.makeHeroTag(videoItem.aid)), + onTap: onPushDetail, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Column( @@ -91,77 +104,7 @@ class VideoCardV extends StatelessWidget { cover: videoItem.cover, duration: videoItem.duration, ), - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(6, 5, 6, 5), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - videoItem.title, - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - height: 1.38, - ), - ), - ), - videoStat(context, theme), - Row( - spacing: 2, - children: [ - if (videoItem.goto == 'bangumi') - PBadge( - text: videoItem.pgcBadge, - isStack: false, - size: PBadgeSize.small, - type: PBadgeType.line_primary, - fontSize: 9, - ), - if (videoItem.rcmdReason != null) - PBadge( - text: videoItem.rcmdReason, - isStack: false, - size: PBadgeSize.small, - type: PBadgeType.secondary, - ), - if (videoItem.goto == 'picture') - const PBadge( - text: '动态', - isStack: false, - size: PBadgeSize.small, - type: PBadgeType.line_primary, - fontSize: 9, - ), - if (videoItem.isFollowed) - const PBadge( - text: '已关注', - isStack: false, - size: PBadgeSize.small, - type: PBadgeType.secondary, - ), - Expanded( - flex: 1, - child: Text( - videoItem.owner.name.toString(), - maxLines: 1, - overflow: TextOverflow.clip, - semanticsLabel: 'UP:${videoItem.owner.name}', - style: TextStyle( - height: 1.5, - fontSize: theme.textTheme.labelMedium!.fontSize, - color: theme.colorScheme.outline, - ), - ), - ), - if (videoItem.goto == 'av') const SizedBox(width: 10), - ], - ), - ], - ), - ), - ), + content(context), ], ), ), @@ -182,6 +125,81 @@ class VideoCardV extends StatelessWidget { ); } + Widget content(BuildContext context) { + final theme = Theme.of(context); + return Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(6, 5, 6, 5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + "${videoItem.title}\n", + maxLines: 2, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + height: 1.38, + ), + ), + ), + videoStat(context, theme), + Row( + spacing: 2, + children: [ + if (videoItem.goto == 'bangumi') + PBadge( + text: videoItem.pgcBadge, + isStack: false, + size: .small, + type: .line_primary, + fontSize: 9, + ), + if (videoItem.rcmdReason != null) + PBadge( + text: videoItem.rcmdReason, + isStack: false, + size: .small, + type: .secondary, + ), + if (videoItem.goto == 'picture') + const PBadge( + text: '动态', + isStack: false, + size: .small, + type: .line_primary, + fontSize: 9, + ), + if (videoItem.isFollowed) + const PBadge( + text: '已关注', + isStack: false, + size: .small, + type: .secondary, + ), + Expanded( + flex: 1, + child: Text( + videoItem.owner.name.toString(), + maxLines: 1, + overflow: TextOverflow.clip, + semanticsLabel: 'UP:${videoItem.owner.name}', + style: TextStyle( + height: 1.5, + fontSize: theme.textTheme.labelMedium!.fontSize, + color: theme.colorScheme.outline, + ), + ), + ), + if (videoItem.goto == 'av') const SizedBox(width: 10), + ], + ), + ], + ), + ), + ); + } + static final shortFormat = DateFormat('M-d'); static final longFormat = DateFormat('yy-M-d'); diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index 75deb62439..1650374671 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/account_type.dart'; @@ -52,13 +53,7 @@ class VideoPopupMenu extends StatelessWidget { if (videoItem.bvid?.isNotEmpty == true) ...[ _VideoCustomAction( videoItem.bvid!, - const Stack( - clipBehavior: Clip.none, - children: [ - Icon(MdiIcons.identifier, size: 16), - Icon(MdiIcons.circleOutline, size: 16), - ], - ), + const Icon(CustomIcons.identifier_circle, size: 16), () => Utils.copyText(videoItem.bvid!), ), _VideoCustomAction( @@ -69,30 +64,7 @@ class VideoPopupMenu extends StatelessWidget { if (videoItem.cid != null && Pref.enableAi) _VideoCustomAction( 'AI总结', - const Stack( - alignment: Alignment.center, - clipBehavior: Clip.none, - children: [ - Icon(Icons.circle_outlined, size: 16), - ExcludeSemantics( - child: Text( - 'AI', - style: TextStyle( - fontSize: 10, - height: 1, - fontWeight: FontWeight.w700, - ), - strutStyle: StrutStyle( - fontSize: 10, - height: 1, - leading: 0, - fontWeight: FontWeight.w700, - ), - textScaler: TextScaler.noScaling, - ), - ), - ], - ), + const Icon(CustomIcons.ai_circle, size: 16), () async { final res = await UgcIntroController.getAiConclusion( videoItem.bvid!, @@ -175,134 +147,115 @@ class VideoPopupMenu extends StatelessWidget { showDialog( context: context, builder: (context) { - return AlertDialog( - content: SingleChildScrollView( - child: Column( - crossAxisAlignment: .start, - children: [ - if (tp.dislikeReasons != null) ...[ - const Text('我不想看'), - const SizedBox(height: 5), - Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: tp.dislikeReasons!.map(( - item, - ) { - return actionButton(item, null); - }).toList(), - ), - ], - if (tp.feedbacks != null) ...[ - const SizedBox(height: 5), - const Text('反馈'), - const SizedBox(height: 5), - Wrap( - spacing: 8.0, - runSpacing: 8.0, - children: tp.feedbacks!.map((item) { - return actionButton(null, item); - }).toList(), - ), - ], - const Divider(), - Center( - child: FilledButton.tonal( - onPressed: () async { - SmartDialog.showLoading( - msg: '正在提交', - ); - final res = - await VideoHttp.feedDislikeCancel( - id: item.param!, - goto: item.goto!, - ); - SmartDialog.dismiss(); - SmartDialog.showToast( - res.isSuccess - ? "成功" - : res.toString(), + return SimpleDialog( + contentPadding: const .fromLTRB(24, 16, 24, 24), + children: [ + if (tp.dislikeReasons != null) ...[ + const Text('我不想看'), + const SizedBox(height: 5), + Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: tp.dislikeReasons! + .map((item) => actionButton(item, null)) + .toList(), + ), + ], + if (tp.feedbacks != null) ...[ + const SizedBox(height: 5), + const Text('反馈'), + const SizedBox(height: 5), + Wrap( + spacing: 8.0, + runSpacing: 8.0, + children: tp.feedbacks! + .map((item) => actionButton(null, item)) + .toList(), + ), + ], + const Divider(), + Center( + child: FilledButton.tonal( + onPressed: () async { + SmartDialog.showLoading( + msg: '正在提交', + ); + final res = + await VideoHttp.feedDislikeCancel( + id: item.param!, + goto: item.goto!, ); - Get.back(); - }, - style: FilledButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - child: const Text("撤销"), - ), + SmartDialog.dismiss(); + SmartDialog.showToast( + res.isSuccess ? "成功" : res.toString(), + ); + Get.back(); + }, + style: FilledButton.styleFrom( + visualDensity: VisualDensity.compact, ), - ], + child: const Text("撤销"), + ), ), - ), + ], ); }, ); } else { showDialog( context: context, - builder: (context) => AlertDialog( - content: SingleChildScrollView( - child: Column( + builder: (context) => SimpleDialog( + contentPadding: const .all(24), + children: [ + const Center(child: Text("web端暂不支持精细选择")), + const SizedBox(height: 5), + Wrap( + spacing: 5.0, + runSpacing: 2.0, + alignment: .center, children: [ - const SizedBox(height: 5), - const Text("web端暂不支持精细选择"), - const SizedBox(height: 5), - Wrap( - spacing: 5.0, - runSpacing: 2.0, - children: [ - FilledButton.tonal( - onPressed: () async { - Get.back(); - SmartDialog.showLoading( - msg: '正在提交', - ); - final res = - await VideoHttp.dislikeVideo( - bvid: videoItem.bvid!, - type: true, - ); - SmartDialog.dismiss(); - if (res.isSuccess) { - SmartDialog.showToast('点踩成功'); - onRemove?.call(); - } else { - res.toast(); - } - }, - style: FilledButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - child: const Text("点踩"), - ), - FilledButton.tonal( - onPressed: () async { - Get.back(); - SmartDialog.showLoading( - msg: '正在提交', - ); - final res = - await VideoHttp.dislikeVideo( - bvid: videoItem.bvid!, - type: false, - ); - SmartDialog.dismiss(); - SmartDialog.showToast( - res.isSuccess - ? '取消踩' - : res.toString(), - ); - }, - style: FilledButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - child: const Text("撤销"), - ), - ], + FilledButton.tonal( + onPressed: () async { + Get.back(); + SmartDialog.showLoading(msg: '正在提交'); + final res = await VideoHttp.dislikeVideo( + bvid: videoItem.bvid!, + type: true, + ); + SmartDialog.dismiss(); + if (res.isSuccess) { + SmartDialog.showToast('点踩成功'); + onRemove?.call(); + } else { + res.toast(); + } + }, + style: FilledButton.styleFrom( + visualDensity: .compact, + ), + child: const Text("点踩"), + ), + FilledButton.tonal( + onPressed: () async { + Get.back(); + SmartDialog.showLoading(msg: '正在提交'); + final res = await VideoHttp.dislikeVideo( + bvid: videoItem.bvid!, + type: false, + ); + SmartDialog.dismiss(); + SmartDialog.showToast( + res.isSuccess ? '取消踩' : res.toString(), + ); + }, + style: FilledButton.styleFrom( + visualDensity: .compact, + ), + child: const Text("撤销"), ), ], ), - ), + ], ), ); } @@ -326,9 +279,7 @@ class VideoPopupMenu extends StatelessWidget { child: Text( '点错了', style: TextStyle( - color: Theme.of( - context, - ).colorScheme.outline, + color: ColorScheme.of(context).outline, ), ), ), diff --git a/lib/grpc/audio.dart b/lib/grpc/audio.dart index 09d8eb6cf2..08b39ac670 100644 --- a/lib/grpc/audio.dart +++ b/lib/grpc/audio.dart @@ -25,8 +25,8 @@ abstract final class AudioGrpc { playerArgs: PlayerArgs( qn: Int64(qn), fnval: Int64(fnval), - forceHost: Int64(2), - voiceBalance: Int64(1), + forceHost: Int64.TWO, + voiceBalance: Int64.ONE, ), ), PlayURLResp.fromBuffer, @@ -60,8 +60,8 @@ abstract final class AudioGrpc { playerArgs: PlayerArgs( qn: Int64(qn), fnval: Int64(fnval), - forceHost: Int64(2), - voiceBalance: Int64(1), + forceHost: Int64.TWO, + voiceBalance: Int64.ONE, ), extraId: extraId, sortOpt: SortOption(order: order), diff --git a/lib/grpc/bilibili/community/service/dm/v1.pb.dart b/lib/grpc/bilibili/community/service/dm/v1.pb.dart index afa7b83837..b980d6a279 100644 --- a/lib/grpc/bilibili/community/service/dm/v1.pb.dart +++ b/lib/grpc/bilibili/community/service/dm/v1.pb.dart @@ -1223,7 +1223,7 @@ class DanmakuElem extends $pb.GeneratedMessage { $core.int? pool, $core.String? idStr, $core.int? attr, - $fixnum.Int64? like, + $fixnum.Int64? likeCount, $core.String? animation, $core.String? extra, DmColorfulType? colorful, @@ -1247,7 +1247,7 @@ class DanmakuElem extends $pb.GeneratedMessage { if (pool != null) result.pool = pool; if (idStr != null) result.idStr = idStr; if (attr != null) result.attr = attr; - if (like != null) result.like = like; + if (likeCount != null) result.likeCount = likeCount; if (animation != null) result.animation = animation; if (extra != null) result.extra = extra; if (colorful != null) result.colorful = colorful; @@ -1286,7 +1286,7 @@ class DanmakuElem extends $pb.GeneratedMessage { ..aI(11, _omitFieldNames ? '' : 'pool') ..aOS(12, _omitFieldNames ? '' : 'idStr') ..aI(13, _omitFieldNames ? '' : 'attr') - ..aInt64(15, _omitFieldNames ? '' : 'like') + ..aInt64(15, _omitFieldNames ? '' : 'likeCount') ..aOS(22, _omitFieldNames ? '' : 'animation') ..aOS(23, _omitFieldNames ? '' : 'extra') ..aE(24, _omitFieldNames ? '' : 'colorful', @@ -1295,8 +1295,8 @@ class DanmakuElem extends $pb.GeneratedMessage { ..aInt64(26, _omitFieldNames ? '' : 'oid') ..aE(27, _omitFieldNames ? '' : 'dmFrom', enumValues: DmFromType.values) - ..aI(28, _omitFieldNames ? '' : 'count') - ..aOB(29, _omitFieldNames ? '' : 'isSelf') + ..aI(100, _omitFieldNames ? '' : 'count') + ..aOB(101, _omitFieldNames ? '' : 'isSelf') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1436,13 +1436,13 @@ class DanmakuElem extends $pb.GeneratedMessage { void clearAttr() => $_clearField(13); @$pb.TagNumber(15) - $fixnum.Int64 get like => $_getI64(13); + $fixnum.Int64 get likeCount => $_getI64(13); @$pb.TagNumber(15) - set like($fixnum.Int64 value) => $_setInt64(13, value); + set likeCount($fixnum.Int64 value) => $_setInt64(13, value); @$pb.TagNumber(15) - $core.bool hasLike() => $_has(13); + $core.bool hasLikeCount() => $_has(13); @$pb.TagNumber(15) - void clearLike() => $_clearField(15); + void clearLikeCount() => $_clearField(15); @$pb.TagNumber(22) $core.String get animation => $_getSZ(14); @@ -1498,23 +1498,25 @@ class DanmakuElem extends $pb.GeneratedMessage { @$pb.TagNumber(27) void clearDmFrom() => $_clearField(27); - @$pb.TagNumber(28) + /// extra field + @$pb.TagNumber(100) $core.int get count => $_getIZ(20); - @$pb.TagNumber(28) + @$pb.TagNumber(100) set count($core.int value) => $_setSignedInt32(20, value); - @$pb.TagNumber(28) + @$pb.TagNumber(100) $core.bool hasCount() => $_has(20); - @$pb.TagNumber(28) - void clearCount() => $_clearField(28); + @$pb.TagNumber(100) + void clearCount() => $_clearField(100); - @$pb.TagNumber(29) + /// extra field + @$pb.TagNumber(101) $core.bool get isSelf => $_getBF(21); - @$pb.TagNumber(29) + @$pb.TagNumber(101) set isSelf($core.bool value) => $_setBool(21, value); - @$pb.TagNumber(29) + @$pb.TagNumber(101) $core.bool hasIsSelf() => $_has(21); - @$pb.TagNumber(29) - void clearIsSelf() => $_clearField(29); + @$pb.TagNumber(101) + void clearIsSelf() => $_clearField(101); } class DanmakuFlag extends $pb.GeneratedMessage { diff --git a/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart b/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart index a455815a12..0418c18d68 100644 --- a/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart +++ b/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart @@ -624,7 +624,7 @@ const DanmakuElem$json = { {'1': 'pool', '3': 11, '4': 1, '5': 5, '10': 'pool'}, {'1': 'id_str', '3': 12, '4': 1, '5': 9, '10': 'idStr'}, {'1': 'attr', '3': 13, '4': 1, '5': 5, '10': 'attr'}, - {'1': 'like', '3': 15, '4': 1, '5': 3, '10': 'like'}, + {'1': 'like_count', '3': 15, '4': 1, '5': 3, '10': 'likeCount'}, {'1': 'animation', '3': 22, '4': 1, '5': 9, '10': 'animation'}, {'1': 'extra', '3': 23, '4': 1, '5': 9, '10': 'extra'}, { @@ -645,8 +645,8 @@ const DanmakuElem$json = { '6': '.bilibili.community.service.dm.v1.DmFromType', '10': 'dmFrom' }, - {'1': 'count', '3': 28, '4': 1, '5': 5, '10': 'count'}, - {'1': 'is_self', '3': 29, '4': 1, '5': 8, '10': 'isSelf'}, + {'1': 'count', '3': 100, '4': 1, '5': 5, '10': 'count'}, + {'1': 'is_self', '3': 101, '4': 1, '5': 8, '10': 'isSelf'}, ], }; @@ -657,13 +657,13 @@ final $typed_data.Uint8List danmakuElemDescriptor = $convert.base64Decode( 'bG9yGAUgASgNUgVjb2xvchIZCghtaWRfaGFzaBgGIAEoCVIHbWlkSGFzaBIYCgdjb250ZW50GA' 'cgASgJUgdjb250ZW50EhQKBWN0aW1lGAggASgDUgVjdGltZRIWCgZ3ZWlnaHQYCSABKAVSBndl' 'aWdodBIWCgZhY3Rpb24YCiABKAlSBmFjdGlvbhISCgRwb29sGAsgASgFUgRwb29sEhUKBmlkX3' - 'N0chgMIAEoCVIFaWRTdHISEgoEYXR0chgNIAEoBVIEYXR0chISCgRsaWtlGA8gASgDUgRsaWtl' - 'EhwKCWFuaW1hdGlvbhgWIAEoCVIJYW5pbWF0aW9uEhQKBWV4dHJhGBcgASgJUgVleHRyYRJMCg' - 'hjb2xvcmZ1bBgYIAEoDjIwLmJpbGliaWxpLmNvbW11bml0eS5zZXJ2aWNlLmRtLnYxLkRtQ29s' - 'b3JmdWxUeXBlUghjb2xvcmZ1bBISCgR0eXBlGBkgASgFUgR0eXBlEhAKA29pZBgaIAEoA1IDb2' - 'lkEkUKB2RtX2Zyb20YGyABKA4yLC5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5E' - 'bUZyb21UeXBlUgZkbUZyb20SFAoFY291bnQYHCABKAVSBWNvdW50EhcKB2lzX3NlbGYYHSABKA' - 'hSBmlzU2VsZg=='); + 'N0chgMIAEoCVIFaWRTdHISEgoEYXR0chgNIAEoBVIEYXR0chIdCgpsaWtlX2NvdW50GA8gASgD' + 'UglsaWtlQ291bnQSHAoJYW5pbWF0aW9uGBYgASgJUglhbmltYXRpb24SFAoFZXh0cmEYFyABKA' + 'lSBWV4dHJhEkwKCGNvbG9yZnVsGBggASgOMjAuYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2Uu' + 'ZG0udjEuRG1Db2xvcmZ1bFR5cGVSCGNvbG9yZnVsEhIKBHR5cGUYGSABKAVSBHR5cGUSEAoDb2' + 'lkGBogASgDUgNvaWQSRQoHZG1fZnJvbRgbIAEoDjIsLmJpbGliaWxpLmNvbW11bml0eS5zZXJ2' + 'aWNlLmRtLnYxLkRtRnJvbVR5cGVSBmRtRnJvbRIUCgVjb3VudBhkIAEoBVIFY291bnQSFwoHaX' + 'Nfc2VsZhhlIAEoCFIGaXNTZWxm'); @$core.Deprecated('Use danmakuFlagDescriptor instead') const DanmakuFlag$json = { diff --git a/lib/grpc/bilibili/main/community/reply/v1.pb.dart b/lib/grpc/bilibili/main/community/reply/v1.pb.dart index 713498cf64..53e01c6f4f 100644 --- a/lib/grpc/bilibili/main/community/reply/v1.pb.dart +++ b/lib/grpc/bilibili/main/community/reply/v1.pb.dart @@ -8159,6 +8159,8 @@ class ReplyControl extends $pb.GeneratedMessage { ReplyControl_EasterEggLabel? easterEggLabel, $core.String? contextFeature, ReplyControl_InsertEffect? insertEffect, + TranslationSwitch? translationSwitch, + $core.bool? showTranslation, }) { final result = create(); if (action != null) result.action = action; @@ -8198,6 +8200,8 @@ class ReplyControl extends $pb.GeneratedMessage { if (easterEggLabel != null) result.easterEggLabel = easterEggLabel; if (contextFeature != null) result.contextFeature = contextFeature; if (insertEffect != null) result.insertEffect = insertEffect; + if (translationSwitch != null) result.translationSwitch = translationSwitch; + if (showTranslation != null) result.showTranslation = showTranslation; return result; } @@ -8257,6 +8261,9 @@ class ReplyControl extends $pb.GeneratedMessage { ..aOS(35, _omitFieldNames ? '' : 'contextFeature') ..aOM(36, _omitFieldNames ? '' : 'insertEffect', subBuilder: ReplyControl_InsertEffect.create) + ..aE(37, _omitFieldNames ? '' : 'translationSwitch', + enumValues: TranslationSwitch.values) + ..aOB(100, _omitFieldNames ? '' : 'showTranslation') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -8604,6 +8611,25 @@ class ReplyControl extends $pb.GeneratedMessage { void clearInsertEffect() => $_clearField(36); @$pb.TagNumber(36) ReplyControl_InsertEffect ensureInsertEffect() => $_ensure(35); + + @$pb.TagNumber(37) + TranslationSwitch get translationSwitch => $_getN(36); + @$pb.TagNumber(37) + set translationSwitch(TranslationSwitch value) => $_setField(37, value); + @$pb.TagNumber(37) + $core.bool hasTranslationSwitch() => $_has(36); + @$pb.TagNumber(37) + void clearTranslationSwitch() => $_clearField(37); + + /// extra field + @$pb.TagNumber(100) + $core.bool get showTranslation => $_getBF(37); + @$pb.TagNumber(100) + set showTranslation($core.bool value) => $_setBool(37, value); + @$pb.TagNumber(100) + $core.bool hasShowTranslation() => $_has(37); + @$pb.TagNumber(100) + void clearShowTranslation() => $_clearField(100); } class ReplyExtra extends $pb.GeneratedMessage { @@ -9494,6 +9520,7 @@ class ReplyInfo extends $pb.GeneratedMessage { ReplyControl? replyControl, MemberV2? memberV2, $core.String? trackInfo, + Content? translatedContent, }) { final result = create(); if (replies != null) result.replies.addAll(replies); @@ -9512,6 +9539,7 @@ class ReplyInfo extends $pb.GeneratedMessage { if (replyControl != null) result.replyControl = replyControl; if (memberV2 != null) result.memberV2 = memberV2; if (trackInfo != null) result.trackInfo = trackInfo; + if (translatedContent != null) result.translatedContent = translatedContent; return result; } @@ -9550,6 +9578,8 @@ class ReplyInfo extends $pb.GeneratedMessage { ..aOM(15, _omitFieldNames ? '' : 'memberV2', subBuilder: MemberV2.create) ..aOS(16, _omitFieldNames ? '' : 'trackInfo') + ..aOM(17, _omitFieldNames ? '' : 'translatedContent', + subBuilder: Content.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -9715,6 +9745,17 @@ class ReplyInfo extends $pb.GeneratedMessage { $core.bool hasTrackInfo() => $_has(15); @$pb.TagNumber(16) void clearTrackInfo() => $_clearField(16); + + @$pb.TagNumber(17) + Content get translatedContent => $_getN(16); + @$pb.TagNumber(17) + set translatedContent(Content value) => $_setField(17, value); + @$pb.TagNumber(17) + $core.bool hasTranslatedContent() => $_has(16); + @$pb.TagNumber(17) + void clearTranslatedContent() => $_clearField(17); + @$pb.TagNumber(17) + Content ensureTranslatedContent() => $_ensure(16); } class ReplyInfoReply extends $pb.GeneratedMessage { @@ -13976,6 +14017,135 @@ class WordSearchParam extends $pb.GeneratedMessage { void clearShownCount() => $_clearField(1); } +class TranslateReplyReq extends $pb.GeneratedMessage { + factory TranslateReplyReq({ + $fixnum.Int64? type, + $fixnum.Int64? oid, + $core.Iterable<$fixnum.Int64>? rpids, + }) { + final result = create(); + if (type != null) result.type = type; + if (oid != null) result.oid = oid; + if (rpids != null) result.rpids.addAll(rpids); + return result; + } + + TranslateReplyReq._(); + + factory TranslateReplyReq.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TranslateReplyReq.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TranslateReplyReq', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'bilibili.main.community.reply.v1'), + createEmptyInstance: create) + ..aInt64(1, _omitFieldNames ? '' : 'type') + ..aInt64(2, _omitFieldNames ? '' : 'oid') + ..p<$fixnum.Int64>(3, _omitFieldNames ? '' : 'rpids', $pb.PbFieldType.K6) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyReq clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyReq copyWith(void Function(TranslateReplyReq) updates) => + super.copyWith((message) => updates(message as TranslateReplyReq)) + as TranslateReplyReq; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TranslateReplyReq create() => TranslateReplyReq._(); + @$core.override + TranslateReplyReq createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TranslateReplyReq getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TranslateReplyReq? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get type => $_getI64(0); + @$pb.TagNumber(1) + set type($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasType() => $_has(0); + @$pb.TagNumber(1) + void clearType() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get oid => $_getI64(1); + @$pb.TagNumber(2) + set oid($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasOid() => $_has(1); + @$pb.TagNumber(2) + void clearOid() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList<$fixnum.Int64> get rpids => $_getList(2); +} + +class TranslateReplyResp extends $pb.GeneratedMessage { + factory TranslateReplyResp({ + $core.Iterable<$core.MapEntry<$fixnum.Int64, ReplyInfo>>? translatedReplies, + }) { + final result = create(); + if (translatedReplies != null) + result.translatedReplies.addEntries(translatedReplies); + return result; + } + + TranslateReplyResp._(); + + factory TranslateReplyResp.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TranslateReplyResp.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TranslateReplyResp', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'bilibili.main.community.reply.v1'), + createEmptyInstance: create) + ..m<$fixnum.Int64, ReplyInfo>(1, _omitFieldNames ? '' : 'translatedReplies', + entryClassName: 'TranslateReplyResp.TranslatedRepliesEntry', + keyFieldType: $pb.PbFieldType.O6, + valueFieldType: $pb.PbFieldType.OM, + valueCreator: ReplyInfo.create, + valueDefaultOrMaker: ReplyInfo.getDefault, + packageName: const $pb.PackageName('bilibili.main.community.reply.v1')) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyResp clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyResp copyWith(void Function(TranslateReplyResp) updates) => + super.copyWith((message) => updates(message as TranslateReplyResp)) + as TranslateReplyResp; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TranslateReplyResp create() => TranslateReplyResp._(); + @$core.override + TranslateReplyResp createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TranslateReplyResp getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TranslateReplyResp? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbMap<$fixnum.Int64, ReplyInfo> get translatedReplies => $_getMap(0); +} + const $core.bool _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const $core.bool _omitMessageNames = diff --git a/lib/grpc/bilibili/main/community/reply/v1.pbenum.dart b/lib/grpc/bilibili/main/community/reply/v1.pbenum.dart index 5ba26c645b..ff13cfda9e 100644 --- a/lib/grpc/bilibili/main/community/reply/v1.pbenum.dart +++ b/lib/grpc/bilibili/main/community/reply/v1.pbenum.dart @@ -194,6 +194,35 @@ class UserCallbackScene extends $pb.ProtobufEnum { const UserCallbackScene._(super.value, super.name); } +class TranslationSwitch extends $pb.ProtobufEnum { + static const TranslationSwitch TRANSLATION_SWITCH_UNSPECIFIED = + TranslationSwitch._( + 0, _omitEnumNames ? '' : 'TRANSLATION_SWITCH_UNSPECIFIED'); + static const TranslationSwitch TRANSLATION_SWITCH_UNSUPPORTED = + TranslationSwitch._( + 1, _omitEnumNames ? '' : 'TRANSLATION_SWITCH_UNSUPPORTED'); + static const TranslationSwitch TRANSLATION_SWITCH_SHOW_TRANSLATION = + TranslationSwitch._( + 2, _omitEnumNames ? '' : 'TRANSLATION_SWITCH_SHOW_TRANSLATION'); + static const TranslationSwitch TRANSLATION_SWITCH_SHOW_ORIGIN = + TranslationSwitch._( + 3, _omitEnumNames ? '' : 'TRANSLATION_SWITCH_SHOW_ORIGIN'); + + static const $core.List values = [ + TRANSLATION_SWITCH_UNSPECIFIED, + TRANSLATION_SWITCH_UNSUPPORTED, + TRANSLATION_SWITCH_SHOW_TRANSLATION, + TRANSLATION_SWITCH_SHOW_ORIGIN, + ]; + + static final $core.List _byValue = + $pb.ProtobufEnum.$_initByValueList(values, 3); + static TranslationSwitch? valueOf($core.int value) => + value < 0 || value >= _byValue.length ? null : _byValue[value]; + + const TranslationSwitch._(super.value, super.name); +} + class AtGroup_Type extends $pb.ProtobufEnum { static const AtGroup_Type AT_GROUP_TYPE_DEFAULT = AtGroup_Type._(0, _omitEnumNames ? '' : 'AT_GROUP_TYPE_DEFAULT'); diff --git a/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart b/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart index 2391b32bfc..bb0a4608e0 100644 --- a/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart +++ b/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart @@ -133,6 +133,24 @@ final $typed_data.Uint8List userCallbackSceneDescriptor = $convert.base64Decode( 'ChFVc2VyQ2FsbGJhY2tTY2VuZRIcChhJbnNlcnRfVXNlckNhbGxiYWNrU2NlbmUQABIYChRSZW' 'NvbW1lbmRTdXBlcmJSZXBseRAB'); +@$core.Deprecated('Use translationSwitchDescriptor instead') +const TranslationSwitch$json = { + '1': 'TranslationSwitch', + '2': [ + {'1': 'TRANSLATION_SWITCH_UNSPECIFIED', '2': 0}, + {'1': 'TRANSLATION_SWITCH_UNSUPPORTED', '2': 1}, + {'1': 'TRANSLATION_SWITCH_SHOW_TRANSLATION', '2': 2}, + {'1': 'TRANSLATION_SWITCH_SHOW_ORIGIN', '2': 3}, + ], +}; + +/// Descriptor for `TranslationSwitch`. Decode as a `google.protobuf.EnumDescriptorProto`. +final $typed_data.Uint8List translationSwitchDescriptor = $convert.base64Decode( + 'ChFUcmFuc2xhdGlvblN3aXRjaBIiCh5UUkFOU0xBVElPTl9TV0lUQ0hfVU5TUEVDSUZJRUQQAB' + 'IiCh5UUkFOU0xBVElPTl9TV0lUQ0hfVU5TVVBQT1JURUQQARInCiNUUkFOU0xBVElPTl9TV0lU' + 'Q0hfU0hPV19UUkFOU0xBVElPThACEiIKHlRSQU5TTEFUSU9OX1NXSVRDSF9TSE9XX09SSUdJTh' + 'AD'); + @$core.Deprecated('Use activityDescriptor instead') const Activity$json = { '1': 'Activity', @@ -2857,6 +2875,21 @@ const ReplyControl$json = { '6': '.bilibili.main.community.reply.v1.ReplyControl.InsertEffect', '10': 'insertEffect' }, + { + '1': 'translation_switch', + '3': 37, + '4': 1, + '5': 14, + '6': '.bilibili.main.community.reply.v1.TranslationSwitch', + '10': 'translationSwitch' + }, + { + '1': 'show_translation', + '3': 100, + '4': 1, + '5': 8, + '10': 'showTranslation' + }, ], '3': [ ReplyControl_EasterEggLabel$json, @@ -2977,18 +3010,21 @@ final $typed_data.Uint8List replyControlDescriptor = $convert.base64Decode( 'aWxpLm1haW4uY29tbXVuaXR5LnJlcGx5LnYxLlJlcGx5Q29udHJvbC5FYXN0ZXJFZ2dMYWJlbF' 'IOZWFzdGVyRWdnTGFiZWwSJwoPY29udGV4dF9mZWF0dXJlGCMgASgJUg5jb250ZXh0RmVhdHVy' 'ZRJgCg1pbnNlcnRfZWZmZWN0GCQgASgLMjsuYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbH' - 'kudjEuUmVwbHlDb250cm9sLkluc2VydEVmZmVjdFIMaW5zZXJ0RWZmZWN0GkEKDkVhc3RlckVn' - 'Z0xhYmVsEhQKBWltYWdlGAEgASgJUgVpbWFnZRIZCghqdW1wX3VybBgCIAEoCVIHanVtcFVybB' - 'rXAQoLR3JhZGVSZWNvcmQSFAoFc2NvcmUYASABKAVSBXNjb3JlElUKBXRleHRzGAIgAygLMj8u' - 'YmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbHkudjEuUmVwbHlDb250cm9sLkdyYWRlUmVjb3' - 'JkLlRleHRSBXRleHRzGlsKBFRleHQSEAoDcmF3GAEgASgJUgNyYXcSQQoFc3R5bGUYAiABKAsy' - 'Ky5iaWxpYmlsaS5tYWluLmNvbW11bml0eS5yZXBseS52MS5UZXh0U3R5bGVSBXN0eWxlGjwKDE' - 'luc2VydEVmZmVjdBIYCgdjb250ZW50GAEgASgJUgdjb250ZW50EhIKBGljb24YAiABKAlSBGlj' - 'b24a8QEKClZvdGVPcHRpb24SYgoKbGFiZWxfa2luZBgBIAEoDjJDLmJpbGliaWxpLm1haW4uY2' - '9tbXVuaXR5LnJlcGx5LnYxLlJlcGx5Q29udHJvbC5Wb3RlT3B0aW9uLkxhYmVsS2luZFIJbGFi' - 'ZWxLaW5kEhIKBGRlc2MYAiABKAlSBGRlc2MSEAoDaWR4GAMgASgDUgNpZHgSFwoHdm90ZV9pZB' - 'gEIAEoA1IGdm90ZUlkIkAKCUxhYmVsS2luZBIVChFERUZBVUxUX0xhYmVsS2luZBAAEgcKA1JF' - 'RBABEggKBEJMVUUQAhIJCgVQTEFJThAD'); + 'kudjEuUmVwbHlDb250cm9sLkluc2VydEVmZmVjdFIMaW5zZXJ0RWZmZWN0EmIKEnRyYW5zbGF0' + 'aW9uX3N3aXRjaBglIAEoDjIzLmJpbGliaWxpLm1haW4uY29tbXVuaXR5LnJlcGx5LnYxLlRyYW' + '5zbGF0aW9uU3dpdGNoUhF0cmFuc2xhdGlvblN3aXRjaBIpChBzaG93X3RyYW5zbGF0aW9uGGQg' + 'ASgIUg9zaG93VHJhbnNsYXRpb24aQQoORWFzdGVyRWdnTGFiZWwSFAoFaW1hZ2UYASABKAlSBW' + 'ltYWdlEhkKCGp1bXBfdXJsGAIgASgJUgdqdW1wVXJsGtcBCgtHcmFkZVJlY29yZBIUCgVzY29y' + 'ZRgBIAEoBVIFc2NvcmUSVQoFdGV4dHMYAiADKAsyPy5iaWxpYmlsaS5tYWluLmNvbW11bml0eS' + '5yZXBseS52MS5SZXBseUNvbnRyb2wuR3JhZGVSZWNvcmQuVGV4dFIFdGV4dHMaWwoEVGV4dBIQ' + 'CgNyYXcYASABKAlSA3JhdxJBCgVzdHlsZRgCIAEoCzIrLmJpbGliaWxpLm1haW4uY29tbXVuaX' + 'R5LnJlcGx5LnYxLlRleHRTdHlsZVIFc3R5bGUaPAoMSW5zZXJ0RWZmZWN0EhgKB2NvbnRlbnQY' + 'ASABKAlSB2NvbnRlbnQSEgoEaWNvbhgCIAEoCVIEaWNvbhrxAQoKVm90ZU9wdGlvbhJiCgpsYW' + 'JlbF9raW5kGAEgASgOMkMuYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbHkudjEuUmVwbHlD' + 'b250cm9sLlZvdGVPcHRpb24uTGFiZWxLaW5kUglsYWJlbEtpbmQSEgoEZGVzYxgCIAEoCVIEZG' + 'VzYxIQCgNpZHgYAyABKANSA2lkeBIXCgd2b3RlX2lkGAQgASgDUgZ2b3RlSWQiQAoJTGFiZWxL' + 'aW5kEhUKEURFRkFVTFRfTGFiZWxLaW5kEAASBwoDUkVEEAESCAoEQkxVRRACEgkKBVBMQUlOEA' + 'M='); @$core.Deprecated('Use replyExtraDescriptor instead') const ReplyExtra$json = { @@ -3311,6 +3347,14 @@ const ReplyInfo$json = { '10': 'memberV2' }, {'1': 'track_info', '3': 16, '4': 1, '5': 9, '10': 'trackInfo'}, + { + '1': 'translated_content', + '3': 17, + '4': 1, + '5': 11, + '6': '.bilibili.main.community.reply.v1.Content', + '10': 'translatedContent' + }, ], }; @@ -3326,8 +3370,9 @@ final $typed_data.Uint8List replyInfoDescriptor = $convert.base64Decode( 'NvbW11bml0eS5yZXBseS52MS5NZW1iZXJSBm1lbWJlchJTCg1yZXBseV9jb250cm9sGA4gASgL' 'Mi4uYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbHkudjEuUmVwbHlDb250cm9sUgxyZXBseU' 'NvbnRyb2wSRwoJbWVtYmVyX3YyGA8gASgLMiouYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVw' - 'bHkudjEuTWVtYmVyVjJSCG1lbWJlclYyEh0KCnRyYWNrX2luZm8YECABKAlSCXRyYWNrSW5mbw' - '=='); + 'bHkudjEuTWVtYmVyVjJSCG1lbWJlclYyEh0KCnRyYWNrX2luZm8YECABKAlSCXRyYWNrSW5mbx' + 'JYChJ0cmFuc2xhdGVkX2NvbnRlbnQYESABKAsyKS5iaWxpYmlsaS5tYWluLmNvbW11bml0eS5y' + 'ZXBseS52MS5Db250ZW50UhF0cmFuc2xhdGVkQ29udGVudA=='); @$core.Deprecated('Use replyInfoReplyDescriptor instead') const ReplyInfoReply$json = { @@ -4561,3 +4606,60 @@ const WordSearchParam$json = { /// Descriptor for `WordSearchParam`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List wordSearchParamDescriptor = $convert.base64Decode( 'Cg9Xb3JkU2VhcmNoUGFyYW0SHwoLc2hvd25fY291bnQYASABKANSCnNob3duQ291bnQ='); + +@$core.Deprecated('Use translateReplyReqDescriptor instead') +const TranslateReplyReq$json = { + '1': 'TranslateReplyReq', + '2': [ + {'1': 'type', '3': 1, '4': 1, '5': 3, '10': 'type'}, + {'1': 'oid', '3': 2, '4': 1, '5': 3, '10': 'oid'}, + {'1': 'rpids', '3': 3, '4': 3, '5': 3, '10': 'rpids'}, + ], +}; + +/// Descriptor for `TranslateReplyReq`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List translateReplyReqDescriptor = $convert.base64Decode( + 'ChFUcmFuc2xhdGVSZXBseVJlcRISCgR0eXBlGAEgASgDUgR0eXBlEhAKA29pZBgCIAEoA1IDb2' + 'lkEhQKBXJwaWRzGAMgAygDUgVycGlkcw=='); + +@$core.Deprecated('Use translateReplyRespDescriptor instead') +const TranslateReplyResp$json = { + '1': 'TranslateReplyResp', + '2': [ + { + '1': 'translated_replies', + '3': 1, + '4': 3, + '5': 11, + '6': + '.bilibili.main.community.reply.v1.TranslateReplyResp.TranslatedRepliesEntry', + '10': 'translatedReplies' + }, + ], + '3': [TranslateReplyResp_TranslatedRepliesEntry$json], +}; + +@$core.Deprecated('Use translateReplyRespDescriptor instead') +const TranslateReplyResp_TranslatedRepliesEntry$json = { + '1': 'TranslatedRepliesEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 3, '10': 'key'}, + { + '1': 'value', + '3': 2, + '4': 1, + '5': 11, + '6': '.bilibili.main.community.reply.v1.ReplyInfo', + '10': 'value' + }, + ], + '7': {'7': true}, +}; + +/// Descriptor for `TranslateReplyResp`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List translateReplyRespDescriptor = $convert.base64Decode( + 'ChJUcmFuc2xhdGVSZXBseVJlc3ASegoSdHJhbnNsYXRlZF9yZXBsaWVzGAEgAygLMksuYmlsaW' + 'JpbGkubWFpbi5jb21tdW5pdHkucmVwbHkudjEuVHJhbnNsYXRlUmVwbHlSZXNwLlRyYW5zbGF0' + 'ZWRSZXBsaWVzRW50cnlSEXRyYW5zbGF0ZWRSZXBsaWVzGnEKFlRyYW5zbGF0ZWRSZXBsaWVzRW' + '50cnkSEAoDa2V5GAEgASgDUgNrZXkSQQoFdmFsdWUYAiABKAsyKy5iaWxpYmlsaS5tYWluLmNv' + 'bW11bml0eS5yZXBseS52MS5SZXBseUluZm9SBXZhbHVlOgI4AQ=='); diff --git a/lib/grpc/dm.dart b/lib/grpc/dm.dart index 5c5c1f4477..74ffb7bdc7 100644 --- a/lib/grpc/dm.dart +++ b/lib/grpc/dm.dart @@ -21,4 +21,12 @@ abstract final class DmGrpc { isolate: true, ); } + + static Future> dmView(int aid, int cid) { + return GrpcReq.request( + GrpcUrl.dmView, + DmViewReq(pid: Int64(aid), oid: Int64(cid), type: 1), + DmViewReply.fromBuffer, + ); + } } diff --git a/lib/grpc/reply.dart b/lib/grpc/reply.dart index de680c6efc..f399eb4f33 100644 --- a/lib/grpc/reply.dart +++ b/lib/grpc/reply.dart @@ -148,4 +148,20 @@ abstract final class ReplyGrpc { SearchItemReply.fromBuffer, ); } + + static Future> translateReply({ + required Int64 type, + required Int64 oid, + required Int64 rpid, + }) { + return GrpcReq.request( + GrpcUrl.translateReply, + TranslateReplyReq( + type: type, + oid: oid, + rpids: [rpid], + ), + TranslateReplyResp.fromBuffer, + ); + } } diff --git a/lib/grpc/space.dart b/lib/grpc/space.dart index e81ae17af1..683c43b16c 100644 --- a/lib/grpc/space.dart +++ b/lib/grpc/space.dart @@ -1,4 +1,6 @@ import 'package:PiliPlus/grpc/bilibili/app/dynamic/v2.pb.dart'; +import 'package:PiliPlus/grpc/bilibili/app/interfaces/v1.pb.dart' + show SearchArchiveReply, SearchArchiveReq; import 'package:PiliPlus/grpc/bilibili/pagination.pb.dart'; import 'package:PiliPlus/grpc/grpc_req.dart'; import 'package:PiliPlus/grpc/url.dart'; @@ -15,13 +17,28 @@ abstract final class SpaceGrpc { GrpcUrl.opusSpaceFlow, OpusSpaceFlowReq( hostMid: Int64(hostMid), - pagination: Pagination( - pageSize: 20, - next: next, - ), + pagination: Pagination(pageSize: 20, next: next), filterType: filterType, ), OpusSpaceFlowResp.fromBuffer, ); } + + static Future> searchArchive({ + required String keyword, + required Int64 mid, + required int pn, + required Int64 ps, + }) { + return GrpcReq.request( + GrpcUrl.searchArchive, + SearchArchiveReq( + keyword: keyword, + mid: mid, + pn: Int64(pn), + ps: ps, + ), + SearchArchiveReply.fromBuffer, + ); + } } diff --git a/lib/grpc/url.dart b/lib/grpc/url.dart index 0bbdc6b3b3..78238ef021 100644 --- a/lib/grpc/url.dart +++ b/lib/grpc/url.dart @@ -14,6 +14,7 @@ abstract final class GrpcUrl { // danmaku static const dmSegMobile = '/bilibili.community.service.dm.v1.DM/DmSegMobile'; + static const dmView = '/bilibili.community.service.dm.v1.DM/DmView'; // reply static const reply = '/bilibili.main.community.reply.v1.Reply'; @@ -22,6 +23,7 @@ abstract final class GrpcUrl { static const dialogList = '$reply/DialogList'; // static const replyInfo = '$reply/ReplyInfo'; static const searchItem = '$reply/SearchItem'; + static const translateReply = '$reply/TranslateReply'; // im static const im = '/bilibili.im.interface.v1.ImInterface'; @@ -54,4 +56,8 @@ abstract final class GrpcUrl { static const audioThumbUp = '$audio/ThumbUp'; static const audioTripleLike = '$audio/TripleLike'; static const audioCoinAdd = '$audio/CoinAdd'; + + // space + static const space = '/bilibili.app.interface.v1.Space'; + static const searchArchive = '$space/SearchArchive'; } diff --git a/lib/grpc/view.dart b/lib/grpc/view.dart index e8de15f062..c0392d2205 100644 --- a/lib/grpc/view.dart +++ b/lib/grpc/view.dart @@ -10,9 +10,7 @@ abstract final class ViewGrpc { }) { return GrpcReq.request( GrpcUrl.view, - ViewReq( - bvid: bvid, - ), + ViewReq(bvid: bvid), ViewReply.fromBuffer, ); } diff --git a/lib/harmony_adapt/continuation.dart b/lib/harmony_adapt/continuation.dart index 6638efb9ed..80c1e614b9 100644 --- a/lib/harmony_adapt/continuation.dart +++ b/lib/harmony_adapt/continuation.dart @@ -50,7 +50,7 @@ abstract class HarmonyContinuation { 'itemType': c.itemType, 'from': c.from.value, 'extraId': c.extraId?.toInt(), - 'progress': c.position.value.inMilliseconds, + 'progress': c.position.value, 'playing': c.isPlaying(), }; diff --git a/lib/harmony_adapt/shell_bars_observer.dart b/lib/harmony_adapt/shell_bars_observer.dart index 6a392cb815..cfff72f377 100644 --- a/lib/harmony_adapt/shell_bars_observer.dart +++ b/lib/harmony_adapt/shell_bars_observer.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; /// 路由栈深度 > 1 时(有页面覆盖在主页之上)隐藏底栏,回到栈底时恢复。 class ShellBarsObserver extends NavigatorObserver { final Set> _activeRoutes = {}; + bool _orientationHidden = false; @override void didPush(Route route, Route? previousRoute) { @@ -15,12 +16,19 @@ class ShellBarsObserver extends NavigatorObserver { @override void didPop(Route route, Route? previousRoute) { _activeRoutes.remove(route); + // 回到主页时清除方向隐藏标记,由 _sync 决定最终状态 + if (_activeRoutes.length <= 1) { + _orientationHidden = false; + } _sync(); } @override void didRemove(Route route, Route? previousRoute) { _activeRoutes.remove(route); + if (_activeRoutes.length <= 1) { + _orientationHidden = false; + } _sync(); } @@ -31,7 +39,16 @@ class ShellBarsObserver extends NavigatorObserver { _sync(); } + /// 由 didChangeDependencies 调用:横屏时隐藏底栏(仅在主页时生效) + void onOrientationChanged(bool isPortrait) { + if (_activeRoutes.length > 1) return; // 有子页面时由路由控制 + _orientationHidden = !isPortrait; + _sync(); + } + void _sync() { - HarmonyChannel.setShellBarsHidden(_activeRoutes.length > 1); + HarmonyChannel.setShellBarsHidden( + _activeRoutes.length > 1 || _orientationHidden, + ); } } diff --git a/lib/http/api.dart b/lib/http/api.dart index 33d1f0b9fc..0d5c735dfd 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -411,7 +411,7 @@ abstract final class Api { // github 获取最新版 static const String latestApp = - 'https://api.github.com/repos/qinshah/PiliPlus/releases'; + 'https://api.github.com/repos/dev4harmony/PiliPlus/releases'; // 多少人在看 // https://api.bilibili.com/x/player/online/total?aid=913663681&cid=1203559746&bvid=BV1MM4y1s7NZ&ts=56427838 @@ -810,6 +810,8 @@ abstract final class Api { static const String topicFeed = '/x/polymer/web-dynamic/v1/feed/topic'; + static const String topicFold = '/x/topic/web/details/fold'; + static const String spaceOpus = '/x/polymer/web-dynamic/v1/opus/feed/space'; static const String articleList = '/x/article/list/web/articles'; @@ -830,6 +832,10 @@ abstract final class Api { static const String dynReserve = '/x/dynamic/feed/reserve/click'; + static const String spaceReserve = '/x/space/reserve'; + + static const String spaceReserveCancel = '/x/space/reserve/cancel'; + static const String favPugv = '/pugv/app/web/favorite/page'; static const String addFavPugv = '/pugv/app/web/favorite/add'; @@ -997,4 +1003,15 @@ abstract final class Api { static const String liveMedalWall = '${HttpString.liveBaseUrl}/xlive/web-ucenter/user/MedalWall'; + + static const String memberGuard = + '${HttpString.liveBaseUrl}/xlive/app-ucenter/v1/guard/MainGuardCardAll'; + + static const String bubble = '/x/tribee/v1/dyn/all'; + + static const String sortFollowTag = '/x/relation/tags/update_sort'; + + static const String replyReport = '/x/v2/reply/report'; + + static const String dynReaction = '/x/polymer/web-dynamic/v1/detail/reaction'; } diff --git a/lib/http/download.dart b/lib/http/download.dart index c05093cb5e..9c14f50f28 100644 --- a/lib/http/download.dart +++ b/lib/http/download.dart @@ -2,7 +2,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/models/common/video/audio_quality.dart'; -import 'package:PiliPlus/models/common/video/video_decode_type.dart'; import 'package:PiliPlus/models/common/video/video_quality.dart'; import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/models/video/play/url.dart'; @@ -12,6 +11,7 @@ import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/video_utils.dart'; +import 'package:collection/collection.dart'; abstract final class DownloadHttp { static const String referer = "https://www.bilibili.com/"; @@ -39,9 +39,9 @@ abstract final class DownloadHttp { }, ); if (res case Success(:final response)) { - final Dash? dash = response.dash; + final dash = response.dash; if (dash != null) { - final List videoList = dash.video!; + final videoList = dash.video!; final curHighestVideoQa = videoList.first.quality.code; final preferVideoQa = entry.preferedVideoQuality; int targetVideoQa = curHighestVideoQa; @@ -54,19 +54,18 @@ abstract final class DownloadHttp { ); } - /// 取出符合当前画质的videoList - final List videosList = videoList - .where((e) => e.quality.code == targetVideoQa) - .toList(); - /// 优先顺序 设置中指定解码格式 -> 当前可选的首个解码格式 - final List supportFormats = response.supportFormats!; + final supportFormats = response.supportFormats!; // 根据画质选编码格式 - final FormatItem targetSupportFormats = supportFormats.firstWhere( + final targetSupportFormats = supportFormats.firstWhere( (e) => e.quality == targetVideoQa, orElse: () => supportFormats.first, ); - final List supportDecodeFormats = targetSupportFormats.codecs!; + + final currentDecodeFormats = VideoUtils.selectCodec( + targetSupportFormats.codecs!, + Pref.preferCodecs, + ); entry ..typeTag = targetVideoQa.toString() @@ -76,31 +75,10 @@ abstract final class DownloadHttp { targetSupportFormats.newDesc ?? VideoQuality.fromCode(targetVideoQa).desc; - String preferDecode = Pref.defaultDecode; // def avc - String preferSecondDecode = Pref.secondDecode; // def av1 - - // 默认从设置中取AV1 - VideoDecodeFormatType currentDecodeFormats = - VideoDecodeFormatType.fromString(preferDecode); - VideoDecodeFormatType secondDecodeFormats = - VideoDecodeFormatType.fromString(preferSecondDecode); - // 当前视频没有对应格式返回第一个 - int flag = 0; - for (final e in supportDecodeFormats) { - if (currentDecodeFormats.codes.any(e.startsWith)) { - flag = 1; - break; - } else if (secondDecodeFormats.codes.any(e.startsWith)) { - flag = 2; - } - } - if (flag == 2) { - currentDecodeFormats = secondDecodeFormats; - } else if (flag == 0) { - currentDecodeFormats = VideoDecodeFormatType.fromString( - supportDecodeFormats.first, - ); - } + /// 取出符合当前画质的videoList + final videosList = videoList + .where((e) => e.quality.code == targetVideoQa) + .toList(); /// 取出符合当前解码格式的videoItem final videoDash = videosList.firstWhere( diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index fac9b1aebf..bd19b516a4 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/api.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/reply.dart'; @@ -15,8 +16,10 @@ import 'package:PiliPlus/models/dynamics/vote_model.dart'; import 'package:PiliPlus/models_new/article/article_info/data.dart'; import 'package:PiliPlus/models_new/article/article_list/data.dart'; import 'package:PiliPlus/models_new/article/article_view/data.dart'; +import 'package:PiliPlus/models_new/bubble/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_mention/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_mention/group.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_reaction/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_reserve/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_reserve_info/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/topic_card_list.dart'; @@ -31,19 +34,14 @@ import 'package:dio/dio.dart'; abstract final class DynamicsHttp { @pragma('vm:notify-debugger-on-exception') static Future> followDynamic({ - DynamicsTabType type = DynamicsTabType.all, + int? hostMid, String? offset, - int? mid, Set? tempBannedList, + DynamicsTabType type = .all, }) async { Map data = { - if (type == DynamicsTabType.up) - 'host_mid': mid - else ...{ - 'type': type.name, - 'timezone_offset': '-480', - }, - 'offset': offset, + if (type == .up) 'host_mid': hostMid else 'type': type.name, + 'offset': ?offset, 'features': Constants.dynFeatures, }; final res = await Request().get(Api.followDynamic, queryParameters: data); @@ -56,10 +54,10 @@ abstract final class DynamicsHttp { tempBannedList: tempBannedList, ); if (data.loadNext == true) { - return followDynamic( + return await followDynamic( type: type, offset: data.offset, - mid: mid, + hostMid: hostMid, tempBannedList: tempBannedList, ); } @@ -87,22 +85,45 @@ abstract final class DynamicsHttp { } } - static Future> dynUpList(String? offset) async { + static Future> dynUpList(String? offset) async { final res = await Request().get( Api.dynUplist, queryParameters: { - 'offset': offset, + 'offset': ?offset, 'platform': 'web', 'web_location': 333.1365, }, ); if (res.data['code'] == 0) { - return Success(DynUpList.fromJson(res.data['data'])); + return Success(FollowUpModel.fromUpList(res.data['data'])); } else { return Error(res.data['message']); } } + static Future> followings({ + int? vmid, + int? pn, + int ps = 20, + String orderType = '', // ''=>最近关注,'attention'=>最常访问 + }) async { + final res = await Request().get( + Api.followings, + queryParameters: { + 'vmid': vmid, + 'pn': pn, + 'ps': ps, + 'order': 'desc', + 'order_type': orderType, + }, + ); + if (res.data['code'] == 0) { + return Success(FollowUpModel.fromFollowList(res.data['data'])); + } else { + return Error(errorMsg[res.data['code']] ?? res.data['message']); + } + } + // 动态点赞 // static Future likeDynamic({ // required String? dynamicId, @@ -374,7 +395,10 @@ abstract final class DynamicsHttp { queryParameters: {'vote_id': voteId}, ); if (res.data['code'] == 0) { - return Success(VoteInfo.fromSeparatedJson(res.data['data'])); + final voteInfo = VoteInfo.fromSeparatedJson(res.data['data']); + return voteInfo.voteId == null + ? const Error('无效的投票id') + : Success(voteInfo); } else { return Error(res.data['message']); } @@ -432,7 +456,7 @@ abstract final class DynamicsHttp { static Future> topicFeed({ required Object topicId, - required String offset, + String? offset, required int sortBy, }) async { final res = await Request().get( @@ -440,17 +464,42 @@ abstract final class DynamicsHttp { queryParameters: { 'topic_id': topicId, 'sort_by': sortBy, - 'offset': offset, + 'offset': ?offset, 'page_size': 20, 'source': 'Web', 'features': Constants.dynFeatures, }, ); if (res.data['code'] == 0) { - TopicCardList? data = res.data['data']?['topic_card_list'] == null - ? null - : TopicCardList.fromJson(res.data['data']['topic_card_list']); - return Success(data); + final list = res.data['data']?['topic_card_list']; + if (list == null) { + return const Success(null); + } else { + return Success(TopicCardList.fromJson(list)); + } + } else { + return Error(res.data['message']); + } + } + + static Future> topicFold({ + required Object topicId, + required int sortBy, + }) async { + final res = await Request().get( + Api.topicFold, + queryParameters: { + 'topic_id': topicId, + 'sort_by': sortBy, + }, + ); + if (res.data['code'] == 0) { + final list = res.data['data']?['topic_card_list']; + if (list == null) { + return const Success(null); + } else { + return Success(TopicCardList.fromJson(list)); + } } else { return Error(res.data['message']); } @@ -777,4 +826,49 @@ abstract final class DynamicsHttp { return Error(res.data['message']); } } + + static Future> bubble({ + required Object tribeId, + Object? categoryId, + int? sortType, + required int page, + }) async { + final res = await Request().get( + Api.bubble, + queryParameters: { + 'tribee_id': tribeId, + 'category_id': ?categoryId, + 'sort_type': ?sortType, + 'page_size': 20, + 'page_num': page, + 'web_location': 333.40165, + 'x-bili-device-req-json': + '{"platform":"web","device":"pc","spmid":"333.40165"}', + }, + ); + if (res.data['code'] == 0) { + return Success(BubbleData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } + + static Future> dynReaction({ + required Object id, + String? offset, + }) async { + final res = await Request().get( + Api.dynReaction, + queryParameters: { + 'id': id, + 'offset': ?offset, + 'web_location': 333.1369, + }, + ); + if (res.data['code'] == 0) { + return Success(DynReactionData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/http/follow.dart b/lib/http/follow.dart index d3ed1c715c..bd19e2fc49 100644 --- a/lib/http/follow.dart +++ b/lib/http/follow.dart @@ -3,6 +3,8 @@ import 'package:PiliPlus/http/error_msg.dart'; import 'package:PiliPlus/http/init.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/follow/data.dart'; +import 'package:PiliPlus/utils/accounts.dart'; +import 'package:dio/dio.dart' show Options, Headers; abstract final class FollowHttp { static Future> followings({ @@ -27,4 +29,26 @@ abstract final class FollowHttp { return Error(errorMsg[res.data['code']] ?? res.data['message']); } } + + static Future> sortFollowTag({ + required String tagids, + }) async { + final res = await Request().post( + Api.sortFollowTag, + queryParameters: { + 'x-bili-device-req-json': + '{"platform":"web","device":"pc","spmid":"333.1387"}', + }, + data: { + 'tagids': tagids, + 'csrf': Accounts.main.csrf, + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return const Success(null); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/http/init.dart b/lib/http/init.dart index 9537e2ca9d..c94ed06f22 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -16,6 +16,7 @@ import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:archive/archive.dart'; import 'package:brotli/brotli.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:dio_http2_adapter/dio_http2_adapter.dart'; @@ -115,28 +116,25 @@ class Request { return h11; } - /* - * config it and create - */ - Request._internal() { - //BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 - BaseOptions options = BaseOptions( - //请求基地址,可以包含子路径 - baseUrl: HttpString.apiBaseUrl, - //连接服务器超时时间,单位是毫秒. - connectTimeout: const Duration(milliseconds: 10000), - //响应流上前后两次接受到数据的间隔,单位为毫秒。 - receiveTimeout: const Duration(milliseconds: 10000), - //Http请求头. - headers: { - 'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头 - if (!_enableHttp2) 'connection': 'keep-alive', - 'accept-encoding': 'br,gzip', - }, - responseDecoder: _responseDecoder, // Http2Adapter没有自动解压 - persistentConnection: true, + static Timer? _networkChangeDebounce; + + // connectivity_plus 5.x 回调单值(鸿蒙适配版本),非上游 7.x 的 List + static void _onConnectivityChanged(ConnectivityResult result) { + if (result == ConnectivityResult.none) { + return; + } + _networkChangeDebounce?.cancel(); + _networkChangeDebounce = Timer( + const Duration(milliseconds: 500), + _resetAdaptersForNetworkChange, ); + } + + static void _watchConnectivity() { + Connectivity().onConnectivityChanged.skip(1).listen(_onConnectivityChanged); + } + static (IOHttpClientAdapter, ConnectionManager?) _createPool() { final bool enableSystemProxy; late final String systemProxyHost; late final int? systemProxyPort; @@ -160,30 +158,72 @@ class Request { ..autoUncompress = false, // Http2Adapter没有自动解压, 统一行为 ); + final connectionManager = _enableHttp2 + ? ConnectionManager( + idleTimeout: const Duration(seconds: 15), + onClientCreate: enableSystemProxy + ? (_, config) => config + ..proxy = Uri( + scheme: 'http', + host: systemProxyHost, + port: systemProxyPort, + ) + ..onBadCertificate = (_) => true + : Pref.badCertificateCallback + ? (_, config) => config.onBadCertificate = (_) => true + : null, + ) + : null; + return (http11Adapter, connectionManager); + } + + @pragma('vm:notify-debugger-on-exception') + static void _resetAdaptersForNetworkChange() { + try { + final (h11, connectionManager) = _createPool(); + if (connectionManager != null) { + (dio.httpClientAdapter as Http2Adapter) + ..connectionManager.close(force: true) + ..connectionManager = connectionManager + ..fallbackAdapter.close(force: true) + ..fallbackAdapter = h11; + _http11Dio?.httpClientAdapter = h11; + } else { + dio + ..httpClientAdapter.close(force: true) + ..httpClientAdapter = h11; + } + } catch (_) {} + } + + /* + * config it and create + */ + Request._internal() { + //BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数 + BaseOptions options = BaseOptions( + //请求基地址,可以包含子路径 + baseUrl: HttpString.apiBaseUrl, + //连接服务器超时时间,单位是毫秒. + connectTimeout: const Duration(milliseconds: 10000), + //响应流上前后两次接受到数据的间隔,单位为毫秒。 + receiveTimeout: const Duration(milliseconds: 10000), + //Http请求头. + headers: { + 'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头 + if (!_enableHttp2) 'connection': 'keep-alive', + 'accept-encoding': 'br,gzip', + }, + responseDecoder: _responseDecoder, // Http2Adapter没有自动解压 + persistentConnection: true, + ); + + final (h11, connectionManager) = _createPool(); + dio = Dio(options) ..httpClientAdapter = _enableHttp2 - ? Http2Adapter( - ConnectionManager( - idleTimeout: const Duration(seconds: 15), - onClientCreate: enableSystemProxy - ? (_, config) { - config - ..proxy = Uri( - scheme: 'http', - host: systemProxyHost, - port: systemProxyPort, - ) - ..onBadCertificate = (_) => true; - } - : Pref.badCertificateCallback - ? (_, config) { - config.onBadCertificate = (_) => true; - } - : null, - ), - fallbackAdapter: http11Adapter, - ) - : http11Adapter; + ? Http2Adapter(connectionManager, fallbackAdapter: h11) + : h11; // 先于其他Interceptor if (Pref.retryCount != 0) { @@ -208,6 +248,8 @@ class Request { ..options.validateStatus = (int? status) { return status != null && status >= 200 && status < 300; }; + + if (Platform.isIOS) _watchConnectivity(); } /* diff --git a/lib/http/live.dart b/lib/http/live.dart index c7b0a154fb..ffb07aa5d7 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -102,7 +102,11 @@ abstract final class LiveHttp { }), ); if (res.data['code'] == 0) { - return Success(RoomPlayInfoData.fromJson(res.data['data'])); + try { + return Success(RoomPlayInfoData.fromJson(res.data['data'])); + } catch (e) { + return Error(e.toString()); + } } else { return Error(res.data['message']); } @@ -163,7 +167,11 @@ abstract final class LiveHttp { }), ); if (res.data['code'] == 0) { - return Success(LiveDmInfoData.fromJson(res.data['data'])); + try { + return Success(LiveDmInfoData.fromJson(res.data['data'])); + } catch (e) { + return Error(e.toString()); + } } else { return Error(res.data['message']); } diff --git a/lib/http/member.dart b/lib/http/member.dart index df8ae83ce1..08d40f6392 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -21,6 +21,7 @@ import 'package:PiliPlus/models_new/member/coin_like_arc/data.dart'; import 'package:PiliPlus/models_new/member/search_archive/data.dart'; import 'package:PiliPlus/models_new/member/season_web/data.dart'; import 'package:PiliPlus/models_new/member_card_info/data.dart'; +import 'package:PiliPlus/models_new/member_guard/data.dart'; import 'package:PiliPlus/models_new/space/space/data.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; import 'package:PiliPlus/models_new/space/space_article/data.dart'; @@ -470,7 +471,7 @@ abstract final class MemberHttp { try { DynamicsDataModel data = DynamicsDataModel.fromJson(res.data['data']); if (data.loadNext == true) { - return memberDynamic(offset: data.offset, mid: mid); + return await memberDynamic(offset: data.offset, mid: mid); } return Success(data); } catch (e, s) { @@ -592,7 +593,7 @@ abstract final class MemberHttp { } } - static Future> createFollowTag(Object tagName) async { + static Future> createFollowTag(String tagName) async { final res = await Request().post( Api.createFollowTag, queryParameters: { @@ -606,7 +607,7 @@ abstract final class MemberHttp { options: Options(contentType: Headers.formUrlEncodedContentType), ); if (res.data['code'] == 0) { - return const Success(null); + return Success(res.data['data']['tagid']); } else { return Error(res.data['message']); } @@ -830,4 +831,23 @@ abstract final class MemberHttp { return Error(res.data['message']); } } + + static Future> memberGuard({ + required Object ruid, + required int page, + }) async { + final res = await Request().get( + Api.memberGuard, + queryParameters: { + 'page': page, + 'page_size': 20, + 'ruid': ruid, + }, + ); + if (res.data['code'] == 0) { + return Success(MemberGuardData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/http/reply.dart b/lib/http/reply.dart index db9bc10096..a96d81a09a 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -186,7 +186,7 @@ abstract final class ReplyHttp { String? reasonDesc, }) async { final res = await Request().post( - '/x/v2/reply/report', + Api.replyReport, data: { 'add_blacklist': banUid, 'csrf': Accounts.main.csrf, diff --git a/lib/http/search.dart b/lib/http/search.dart index c458050ebc..6463224655 100644 --- a/lib/http/search.dart +++ b/lib/http/search.dart @@ -10,6 +10,8 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/data.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart'; import 'package:PiliPlus/models_new/search/search_rcmd/data.dart'; import 'package:PiliPlus/models_new/search/search_trending/data.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; +import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/wbi_sign.dart'; import 'package:dio/dio.dart'; @@ -172,6 +174,14 @@ abstract final class SearchHttp { } static Future ab2c({dynamic aid, dynamic bvid, int? part}) async { + return (await ab2cWithDimension(aid: aid, bvid: bvid, part: part))?.cid; + } + + static Future<({int? cid, Dimension? dimension})?> ab2cWithDimension({ + dynamic aid, + dynamic bvid, + int? part, + }) async { final res = await Request().get( Api.ab2c, queryParameters: { @@ -181,13 +191,19 @@ abstract final class SearchHttp { ); if (res.data['code'] == 0) { if (res.data['data'] case List list) { - return part != null - ? (list.elementAtOrNull(part - 1)?['cid'] ?? - list.firstOrNull?['cid']) - : list.firstOrNull?['cid']; - } else { - return null; + final target = part != null + ? (list.getOrNull(part - 1) ?? list.firstOrNull) + : list.firstOrNull; + if (target != null) { + return ( + cid: target['cid'] as int?, + dimension: target['dimension'] == null + ? null + : Dimension.fromJson(target['dimension']), + ); + } } + return null; } else { SmartDialog.showToast("ab2c error: ${res.data['message']}"); return null; @@ -246,6 +262,7 @@ abstract final class SearchHttp { static Future> searchTrending({ int limit = 30, + bool needsTop = false, }) async { final res = await Request().get( Api.searchTrending, @@ -254,7 +271,9 @@ abstract final class SearchHttp { }, ); if (res.data['code'] == 0) { - return Success(SearchTrendingData.fromJson(res.data['data'])); + return Success( + SearchTrendingData.fromJson(res.data['data'], needsTop: needsTop), + ); } else { return Error(res.data['message']); } diff --git a/lib/http/sponsor_block.dart b/lib/http/sponsor_block.dart index 553f2f051e..0dfaa69b60 100644 --- a/lib/http/sponsor_block.dart +++ b/lib/http/sponsor_block.dart @@ -79,7 +79,7 @@ abstract final class SponsorBlock { int? type, SegmentType? category, }) async { - assert((type == null) == (category == null)); + assert((type == null) != (category == null)); final res = await Request().post( _api(SponsorBlockApi.voteOnSponsorTime), queryParameters: { diff --git a/lib/http/user.dart b/lib/http/user.dart index d8d76743a7..a5110789ca 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -431,7 +431,9 @@ abstract final class UserHttp { } } - static Future> spaceSettingMod(Map data) async { + static Future> spaceSettingMod( + Map data, + ) async { final res = await Request().post( Api.spaceSettingMod, queryParameters: { @@ -569,4 +571,23 @@ abstract final class UserHttp { return Error(res.data['message']); } } + + static Future> spaceReserve({ + required Object sid, + required bool isFollow, + }) async { + final res = await Request().post( + isFollow ? Api.spaceReserveCancel : Api.spaceReserve, + data: { + 'sid': sid, + 'csrf': Accounts.main.csrf, + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return const Success(null); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/http/video.dart b/lib/http/video.dart index 737806c8ec..82978c7395 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -37,6 +37,7 @@ import 'package:PiliPlus/utils/recommend_filter.dart'; import 'package:PiliPlus/utils/request_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; +import 'package:PiliPlus/utils/subtitle_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/wbi_sign.dart'; import 'package:dio/dio.dart'; @@ -208,7 +209,10 @@ abstract final class VideoHttp { required bool tryLook, required VideoType videoType, String? language, + bool voiceBalance = false, }) async { + final dmImgStr = Utils.base64EncodeRandomString(16, 64); + final dmCoverImgStr = Utils.base64EncodeRandomString(32, 128); final params = await WbiSign.makSign({ 'avid': ?avid, 'bvid': ?bvid, @@ -220,12 +224,16 @@ abstract final class VideoHttp { 'fnval': 4048, 'fourk': 1, 'fnver': 0, - 'voice_balance': 1, + 'voice_balance': voiceBalance ? 1 : 0, 'gaia_source': 'pre-load', 'isGaiaAvoided': true, 'web_location': 1315873, // 免登录查看1080p if (tryLook) 'try_look': 1, + 'dm_img_list': '[]', + 'dm_img_str': dmImgStr, + 'dm_cover_img_str': dmCoverImgStr, + 'dm_img_inter': '{"ds":[],"wh":[0,0,0],"of":[0,0,0]}', 'cur_language': ?language, }); @@ -235,25 +243,24 @@ abstract final class VideoHttp { if (res.data['code'] == 0) { late PlayUrlModel data; switch (videoType) { - case VideoType.ugc: + case .ugc: data = PlayUrlModel.fromJson(res.data['data']); - break; - case VideoType.pugv: - final result = res.data['data']; - data = PlayUrlModel.fromJson(result) - ..lastPlayTime = - result?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']; - break; - case VideoType.pgc: + + case .pgc: final result = res.data['result']; data = PlayUrlModel.fromJson(result['video_info']) ..lastPlayTime = - result?['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']; - break; + result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']; + + case .pugv: + final result = res.data['data']; + data = PlayUrlModel.fromJson(result) + ..lastPlayTime = + result['play_view_business_info']?['user_status']?['watch_progress']?['current_watch_progress']; } return Success(data); - } else if (epid != null && videoType == VideoType.ugc) { - return videoUrl( + } else if (epid != null && videoType == .ugc) { + return await videoUrl( avid: avid, bvid: bvid, cid: cid, @@ -261,7 +268,7 @@ abstract final class VideoHttp { epid: epid, seasonId: seasonId, tryLook: tryLook, - videoType: VideoType.pgc, + videoType: .pgc, ); } return Error(_parseVideoErr(res.data['code'], res.data['message'])); @@ -828,33 +835,20 @@ abstract final class VideoHttp { } } - static String _subtitleTimecode(num seconds) { - int h = seconds ~/ 3600; - seconds %= 3600; - int m = seconds ~/ 60; - seconds %= 60; - String sms = seconds.toStringAsFixed(3).padLeft(6, '0'); - return h == 0 - ? "${m.toString().padLeft(2, '0')}:$sms" - : "${h.toString().padLeft(2, '0')}:${m.toString().padLeft(2, '0')}:$sms"; - } - - static String processList(List list) { - final sb = StringBuffer('WEBVTT\n\n') - ..writeAll( - list.map( - (item) => - '${item?['sid'] ?? 0}\n${_subtitleTimecode(item['from'])} --> ${_subtitleTimecode(item['to'])}\n${item['content'].trim()}', - ), - '\n\n', - ); - return sb.toString(); - } - - static Future vttSubtitles(String subtitleUrl) async { + static Future vttSubtitles( + String subtitleUrl, { + SubtitleFormat format = .vtt, + }) async { final res = await Request().get("https:$subtitleUrl"); if (res.data?['body'] case List list) { - return compute(processList, list); + switch (format) { + case .json: + throw UnimplementedError(); + case .vtt: + return compute(SubtitleUtils.json2Vtt, list); + case .srt: + return compute(SubtitleUtils.json2Srt, list); + } } return null; } diff --git a/lib/main.dart b/lib/main.dart index f9c1196a57..9ec55a5f0a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,10 +18,10 @@ import 'package:PiliPlus/services/service_locator.dart'; import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/calc_window_position.dart'; import 'package:PiliPlus/utils/date_utils.dart'; -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/image_memory_cleaner.dart'; import 'package:PiliPlus/utils/json_file_handler.dart'; +import 'package:PiliPlus/utils/max_screen_size.dart'; import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; @@ -32,6 +32,7 @@ import 'package:PiliPlus/utils/theme_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:auto_orientation/auto_orientation.dart'; import 'package:catcher_2/catcher_2.dart'; +import 'package:collection/collection.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart' show DeviceGestureSettings; @@ -46,10 +47,13 @@ import 'package:media_kit/media_kit.dart'; import 'package:os_type/os_type.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; +import 'package:screen_brightness_platform_interface/screen_brightness_platform_interface.dart'; import 'package:window_manager/window_manager.dart' hide calcWindowPosition; WebViewEnvironment? webViewEnvironment; +EdgeInsets? tmpPadding; + Future _initDownPath() async { if (PlatformUtils.isDesktop) { final customDownPath = Pref.downloadPath; @@ -103,7 +107,11 @@ void main() async { exit(0); } ScaledWidgetsFlutterBinding.instance.scaleFactor = Pref.uiScale; - await Future.wait([_initDownPath(), _initTmpPath()]); + await Future.wait([ + _initDownPath(), + _initTmpPath(), + CacheManager.ensureInitialized(), + ]); Get ..lazyPut(AccountService.new) ..lazyPut(DownloadService.new); @@ -111,9 +119,8 @@ void main() async { // 配置网络请求 HttpOverrides.global = _CustomHttpOverrides(); - CacheManager.autoClearCache(); - if (PlatformUtils.isMobile) { + if (Platform.isAndroid) MaxScreenSize.init(); await Future.wait([ SystemChrome.setPreferredOrientations( [ @@ -124,6 +131,7 @@ void main() async { ], ], ), + setupServiceLocator(), ]); // 鸿蒙embedder将 portraitUp+landscapeLeft+landscapeRight 组合映射为 // window.Orientation.LOCKED(锁定启动时的方向),导致平板无法自动旋转, @@ -131,13 +139,11 @@ void main() async { if (OS.isHarmony && Pref.horizontalScreen) { await AutoOrientation.setScreenOrientationUser(); } - } - - if (PlatformUtils.isMobile || OS.isHarmony) { + } else if (OS.isHarmony) { + // 鸿蒙 2in1 设备 isPCOS 为 true(isMobile 为 false),但同样需要媒体服务, + // 否则后台播放与系统播控失效 await setupServiceLocator(); - } - - if (Platform.isWindows) { + } else if (Platform.isWindows) { if (await WebViewEnvironment.getAvailableVersion() != null) { webViewEnvironment = await WebViewEnvironment.create( settings: WebViewEnvironmentSettings( @@ -156,12 +162,10 @@ void main() async { Request.setCookie(); RequestUtils.syncHistoryStatus(); - SmartDialog.config.toast = SmartConfigToast( - displayType: SmartToastType.onlyRefresh, - ); + SmartDialog.config.toast = SmartConfigToast(displayType: .onlyRefresh); if (PlatformUtils.isMobile) { - SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); + SystemChrome.setEnabledSystemUIMode(.edgeToEdge); SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( systemNavigationBarColor: Colors.transparent, @@ -183,6 +187,8 @@ void main() async { } FlutterDisplayMode.setPreferredMode(displayMode ?? DisplayMode.auto); }); + } else { + ScreenBrightnessPlatform.instance.setAutoReset(false); } } else if (PlatformUtils.isDesktop && !OS.isHarmony) { await windowManager.ensureInitialized(); @@ -213,6 +219,7 @@ void main() async { // TODO: 鸿蒙待适配 异常捕获 if (Pref.enableLog && !OS.isHarmony) { // 异常捕获 logo记录 + // catcher_2 保持 ohos 使用的 pub 版本 API(上游用的是其 fork) final customParameters = { 'BuildConfig': '\nBuild Time: ${DateFormatUtils.format(BuildConfig.buildTime, format: DateFormatUtils.longFormatDs)}\n' @@ -267,9 +274,7 @@ class MyApp extends StatelessWidget { const MyApp({super.key}); static ColorScheme? _light, _dark; - static final _shellBarsObserver = ShellBarsObserver(); - - static ThemeData? darkThemeData; + static final shellBarsObserver = ShellBarsObserver(); static void _onBack() { if (SmartDialog.checkExist()) { @@ -296,13 +301,13 @@ class MyApp extends StatelessWidget { late final brandColor = colorThemeTypes[Pref.customColor].color; late final variant = Pref.schemeVariant; return ( - ThemeUtils.getThemeData( + ThemeUtils.lightTheme = ThemeUtils.getThemeData( colorScheme: dynamicColor ? _light! : brandColor.asColorSchemeSeed(variant, Brightness.light), isDynamic: dynamicColor, ), - ThemeUtils.getThemeData( + ThemeUtils.darkTheme = ThemeUtils.getThemeData( isDark: true, colorScheme: dynamicColor ? _dark! @@ -319,7 +324,7 @@ class MyApp extends StatelessWidget { title: Constants.appName, theme: light, darkTheme: dark, - themeMode: Pref.themeMode, + themeMode: ThemeUtils.themeMode = Pref.themeMode, localizationsDelegates: const [ GlobalCupertinoLocalizations.delegate, GlobalMaterialLocalizations.delegate, @@ -332,14 +337,17 @@ class MyApp extends StatelessWidget { getPages: Routes.getPages, defaultTransition: Pref.pageTransition, builder: FlutterSmartDialog.init( - toastBuilder: (msg) => CustomToast(msg: msg), - loadingBuilder: (msg) => LoadingWidget(msg: msg), + toastBuilder: CustomToast.new, + loadingBuilder: LoadingWidget.new, + notifyStyle: const FlutterSmartNotifyStyle( + warningBuilder: NotifyWarning.new, + ), builder: _builder, ), navigatorObservers: [ routeObserver, FlutterSmartDialog.observer, - _shellBarsObserver, + shellBarsObserver, ], scrollBehavior: PlatformUtils.isDesktop ? const CustomScrollBehavior(desktopDragDevices) @@ -376,9 +384,9 @@ class MyApp extends StatelessWidget { data: mediaQuery.copyWith( textScaler: textScaler, size: mediaQuery.size / uiScale, - padding: mediaQuery.padding / uiScale, + padding: tmpPadding ?? mediaQuery.padding / uiScale, viewInsets: mediaQuery.viewInsets / uiScale, - viewPadding: mediaQuery.viewPadding / uiScale, + viewPadding: tmpPadding ?? mediaQuery.viewPadding / uiScale, devicePixelRatio: mediaQuery.devicePixelRatio * uiScale, gestureSettings: gestureSettings, ), @@ -386,10 +394,7 @@ class MyApp extends StatelessWidget { ); } else { child = MediaQuery( - data: mediaQuery.copyWith( - textScaler: textScaler, - gestureSettings: gestureSettings, - ), + data: mediaQuery.copyWith(textScaler: textScaler), child: child!, ); } diff --git a/lib/models/common/account_type.dart b/lib/models/common/account_type.dart index 475f9917ab..ddd81b5e23 100644 --- a/lib/models/common/account_type.dart +++ b/lib/models/common/account_type.dart @@ -2,7 +2,7 @@ enum AccountType { main('主账号'), heartbeat('记录观看'), recommend('推荐'), - video('视频取流') + video('视频取流'), ; final String title; diff --git a/lib/models/common/audio_normalization.dart b/lib/models/common/audio_normalization.dart index e81bd72e63..79ea9f648c 100644 --- a/lib/models/common/audio_normalization.dart +++ b/lib/models/common/audio_normalization.dart @@ -3,7 +3,7 @@ enum AudioNormalization { // ref https://github.com/KRTirtho/spotube/commit/da10ab2e291d4ba4d3082b9a6ae535639fb8f1b7 dynaudnorm('预设 dynaudnorm', 'dynaudnorm=g=5:f=250:r=0.9:p=0.5'), loudnorm('预设 loudnorm', 'loudnorm=I=-16:LRA=11:TP=-1.5'), - custom('自定义参数') + custom('自定义参数'), ; final String title; diff --git a/lib/models/common/avatar_badge_type.dart b/lib/models/common/avatar_badge_type.dart index 7baecf179d..0d10e40145 100644 --- a/lib/models/common/avatar_badge_type.dart +++ b/lib/models/common/avatar_badge_type.dart @@ -1,10 +1,11 @@ +import 'package:PiliPlus/utils/bili_colors.dart'; import 'package:flutter/material.dart'; enum BadgeType { none(), vip('大会员'), - person('认证个人', Color(0xFFFFCC00)), - institution('认证机构', Colors.lightBlueAccent) + person('认证个人', BiliColors.yellow), + institution('认证机构', Colors.lightBlueAccent), ; final String? desc; diff --git a/lib/models/common/bar_hide_type.dart b/lib/models/common/bar_hide_type.dart index 243083928e..db9fffed46 100644 --- a/lib/models/common/bar_hide_type.dart +++ b/lib/models/common/bar_hide_type.dart @@ -2,7 +2,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart'; enum BarHideType with EnumWithLabel { instant('即时'), - sync('同步') + sync('同步'), ; @override diff --git a/lib/models/common/dm_block_type.dart b/lib/models/common/dm_block_type.dart index 53adb78dcb..cee31e94bd 100644 --- a/lib/models/common/dm_block_type.dart +++ b/lib/models/common/dm_block_type.dart @@ -1,7 +1,7 @@ enum DmBlockType { keyword('关键词'), regex('正则'), - uid('用户') + uid('用户'), ; final String label; diff --git a/lib/models/common/dynamic/dynamic_badge_mode.dart b/lib/models/common/dynamic/dynamic_badge_mode.dart index 2beabe1bc1..9ce018d6b9 100644 --- a/lib/models/common/dynamic/dynamic_badge_mode.dart +++ b/lib/models/common/dynamic/dynamic_badge_mode.dart @@ -1,7 +1,7 @@ enum DynamicBadgeMode { hidden('隐藏'), point('红点'), - number('数字') + number('数字'), ; final String desc; diff --git a/lib/models/common/dynamic/dynamics_type.dart b/lib/models/common/dynamic/dynamics_type.dart index 7065cb0dc0..a2c3974228 100644 --- a/lib/models/common/dynamic/dynamics_type.dart +++ b/lib/models/common/dynamic/dynamics_type.dart @@ -3,7 +3,7 @@ enum DynamicsTabType { video('投稿'), pgc('番剧'), article('专栏'), - up('UP') + up('UP'), ; final String label; diff --git a/lib/models/common/dynamic/up_panel_position.dart b/lib/models/common/dynamic/up_panel_position.dart index 0fc276cf54..d7697e7819 100644 --- a/lib/models/common/dynamic/up_panel_position.dart +++ b/lib/models/common/dynamic/up_panel_position.dart @@ -3,7 +3,7 @@ enum UpPanelPosition { leftFixed('左侧常驻'), rightFixed('右侧常驻'), leftDrawer('左侧抽屉'), - rightDrawer('右侧抽屉') + rightDrawer('右侧抽屉'), ; final String label; diff --git a/lib/models/common/episode_panel_type.dart b/lib/models/common/episode_panel_type.dart index da6135452c..d19cfe5479 100644 --- a/lib/models/common/episode_panel_type.dart +++ b/lib/models/common/episode_panel_type.dart @@ -1,7 +1,7 @@ enum EpisodeType { part('分P'), season('合集'), - pgc('剧集') + pgc('剧集'), ; final String title; diff --git a/lib/models/common/fav_order_type.dart b/lib/models/common/fav_order_type.dart index 1c1e639071..e6f7b4d648 100644 --- a/lib/models/common/fav_order_type.dart +++ b/lib/models/common/fav_order_type.dart @@ -1,7 +1,7 @@ enum FavOrderType { mtime('最近收藏'), view('最多播放'), - pubtime('最近投稿') + pubtime('最近投稿'), ; final String label; diff --git a/lib/models/common/fav_type.dart b/lib/models/common/fav_type.dart index 7d4aebfc31..b0f03f0a11 100644 --- a/lib/models/common/fav_type.dart +++ b/lib/models/common/fav_type.dart @@ -13,7 +13,7 @@ enum FavTabType { article('专栏', FavArticlePage()), note('笔记', FavNotePage()), topic('话题', FavTopicPage()), - cheese('课堂', FavCheesePage()) + cheese('课堂', FavCheesePage()), ; final String title; diff --git a/lib/models/common/follow_order_type.dart b/lib/models/common/follow_order_type.dart index 0af6718fd9..bcaae6e546 100644 --- a/lib/models/common/follow_order_type.dart +++ b/lib/models/common/follow_order_type.dart @@ -1,6 +1,6 @@ enum FollowOrderType { def('', '最近关注'), - attention('attention', '最常访问') + attention('attention', '最常访问'), ; final String type; diff --git a/lib/models/common/home_tab_type.dart b/lib/models/common/home_tab_type.dart index e168616e77..5f124ddcdf 100644 --- a/lib/models/common/home_tab_type.dart +++ b/lib/models/common/home_tab_type.dart @@ -19,7 +19,7 @@ enum HomeTabType implements EnumWithLabel { hot('热门'), rank('分区'), bangumi('番剧'), - cinema('影视') + cinema('影视'), ; @override diff --git a/lib/models/common/later_view_type.dart b/lib/models/common/later_view_type.dart index 1012e12225..bcd7be2fa6 100644 --- a/lib/models/common/later_view_type.dart +++ b/lib/models/common/later_view_type.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; enum LaterViewType { all(0, '全部'), // toView(1, '未看'), - unfinished(2, '未看完') + unfinished(2, '未看完'), // viewed(3, '已看完'), ; diff --git a/lib/models/common/live/live_contribution_rank_type.dart b/lib/models/common/live/live_contribution_rank_type.dart index 0a688eb109..ad5a40a8ce 100644 --- a/lib/models/common/live/live_contribution_rank_type.dart +++ b/lib/models/common/live/live_contribution_rank_type.dart @@ -4,7 +4,7 @@ enum LiveContributionRankType { online_rank('在线榜', 'contribution_rank'), daily_rank('日榜', 'today_rank'), weekly_rank('周榜', 'current_week_rank'), - monthly_rank('月榜', 'current_month_rank') + monthly_rank('月榜', 'current_month_rank'), ; final String title; diff --git a/lib/models/common/member/contribute_type.dart b/lib/models/common/member/contribute_type.dart index e0076673a5..e00eb49ea7 100644 --- a/lib/models/common/member/contribute_type.dart +++ b/lib/models/common/member/contribute_type.dart @@ -6,7 +6,7 @@ enum ContributeType { season(Api.spaceSeason), series(Api.spaceSeries), bangumi(Api.spaceBangumi), - comic(Api.spaceComic) + comic(Api.spaceComic), ; final String api; diff --git a/lib/models/common/member/tab_type.dart b/lib/models/common/member/tab_type.dart index 7cb96a4fd1..688e73c78e 100644 --- a/lib/models/common/member/tab_type.dart +++ b/lib/models/common/member/tab_type.dart @@ -8,7 +8,7 @@ enum MemberTabType { favorite('收藏'), bangumi('番剧'), cheese('课堂'), - shop('小店') + shop('小店'), ; static bool showMemberShop = Pref.showMemberShop; diff --git a/lib/models/common/msg/msg_unread_type.dart b/lib/models/common/msg/msg_unread_type.dart index d0c88fe2ad..3c05e4f573 100644 --- a/lib/models/common/msg/msg_unread_type.dart +++ b/lib/models/common/msg/msg_unread_type.dart @@ -3,7 +3,7 @@ enum MsgUnReadType { reply('回复我的'), at('@我'), like('收到的赞'), - sysMsg('系统通知') + sysMsg('系统通知'), ; final String title; diff --git a/lib/models/common/nav_bar_config.dart b/lib/models/common/nav_bar_config.dart index 920374458a..d69c8cde54 100644 --- a/lib/models/common/nav_bar_config.dart +++ b/lib/models/common/nav_bar_config.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/models/common/enum_with_label.dart'; import 'package:PiliPlus/pages/dynamics/view.dart'; import 'package:PiliPlus/pages/home/view.dart'; @@ -7,22 +8,22 @@ import 'package:flutter/material.dart'; enum NavigationBarType implements EnumWithLabel { home( '首页', - Icon(Icons.home_outlined, size: 23), - Icon(Icons.home, size: 21), + Icon(Icons.home_outlined), + Icon(Icons.home), HomePage(), ), dynamics( '动态', - Icon(Icons.motion_photos_on_outlined, size: 21), - Icon(Icons.motion_photos_on, size: 21), + Icon(CustomIcons.motion_photos_on_outlined), + Icon(CustomIcons.motion_photos_on), DynamicsPage(), ), mine( '我的', - Icon(Icons.person_outline, size: 21), - Icon(Icons.person, size: 21), + Icon(Icons.person_outline), + Icon(Icons.person), MinePage(), - ) + ), ; @override diff --git a/lib/models/common/pgc_review_type.dart b/lib/models/common/pgc_review_type.dart index 4c9f6e63ed..c99e50bb54 100644 --- a/lib/models/common/pgc_review_type.dart +++ b/lib/models/common/pgc_review_type.dart @@ -2,7 +2,7 @@ import 'package:PiliPlus/http/api.dart'; enum PgcReviewType { long(label: '长评', api: Api.pgcReviewL), - short(label: '短评', api: Api.pgcReviewS) + short(label: '短评', api: Api.pgcReviewS), ; final String label; @@ -15,7 +15,7 @@ enum PgcReviewType { enum PgcReviewSortType { def('默认', 0), - latest('最新', 1) + latest('最新', 1), ; final int sort; diff --git a/lib/models/common/rank_type.dart b/lib/models/common/rank_type.dart index 73fd8a1764..c5fd54cb8c 100644 --- a/lib/models/common/rank_type.dart +++ b/lib/models/common/rank_type.dart @@ -19,7 +19,7 @@ enum RankType { documentary('记录', seasonType: 3), movie('电影', seasonType: 2), tv('剧集', seasonType: 5), - variety('综艺', seasonType: 7) + variety('综艺', seasonType: 7), ; final String label; diff --git a/lib/models/common/reply/reply_option_type.dart b/lib/models/common/reply/reply_option_type.dart index 122231f536..b5ef8da746 100644 --- a/lib/models/common/reply/reply_option_type.dart +++ b/lib/models/common/reply/reply_option_type.dart @@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart enum ReplyOptionType { allow('允许评论'), close('关闭评论'), - choose('精选评论') + choose('精选评论'), ; final String title; diff --git a/lib/models/common/reply/reply_sort_type.dart b/lib/models/common/reply/reply_sort_type.dart index 30612175f9..6ee4486ce7 100644 --- a/lib/models/common/reply/reply_sort_type.dart +++ b/lib/models/common/reply/reply_sort_type.dart @@ -1,10 +1,11 @@ enum ReplySortType { - time('最新评论', '最新'), - hot('最热评论', '最热'), - select('精选评论', '精选') + time('最新评论', '最新', text: '按时间'), + hot('最热评论', '最热', text: '按热度'), + select('精选评论', '精选'), ; final String title; final String label; - const ReplySortType(this.title, this.label); + final String? text; + const ReplySortType(this.title, this.label, {this.text}); } diff --git a/lib/models/common/search/article_search_type.dart b/lib/models/common/search/article_search_type.dart index 29f8273e95..3a5f790772 100644 --- a/lib/models/common/search/article_search_type.dart +++ b/lib/models/common/search/article_search_type.dart @@ -3,7 +3,7 @@ enum ArticleOrderType { pubdate('最新发布'), click('最多点击'), attention('最多喜欢'), - scores('最多评论') + scores('最多评论'), ; String get order => name; @@ -20,7 +20,7 @@ enum ArticleZoneType { interest('兴趣', 29), novel('轻小说', 16), tech('科技', 17), - note('笔记', 41) + note('笔记', 41), ; final String label; diff --git a/lib/models/common/search/search_type.dart b/lib/models/common/search/search_type.dart index 72d91f73cd..d56382bd6e 100644 --- a/lib/models/common/search/search_type.dart +++ b/lib/models/common/search/search_type.dart @@ -18,7 +18,7 @@ enum SearchType { // 用户:bili_user bili_user('用户'), // 专栏:article - article('专栏') + article('专栏'), ; // 相簿:photo // photo diff --git a/lib/models/common/search/user_search_type.dart b/lib/models/common/search/user_search_type.dart index 28a03045d0..d30c42e816 100644 --- a/lib/models/common/search/user_search_type.dart +++ b/lib/models/common/search/user_search_type.dart @@ -3,7 +3,7 @@ enum UserOrderType { fansDesc('粉丝数由高到低', 0, 'fans'), fansAsc('粉丝数由低到高', 1, 'fans'), levelDesc('Lv等级由高到低', 0, 'level'), - levelAsc('Lv等级由低到高', 1, 'level') + levelAsc('Lv等级由低到高', 1, 'level'), ; final String label; @@ -16,7 +16,7 @@ enum UserType { all('全部用户'), up('UP主'), common('普通用户'), - verified('认证用户') + verified('认证用户'), ; final String label; diff --git a/lib/models/common/search/video_search_type.dart b/lib/models/common/search/video_search_type.dart index bee53bbf04..b166369122 100644 --- a/lib/models/common/search/video_search_type.dart +++ b/lib/models/common/search/video_search_type.dart @@ -2,7 +2,7 @@ enum VideoPubTimeType { all('不限'), day('最近一天'), week('最近一周'), - halfYear('最近半年') + halfYear('最近半年'), ; final String label; @@ -14,7 +14,7 @@ enum VideoDurationType { tenMins('0-10分钟'), halfHour('10-30分钟'), hour('30-60分钟'), - hourPlus('60分钟+') + hourPlus('60分钟+'), ; final String label; @@ -43,7 +43,7 @@ enum VideoZoneType { cinephile('影视', tids: 181), documentary('记录', tids: 177), movie('电影', tids: 23), - tv('电视', tids: 11) + tv('电视', tids: 11), ; final String label; @@ -58,7 +58,7 @@ enum ArchiveFilterType { pubdate('新发布'), dm('弹幕多'), stow('收藏多'), - scores('评论多') + scores('评论多'), ; // 专栏 // attention('最多喜欢'), diff --git a/lib/models/common/setting_type.dart b/lib/models/common/setting_type.dart index 5a4ff864d7..060ab6afeb 100644 --- a/lib/models/common/setting_type.dart +++ b/lib/models/common/setting_type.dart @@ -1,3 +1,11 @@ +import 'package:PiliPlus/pages/setting/models/extra_settings.dart'; +import 'package:PiliPlus/pages/setting/models/model.dart'; +import 'package:PiliPlus/pages/setting/models/play_settings.dart'; +import 'package:PiliPlus/pages/setting/models/privacy_settings.dart'; +import 'package:PiliPlus/pages/setting/models/recommend_settings.dart'; +import 'package:PiliPlus/pages/setting/models/style_settings.dart'; +import 'package:PiliPlus/pages/setting/models/video_settings.dart'; + enum SettingType { privacySetting('隐私设置'), recommendSetting('推荐流设置'), @@ -7,9 +15,19 @@ enum SettingType { extraSetting('其它设置'), webdavSetting('WebDAV 设置'), experimentalSetting('试验性功能'), - about('关于') + about('关于'), ; final String title; const SettingType(this.title); + + List get settings => switch (this) { + .privacySetting => privacySettings, + .recommendSetting => recommendSettings, + .videoSetting => videoSettings, + .playSetting => playSettings, + .styleSetting => styleSettings, + .extraSetting => extraSettings, + _ => throw UnimplementedError(), + }; } diff --git a/lib/models/common/sponsor_block/action_type.dart b/lib/models/common/sponsor_block/action_type.dart index 5efd5d5c6d..6a73a1337a 100644 --- a/lib/models/common/sponsor_block/action_type.dart +++ b/lib/models/common/sponsor_block/action_type.dart @@ -2,7 +2,7 @@ enum ActionType { skip('跳过'), mute('静音'), full('整个视频'), - poi('精彩时刻') + poi('精彩时刻'), ; final String title; diff --git a/lib/models/common/sponsor_block/skip_type.dart b/lib/models/common/sponsor_block/skip_type.dart index 63616264a7..cbbcb5320d 100644 --- a/lib/models/common/sponsor_block/skip_type.dart +++ b/lib/models/common/sponsor_block/skip_type.dart @@ -5,7 +5,7 @@ enum SkipType implements EnumWithLabel { skipOnce('跳过一次'), skipManually('手动跳过'), showOnly('仅显示'), - disable('禁用') + disable('禁用'), ; @override diff --git a/lib/models/common/stat_type.dart b/lib/models/common/stat_type.dart index c7a7eb9bc1..dbab936940 100644 --- a/lib/models/common/stat_type.dart +++ b/lib/models/common/stat_type.dart @@ -7,7 +7,7 @@ enum StatType { reply(Icons.comment_outlined, '评论'), follow(Icons.favorite_border, '关注'), play(Icons.play_circle_outlined, '播放'), - listen(Icons.headset_outlined, '播放') + listen(Icons.headset_outlined, '播放'), ; final IconData iconData; diff --git a/lib/models/common/super_resolution_type.dart b/lib/models/common/super_resolution_type.dart index 0ca532fc03..792ac9f8d5 100644 --- a/lib/models/common/super_resolution_type.dart +++ b/lib/models/common/super_resolution_type.dart @@ -3,7 +3,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart'; enum SuperResolutionType with EnumWithLabel { disable('禁用'), efficiency('效率'), - quality('画质') + quality('画质'), ; @override diff --git a/lib/models/common/theme/theme_type.dart b/lib/models/common/theme/theme_type.dart index e3aefe1de6..751695bfd8 100644 --- a/lib/models/common/theme/theme_type.dart +++ b/lib/models/common/theme/theme_type.dart @@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart enum ThemeType { light('浅色'), dark('深色'), - system('跟随系统') + system('跟随系统'), ; final String desc; diff --git a/lib/models/common/video/audio_quality.dart b/lib/models/common/video/audio_quality.dart index f15f5f898c..c721ea6b33 100644 --- a/lib/models/common/video/audio_quality.dart +++ b/lib/models/common/video/audio_quality.dart @@ -7,7 +7,7 @@ enum AudioQuality { dolby_30255(30255, '杜比全景声'), k192(30280, '192K'), k132(30232, '132K'), - k64(30216, '64K') + k64(30216, '64K'), ; final int code; diff --git a/lib/models/common/video/cdn_type.dart b/lib/models/common/video/cdn_type.dart index 55326f9e98..8c8fe08201 100644 --- a/lib/models/common/video/cdn_type.dart +++ b/lib/models/common/video/cdn_type.dart @@ -24,7 +24,7 @@ enum CDNService { aliov('aliov(阿里云海外)', 'upos-sz-mirroraliov.bilivideo.com'), cosov('cosov(腾讯云海外)', 'upos-sz-mirrorcosov.bilivideo.com'), hwov('hwov(华为云海外)', 'upos-sz-mirrorhwov.bilivideo.com'), - hk_bcache('hk_bcache(Bilibili海外)', 'cn-hk-eq-bcache-01.bilivideo.com') + hk_bcache('hk_bcache(Bilibili海外)', 'cn-hk-eq-bcache-01.bilivideo.com'), ; final String desc; diff --git a/lib/models/common/video/live_quality.dart b/lib/models/common/video/live_quality.dart index bdcbfdef94..046c566160 100644 --- a/lib/models/common/video/live_quality.dart +++ b/lib/models/common/video/live_quality.dart @@ -7,7 +7,7 @@ enum LiveQuality { bluRay(400, '蓝光'), superHD(250, '超清'), smooth(150, '高清'), - flunt(80, '流畅') + flunt(80, '流畅'), ; final int code; diff --git a/lib/models/common/video/source_type.dart b/lib/models/common/video/source_type.dart index 19d9a77ad6..dea3e5268a 100644 --- a/lib/models/common/video/source_type.dart +++ b/lib/models/common/video/source_type.dart @@ -26,7 +26,7 @@ enum SourceType { extraId: 4, playlistSource: PlaylistSource.MEDIA_LIST, ), - file + file, ; final int? mediaType; diff --git a/lib/models/common/video/subtitle_pref_type.dart b/lib/models/common/video/subtitle_pref_type.dart index 3fc75216be..265cdb08bb 100644 --- a/lib/models/common/video/subtitle_pref_type.dart +++ b/lib/models/common/video/subtitle_pref_type.dart @@ -2,7 +2,7 @@ enum SubtitlePrefType { off('默认不显示字幕'), on('优先选择非自动生成(ai)字幕'), withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'), - auto('静音时等同第二项,非静音时等同第三项') + auto('静音时等同第二项,非静音时等同第三项'), ; final String desc; diff --git a/lib/models/common/video/video_decode_type.dart b/lib/models/common/video/video_decode_type.dart index f2f57d55a5..4020de3f2c 100644 --- a/lib/models/common/video/video_decode_type.dart +++ b/lib/models/common/video/video_decode_type.dart @@ -4,7 +4,7 @@ enum VideoDecodeFormatType { DVH1(['dvh1']), AV1(['av01']), HEVC(['hev1', 'hvc1']), - AVC(['avc1']) + AVC(['avc1']), ; String get description => name; @@ -12,9 +12,6 @@ enum VideoDecodeFormatType { const VideoDecodeFormatType(this.codes); - static VideoDecodeFormatType fromCode(String code) => - values.firstWhere((i) => i.codes.contains(code)); - static VideoDecodeFormatType fromString(String val) => values.firstWhere((i) => i.codes.any(val.startsWith)); } diff --git a/lib/models/common/video/video_quality.dart b/lib/models/common/video/video_quality.dart index bd91f362e5..98329bde23 100644 --- a/lib/models/common/video/video_quality.dart +++ b/lib/models/common/video/video_quality.dart @@ -11,7 +11,7 @@ enum VideoQuality { high720(64, '720P 准高清', '720P'), clear480(32, '480P 标清', '480P'), fluent360(16, '360P 流畅', '360P'), - speed240(6, '240P 极速', '240P') + speed240(6, '240P 极速', '240P'), ; final int code; diff --git a/lib/models/common/video/video_type.dart b/lib/models/common/video/video_type.dart index d564769708..f5306f0599 100644 --- a/lib/models/common/video/video_type.dart +++ b/lib/models/common/video/video_type.dart @@ -13,7 +13,7 @@ enum VideoType { type: 10, replyType: 33, api: Api.pugvUrl, - ) + ), ; final int type; diff --git a/lib/models/common/webview_menu_type.dart b/lib/models/common/webview_menu_type.dart index 4758938e92..8fd0a042f0 100644 --- a/lib/models/common/webview_menu_type.dart +++ b/lib/models/common/webview_menu_type.dart @@ -4,7 +4,7 @@ enum WebviewMenuItem { openInBrowser('浏览器中打开'), clearCache('清除缓存'), resetCookie('重新设置Cookie'), - goBack('返回') + goBack('返回'), ; final String title; diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index 1412e152d8..6f5e6c28e6 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -1,7 +1,8 @@ import 'package:PiliPlus/common/style.dart' as common_style; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; -import 'package:PiliPlus/utils/utils.dart'; +import 'package:PiliPlus/utils/color_utils.dart'; +import 'package:PiliPlus/utils/parse_int.dart'; class ArticleContentModel { int? align; @@ -131,10 +132,17 @@ class Word { style = json['style'] == null ? null : Style.fromJson(json['style']); if (json['color'] case final String rawColor when rawColor.startsWith('#')) { - color = Utils.parseColorInt(json['color']); + color = ColourUtils.parse2Int(rawColor); } fontLevel = json['font_level']; } + + // font_level 映射处理: + // "small" → 13px + // "regular" → 16px(与旧版 HTML 专栏基准一致) + // 其余/null → 同 regular + double get effectiveFontSize => + fontSize ?? (fontLevel == 'small' ? 13.0 : 16.0); } class Style { @@ -269,7 +277,7 @@ class Music { Music.fromJson(Map json) { cover = json['cover']; - id = Utils.safeToInt(json['id']); + id = safeToInt(json['id']); jumpUrl = json['jump_url']; label = json['label']; title = json['title']; @@ -285,12 +293,12 @@ class Opus { int? statView; Opus.fromJson(Map json) { - authorMid = Utils.safeToInt(json['author']?['mid']); + authorMid = safeToInt(json['author']?['mid']); authorName = json['author']?['name']; cover = json['cover']; jumpUrl = json['jump_url']; title = json['title']; - statView = Utils.safeToInt(json['stat']?['view']); + statView = safeToInt(json['stat']?['view']); } } @@ -311,9 +319,9 @@ class Live { descSecond = json['desc_second']; title = json['title']; jumpUrl = json['jump_url']; - id = Utils.safeToInt(json['id']); - liveState = Utils.safeToInt(json['live_state']); - reserveType = Utils.safeToInt(json['reserve_type']); + id = safeToInt(json['id']); + liveState = safeToInt(json['live_state']); + reserveType = safeToInt(json['reserve_type']); badgeText = json['badge']?['text']; } } diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index 4aff0b96d8..110ed9d250 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -7,9 +7,10 @@ import 'package:PiliPlus/models/model_avatar.dart'; import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/live/live_feed_index/watched_show.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; +import 'package:PiliPlus/utils/parse_bool.dart'; +import 'package:PiliPlus/utils/parse_int.dart'; import 'package:PiliPlus/utils/parse_string.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:PiliPlus/utils/utils.dart'; class DynamicsDataModel { bool? hasMore; @@ -90,7 +91,7 @@ class DynamicsDataModel { } offset = json['offset']; - total = Utils.safeToInt(json['total']); + total = safeToInt(json['total']); } } @@ -306,7 +307,7 @@ class ModuleCollection { ModuleCollection.fromJson(Map json) { count = json['count']; - id = Utils.safeToInt(json['id']); + id = safeToInt(json['id']); name = json['name']; title = json['title']; } @@ -350,7 +351,7 @@ class ModuleBlocked { ModuleBlocked.fromJson(Map json) { bgImg = json['bg_img'] == null ? null : BgImg.fromJson(json['bg_img']); - blockedType = Utils.safeToInt(json['blocked_type']); + blockedType = safeToInt(json['blocked_type']); button = json['button'] == null ? null : Button.fromJson(json['button']); title = json['title']; hintMessage = json['hint_message']; @@ -401,7 +402,7 @@ class Basic { Basic.fromJson(Map json) { commentIdStr = json['comment_id_str']; - commentType = Utils.safeToInt(json['comment_type']); + commentType = safeToInt(json['comment_type']); ridStr = json['rid_str']; } } @@ -422,7 +423,9 @@ class ModuleAuthorModel extends Avatar { } pubAction = json['pub_action']; pubTime = json['pub_time']; - pubTs = json['pub_ts'] == 0 ? null : Utils.safeToInt(json['pub_ts']); + if (safeToInt(json['pub_ts']) case final pubTs? when pubTs > 0) { + this.pubTs = pubTs; + } type = json['type']; if (PendantAvatar.showDecorate) { decorate = json['decorate'] == null @@ -432,7 +435,7 @@ class ModuleAuthorModel extends Avatar { pendant = null; } isTop = json['is_top']; - badgeText = noneNullOrEmptyString(json['icon_badge']?['text']); + badgeText = nonNullOrEmptyString(json['icon_badge']?['text']); } } @@ -683,11 +686,11 @@ class Vote { String? title; Vote.fromJson(Map json) { - joinNum = Utils.safeToInt(json['join_num']); - voteId = Utils.safeToInt(json['vote_id']); + joinNum = safeToInt(json['join_num']); + voteId = safeToInt(json['vote_id']); title = - noneNullOrEmptyString(json['title']) ?? - noneNullOrEmptyString(json['desc']); + nonNullOrEmptyString(json['title']) ?? + nonNullOrEmptyString(json['desc']); } } @@ -740,10 +743,10 @@ class Reserve { desc1 = json['desc1'] == null ? null : Desc.fromJson(json['desc1']); desc2 = json['desc2'] == null ? null : Desc.fromJson(json['desc2']); desc3 = json['desc3'] == null ? null : Desc.fromJson(json['desc3']); - reserveTotal = Utils.safeToInt(json['reserve_total']); - rid = Utils.safeToInt(json['rid']); - state = Utils.safeToInt(json['state']); - state = Utils.safeToInt(json['state']); + reserveTotal = safeToInt(json['reserve_total']); + rid = safeToInt(json['rid']); + state = safeToInt(json['state']); + state = safeToInt(json['state']); title = json['title']; } } @@ -765,11 +768,11 @@ class ReserveBtn { String? jumpUrl; ReserveBtn.fromJson(Map json) { - status = Utils.safeToInt(json['status']); - type = Utils.safeToInt(json['type']); + status = safeToInt(json['status']); + type = safeToInt(json['type']); checkText = json['check']?['text'] ?? '已预约'; uncheckText = json['uncheck']?['text'] ?? '预约'; - disable = Utils.safeToInt(json['uncheck']?['disable']); + disable = safeToInt(json['uncheck']?['disable']); jumpText = json['jump_style']?['text']; jumpUrl = json['jump_url']; } @@ -929,7 +932,7 @@ class Music { String? label; Music.fromJson(Map json) { - id = Utils.safeToInt(json['id']); + id = safeToInt(json['id']); cover = json['cover']; title = json['title']; label = json['label']; @@ -1016,8 +1019,8 @@ class LivePlayInfo { }); factory LivePlayInfo.fromJson(Map json) => LivePlayInfo( - roomId: Utils.safeToInt(json["room_id"]), - liveStatus: Utils.safeToInt(json["live_status"]), + roomId: safeToInt(json["room_id"]), + liveStatus: safeToInt(json["live_status"]), title: json["title"], cover: json["cover"], areaName: json["area_name"], @@ -1037,7 +1040,7 @@ class DynamicTopicModel { String? name; DynamicTopicModel.fromJson(Map json) { - id = Utils.safeToInt(json['id']); + id = safeToInt(json['id']); name = json['name']; } } @@ -1072,8 +1075,8 @@ class DynamicArchiveModel { int? seasonId; DynamicArchiveModel.fromJson(Map json) { - id = Utils.safeToInt(json['id']); - aid = Utils.safeToInt(json['aid']); + id = safeToInt(json['id']); + aid = safeToInt(json['aid']); badge = json['badge'] == null ? null : Badge.fromJson(json['badge']); bvid = json['bvid'] ?? json['epid'].toString() ?? ' '; cover = json['cover']; @@ -1081,9 +1084,9 @@ class DynamicArchiveModel { jumpUrl = json['jump_url']; stat = json['stat'] != null ? Stat.fromJson(json['stat']) : null; title = json['title']; - type = Utils.safeToInt(json['type']); - epid = Utils.safeToInt(json['epid']); - seasonId = Utils.safeToInt(json['season_id']); + type = safeToInt(json['type']); + epid = safeToInt(json['epid']); + seasonId = safeToInt(json['season_id']); } } @@ -1175,9 +1178,9 @@ class Emoji { Emoji.fromJson(Map json) { url = - noneNullOrEmptyString(json['webp_url']) ?? - noneNullOrEmptyString(json['gif_url']) ?? - noneNullOrEmptyString(json['icon_url']); + nonNullOrEmptyString(json['webp_url']) ?? + nonNullOrEmptyString(json['gif_url']) ?? + nonNullOrEmptyString(json['icon_url']); size = json['size'] ?? 1; } } @@ -1221,8 +1224,8 @@ class OpusPicModel extends PicModel { num? size; OpusPicModel.fromJson(Map json) { - width = Utils.safeToInt(json['width']); - height = Utils.safeToInt(json['height']); + width = safeToInt(json['width']); + height = safeToInt(json['height']); src = json['src']; url = json['url']; liveUrl = json['live_url']; @@ -1250,8 +1253,8 @@ class DynamicLiveModel { Map data = jsonDecode(json['content']); Map livePlayInfo = data['live_play_info']; - roomId = Utils.safeToInt(livePlayInfo['room_id']); - liveStatus = Utils.safeToInt(livePlayInfo['live_status']); + roomId = safeToInt(livePlayInfo['room_id']); + liveStatus = safeToInt(livePlayInfo['live_status']); cover = livePlayInfo['cover']; areaName = livePlayInfo['area_name']; title = livePlayInfo['title']; @@ -1283,8 +1286,8 @@ class DynamicLive2Model { badge = json['badge'] == null ? null : Badge.fromJson(json['badge']); cover = json['cover']; descFirst = json['desc_first']; - id = Utils.safeToInt(json['id']); - liveState = Utils.safeToInt(json['live_state']); + id = safeToInt(json['id']); + liveState = safeToInt(json['live_state']); title = json['title']; } } @@ -1297,7 +1300,7 @@ class ModuleTag { String? text; ModuleTag.fromJson(Map json) { - text = noneNullOrEmptyString(json['text']); + text = nonNullOrEmptyString(json['text']); } } @@ -1340,8 +1343,10 @@ class DynamicStat { bool? status; DynamicStat.fromJson(Map json) { - count = json['count'] == 0 ? null : Utils.safeToInt(json['count']); - status = json['status']; + if (safeToInt(json['count']) case final count? when count > 0) { + this.count = count; + } + status = safeToBool(json['status'], () => 'STATE_LIKE'); } } diff --git a/lib/models/dynamics/up.dart b/lib/models/dynamics/up.dart index 2b5bfe9284..2aba3faff8 100644 --- a/lib/models/dynamics/up.dart +++ b/lib/models/dynamics/up.dart @@ -1,41 +1,43 @@ -import 'package:PiliPlus/utils/utils.dart'; +import 'package:PiliPlus/models_new/follow/list.dart'; +import 'package:PiliPlus/utils/parse_int.dart'; class FollowUpModel { - FollowUpModel({ - this.liveUsers, - required this.upList, - }); - LiveUsers? liveUsers; - late List upList; + List? upList; bool? hasMore; String? offset; - FollowUpModel.fromJson(Map json) { - liveUsers = json['live_users'] != null - ? LiveUsers.fromJson(json['live_users']) - : null; - upList = - (json['up_list']?['items'] as List?) - ?.map((e) => UpItem.fromJson(e)) - .toList() ?? - []; - hasMore = json['up_list']?['has_more']; - offset = json['up_list']?['offset']; + void addAllUpList(List newList) { + if (upList != null) { + upList!.addAll(newList); + } else { + upList = newList; + } } -} -class DynUpList { - List? upList; - bool? hasMore; - String? offset; + factory FollowUpModel.fromJson(Map json) { + final model = FollowUpModel.fromUpList(json['up_list']); + final liveUsers = json['live_users']; + if (liveUsers != null) { + model.liveUsers = LiveUsers.fromJson(liveUsers); + } + return model; + } + + FollowUpModel.fromUpList(Map? json) { + if (json != null) { + upList = (json['items'] as List?) + ?.map((e) => UpItem.fromJson(e)) + .toList(); + hasMore = json['has_more']; + offset = json['offset']; + } + } - DynUpList.fromJson(Map json) { - upList = (json['items'] as List?) - ?.map((e) => UpItem.fromJson(e)) + FollowUpModel.fromFollowList(Map json) { + upList = (json['list'] as List?) + ?.map((e) => FollowItemModel.fromJson(e)) .toList(); - hasMore = json['has_more']; - offset = json['offset']; } } @@ -51,7 +53,7 @@ class LiveUsers { List? items; LiveUsers.fromJson(Map json) { - count = Utils.safeToInt(json['count']) ?? 0; + count = safeToInt(json['count']) ?? 0; group = json['group']; items = (json['items'] as List?) ?.map((e) => LiveUserItem.fromJson(e)) @@ -68,7 +70,7 @@ class LiveUserItem extends UpItem { LiveUserItem.fromJson(Map json) : super.fromJson(json) { isReserveRecall = json['is_reserve_recall']; jumpUrl = json['jump_url']; - roomId = Utils.safeToInt(json['room_id']); + roomId = safeToInt(json['room_id']); title = json['title']; } } @@ -89,7 +91,7 @@ class UpItem { UpItem.fromJson(Map json) { face = json['face']; hasUpdate = json['has_update']; - mid = Utils.safeToInt(json['mid']) ?? 0; + mid = safeToInt(json['mid']) ?? 0; uname = json['uname']; } diff --git a/lib/models/dynamics/vote_model.dart b/lib/models/dynamics/vote_model.dart index e77744fae5..5e647d3e72 100644 --- a/lib/models/dynamics/vote_model.dart +++ b/lib/models/dynamics/vote_model.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/utils/extension/iterable_ext.dart'; -import 'package:PiliPlus/utils/utils.dart'; +import 'package:PiliPlus/utils/parse_int.dart'; class SimpleVoteInfo { int? choiceCnt; @@ -23,14 +23,14 @@ class SimpleVoteInfo { }); SimpleVoteInfo.fromJson(Map json) { - choiceCnt = Utils.safeToInt(json['choice_cnt']); - defaultShare = Utils.safeToInt(json['default_share']); + choiceCnt = safeToInt(json['choice_cnt']); + defaultShare = safeToInt(json['default_share']); desc = json['desc']; - endTime = Utils.safeToInt(json['end_time']); - status = Utils.safeToInt(json['status']); - uid = Utils.safeToInt(json['uid']); - voteId = Utils.safeToInt(json['vote_id']); - joinNum = Utils.safeToInt(json['join_num']) ?? 0; + endTime = safeToInt(json['end_time']); + status = safeToInt(json['status']); + uid = safeToInt(json['uid']); + voteId = safeToInt(json['vote_id']); + joinNum = safeToInt(json['join_num']) ?? 0; } } diff --git a/lib/models/horizontal_video_model.dart b/lib/models/horizontal_video_model.dart new file mode 100644 index 0000000000..c99bceafaa --- /dev/null +++ b/lib/models/horizontal_video_model.dart @@ -0,0 +1,21 @@ +import 'package:PiliPlus/models/model_video.dart'; +import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; + +abstract class HorizontalVideoModel extends BaseVideoItemModel { + bool? isPugv; + int? seasonId; + + int? roomId; + bool? isLive; + + Dimension? dimension; + + String? badge; + + num? progress; + + String? redirectUrl; + + // search + List<({bool isEm, String text})>? titleList; +} diff --git a/lib/models/member/tags.dart b/lib/models/member/tags.dart index e6c9c4daab..692e3f99cd 100644 --- a/lib/models/member/tags.dart +++ b/lib/models/member/tags.dart @@ -17,4 +17,12 @@ class MemberTagItemModel { tagid = json['tagid']; tip = json['tip']; } + + MemberTagItemModel.fromCreate( + ({int tagid, String tagName}) res, { + this.count = 0, + }) { + tagid = res.tagid; + name = res.tagName; + } } diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index dcdccec6e7..8722947c64 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -1,25 +1,19 @@ +import 'package:PiliPlus/models/horizontal_video_model.dart'; import 'package:PiliPlus/models/model_owner.dart'; -import 'package:PiliPlus/models/model_rec_video_item.dart'; import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; // 稍后再看, 排行榜等网页返回也使用该类 -class HotVideoItemModel extends BaseRcmdVideoItemModel with MultiSelectData { +class HotVideoItemModel extends HorizontalVideoModel with MultiSelectData { int? videos; int? tid; String? tname; int? copyright; int? ctime; int? state; - Dimension? dimension; String? firstFrame; String? pubLocation; - String? pgcLabel; - String? redirectUrl; - num? progress; - int? isCooperation; - bool? isCharging; HotVideoItemModel.fromJson(Map json) { aid = json["aid"]; @@ -38,26 +32,21 @@ class HotVideoItemModel extends BaseRcmdVideoItemModel with MultiSelectData { duration = json["duration"]; owner = Owner.fromJson(json["owner"]); stat = HotStat.fromJson(json['stat']); - dimension = Dimension.fromJson(json['dimension']); + dimension = json['dimension'] == null + ? null + : Dimension.fromJson(json['dimension']); firstFrame = json["first_frame"]; pubLocation = json["pub_location"]; - dynamic rcmd = json['rcmd_reason']; - rcmdReason = rcmd is Map ? rcmd['content'] : rcmd; // 相关视频里rcmd为String, - if (rcmdReason?.isEmpty == true) rcmdReason = null; - pgcLabel = json['pgc_label']; redirectUrl = json['redirect_url']; - // uri = json['uri']; // 仅在稍后再看存在 progress = json['progress']; - isCooperation = json['rights']?['is_cooperation']; - isCharging = json['charging_pay']?['level'] != null; + if (json['charging_pay']?['level'] != null) { + badge = '充电专属'; + } else if (json['rights']?['is_cooperation'] == 1) { + badge = '合作'; + } else { + badge = json['pgc_label']; + } } - - // @override - // get isFollowed => false; - // @override - // get goto => 'av'; - // @override - // get uri => 'bilibili://video/$aid'; } class HotStat extends Stat { diff --git a/lib/models/model_owner.dart b/lib/models/model_owner.dart index d7db6871cb..50f836e38b 100644 --- a/lib/models/model_owner.dart +++ b/lib/models/model_owner.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/models/model_video.dart'; -import 'package:PiliPlus/utils/utils.dart'; +import 'package:PiliPlus/utils/parse_int.dart'; import 'package:hive_ce/hive.dart'; part 'model_owner.g.dart'; @@ -21,7 +21,7 @@ class Owner implements BaseOwner { String? face; Owner.fromJson(Map json) { - mid = Utils.safeToInt(json["mid"]); + mid = safeToInt(json["mid"]); name = json["name"]; face = json['face']; } diff --git a/lib/models/search/result.dart b/lib/models/search/result.dart index 4aa50c4fb0..129e19b7ac 100644 --- a/lib/models/search/result.dart +++ b/lib/models/search/result.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/models/horizontal_video_model.dart'; import 'package:PiliPlus/models/model_avatar.dart'; import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_video.dart'; @@ -64,18 +65,16 @@ class SearchVideoData extends SearchNumData { } } -class SearchVideoItemModel extends BaseVideoItemModel { - String? type; +class SearchVideoItemModel extends HorizontalVideoModel { int? id; String? arcurl; String? tag; int? ctime; - int? isUnionVideo; - List<({bool isEm, String text})>? titleList; + @override + int? get seasonId => aid; SearchVideoItemModel.fromJson(Map json) { - type = json['type']; id = json['id']; arcurl = json['arcurl']; aid = json['aid']; @@ -89,7 +88,19 @@ class SearchVideoItemModel extends BaseVideoItemModel { duration = DurationUtils.parseDuration(json['duration']); owner = SearchOwner.fromJson(json); stat = SearchStat.fromJson(json); - isUnionVideo = json['is_union_video']; + switch (json['type']) { + case 'ketang': + badge = '课堂'; + isPugv = true; + case 'live_room': + badge = '直播'; + isLive = true; + roomId = json['roomid']; + default: + if (json['is_union_video'] == 1) { + badge = '合作'; + } + } } } diff --git a/lib/models/video/play/url.dart b/lib/models/video/play/url.dart index a8234de282..2cafbc7ab3 100644 --- a/lib/models/video/play/url.dart +++ b/lib/models/video/play/url.dart @@ -22,9 +22,9 @@ class PlayUrlModel { this.seekType, this.dash, this.supportFormats, - this.lastPlayTime, + int lastPlayTime = 0, this.lastPlayCid, - }); + }) : _lastPlayTime = lastPlayTime; String? from; String? result; @@ -42,7 +42,17 @@ class PlayUrlModel { List? durl; List? supportFormats; Volume? volume; - int? lastPlayTime; + + late int _lastPlayTime; + int get lastPlayTime => _lastPlayTime; + set lastPlayTime(int? value) { + if (value != null && value > 0) { + _lastPlayTime = value; + } else { + _lastPlayTime = 0; + } + } + int? lastPlayCid; String? curLanguage; Language? language; diff --git a/lib/models_new/account_myinfo/data.dart b/lib/models_new/account_myinfo/data.dart index 557338548c..6e5a77a81b 100644 --- a/lib/models_new/account_myinfo/data.dart +++ b/lib/models_new/account_myinfo/data.dart @@ -5,19 +5,7 @@ class AccountMyInfoData { num? coins; String? birthday; String? face; - int? faceNftNew; int? sex; - int? level; - int? rank; - int? silence; - int? emailStatus; - int? telStatus; - int? identification; - int? isTourist; - int? pinPrompting; - int? inRegAudit; - bool? hasFaceNft; - bool? setBirthday; AccountMyInfoData({ this.mid, @@ -26,19 +14,7 @@ class AccountMyInfoData { this.coins, this.birthday, this.face, - this.faceNftNew, this.sex, - this.level, - this.rank, - this.silence, - this.emailStatus, - this.telStatus, - this.identification, - this.isTourist, - this.pinPrompting, - this.inRegAudit, - this.hasFaceNft, - this.setBirthday, }); factory AccountMyInfoData.fromJson(Map json) => @@ -49,18 +25,6 @@ class AccountMyInfoData { coins: json['coins'] as num?, birthday: json['birthday'] as String?, face: json['face'] as String?, - faceNftNew: json['face_nft_new'] as int?, sex: json['sex'] as int?, - level: json['level'] as int?, - rank: json['rank'] as int?, - silence: json['silence'] as int?, - emailStatus: json['email_status'] as int?, - telStatus: json['tel_status'] as int?, - identification: json['identification'] as int?, - isTourist: json['is_tourist'] as int?, - pinPrompting: json['pin_prompting'] as int?, - inRegAudit: json['in_reg_audit'] as int?, - hasFaceNft: json['has_face_nft'] as bool?, - setBirthday: json['set_birthday'] as bool?, ); } diff --git a/lib/models_new/article/article_info/data.dart b/lib/models_new/article/article_info/data.dart index 5f74a4b76c..232a7b0287 100644 --- a/lib/models_new/article/article_info/data.dart +++ b/lib/models_new/article/article_info/data.dart @@ -1,86 +1,26 @@ -import 'package:PiliPlus/models_new/article/article_info/share_channel.dart'; import 'package:PiliPlus/models_new/article/article_info/stats.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class ArticleInfoData { - int? like; - bool? attention; bool? favorite; - num? coin; Stats? stats; String? title; - String? bannerUrl; - int? mid; - String? authorName; - bool? isAuthor; - List? imageUrls; List? originImageUrls; - bool? shareable; - bool? showLaterWatch; - bool? showSmallWindow; - bool? inList; - int? pre; - int? next; - List? shareChannels; - int? type; - String? videoUrl; - String? location; - bool? disableShare; ArticleInfoData({ - this.like, - this.attention, this.favorite, - this.coin, this.stats, this.title, - this.bannerUrl, - this.mid, - this.authorName, - this.isAuthor, - this.imageUrls, this.originImageUrls, - this.shareable, - this.showLaterWatch, - this.showSmallWindow, - this.inList, - this.pre, - this.next, - this.shareChannels, - this.type, - this.videoUrl, - this.location, - this.disableShare, }); factory ArticleInfoData.fromJson(Map json) => ArticleInfoData( - like: json['like'] as int?, - attention: json['attention'] as bool?, favorite: json['favorite'] as bool?, - coin: json['coin'] as num?, stats: json['stats'] == null ? null : Stats.fromJson(json['stats'] as Map), title: json['title'] as String?, - bannerUrl: json['banner_url'] as String?, - mid: json['mid'] as int?, - authorName: json['author_name'] as String?, - isAuthor: json['is_author'] as bool?, - imageUrls: (json['image_urls'] as List?)?.fromCast(), originImageUrls: (json['origin_image_urls'] as List?)?.fromCast(), - shareable: json['shareable'] as bool?, - showLaterWatch: json['show_later_watch'] as bool?, - showSmallWindow: json['show_small_window'] as bool?, - inList: json['in_list'] as bool?, - pre: json['pre'] as int?, - next: json['next'] as int?, - shareChannels: (json['share_channels'] as List?) - ?.map((e) => ShareChannel.fromJson(e as Map)) - .toList(), - type: json['type'] as int?, - videoUrl: json['video_url'] as String?, - location: json['location'] as String?, - disableShare: json['disable_share'] as bool?, ); } diff --git a/lib/models_new/article/article_info/share_channel.dart b/lib/models_new/article/article_info/share_channel.dart deleted file mode 100644 index 5eb5eab04d..0000000000 --- a/lib/models_new/article/article_info/share_channel.dart +++ /dev/null @@ -1,13 +0,0 @@ -class ShareChannel { - String? name; - String? picture; - String? shareChannel; - - ShareChannel({this.name, this.picture, this.shareChannel}); - - factory ShareChannel.fromJson(Map json) => ShareChannel( - name: json['name'] as String?, - picture: json['picture'] as String?, - shareChannel: json['share_channel'] as String?, - ); -} diff --git a/lib/models_new/article/article_info/stats.dart b/lib/models_new/article/article_info/stats.dart index 590b8c79a7..5a00717a31 100644 --- a/lib/models_new/article/article_info/stats.dart +++ b/lib/models_new/article/article_info/stats.dart @@ -1,32 +1,20 @@ class Stats { - int? view; int? favorite; int? like; - int? dislike; int? reply; int? share; - num? coin; - int? dynam1c; Stats({ - this.view, this.favorite, this.like, - this.dislike, this.reply, this.share, - this.coin, - this.dynam1c, }); factory Stats.fromJson(Map json) => Stats( - view: json['view'] as int?, favorite: json['favorite'] as int?, like: json['like'] as int?, - dislike: json['dislike'] as int?, reply: json['reply'] as int?, share: json['share'] as int?, - coin: json['coin'] as num?, - dynam1c: json['dynamic'] as int?, ); } diff --git a/lib/models_new/article/article_list/article.dart b/lib/models_new/article/article_list/article.dart index 7767a3c6dd..e50777222a 100644 --- a/lib/models_new/article/article_list/article.dart +++ b/lib/models_new/article/article_list/article.dart @@ -1,67 +1,32 @@ -import 'package:PiliPlus/models_new/article/article_list/category.dart'; import 'package:PiliPlus/models_new/article/article_list/stats.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class ArticleListItemModel { int? id; String? title; - int? state; - int? publishTime; - int? words; List? imageUrls; - Category? category; - List? categories; String? summary; - int? type; String? dynIdStr; - int? attributes; - int? authorUid; - int? onlyFans; Stats? stats; - int? likeState; ArticleListItemModel({ this.id, this.title, - this.state, - this.publishTime, - this.words, this.imageUrls, - this.category, - this.categories, this.summary, - this.type, this.dynIdStr, - this.attributes, - this.authorUid, - this.onlyFans, this.stats, - this.likeState, }); factory ArticleListItemModel.fromJson(Map json) => ArticleListItemModel( id: json['id'] as int?, title: json['title'] as String?, - state: json['state'] as int?, - publishTime: json['publish_time'] as int?, - words: json['words'] as int?, imageUrls: (json['image_urls'] as List?)?.fromCast(), - category: json['category'] == null - ? null - : Category.fromJson(json['category'] as Map), - categories: (json['categories'] as List?) - ?.map((e) => Category.fromJson(e as Map)) - .toList(), summary: json['summary'] as String?, - type: json['type'] as int?, dynIdStr: json['dyn_id_str'] as String?, - attributes: json['attributes'] as int?, - authorUid: json['author_uid'] as int?, - onlyFans: json['only_fans'] as int?, stats: json['stats'] == null ? null : Stats.fromJson(json['stats'] as Map), - likeState: json['like_state'] as int?, ); } diff --git a/lib/models_new/article/article_list/category.dart b/lib/models_new/article/article_list/category.dart deleted file mode 100644 index 7b202e7013..0000000000 --- a/lib/models_new/article/article_list/category.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Category { - int? id; - int? parentId; - String? name; - - Category({this.id, this.parentId, this.name}); - - factory Category.fromJson(Map json) => Category( - id: json['id'] as int?, - parentId: json['parent_id'] as int?, - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/article/article_list/data.dart b/lib/models_new/article/article_list/data.dart index a9435b52f0..5fd09fb145 100644 --- a/lib/models_new/article/article_list/data.dart +++ b/lib/models_new/article/article_list/data.dart @@ -1,21 +1,16 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/article/article_list/article.dart'; -import 'package:PiliPlus/models_new/article/article_list/last.dart'; import 'package:PiliPlus/models_new/article/article_list/list.dart'; class ArticleListData { ArticleListInfo? list; List? articles; Owner? author; - Last? last; - bool? attention; ArticleListData({ this.list, this.articles, this.author, - this.last, - this.attention, }); factory ArticleListData.fromJson(Map json) => @@ -31,9 +26,5 @@ class ArticleListData { author: json['author'] == null ? null : Owner.fromJson(json['author'] as Map), - last: json['last'] == null - ? null - : Last.fromJson(json['last'] as Map), - attention: json['attention'] as bool?, ); } diff --git a/lib/models_new/article/article_list/label.dart b/lib/models_new/article/article_list/label.dart deleted file mode 100644 index 638ea14b2e..0000000000 --- a/lib/models_new/article/article_list/label.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Label { - String? path; - String? text; - String? labelTheme; - - Label({this.path, this.text, this.labelTheme}); - - factory Label.fromJson(Map json) => Label( - path: json['path'] as String?, - text: json['text'] as String?, - labelTheme: json['label_theme'] as String?, - ); -} diff --git a/lib/models_new/article/article_list/last.dart b/lib/models_new/article/article_list/last.dart deleted file mode 100644 index ae283f843f..0000000000 --- a/lib/models_new/article/article_list/last.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:PiliPlus/models_new/article/article_list/category.dart'; -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; - -class Last { - int? id; - String? title; - int? state; - int? publishTime; - int? words; - List? imageUrls; - Category? category; - dynamic categories; - String? summary; - int? type; - String? dynIdStr; - int? attributes; - int? authorUid; - int? onlyFans; - - Last({ - this.id, - this.title, - this.state, - this.publishTime, - this.words, - this.imageUrls, - this.category, - this.categories, - this.summary, - this.type, - this.dynIdStr, - this.attributes, - this.authorUid, - this.onlyFans, - }); - - factory Last.fromJson(Map json) => Last( - id: json['id'] as int?, - title: json['title'] as String?, - state: json['state'] as int?, - publishTime: json['publish_time'] as int?, - words: json['words'] as int?, - imageUrls: (json['image_urls'] as List?)?.fromCast(), - category: json['category'] == null - ? null - : Category.fromJson(json['category'] as Map), - categories: json['categories'] as dynamic, - summary: json['summary'] as String?, - type: json['type'] as int?, - dynIdStr: json['dyn_id_str'] as String?, - attributes: json['attributes'] as int?, - authorUid: json['author_uid'] as int?, - onlyFans: json['only_fans'] as int?, - ); -} diff --git a/lib/models_new/article/article_list/list.dart b/lib/models_new/article/article_list/list.dart index 3c349e1b29..069fd1b813 100644 --- a/lib/models_new/article/article_list/list.dart +++ b/lib/models_new/article/article_list/list.dart @@ -1,54 +1,30 @@ class ArticleListInfo { int? id; - int? mid; String? name; String? imageUrl; int? updateTime; - int? ctime; - int? publishTime; - String? summary; int? words; int? read; int? articlesCount; - int? state; - String? reason; - String? applyTime; - String? checkTime; ArticleListInfo({ this.id, - this.mid, this.name, this.imageUrl, this.updateTime, - this.ctime, - this.publishTime, - this.summary, this.words, this.read, this.articlesCount, - this.state, - this.reason, - this.applyTime, - this.checkTime, }); factory ArticleListInfo.fromJson(Map json) => ArticleListInfo( id: json['id'] as int?, - mid: json['mid'] as int?, name: json['name'] as String?, imageUrl: json['image_url'] as String?, updateTime: json['update_time'] as int?, - ctime: json['ctime'] as int?, - publishTime: json['publish_time'] as int?, - summary: json['summary'] as String?, words: json['words'] as int?, read: json['read'] as int?, articlesCount: json['articles_count'] as int?, - state: json['state'] as int?, - reason: json['reason'] as String?, - applyTime: json['apply_time'] as String?, - checkTime: json['check_time'] as String?, ); } diff --git a/lib/models_new/article/article_list/stats.dart b/lib/models_new/article/article_list/stats.dart index 590b8c79a7..81f6cf6e30 100644 --- a/lib/models_new/article/article_list/stats.dart +++ b/lib/models_new/article/article_list/stats.dart @@ -1,32 +1,17 @@ class Stats { int? view; - int? favorite; int? like; - int? dislike; int? reply; - int? share; - num? coin; - int? dynam1c; Stats({ this.view, - this.favorite, this.like, - this.dislike, this.reply, - this.share, - this.coin, - this.dynam1c, }); factory Stats.fromJson(Map json) => Stats( view: json['view'] as int?, - favorite: json['favorite'] as int?, like: json['like'] as int?, - dislike: json['dislike'] as int?, reply: json['reply'] as int?, - share: json['share'] as int?, - coin: json['coin'] as num?, - dynam1c: json['dynamic'] as int?, ); } diff --git a/lib/models_new/article/article_view/category.dart b/lib/models_new/article/article_view/category.dart deleted file mode 100644 index 7b202e7013..0000000000 --- a/lib/models_new/article/article_view/category.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Category { - int? id; - int? parentId; - String? name; - - Category({this.id, this.parentId, this.name}); - - factory Category.fromJson(Map json) => Category( - id: json['id'] as int?, - parentId: json['parent_id'] as int?, - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/article/article_view/data.dart b/lib/models_new/article/article_view/data.dart index 29d8e90e91..e5586f627a 100644 --- a/lib/models_new/article/article_view/data.dart +++ b/lib/models_new/article/article_view/data.dart @@ -1,95 +1,28 @@ import 'package:PiliPlus/models/model_avatar.dart'; -import 'package:PiliPlus/models_new/article/article_view/category.dart'; -import 'package:PiliPlus/models_new/article/article_view/media.dart'; import 'package:PiliPlus/models_new/article/article_view/ops.dart'; import 'package:PiliPlus/models_new/article/article_view/opus.dart'; -import 'package:PiliPlus/models_new/article/article_view/stats.dart'; -import 'package:PiliPlus/models_new/article/article_view/tag.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class ArticleViewData { int? id; - Category? category; - List? categories; String? title; - String? summary; - String? bannerUrl; - int? templateId; - int? state; Avatar? author; - int? reprint; - List? imageUrls; int? publishTime; - int? ctime; - int? mtime; - Stats? stats; - List? tags; - int? words; List? originImageUrls; - dynamic list; - bool? isLike; - Media? media; - String? applyTime; - String? checkTime; - int? original; - int? actId; - dynamic dispute; - dynamic authenMark; - int? coverAvid; - dynamic topVideoInfo; int? type; - int? checkState; - int? originTemplateId; - int? privatePub; - dynamic contentPicList; String? content; - String? keywords; - int? versionId; String? dynIdStr; - int? totalArtNum; ArticleOpus? opus; List? ops; ArticleViewData({ this.id, - this.category, - this.categories, - this.title, - this.summary, - this.bannerUrl, - this.templateId, - this.state, this.author, - this.reprint, - this.imageUrls, this.publishTime, - this.ctime, - this.mtime, - this.stats, - this.tags, - this.words, this.originImageUrls, - this.list, - this.isLike, - this.media, - this.applyTime, - this.checkTime, - this.original, - this.actId, - this.dispute, - this.authenMark, - this.coverAvid, - this.topVideoInfo, this.type, - this.checkState, - this.originTemplateId, - this.privatePub, - this.contentPicList, this.content, - this.keywords, - this.versionId, this.dynIdStr, - this.totalArtNum, this.opus, this.ops, }); @@ -97,56 +30,14 @@ class ArticleViewData { factory ArticleViewData.fromJson(Map json) => ArticleViewData( id: json['id'] as int?, - category: json['category'] == null - ? null - : Category.fromJson(json['category'] as Map), - categories: (json['categories'] as List?) - ?.map((e) => Category.fromJson(e as Map)) - .toList(), - title: json['title'] as String?, - summary: json['summary'] as String?, - bannerUrl: json['banner_url'] as String?, - templateId: json['template_id'] as int?, - state: json['state'] as int?, author: json['author'] == null ? null : Avatar.fromJson(json['author'] as Map), - reprint: json['reprint'] as int?, - imageUrls: (json['image_urls'] as List?)?.fromCast(), publishTime: json['publish_time'] as int?, - ctime: json['ctime'] as int?, - mtime: json['mtime'] as int?, - stats: json['stats'] == null - ? null - : Stats.fromJson(json['stats'] as Map), - tags: (json['tags'] as List?) - ?.map((e) => Tag.fromJson(e as Map)) - .toList(), - words: json['words'] as int?, originImageUrls: (json['origin_image_urls'] as List?)?.fromCast(), - list: json['list'] as dynamic, - isLike: json['is_like'] as bool?, - media: json['media'] == null - ? null - : Media.fromJson(json['media'] as Map), - applyTime: json['apply_time'] as String?, - checkTime: json['check_time'] as String?, - original: json['original'] as int?, - actId: json['act_id'] as int?, - dispute: json['dispute'] as dynamic, - authenMark: json['authenMark'] as dynamic, - coverAvid: json['cover_avid'] as int?, - topVideoInfo: json['top_video_info'] as dynamic, type: json['type'] as int?, - checkState: json['check_state'] as int?, - originTemplateId: json['origin_template_id'] as int?, - privatePub: json['private_pub'] as int?, - contentPicList: json['content_pic_list'] as dynamic, content: json['content'] as String?, - keywords: json['keywords'] as String?, - versionId: json['version_id'] as int?, dynIdStr: json['dyn_id_str'] as String?, - totalArtNum: json['total_art_num'] as int?, opus: json['opus'] == null ? null : ArticleOpus.fromJson(json['opus'] as Map), diff --git a/lib/models_new/article/article_view/label.dart b/lib/models_new/article/article_view/label.dart deleted file mode 100644 index 638ea14b2e..0000000000 --- a/lib/models_new/article/article_view/label.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Label { - String? path; - String? text; - String? labelTheme; - - Label({this.path, this.text, this.labelTheme}); - - factory Label.fromJson(Map json) => Label( - path: json['path'] as String?, - text: json['text'] as String?, - labelTheme: json['label_theme'] as String?, - ); -} diff --git a/lib/models_new/article/article_view/media.dart b/lib/models_new/article/article_view/media.dart deleted file mode 100644 index 83c707baf5..0000000000 --- a/lib/models_new/article/article_view/media.dart +++ /dev/null @@ -1,35 +0,0 @@ -class Media { - int? score; - int? mediaId; - String? title; - String? cover; - String? area; - int? typeId; - String? typeName; - int? spoiler; - int? seasonId; - - Media({ - this.score, - this.mediaId, - this.title, - this.cover, - this.area, - this.typeId, - this.typeName, - this.spoiler, - this.seasonId, - }); - - factory Media.fromJson(Map json) => Media( - score: json['score'] as int?, - mediaId: json['media_id'] as int?, - title: json['title'] as String?, - cover: json['cover'] as String?, - area: json['area'] as String?, - typeId: json['type_id'] as int?, - typeName: json['type_name'] as String?, - spoiler: json['spoiler'] as int?, - seasonId: json['season_id'] as int?, - ); -} diff --git a/lib/models_new/article/article_view/opus.dart b/lib/models_new/article/article_view/opus.dart index cc1cbaf56a..5ad695bcd3 100644 --- a/lib/models_new/article/article_view/opus.dart +++ b/lib/models_new/article/article_view/opus.dart @@ -1,15 +1,9 @@ import 'package:PiliPlus/models/dynamics/article_content_model.dart'; class ArticleOpus { - int? opusid; - int? opussource; - String? title; List? content; ArticleOpus.fromJson(Map json) { - opusid = json['opus_id']; - opussource = json['opus_source']; - title = json['title']; if (json['content']?['paragraphs'] case List list) { content = list.map((i) => ArticleContentModel.fromJson(i)).toList(); } diff --git a/lib/models_new/article/article_view/stats.dart b/lib/models_new/article/article_view/stats.dart deleted file mode 100644 index 590b8c79a7..0000000000 --- a/lib/models_new/article/article_view/stats.dart +++ /dev/null @@ -1,32 +0,0 @@ -class Stats { - int? view; - int? favorite; - int? like; - int? dislike; - int? reply; - int? share; - num? coin; - int? dynam1c; - - Stats({ - this.view, - this.favorite, - this.like, - this.dislike, - this.reply, - this.share, - this.coin, - this.dynam1c, - }); - - factory Stats.fromJson(Map json) => Stats( - view: json['view'] as int?, - favorite: json['favorite'] as int?, - like: json['like'] as int?, - dislike: json['dislike'] as int?, - reply: json['reply'] as int?, - share: json['share'] as int?, - coin: json['coin'] as num?, - dynam1c: json['dynamic'] as int?, - ); -} diff --git a/lib/models_new/article/article_view/tag.dart b/lib/models_new/article/article_view/tag.dart deleted file mode 100644 index 33fe703253..0000000000 --- a/lib/models_new/article/article_view/tag.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Tag { - int? tid; - String? name; - - Tag({this.tid, this.name}); - - factory Tag.fromJson(Map json) => Tag( - tid: json['tid'] as int?, - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/blacklist/data.dart b/lib/models_new/blacklist/data.dart index 7741df5b5b..1cf97c7806 100644 --- a/lib/models_new/blacklist/data.dart +++ b/lib/models_new/blacklist/data.dart @@ -2,16 +2,14 @@ import 'package:PiliPlus/models_new/blacklist/list.dart'; class BlackListData { List? list; - int? reVersion; int? total; - BlackListData({this.list, this.reVersion, this.total}); + BlackListData({this.list, this.total}); factory BlackListData.fromJson(Map json) => BlackListData( list: (json['list'] as List?) ?.map((e) => BlackListItem.fromJson(e as Map)) .toList(), - reVersion: json['re_version'] as int?, total: json['total'] as int?, ); } diff --git a/lib/models_new/blacklist/list.dart b/lib/models_new/blacklist/list.dart index edbd86a1c1..ff54766a68 100644 --- a/lib/models_new/blacklist/list.dart +++ b/lib/models_new/blacklist/list.dart @@ -1,61 +1,20 @@ -import 'package:PiliPlus/models/model_avatar.dart'; - class BlackListItem { int? mid; - int? attribute; int? mtime; - dynamic tag; - int? special; String? uname; String? face; - String? sign; - int? faceNft; - BaseOfficialVerify? officialVerify; - Vip? vip; - String? nftIcon; - String? recReason; - String? trackId; - String? followTime; BlackListItem({ this.mid, - this.attribute, this.mtime, - this.tag, - this.special, this.uname, this.face, - this.sign, - this.faceNft, - this.officialVerify, - this.vip, - this.nftIcon, - this.recReason, - this.trackId, - this.followTime, }); factory BlackListItem.fromJson(Map json) => BlackListItem( mid: json['mid'] as int?, - attribute: json['attribute'] as int?, mtime: json['mtime'] as int?, - tag: json['tag'] as dynamic, - special: json['special'] as int?, uname: json['uname'] as String?, face: json['face'] as String?, - sign: json['sign'] as String?, - faceNft: json['face_nft'] as int?, - officialVerify: json['official_verify'] == null - ? null - : BaseOfficialVerify.fromJson( - json['official_verify'] as Map, - ), - vip: json['vip'] == null - ? null - : Vip.fromJson(json['vip'] as Map), - nftIcon: json['nft_icon'] as String?, - recReason: json['rec_reason'] as String?, - trackId: json['track_id'] as String?, - followTime: json['follow_time'] as String?, ); } diff --git a/lib/models_new/bubble/base_info.dart b/lib/models_new/bubble/base_info.dart new file mode 100644 index 0000000000..66f5fae387 --- /dev/null +++ b/lib/models_new/bubble/base_info.dart @@ -0,0 +1,15 @@ +import 'package:PiliPlus/models_new/bubble/tribee_info.dart'; + +class BaseInfo { + TribeInfo? tribeInfo; + bool? isJoined; + + BaseInfo({this.tribeInfo, this.isJoined}); + + factory BaseInfo.fromJson(Map json) => BaseInfo( + tribeInfo: json['tribee_info'] == null + ? null + : TribeInfo.fromJson(json['tribee_info'] as Map), + isJoined: json['is_joined'] as bool?, + ); +} diff --git a/lib/models_new/bubble/basic_info.dart b/lib/models_new/bubble/basic_info.dart new file mode 100644 index 0000000000..0e0282e08d --- /dev/null +++ b/lib/models_new/bubble/basic_info.dart @@ -0,0 +1,13 @@ +class BasicInfo { + String? icon; + String? title; + String? jumpUri; + + BasicInfo({this.icon, this.title, this.jumpUri}); + + factory BasicInfo.fromJson(Map json) => BasicInfo( + icon: json['icon'] as String?, + title: json['title'] as String?, + jumpUri: json['jump_uri'] as String?, + ); +} diff --git a/lib/models_new/bubble/category.dart b/lib/models_new/bubble/category.dart new file mode 100644 index 0000000000..175182d0ba --- /dev/null +++ b/lib/models_new/bubble/category.dart @@ -0,0 +1,13 @@ +import 'package:PiliPlus/models_new/bubble/category_list.dart'; + +class Category { + List? categoryList; + + Category({this.categoryList}); + + factory Category.fromJson(Map json) => Category( + categoryList: (json['category_list'] as List?) + ?.map((e) => CategoryList.fromJson(e as Map)) + .toList(), + ); +} diff --git a/lib/models_new/bubble/category_list.dart b/lib/models_new/bubble/category_list.dart new file mode 100644 index 0000000000..dbff1b53c1 --- /dev/null +++ b/lib/models_new/bubble/category_list.dart @@ -0,0 +1,13 @@ +class CategoryList { + String? id; + String? name; + int? type; + + CategoryList({this.id, this.name, this.type}); + + factory CategoryList.fromJson(Map json) => CategoryList( + id: json['id'] as String?, + name: json['name'] as String?, + type: json['type'] as int?, + ); +} diff --git a/lib/models_new/bubble/content.dart b/lib/models_new/bubble/content.dart new file mode 100644 index 0000000000..06ba8495b9 --- /dev/null +++ b/lib/models_new/bubble/content.dart @@ -0,0 +1,15 @@ +import 'package:PiliPlus/models_new/bubble/dyn_list.dart'; + +class Content { + String? count; + List? dynList; + + Content({this.count, this.dynList}); + + factory Content.fromJson(Map json) => Content( + count: json['count'] as String?, + dynList: (json['dyn_list'] as List?) + ?.map((e) => DynList.fromJson(e as Map)) + .toList(), + ); +} diff --git a/lib/models_new/bubble/data.dart b/lib/models_new/bubble/data.dart new file mode 100644 index 0000000000..ea224cf4f8 --- /dev/null +++ b/lib/models_new/bubble/data.dart @@ -0,0 +1,33 @@ +import 'package:PiliPlus/models_new/bubble/base_info.dart'; +import 'package:PiliPlus/models_new/bubble/category.dart'; +import 'package:PiliPlus/models_new/bubble/content.dart'; +import 'package:PiliPlus/models_new/bubble/sort_info.dart'; + +class BubbleData { + BaseInfo? baseInfo; + Content? content; + Category? category; + SortInfo? sortInfo; + + BubbleData({ + this.baseInfo, + this.content, + this.category, + this.sortInfo, + }); + + factory BubbleData.fromJson(Map json) => BubbleData( + baseInfo: json['base_info'] == null + ? null + : BaseInfo.fromJson(json['base_info'] as Map), + content: json['content'] == null + ? null + : Content.fromJson(json['content'] as Map), + category: json['category'] == null + ? null + : Category.fromJson(json['category'] as Map), + sortInfo: json['sort_info'] == null + ? null + : SortInfo.fromJson(json['sort_info'] as Map), + ); +} diff --git a/lib/models_new/bubble/dyn_list.dart b/lib/models_new/bubble/dyn_list.dart new file mode 100644 index 0000000000..373d0342a2 --- /dev/null +++ b/lib/models_new/bubble/dyn_list.dart @@ -0,0 +1,21 @@ +import 'package:PiliPlus/models_new/bubble/meta.dart'; + +class DynList { + String? dynId; + String? title; + Meta? meta; + + DynList({ + this.dynId, + this.title, + this.meta, + }); + + factory DynList.fromJson(Map json) => DynList( + dynId: json['dyn_id'] as String?, + title: json['title'] as String?, + meta: json['meta'] == null + ? null + : Meta.fromJson(json['meta'] as Map), + ); +} diff --git a/lib/models_new/bubble/meta.dart b/lib/models_new/bubble/meta.dart new file mode 100644 index 0000000000..bf59b6888c --- /dev/null +++ b/lib/models_new/bubble/meta.dart @@ -0,0 +1,20 @@ +class Meta { + String? author; + String? timeText; + String? replyCount; + String? viewStat; + + Meta({ + this.author, + this.timeText, + this.replyCount, + this.viewStat, + }); + + factory Meta.fromJson(Map json) => Meta( + author: json['author'] as String?, + timeText: json['time_text'] as String?, + replyCount: json['reply_count'] as String?, + viewStat: json['view_stat'] as String?, + ); +} diff --git a/lib/models_new/bubble/sort_info.dart b/lib/models_new/bubble/sort_info.dart new file mode 100644 index 0000000000..510de6021b --- /dev/null +++ b/lib/models_new/bubble/sort_info.dart @@ -0,0 +1,21 @@ +import 'package:PiliPlus/models_new/bubble/sort_item.dart'; + +class SortInfo { + bool? showSort; + List? sortItems; + int? curSortType; + + SortInfo({ + this.showSort, + this.sortItems, + this.curSortType, + }); + + factory SortInfo.fromJson(Map json) => SortInfo( + showSort: json['show_sort'] as bool?, + sortItems: (json['sort_items'] as List?) + ?.map((e) => SortItem.fromJson(e as Map)) + .toList(), + curSortType: json['cur_sort_type'] as int?, + ); +} diff --git a/lib/models_new/bubble/sort_item.dart b/lib/models_new/bubble/sort_item.dart new file mode 100644 index 0000000000..0dbb2c73cf --- /dev/null +++ b/lib/models_new/bubble/sort_item.dart @@ -0,0 +1,11 @@ +class SortItem { + int? sortType; + String? text; + + SortItem({this.sortType, this.text}); + + factory SortItem.fromJson(Map json) => SortItem( + sortType: json['sort_type'] as int?, + text: json['text'] as String?, + ); +} diff --git a/lib/models_new/bubble/tribee_info.dart b/lib/models_new/bubble/tribee_info.dart new file mode 100644 index 0000000000..6f2074e9b9 --- /dev/null +++ b/lib/models_new/bubble/tribee_info.dart @@ -0,0 +1,26 @@ +class TribeInfo { + String? id; + String? title; + String? subTitle; + String? faceUrl; + String? jumpUri; + String? summary; + + TribeInfo({ + this.id, + this.title, + this.subTitle, + this.faceUrl, + this.jumpUri, + this.summary, + }); + + factory TribeInfo.fromJson(Map json) => TribeInfo( + id: json['id'] as String?, + title: json['title'] as String?, + subTitle: json['sub_title'] as String?, + faceUrl: json['face_url'] as String?, + jumpUri: json['jump_uri'] as String?, + summary: json['summary'] as String?, + ); +} diff --git a/lib/models_new/coin_log/data.dart b/lib/models_new/coin_log/data.dart index bba0316641..dff395016d 100644 --- a/lib/models_new/coin_log/data.dart +++ b/lib/models_new/coin_log/data.dart @@ -1,15 +1,13 @@ import 'package:PiliPlus/models_new/coin_log/list.dart'; class CoinLogData { - List? list; - int? count; + CoinLogData({this.list}); - CoinLogData({this.list, this.count}); + List? list; factory CoinLogData.fromJson(Map json) => CoinLogData( list: (json['list'] as List?) ?.map((e) => CoinLogItem.fromJson(e as Map)) .toList(), - count: json['count'] as int?, ); } diff --git a/lib/models_new/coin_log/list.dart b/lib/models_new/coin_log/list.dart index 6e9b291a79..030b6f695d 100644 --- a/lib/models_new/coin_log/list.dart +++ b/lib/models_new/coin_log/list.dart @@ -1,14 +1,14 @@ class CoinLogItem { - final String time; - final String delta; - final String reason; - const CoinLogItem({ required this.time, required this.delta, required this.reason, }); + final String time; + final String delta; + final String reason; + factory CoinLogItem.fromJson(Map json) => CoinLogItem( time: json['time'], delta: (json['delta'] as num).toString(), diff --git a/lib/models_new/danmaku/post.dart b/lib/models_new/danmaku/post.dart index 678934aa3f..6f9bb3c1c6 100644 --- a/lib/models_new/danmaku/post.dart +++ b/lib/models_new/danmaku/post.dart @@ -1,31 +1,13 @@ class DanmakuPost { DanmakuPost({ - required this.action, - required this.animation, - required this.colorfulSrc, - required this.dmContent, required this.dmid, - required this.dmidStr, - required this.visible, }); - final String? action; - final String? animation; - final dynamic colorfulSrc; - final String? dmContent; final int? dmid; - final String? dmidStr; - final bool? visible; factory DanmakuPost.fromJson(Map json) { return DanmakuPost( - action: json["action"], - animation: json["animation"], - colorfulSrc: json["colorful_src"], - dmContent: json["dm_content"], dmid: json["dmid"], - dmidStr: json["dmid_str"], - visible: json["visible"], ); } } diff --git a/lib/models_new/download/bili_download_entry_info.dart b/lib/models_new/download/bili_download_entry_info.dart index b92835e5d6..e1059b50dc 100644 --- a/lib/models_new/download/bili_download_entry_info.dart +++ b/lib/models_new/download/bili_download_entry_info.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/pages/common/multi_select/base.dart' show MultiSelectData; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/path_utils.dart'; +import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -60,7 +61,7 @@ class BiliDownloadEntryInfo with MultiSelectData { return title; } - Widget moreBtn(ThemeData theme) => SizedBox( + Widget moreBtn(ColorScheme colorScheme) => SizedBox( width: 29, height: 29, child: PopupMenuButton( @@ -68,16 +69,13 @@ class BiliDownloadEntryInfo with MultiSelectData { position: PopupMenuPosition.under, icon: Icon( Icons.more_vert_outlined, - color: theme.colorScheme.outline, + color: colorScheme.outline, size: 18, ), itemBuilder: (_) => [ PopupMenuItem( height: 38, - child: const Text( - '查看详情页', - style: TextStyle(fontSize: 13), - ), + child: const Text('查看详情页', style: TextStyle(fontSize: 13)), onTap: () { if (ep case final ep?) { if (ep.from == VideoType.pugv.name) { @@ -100,17 +98,38 @@ class BiliDownloadEntryInfo with MultiSelectData { epId: ep?.episodeId, title: title, cover: cover, + isVertical: pageData?.isVertical ?? false, ); }, ), + if (PlatformUtils.isDesktop) + PopupMenuItem( + height: 38, + child: const Text('打开本地文件夹', style: TextStyle(fontSize: 13)), + onTap: () async { + try { + final String executable; + if (Platform.isWindows) { + executable = 'explorer'; + } else if (Platform.isMacOS) { + executable = 'open'; + } else if (Platform.isLinux) { + executable = 'xdg-open'; + } else { + throw UnimplementedError(); + } + await Process.run(executable, [entryDirPath]); + } catch (e) { + SmartDialog.showToast(e.toString()); + } + }, + ), if (ownerId case final mid?) PopupMenuItem( height: 38, child: Text( '访问${ownerName != null ? ':$ownerName' : '用户主页'}', - style: const TextStyle( - fontSize: 13, - ), + style: const TextStyle(fontSize: 13), ), onTap: () => Get.toNamed('/member?mid=$mid'), ), @@ -128,16 +147,20 @@ class BiliDownloadEntryInfo with MultiSelectData { Future shareSelf() async { final xFiles = []; - final videoFileName = - mediaType == 1 ? PathUtils.videoNameType1 : PathUtils.videoNameType2; + final videoFileName = mediaType == 1 + ? PathUtils.videoNameType1 + : PathUtils.videoNameType2; final videoPath = path.join(entryDirPath, typeTag, videoFileName); final videoFile = File(videoPath); if (videoFile.existsSync()) { xFiles.add(XFile(videoPath, name: '$title.mp4')); } if (mediaType != 1 && hasDashAudio) { - final audioPath = - path.join(entryDirPath, typeTag, PathUtils.audioNameType2); + final audioPath = path.join( + entryDirPath, + typeTag, + PathUtils.audioNameType2, + ); final audioFile = File(audioPath); if (audioFile.existsSync()) { xFiles.add(XFile(audioPath, name: '${title}_audio.m4s')); @@ -279,6 +302,8 @@ class PageInfo { bool get cacheWidth => width <= height; + bool get isVertical => rotate == 1 ? width > height : height > width; + PageInfo({ required this.cid, required this.page, @@ -433,7 +458,7 @@ enum DownloadStatus { failDanmaku('获取弹幕失败'), failPlayUrl('获取播放地址失败'), pause('暂停中'), - wait('等待中') + wait('等待中'), ; final String message; diff --git a/lib/models_new/dynamic/dyn_mention/group.dart b/lib/models_new/dynamic/dyn_mention/group.dart index 711309a5f5..6d82649ed9 100644 --- a/lib/models_new/dynamic/dyn_mention/group.dart +++ b/lib/models_new/dynamic/dyn_mention/group.dart @@ -2,14 +2,12 @@ import 'package:PiliPlus/models_new/dynamic/dyn_mention/item.dart'; class MentionGroup { String? groupName; - int? groupType; List? items; - MentionGroup({this.groupName, this.groupType, this.items}); + MentionGroup({this.groupName, this.items}); factory MentionGroup.fromJson(Map json) => MentionGroup( groupName: json['group_name'] as String?, - groupType: json['group_type'] as int?, items: (json['items'] as List?) ?.map((e) => MentionItem.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/dynamic/dyn_mention/item.dart b/lib/models_new/dynamic/dyn_mention/item.dart index 7d3572f4e7..c5207a66c8 100644 --- a/lib/models_new/dynamic/dyn_mention/item.dart +++ b/lib/models_new/dynamic/dyn_mention/item.dart @@ -4,14 +4,12 @@ class MentionItem with MultiSelectData { final String? face; final int? fans; final String? name; - final int? officialVerifyType; final String? uid; MentionItem({ this.face, this.fans, this.name, - this.officialVerifyType, this.uid, }); @@ -19,7 +17,6 @@ class MentionItem with MultiSelectData { face: json['face'] as String?, fans: json['fans'] as int?, name: json['name'] as String?, - officialVerifyType: json['official_verify_type'] as int?, uid: json['uid'] as String?, ); diff --git a/lib/models_new/dynamic/dyn_reaction/data.dart b/lib/models_new/dynamic/dyn_reaction/data.dart new file mode 100644 index 0000000000..16269d99f5 --- /dev/null +++ b/lib/models_new/dynamic/dyn_reaction/data.dart @@ -0,0 +1,20 @@ +import 'package:PiliPlus/models_new/dynamic/dyn_reaction/item.dart'; + +class DynReactionData { + bool? hasMore; + List? items; + String? offset; + int total; + + DynReactionData({this.hasMore, this.items, this.offset, required this.total}); + + factory DynReactionData.fromJson(Map json) => + DynReactionData( + hasMore: json['has_more'] as bool?, + items: (json['items'] as List?) + ?.map((e) => DynReactionItem.fromJson(e as Map)) + .toList(), + offset: json['offset'] as String?, + total: json['total'] as int? ?? 0, + ); +} diff --git a/lib/models_new/dynamic/dyn_reaction/item.dart b/lib/models_new/dynamic/dyn_reaction/item.dart new file mode 100644 index 0000000000..2743cd7221 --- /dev/null +++ b/lib/models_new/dynamic/dyn_reaction/item.dart @@ -0,0 +1,21 @@ +class DynReactionItem { + String? action; + String? face; + String? mid; + String? name; + + DynReactionItem({ + this.action, + this.face, + this.mid, + this.name, + }); + + factory DynReactionItem.fromJson(Map json) => + DynReactionItem( + action: json['action'] as String?, + face: json['face'] as String?, + mid: json['mid'] as String?, + name: json['name'] as String?, + ); +} diff --git a/lib/models_new/dynamic/dyn_reserve/data.dart b/lib/models_new/dynamic/dyn_reserve/data.dart index fd82f63802..3401811513 100644 --- a/lib/models_new/dynamic/dyn_reserve/data.dart +++ b/lib/models_new/dynamic/dyn_reserve/data.dart @@ -1,23 +1,17 @@ class DynReserveData { int? finalBtnStatus; - int? btnMode; int? reserveUpdate; String? descUpdate; - String? toast; DynReserveData({ this.finalBtnStatus, - this.btnMode, this.reserveUpdate, this.descUpdate, - this.toast, }); factory DynReserveData.fromJson(Map json) => DynReserveData( finalBtnStatus: json['final_btn_status'] as int?, - btnMode: json['btn_mode'] as int?, reserveUpdate: json['reserve_update'] as int?, descUpdate: json['desc_update'] as String?, - toast: json['toast'] as String?, ); } diff --git a/lib/models_new/dynamic/dyn_reserve_info/data.dart b/lib/models_new/dynamic/dyn_reserve_info/data.dart index 363c72054c..ad58d70b66 100644 --- a/lib/models_new/dynamic/dyn_reserve_info/data.dart +++ b/lib/models_new/dynamic/dyn_reserve_info/data.dart @@ -1,48 +1,18 @@ class ReserveInfoData { int? id; String? title; - int? stime; - int? etime; - int? type; int? livePlanStartTime; - int? lotteryType; - String? lotteryId; - int? subType; ReserveInfoData({ this.id, this.title, - this.stime, - this.etime, - this.type, this.livePlanStartTime, - this.lotteryType, - this.lotteryId, - this.subType, }); factory ReserveInfoData.fromJson(Map json) => ReserveInfoData( id: json['id'] as int?, title: json['title'] as String?, - stime: json['stime'] as int?, - etime: json['etime'] as int?, - type: json['type'] as int?, livePlanStartTime: json['live_plan_start_time'] as int?, - lotteryType: json['lottery_type'] as int?, - lotteryId: json['lottery_id'] as String?, - subType: json['sub_type'] as int?, ); - - Map toJson() => { - 'id': id, - 'title': title, - 'stime': stime, - 'etime': etime, - 'type': type, - 'live_plan_start_time': livePlanStartTime, - 'lottery_type': lotteryType, - 'lottery_id': lotteryId, - 'sub_type': subType, - }; } diff --git a/lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart b/lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart new file mode 100644 index 0000000000..76cb42762e --- /dev/null +++ b/lib/models_new/dynamic/dyn_topic_feed/fold_card_item.dart @@ -0,0 +1,11 @@ +class FoldCardItem { + int? foldCount; + String? foldDesc; + + FoldCardItem({this.foldCount, this.foldDesc}); + + factory FoldCardItem.fromJson(Map json) => FoldCardItem( + foldCount: json['fold_count'] as int?, + foldDesc: json['fold_desc'] as String?, + ); +} diff --git a/lib/models_new/dynamic/dyn_topic_feed/item.dart b/lib/models_new/dynamic/dyn_topic_feed/item.dart index 78ce310571..112dd769d3 100644 --- a/lib/models_new/dynamic/dyn_topic_feed/item.dart +++ b/lib/models_new/dynamic/dyn_topic_feed/item.dart @@ -1,10 +1,12 @@ import 'package:PiliPlus/models/dynamics/result.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/fold_card_item.dart'; class TopicCardItem { + FoldCardItem? foldCardItem; DynamicItemModel? dynamicCardItem; String? topicType; - TopicCardItem({this.dynamicCardItem, this.topicType}); + TopicCardItem({this.dynamicCardItem, this.foldCardItem, this.topicType}); factory TopicCardItem.fromJson(Map json) => TopicCardItem( dynamicCardItem: json['dynamic_card_item'] == null @@ -12,6 +14,11 @@ class TopicCardItem { : DynamicItemModel.fromJson( json['dynamic_card_item'] as Map, ), + foldCardItem: json['fold_card_item'] == null + ? null + : FoldCardItem.fromJson( + json['fold_card_item'] as Map, + ), topicType: json['topic_type'] as String?, ); } diff --git a/lib/models_new/dynamic/dyn_topic_feed/topic_sort_by_conf.dart b/lib/models_new/dynamic/dyn_topic_feed/topic_sort_by_conf.dart index bd0a74794a..407413e1d0 100644 --- a/lib/models_new/dynamic/dyn_topic_feed/topic_sort_by_conf.dart +++ b/lib/models_new/dynamic/dyn_topic_feed/topic_sort_by_conf.dart @@ -2,17 +2,15 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/all_sort_by.dart'; class TopicSortByConf { List? allSortBy; - int? defaultSortBy; int? showSortBy; - TopicSortByConf({this.allSortBy, this.defaultSortBy, this.showSortBy}); + TopicSortByConf({this.allSortBy, this.showSortBy}); factory TopicSortByConf.fromJson(Map json) { return TopicSortByConf( allSortBy: (json['all_sort_by'] as List?) ?.map((e) => AllSortBy.fromJson(e as Map)) .toList(), - defaultSortBy: json['default_sort_by'] as int?, showSortBy: json['show_sort_by'] as int?, ); } diff --git a/lib/models_new/dynamic/dyn_topic_pub_search/data.dart b/lib/models_new/dynamic/dyn_topic_pub_search/data.dart index ca05cb7e3c..73c2a59318 100644 --- a/lib/models_new/dynamic/dyn_topic_pub_search/data.dart +++ b/lib/models_new/dynamic/dyn_topic_pub_search/data.dart @@ -1,32 +1,20 @@ -import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/new_topic.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/page_info.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; class TopicPubSearchData { - NewTopic? newTopic; - bool? hasCreateJurisdiction; List? topicItems; - String? requestId; PageInfo? pageInfo; TopicPubSearchData({ - this.newTopic, - this.hasCreateJurisdiction, this.topicItems, - this.requestId, this.pageInfo, }); factory TopicPubSearchData.fromJson(Map json) => TopicPubSearchData( - newTopic: json['new_topic'] == null - ? null - : NewTopic.fromJson(json['new_topic'] as Map), - hasCreateJurisdiction: json['has_create_jurisdiction'] as bool?, topicItems: (json['topic_items'] as List?) ?.map((e) => TopicItem.fromJson(e as Map)) .toList(), - requestId: json['request_id'] as String?, pageInfo: json['page_info'] == null ? null : PageInfo.fromJson(json['page_info'] as Map), diff --git a/lib/models_new/dynamic/dyn_topic_pub_search/new_topic.dart b/lib/models_new/dynamic/dyn_topic_pub_search/new_topic.dart deleted file mode 100644 index e8c2db534f..0000000000 --- a/lib/models_new/dynamic/dyn_topic_pub_search/new_topic.dart +++ /dev/null @@ -1,9 +0,0 @@ -class NewTopic { - String? name; - - NewTopic({this.name}); - - factory NewTopic.fromJson(Map json) => NewTopic( - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/dynamic/dyn_topic_pub_search/page_info.dart b/lib/models_new/dynamic/dyn_topic_pub_search/page_info.dart index c175a7f17d..61ea5daf69 100644 --- a/lib/models_new/dynamic/dyn_topic_pub_search/page_info.dart +++ b/lib/models_new/dynamic/dyn_topic_pub_search/page_info.dart @@ -1,11 +1,9 @@ class PageInfo { - int? offset; bool? hasMore; - PageInfo({this.offset, this.hasMore}); + PageInfo({this.hasMore}); factory PageInfo.fromJson(Map json) => PageInfo( - offset: json['offset'] as int?, hasMore: json['has_more'] as bool?, ); } diff --git a/lib/models_new/dynamic/dyn_topic_top/top_details.dart b/lib/models_new/dynamic/dyn_topic_top/top_details.dart index 2be417e3c0..68d749e36a 100644 --- a/lib/models_new/dynamic/dyn_topic_top/top_details.dart +++ b/lib/models_new/dynamic/dyn_topic_top/top_details.dart @@ -4,16 +4,10 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; class TopDetails { TopicItem? topicItem; TopicCreator? topicCreator; - bool? hasCreateJurisdiction; - int? wordColor; - bool? closePubLayerEntry; TopDetails({ this.topicItem, this.topicCreator, - this.hasCreateJurisdiction, - this.wordColor, - this.closePubLayerEntry, }); factory TopDetails.fromJson(Map json) => TopDetails( @@ -23,8 +17,5 @@ class TopDetails { topicCreator: json['topic_creator'] == null ? null : TopicCreator.fromJson(json['topic_creator'] as Map), - hasCreateJurisdiction: json['has_create_jurisdiction'] as bool?, - wordColor: json['word_color'] as int?, - closePubLayerEntry: json['close_pub_layer_entry'] as bool?, ); } diff --git a/lib/models_new/dynamic/dyn_topic_top/topic_item.dart b/lib/models_new/dynamic/dyn_topic_top/topic_item.dart index 4996a13e62..d4bb93083e 100644 --- a/lib/models_new/dynamic/dyn_topic_top/topic_item.dart +++ b/lib/models_new/dynamic/dyn_topic_top/topic_item.dart @@ -5,14 +5,7 @@ class TopicItem { int discuss; int fav; int like; - int? dynamics; - String? jumpUrl; - String? backColor; String? description; - String? sharePic; - String? shareUrl; - int? ctime; - bool? showInteractData; bool? isFav; bool? isLike; @@ -23,14 +16,7 @@ class TopicItem { required this.discuss, required this.fav, required this.like, - this.dynamics, - this.jumpUrl, - this.backColor, this.description, - this.sharePic, - this.shareUrl, - this.ctime, - this.showInteractData, this.isFav, this.isLike, }); @@ -42,14 +28,7 @@ class TopicItem { discuss: json['discuss'] ?? 0, fav: json['fav'] ?? 0, like: json['like'] ?? 0, - dynamics: json['dynamics'] as int?, - jumpUrl: json['jump_url'] as String?, - backColor: json['back_color'] as String?, description: json['description'] as String?, - sharePic: json['share_pic'] as String?, - shareUrl: json['share_url'] as String?, - ctime: json['ctime'] as int?, - showInteractData: json['show_interact_data'] as bool?, isFav: json['is_fav'] as bool?, isLike: json['is_like'] as bool?, ); diff --git a/lib/models_new/fav/fav_article/author.dart b/lib/models_new/fav/fav_article/author.dart index 778aae8967..997e904fc8 100644 --- a/lib/models_new/fav/fav_article/author.dart +++ b/lib/models_new/fav/fav_article/author.dart @@ -1,13 +1,9 @@ class Author { String? name; - String? face; - String? mid; - Author({this.name, this.face, this.mid}); + Author({this.name}); factory Author.fromJson(Map json) => Author( name: json['name'] as String?, - face: json['face'] as String?, - mid: json['mid'] as String?, ); } diff --git a/lib/models_new/fav/fav_article/cover.dart b/lib/models_new/fav/fav_article/cover.dart index abcf6001a4..6463e37862 100644 --- a/lib/models_new/fav/fav_article/cover.dart +++ b/lib/models_new/fav/fav_article/cover.dart @@ -1,13 +1,9 @@ class Cover { String? url; - int? width; - int? height; - Cover({this.url, this.width, this.height}); + Cover({this.url}); factory Cover.fromJson(Map json) => Cover( url: json['url'] as String?, - width: json['width'] as int?, - height: json['height'] as int?, ); } diff --git a/lib/models_new/fav/fav_article/data.dart b/lib/models_new/fav/fav_article/data.dart index 8f326f4ef6..6342b8f117 100644 --- a/lib/models_new/fav/fav_article/data.dart +++ b/lib/models_new/fav/fav_article/data.dart @@ -3,16 +3,10 @@ import 'package:PiliPlus/models_new/fav/fav_article/item.dart'; class FavArticleData { List? items; bool? hasMore; - String? offset; - String? updateNum; - String? updateBaseline; FavArticleData({ this.items, this.hasMore, - this.offset, - this.updateNum, - this.updateBaseline, }); factory FavArticleData.fromJson(Map json) => FavArticleData( @@ -20,8 +14,5 @@ class FavArticleData { ?.map((e) => FavArticleItemModel.fromJson(e as Map)) .toList(), hasMore: json['has_more'] as bool?, - offset: json['offset'] as String?, - updateNum: json['update_num'] as String?, - updateBaseline: json['update_baseline'] as String?, ); } diff --git a/lib/models_new/fav/fav_article/item.dart b/lib/models_new/fav/fav_article/item.dart index 07b40b7a86..e3f0ca1a49 100644 --- a/lib/models_new/fav/fav_article/item.dart +++ b/lib/models_new/fav/fav_article/item.dart @@ -3,20 +3,16 @@ import 'package:PiliPlus/models_new/fav/fav_article/cover.dart'; import 'package:PiliPlus/models_new/fav/fav_article/stat.dart'; class FavArticleItemModel { - String? jumpUrl; String? opusId; String? content; - dynamic badge; Author? author; Cover? cover; Stat? stat; String? pubTime; FavArticleItemModel({ - this.jumpUrl, this.opusId, this.content, - this.badge, this.author, this.cover, this.stat, @@ -25,10 +21,8 @@ class FavArticleItemModel { factory FavArticleItemModel.fromJson(Map json) => FavArticleItemModel( - jumpUrl: json['jump_url'] as String?, opusId: json['opus_id'] as String?, content: json['content'] as String?, - badge: json['badge'] as dynamic, author: json['author'] == null ? null : Author.fromJson(json['author'] as Map), diff --git a/lib/models_new/fav/fav_article/stat.dart b/lib/models_new/fav/fav_article/stat.dart index e16802ff61..228afc060f 100644 --- a/lib/models_new/fav/fav_article/stat.dart +++ b/lib/models_new/fav/fav_article/stat.dart @@ -1,11 +1,9 @@ class Stat { - String? view; String? like; - Stat({this.view, this.like}); + Stat({this.like}); factory Stat.fromJson(Map json) => Stat( - view: json['view'] as String?, like: json['like'] as String?, ); } diff --git a/lib/models_new/fav/fav_detail/cnt_info.dart b/lib/models_new/fav/fav_detail/cnt_info.dart index 6f390a2925..e646792c92 100644 --- a/lib/models_new/fav/fav_detail/cnt_info.dart +++ b/lib/models_new/fav/fav_detail/cnt_info.dart @@ -1,41 +1,15 @@ class CntInfo { - int? collect; int? play; - int? thumbUp; - int? thumbDown; - int? share; - int? reply; int? danmaku; - num? coin; - int? vt; - int? playSwitch; - String? viewText1; CntInfo({ - this.collect, this.play, - this.thumbUp, - this.thumbDown, - this.share, - this.reply, + this.danmaku, - this.coin, - this.vt, - this.playSwitch, - this.viewText1, }); factory CntInfo.fromJson(Map json) => CntInfo( - collect: json['collect'] as int?, play: json['play'] as int?, - thumbUp: json['thumb_up'] as int?, - thumbDown: json['thumb_down'] as int?, - share: json['share'] as int?, - reply: json['reply'] as int?, danmaku: json['danmaku'] as int?, - coin: json['coin'] as num?, - vt: json['vt'] as int?, - playSwitch: json['play_switch'] as int?, - viewText1: json['view_text_1'] as String?, ); } diff --git a/lib/models_new/fav/fav_detail/data.dart b/lib/models_new/fav/fav_detail/data.dart index 53a11ab217..72d4265892 100644 --- a/lib/models_new/fav/fav_detail/data.dart +++ b/lib/models_new/fav/fav_detail/data.dart @@ -5,9 +5,8 @@ class FavDetailData { FavFolderInfo? info; List? medias; bool? hasMore; - int? ttl; - FavDetailData({this.info, this.medias, this.hasMore, this.ttl}); + FavDetailData({this.info, this.medias, this.hasMore}); factory FavDetailData.fromJson(Map json) => FavDetailData( info: json['info'] == null @@ -17,6 +16,5 @@ class FavDetailData { ?.map((e) => FavDetailItemModel.fromJson(e as Map)) .toList(), hasMore: json['has_more'] as bool?, - ttl: json['ttl'] as int?, ); } diff --git a/lib/models_new/fav/fav_detail/info.dart b/lib/models_new/fav/fav_detail/info.dart deleted file mode 100644 index a6e31cc7f8..0000000000 --- a/lib/models_new/fav/fav_detail/info.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:PiliPlus/models/model_owner.dart'; -import 'package:PiliPlus/models_new/fav/fav_detail/cnt_info.dart'; - -class FavDetailInfo { - int? id; - int? fid; - int? mid; - int? attr; - String? title; - String? cover; - Owner? upper; - int? coverType; - CntInfo? cntInfo; - int? type; - String? intro; - int? ctime; - int? mtime; - int? state; - int? favState; - int? likeState; - int? mediaCount; - bool? isTop; - - FavDetailInfo({ - this.id, - this.fid, - this.mid, - this.attr, - this.title, - this.cover, - this.upper, - this.coverType, - this.cntInfo, - this.type, - this.intro, - this.ctime, - this.mtime, - this.state, - this.favState, - this.likeState, - this.mediaCount, - this.isTop, - }); - - factory FavDetailInfo.fromJson(Map json) => FavDetailInfo( - id: json['id'] as int?, - fid: json['fid'] as int?, - mid: json['mid'] as int?, - attr: json['attr'] as int?, - title: json['title'] as String?, - cover: json['cover'] as String?, - upper: json['upper'] == null - ? null - : Owner.fromJson(json['upper'] as Map), - coverType: json['cover_type'] as int?, - cntInfo: json['cnt_info'] == null - ? null - : CntInfo.fromJson(json['cnt_info'] as Map), - type: json['type'] as int?, - intro: json['intro'] as String?, - ctime: json['ctime'] as int?, - mtime: json['mtime'] as int?, - state: json['state'] as int?, - favState: json['fav_state'] as int?, - likeState: json['like_state'] as int?, - mediaCount: json['media_count'] as int?, - isTop: json['is_top'] as bool?, - ); -} diff --git a/lib/models_new/fav/fav_detail/media.dart b/lib/models_new/fav/fav_detail/media.dart index e81b6cafce..662a34e84e 100644 --- a/lib/models_new/fav/fav_detail/media.dart +++ b/lib/models_new/fav/fav_detail/media.dart @@ -10,19 +10,14 @@ class FavDetailItemModel with MultiSelectData { String? title; String? cover; String? intro; - int? page; int? duration; Owner? upper; int? attr; CntInfo? cntInfo; - String? link; - int? ctime; - int? pubtime; int? favTime; String? bvid; Ogv? ogv; Ugc? ugc; - String? mediaListLink; FavDetailItemModel({ this.id, @@ -30,19 +25,14 @@ class FavDetailItemModel with MultiSelectData { this.title, this.cover, this.intro, - this.page, this.duration, this.upper, this.attr, this.cntInfo, - this.link, - this.ctime, - this.pubtime, this.favTime, this.bvid, this.ogv, this.ugc, - this.mediaListLink, }); factory FavDetailItemModel.fromJson(Map json) => @@ -52,7 +42,6 @@ class FavDetailItemModel with MultiSelectData { title: json['title'] as String?, cover: json['cover'] as String?, intro: json['intro'] as String?, - page: json['page'] as int?, duration: json['duration'] as int?, upper: json['upper'] == null ? null @@ -61,15 +50,11 @@ class FavDetailItemModel with MultiSelectData { cntInfo: json['cnt_info'] == null ? null : CntInfo.fromJson(json['cnt_info'] as Map), - link: json['link'] as String?, - ctime: json['ctime'] as int?, - pubtime: json['pubtime'] as int?, favTime: json['fav_time'] as int?, bvid: json['bvid'] ?? json['bv_id'], ogv: json['ogv'] == null ? null : Ogv.fromJson(json['ogv']), ugc: json['ugc'] == null ? null : Ugc.fromJson(json['ugc'] as Map), - mediaListLink: json['media_list_link'] as String?, ); } diff --git a/lib/models_new/fav/fav_detail/ogv.dart b/lib/models_new/fav/fav_detail/ogv.dart index a560a46efd..47ede51361 100644 --- a/lib/models_new/fav/fav_detail/ogv.dart +++ b/lib/models_new/fav/fav_detail/ogv.dart @@ -1,17 +1,14 @@ class Ogv { String? typeName; - int? typeId; int? seasonId; Ogv({ this.typeName, - this.typeId, this.seasonId, }); factory Ogv.fromJson(Map json) => Ogv( typeName: json['type_name'], - typeId: json['type_id'], seasonId: json['season_id'], ); } diff --git a/lib/models_new/fav/fav_folder/list.dart b/lib/models_new/fav/fav_folder/list.dart index 16eb9ac71d..ec8e4482ae 100644 --- a/lib/models_new/fav/fav_folder/list.dart +++ b/lib/models_new/fav/fav_folder/list.dart @@ -5,42 +5,24 @@ class FavFolderInfo { int? fid; int mid; int attr; - String? attrDesc; String title; String cover; Owner? upper; - int? coverType; String? intro; - int? ctime; - int? mtime; - int? state; int? favState; int mediaCount; - int? viewCount; - bool? isTop; - int? type; - String? bvid; FavFolderInfo({ this.id = 0, this.fid, this.mid = 0, this.attr = -1, - this.attrDesc, this.title = '', this.cover = '', this.upper, - this.coverType, this.intro, - this.ctime, - this.mtime, - this.state, this.favState, this.mediaCount = 0, - this.viewCount, - this.isTop, - this.type, - this.bvid, }); factory FavFolderInfo.fromJson(Map json) => FavFolderInfo( @@ -48,22 +30,13 @@ class FavFolderInfo { fid: json['fid'] as int?, mid: json['mid'] as int? ?? 0, attr: json['attr'] as int? ?? 0, - attrDesc: json['attr_desc'] as String?, title: json['title'] as String? ?? '', cover: json['cover'] as String? ?? '', upper: json['upper'] == null ? null : Owner.fromJson(json['upper'] as Map), - coverType: json['cover_type'] as int?, intro: json['intro'] as String?, - ctime: json['ctime'] as int?, - mtime: json['mtime'] as int?, - state: json['state'] as int?, favState: json['fav_state'] as int?, mediaCount: json['media_count'] as int? ?? 0, - viewCount: json['view_count'] as int?, - isTop: json['is_top'] as bool?, - type: json['type'] as int?, - bvid: json['bvid'] as String?, ); } diff --git a/lib/models_new/fav/fav_note/arc.dart b/lib/models_new/fav/fav_note/arc.dart deleted file mode 100644 index 710c3761f0..0000000000 --- a/lib/models_new/fav/fav_note/arc.dart +++ /dev/null @@ -1,29 +0,0 @@ -class Arc { - int? oid; - String? bvid; - String? pic; - String? desc; - int? status; - int? oidType; - int? aid; - - Arc({ - this.oid, - this.bvid, - this.pic, - this.desc, - this.status, - this.oidType, - this.aid, - }); - - factory Arc.fromJson(Map json) => Arc( - oid: json['oid'] as int?, - bvid: json['bvid'] as String?, - pic: json['pic'] as String?, - desc: json['desc'] as String?, - status: json['status'] as int?, - oidType: json['oid_type'] as int?, - aid: json['aid'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_note/data.dart b/lib/models_new/fav/fav_note/data.dart index 37d446d366..316cb859ce 100644 --- a/lib/models_new/fav/fav_note/data.dart +++ b/lib/models_new/fav/fav_note/data.dart @@ -1,18 +1,13 @@ import 'package:PiliPlus/models_new/fav/fav_note/list.dart'; -import 'package:PiliPlus/models_new/fav/fav_note/page.dart'; class FavNoteData { List? list; - Page? page; - FavNoteData({this.list, this.page}); + FavNoteData({this.list}); factory FavNoteData.fromJson(Map json) => FavNoteData( list: (json['list'] as List?) ?.map((e) => FavNoteItemModel.fromJson(e as Map)) .toList(), - page: json['page'] == null - ? null - : Page.fromJson(json['page'] as Map), ); } diff --git a/lib/models_new/fav/fav_note/page.dart b/lib/models_new/fav/fav_note/page.dart deleted file mode 100644 index 22984a0714..0000000000 --- a/lib/models_new/fav/fav_note/page.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Page { - int? total; - int? size; - int? num; - - Page({this.total, this.size, this.num}); - - factory Page.fromJson(Map json) => Page( - total: json['total'] as int?, - size: json['size'] as int?, - num: json['num'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/area.dart b/lib/models_new/fav/fav_pgc/area.dart deleted file mode 100644 index 8540bf56fc..0000000000 --- a/lib/models_new/fav/fav_pgc/area.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Area { - int? id; - String? name; - - Area({this.id, this.name}); - - factory Area.fromJson(Map json) => Area( - id: json['id'] as int?, - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/badge_info.dart b/lib/models_new/fav/fav_pgc/badge_info.dart deleted file mode 100644 index fd1c023d31..0000000000 --- a/lib/models_new/fav/fav_pgc/badge_info.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:PiliPlus/models_new/fav/fav_pgc/multi_img.dart'; - -class BadgeInfo { - String? text; - String? bgColor; - String? bgColorNight; - String? img; - MultiImg? multiImg; - - BadgeInfo({ - this.text, - this.bgColor, - this.bgColorNight, - this.img, - this.multiImg, - }); - - factory BadgeInfo.fromJson(Map json) => BadgeInfo( - text: json['text'] as String?, - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - img: json['img'] as String?, - multiImg: json['multi_img'] == null - ? null - : MultiImg.fromJson(json['multi_img'] as Map), - ); -} diff --git a/lib/models_new/fav/fav_pgc/badge_infos.dart b/lib/models_new/fav/fav_pgc/badge_infos.dart deleted file mode 100644 index 5c70bdd3b8..0000000000 --- a/lib/models_new/fav/fav_pgc/badge_infos.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:PiliPlus/models_new/fav/fav_pgc/content_attr.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/vip_or_pay.dart'; - -class BadgeInfos { - ContentAttr? contentAttr; - VipOrPay? vipOrPay; - - BadgeInfos({this.contentAttr, this.vipOrPay}); - - factory BadgeInfos.fromJson(Map json) => BadgeInfos( - contentAttr: json['content_attr'] == null - ? null - : ContentAttr.fromJson(json['content_attr'] as Map), - vipOrPay: json['vip_or_pay'] == null - ? null - : VipOrPay.fromJson(json['vip_or_pay'] as Map), - ); -} diff --git a/lib/models_new/fav/fav_pgc/cc_on_lock.dart b/lib/models_new/fav/fav_pgc/cc_on_lock.dart deleted file mode 100644 index a83feefecc..0000000000 --- a/lib/models_new/fav/fav_pgc/cc_on_lock.dart +++ /dev/null @@ -1,9 +0,0 @@ -class CcOnLock { - String? typeUrl; - - CcOnLock({this.typeUrl}); - - factory CcOnLock.fromJson(Map json) => CcOnLock( - typeUrl: json['type_url'] as String?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/config_attrs.dart b/lib/models_new/fav/fav_pgc/config_attrs.dart deleted file mode 100644 index 1668484e9e..0000000000 --- a/lib/models_new/fav/fav_pgc/config_attrs.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:PiliPlus/models_new/fav/fav_pgc/cc_on_lock.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/highlight_ineffective_hd.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/highlight_ineffective_ott.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/highlight_ineffective_pink.dart'; - -class ConfigAttrs { - CcOnLock? ccOnLock; - HighlightIneffectiveHd? highlightIneffectiveHd; - HighlightIneffectiveOtt? highlightIneffectiveOtt; - HighlightIneffectivePink? highlightIneffectivePink; - - ConfigAttrs({ - this.ccOnLock, - this.highlightIneffectiveHd, - this.highlightIneffectiveOtt, - this.highlightIneffectivePink, - }); - - factory ConfigAttrs.fromJson(Map json) => ConfigAttrs( - ccOnLock: json['cc_on_lock'] == null - ? null - : CcOnLock.fromJson(json['cc_on_lock'] as Map), - highlightIneffectiveHd: json['highlight_ineffective_hd'] == null - ? null - : HighlightIneffectiveHd.fromJson( - json['highlight_ineffective_hd'] as Map, - ), - highlightIneffectiveOtt: json['highlight_ineffective_ott'] == null - ? null - : HighlightIneffectiveOtt.fromJson( - json['highlight_ineffective_ott'] as Map, - ), - highlightIneffectivePink: json['highlight_ineffective_pink'] == null - ? null - : HighlightIneffectivePink.fromJson( - json['highlight_ineffective_pink'] as Map, - ), - ); -} diff --git a/lib/models_new/fav/fav_pgc/content_attr.dart b/lib/models_new/fav/fav_pgc/content_attr.dart deleted file mode 100644 index ecb4beba08..0000000000 --- a/lib/models_new/fav/fav_pgc/content_attr.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:PiliPlus/models_new/fav/fav_pgc/multi_img.dart'; - -class ContentAttr { - String? text; - String? bgColor; - String? bgColorNight; - String? img; - MultiImg? multiImg; - - ContentAttr({ - this.text, - this.bgColor, - this.bgColorNight, - this.img, - this.multiImg, - }); - - factory ContentAttr.fromJson(Map json) => ContentAttr( - text: json['text'] as String?, - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - img: json['img'] as String?, - multiImg: json['multi_img'] == null - ? null - : MultiImg.fromJson(json['multi_img'] as Map), - ); -} diff --git a/lib/models_new/fav/fav_pgc/data.dart b/lib/models_new/fav/fav_pgc/data.dart index db84fd9b3f..24ed24b73e 100644 --- a/lib/models_new/fav/fav_pgc/data.dart +++ b/lib/models_new/fav/fav_pgc/data.dart @@ -2,18 +2,14 @@ import 'package:PiliPlus/models_new/fav/fav_pgc/list.dart'; class FavPgcData { List? list; - int? pn; - int? ps; int? total; - FavPgcData({this.list, this.pn, this.ps, this.total}); + FavPgcData({this.list, this.total}); factory FavPgcData.fromJson(Map json) => FavPgcData( list: (json['list'] as List?) ?.map((e) => FavPgcItemModel.fromJson(e as Map)) .toList(), - pn: json['pn'] as int?, - ps: json['ps'] as int?, total: json['total'] as int?, ); } diff --git a/lib/models_new/fav/fav_pgc/first_ep_info.dart b/lib/models_new/fav/fav_pgc/first_ep_info.dart deleted file mode 100644 index 452f637def..0000000000 --- a/lib/models_new/fav/fav_pgc/first_ep_info.dart +++ /dev/null @@ -1,26 +0,0 @@ -class FirstEpInfo { - int? id; - String? cover; - String? title; - String? longTitle; - String? pubTime; - int? duration; - - FirstEpInfo({ - this.id, - this.cover, - this.title, - this.longTitle, - this.pubTime, - this.duration, - }); - - factory FirstEpInfo.fromJson(Map json) => FirstEpInfo( - id: json['id'] as int?, - cover: json['cover'] as String?, - title: json['title'] as String?, - longTitle: json['long_title'] as String?, - pubTime: json['pub_time'] as String?, - duration: json['duration'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/highlight_ineffective_hd.dart b/lib/models_new/fav/fav_pgc/highlight_ineffective_hd.dart deleted file mode 100644 index 38753864b9..0000000000 --- a/lib/models_new/fav/fav_pgc/highlight_ineffective_hd.dart +++ /dev/null @@ -1,11 +0,0 @@ -class HighlightIneffectiveHd { - String? typeUrl; - - HighlightIneffectiveHd({this.typeUrl}); - - factory HighlightIneffectiveHd.fromJson(Map json) { - return HighlightIneffectiveHd( - typeUrl: json['type_url'] as String?, - ); - } -} diff --git a/lib/models_new/fav/fav_pgc/highlight_ineffective_ott.dart b/lib/models_new/fav/fav_pgc/highlight_ineffective_ott.dart deleted file mode 100644 index 60961e665d..0000000000 --- a/lib/models_new/fav/fav_pgc/highlight_ineffective_ott.dart +++ /dev/null @@ -1,11 +0,0 @@ -class HighlightIneffectiveOtt { - String? typeUrl; - - HighlightIneffectiveOtt({this.typeUrl}); - - factory HighlightIneffectiveOtt.fromJson(Map json) { - return HighlightIneffectiveOtt( - typeUrl: json['type_url'] as String?, - ); - } -} diff --git a/lib/models_new/fav/fav_pgc/highlight_ineffective_pink.dart b/lib/models_new/fav/fav_pgc/highlight_ineffective_pink.dart deleted file mode 100644 index 3f0433e384..0000000000 --- a/lib/models_new/fav/fav_pgc/highlight_ineffective_pink.dart +++ /dev/null @@ -1,11 +0,0 @@ -class HighlightIneffectivePink { - String? typeUrl; - - HighlightIneffectivePink({this.typeUrl}); - - factory HighlightIneffectivePink.fromJson(Map json) { - return HighlightIneffectivePink( - typeUrl: json['type_url'] as String?, - ); - } -} diff --git a/lib/models_new/fav/fav_pgc/list.dart b/lib/models_new/fav/fav_pgc/list.dart index 70a711fcde..5dacc8ce84 100644 --- a/lib/models_new/fav/fav_pgc/list.dart +++ b/lib/models_new/fav/fav_pgc/list.dart @@ -1,208 +1,39 @@ -import 'package:PiliPlus/models_new/fav/fav_pgc/area.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/badge_info.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/badge_infos.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/config_attrs.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/first_ep_info.dart'; import 'package:PiliPlus/models_new/fav/fav_pgc/new_ep.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/producer.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/publish.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/rating.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/rights.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/section.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/series.dart'; -import 'package:PiliPlus/models_new/fav/fav_pgc/stat.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class FavPgcItemModel with MultiSelectData { int? seasonId; - int? mediaId; - int? seasonType; - String? seasonTypeName; String? title; String? cover; - int? totalCount; int? isFinish; - int? isStarted; - int? isPlay; String? badge; - int? badgeType; - Rights? rights; - Stat? stat; NewEp? newEp; - Rating? rating; - String? squareCover; - int? seasonStatus; - String? seasonTitle; - String? badgeEp; - int? mediaAttr; - int? seasonAttr; - String? evaluate; - List? areas; - String? subtitle; - int? firstEp; - int? canWatch; - Series? series; - Publish? publish; - int? mode; - List
? section; - String? url; - BadgeInfo? badgeInfo; String? renewalTime; - FirstEpInfo? firstEpInfo; - int? formalEpCount; - String? shortUrl; - BadgeInfos? badgeInfos; - String? seasonVersion; - String? horizontalCover169; - String? horizontalCover1610; - String? subtitle14; - int? viewableCrowdType; - List? producers; - String? summary; - List? styles; - ConfigAttrs? configAttrs; - int? followStatus; - int? isNew; String? progress; - bool? bothFollow; - String? subtitle25; FavPgcItemModel({ this.seasonId, - this.mediaId, - this.seasonType, - this.seasonTypeName, this.title, this.cover, - this.totalCount, this.isFinish, - this.isStarted, - this.isPlay, this.badge, - this.badgeType, - this.rights, - this.stat, this.newEp, - this.rating, - this.squareCover, - this.seasonStatus, - this.seasonTitle, - this.badgeEp, - this.mediaAttr, - this.seasonAttr, - this.evaluate, - this.areas, - this.subtitle, - this.firstEp, - this.canWatch, - this.series, - this.publish, - this.mode, - this.section, - this.url, - this.badgeInfo, this.renewalTime, - this.firstEpInfo, - this.formalEpCount, - this.shortUrl, - this.badgeInfos, - this.seasonVersion, - this.horizontalCover169, - this.horizontalCover1610, - this.subtitle14, - this.viewableCrowdType, - this.producers, - this.summary, - this.styles, - this.configAttrs, - this.followStatus, - this.isNew, this.progress, - this.bothFollow, - this.subtitle25, }); factory FavPgcItemModel.fromJson( Map json, ) => FavPgcItemModel( seasonId: json['season_id'] as int?, - mediaId: json['media_id'] as int?, - seasonType: json['season_type'] as int?, - seasonTypeName: json['season_type_name'] as String?, title: json['title'] as String?, cover: json['cover'] as String?, - totalCount: json['total_count'] as int?, isFinish: json['is_finish'] as int?, - isStarted: json['is_started'] as int?, - isPlay: json['is_play'] as int?, badge: json['badge'] as String?, - badgeType: json['badge_type'] as int?, - rights: json['rights'] == null - ? null - : Rights.fromJson(json['rights'] as Map), - stat: json['stat'] == null - ? null - : Stat.fromJson(json['stat'] as Map), newEp: json['new_ep'] == null ? null : NewEp.fromJson(json['new_ep'] as Map), - rating: json['rating'] == null - ? null - : Rating.fromJson(json['rating'] as Map), - squareCover: json['square_cover'] as String?, - seasonStatus: json['season_status'] as int?, - seasonTitle: json['season_title'] as String?, - badgeEp: json['badge_ep'] as String?, - mediaAttr: json['media_attr'] as int?, - seasonAttr: json['season_attr'] as int?, - evaluate: json['evaluate'] as String?, - areas: (json['areas'] as List?) - ?.map((e) => Area.fromJson(e as Map)) - .toList(), - subtitle: json['subtitle'] as String?, - firstEp: json['first_ep'] as int?, - canWatch: json['can_watch'] as int?, - series: json['series'] == null - ? null - : Series.fromJson(json['series'] as Map), - publish: json['publish'] == null - ? null - : Publish.fromJson(json['publish'] as Map), - mode: json['mode'] as int?, - section: (json['section'] as List?) - ?.map((e) => Section.fromJson(e as Map)) - .toList(), - url: json['url'] as String?, - badgeInfo: json['badge_info'] == null - ? null - : BadgeInfo.fromJson(json['badge_info'] as Map), renewalTime: json['renewal_time'] as String?, - firstEpInfo: json['first_ep_info'] == null - ? null - : FirstEpInfo.fromJson(json['first_ep_info'] as Map), - formalEpCount: json['formal_ep_count'] as int?, - shortUrl: json['short_url'] as String?, - badgeInfos: json['badge_infos'] == null - ? null - : BadgeInfos.fromJson(json['badge_infos'] as Map), - seasonVersion: json['season_version'] as String?, - horizontalCover169: json['horizontal_cover_16_9'] as String?, - horizontalCover1610: json['horizontal_cover_16_10'] as String?, - subtitle14: json['subtitle_14'] as String?, - viewableCrowdType: json['viewable_crowd_type'] as int?, - producers: (json['producers'] as List?) - ?.map((e) => Producer.fromJson(e as Map)) - .toList(), - summary: json['summary'] as String?, - styles: (json['styles'] as List?)?.fromCast(), - configAttrs: json['config_attrs'] == null - ? null - : ConfigAttrs.fromJson(json['config_attrs'] as Map), - followStatus: json['follow_status'] as int?, - isNew: json['is_new'] as int?, progress: json['progress'] == '' ? null : json['progress'], - bothFollow: json['both_follow'] as bool?, - subtitle25: json['subtitle_25'] as String?, ); } diff --git a/lib/models_new/fav/fav_pgc/multi_img.dart b/lib/models_new/fav/fav_pgc/multi_img.dart deleted file mode 100644 index fbb762ba76..0000000000 --- a/lib/models_new/fav/fav_pgc/multi_img.dart +++ /dev/null @@ -1,11 +0,0 @@ -class MultiImg { - String? color; - String? mediumRemind; - - MultiImg({this.color, this.mediumRemind}); - - factory MultiImg.fromJson(Map json) => MultiImg( - color: json['color'] as String?, - mediumRemind: json['medium_remind'] as String?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/new_ep.dart b/lib/models_new/fav/fav_pgc/new_ep.dart index 6c1bda5db5..909748e80b 100644 --- a/lib/models_new/fav/fav_pgc/new_ep.dart +++ b/lib/models_new/fav/fav_pgc/new_ep.dart @@ -1,29 +1,11 @@ class NewEp { - int? id; String? indexShow; - String? cover; - String? title; - String? longTitle; - String? pubTime; - int? duration; NewEp({ - this.id, this.indexShow, - this.cover, - this.title, - this.longTitle, - this.pubTime, - this.duration, }); factory NewEp.fromJson(Map json) => NewEp( - id: json['id'] as int?, indexShow: json['index_show'] as String?, - cover: json['cover'] as String?, - title: json['title'] as String?, - longTitle: json['long_title'] as String?, - pubTime: json['pub_time'] as String?, - duration: json['duration'] as int?, ); } diff --git a/lib/models_new/fav/fav_pgc/producer.dart b/lib/models_new/fav/fav_pgc/producer.dart deleted file mode 100644 index e08790a572..0000000000 --- a/lib/models_new/fav/fav_pgc/producer.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Producer { - int? mid; - int? type; - int? isContribute; - String? title; - - Producer({this.mid, this.type, this.isContribute, this.title}); - - factory Producer.fromJson(Map json) => Producer( - mid: json['mid'] as int?, - type: json['type'] as int?, - isContribute: json['is_contribute'] as int?, - title: json['title'] as String?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/publish.dart b/lib/models_new/fav/fav_pgc/publish.dart deleted file mode 100644 index a0be984fc8..0000000000 --- a/lib/models_new/fav/fav_pgc/publish.dart +++ /dev/null @@ -1,20 +0,0 @@ -class Publish { - String? pubTime; - String? pubTimeShow; - String? releaseDate; - String? releaseDateShow; - - Publish({ - this.pubTime, - this.pubTimeShow, - this.releaseDate, - this.releaseDateShow, - }); - - factory Publish.fromJson(Map json) => Publish( - pubTime: json['pub_time'] as String?, - pubTimeShow: json['pub_time_show'] as String?, - releaseDate: json['release_date'] as String?, - releaseDateShow: json['release_date_show'] as String?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/rating.dart b/lib/models_new/fav/fav_pgc/rating.dart deleted file mode 100644 index 4b7e6fd9f0..0000000000 --- a/lib/models_new/fav/fav_pgc/rating.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Rating { - double? score; - int? count; - - Rating({this.score, this.count}); - - factory Rating.fromJson(Map json) => Rating( - score: (json['score'] as num?)?.toDouble(), - count: json['count'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/rights.dart b/lib/models_new/fav/fav_pgc/rights.dart deleted file mode 100644 index 3882f251d0..0000000000 --- a/lib/models_new/fav/fav_pgc/rights.dart +++ /dev/null @@ -1,23 +0,0 @@ -class Rights { - int? allowReview; - int? allowPreview; - int? isSelection; - int? selectionStyle; - int? isRcmd; - - Rights({ - this.allowReview, - this.allowPreview, - this.isSelection, - this.selectionStyle, - this.isRcmd, - }); - - factory Rights.fromJson(Map json) => Rights( - allowReview: json['allow_review'] as int?, - allowPreview: json['allow_preview'] as int?, - isSelection: json['is_selection'] as int?, - selectionStyle: json['selection_style'] as int?, - isRcmd: json['is_rcmd'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/section.dart b/lib/models_new/fav/fav_pgc/section.dart deleted file mode 100644 index 100ddd7248..0000000000 --- a/lib/models_new/fav/fav_pgc/section.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; - -class Section { - int? sectionId; - int? seasonId; - int? limitGroup; - int? watchPlatform; - String? copyright; - int? banAreaShow; - List? episodeIds; - - Section({ - this.sectionId, - this.seasonId, - this.limitGroup, - this.watchPlatform, - this.copyright, - this.banAreaShow, - this.episodeIds, - }); - - factory Section.fromJson(Map json) => Section( - sectionId: json['section_id'] as int?, - seasonId: json['season_id'] as int?, - limitGroup: json['limit_group'] as int?, - watchPlatform: json['watch_platform'] as int?, - copyright: json['copyright'] as String?, - banAreaShow: json['ban_area_show'] as int?, - episodeIds: (json['episode_ids'] as List?)?.fromCast(), - ); -} diff --git a/lib/models_new/fav/fav_pgc/series.dart b/lib/models_new/fav/fav_pgc/series.dart deleted file mode 100644 index 82a195e3ef..0000000000 --- a/lib/models_new/fav/fav_pgc/series.dart +++ /dev/null @@ -1,23 +0,0 @@ -class Series { - int? seriesId; - String? title; - int? seasonCount; - int? newSeasonId; - int? seriesOrd; - - Series({ - this.seriesId, - this.title, - this.seasonCount, - this.newSeasonId, - this.seriesOrd, - }); - - factory Series.fromJson(Map json) => Series( - seriesId: json['series_id'] as int?, - title: json['title'] as String?, - seasonCount: json['season_count'] as int?, - newSeasonId: json['new_season_id'] as int?, - seriesOrd: json['series_ord'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/stat.dart b/lib/models_new/fav/fav_pgc/stat.dart deleted file mode 100644 index 07674e8a7a..0000000000 --- a/lib/models_new/fav/fav_pgc/stat.dart +++ /dev/null @@ -1,35 +0,0 @@ -class Stat { - int? follow; - int? view; - int? danmaku; - int? reply; - num? coin; - int? seriesFollow; - int? seriesView; - int? likes; - int? favorite; - - Stat({ - this.follow, - this.view, - this.danmaku, - this.reply, - this.coin, - this.seriesFollow, - this.seriesView, - this.likes, - this.favorite, - }); - - factory Stat.fromJson(Map json) => Stat( - follow: json['follow'] as int?, - view: json['view'] as int?, - danmaku: json['danmaku'] as int?, - reply: json['reply'] as int?, - coin: json['coin'] as num?, - seriesFollow: json['series_follow'] as int?, - seriesView: json['series_view'] as int?, - likes: json['likes'] as int?, - favorite: json['favorite'] as int?, - ); -} diff --git a/lib/models_new/fav/fav_pgc/vip_or_pay.dart b/lib/models_new/fav/fav_pgc/vip_or_pay.dart deleted file mode 100644 index 2b8f305680..0000000000 --- a/lib/models_new/fav/fav_pgc/vip_or_pay.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:PiliPlus/models_new/fav/fav_pgc/multi_img.dart'; - -class VipOrPay { - String? text; - String? bgColor; - String? bgColorNight; - String? img; - MultiImg? multiImg; - - VipOrPay({ - this.text, - this.bgColor, - this.bgColorNight, - this.img, - this.multiImg, - }); - - factory VipOrPay.fromJson(Map json) => VipOrPay( - text: json['text'] as String?, - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - img: json['img'] as String?, - multiImg: json['multi_img'] == null - ? null - : MultiImg.fromJson(json['multi_img'] as Map), - ); -} diff --git a/lib/models_new/fav/fav_topic/page_info.dart b/lib/models_new/fav/fav_topic/page_info.dart index 6974f79cd1..0962db4db3 100644 --- a/lib/models_new/fav/fav_topic/page_info.dart +++ b/lib/models_new/fav/fav_topic/page_info.dart @@ -1,11 +1,9 @@ class PageInfo { - int? curPageNum; int? total; - PageInfo({this.curPageNum, this.total}); + PageInfo({this.total}); factory PageInfo.fromJson(Map json) => PageInfo( - curPageNum: json['cur_page_num'] as int?, total: json['total'] as int?, ); } diff --git a/lib/models_new/fav/fav_topic/topic_item.dart b/lib/models_new/fav/fav_topic/topic_item.dart index e008a87d30..43fdaed888 100644 --- a/lib/models_new/fav/fav_topic/topic_item.dart +++ b/lib/models_new/fav/fav_topic/topic_item.dart @@ -1,29 +1,14 @@ class FavTopicItem { int? id; String? name; - int? view; - int? discuss; - String? jumpUrl; - String? statDesc; - bool? showInteractData; FavTopicItem({ this.id, this.name, - this.view, - this.discuss, - this.jumpUrl, - this.statDesc, - this.showInteractData, }); factory FavTopicItem.fromJson(Map json) => FavTopicItem( id: json['id'] as int?, name: json['name'] as String?, - view: json['view'] as int?, - discuss: json['discuss'] as int?, - jumpUrl: json['jump_url'] as String?, - statDesc: json['stat_desc'] as String?, - showInteractData: json['show_interact_data'] as bool?, ); } diff --git a/lib/models_new/follow/data.dart b/lib/models_new/follow/data.dart index 7efe4ff424..9dedeee524 100644 --- a/lib/models_new/follow/data.dart +++ b/lib/models_new/follow/data.dart @@ -1,17 +1,15 @@ import 'package:PiliPlus/models_new/follow/list.dart'; class FollowData { - late List list; + List? list; int? total; FollowData({required this.list, this.total}); factory FollowData.fromJson(Map json) => FollowData( - list: - (json['list'] as List?) - ?.map((e) => FollowItemModel.fromJson(e as Map)) - .toList() ?? - [], + list: (json['list'] as List?) + ?.map((e) => FollowItemModel.fromJson(e as Map)) + .toList(), total: json['total'] as int?, ); } diff --git a/lib/models_new/follow/list.dart b/lib/models_new/follow/list.dart index f9a53e13fa..92c9c1d485 100644 --- a/lib/models_new/follow/list.dart +++ b/lib/models_new/follow/list.dart @@ -3,35 +3,22 @@ import 'package:PiliPlus/models/model_avatar.dart'; class FollowItemModel extends UpItem { int? attribute; - int? mtime; - dynamic tag; - int? special; String? sign; BaseOfficialVerify? officialVerify; - Vip? vip; - String? followTime; FollowItemModel({ required super.mid, this.attribute, - this.mtime, - this.tag, - this.special, super.uname, super.face, this.sign, this.officialVerify, - this.vip, - this.followTime, }); factory FollowItemModel.fromJson(Map json) => FollowItemModel( mid: json['mid'] as int? ?? 0, attribute: json['attribute'] as int?, - mtime: json['mtime'] as int?, - tag: json['tag'] as dynamic, - special: json['special'] as int?, uname: json['uname'] as String?, face: json['face'] as String?, sign: json['sign'] as String?, @@ -40,9 +27,5 @@ class FollowItemModel extends UpItem { : BaseOfficialVerify.fromJson( json['official_verify'] as Map, ), - vip: json['vip'] == null - ? null - : Vip.fromJson(json['vip'] as Map), - followTime: json['follow_time'] as String?, ); } diff --git a/lib/models_new/history/cursor.dart b/lib/models_new/history/cursor.dart deleted file mode 100644 index a644dcd323..0000000000 --- a/lib/models_new/history/cursor.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Cursor { - int? max; - int? viewAt; - String? business; - int? ps; - - Cursor({this.max, this.viewAt, this.business, this.ps}); - - factory Cursor.fromJson(Map json) => Cursor( - max: json['max'] as int?, - viewAt: json['view_at'] as int?, - business: json['business'] as String?, - ps: json['ps'] as int?, - ); -} diff --git a/lib/models_new/history/data.dart b/lib/models_new/history/data.dart index 3f6e96f02a..624a352e82 100644 --- a/lib/models_new/history/data.dart +++ b/lib/models_new/history/data.dart @@ -1,18 +1,13 @@ -import 'package:PiliPlus/models_new/history/cursor.dart'; import 'package:PiliPlus/models_new/history/list.dart'; import 'package:PiliPlus/models_new/history/tab.dart'; class HistoryData { - Cursor? cursor; List? tab; List? list; - HistoryData({this.cursor, this.tab, this.list}); + HistoryData({this.tab, this.list}); factory HistoryData.fromJson(Map json) => HistoryData( - cursor: json['cursor'] == null - ? null - : Cursor.fromJson(json['cursor'] as Map), tab: (json['tab'] as List?) ?.map((e) => HistoryTab.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/history/history.dart b/lib/models_new/history/history.dart index b9b45f4c2c..5a2e734946 100644 --- a/lib/models_new/history/history.dart +++ b/lib/models_new/history/history.dart @@ -4,7 +4,6 @@ class History { String? bvid; int? page; int? cid; - String? part; String? business; History({ @@ -13,7 +12,6 @@ class History { this.bvid, this.page, this.cid, - this.part, this.business, }); @@ -23,7 +21,6 @@ class History { bvid: json['bvid'], page: json['page'], cid: json['cid'] == 0 ? null : json['cid'], - part: json['part'], business: json['business'], ); } diff --git a/lib/models_new/history/list.dart b/lib/models_new/history/list.dart index 2c9d485f3a..21540a18bf 100644 --- a/lib/models_new/history/list.dart +++ b/lib/models_new/history/list.dart @@ -4,24 +4,18 @@ import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class HistoryItemModel with MultiSelectData { String? title; - String? longTitle; String? cover; List? covers; String? uri; late History history; int? videos; String? authorName; - String? authorFace; int? authorMid; int? viewAt; int? progress; String? badge; String? showTitle; int? duration; - String? current; - int? total; - String? newDesc; - int? isFinish; int? isFav; int? kid; String? tagName; @@ -29,24 +23,18 @@ class HistoryItemModel with MultiSelectData { HistoryItemModel({ this.title, - this.longTitle, this.cover, this.covers, this.uri, required this.history, this.videos, this.authorName, - this.authorFace, this.authorMid, this.viewAt, this.progress, this.badge, this.showTitle, this.duration, - this.current, - this.total, - this.newDesc, - this.isFinish, this.isFav, this.kid, this.tagName, @@ -56,7 +44,6 @@ class HistoryItemModel with MultiSelectData { factory HistoryItemModel.fromJson(Map json) => HistoryItemModel( title: json['title'] as String?, - longTitle: json['long_title'] as String?, cover: json['cover'] as String?, covers: (json['covers'] as List?)?.fromCast(), uri: json['uri'] as String?, @@ -65,17 +52,12 @@ class HistoryItemModel with MultiSelectData { : History.fromJson(json['history'] as Map), videos: json['videos'] as int?, authorName: json['author_name'] as String?, - authorFace: json['author_face'] as String?, authorMid: json['author_mid'] as int?, viewAt: json['view_at'] as int?, progress: json['progress'] as int?, badge: json['badge'] as String?, showTitle: json['show_title'] as String?, duration: json['duration'] as int?, - current: json['current'] as String?, - total: json['total'] as int?, - newDesc: json['new_desc'] as String?, - isFinish: json['is_finish'] as int?, isFav: json['is_fav'] as int?, kid: json['kid'] as int?, tagName: json['tag_name'] as String?, diff --git a/lib/models_new/later/bangumi.dart b/lib/models_new/later/bangumi.dart index 0b1a55efb2..a0dc2df203 100644 --- a/lib/models_new/later/bangumi.dart +++ b/lib/models_new/later/bangumi.dart @@ -2,24 +2,15 @@ import 'package:PiliPlus/models_new/later/season.dart'; class Bangumi { int? epId; - String? title; - String? longTitle; - String? cover; Season? season; Bangumi({ this.epId, - this.title, - this.longTitle, - this.cover, this.season, }); factory Bangumi.fromJson(Map json) => Bangumi( epId: json['ep_id'] as int?, - title: json['title'] as String?, - longTitle: json['long_title'] as String?, - cover: json['cover'] as String?, season: json['season'] == null ? null : Season.fromJson(json['season'] as Map), diff --git a/lib/models_new/later/list.dart b/lib/models_new/later/list.dart index 088a299cfb..195c417e18 100644 --- a/lib/models_new/later/list.dart +++ b/lib/models_new/later/list.dart @@ -1,6 +1,5 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/later/bangumi.dart'; -import 'package:PiliPlus/models_new/later/page.dart'; import 'package:PiliPlus/models_new/later/rights.dart'; import 'package:PiliPlus/models_new/later/stat.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; @@ -8,7 +7,6 @@ import 'package:PiliPlus/pages/common/multi_select/base.dart'; class LaterItemModel with MultiSelectData { int? aid; - int? videos; String? pic; String? title; String? subtitle; @@ -18,7 +16,6 @@ class LaterItemModel with MultiSelectData { Rights? rights; Owner? owner; Stat? stat; - List? pages; Bangumi? bangumi; int? cid; int? progress; @@ -26,13 +23,11 @@ class LaterItemModel with MultiSelectData { bool? isPgc; String? pgcLabel; bool? isPugv; - int? seasonId; bool? isCharging; Dimension? dimension; LaterItemModel({ this.aid, - this.videos, this.pic, this.title, this.subtitle, @@ -42,7 +37,6 @@ class LaterItemModel with MultiSelectData { this.rights, this.owner, this.stat, - this.pages, this.bangumi, this.cid, this.progress, @@ -50,14 +44,12 @@ class LaterItemModel with MultiSelectData { this.isPgc, this.pgcLabel, this.isPugv, - this.seasonId, this.isCharging, this.dimension, }); factory LaterItemModel.fromJson(Map json) => LaterItemModel( aid: json['aid'] as int?, - videos: json['videos'] as int?, pic: json['pic'] as String?, title: json['title'] as String?, pubdate: json['pubdate'] as int?, @@ -72,9 +64,6 @@ class LaterItemModel with MultiSelectData { stat: json['stat'] == null ? null : Stat.fromJson(json['stat'] as Map), - pages: (json['pages'] as List?) - ?.map((e) => Page.fromJson(e as Map)) - .toList(), bangumi: json['bangumi'] == null ? null : Bangumi.fromJson(json['bangumi'] as Map), @@ -90,7 +79,6 @@ class LaterItemModel with MultiSelectData { isPgc: json['is_pgc'] as bool?, pgcLabel: json['pgc_label'] == '' ? null : json['pgc_label'], isPugv: json['is_pugv'] as bool?, - seasonId: json['season_id'] as int?, isCharging: json['charging_pay']?['level'] != null, dimension: json['dimension'] == null ? null diff --git a/lib/models_new/later/page.dart b/lib/models_new/later/page.dart deleted file mode 100644 index f902e749cc..0000000000 --- a/lib/models_new/later/page.dart +++ /dev/null @@ -1,17 +0,0 @@ -class Page { - int? cid; - int? page; - int? duration; - - Page({ - this.cid, - this.page, - this.duration, - }); - - factory Page.fromJson(Map json) => Page( - cid: json['cid'] as int?, - page: json['page'] as int?, - duration: json['duration'] as int?, - ); -} diff --git a/lib/models_new/later/season.dart b/lib/models_new/later/season.dart index c45969b3f7..3d413a980f 100644 --- a/lib/models_new/later/season.dart +++ b/lib/models_new/later/season.dart @@ -1,14 +1,11 @@ class Season { - int? seasonId; String? title; Season({ - this.seasonId, this.title, }); factory Season.fromJson(Map json) => Season( - seasonId: json['season_id'] as int?, title: json['title'] as String?, ); } diff --git a/lib/models_new/later/stat.dart b/lib/models_new/later/stat.dart index 8c03db17db..fafc04b747 100644 --- a/lib/models_new/later/stat.dart +++ b/lib/models_new/later/stat.dart @@ -1,16 +1,13 @@ class Stat { - int? aid; int? view; int? danmaku; Stat({ - this.aid, this.view, this.danmaku, }); factory Stat.fromJson(Map json) => Stat( - aid: json['aid'] as int?, view: json['view'] as int?, danmaku: json['danmaku'] as int?, ); diff --git a/lib/models_new/live/live_area_list/area_list.dart b/lib/models_new/live/live_area_list/area_list.dart index e9d83bb380..e7dfd6b21b 100644 --- a/lib/models_new/live/live_area_list/area_list.dart +++ b/lib/models_new/live/live_area_list/area_list.dart @@ -1,17 +1,13 @@ import 'package:PiliPlus/models_new/live/live_area_list/area_item.dart'; class AreaList { - int? id; String? name; - int? parentAreaType; List? areaList; - AreaList({this.id, this.name, this.parentAreaType, this.areaList}); + AreaList({this.name, this.areaList}); factory AreaList.fromJson(Map json) => AreaList( - id: json['id'] as int?, name: json['name'] ?? '', - parentAreaType: json['parent_area_type'] as int?, areaList: (json['area_list'] as List?) ?.map((e) => AreaItem.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/live/live_contribution_rank/item.dart b/lib/models_new/live/live_contribution_rank/item.dart index 4cc838f0c1..33ea786f41 100644 --- a/lib/models_new/live/live_contribution_rank/item.dart +++ b/lib/models_new/live/live_contribution_rank/item.dart @@ -4,7 +4,6 @@ class LiveContributionRankItem { int? uid; String? name; String? face; - int? rank; int? score; UinfoMedal? uinfoMedal; @@ -12,7 +11,6 @@ class LiveContributionRankItem { this.uid, this.name, this.face, - this.rank, this.score, this.uinfoMedal, }); @@ -22,7 +20,6 @@ class LiveContributionRankItem { uid: json['uid'] as int?, name: json['name'] as String?, face: json['face'] as String?, - rank: json['rank'] as int?, score: json['score'] as int?, uinfoMedal: json['uinfo']?['medal'] == null ? null diff --git a/lib/models_new/live/live_dm_info/data.dart b/lib/models_new/live/live_dm_info/data.dart index ffca3bee25..519c9db21e 100644 --- a/lib/models_new/live/live_dm_info/data.dart +++ b/lib/models_new/live/live_dm_info/data.dart @@ -1,18 +1,18 @@ import 'package:PiliPlus/models_new/live/live_dm_info/host_list.dart'; class LiveDmInfoData { - String? token; - List? hostList; + String token; + List hostList; LiveDmInfoData({ - this.token, - this.hostList, + required this.token, + required this.hostList, }); factory LiveDmInfoData.fromJson(Map json) => LiveDmInfoData( - token: json['token'] as String?, - hostList: (json['host_list'] as List?) - ?.map((e) => HostList.fromJson(e as Map)) + token: json['token'] as String, + hostList: (json['host_list'] as List) + .map((e) => HostList.fromJson(e as Map)) .toList(), ); } diff --git a/lib/models_new/live/live_feed_index/card_data_list_item.dart b/lib/models_new/live/live_feed_index/card_data_list_item.dart index 066924d78e..41c68fc04d 100644 --- a/lib/models_new/live/live_feed_index/card_data_list_item.dart +++ b/lib/models_new/live/live_feed_index/card_data_list_item.dart @@ -10,11 +10,8 @@ class CardLiveItem { String? _systemCover; String? get systemCover => _systemCover ?? cover; String? title; - int? liveTime; String? areaName; int? areaV2Id; - String? areaV2Name; - String? areaV2ParentName; int? areaV2ParentId; WatchedShow? watchedShow; @@ -26,14 +23,11 @@ class CardLiveItem { this.cover, String? systemCover, this.title, - this.liveTime, this.areaName, this.areaV2Id, - this.areaV2Name, - this.areaV2ParentName, this.areaV2ParentId, this.watchedShow, - }) : _systemCover = noneNullOrEmptyString(systemCover); + }) : _systemCover = nonNullOrEmptyString(systemCover); factory CardLiveItem.fromJson(Map json) => CardLiveItem( roomid: json['roomid'] ?? json['id'], @@ -43,11 +37,8 @@ class CardLiveItem { cover: json['cover'] as String?, systemCover: json['system_cover'], title: json['title'] as String?, - liveTime: json['live_time'] as int?, areaName: json['area_name'] as String?, areaV2Id: json['area_v2_id'] as int?, - areaV2Name: json['area_v2_name'] as String?, - areaV2ParentName: json['area_v2_parent_name'] as String?, areaV2ParentId: json['area_v2_parent_id'] as int?, watchedShow: json['watched_show'] == null ? null diff --git a/lib/models_new/live/live_follow/item.dart b/lib/models_new/live/live_follow/item.dart index 4472d97ed3..3163aa0717 100644 --- a/lib/models_new/live/live_follow/item.dart +++ b/lib/models_new/live/live_follow/item.dart @@ -1,37 +1,25 @@ class LiveFollowItem { int? roomid; - int? uid; String? uname; String? title; - String? face; - int? liveStatus; String? areaName; - String? areaNameV2; String? textSmall; String? roomCover; LiveFollowItem({ this.roomid, - this.uid, this.uname, this.title, - this.face, - this.liveStatus, this.areaName, - this.areaNameV2, this.textSmall, this.roomCover, }); factory LiveFollowItem.fromJson(Map json) => LiveFollowItem( roomid: json['roomid'] as int?, - uid: json['uid'] as int?, uname: json['uname'] as String?, title: json['title'] as String?, - face: json['face'] as String?, - liveStatus: json['live_status'] as int?, areaName: json['area_name'] as String?, - areaNameV2: json['area_name_v2'] as String?, textSmall: json['text_small'] as String?, roomCover: json['room_cover'] as String?, ); diff --git a/lib/models_new/live/live_medal_wall/data.dart b/lib/models_new/live/live_medal_wall/data.dart index 9c37865aeb..02b1f844a9 100644 --- a/lib/models_new/live/live_medal_wall/data.dart +++ b/lib/models_new/live/live_medal_wall/data.dart @@ -5,16 +5,12 @@ class MedalWallData { int? count; String? name; String? icon; - int? uid; - int? level; MedalWallData({ this.list, this.count, this.name, this.icon, - this.uid, - this.level, }); factory MedalWallData.fromJson(Map json) => MedalWallData( @@ -24,7 +20,5 @@ class MedalWallData { count: json['count'] as int?, name: json['name'] as String?, icon: json['icon'] as String?, - uid: json['uid'] as int?, - level: json['level'] as int?, ); } diff --git a/lib/models_new/live/live_room_info_h5/room_info.dart b/lib/models_new/live/live_room_info_h5/room_info.dart index 9b575d5849..7d7124bc79 100644 --- a/lib/models_new/live/live_room_info_h5/room_info.dart +++ b/lib/models_new/live/live_room_info_h5/room_info.dart @@ -1,35 +1,20 @@ class RoomInfo { int? uid; - int? roomId; String? title; String? cover; - int? liveStatus; - int? liveStartTime; - int? online; String? appBackground; - String? subSessionKey; RoomInfo({ this.uid, - this.roomId, this.title, this.cover, - this.liveStatus, - this.liveStartTime, - this.online, this.appBackground, - this.subSessionKey, }); factory RoomInfo.fromJson(Map json) => RoomInfo( uid: json['uid'] as int?, - roomId: json['room_id'] as int?, title: json['title'] as String?, cover: json['cover'] as String?, - liveStatus: json['live_status'] as int?, - liveStartTime: json['live_start_time'] as int?, - online: json['online'] as int?, appBackground: json['app_background'] as String?, - subSessionKey: json['sub_session_key'] as String?, ); } diff --git a/lib/models_new/live/live_room_play_info/codec.dart b/lib/models_new/live/live_room_play_info/codec.dart index f918cf1c18..dc4b4e687b 100644 --- a/lib/models_new/live/live_room_play_info/codec.dart +++ b/lib/models_new/live/live_room_play_info/codec.dart @@ -3,38 +3,26 @@ import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class CodecItem { String? codecName; - int? currentQn; - List? acceptQn; - String? baseUrl; - List? urlInfo; - dynamic hdrQn; - int? dolbyType; - String? attrName; - int? hdrType; + int currentQn; + List acceptQn; + String baseUrl; + List urlInfo; CodecItem({ this.codecName, - this.currentQn, - this.acceptQn, - this.baseUrl, - this.urlInfo, - this.hdrQn, - this.dolbyType, - this.attrName, - this.hdrType, + required this.currentQn, + required this.acceptQn, + required this.baseUrl, + required this.urlInfo, }); factory CodecItem.fromJson(Map json) => CodecItem( - codecName: json['codec_name'] as String?, - currentQn: json['current_qn'] as int?, - acceptQn: (json['accept_qn'] as List?)?.fromCast(), - baseUrl: json['base_url'] as String?, - urlInfo: (json['url_info'] as List?) - ?.map((e) => UrlInfo.fromJson(e as Map)) + codecName: json['codec_name'], + currentQn: json['current_qn'] as int, + acceptQn: (json['accept_qn'] as List).fromCast(), + baseUrl: json['base_url'] as String, + urlInfo: (json['url_info'] as List) + .map((e) => UrlInfo.fromJson(e as Map)) .toList(), - hdrQn: json['hdr_qn'] as dynamic, - dolbyType: json['dolby_type'] as int?, - attrName: json['attr_name'] as String?, - hdrType: json['hdr_type'] as int?, ); } diff --git a/lib/models_new/live/live_room_play_info/format.dart b/lib/models_new/live/live_room_play_info/format.dart index 25d0578d88..35dba67a79 100644 --- a/lib/models_new/live/live_room_play_info/format.dart +++ b/lib/models_new/live/live_room_play_info/format.dart @@ -2,16 +2,17 @@ import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart'; class Format { String? formatName; - List? codec; - String? masterUrl; + List codec; - Format({this.formatName, this.codec, this.masterUrl}); + Format({ + this.formatName, + required this.codec, + }); factory Format.fromJson(Map json) => Format( - formatName: json['format_name'] as String?, - codec: (json['codec'] as List?) - ?.map((e) => CodecItem.fromJson(e as Map)) + formatName: json['format_name'], + codec: (json['codec'] as List) + .map((e) => CodecItem.fromJson(e as Map)) .toList(), - masterUrl: json['master_url'] as String?, ); } diff --git a/lib/models_new/live/live_room_play_info/playurl.dart b/lib/models_new/live/live_room_play_info/playurl.dart index f8b220fb9e..63421a37d2 100644 --- a/lib/models_new/live/live_room_play_info/playurl.dart +++ b/lib/models_new/live/live_room_play_info/playurl.dart @@ -1,18 +1,15 @@ import 'package:PiliPlus/models_new/live/live_room_play_info/stream.dart'; class Playurl { - int? cid; - List? stream; + List stream; Playurl({ - this.cid, - this.stream, + required this.stream, }); factory Playurl.fromJson(Map json) => Playurl( - cid: json['cid'] as int?, - stream: (json['stream'] as List?) - ?.map((e) => Stream.fromJson(e as Map)) + stream: (json['stream'] as List) + .map((e) => Stream.fromJson(e as Map)) .toList(), ); } diff --git a/lib/models_new/live/live_room_play_info/stream.dart b/lib/models_new/live/live_room_play_info/stream.dart index 189d6eddec..7aa09ff404 100644 --- a/lib/models_new/live/live_room_play_info/stream.dart +++ b/lib/models_new/live/live_room_play_info/stream.dart @@ -2,14 +2,14 @@ import 'package:PiliPlus/models_new/live/live_room_play_info/format.dart'; class Stream { String? protocolName; - List? format; + List format; - Stream({this.protocolName, this.format}); + Stream({this.protocolName, required this.format}); factory Stream.fromJson(Map json) => Stream( - protocolName: json['protocol_name'] as String?, - format: (json['format'] as List?) - ?.map((e) => Format.fromJson(e as Map)) + protocolName: json['protocol_name'], + format: (json['format'] as List) + .map((e) => Format.fromJson(e as Map)) .toList(), ); } diff --git a/lib/models_new/live/live_room_play_info/url_info.dart b/lib/models_new/live/live_room_play_info/url_info.dart index 1252553a00..458b0dab4e 100644 --- a/lib/models_new/live/live_room_play_info/url_info.dart +++ b/lib/models_new/live/live_room_play_info/url_info.dart @@ -1,13 +1,11 @@ class UrlInfo { - String? host; - String? extra; - int? streamTtl; + String host; + String extra; - UrlInfo({this.host, this.extra, this.streamTtl}); + UrlInfo({required this.host, required this.extra}); factory UrlInfo.fromJson(Map json) => UrlInfo( - host: json['host'] as String?, - extra: json['extra'] as String?, - streamTtl: json['stream_ttl'] as int?, + host: json['host'] as String, + extra: json['extra'] as String, ); } diff --git a/lib/models_new/live/live_search/data.dart b/lib/models_new/live/live_search/data.dart index 7a5853b95f..ca649254d1 100644 --- a/lib/models_new/live/live_search/data.dart +++ b/lib/models_new/live/live_search/data.dart @@ -2,24 +2,15 @@ import 'package:PiliPlus/models_new/live/live_search/room.dart'; import 'package:PiliPlus/models_new/live/live_search/user.dart'; class LiveSearchData { - String? type; - int? page; - int? pagesize; Room? room; User? user; LiveSearchData({ - this.type, - this.page, - this.pagesize, this.room, this.user, }); factory LiveSearchData.fromJson(Map json) => LiveSearchData( - type: json['type'] as String?, - page: json['page'] as int?, - pagesize: json['pagesize'] as int?, room: json['room'] == null ? null : Room.fromJson(json['room'] as Map), diff --git a/lib/models_new/live/live_search/live_search.dart b/lib/models_new/live/live_search/live_search.dart deleted file mode 100644 index 867b269cd2..0000000000 --- a/lib/models_new/live/live_search/live_search.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:PiliPlus/models_new/live/live_search/data.dart'; - -class LiveSearch { - int? code; - String? message; - int? ttl; - LiveSearchData? data; - - LiveSearch({this.code, this.message, this.ttl, this.data}); - - factory LiveSearch.fromJson(Map json) => LiveSearch( - code: json['code'] as int?, - message: json['message'] as String?, - ttl: json['ttl'] as int?, - data: json['data'] == null - ? null - : LiveSearchData.fromJson(json['data'] as Map), - ); -} diff --git a/lib/models_new/live/live_search/room.dart b/lib/models_new/live/live_search/room.dart index a1ada09739..d3dbde3038 100644 --- a/lib/models_new/live/live_search/room.dart +++ b/lib/models_new/live/live_search/room.dart @@ -3,9 +3,8 @@ import 'package:PiliPlus/models_new/live/live_search/room_item.dart'; class Room { List? list; int? totalRoom; - int? totalPage; - Room({this.list, this.totalRoom, this.totalPage}); + Room({this.list, this.totalRoom}); factory Room.fromJson(Map json) => Room( list: (json['list'] as List?) @@ -14,6 +13,5 @@ class Room { ) .toList(), totalRoom: json['total_room'] as int?, - totalPage: json['total_page'] as int?, ); } diff --git a/lib/models_new/live/live_search/user.dart b/lib/models_new/live/live_search/user.dart index 54f348723e..dbbaf7919b 100644 --- a/lib/models_new/live/live_search/user.dart +++ b/lib/models_new/live/live_search/user.dart @@ -3,15 +3,13 @@ import 'package:PiliPlus/models_new/live/live_search/user_item.dart'; class User { List? list; int? totalUser; - int? totalPage; - User({this.list, this.totalUser, this.totalPage}); + User({this.list, this.totalUser}); factory User.fromJson(Map json) => User( list: (json['list'] as List?) ?.map((e) => LiveSearchUserItemModel.fromJson(e)) .toList(), totalUser: json['total_user'] as int?, - totalPage: json['total_page'] as int?, ); } diff --git a/lib/models_new/live/live_second_list/tag.dart b/lib/models_new/live/live_second_list/tag.dart index ed6ecde86a..b84a6761ce 100644 --- a/lib/models_new/live/live_second_list/tag.dart +++ b/lib/models_new/live/live_second_list/tag.dart @@ -1,16 +1,13 @@ class LiveSecondTag { - int? id; String? name; String? sortType; LiveSecondTag({ - this.id, this.name, this.sortType, }); factory LiveSecondTag.fromJson(Map json) => LiveSecondTag( - id: json['id'], name: json['name'], sortType: json['sort_type'], ); diff --git a/lib/models_new/live/live_superchat/item.dart b/lib/models_new/live/live_superchat/item.dart index 689a047e08..3862d85921 100644 --- a/lib/models_new/live/live_superchat/item.dart +++ b/lib/models_new/live/live_superchat/item.dart @@ -1,6 +1,7 @@ import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart'; import 'package:PiliPlus/models_new/live/live_superchat/user_info.dart'; import 'package:PiliPlus/utils/global_data.dart'; +import 'package:PiliPlus/utils/parse_int.dart'; import 'package:PiliPlus/utils/parse_string.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -64,18 +65,18 @@ class SuperChatItem { }); factory SuperChatItem.fromJson(Map json) => SuperChatItem( - id: Utils.safeToInt(json['id']) ?? Utils.random.nextInt(2147483647), - uid: Utils.safeToInt(json['uid'])!, + id: safeToInt(json['id']) ?? Utils.random.nextInt(2147483647), + uid: safeToInt(json['uid'])!, price: json['price'], - backgroundImage: noneNullOrEmptyString(json['background_image']), + backgroundImage: nonNullOrEmptyString(json['background_image']), backgroundColor: json['background_color'] ?? '#EDF5FF', backgroundBottomColor: json['background_bottom_color'] ?? '#2A60B2', backgroundPriceColor: json['background_price_color'] ?? '#7497CD', messageFontColor: json['message_font_color'] ?? '#FFFFFF', - endTime: Utils.safeToInt(json['end_time'])!, + endTime: safeToInt(json['end_time'])!, message: json['message'], token: json['token'], - ts: Utils.safeToInt(json['ts'])!, + ts: safeToInt(json['ts'])!, userInfo: UserInfo.fromJson(json['user_info'] as Map), medalInfo: !GlobalData().showMedal || json['uinfo']?['medal'] == null ? null diff --git a/lib/models_new/login_devices/device.dart b/lib/models_new/login_devices/device.dart index 692711194e..597dd2a83f 100644 --- a/lib/models_new/login_devices/device.dart +++ b/lib/models_new/login_devices/device.dart @@ -1,32 +1,20 @@ class LoginDevice { - int? mid; - String? localId; String? deviceName; - String? devicePlatform; bool? isCurrentDevice; String? latestLoginAt; String? source; - int? origin; LoginDevice({ - this.mid, - this.localId, this.deviceName, - this.devicePlatform, this.isCurrentDevice, this.latestLoginAt, this.source, - this.origin, }); factory LoginDevice.fromJson(Map json) => LoginDevice( - mid: json['mid'] as int?, - localId: json['local_id'] as String?, deviceName: json['device_name'] as String?, - devicePlatform: json['device_platform'] as String?, isCurrentDevice: json['is_current_device'] as bool?, latestLoginAt: json['latest_login_at'] as String?, source: json['source'] as String?, - origin: json['origin'] as int?, ); } diff --git a/lib/models_new/login_log/data.dart b/lib/models_new/login_log/data.dart index 18edab6eeb..9f68ac61aa 100644 --- a/lib/models_new/login_log/data.dart +++ b/lib/models_new/login_log/data.dart @@ -1,13 +1,11 @@ import 'package:PiliPlus/models_new/login_log/list.dart'; class LoginLogData { - int? count; List? list; - LoginLogData({this.count, this.list}); + LoginLogData({this.list}); factory LoginLogData.fromJson(Map json) => LoginLogData( - count: json['count'] as int?, list: (json['list'] as List?) ?.map((e) => LoginLogItem.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/login_log/list.dart b/lib/models_new/login_log/list.dart index 032c7141a6..7cc2f7d851 100644 --- a/lib/models_new/login_log/list.dart +++ b/lib/models_new/login_log/list.dart @@ -1,26 +1,17 @@ class LoginLogItem { final String ip; - final int? time; final String timeAt; - final bool? status; - final int? type; final String geo; const LoginLogItem({ required this.ip, - this.time, required this.timeAt, - this.status, - this.type, required this.geo, }); factory LoginLogItem.fromJson(Map json) => LoginLogItem( ip: json['ip'] ?? '', - time: json['time'] as int?, timeAt: json['time_at'] ?? '', - status: json['status'] as bool?, - type: json['type'] as int?, geo: json['geo'] ?? '', ); } diff --git a/lib/models_new/match/match_info/contest.dart b/lib/models_new/match/match_info/contest.dart index e3dad8056a..38a28dd60a 100644 --- a/lib/models_new/match/match_info/contest.dart +++ b/lib/models_new/match/match_info/contest.dart @@ -1,159 +1,41 @@ -import 'package:PiliPlus/models_new/match/match_info/home_away.dart'; import 'package:PiliPlus/models_new/match/match_info/season.dart'; -import 'package:PiliPlus/models_new/match/match_info/success_teaminfo.dart'; import 'package:PiliPlus/models_new/match/match_info/team.dart'; class MatchContest { - int? id; String? gameStage; int? stime; - int? etime; int? homeId; int? awayId; int? homeScore; int? awayScore; int? liveRoom; - int? aid; - int? collection; - String? collectionBvid; - int? gameState; - String? dic; - String? ctime; - String? mtime; - int? status; - int? sid; - int? mid; Season? season; MatchTeam? homeTeam; MatchTeam? awayTeam; - int? special; - int? successTeam; - SuccessTeaminfo? successTeaminfo; - String? specialName; - String? specialTips; - String? specialImage; - String? playback; - String? collectionUrl; - String? liveUrl; - int? dataType; - int? matchId; - int? guessType; - int? guessShow; - String? bvid; - String? gameStage1; - String? gameStage2; - int? liveStatus; - int? livePopular; - String? liveCover; - int? pushSwitch; - String? liveTitle; - int? seriesId; int? contestStatus; - int? contestFreeze; - int? startTime; - int? endTime; - String? title; - String? playBack; - int? seasonId; - int? isSub; - int? isGuess; - HomeAway? home; - HomeAway? away; - dynamic series; - String? prospect; - String? afterContestVideo; - int? homeSmallScore; - int? awaySmallScore; - String? watchPoint; - String? watchPointIcon; - dynamic hottestPlayer; MatchContest({ - this.id, this.gameStage, this.stime, - this.etime, this.homeId, this.awayId, this.homeScore, this.awayScore, this.liveRoom, - this.aid, - this.collection, - this.collectionBvid, - this.gameState, - this.dic, - this.ctime, - this.mtime, - this.status, - this.sid, - this.mid, this.season, this.homeTeam, this.awayTeam, - this.special, - this.successTeam, - this.successTeaminfo, - this.specialName, - this.specialTips, - this.specialImage, - this.playback, - this.collectionUrl, - this.liveUrl, - this.dataType, - this.matchId, - this.guessType, - this.guessShow, - this.bvid, - this.gameStage1, - this.gameStage2, - this.liveStatus, - this.livePopular, - this.liveCover, - this.pushSwitch, - this.liveTitle, - this.seriesId, this.contestStatus, - this.contestFreeze, - this.startTime, - this.endTime, - this.title, - this.playBack, - this.seasonId, - this.isSub, - this.isGuess, - this.home, - this.away, - this.series, - this.prospect, - this.afterContestVideo, - this.homeSmallScore, - this.awaySmallScore, - this.watchPoint, - this.watchPointIcon, - this.hottestPlayer, }); factory MatchContest.fromJson(Map json) => MatchContest( - id: json['id'] as int?, gameStage: json['game_stage'] as String?, stime: json['stime'] as int?, - etime: json['etime'] as int?, homeId: json['home_id'] as int?, awayId: json['away_id'] as int?, homeScore: json['home_score'] as int?, awayScore: json['away_score'] as int?, liveRoom: json['live_room'] as int?, - aid: json['aid'] as int?, - collection: json['collection'] as int?, - collectionBvid: json['collection_bvid'] as String?, - gameState: json['game_state'] as int?, - dic: json['dic'] as String?, - ctime: json['ctime'] as String?, - mtime: json['mtime'] as String?, - status: json['status'] as int?, - sid: json['sid'] as int?, - mid: json['mid'] as int?, season: json['season'] == null ? null : Season.fromJson(json['season'] as Map), @@ -163,54 +45,6 @@ class MatchContest { awayTeam: json['away_team'] == null ? null : MatchTeam.fromJson(json['away_team'] as Map), - special: json['special'] as int?, - successTeam: json['success_team'] as int?, - successTeaminfo: json['success_teaminfo'] == null - ? null - : SuccessTeaminfo.fromJson( - json['success_teaminfo'] as Map, - ), - specialName: json['special_name'] as String?, - specialTips: json['special_tips'] as String?, - specialImage: json['special_image'] as String?, - playback: json['playback'] as String?, - collectionUrl: json['collection_url'] as String?, - liveUrl: json['live_url'] as String?, - dataType: json['data_type'] as int?, - matchId: json['match_id'] as int?, - guessType: json['guess_type'] as int?, - guessShow: json['guess_show'] as int?, - bvid: json['bvid'] as String?, - gameStage1: json['game_stage1'] as String?, - gameStage2: json['game_stage2'] as String?, - liveStatus: json['live_status'] as int?, - livePopular: json['live_popular'] as int?, - liveCover: json['live_cover'] as String?, - pushSwitch: json['push_switch'] as int?, - liveTitle: json['live_title'] as String?, - seriesId: json['series_id'] as int?, contestStatus: json['contest_status'] as int?, - contestFreeze: json['contest_freeze'] as int?, - startTime: json['start_time'] as int?, - endTime: json['end_time'] as int?, - title: json['title'] as String?, - playBack: json['play_back'] as String?, - seasonId: json['season_id'] as int?, - isSub: json['is_sub'] as int?, - isGuess: json['is_guess'] as int?, - home: json['home'] == null - ? null - : HomeAway.fromJson(json['home'] as Map), - away: json['away'] == null - ? null - : HomeAway.fromJson(json['away'] as Map), - series: json['series'] as dynamic, - prospect: json['prospect'] as String?, - afterContestVideo: json['after_contest_video'] as String?, - homeSmallScore: json['home_small_score'] as int?, - awaySmallScore: json['away_small_score'] as int?, - watchPoint: json['watch_point'] as String?, - watchPointIcon: json['watch_point_icon'] as String?, - hottestPlayer: json['hottest_player'] as dynamic, ); } diff --git a/lib/models_new/match/match_info/home_away.dart b/lib/models_new/match/match_info/home_away.dart deleted file mode 100644 index 727cd046de..0000000000 --- a/lib/models_new/match/match_info/home_away.dart +++ /dev/null @@ -1,41 +0,0 @@ -class HomeAway { - int? id; - String? icon; - String? name; - int? wins; - String? region; - int? regionId; - int? externalTeamId; - String? divisionName; - String? divisionLogo; - dynamic playerGradeDetail; - bool? isSuccessTeam; - - HomeAway({ - this.id, - this.icon, - this.name, - this.wins, - this.region, - this.regionId, - this.externalTeamId, - this.divisionName, - this.divisionLogo, - this.playerGradeDetail, - this.isSuccessTeam, - }); - - factory HomeAway.fromJson(Map json) => HomeAway( - id: json['id'] as int?, - icon: json['icon'] as String?, - name: json['name'] as String?, - wins: json['wins'] as int?, - region: json['region'] as String?, - regionId: json['region_id'] as int?, - externalTeamId: json['ExternalTeamId'] as int?, - divisionName: json['division_name'] as String?, - divisionLogo: json['division_logo'] as String?, - playerGradeDetail: json['player_grade_detail'] as dynamic, - isSuccessTeam: json['is_success_team'] as bool?, - ); -} diff --git a/lib/models_new/match/match_info/season.dart b/lib/models_new/match/match_info/season.dart index 70d526e545..6af2fd7172 100644 --- a/lib/models_new/match/match_info/season.dart +++ b/lib/models_new/match/match_info/season.dart @@ -1,83 +1,14 @@ class Season { - int? id; - int? mid; String? title; - String? subTitle; - int? stime; - int? etime; - String? sponsor; String? logo; - String? dic; - int? status; - int? ctime; - int? mtime; - int? rank; - int? isApp; - String? url; - String? dataFocus; - String? focusUrl; - int? leidaSid; - int? gameType; - String? searchImage; - int? syncPlatform; - String? centreLogo; - int? centreStatus; - String? centrePcLogo; - int? seasonType; Season({ - this.id, - this.mid, this.title, - this.subTitle, - this.stime, - this.etime, - this.sponsor, this.logo, - this.dic, - this.status, - this.ctime, - this.mtime, - this.rank, - this.isApp, - this.url, - this.dataFocus, - this.focusUrl, - this.leidaSid, - this.gameType, - this.searchImage, - this.syncPlatform, - this.centreLogo, - this.centreStatus, - this.centrePcLogo, - this.seasonType, }); factory Season.fromJson(Map json) => Season( - id: json['id'] as int?, - mid: json['mid'] as int?, title: json['title'] as String?, - subTitle: json['sub_title'] as String?, - stime: json['stime'] as int?, - etime: json['etime'] as int?, - sponsor: json['sponsor'] as String?, logo: json['logo'] as String?, - dic: json['dic'] as String?, - status: json['status'] as int?, - ctime: json['ctime'] as int?, - mtime: json['mtime'] as int?, - rank: json['rank'] as int?, - isApp: json['is_app'] as int?, - url: json['url'] as String?, - dataFocus: json['data_focus'] as String?, - focusUrl: json['focus_url'] as String?, - leidaSid: json['leida_sid'] as int?, - gameType: json['game_type'] as int?, - searchImage: json['search_image'] as String?, - syncPlatform: json['sync_platform'] as int?, - centreLogo: json['centre_logo'] as String?, - centreStatus: json['centre_status'] as int?, - centrePcLogo: json['centre_pc_logo'] as String?, - seasonType: json['season_type'] as int?, ); } diff --git a/lib/models_new/match/match_info/success_teaminfo.dart b/lib/models_new/match/match_info/success_teaminfo.dart deleted file mode 100644 index b5f04fc09a..0000000000 --- a/lib/models_new/match/match_info/success_teaminfo.dart +++ /dev/null @@ -1,67 +0,0 @@ -class SuccessTeaminfo { - int? id; - String? title; - String? subTitle; - String? eTitle; - int? createTime; - String? area; - String? logo; - int? uid; - String? members; - String? dic; - int? isDeleted; - String? videoUrl; - String? profile; - int? leidaTid; - int? replyId; - int? teamType; - int? regionId; - String? divisionName; - String? divisionLogo; - - SuccessTeaminfo({ - this.id, - this.title, - this.subTitle, - this.eTitle, - this.createTime, - this.area, - this.logo, - this.uid, - this.members, - this.dic, - this.isDeleted, - this.videoUrl, - this.profile, - this.leidaTid, - this.replyId, - this.teamType, - this.regionId, - this.divisionName, - this.divisionLogo, - }); - - factory SuccessTeaminfo.fromJson(Map json) { - return SuccessTeaminfo( - id: json['id'] as int?, - title: json['title'] as String?, - subTitle: json['sub_title'] as String?, - eTitle: json['e_title'] as String?, - createTime: json['create_time'] as int?, - area: json['area'] as String?, - logo: json['logo'] as String?, - uid: json['uid'] as int?, - members: json['members'] as String?, - dic: json['dic'] as String?, - isDeleted: json['is_deleted'] as int?, - videoUrl: json['video_url'] as String?, - profile: json['profile'] as String?, - leidaTid: json['leida_tid'] as int?, - replyId: json['reply_id'] as int?, - teamType: json['team_type'] as int?, - regionId: json['region_id'] as int?, - divisionName: json['division_name'] as String?, - divisionLogo: json['division_logo'] as String?, - ); - } -} diff --git a/lib/models_new/match/match_info/team.dart b/lib/models_new/match/match_info/team.dart index e3e7c227f3..0931b47246 100644 --- a/lib/models_new/match/match_info/team.dart +++ b/lib/models_new/match/match_info/team.dart @@ -1,87 +1,14 @@ class MatchTeam { - int? id; String? title; - String? subTitle; - String? eTitle; - int? createTime; - String? area; String? logo; - int? uid; - String? members; - String? dic; - int? isDeleted; - String? videoUrl; - String? profile; - int? leidaTid; - int? replyId; - int? teamType; - int? regionId; - String? divisionName; - String? divisionLogo; MatchTeam({ - this.id, this.title, - this.subTitle, - this.eTitle, - this.createTime, - this.area, this.logo, - this.uid, - this.members, - this.dic, - this.isDeleted, - this.videoUrl, - this.profile, - this.leidaTid, - this.replyId, - this.teamType, - this.regionId, - this.divisionName, - this.divisionLogo, }); factory MatchTeam.fromJson(Map json) => MatchTeam( - id: json['id'] as int?, title: json['title'] as String?, - subTitle: json['sub_title'] as String?, - eTitle: json['e_title'] as String?, - createTime: json['create_time'] as int?, - area: json['area'] as String?, logo: json['logo'] as String?, - uid: json['uid'] as int?, - members: json['members'] as String?, - dic: json['dic'] as String?, - isDeleted: json['is_deleted'] as int?, - videoUrl: json['video_url'] as String?, - profile: json['profile'] as String?, - leidaTid: json['leida_tid'] as int?, - replyId: json['reply_id'] as int?, - teamType: json['team_type'] as int?, - regionId: json['region_id'] as int?, - divisionName: json['division_name'] as String?, - divisionLogo: json['division_logo'] as String?, ); - - Map toJson() => { - 'id': id, - 'title': title, - 'sub_title': subTitle, - 'e_title': eTitle, - 'create_time': createTime, - 'area': area, - 'logo': logo, - 'uid': uid, - 'members': members, - 'dic': dic, - 'is_deleted': isDeleted, - 'video_url': videoUrl, - 'profile': profile, - 'leida_tid': leidaTid, - 'reply_id': replyId, - 'team_type': teamType, - 'region_id': regionId, - 'division_name': divisionName, - 'division_logo': divisionLogo, - }; } diff --git a/lib/models_new/media_list/badge.dart b/lib/models_new/media_list/badge.dart deleted file mode 100644 index f4ebc7cef8..0000000000 --- a/lib/models_new/media_list/badge.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Badge { - String? text; - int? bgStyle; - String? img; - - Badge({this.text, this.bgStyle, this.img}); - - factory Badge.fromJson(Map json) => Badge( - text: json['text'] as String?, - bgStyle: json['bg_style'] as int?, - img: json['img'] as String?, - ); -} diff --git a/lib/models_new/media_list/coin.dart b/lib/models_new/media_list/coin.dart deleted file mode 100644 index 98bab6dd10..0000000000 --- a/lib/models_new/media_list/coin.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Coin { - num? maxNum; - num? coinNumber; - - Coin({this.maxNum, this.coinNumber}); - - factory Coin.fromJson(Map json) => Coin( - maxNum: json['max_num'] as num?, - coinNumber: json['coin_number'] as num?, - ); -} diff --git a/lib/models_new/media_list/data.dart b/lib/models_new/media_list/data.dart index e4331fa413..4b1acb88ba 100644 --- a/lib/models_new/media_list/data.dart +++ b/lib/models_new/media_list/data.dart @@ -2,15 +2,9 @@ import 'package:PiliPlus/models_new/media_list/media_list.dart'; class MediaListData { List mediaList; - bool? hasMore; - int? totalCount; - String? nextStartKey; MediaListData({ required this.mediaList, - this.hasMore, - this.totalCount, - this.nextStartKey, }); factory MediaListData.fromJson(Map json) => MediaListData( @@ -19,8 +13,5 @@ class MediaListData { ?.map((e) => MediaListItemModel.fromJson(e as Map)) .toList() ?? [], - hasMore: json['has_more'] as bool?, - totalCount: json['total_count'] as int?, - nextStartKey: json['next_start_key'] as String?, ); } diff --git a/lib/models_new/media_list/media_list.dart b/lib/models_new/media_list/media_list.dart index 54662209b2..e7b68da936 100644 --- a/lib/models_new/media_list/media_list.dart +++ b/lib/models_new/media_list/media_list.dart @@ -1,110 +1,48 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/fav/fav_detail/cnt_info.dart'; -import 'package:PiliPlus/models_new/media_list/coin.dart'; -import 'package:PiliPlus/models_new/media_list/ogv_info.dart'; import 'package:PiliPlus/models_new/media_list/page.dart'; -import 'package:PiliPlus/models_new/media_list/rights.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart'; class MediaListItemModel extends BaseEpisodeItem { @override int? get id => aid; - int? offset; - int? index; String? intro; - int? attr; - int? tid; - int? copyRight; CntInfo? cntInfo; int? duration; - int? pubtime; - int? likeState; - int? favState; - int? page; List? pages; int? type; Owner? upper; - String? link; - String? shortLink; - Rights? rights; - dynamic elecInfo; - Coin? coin; - OgvInfo? ogvInfo; - double? progressPercent; - bool? forbidFav; - int? moreType; - int? businessOid; @override int? get cid => pages?.firstOrNull?.id; MediaListItemModel({ super.aid, - this.offset, - this.index, this.intro, - this.attr, - this.tid, - this.copyRight, this.cntInfo, super.cover, this.duration, - this.pubtime, - this.likeState, - this.favState, - this.page, this.pages, super.title, this.type, this.upper, - this.link, super.bvid, - this.shortLink, - this.rights, - this.elecInfo, - this.coin, - this.ogvInfo, - this.progressPercent, super.badge, - this.forbidFav, - this.moreType, - this.businessOid, super.cid, }); MediaListItemModel.fromJson(Map json) { aid = json['id'] as int?; - offset = json['offset'] as int?; - index = json['index'] as int?; intro = json['intro'] as String?; - attr = json['attr'] as int?; - tid = json['tid'] as int?; - copyRight = json['copy_right'] as int?; cntInfo = json['cnt_info'] == null ? null : CntInfo.fromJson(json['cnt_info']); cover = json['cover'] as String?; duration = json['duration'] as int?; - pubtime = json['pubtime'] as int?; - likeState = json['like_state'] as int?; - favState = json['fav_state'] as int?; - page = json['page'] as int?; pages = (json['pages'] as List?)?.map((e) => Page.fromJson(e)).toList(); title = json['title'] as String?; type = json['type'] as int?; upper = json['upper'] == null ? null : Owner.fromJson(json['upper']); - link = json['link'] as String?; bvid = json['bv_id'] as String?; - shortLink = json['short_link'] as String?; - rights = json['rights'] == null ? null : Rights.fromJson(json['rights']); - elecInfo = json['elec_info'] as dynamic; - coin = json['coin'] == null ? null : Coin.fromJson(json['coin']); - ogvInfo = json['ogv_info'] == null - ? null - : OgvInfo.fromJson(json['ogv_info']); - progressPercent = (json['progress_percent'] as num?)?.toDouble(); badge = json['badge']?['text']; - forbidFav = json['forbid_fav'] as bool?; - moreType = json['more_type'] as int?; - businessOid = json['business_oid'] as int?; } } diff --git a/lib/models_new/media_list/ogv_info.dart b/lib/models_new/media_list/ogv_info.dart deleted file mode 100644 index d4d0276d4d..0000000000 --- a/lib/models_new/media_list/ogv_info.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; - -class OgvInfo { - int? epid; - int? seasonId; - int? aid; - int? cid; - Dimension? dimension; - - OgvInfo({this.epid, this.seasonId, this.aid, this.cid, this.dimension}); - - factory OgvInfo.fromJson(Map json) => OgvInfo( - epid: json['epid'] as int?, - seasonId: json['season_id'] as int?, - aid: json['aid'] as int?, - cid: json['cid'] as int?, - dimension: json['dimension'] == null - ? null - : Dimension.fromJson(json['dimension'] as Map), - ); -} diff --git a/lib/models_new/media_list/page.dart b/lib/models_new/media_list/page.dart index a85b978ed2..c2e8982952 100644 --- a/lib/models_new/media_list/page.dart +++ b/lib/models_new/media_list/page.dart @@ -1,36 +1,11 @@ -import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; - class Page { int? id; - String? title; - String? intro; - int? duration; - String? link; - int? page; - String? from; - Dimension? dimension; Page({ this.id, - this.title, - this.intro, - this.duration, - this.link, - this.page, - this.from, - this.dimension, }); factory Page.fromJson(Map json) => Page( id: json["id"], - title: json["title"], - intro: json["intro"], - duration: json["duration"], - link: json["link"], - page: json["page"], - from: json["from"], - dimension: json["dimension"] == null - ? null - : Dimension.fromJson(json["dimension"]), ); } diff --git a/lib/models_new/media_list/rights.dart b/lib/models_new/media_list/rights.dart deleted file mode 100644 index 278fe7d7c9..0000000000 --- a/lib/models_new/media_list/rights.dart +++ /dev/null @@ -1,38 +0,0 @@ -class Rights { - int? bp; - int? elec; - int? download; - int? movie; - int? pay; - int? ugcPay; - int? hd5; - int? noReprint; - int? autoplay; - int? noBackground; - - Rights({ - this.bp, - this.elec, - this.download, - this.movie, - this.pay, - this.ugcPay, - this.hd5, - this.noReprint, - this.autoplay, - this.noBackground, - }); - - factory Rights.fromJson(Map json) => Rights( - bp: json['bp'] as int?, - elec: json['elec'] as int?, - download: json['download'] as int?, - movie: json['movie'] as int?, - pay: json['pay'] as int?, - ugcPay: json['ugc_pay'] as int?, - hd5: json['hd5'] as int?, - noReprint: json['no_reprint'] as int?, - autoplay: json['autoplay'] as int?, - noBackground: json['no_background'] as int?, - ); -} diff --git a/lib/models_new/member/coin_like_arc/item.dart b/lib/models_new/member/coin_like_arc/item.dart index cb39e552d3..7594e57a09 100644 --- a/lib/models_new/member/coin_like_arc/item.dart +++ b/lib/models_new/member/coin_like_arc/item.dart @@ -1,96 +1,42 @@ class CoinLikeArcItem { String? title; - String? subtitle; - String? tname; String? cover; - String? coverIcon; String? uri; String? param; - String? goto; - String? length; int? duration; - bool? isPopular; bool? isSteins; - bool? isUgcpay; bool? isCooperation; bool? isPgc; - bool? isLivePlayback; - bool? isPugv; - bool? isFold; - bool? isOneself; int? play; int? danmaku; int? ctime; - int? ugcPay; - String? author; - bool? state; - int? videos; - String? viewContent; - int? iconType; - String? publishTimeText; CoinLikeArcItem({ this.title, - this.subtitle, - this.tname, this.cover, - this.coverIcon, this.uri, this.param, - this.goto, - this.length, this.duration, - this.isPopular, this.isSteins, - this.isUgcpay, this.isCooperation, this.isPgc, - this.isLivePlayback, - this.isPugv, - this.isFold, - this.isOneself, this.play, this.danmaku, this.ctime, - this.ugcPay, - this.author, - this.state, - this.videos, - this.viewContent, - this.iconType, - this.publishTimeText, }); factory CoinLikeArcItem.fromJson(Map json) => CoinLikeArcItem( title: json['title'] as String?, - subtitle: json['subtitle'] as String?, - tname: json['tname'] as String?, cover: json['cover'] as String?, - coverIcon: json['cover_icon'] as String?, uri: json['uri'] as String?, param: json['param'] as String?, - goto: json['goto'] as String?, - length: json['length'] as String?, duration: json['duration'] as int?, - isPopular: json['is_popular'] as bool?, isSteins: json['is_steins'] as bool?, - isUgcpay: json['is_ugcpay'] as bool?, isCooperation: json['is_cooperation'] as bool?, isPgc: json['is_pgc'] as bool?, - isLivePlayback: json['is_live_playback'] as bool?, - isPugv: json['is_pugv'] as bool?, - isFold: json['is_fold'] as bool?, - isOneself: json['is_oneself'] as bool?, play: json['play'] as int?, danmaku: json['danmaku'] as int?, ctime: json['ctime'] as int?, - ugcPay: json['ugc_pay'] as int?, - author: json['author'] as String?, - state: json['state'] as bool?, - videos: json['videos'] as int?, - viewContent: json['view_content'] as String?, - iconType: json['icon_type'] as int?, - publishTimeText: json['publish_time_text'] as String?, ); } diff --git a/lib/models_new/member/search_archive/page.dart b/lib/models_new/member/search_archive/page.dart index 4aecc812e0..feefb16dc6 100644 --- a/lib/models_new/member/search_archive/page.dart +++ b/lib/models_new/member/search_archive/page.dart @@ -1,13 +1,9 @@ class Page { - int? pn; - int? ps; int? count; - Page({this.pn, this.ps, this.count}); + Page({this.count}); factory Page.fromJson(Map json) => Page( - pn: json['pn'] as int?, - ps: json['ps'] as int?, count: json['count'] as int?, ); } diff --git a/lib/models_new/member/search_archive/vlist.dart b/lib/models_new/member/search_archive/vlist.dart index 95f93d1fff..a7018313c5 100644 --- a/lib/models_new/member/search_archive/vlist.dart +++ b/lib/models_new/member/search_archive/vlist.dart @@ -1,7 +1,8 @@ +import 'package:PiliPlus/models/horizontal_video_model.dart'; import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; -class VListItemModel extends BaseVideoItemModel { +class VListItemModel extends HorizontalVideoModel { VListItemModel.fromJson(Map json) { cover = json['pic']; desc = json['description']; @@ -14,6 +15,24 @@ class VListItemModel extends BaseVideoItemModel { bvid = json['bvid']; stat = VListStat.fromJson(json); owner = VListOwner.fromJson(json); + if (json['is_lesson_video'] == 1) { + isPugv = true; + badge = '课堂'; + } else if (json['is_charging_arc'] == true) { + badge = '充电专属'; + } else if (json['is_union_video'] == 1) { + badge = '合作'; + } + seasonId = json['season_id']; + redirectUrl = json['jump_url']; + final position = json['playback_position'] as num?; // percent + if (position != null) { + if (position == 100) { + progress = -1; + } else { + progress = ((position / 100) * duration).round(); + } + } } } diff --git a/lib/models_new/member/season_web/archive.dart b/lib/models_new/member/season_web/archive.dart index 4d11ab98ab..79e353b164 100644 --- a/lib/models_new/member/season_web/archive.dart +++ b/lib/models_new/member/season_web/archive.dart @@ -1,6 +1,7 @@ +import 'package:PiliPlus/models/horizontal_video_model.dart'; import 'package:PiliPlus/models/model_video.dart'; -class SeasonArchive extends BaseVideoItemModel { +class SeasonArchive extends HorizontalVideoModel { SeasonArchive.fromJson(Map json) { aid = json['aid']; bvid = json['bvid']; diff --git a/lib/models_new/member/season_web/page.dart b/lib/models_new/member/season_web/page.dart index 84f03b43a7..748324093b 100644 --- a/lib/models_new/member/season_web/page.dart +++ b/lib/models_new/member/season_web/page.dart @@ -1,13 +1,9 @@ class Page { - int? pageNum; - int? pageSize; int? total; - Page({this.pageNum, this.pageSize, this.total}); + Page({this.total}); factory Page.fromJson(Map json) => Page( - pageNum: json['page_num'] ?? json['num'], - pageSize: json['page_size'] ?? json['size'], total: json['total'] as int?, ); } diff --git a/lib/models_new/member_card_info/card.dart b/lib/models_new/member_card_info/card.dart index ec86403624..2a0b30e927 100644 --- a/lib/models_new/member_card_info/card.dart +++ b/lib/models_new/member_card_info/card.dart @@ -4,8 +4,6 @@ class Card { String? mid; String? name; String? face; - int? fans; - int? attention; BaseOfficialVerify? official; Vip? vip; @@ -13,8 +11,6 @@ class Card { this.mid, this.name, this.face, - this.fans, - this.attention, this.official, this.vip, }); @@ -23,8 +19,6 @@ class Card { mid: json['mid'] as String?, name: json['name'] as String?, face: json['face'] as String?, - fans: json['fans'] as int?, - attention: json['attention'] as int?, official: json['Official'] == null ? null : BaseOfficialVerify.fromJson( diff --git a/lib/models_new/member_card_info/data.dart b/lib/models_new/member_card_info/data.dart index 81b68f812d..cf8fb1d127 100644 --- a/lib/models_new/member_card_info/data.dart +++ b/lib/models_new/member_card_info/data.dart @@ -2,19 +2,13 @@ import 'package:PiliPlus/models_new/member_card_info/card.dart'; class MemberCardInfoData { Card? card; - bool? following; int? archiveCount; - int? articleCount; int? follower; - int? likeNum; MemberCardInfoData({ this.card, - this.following, this.archiveCount, - this.articleCount, this.follower, - this.likeNum, }); factory MemberCardInfoData.fromJson(Map json) => @@ -22,10 +16,7 @@ class MemberCardInfoData { card: json['card'] == null ? null : Card.fromJson(json['card'] as Map), - following: json['following'] as bool?, archiveCount: json['archive_count'] as int?, - articleCount: json['article_count'] as int?, follower: json['follower'] as int?, - likeNum: json['like_num'] as int?, ); } diff --git a/lib/models_new/member_guard/data.dart b/lib/models_new/member_guard/data.dart new file mode 100644 index 0000000000..2267874fa2 --- /dev/null +++ b/lib/models_new/member_guard/data.dart @@ -0,0 +1,19 @@ +import 'package:PiliPlus/models_new/member_guard/guard_top_list.dart'; + +class MemberGuardData { + List guardTopList; + int? hasMore; + + MemberGuardData({ + required this.guardTopList, + this.hasMore, + }); + + factory MemberGuardData.fromJson(Map json) => + MemberGuardData( + guardTopList: (json['guard_top_list'] as List) + .map((e) => GuardItem.fromJson(e as Map)) + .toList(), + hasMore: json['has_more'] as int?, + ); +} diff --git a/lib/models_new/member_guard/guard_top_list.dart b/lib/models_new/member_guard/guard_top_list.dart new file mode 100644 index 0000000000..f2dbe50bbb --- /dev/null +++ b/lib/models_new/member_guard/guard_top_list.dart @@ -0,0 +1,20 @@ +class GuardItem { + int uid; + String username; + String face; + int guardLevel; + + GuardItem({ + required this.uid, + required this.username, + required this.face, + required this.guardLevel, + }); + + factory GuardItem.fromJson(Map json) => GuardItem( + uid: json['uid'], + username: json['username'], + face: json['face'], + guardLevel: json['guard_level'], + ); +} diff --git a/lib/models_new/msg/im_user_infos/datum.dart b/lib/models_new/msg/im_user_infos/datum.dart index b994dbdec5..9d641ae63a 100644 --- a/lib/models_new/msg/im_user_infos/datum.dart +++ b/lib/models_new/msg/im_user_infos/datum.dart @@ -3,58 +3,28 @@ import 'package:PiliPlus/models/model_avatar.dart'; class ImUserInfosData { int? mid; String? name; - String? sex; String? face; String? sign; - int? rank; - int? level; - int? silence; Vip? vip; Pendant? pendant; BaseOfficialVerify? official; - int? birthday; - int? isFakeAccount; - int? isDeleted; - int? inRegAudit; - int? faceNft; - int? faceNftNew; - int? isSeniorMember; - String? digitalId; - int? digitalType; ImUserInfosData({ this.mid, this.name, - this.sex, this.face, this.sign, - this.rank, - this.level, - this.silence, this.vip, this.pendant, this.official, - this.birthday, - this.isFakeAccount, - this.isDeleted, - this.inRegAudit, - this.faceNft, - this.faceNftNew, - this.isSeniorMember, - this.digitalId, - this.digitalType, }); factory ImUserInfosData.fromJson(Map json) => ImUserInfosData( mid: json['mid'] as int?, name: json['name'] as String?, - sex: json['sex'] as String?, face: json['face'] as String?, sign: json['sign'] as String?, - rank: json['rank'] as int?, - level: json['level'] as int?, - silence: json['silence'] as int?, vip: json['vip'] == null ? null : Vip.fromJson(json['vip'] as Map), @@ -66,14 +36,5 @@ class ImUserInfosData { : BaseOfficialVerify.fromJson( json['official'] as Map, ), - birthday: json['birthday'] as int?, - isFakeAccount: json['is_fake_account'] as int?, - isDeleted: json['is_deleted'] as int?, - inRegAudit: json['in_reg_audit'] as int?, - faceNft: json['face_nft'] as int?, - faceNftNew: json['face_nft_new'] as int?, - isSeniorMember: json['is_senior_member'] as int?, - digitalId: json['digital_id'] as String?, - digitalType: json['digital_type'] as int?, ); } diff --git a/lib/models_new/msg/msg_at/content.dart b/lib/models_new/msg/msg_at/content.dart index 82cfd65c95..5741b16b6e 100644 --- a/lib/models_new/msg/msg_at/content.dart +++ b/lib/models_new/msg/msg_at/content.dart @@ -1,53 +1,20 @@ class MsgAtContent { - String? type; String? business; - int? businessId; - String? title; String? image; - String? uri; - int? subjectId; - int? rootId; - int? targetId; - int? sourceId; String? sourceContent; String? nativeUri; - List? atDetails; - List? topicDetails; - bool? hideReplyButton; MsgAtContent({ - this.type, this.business, - this.businessId, - this.title, this.image, - this.uri, - this.subjectId, - this.rootId, - this.targetId, - this.sourceId, this.sourceContent, this.nativeUri, - this.atDetails, - this.topicDetails, - this.hideReplyButton, }); factory MsgAtContent.fromJson(Map json) => MsgAtContent( - type: json['type'] as String?, business: json['business'] as String?, - businessId: json['business_id'] as int?, - title: json['title'] as String?, image: json['image'] as String?, - uri: json['uri'] as String?, - subjectId: json['subject_id'] as int?, - rootId: json['root_id'] as int?, - targetId: json['target_id'] as int?, - sourceId: json['source_id'] as int?, sourceContent: json['source_content'] as String?, nativeUri: json['native_uri'] as String?, - atDetails: json['at_details'] as List?, - topicDetails: json['topic_details'] as List?, - hideReplyButton: json['hide_reply_button'] as bool?, ); } diff --git a/lib/models_new/msg/msg_at/user.dart b/lib/models_new/msg/msg_at/user.dart index 46f7a6f778..f99cadc24b 100644 --- a/lib/models_new/msg/msg_at/user.dart +++ b/lib/models_new/msg/msg_at/user.dart @@ -1,26 +1,17 @@ class User { int? mid; - int? fans; String? nickname; String? avatar; - String? midLink; - bool? follow; User({ this.mid, - this.fans, this.nickname, this.avatar, - this.midLink, - this.follow, }); factory User.fromJson(Map json) => User( mid: json['mid'] as int?, - fans: json['fans'] as int?, nickname: json['nickname'] as String?, avatar: json['avatar'] as String?, - midLink: json['mid_link'] as String?, - follow: json['follow'] as bool?, ); } diff --git a/lib/models_new/msg/msg_dnd/uid_setting.dart b/lib/models_new/msg/msg_dnd/uid_setting.dart index 27c2c08a71..d7d9f8c2cd 100644 --- a/lib/models_new/msg/msg_dnd/uid_setting.dart +++ b/lib/models_new/msg/msg_dnd/uid_setting.dart @@ -1,11 +1,9 @@ class UidSetting { - int? id; int? setting; - UidSetting({this.id, this.setting}); + UidSetting({this.setting}); factory UidSetting.fromJson(Map json) => UidSetting( - id: json['id'] as int?, setting: json['setting'] as int?, ); } diff --git a/lib/models_new/msg/msg_like/content.dart b/lib/models_new/msg/msg_like/content.dart index 9b37a4d142..cae1dd7e95 100644 --- a/lib/models_new/msg/msg_like/content.dart +++ b/lib/models_new/msg/msg_like/content.dart @@ -1,52 +1,22 @@ class MsgLikeContent { - int? itemId; - int? pid; - String? type; String? business; - int? businessId; - int? replyBusinessId; - int? likeBusinessId; String? title; - String? desc; String? image; - String? uri; - String? detailName; String? nativeUri; - int? ctime; MsgLikeContent({ - this.itemId, - this.pid, - this.type, this.business, - this.businessId, - this.replyBusinessId, - this.likeBusinessId, this.title, - this.desc, this.image, - this.uri, - this.detailName, this.nativeUri, - this.ctime, }); factory MsgLikeContent.fromJson(Map json) { return MsgLikeContent( - itemId: json['item_id'] as int?, - pid: json['pid'] as int?, - type: json['type'] as String?, business: json['business'] as String?, - businessId: json['business_id'] as int?, - replyBusinessId: json['reply_business_id'] as int?, - likeBusinessId: json['like_business_id'] as int?, title: json['title'] as String?, - desc: json['desc'] as String?, image: json['image'] as String?, - uri: json['uri'] as String?, - detailName: json['detail_name'] as String?, nativeUri: json['native_uri'] as String?, - ctime: json['ctime'] as int?, ); } } diff --git a/lib/models_new/msg/msg_like/user.dart b/lib/models_new/msg/msg_like/user.dart index 46f7a6f778..13308329ea 100644 --- a/lib/models_new/msg/msg_like/user.dart +++ b/lib/models_new/msg/msg_like/user.dart @@ -1,26 +1,14 @@ class User { - int? mid; - int? fans; String? nickname; String? avatar; - String? midLink; - bool? follow; User({ - this.mid, - this.fans, this.nickname, this.avatar, - this.midLink, - this.follow, }); factory User.fromJson(Map json) => User( - mid: json['mid'] as int?, - fans: json['fans'] as int?, nickname: json['nickname'] as String?, avatar: json['avatar'] as String?, - midLink: json['mid_link'] as String?, - follow: json['follow'] as bool?, ); } diff --git a/lib/models_new/msg/msg_like_detail/card.dart b/lib/models_new/msg/msg_like_detail/card.dart index fdd9f39e2b..fb02bac571 100644 --- a/lib/models_new/msg/msg_like_detail/card.dart +++ b/lib/models_new/msg/msg_like_detail/card.dart @@ -1,51 +1,15 @@ class MsgLikeDetailCard { - int? itemId; - int? pid; - String? type; String? business; - int? businessId; - int? replyBusinessId; - int? likeBusinessId; String? title; - String? desc; - String? image; - String? uri; - String? detailName; - String? nativeUri; - int? ctime; MsgLikeDetailCard({ - this.itemId, - this.pid, - this.type, this.business, - this.businessId, - this.replyBusinessId, - this.likeBusinessId, this.title, - this.desc, - this.image, - this.uri, - this.detailName, - this.nativeUri, - this.ctime, }); factory MsgLikeDetailCard.fromJson(Map json) => MsgLikeDetailCard( - itemId: json['item_id'] as int?, - pid: json['pid'] as int?, - type: json['type'] as String?, business: json['business'] as String?, - businessId: json['business_id'] as int?, - replyBusinessId: json['reply_business_id'] as int?, - likeBusinessId: json['like_business_id'] as int?, title: json['title'] as String?, - desc: json['desc'] as String?, - image: json['image'] as String?, - uri: json['uri'] as String?, - detailName: json['detail_name'] as String?, - nativeUri: json['native_uri'] as String?, - ctime: json['ctime'] as int?, ); } diff --git a/lib/models_new/msg/msg_like_detail/user.dart b/lib/models_new/msg/msg_like_detail/user.dart index 273362a896..ce0730c2ea 100644 --- a/lib/models_new/msg/msg_like_detail/user.dart +++ b/lib/models_new/msg/msg_like_detail/user.dart @@ -1,27 +1,18 @@ class MsgLikeDetailUser { int? mid; - int? fans; String? nickname; String? avatar; - String? midLink; - bool? follow; MsgLikeDetailUser({ this.mid, - this.fans, this.nickname, this.avatar, - this.midLink, - this.follow, }); factory MsgLikeDetailUser.fromJson(Map json) => MsgLikeDetailUser( mid: json['mid'] as int?, - fans: json['fans'] as int?, nickname: json['nickname'] as String?, avatar: json['avatar'] as String?, - midLink: json['mid_link'] as String?, - follow: json['follow'] as bool?, ); } diff --git a/lib/models_new/msg/msg_reply/content.dart b/lib/models_new/msg/msg_reply/content.dart index c278950f2f..2a4d57f0dd 100644 --- a/lib/models_new/msg/msg_reply/content.dart +++ b/lib/models_new/msg/msg_reply/content.dart @@ -1,79 +1,31 @@ class MsgReplyContent { int? subjectId; - int? rootId; - int? sourceId; - int? targetId; - String? type; int? businessId; String? business; - String? title; - String? desc; - String? image; - String? uri; String? nativeUri; - String? detailTitle; String? rootReplyContent; String? sourceContent; String? targetReplyContent; - List? atDetails; - List? topicDetails; - bool? hideReplyButton; - bool? hideLikeButton; - int? likeState; - dynamic danmu; - String? message; MsgReplyContent({ this.subjectId, - this.rootId, - this.sourceId, - this.targetId, - this.type, this.businessId, this.business, - this.title, - this.desc, - this.image, - this.uri, this.nativeUri, - this.detailTitle, this.rootReplyContent, this.sourceContent, this.targetReplyContent, - this.atDetails, - this.topicDetails, - this.hideReplyButton, - this.hideLikeButton, - this.likeState, - this.danmu, - this.message, }); factory MsgReplyContent.fromJson(Map json) { return MsgReplyContent( subjectId: json['subject_id'] as int?, - rootId: json['root_id'] as int?, - sourceId: json['source_id'] as int?, - targetId: json['target_id'] as int?, - type: json['type'] as String?, businessId: json['business_id'] as int?, business: json['business'] as String?, - title: json['title'] as String?, - desc: json['desc'] as String?, - image: json['image'] as String?, - uri: json['uri'] as String?, nativeUri: json['native_uri'] as String?, - detailTitle: json['detail_title'] as String?, rootReplyContent: json['root_reply_content'] as String?, sourceContent: json['source_content'] as String?, targetReplyContent: json['target_reply_content'] as String?, - atDetails: json['at_details'] as List?, - topicDetails: json['topic_details'] as List?, - hideReplyButton: json['hide_reply_button'] as bool?, - hideLikeButton: json['hide_like_button'] as bool?, - likeState: json['like_state'] as int?, - danmu: json['danmu'] as dynamic, - message: json['message'] as String?, ); } } diff --git a/lib/models_new/msg/msg_reply/user.dart b/lib/models_new/msg/msg_reply/user.dart index 46f7a6f778..f99cadc24b 100644 --- a/lib/models_new/msg/msg_reply/user.dart +++ b/lib/models_new/msg/msg_reply/user.dart @@ -1,26 +1,17 @@ class User { int? mid; - int? fans; String? nickname; String? avatar; - String? midLink; - bool? follow; User({ this.mid, - this.fans, this.nickname, this.avatar, - this.midLink, - this.follow, }); factory User.fromJson(Map json) => User( mid: json['mid'] as int?, - fans: json['fans'] as int?, nickname: json['nickname'] as String?, avatar: json['avatar'] as String?, - midLink: json['mid_link'] as String?, - follow: json['follow'] as bool?, ); } diff --git a/lib/models_new/msg/msg_sys/data.dart b/lib/models_new/msg/msg_sys/data.dart index a3c2f0e9f6..74fce1777b 100644 --- a/lib/models_new/msg/msg_sys/data.dart +++ b/lib/models_new/msg/msg_sys/data.dart @@ -1,56 +1,23 @@ import 'dart:convert'; -import 'package:PiliPlus/models_new/msg/msg_sys/publisher.dart'; -import 'package:PiliPlus/models_new/msg/msg_sys/source.dart'; - class MsgSysItem { int? id; int? cursor; - Publisher? publisher; - int? type; String? title; String? content; - Source? source; String? timeAt; - int? cardType; - String? cardBrief; - String? cardMsgBrief; - String? cardCover; - String? cardStoryTitle; - String? cardLink; - String? mc; - int? isStation; - int? isSend; - int? notifyCursor; MsgSysItem({ this.id, this.cursor, - this.publisher, - this.type, this.title, this.content, - this.source, this.timeAt, - this.cardType, - this.cardBrief, - this.cardMsgBrief, - this.cardCover, - this.cardStoryTitle, - this.cardLink, - this.mc, - this.isStation, - this.isSend, - this.notifyCursor, }); MsgSysItem.fromJson(Map json) { id = json['id'] as int?; cursor = json['cursor'] as int?; - publisher = json['publisher'] == null - ? null - : Publisher.fromJson(json['publisher'] as Map); - type = json['type'] as int?; title = json['title'] as String?; content = json['content'] as String?; if (content != null) { @@ -61,19 +28,6 @@ class MsgSysItem { } } catch (_) {} } - source = json['source'] == null - ? null - : Source.fromJson(json['source'] as Map); timeAt = json['time_at'] as String?; - cardType = json['card_type'] as int?; - cardBrief = json['card_brief'] as String?; - cardMsgBrief = json['card_msg_brief'] as String?; - cardCover = json['card_cover'] as String?; - cardStoryTitle = json['card_story_title'] as String?; - cardLink = json['card_link'] as String?; - mc = json['mc'] as String?; - isStation = json['is_station'] as int?; - isSend = json['is_send'] as int?; - notifyCursor = json['notify_cursor'] as int?; } } diff --git a/lib/models_new/msg/msg_sys/publisher.dart b/lib/models_new/msg/msg_sys/publisher.dart deleted file mode 100644 index aa8233cef0..0000000000 --- a/lib/models_new/msg/msg_sys/publisher.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Publisher { - String? name; - int? mid; - String? face; - - Publisher({this.name, this.mid, this.face}); - - factory Publisher.fromJson(Map json) => Publisher( - name: json['name'] as String?, - mid: json['mid'] as int?, - face: json['face'] as String?, - ); -} diff --git a/lib/models_new/msg/msg_sys/source.dart b/lib/models_new/msg/msg_sys/source.dart deleted file mode 100644 index cc53af09cc..0000000000 --- a/lib/models_new/msg/msg_sys/source.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Source { - String? name; - String? logo; - - Source({this.name, this.logo}); - - factory Source.fromJson(Map json) => Source( - name: json['name'] as String?, - logo: json['logo'] as String?, - ); -} diff --git a/lib/models_new/msg/msgfeed_unread.dart b/lib/models_new/msg/msgfeed_unread.dart index 671c70e95b..47c4d4c1e9 100644 --- a/lib/models_new/msg/msgfeed_unread.dart +++ b/lib/models_new/msg/msgfeed_unread.dart @@ -1,4 +1,4 @@ -import 'package:fixnum/fixnum.dart'; +import 'package:fixnum/fixnum.dart' show Int64; class MsgFeedUnread { int at = 0; diff --git a/lib/models_new/msg/session_ss/data.dart b/lib/models_new/msg/session_ss/data.dart index 9bd03d4900..822d726aba 100644 --- a/lib/models_new/msg/session_ss/data.dart +++ b/lib/models_new/msg/session_ss/data.dart @@ -1,19 +1,16 @@ class SessionSsData { int? followStatus; - int? special; int? pushSetting; int? showPushSetting; SessionSsData({ this.followStatus, - this.special, this.pushSetting, this.showPushSetting, }); factory SessionSsData.fromJson(Map json) => SessionSsData( followStatus: json['follow_status'] as int?, - special: json['special'] as int?, pushSetting: json['push_setting'] as int?, showPushSetting: json['show_push_setting'] as int?, ); diff --git a/lib/models_new/msgfeed_unread/data.dart b/lib/models_new/msgfeed_unread/data.dart index ac0536801d..02ad72ce4e 100644 --- a/lib/models_new/msgfeed_unread/data.dart +++ b/lib/models_new/msgfeed_unread/data.dart @@ -1,42 +1,21 @@ class MsgFeedUnreadData { int at; - int coin; - int danmu; - int favorite; int like; - int recvLike; - int recvReply; int reply; int sysMsg; - int sysMsgStyle; - int up; MsgFeedUnreadData({ required this.at, - required this.coin, - required this.danmu, - required this.favorite, required this.like, - required this.recvLike, - required this.recvReply, required this.reply, required this.sysMsg, - required this.sysMsgStyle, - required this.up, }); factory MsgFeedUnreadData.fromJson(Map json) => MsgFeedUnreadData( at: json['at'] ?? 0, - coin: json['coin'] ?? 0, - danmu: json['danmu'] ?? 0, - favorite: json['favorite'] ?? 0, like: json['like'] ?? 0, - recvLike: json['recv_like'] ?? 0, - recvReply: json['recv_reply'] ?? 0, reply: json['reply'] ?? 0, sysMsg: json['sys_msg'] ?? 0, - sysMsgStyle: json['sys_msg_style'] ?? 0, - up: json['up'] ?? 0, ); } diff --git a/lib/models_new/music/bgm_detail.dart b/lib/models_new/music/bgm_detail.dart index 053c757b5e..7bdb577bc8 100644 --- a/lib/models_new/music/bgm_detail.dart +++ b/lib/models_new/music/bgm_detail.dart @@ -1,5 +1,4 @@ import 'package:PiliPlus/models/model_owner.dart'; -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class MusicDetail { MusicDetail({ @@ -9,82 +8,38 @@ class MusicDetail { required this.mvAid, required this.mvCid, required this.mvBvid, - required this.mvIndexOrder, - required this.mvFav, - required this.mvLikes, - required this.mvShares, required this.mvCover, - required this.bgColor, - required this.mvLyric, - required this.supportListen, required this.wishListen, required this.wishCount, - required this.musicShares, required this.musicSource, required this.album, - required this.artists, required this.artistsList, required this.listenPv, required this.achievement, - required this.musicRank, - required this.maxListId, - required this.showChosen, required this.hotSongHeat, - required this.hotSongRank, - required this.creationRank, - required this.musicOutUrl, - // required this.abTest, required this.musicComment, - // required this.musicMaterial, - required this.isNextgenActivity, - required this.isOriginal, - required this.musicHot, required this.musicRelation, required this.musicPublish, - // required this.musicAchievementTimeline, - required this.flowAttr, }); final String? musicTitle; final String? originArtist; final String? originArtistList; final int? mvAid; - final int? mvCid; + final int mvCid; final String? mvBvid; - final int? mvIndexOrder; - final int? mvFav; - final int? mvLikes; - final int? mvShares; final String? mvCover; - final String? bgColor; - final String? mvLyric; - final bool? supportListen; bool? wishListen; int? wishCount; - final int? musicShares; final String? musicSource; final String? album; - final List? artists; final List? artistsList; final int? listenPv; - final List? achievement; - final String? musicRank; - final int? maxListId; - final bool? showChosen; + final List achievement; final HotSongHeat? hotSongHeat; - final Rank? hotSongRank; - final Rank? creationRank; - final String? musicOutUrl; - // final dynamic abTest; final MusicComment? musicComment; - // final dynamic musicMaterial; - final int? isNextgenActivity; - final int? isOriginal; - final int? musicHot; final int? musicRelation; final String? musicPublish; - // final List? musicAchievementTimeline; - final FlowAttr? flowAttr; factory MusicDetail.fromJson(Map json) { return MusicDetail( @@ -92,123 +47,39 @@ class MusicDetail { originArtist: json["origin_artist"], originArtistList: json["origin_artist_list"], mvAid: json["mv_aid"], - mvCid: json["mv_cid"], + mvCid: json["mv_cid"] ?? 0, mvBvid: json["mv_bvid"], - mvIndexOrder: json["mv_index_order"], - mvFav: json["mv_fav"], - mvLikes: json["mv_likes"], - mvShares: json["mv_shares"], mvCover: json["mv_cover"], - bgColor: json["bg_color"], - mvLyric: json["mv_lyric"], - supportListen: json["support_listen"], wishListen: json["wish_listen"], wishCount: json["wish_count"], - musicShares: json["music_shares"], musicSource: json["music_source"], album: json["album"], - artists: (json["artists"] as List?) - ?.map((x) => Artist.fromJson(x)) - .toList(), artistsList: (json["artists_list"] as List?) ?.map((x) => Artist.fromJson(x)) .toList(), listenPv: json["listen_pv"], - achievement: (json["achievement"] as List?)?.fromCast(), - musicRank: json["music_rank"], - maxListId: json["max_list_id"], - showChosen: json["show_chosen"], + achievement: [ + ...?json["achievement"], + ?json["music_rank"], + ?json["recreation_rank"], + ], hotSongHeat: json["hot_song_heat"] == null ? null : HotSongHeat.fromJson(json["hot_song_heat"]), - hotSongRank: json["hot_song_rank"] == null - ? null - : Rank.fromJson(json["hot_song_rank"]), - creationRank: json["creation_rank"] == null - ? null - : Rank.fromJson(json["creation_rank"]), - musicOutUrl: json["music_out_url"], musicComment: json["music_comment"] == null ? null : MusicComment.fromJson(json["music_comment"]), - isNextgenActivity: json["is_nextgen_activity"], - isOriginal: json["is_original"], - musicHot: json["music_hot"], musicRelation: json["music_relation"], musicPublish: json["music_publish"], - flowAttr: json["flow_attr"] == null - ? null - : FlowAttr.fromJson(json["flow_attr"]), ); } } class Artist extends Owner { String? identity; - int? identifyType; Artist.fromJson(Map json) : super.fromJson(json) { identity = json["identity"]; - identifyType = json["identify_type"]; - } -} - -class Rank { - Rank({ - required this.lastUpdate, - required this.highestRank, - required this.onListTimes, - required this.listDetail, - }); - - final int? lastUpdate; - final int? highestRank; - final int? onListTimes; - final List? listDetail; - - factory Rank.fromJson(Map json) { - return Rank( - lastUpdate: json["last_update"], - highestRank: json["highest_rank"], - onListTimes: json["on_list_times"], - listDetail: (json["list_detail"] as List?) - ?.map((x) => ListDetail.fromJson(x)) - .toList(), - ); - } -} - -class ListDetail { - ListDetail({ - required this.date, - required this.rank, - }); - - final int? date; - final int? rank; - - factory ListDetail.fromJson(Map json) { - return ListDetail( - date: json["date"], - rank: json["rank"], - ); - } -} - -class FlowAttr { - FlowAttr({ - required this.noShare, - required this.noComment, - }); - - final bool? noShare; - final bool? noComment; - - factory FlowAttr.fromJson(Map json) { - return FlowAttr( - noShare: json["no_share"], - noComment: json["no_comment"], - ); } } @@ -250,20 +121,17 @@ class SongHeat { class MusicComment { MusicComment({ - required this.state, required this.nums, required this.oid, required this.pageType, }); - final int? state; final int? nums; final int? oid; final int? pageType; factory MusicComment.fromJson(Map json) { return MusicComment( - state: json["state"], nums: json["nums"], oid: json["oid"], pageType: json["page_type"], diff --git a/lib/models_new/music/bgm_recommend_list.dart b/lib/models_new/music/bgm_recommend_list.dart index ea84b28d93..dc52e871d4 100644 --- a/lib/models_new/music/bgm_recommend_list.dart +++ b/lib/models_new/music/bgm_recommend_list.dart @@ -1,109 +1,39 @@ class BgmRecommend { BgmRecommend({ - required this.aid, required this.bvid, - required this.indexOrder, required this.cid, required this.cover, required this.title, - required this.mid, required this.upNickName, required this.play, - required this.vt, - required this.isVt, required this.danmu, required this.duration, - required this.label, required this.labelList, - required this.isTop, - required this.showType, - required this.clickType, - required this.jumpUrl, - required this.vtDisplay, - required this.aidSource, - required this.tid, - required this.subTid, - required this.subTagName, - required this.coverMark, - // required this.districtLabel, }); - final int? aid; final String? bvid; - final int? indexOrder; final int? cid; final String? cover; final String? title; - final int? mid; final String? upNickName; final int? play; - final int? vt; - final int? isVt; final int? danmu; final int? duration; - final String? label; final List? labelList; - final bool? isTop; - final int? showType; - final int? clickType; - final String? jumpUrl; - final String? vtDisplay; - final int? aidSource; - final int? tid; - final int? subTid; - final String? subTagName; - final CoverMark? coverMark; - // final dynamic districtLabel; factory BgmRecommend.fromJson(Map json) { return BgmRecommend( - aid: json["aid"], bvid: json["bvid"], - indexOrder: json["index_order"], cid: json["cid"], cover: json["cover"], title: json["title"], - mid: json["mid"], upNickName: json["up_nick_name"], play: json["play"], - vt: json["vt"], - isVt: json["is_vt"], danmu: json["danmu"], duration: json["duration"], - label: json["label"], labelList: (json["label_list"] as List?) ?.map((x) => LabelList.fromJson(x)) .toList(), - isTop: json["is_top"], - showType: json["show_type"], - clickType: json["click_type"], - jumpUrl: json["jump_url"], - vtDisplay: json["vt_display"], - aidSource: json["aid_source"], - tid: json["tid"], - subTid: json["sub_tid"], - subTagName: json["sub_tag_name"], - coverMark: json["cover_mark"] == null - ? null - : CoverMark.fromJson(json["cover_mark"]), - // districtLabel: json["district_label"], - ); - } -} - -class CoverMark { - CoverMark({ - required this.name, - required this.value, - }); - - final String? name; - final String? value; - - factory CoverMark.fromJson(Map json) { - return CoverMark( - name: json["name"], - value: json["value"], ); } } @@ -111,16 +41,13 @@ class CoverMark { class LabelList { LabelList({ required this.name, - required this.value, }); final String? name; - final int? value; factory LabelList.fromJson(Map json) { return LabelList( name: json["name"], - value: json["value"], ); } } diff --git a/lib/models_new/pgc/pgc_index_result/badge_info.dart b/lib/models_new/pgc/pgc_index_result/badge_info.dart deleted file mode 100644 index a5c15c0285..0000000000 --- a/lib/models_new/pgc/pgc_index_result/badge_info.dart +++ /dev/null @@ -1,13 +0,0 @@ -class BadgeInfo { - String? bgColor; - String? bgColorNight; - String? text; - - BadgeInfo({this.bgColor, this.bgColorNight, this.text}); - - factory BadgeInfo.fromJson(Map json) => BadgeInfo( - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - text: json['text'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_index_result/data.dart b/lib/models_new/pgc/pgc_index_result/data.dart index 45b565e54b..dea4f7ee52 100644 --- a/lib/models_new/pgc/pgc_index_result/data.dart +++ b/lib/models_new/pgc/pgc_index_result/data.dart @@ -3,19 +3,13 @@ import 'package:PiliPlus/models_new/pgc/pgc_index_result/list.dart'; class PgcIndexResult { int? hasNext; List? list; - int? num; - int? size; - int? total; - PgcIndexResult({this.hasNext, this.list, this.num, this.size, this.total}); + PgcIndexResult({this.hasNext, this.list}); factory PgcIndexResult.fromJson(Map json) => PgcIndexResult( hasNext: json['has_next'] as int?, list: (json['list'] as List?) ?.map((e) => PgcIndexItem.fromJson(e as Map)) .toList(), - num: json['num'] as int?, - size: json['size'] as int?, - total: json['total'] as int?, ); } diff --git a/lib/models_new/pgc/pgc_index_result/first_ep.dart b/lib/models_new/pgc/pgc_index_result/first_ep.dart deleted file mode 100644 index 8b449bf656..0000000000 --- a/lib/models_new/pgc/pgc_index_result/first_ep.dart +++ /dev/null @@ -1,11 +0,0 @@ -class FirstEp { - String? cover; - int? epId; - - FirstEp({this.cover, this.epId}); - - factory FirstEp.fromJson(Map json) => FirstEp( - cover: json['cover'] as String?, - epId: json['ep_id'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_index_result/list.dart b/lib/models_new/pgc/pgc_index_result/list.dart index 59d41004da..ffb4b7f83a 100644 --- a/lib/models_new/pgc/pgc_index_result/list.dart +++ b/lib/models_new/pgc/pgc_index_result/list.dart @@ -1,69 +1,26 @@ -import 'package:PiliPlus/models_new/pgc/pgc_index_result/badge_info.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_index_result/first_ep.dart'; - class PgcIndexItem { String? badge; - BadgeInfo? badgeInfo; - int? badgeType; String? cover; - FirstEp? firstEp; String? indexShow; - int? isFinish; - String? link; - int? mediaId; String? order; - String? orderType; - String? score; int? seasonId; - int? seasonStatus; - int? seasonType; - String? subTitle; String? title; - String? titleIcon; PgcIndexItem({ this.badge, - this.badgeInfo, - this.badgeType, this.cover, - this.firstEp, this.indexShow, - this.isFinish, - this.link, - this.mediaId, this.order, - this.orderType, - this.score, this.seasonId, - this.seasonStatus, - this.seasonType, - this.subTitle, this.title, - this.titleIcon, }); factory PgcIndexItem.fromJson(Map json) => PgcIndexItem( badge: json['badge'] as String?, - badgeInfo: json['badge_info'] == null - ? null - : BadgeInfo.fromJson(json['badge_info'] as Map), - badgeType: json['badge_type'] as int?, cover: json['cover'] as String?, - firstEp: json['first_ep'] == null - ? null - : FirstEp.fromJson(json['first_ep'] as Map), indexShow: json['index_show'] as String?, - isFinish: json['is_finish'] as int?, - link: json['link'] as String?, - mediaId: json['media_id'] as int?, order: json['order'] as String?, - orderType: json['order_type'] as String?, - score: json['score'] as String?, seasonId: json['season_id'] as int?, - seasonStatus: json['season_status'] as int?, - seasonType: json['season_type'] as int?, - subTitle: json['subTitle'] as String?, title: json['title'] as String?, - titleIcon: json['title_icon'] as String?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/activity.dart b/lib/models_new/pgc/pgc_info_model/activity.dart deleted file mode 100644 index 99fce23711..0000000000 --- a/lib/models_new/pgc/pgc_info_model/activity.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Activity { - String? headBgUrl; - int? id; - String? title; - - Activity({this.headBgUrl, this.id, this.title}); - - factory Activity.fromJson(Map json) => Activity( - headBgUrl: json['head_bg_url'] as String?, - id: json['id'] as int?, - title: json['title'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/area.dart b/lib/models_new/pgc/pgc_info_model/area.dart index 8540bf56fc..a4524f1747 100644 --- a/lib/models_new/pgc/pgc_info_model/area.dart +++ b/lib/models_new/pgc/pgc_info_model/area.dart @@ -1,11 +1,9 @@ class Area { - int? id; String? name; - Area({this.id, this.name}); + Area({this.name}); factory Area.fromJson(Map json) => Area( - id: json['id'] as int?, name: json['name'] as String?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/badge_info.dart b/lib/models_new/pgc/pgc_info_model/badge_info.dart deleted file mode 100644 index a5c15c0285..0000000000 --- a/lib/models_new/pgc/pgc_info_model/badge_info.dart +++ /dev/null @@ -1,13 +0,0 @@ -class BadgeInfo { - String? bgColor; - String? bgColorNight; - String? text; - - BadgeInfo({this.bgColor, this.bgColorNight, this.text}); - - factory BadgeInfo.fromJson(Map json) => BadgeInfo( - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - text: json['text'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/danmaku.dart b/lib/models_new/pgc/pgc_info_model/danmaku.dart deleted file mode 100644 index b5bef5ae40..0000000000 --- a/lib/models_new/pgc/pgc_info_model/danmaku.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Danmaku { - String? icon; - String? pureText; - String? text; - int? value; - - Danmaku({this.icon, this.pureText, this.text, this.value}); - - factory Danmaku.fromJson(Map json) => Danmaku( - icon: json['icon'] as String?, - pureText: json['pure_text'] as String?, - text: json['text'] as String?, - value: json['value'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/ed.dart b/lib/models_new/pgc/pgc_info_model/ed.dart deleted file mode 100644 index cb828732d1..0000000000 --- a/lib/models_new/pgc/pgc_info_model/ed.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Ed { - int? end; - int? start; - - Ed({this.end, this.start}); - - factory Ed.fromJson(Map json) => Ed( - end: json['end'] as int?, - start: json['start'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/episode.dart b/lib/models_new/pgc/pgc_info_model/episode.dart index 32740790fb..2aef6b4704 100644 --- a/lib/models_new/pgc/pgc_info_model/episode.dart +++ b/lib/models_new/pgc/pgc_info_model/episode.dart @@ -1,78 +1,43 @@ -import 'package:PiliPlus/models_new/pgc/pgc_info_model/badge_info.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/rights.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/skip.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart' show BaseEpisodeItem; class EpisodeItem extends BaseEpisodeItem { - BadgeInfo? badgeInfo; - int? badgeType; Dimension? dimension; int? duration; // pgc: millisec , pugv: sec - bool? enableVt; String? from; - bool? isViewHide; String? link; String? longTitle; int? pubTime; - int? pv; - // String? releaseDate; - Rights? rights; - int? sectionType; String? shareCopy; String? shareUrl; - String? shortLink; - bool? showDrmLoginDialog; String? showTitle; - Skip? skip; - int? status; - String? subtitle; - String? vid; int? play; EpisodeItem({ super.aid, super.badge, - this.badgeInfo, - this.badgeType, super.bvid, super.cid, super.cover, this.dimension, this.duration, - this.enableVt, super.epId, this.from, super.id, - this.isViewHide, this.link, this.longTitle, this.pubTime, - this.pv, - // this.releaseDate, - this.rights, - this.sectionType, this.shareCopy, this.shareUrl, - this.shortLink, - this.showDrmLoginDialog, this.showTitle, - this.skip, - this.status, - this.subtitle, super.title, - this.vid, this.play, }); factory EpisodeItem.fromJson(Map json) => EpisodeItem( aid: json['aid'] as int?, badge: json['badge'] as String?, - badgeInfo: json['badge_info'] == null - ? null - : BadgeInfo.fromJson(json['badge_info'] as Map), - badgeType: json['badge_type'] as int?, bvid: json['bvid'] as String?, cid: json['cid'] as int?, cover: json['cover'] as String?, @@ -80,32 +45,16 @@ class EpisodeItem extends BaseEpisodeItem { ? null : Dimension.fromJson(json['dimension'] as Map), duration: json['duration'] as int?, - enableVt: json['enable_vt'] as bool?, epId: json['ep_id'] as int?, from: json['from'] as String?, id: json['id'] as int?, - isViewHide: json['is_view_hide'] as bool?, link: json['link'] as String?, longTitle: json['long_title'] as String?, pubTime: json['pub_time'] ?? json['release_date'], - pv: json['pv'] as int?, - // releaseDate: json['release_date'] as String?, - rights: json['rights'] == null - ? null - : Rights.fromJson(json['rights'] as Map), - sectionType: json['section_type'] as int?, shareCopy: json['share_copy'] as String?, shareUrl: json['share_url'] as String?, - shortLink: json['short_link'] as String?, - showDrmLoginDialog: json['showDrmLoginDialog'] as bool?, showTitle: json['show_title'] as String?, - skip: json['skip'] == null - ? null - : Skip.fromJson(json['skip'] as Map), - status: json['status'] as int?, - subtitle: json['subtitle'] as String?, title: json['title'] as String?, - vid: json['vid'] as String?, play: json['play'] as int?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/icon_font.dart b/lib/models_new/pgc/pgc_info_model/icon_font.dart deleted file mode 100644 index 0c5e2d35c9..0000000000 --- a/lib/models_new/pgc/pgc_info_model/icon_font.dart +++ /dev/null @@ -1,11 +0,0 @@ -class IconFont { - String? name; - String? text; - - IconFont({this.name, this.text}); - - factory IconFont.fromJson(Map json) => IconFont( - name: json['name'] as String?, - text: json['text'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/new_ep.dart b/lib/models_new/pgc/pgc_info_model/new_ep.dart index a11fcb57e6..39dbc6b2fc 100644 --- a/lib/models_new/pgc/pgc_info_model/new_ep.dart +++ b/lib/models_new/pgc/pgc_info_model/new_ep.dart @@ -1,15 +1,11 @@ class NewEp { String? desc; - int? id; - int? isNew; String? title; - NewEp({this.desc, this.id, this.isNew, this.title}); + NewEp({this.desc, this.title}); factory NewEp.fromJson(Map json) => NewEp( desc: json['desc'] as String?, - id: json['id'] as int?, - isNew: json['is_new'] as int?, title: json['title'] as String?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/op.dart b/lib/models_new/pgc/pgc_info_model/op.dart deleted file mode 100644 index fa1170b35d..0000000000 --- a/lib/models_new/pgc/pgc_info_model/op.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Op { - int? end; - int? start; - - Op({this.end, this.start}); - - factory Op.fromJson(Map json) => Op( - end: json['end'] as int?, - start: json['start'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/publish.dart b/lib/models_new/pgc/pgc_info_model/publish.dart index 065cad121f..82eb2278c5 100644 --- a/lib/models_new/pgc/pgc_info_model/publish.dart +++ b/lib/models_new/pgc/pgc_info_model/publish.dart @@ -1,26 +1,11 @@ class Publish { - int? isFinish; - int? isStarted; - String? pubTime; String? pubTimeShow; - int? unknowPubDate; - int? weekday; Publish({ - this.isFinish, - this.isStarted, - this.pubTime, this.pubTimeShow, - this.unknowPubDate, - this.weekday, }); factory Publish.fromJson(Map json) => Publish( - isFinish: json['is_finish'] as int?, - isStarted: json['is_started'] as int?, - pubTime: json['pub_time'] as String?, pubTimeShow: json['pub_time_show'] as String?, - unknowPubDate: json['unknow_pub_date'] as int?, - weekday: json['weekday'] as int?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/rating.dart b/lib/models_new/pgc/pgc_info_model/rating.dart index 0f082f86de..56af2a1820 100644 --- a/lib/models_new/pgc/pgc_info_model/rating.dart +++ b/lib/models_new/pgc/pgc_info_model/rating.dart @@ -1,11 +1,9 @@ class Rating { - int? count; double? score; - Rating({this.count, this.score}); + Rating({this.score}); factory Rating.fromJson(Map json) => Rating( - count: json['count'] as int?, score: (json['score'] as num?)?.toDouble(), ); } diff --git a/lib/models_new/pgc/pgc_info_model/result.dart b/lib/models_new/pgc/pgc_info_model/result.dart index 7d529e5e5c..29bdab3d34 100644 --- a/lib/models_new/pgc/pgc_info_model/result.dart +++ b/lib/models_new/pgc/pgc_info_model/result.dart @@ -1,57 +1,31 @@ -import 'package:PiliPlus/models_new/pgc/pgc_info_model/activity.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/area.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/brief.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/cooperator.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/icon_font.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/new_ep.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/publish.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/rating.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/rights.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/season.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/section.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/series.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/stat.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/up_info.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/user_status.dart'; class PgcInfoModel { - Activity? activity; String? actors; - String? alias; List? areas; - String? bkgCover; String? cover; - bool? enableVt; List? episodes; String? evaluate; - int? hideEpVvVtDm; - IconFont? iconFont; - String? jpTitle; - String? link; int? mediaId; - int? mode; NewEp? newEp; Publish? publish; Rating? rating; - String? record; - Rights? rights; int? seasonId; String? seasonTitle; - List? seasons; List
? section; - Series? series; - String? shareCopy; - String? shareSubTitle; - String? shareUrl; - int? showSeasonType; - String? squareCover; - String? staff; PgcStat? stat; - int? status; String? subtitle; String? title; - int? total; int? type; UpInfo? upInfo; UserStatus? userStatus; @@ -59,42 +33,21 @@ class PgcInfoModel { Brief? brief; PgcInfoModel({ - this.activity, this.actors, - this.alias, this.areas, - this.bkgCover, this.cover, - this.enableVt, this.episodes, this.evaluate, - this.hideEpVvVtDm, - this.iconFont, - this.jpTitle, - this.link, this.mediaId, - this.mode, this.newEp, this.publish, this.rating, - this.record, - this.rights, this.seasonId, this.seasonTitle, - this.seasons, this.section, - this.series, - this.shareCopy, - this.shareSubTitle, - this.shareUrl, - this.showSeasonType, - this.squareCover, - this.staff, this.stat, - this.status, this.subtitle, this.title, - this.total, this.type, this.upInfo, this.userStatus, @@ -103,29 +56,16 @@ class PgcInfoModel { }); factory PgcInfoModel.fromJson(Map json) => PgcInfoModel( - activity: json['activity'] == null - ? null - : Activity.fromJson(json['activity'] as Map), actors: json['actors'] as String?, - alias: json['alias'] as String?, areas: (json['areas'] as List?) ?.map((e) => Area.fromJson(e as Map)) .toList(), - bkgCover: json['bkg_cover'] as String?, cover: json['cover'] as String?, - enableVt: json['enable_vt'] as bool?, episodes: (json['episodes'] as List?) ?.map((e) => EpisodeItem.fromJson(e as Map)) .toList(), evaluate: json['evaluate'] as String?, - hideEpVvVtDm: json['hide_ep_vv_vt_dm'] as int?, - iconFont: json['icon_font'] == null - ? null - : IconFont.fromJson(json['icon_font'] as Map), - jpTitle: json['jp_title'] as String?, - link: json['link'] as String?, mediaId: json['media_id'] as int?, - mode: json['mode'] as int?, newEp: json['new_ep'] == null ? null : NewEp.fromJson(json['new_ep'] as Map), @@ -135,34 +75,16 @@ class PgcInfoModel { rating: json['rating'] == null ? null : Rating.fromJson(json['rating'] as Map), - record: json['record'] as String?, - rights: json['rights'] == null - ? null - : Rights.fromJson(json['rights'] as Map), seasonId: json['season_id'] as int?, seasonTitle: json['season_title'] as String?, - seasons: (json['seasons'] as List?) - ?.map((e) => Season.fromJson(e as Map)) - .toList(), section: (json['section'] as List?) ?.map((e) => Section.fromJson(e as Map)) .toList(), - series: json['series'] == null - ? null - : Series.fromJson(json['series'] as Map), - shareCopy: json['share_copy'] as String?, - shareSubTitle: json['share_sub_title'] as String?, - shareUrl: json['share_url'] as String?, - showSeasonType: json['show_season_type'] as int?, - squareCover: json['square_cover'] as String?, - staff: json['staff'] as String?, stat: json['stat'] == null ? null : PgcStat.fromJson(json['stat'] as Map), - status: json['status'] as int?, subtitle: json['subtitle'] as String?, title: json['title'] as String?, - total: json['total'] as int?, type: json['type'] as int?, upInfo: json['up_info'] == null ? null diff --git a/lib/models_new/pgc/pgc_info_model/rights.dart b/lib/models_new/pgc/pgc_info_model/rights.dart deleted file mode 100644 index 2f70a13d19..0000000000 --- a/lib/models_new/pgc/pgc_info_model/rights.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Rights { - int? allowDm; - int? allowDownload; - int? areaLimit; - - Rights({this.allowDm, this.allowDownload, this.areaLimit}); - - factory Rights.fromJson(Map json) => Rights( - allowDm: json['allow_dm'] as int?, - allowDownload: json['allow_download'] as int?, - areaLimit: json['area_limit'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/season.dart b/lib/models_new/pgc/pgc_info_model/season.dart deleted file mode 100644 index abd72e9561..0000000000 --- a/lib/models_new/pgc/pgc_info_model/season.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:PiliPlus/models_new/pgc/pgc_info_model/badge_info.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/icon_font.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/new_ep.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/stat.dart'; - -class Season { - String? badge; - BadgeInfo? badgeInfo; - int? badgeType; - String? cover; - bool? enableVt; - String? horizontalCover1610; - String? horizontalCover169; - IconFont? iconFont; - int? mediaId; - NewEp? newEp; - int? seasonId; - String? seasonTitle; - int? seasonType; - PgcStat? stat; - - Season({ - this.badge, - this.badgeInfo, - this.badgeType, - this.cover, - this.enableVt, - this.horizontalCover1610, - this.horizontalCover169, - this.iconFont, - this.mediaId, - this.newEp, - this.seasonId, - this.seasonTitle, - this.seasonType, - this.stat, - }); - - factory Season.fromJson(Map json) => Season( - badge: json['badge'] as String?, - badgeInfo: json['badge_info'] == null - ? null - : BadgeInfo.fromJson(json['badge_info'] as Map), - badgeType: json['badge_type'] as int?, - cover: json['cover'] as String?, - enableVt: json['enable_vt'] as bool?, - horizontalCover1610: json['horizontal_cover_1610'] as String?, - horizontalCover169: json['horizontal_cover_169'] as String?, - iconFont: json['icon_font'] == null - ? null - : IconFont.fromJson(json['icon_font'] as Map), - mediaId: json['media_id'] as int?, - newEp: json['new_ep'] == null - ? null - : NewEp.fromJson(json['new_ep'] as Map), - seasonId: json['season_id'] as int?, - seasonTitle: json['season_title'] as String?, - seasonType: json['season_type'] as int?, - stat: json['stat'] == null - ? null - : PgcStat.fromJson(json['stat'] as Map), - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/section.dart b/lib/models_new/pgc/pgc_info_model/section.dart index ad208740d1..65ef7447f2 100644 --- a/lib/models_new/pgc/pgc_info_model/section.dart +++ b/lib/models_new/pgc/pgc_info_model/section.dart @@ -1,36 +1,15 @@ import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart'; class Section { - int? attr; - int? episodeId; - List? episodeIds; List? episodes; - int? id; - String? title; - int? type; - int? type2; Section({ - this.attr, - this.episodeId, - this.episodeIds, this.episodes, - this.id, - this.title, - this.type, - this.type2, }); factory Section.fromJson(Map json) => Section( - attr: json['attr'] as int?, - episodeId: json['episode_id'] as int?, - episodeIds: json['episode_ids'] as List?, episodes: (json['episodes'] as List?) ?.map((e) => EpisodeItem.fromJson(e as Map)) .toList(), - id: json['id'] as int?, - title: json['title'] as String?, - type: json['type'] as int?, - type2: json['type2'] as int?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/series.dart b/lib/models_new/pgc/pgc_info_model/series.dart deleted file mode 100644 index a359d21b7c..0000000000 --- a/lib/models_new/pgc/pgc_info_model/series.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Series { - int? displayType; - int? seriesId; - String? seriesTitle; - - Series({this.displayType, this.seriesId, this.seriesTitle}); - - factory Series.fromJson(Map json) => Series( - displayType: json['display_type'] as int?, - seriesId: json['series_id'] as int?, - seriesTitle: json['series_title'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/skip.dart b/lib/models_new/pgc/pgc_info_model/skip.dart deleted file mode 100644 index 106e098cde..0000000000 --- a/lib/models_new/pgc/pgc_info_model/skip.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:PiliPlus/models_new/pgc/pgc_info_model/ed.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/op.dart'; - -class Skip { - Ed? ed; - Op? op; - - Skip({this.ed, this.op}); - - factory Skip.fromJson(Map json) => Skip( - ed: json['ed'] == null - ? null - : Ed.fromJson(json['ed'] as Map), - op: json['op'] == null - ? null - : Op.fromJson(json['op'] as Map), - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/stat.dart b/lib/models_new/pgc/pgc_info_model/stat.dart index 2faa627e52..913f8761d7 100644 --- a/lib/models_new/pgc/pgc_info_model/stat.dart +++ b/lib/models_new/pgc/pgc_info_model/stat.dart @@ -1,19 +1,13 @@ import 'package:PiliPlus/models_new/video/video_detail/stat_detail.dart'; class PgcStat extends StatDetail { - int? favorites; - String? followText; - PgcStat.fromJson(Map json) { coin = json["coins"] ?? 0; danmaku = json["danmakus"]; favorite = json["favorite"] ?? 0; - favorites = json["favorites"]; - followText = json["follow_text"]; like = json["likes"] ?? 0; reply = json["reply"]; share = json["share"]; view = json["views"]; - vt = json["vt"]; } } diff --git a/lib/models_new/pgc/pgc_info_model/stat_for_unity.dart b/lib/models_new/pgc/pgc_info_model/stat_for_unity.dart deleted file mode 100644 index f8c4204804..0000000000 --- a/lib/models_new/pgc/pgc_info_model/stat_for_unity.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:PiliPlus/models_new/pgc/pgc_info_model/danmaku.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_info_model/vt.dart'; - -class StatForUnity { - num? coin; - Danmaku? danmaku; - int? likes; - int? reply; - Vt? vt; - - StatForUnity({this.coin, this.danmaku, this.likes, this.reply, this.vt}); - - factory StatForUnity.fromJson(Map json) => StatForUnity( - coin: json['coin'] as num?, - danmaku: json['danmaku'] == null - ? null - : Danmaku.fromJson(json['danmaku'] as Map), - likes: json['likes'] as int?, - reply: json['reply'] as int?, - vt: json['vt'] == null - ? null - : Vt.fromJson(json['vt'] as Map), - ); -} diff --git a/lib/models_new/pgc/pgc_info_model/up_info.dart b/lib/models_new/pgc/pgc_info_model/up_info.dart index 2f2f57e29f..fc56444148 100644 --- a/lib/models_new/pgc/pgc_info_model/up_info.dart +++ b/lib/models_new/pgc/pgc_info_model/up_info.dart @@ -1,41 +1,17 @@ class UpInfo { String? avatar; - String? avatarSubscriptUrl; - int? follower; - int? isFollow; int? mid; - String? nicknameColor; - int? themeType; String? uname; - int? verifyType; - int? vipStatus; - int? vipType; UpInfo({ this.avatar, - this.avatarSubscriptUrl, - this.follower, - this.isFollow, this.mid, - this.nicknameColor, - this.themeType, this.uname, - this.verifyType, - this.vipStatus, - this.vipType, }); factory UpInfo.fromJson(Map json) => UpInfo( avatar: json['avatar'] as String?, - avatarSubscriptUrl: json['avatar_subscript_url'] as String?, - follower: json['follower'] as int?, - isFollow: json['is_follow'] as int?, mid: json['mid'] as int?, - nicknameColor: json['nickname_color'] as String?, - themeType: json['theme_type'] as int?, uname: json['uname'] as String?, - verifyType: json['verify_type'] as int?, - vipStatus: json['vip_status'] as int?, - vipType: json['vip_type'] as int?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/user_progress.dart b/lib/models_new/pgc/pgc_info_model/user_progress.dart index 7cc426e0c5..759a0d05ab 100644 --- a/lib/models_new/pgc/pgc_info_model/user_progress.dart +++ b/lib/models_new/pgc/pgc_info_model/user_progress.dart @@ -1,15 +1,11 @@ class UserProgress { UserProgress({ this.lastEpId, - this.lastEpIndex, - this.lastTime, }); + int? lastEpId; - String? lastEpIndex; - int? lastTime; + UserProgress.fromJson(Map json) { lastEpId = json['last_ep_id']; - lastEpIndex = json['last_ep_index']; - lastTime = json['last_time']; } } diff --git a/lib/models_new/pgc/pgc_info_model/user_status.dart b/lib/models_new/pgc/pgc_info_model/user_status.dart index 488066fc78..0abf359728 100644 --- a/lib/models_new/pgc/pgc_info_model/user_status.dart +++ b/lib/models_new/pgc/pgc_info_model/user_status.dart @@ -1,39 +1,15 @@ import 'package:PiliPlus/models_new/pgc/pgc_info_model/user_progress.dart'; class UserStatus { - int? areaLimit; - int? banAreaShow; - int? follow; - int? followStatus; - int? login; - int? pay; - int? payPackPaid; - int? sponsor; UserProgress? progress; int? favored; UserStatus({ - this.areaLimit, - this.banAreaShow, - this.follow, - this.followStatus, - this.login, - this.pay, - this.payPackPaid, - this.sponsor, this.progress, this.favored, }); factory UserStatus.fromJson(Map json) => UserStatus( - areaLimit: json['area_limit'] as int?, - banAreaShow: json['ban_area_show'] as int?, - follow: json['follow'] as int?, - followStatus: json['follow_status'] as int?, - login: json['login'] as int?, - pay: json['pay'] as int?, - payPackPaid: json['pay_pack_paid'] as int?, - sponsor: json['sponsor'] as int?, progress: json['progress'] == null ? null : UserProgress.fromJson(json['progress']), diff --git a/lib/models_new/pgc/pgc_info_model/vt.dart b/lib/models_new/pgc/pgc_info_model/vt.dart deleted file mode 100644 index c1b0a4bfac..0000000000 --- a/lib/models_new/pgc/pgc_info_model/vt.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Vt { - String? icon; - String? pureText; - String? text; - int? value; - - Vt({this.icon, this.pureText, this.text, this.value}); - - factory Vt.fromJson(Map json) => Vt( - icon: json['icon'] as String?, - pureText: json['pure_text'] as String?, - text: json['text'] as String?, - value: json['value'] as int?, - ); -} diff --git a/lib/models_new/pgc/pgc_rank/badge_info.dart b/lib/models_new/pgc/pgc_rank/badge_info.dart deleted file mode 100644 index a5c15c0285..0000000000 --- a/lib/models_new/pgc/pgc_rank/badge_info.dart +++ /dev/null @@ -1,13 +0,0 @@ -class BadgeInfo { - String? bgColor; - String? bgColorNight; - String? text; - - BadgeInfo({this.bgColor, this.bgColorNight, this.text}); - - factory BadgeInfo.fromJson(Map json) => BadgeInfo( - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - text: json['text'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_rank/icon_font.dart b/lib/models_new/pgc/pgc_rank/icon_font.dart deleted file mode 100644 index 0c5e2d35c9..0000000000 --- a/lib/models_new/pgc/pgc_rank/icon_font.dart +++ /dev/null @@ -1,11 +0,0 @@ -class IconFont { - String? name; - String? text; - - IconFont({this.name, this.text}); - - factory IconFont.fromJson(Map json) => IconFont( - name: json['name'] as String?, - text: json['text'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_rank/new_ep.dart b/lib/models_new/pgc/pgc_rank/new_ep.dart index 37c5c1bf7b..6fcb7d9cd0 100644 --- a/lib/models_new/pgc/pgc_rank/new_ep.dart +++ b/lib/models_new/pgc/pgc_rank/new_ep.dart @@ -1,11 +1,9 @@ class NewEp { - String? cover; String? indexShow; - NewEp({this.cover, this.indexShow}); + NewEp({this.indexShow}); factory NewEp.fromJson(Map json) => NewEp( - cover: json['cover'] as String?, indexShow: json['index_show'] as String?, ); } diff --git a/lib/models_new/pgc/pgc_rank/pgc_rank_item_model.dart b/lib/models_new/pgc/pgc_rank/pgc_rank_item_model.dart index 5d3cd39bf8..377fea23c9 100644 --- a/lib/models_new/pgc/pgc_rank/pgc_rank_item_model.dart +++ b/lib/models_new/pgc/pgc_rank/pgc_rank_item_model.dart @@ -1,38 +1,16 @@ -import 'package:PiliPlus/models_new/pgc/pgc_rank/badge_info.dart'; -import 'package:PiliPlus/models_new/pgc/pgc_rank/icon_font.dart'; import 'package:PiliPlus/models_new/pgc/pgc_rank/new_ep.dart'; import 'package:PiliPlus/models_new/pgc/pgc_rank/stat.dart'; class PgcRankItemModel { - String? badge; - BadgeInfo? badgeInfo; - int? badgeType; String? cover; - String? desc; - bool? enableVt; - IconFont? iconFont; NewEp? newEp; - int? rank; - String? rating; - int? seasonId; - String? ssHorizontalCover; Stat? stat; String? title; String? url; PgcRankItemModel({ - this.badge, - this.badgeInfo, - this.badgeType, this.cover, - this.desc, - this.enableVt, - this.iconFont, this.newEp, - this.rank, - this.rating, - this.seasonId, - this.ssHorizontalCover, this.stat, this.title, this.url, @@ -40,24 +18,10 @@ class PgcRankItemModel { factory PgcRankItemModel.fromJson(Map json) => PgcRankItemModel( - badge: json['badge'] as String?, - badgeInfo: json['badge_info'] == null - ? null - : BadgeInfo.fromJson(json['badge_info'] as Map), - badgeType: json['badge_type'] as int?, cover: json['cover'] as String?, - desc: json['desc'] as String?, - enableVt: json['enable_vt'] as bool?, - iconFont: json['icon_font'] == null - ? null - : IconFont.fromJson(json['icon_font'] as Map), newEp: json['new_ep'] == null ? null : NewEp.fromJson(json['new_ep'] as Map), - rank: json['rank'] as int?, - rating: json['rating'] as String?, - seasonId: json['season_id'] as int?, - ssHorizontalCover: json['ss_horizontal_cover'] as String?, stat: json['stat'] == null ? null : Stat.fromJson(json['stat'] as Map), diff --git a/lib/models_new/pgc/pgc_rank/stat.dart b/lib/models_new/pgc/pgc_rank/stat.dart index 0a06129ac7..5550df480c 100644 --- a/lib/models_new/pgc/pgc_rank/stat.dart +++ b/lib/models_new/pgc/pgc_rank/stat.dart @@ -1,15 +1,11 @@ class Stat { - int? danmaku; int? follow; - int? seriesFollow; int? view; - Stat({this.danmaku, this.follow, this.seriesFollow, this.view}); + Stat({this.follow, this.view}); factory Stat.fromJson(Map json) => Stat( - danmaku: json['danmaku'] as int?, follow: (json['follow'] as int?) ?? 0, - seriesFollow: json['series_follow'] as int?, view: (json['view'] as int?) ?? 0, ); } diff --git a/lib/models_new/pgc/pgc_review/list.dart b/lib/models_new/pgc/pgc_review/list.dart index 16fcdd0a72..f7e921f923 100644 --- a/lib/models_new/pgc/pgc_review/list.dart +++ b/lib/models_new/pgc/pgc_review/list.dart @@ -5,11 +5,6 @@ class PgcReviewItemModel { Author? author; String? title; String? content; - int? ctime; - int? mediaId; - int? mid; - int? mtime; - String? progress; String? pushTimeStr; int? reviewId; late int score; @@ -20,11 +15,6 @@ class PgcReviewItemModel { this.author, this.title, this.content, - this.ctime, - this.mediaId, - this.mid, - this.mtime, - this.progress, this.pushTimeStr, this.reviewId, required this.score, @@ -40,11 +30,6 @@ class PgcReviewItemModel { : Author.fromJson(json['author'] as Map), title: json['title'] as String?, content: json['content'] as String?, - ctime: json['ctime'] as int?, - mediaId: json['media_id'] as int?, - mid: json['mid'] as int?, - mtime: json['mtime'] as int?, - progress: json['progress'] as String?, pushTimeStr: json['push_time_str'] as String?, reviewId: json['review_id'] as int?, score: json['score'] == null ? 0 : json['score'] ~/ 2, diff --git a/lib/models_new/pgc/pgc_timeline/episode.dart b/lib/models_new/pgc/pgc_timeline/episode.dart index 417c6d1757..4c2ba0ef11 100644 --- a/lib/models_new/pgc/pgc_timeline/episode.dart +++ b/lib/models_new/pgc/pgc_timeline/episode.dart @@ -1,69 +1,29 @@ -import 'package:PiliPlus/models_new/pgc/pgc_timeline/icon_font.dart'; - class Episode { String? cover; - int? delay; - int? delayId; - String? delayIndex; - String? delayReason; - bool? enableVt; - String? epCover; int? episodeId; int? follow; - String? follows; - IconFont? iconFont; - String? plays; String? pubIndex; String? pubTime; - int? pubTs; - int? published; int? seasonId; - String? squareCover; String? title; Episode({ this.cover, - this.delay, - this.delayId, - this.delayIndex, - this.delayReason, - this.enableVt, - this.epCover, this.episodeId, this.follow, - this.follows, - this.iconFont, - this.plays, this.pubIndex, this.pubTime, - this.pubTs, - this.published, this.seasonId, - this.squareCover, this.title, }); factory Episode.fromJson(Map json) => Episode( cover: json['cover'] as String?, - delay: json['delay'] as int?, - delayId: json['delay_id'] as int?, - delayIndex: json['delay_index'] as String?, - delayReason: json['delay_reason'] as String?, - enableVt: json['enable_vt'] as bool?, - epCover: json['ep_cover'] as String?, episodeId: json['episode_id'] as int?, follow: json['follow'] as int?, - follows: json['follows'] as String?, - iconFont: json['icon_font'] == null - ? null - : IconFont.fromJson(json['icon_font'] as Map), - plays: json['plays'] as String?, pubIndex: json['pub_index'] as String?, pubTime: json['pub_time'] as String?, - pubTs: json['pub_ts'] as int?, - published: json['published'] as int?, seasonId: json['season_id'] as int?, - squareCover: json['square_cover'] as String?, title: json['title'] as String?, ); } diff --git a/lib/models_new/pgc/pgc_timeline/icon_font.dart b/lib/models_new/pgc/pgc_timeline/icon_font.dart deleted file mode 100644 index 0c5e2d35c9..0000000000 --- a/lib/models_new/pgc/pgc_timeline/icon_font.dart +++ /dev/null @@ -1,11 +0,0 @@ -class IconFont { - String? name; - String? text; - - IconFont({this.name, this.text}); - - factory IconFont.fromJson(Map json) => IconFont( - name: json['name'] as String?, - text: json['text'] as String?, - ); -} diff --git a/lib/models_new/pgc/pgc_timeline/pgc_timeline.dart b/lib/models_new/pgc/pgc_timeline/pgc_timeline.dart index e6275b7e02..6581fba5f8 100644 --- a/lib/models_new/pgc/pgc_timeline/pgc_timeline.dart +++ b/lib/models_new/pgc/pgc_timeline/pgc_timeline.dart @@ -1,15 +1,11 @@ import 'package:PiliPlus/models_new/pgc/pgc_timeline/result.dart'; class PgcTimeline { - int? code; - String? message; List? result; - PgcTimeline({this.code, this.message, this.result}); + PgcTimeline({this.result}); factory PgcTimeline.fromJson(Map json) => PgcTimeline( - code: json['code'] as int?, - message: json['message'] as String?, result: (json['result'] as List?) ?.map((e) => TimelineResult.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/popular/popular_precious/data.dart b/lib/models_new/popular/popular_precious/data.dart index 1be3efe068..b7e8642f86 100644 --- a/lib/models_new/popular/popular_precious/data.dart +++ b/lib/models_new/popular/popular_precious/data.dart @@ -1,18 +1,14 @@ import 'package:PiliPlus/models/model_hot_video_item.dart'; class PopularPreciousData { - String? title; int? mediaId; - String? explain; List? list; - PopularPreciousData({this.title, this.mediaId, this.explain, this.list}); + PopularPreciousData({this.mediaId, this.list}); factory PopularPreciousData.fromJson(Map json) => PopularPreciousData( - title: json['title'] as String?, mediaId: json['media_id'] as int?, - explain: json['explain'] as String?, list: (json['list'] as List?) ?.map((e) => HotVideoItemModel.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/popular/popular_series_list/list.dart b/lib/models_new/popular/popular_series_list/list.dart index e9e5ec4fd5..98bc2aa235 100644 --- a/lib/models_new/popular/popular_series_list/list.dart +++ b/lib/models_new/popular/popular_series_list/list.dart @@ -1,16 +1,12 @@ class PopularSeriesListItem { int? number; - String? subject; - int? status; String? name; - PopularSeriesListItem({this.number, this.subject, this.status, this.name}); + PopularSeriesListItem({this.number, this.name}); factory PopularSeriesListItem.fromJson(Map json) => PopularSeriesListItem( number: json['number'] as int?, - subject: json['subject'] as String?, - status: json['status'] as int?, name: json['name'] as String?, ); } diff --git a/lib/models_new/popular/popular_series_one/config.dart b/lib/models_new/popular/popular_series_one/config.dart index ec0d7b72c8..bd1fcc1e07 100644 --- a/lib/models_new/popular/popular_series_one/config.dart +++ b/lib/models_new/popular/popular_series_one/config.dart @@ -1,54 +1,18 @@ class PopularSeriesConfig { - int? id; - String? type; - int? number; - String? subject; - int? stime; - int? etime; - int? status; String? name; String? label; - String? hint; - int? color; - String? cover; - String? shareTitle; - String? shareSubtitle; int? mediaId; PopularSeriesConfig({ - this.id, - this.type, - this.number, - this.subject, - this.stime, - this.etime, - this.status, this.name, this.label, - this.hint, - this.color, - this.cover, - this.shareTitle, - this.shareSubtitle, this.mediaId, }); factory PopularSeriesConfig.fromJson(Map json) => PopularSeriesConfig( - id: json['id'] as int?, - type: json['type'] as String?, - number: json['number'] as int?, - subject: json['subject'] as String?, - stime: json['stime'] as int?, - etime: json['etime'] as int?, - status: json['status'] as int?, name: json['name'] as String?, label: json['label'] as String?, - hint: json['hint'] as String?, - color: json['color'] as int?, - cover: json['cover'] as String?, - shareTitle: json['share_title'] as String?, - shareSubtitle: json['share_subtitle'] as String?, mediaId: json['media_id'] as int?, ); } diff --git a/lib/models_new/relation/data.dart b/lib/models_new/relation/data.dart index 1a061965e2..9acaec43da 100644 --- a/lib/models_new/relation/data.dart +++ b/lib/models_new/relation/data.dart @@ -1,14 +1,12 @@ import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class RelationData { - int? mid; int? attribute; int? mtime; List? tag; int? special; RelationData({ - this.mid, this.attribute, this.mtime, this.tag, @@ -16,7 +14,6 @@ class RelationData { }); factory RelationData.fromJson(Map json) => RelationData( - mid: json['mid'] as int?, attribute: json['attribute'] as int?, mtime: json['mtime'] as int?, tag: (json['tag'] as List?)?.fromCast(), diff --git a/lib/models_new/search/search_trending/data.dart b/lib/models_new/search/search_trending/data.dart index 8fc2fb2c04..f11c204e0c 100644 --- a/lib/models_new/search/search_trending/data.dart +++ b/lib/models_new/search/search_trending/data.dart @@ -2,23 +2,27 @@ import 'package:PiliPlus/models_new/search/search_rcmd/data.dart'; import 'package:PiliPlus/models_new/search/search_trending/list.dart'; class SearchTrendingData extends SearchRcmdData { - List? topList; + late int topCount; - SearchTrendingData({super.list, this.topList}); - - factory SearchTrendingData.fromJson(Map json) => - SearchTrendingData( - list: (json['list'] as List?) - ?.map( - (e) => - SearchTrendingItemModel.fromJson(e as Map), - ) - .toList(), - topList: (json['top_list'] as List?) - ?.map( - (e) => - SearchTrendingItemModel.fromJson(e as Map), - ) - .toList(), - ); + SearchTrendingData.fromJson( + Map json, { + bool needsTop = false, + }) { + list = (json['list'] as List?) + ?.map((e) => SearchTrendingItemModel.fromJson(e)) + .toList(); + if (needsTop) { + final topList = (json['top_list'] as List?) + ?.map((e) => SearchTrendingItemModel.fromJson(e)) + .toList(); + topCount = topList?.length ?? 0; + if (topList != null && topList.isNotEmpty) { + if (list != null) { + list!.insertAll(0, topList); + } else { + list = topList; + } + } + } + } } diff --git a/lib/models_new/search/search_trending/list.dart b/lib/models_new/search/search_trending/list.dart index df3ed440f5..8e4ac1a66b 100644 --- a/lib/models_new/search/search_trending/list.dart +++ b/lib/models_new/search/search_trending/list.dart @@ -1,13 +1,11 @@ class SearchTrendingItemModel { String? keyword; - String? showName; String? icon; bool? showLiveIcon; String? recommendReason; SearchTrendingItemModel({ this.keyword, - this.showName, this.icon, this.showLiveIcon, this.recommendReason, @@ -16,7 +14,6 @@ class SearchTrendingItemModel { factory SearchTrendingItemModel.fromJson(Map json) => SearchTrendingItemModel( keyword: json['keyword'] as String?, - showName: json['show_name'] as String?, icon: json['icon'] as String?, showLiveIcon: json['show_live_icon'] as bool?, recommendReason: (json['recommend_reason'] as String?)?.replaceFirst( diff --git a/lib/models_new/space/space/archive.dart b/lib/models_new/space/space/archive.dart index 7e0d4bc7f6..fc053ac4ee 100644 --- a/lib/models_new/space/space/archive.dart +++ b/lib/models_new/space/space/archive.dart @@ -1,21 +1,14 @@ -import 'package:PiliPlus/models_new/space/space/episodic_button.dart'; import 'package:PiliPlus/models_new/space/space/order.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; class Archive { - EpisodicButton? episodicButton; List? order; int? count; List? item; - Archive({this.episodicButton, this.order, this.count, this.item}); + Archive({this.order, this.count, this.item}); factory Archive.fromJson(Map json) => Archive( - episodicButton: json['episodic_button'] == null - ? null - : EpisodicButton.fromJson( - json['episodic_button'] as Map, - ), order: (json['order'] as List?) ?.map((e) => Order.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/space/space/attention_tip.dart b/lib/models_new/space/space/attention_tip.dart deleted file mode 100644 index 7e75236d29..0000000000 --- a/lib/models_new/space/space/attention_tip.dart +++ /dev/null @@ -1,11 +0,0 @@ -class AttentionTip { - int? cardNum; - String? tip; - - AttentionTip({this.cardNum, this.tip}); - - factory AttentionTip.fromJson(Map json) => AttentionTip( - cardNum: json['card_num'] as int?, - tip: json['tip'] as String?, - ); -} diff --git a/lib/models_new/space/space/author.dart b/lib/models_new/space/space/author.dart deleted file mode 100644 index 4eda4c5940..0000000000 --- a/lib/models_new/space/space/author.dart +++ /dev/null @@ -1,43 +0,0 @@ -import 'package:PiliPlus/models/model_avatar.dart'; -import 'package:PiliPlus/models_new/space/space/nameplate.dart'; -import 'package:PiliPlus/models_new/space/space/official_verify.dart'; - -class Author { - int? mid; - String? name; - String? face; - Pendant? pendant; - OfficialVerify? officialVerify; - Nameplate? nameplate; - Vip? vip; - - Author({ - this.mid, - this.name, - this.face, - this.pendant, - this.officialVerify, - this.nameplate, - this.vip, - }); - - factory Author.fromJson(Map json) => Author( - mid: json['mid'] as int?, - name: json['name'] as String?, - face: json['face'] as String?, - pendant: json['pendant'] == null - ? null - : Pendant.fromJson(json['pendant'] as Map), - officialVerify: json['official_verify'] == null - ? null - : OfficialVerify.fromJson( - json['official_verify'] as Map, - ), - nameplate: json['nameplate'] == null - ? null - : Nameplate.fromJson(json['nameplate'] as Map), - vip: json['vip'] == null - ? null - : Vip.fromJson(json['vip'] as Map), - ); -} diff --git a/lib/models_new/space/space/badge.dart b/lib/models_new/space/space/badge.dart deleted file mode 100644 index 8e814f8176..0000000000 --- a/lib/models_new/space/space/badge.dart +++ /dev/null @@ -1,32 +0,0 @@ -class Badge { - String? text; - String? textColor; - String? textColorNight; - String? bgColor; - String? bgColorNight; - String? borderColor; - String? borderColorNight; - int? bgStyle; - - Badge({ - this.text, - this.textColor, - this.textColorNight, - this.bgColor, - this.bgColorNight, - this.borderColor, - this.borderColorNight, - this.bgStyle, - }); - - factory Badge.fromJson(Map json) => Badge( - text: json['text'] as String?, - textColor: json['text_color'] as String?, - textColorNight: json['text_color_night'] as String?, - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - borderColor: json['border_color'] as String?, - borderColorNight: json['border_color_night'] as String?, - bgStyle: json['bg_style'] as int?, - ); -} diff --git a/lib/models_new/space/space/button.dart b/lib/models_new/space/space/button.dart deleted file mode 100644 index 3f1f813a5e..0000000000 --- a/lib/models_new/space/space/button.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Button { - int? type; - String? text; - String? jumpUrl; - - Button({this.type, this.text, this.jumpUrl}); - - factory Button.fromJson(Map json) => Button( - type: json['type'] as int?, - text: json['text'] as String?, - jumpUrl: json['jump_url'] as String?, - ); -} diff --git a/lib/models_new/space/space/card.dart b/lib/models_new/space/space/card.dart index 0504e43625..5553f71f37 100644 --- a/lib/models_new/space/space/card.dart +++ b/lib/models_new/space/space/card.dart @@ -1,42 +1,28 @@ import 'package:PiliPlus/models/model_avatar.dart'; import 'package:PiliPlus/models_new/space/space/achieve.dart'; -import 'package:PiliPlus/models_new/space/space/entrance.dart'; import 'package:PiliPlus/models_new/space/space/followings_followed_upper.dart'; -import 'package:PiliPlus/models_new/space/space/honours.dart'; import 'package:PiliPlus/models_new/space/space/level_info.dart'; import 'package:PiliPlus/models_new/space/space/likes.dart'; import 'package:PiliPlus/models_new/space/space/live_fans_wearing.dart'; -import 'package:PiliPlus/models_new/space/space/nameplate.dart'; -import 'package:PiliPlus/models_new/space/space/nft_certificate.dart'; import 'package:PiliPlus/models_new/space/space/official_verify.dart'; import 'package:PiliPlus/models_new/space/space/pr_info.dart'; -import 'package:PiliPlus/models_new/space/space/profession_verify.dart'; import 'package:PiliPlus/models_new/space/space/relation.dart'; import 'package:PiliPlus/models_new/space/space/space_tag.dart'; class SpaceCard { String? mid; String? name; - bool? approve; - String? rank; String? face; - String? displayRank; int? regtime; - int? spacesta; String? birthday; - String? place; - String? description; int? article; - dynamic attentions; int? fans; int? friend; int? attention; String? sign; LevelInfo? levelInfo; Pendant? pendant; - Nameplate? nameplate; OfficialVerify? officialVerify; - ProfessionVerify? professionVerify; Vip? vip; int? silence; int? endTime; @@ -44,45 +30,25 @@ class SpaceCard { Likes? likes; Achieve? achieve; SpaceRelation? relation; - int? isDeleted; - Honours? honours; LiveFansWearing? liveFansWearing; List? spaceTag; - int? faceNftNew; - bool? hasFaceNft; - NftCertificate? nftCertificate; - Entrance? entrance; - String? nftId; - dynamic nftFaceIcon; - String? digitalId; - int? digitalType; - bool? hasDigitalAsset; SpacePrInfo? prInfo; FollowingsFollowedUpper? followingsFollowedUpper; SpaceCard({ this.mid, this.name, - this.approve, - this.rank, this.face, - this.displayRank, this.regtime, - this.spacesta, this.birthday, - this.place, - this.description, this.article, - this.attentions, this.fans, this.friend, this.attention, this.sign, this.levelInfo, this.pendant, - this.nameplate, this.officialVerify, - this.professionVerify, this.vip, this.silence, this.endTime, @@ -90,19 +56,8 @@ class SpaceCard { this.likes, this.achieve, this.relation, - this.isDeleted, - this.honours, this.liveFansWearing, this.spaceTag, - this.faceNftNew, - this.hasFaceNft, - this.nftCertificate, - this.entrance, - this.nftId, - this.nftFaceIcon, - this.digitalId, - this.digitalType, - this.hasDigitalAsset, this.prInfo, this.followingsFollowedUpper, }); @@ -110,17 +65,10 @@ class SpaceCard { factory SpaceCard.fromJson(Map json) => SpaceCard( mid: json['mid'] as String?, name: json['name'] as String?, - approve: json['approve'] as bool?, - rank: json['rank'] as String?, face: json['face'] as String?, - displayRank: json['DisplayRank'] as String?, regtime: json['regtime'] as int?, - spacesta: json['spacesta'] as int?, birthday: json['birthday'] as String?, - place: json['place'] as String?, - description: json['description'] as String?, article: json['article'] as int?, - attentions: json['attentions'] as dynamic, fans: json['fans'] as int?, friend: json['friend'] as int?, attention: json['attention'] as int?, @@ -131,19 +79,11 @@ class SpaceCard { pendant: json['pendant'] == null ? null : Pendant.fromJson(json['pendant'] as Map), - nameplate: json['nameplate'] == null - ? null - : Nameplate.fromJson(json['nameplate'] as Map), officialVerify: json['official_verify'] == null ? null : OfficialVerify.fromJson( json['official_verify'] as Map, ), - professionVerify: json['profession_verify'] == null - ? null - : ProfessionVerify.fromJson( - json['profession_verify'] as Map, - ), vip: json['vip'] == null ? null : Vip.fromJson(json['vip'] as Map), @@ -159,10 +99,6 @@ class SpaceCard { relation: json['relation'] == null ? null : SpaceRelation.fromJson(json['relation'] as Map), - isDeleted: json['is_deleted'] as int?, - honours: json['honours'] == null - ? null - : Honours.fromJson(json['honours'] as Map), liveFansWearing: json['live_fans_wearing'] == null ? null : LiveFansWearing.fromJson( @@ -172,21 +108,6 @@ class SpaceCard { ?.where((e) => const ['location', 'real_name'].contains(e['type'])) .map((e) => SpaceTag.fromJson(e as Map)) .toList(), - faceNftNew: json['face_nft_new'] as int?, - hasFaceNft: json['has_face_nft'] as bool?, - nftCertificate: json['nft_certificate'] == null - ? null - : NftCertificate.fromJson( - json['nft_certificate'] as Map, - ), - entrance: json['entrance'] == null - ? null - : Entrance.fromJson(json['entrance'] as Map), - nftId: json['nft_id'] as String?, - nftFaceIcon: json['nft_face_icon'] as dynamic, - digitalId: json['digital_id'] as String?, - digitalType: json['digital_type'] as int?, - hasDigitalAsset: json['has_digital_asset'] as bool?, prInfo: json['pr_info'] == null ? null : SpacePrInfo.fromJson(json['pr_info'] as Map), diff --git a/lib/models_new/space/space/category.dart b/lib/models_new/space/space/category.dart deleted file mode 100644 index 7b202e7013..0000000000 --- a/lib/models_new/space/space/category.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Category { - int? id; - int? parentId; - String? name; - - Category({this.id, this.parentId, this.name}); - - factory Category.fromJson(Map json) => Category( - id: json['id'] as int?, - parentId: json['parent_id'] as int?, - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/space/space/collection_top_simple.dart b/lib/models_new/space/space/collection_top_simple.dart index f342af140e..365c987fcc 100644 --- a/lib/models_new/space/space/collection_top_simple.dart +++ b/lib/models_new/space/space/collection_top_simple.dart @@ -1,17 +1,10 @@ -import 'package:PiliPlus/models_new/space/space/preference.dart'; import 'package:PiliPlus/models_new/space/space/top.dart'; class CollectionTopSimple { Top? top; - int? max; - Preference? preference; - String? collectionCompletedUrl; CollectionTopSimple({ this.top, - this.max, - this.preference, - this.collectionCompletedUrl, }); factory CollectionTopSimple.fromJson(Map json) { @@ -19,11 +12,6 @@ class CollectionTopSimple { top: json['top'] == null ? null : Top.fromJson(json['top'] as Map), - max: json['max'] as int?, - preference: json['preference'] == null - ? null - : Preference.fromJson(json['preference'] as Map), - collectionCompletedUrl: json['collection_completed_url'] as String?, ); } } diff --git a/lib/models_new/space/space/color_config.dart b/lib/models_new/space/space/color_config.dart deleted file mode 100644 index 10e68358fc..0000000000 --- a/lib/models_new/space/space/color_config.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/day.dart'; -import 'package:PiliPlus/models_new/space/space/night.dart'; - -class ColorConfig { - bool? isDarkModeAware; - Day? day; - Night? night; - - ColorConfig({this.isDarkModeAware, this.day, this.night}); - - factory ColorConfig.fromJson(Map json) => ColorConfig( - isDarkModeAware: json['is_dark_mode_aware'] as bool?, - day: json['day'] == null - ? null - : Day.fromJson(json['day'] as Map), - night: json['night'] == null - ? null - : Night.fromJson(json['night'] as Map), - ); -} diff --git a/lib/models_new/space/space/colour.dart b/lib/models_new/space/space/colour.dart deleted file mode 100644 index 2bb5ef6f71..0000000000 --- a/lib/models_new/space/space/colour.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Colour { - String? dark; - String? normal; - - Colour({this.dark, this.normal}); - - factory Colour.fromJson(Map json) => Colour( - dark: json['dark'] as String?, - normal: json['normal'] as String?, - ); -} diff --git a/lib/models_new/space/space/container_size.dart b/lib/models_new/space/space/container_size.dart deleted file mode 100644 index 8d2252460f..0000000000 --- a/lib/models_new/space/space/container_size.dart +++ /dev/null @@ -1,11 +0,0 @@ -class ContainerSize { - double? width; - double? height; - - ContainerSize({this.width, this.height}); - - factory ContainerSize.fromJson(Map json) => ContainerSize( - width: (json['width'] as num?)?.toDouble(), - height: (json['height'] as num?)?.toDouble(), - ); -} diff --git a/lib/models_new/space/space/cover.dart b/lib/models_new/space/space/cover.dart deleted file mode 100644 index 6463e37862..0000000000 --- a/lib/models_new/space/space/cover.dart +++ /dev/null @@ -1,9 +0,0 @@ -class Cover { - String? url; - - Cover({this.url}); - - factory Cover.fromJson(Map json) => Cover( - url: json['url'] as String?, - ); -} diff --git a/lib/models_new/space/space/data.dart b/lib/models_new/space/space/data.dart index b2456c2ef1..4155dc4be3 100644 --- a/lib/models_new/space/space/data.dart +++ b/lib/models_new/space/space/data.dart @@ -1,34 +1,28 @@ import 'package:PiliPlus/models_new/space/space/archive.dart'; import 'package:PiliPlus/models_new/space/space/article.dart'; -import 'package:PiliPlus/models_new/space/space/attention_tip.dart'; import 'package:PiliPlus/models_new/space/space/audios.dart'; import 'package:PiliPlus/models_new/space/space/card.dart'; import 'package:PiliPlus/models_new/space/space/cheese.dart'; import 'package:PiliPlus/models_new/space/space/coin_archive.dart'; import 'package:PiliPlus/models_new/space/space/comic.dart'; import 'package:PiliPlus/models_new/space/space/elec.dart'; -import 'package:PiliPlus/models_new/space/space/entry.dart'; import 'package:PiliPlus/models_new/space/space/favourite2.dart'; import 'package:PiliPlus/models_new/space/space/guard.dart'; import 'package:PiliPlus/models_new/space/space/images.dart'; import 'package:PiliPlus/models_new/space/space/like_archive.dart'; import 'package:PiliPlus/models_new/space/space/live.dart'; -import 'package:PiliPlus/models_new/space/space/nft_show_module.dart'; -import 'package:PiliPlus/models_new/space/space/play_game.dart'; +import 'package:PiliPlus/models_new/space/space/reservation_card_list.dart'; import 'package:PiliPlus/models_new/space/space/season.dart'; import 'package:PiliPlus/models_new/space/space/series.dart'; import 'package:PiliPlus/models_new/space/space/setting.dart'; -import 'package:PiliPlus/models_new/space/space/space_button_list.dart'; import 'package:PiliPlus/models_new/space/space/tab.dart'; import 'package:PiliPlus/models_new/space/space/tab2.dart'; import 'package:PiliPlus/models_new/space/space/ugc_season.dart'; class SpaceData { int? relation; - int? guestRelation; int? medal; String? defaultTab; - bool? isParams; SpaceSetting? setting; SpaceTab? tab; SpaceCard? card; @@ -37,7 +31,6 @@ class SpaceData { Elec? elec; Archive? archive; SpaceSeries? series; - PlayGame? playGame; Article? article; SpaceSeason? season; CoinArchive? coinArchive; @@ -46,26 +39,17 @@ class SpaceData { Favourite2? favourite2; Comic? comic; UgcSeason? ugcSeason; - int? adShopType; - String? adContainerPath; Cheese? cheese; Guard? guard; - AttentionTip? attentionTip; - NftShowModule? nftShowModule; List? tab2; - dynamic nftFaceButton; - dynamic digitalButton; - List? entry; - List? spaceButtonList; int? relSpecial; bool? hasItem; + List? reservationCardList; SpaceData({ this.relation, - this.guestRelation, this.medal, this.defaultTab, - this.isParams, this.setting, this.tab, this.card, @@ -74,7 +58,6 @@ class SpaceData { this.elec, this.archive, this.series, - this.playGame, this.article, this.season, this.coinArchive, @@ -85,22 +68,15 @@ class SpaceData { this.ugcSeason, this.cheese, this.guard, - this.attentionTip, - this.nftShowModule, this.tab2, - this.nftFaceButton, - this.digitalButton, - this.entry, - this.spaceButtonList, this.relSpecial, + this.reservationCardList, }); SpaceData.fromJson(Map json) { relation = json['relation'] as int?; - guestRelation = json['guest_relation'] as int?; medal = json['medal'] as int?; defaultTab = json['default_tab'] as String?; - isParams = json['is_params'] as bool?; setting = json['setting'] == null ? null : SpaceSetting.fromJson(json['setting'] as Map); @@ -125,9 +101,6 @@ class SpaceData { series = json['series'] == null ? null : SpaceSeries.fromJson(json['series'] as Map); - playGame = json['play_game'] == null - ? null - : PlayGame.fromJson(json['play_game'] as Map); article = json['article'] == null ? null : Article.fromJson(json['article'] as Map); @@ -158,26 +131,13 @@ class SpaceData { guard = json['guard'] == null ? null : Guard.fromJson(json['guard'] as Map); - attentionTip = json['attention_tip'] == null - ? null - : AttentionTip.fromJson(json['attention_tip'] as Map); - nftShowModule = json['nft_show_module'] == null - ? null - : NftShowModule.fromJson( - json['nft_show_module'] as Map, - ); tab2 = (json['tab2'] as List?) ?.map((e) => SpaceTab2.fromJson(e as Map)) .toList(); - nftFaceButton = json['nft_face_button'] as dynamic; - digitalButton = json['digital_button'] as dynamic; - entry = (json['entry'] as List?) - ?.map((e) => Entry.fromJson(e as Map)) - .toList(); - spaceButtonList = (json['space_button_list'] as List?) - ?.map((e) => SpaceButtonList.fromJson(e as Map)) - .toList(); relSpecial = (json['rel_special'] as num?)?.toInt(); + reservationCardList = (json['reservation_card_list'] as List?) + ?.map((e) => ReservationCardItem.fromJson(e)) + .toList(); hasItem = archive?.item?.isNotEmpty == true || favourite2?.item?.isNotEmpty == true || diff --git a/lib/models_new/space/space/day.dart b/lib/models_new/space/space/day.dart deleted file mode 100644 index ac458a786b..0000000000 --- a/lib/models_new/space/space/day.dart +++ /dev/null @@ -1,9 +0,0 @@ -class Day { - String? argb; - - Day({this.argb}); - - factory Day.fromJson(Map json) => Day( - argb: json['argb'] as String?, - ); -} diff --git a/lib/models_new/space/space/digital_info.dart b/lib/models_new/space/space/digital_info.dart deleted file mode 100644 index eef172c60d..0000000000 --- a/lib/models_new/space/space/digital_info.dart +++ /dev/null @@ -1,44 +0,0 @@ -class DigitalInfo { - bool? active; - String? jumpUrl; - int? nftType; - int? backgroundHandle; - String? animationFirstFrame; - dynamic musicAlbum; - dynamic animation; - String? nftRegionTitle; - int? cardId; - String? cutSpaceBg; - int? partType; - String? itemJumpUrl; - - DigitalInfo({ - this.active, - this.jumpUrl, - this.nftType, - this.backgroundHandle, - this.animationFirstFrame, - this.musicAlbum, - this.animation, - this.nftRegionTitle, - this.cardId, - this.cutSpaceBg, - this.partType, - this.itemJumpUrl, - }); - - factory DigitalInfo.fromJson(Map json) => DigitalInfo( - active: json['active'] as bool?, - jumpUrl: json['jump_url'] as String?, - nftType: json['nft_type'] as int?, - backgroundHandle: json['background_handle'] as int?, - animationFirstFrame: json['animation_first_frame'] as String?, - musicAlbum: json['music_album'] as dynamic, - animation: json['animation'] as dynamic, - nftRegionTitle: json['nft_region_title'] as String?, - cardId: json['card_id'] as int?, - cutSpaceBg: json['cut_space_bg'] as String?, - partType: json['part_type'] as int?, - itemJumpUrl: json['item_jump_url'] as String?, - ); -} diff --git a/lib/models_new/space/space/display.dart b/lib/models_new/space/space/display.dart deleted file mode 100644 index a7beb2f52c..0000000000 --- a/lib/models_new/space/space/display.dart +++ /dev/null @@ -1,20 +0,0 @@ -class Display { - String? bgThemeLight; - String? bgThemeNight; - String? nftPoster; - String? nftRaw; - - Display({ - this.bgThemeLight, - this.bgThemeNight, - this.nftPoster, - this.nftRaw, - }); - - factory Display.fromJson(Map json) => Display( - bgThemeLight: json['bg_theme_light'] as String?, - bgThemeNight: json['bg_theme_night'] as String?, - nftPoster: json['nft_poster'] as String?, - nftRaw: json['nft_raw'] as String?, - ); -} diff --git a/lib/models_new/space/space/draw.dart b/lib/models_new/space/space/draw.dart deleted file mode 100644 index 6408c33144..0000000000 --- a/lib/models_new/space/space/draw.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/color_config.dart'; - -class Draw { - int? drawType; - int? fillMode; - ColorConfig? colorConfig; - - Draw({this.drawType, this.fillMode, this.colorConfig}); - - factory Draw.fromJson(Map json) => Draw( - drawType: json['draw_type'] as int?, - fillMode: json['fill_mode'] as int?, - colorConfig: json['color_config'] == null - ? null - : ColorConfig.fromJson(json['color_config'] as Map), - ); -} diff --git a/lib/models_new/space/space/draw_src.dart b/lib/models_new/space/space/draw_src.dart deleted file mode 100644 index 58a3e60015..0000000000 --- a/lib/models_new/space/space/draw_src.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/draw.dart'; - -class DrawSrc { - int? srcType; - Draw? draw; - - DrawSrc({this.srcType, this.draw}); - - factory DrawSrc.fromJson(Map json) => DrawSrc( - srcType: json['src_type'] as int?, - draw: json['draw'] == null - ? null - : Draw.fromJson(json['draw'] as Map), - ); -} diff --git a/lib/models_new/space/space/elec.dart b/lib/models_new/space/space/elec.dart index 1db500ad56..3e2a9518b3 100644 --- a/lib/models_new/space/space/elec.dart +++ b/lib/models_new/space/space/elec.dart @@ -1,54 +1,35 @@ -import 'package:PiliPlus/models_new/space/space/elec_set.dart'; -import 'package:PiliPlus/models_new/space/space/list.dart'; +import 'package:PiliPlus/models/model_owner.dart'; class Elec { - bool? show; int? total; - int? count; - int? elecNum; - List? list; - ElecSet? elecSet; - int? state; - String? upowerTitle; - String? upowerJumpUrl; - String? upowerIconUrl; - int? upowerState; - String? rankTitle; - String? rankUrl; + List? list; Elec({ - this.show, this.total, - this.count, - this.elecNum, this.list, - this.elecSet, - this.state, - this.upowerTitle, - this.upowerJumpUrl, - this.upowerIconUrl, - this.upowerState, - this.rankTitle, - this.rankUrl, }); factory Elec.fromJson(Map json) => Elec( - show: json['show'] as bool?, total: json['total'] as int?, - count: json['count'] as int?, - elecNum: json['elec_num'] as int?, list: (json['list'] as List?) - ?.map((e) => ListItem.fromJson(e as Map)) + ?.map((e) => ElecItem.fromJson(e)) .toList(), - elecSet: json['elec_set'] == null - ? null - : ElecSet.fromJson(json['elec_set'] as Map), - state: json['state'] as int?, - upowerTitle: json['upower_title'] as String?, - upowerJumpUrl: json['upower_jump_url'] as String?, - upowerIconUrl: json['upower_icon_url'] as String?, - upowerState: json['upower_state'] as int?, - rankTitle: json['rank_title'] as String?, - rankUrl: json['rank_url'] as String?, + ); +} + +class ElecItem extends Owner { + String? uname; + String? avatar; + @override + String? get face => avatar; + + ElecItem({ + this.uname, + this.avatar, + }); + + factory ElecItem.fromJson(Map json) => ElecItem( + uname: json['uname'] as String?, + avatar: json['avatar'] as String?, ); } diff --git a/lib/models_new/space/space/elec_list.dart b/lib/models_new/space/space/elec_list.dart deleted file mode 100644 index 0696d5d67a..0000000000 --- a/lib/models_new/space/space/elec_list.dart +++ /dev/null @@ -1,44 +0,0 @@ -class ElecList { - String? title; - int? elecNum; - int? isCustomize; - String? bpNum; - String? minBp; - String? maxBp; - int? bpNumFen; - int? isDefault; - int? minElec; - int? maxElec; - int? minBpFen; - int? maxBpFen; - - ElecList({ - this.title, - this.elecNum, - this.isCustomize, - this.bpNum, - this.minBp, - this.maxBp, - this.bpNumFen, - this.isDefault, - this.minElec, - this.maxElec, - this.minBpFen, - this.maxBpFen, - }); - - factory ElecList.fromJson(Map json) => ElecList( - title: json['title'] as String?, - elecNum: json['elec_num'] as int?, - isCustomize: json['is_customize'] as int?, - bpNum: json['bp_num'] as String?, - minBp: json['min_bp'] as String?, - maxBp: json['max_bp'] as String?, - bpNumFen: json['bp_num_fen'] as int?, - isDefault: json['is_default'] as int?, - minElec: json['min_elec'] as int?, - maxElec: json['max_elec'] as int?, - minBpFen: json['min_bp_fen'] as int?, - maxBpFen: json['max_bp_fen'] as int?, - ); -} diff --git a/lib/models_new/space/space/elec_set.dart b/lib/models_new/space/space/elec_set.dart deleted file mode 100644 index a48c581d99..0000000000 --- a/lib/models_new/space/space/elec_set.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/elec_list.dart'; - -class ElecSet { - int? elecTheme; - int? rmbRate; - int? integrityRate; - int? roundMode; - List? elecList; - String? batteryItemDesc; - - ElecSet({ - this.elecTheme, - this.rmbRate, - this.integrityRate, - this.roundMode, - this.elecList, - this.batteryItemDesc, - }); - - factory ElecSet.fromJson(Map json) => ElecSet( - elecTheme: json['elec_theme'] as int?, - rmbRate: json['rmb_rate'] as int?, - integrityRate: json['integrity_rate'] as int?, - roundMode: json['round_mode'] as int?, - elecList: (json['elec_list'] as List?) - ?.map((e) => ElecList.fromJson(e as Map)) - .toList(), - batteryItemDesc: json['battery_item_desc'] as String?, - ); -} diff --git a/lib/models_new/space/space/entrance.dart b/lib/models_new/space/space/entrance.dart deleted file mode 100644 index e2c6dd941b..0000000000 --- a/lib/models_new/space/space/entrance.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Entrance { - String? icon; - String? jumpUrl; - bool? isShowEntrance; - - Entrance({this.icon, this.jumpUrl, this.isShowEntrance}); - - factory Entrance.fromJson(Map json) => Entrance( - icon: json['icon'] as String?, - jumpUrl: json['jump_url'] as String?, - isShowEntrance: json['is_show_entrance'] as bool?, - ); -} diff --git a/lib/models_new/space/space/entrance_button.dart b/lib/models_new/space/space/entrance_button.dart deleted file mode 100644 index e70dbca058..0000000000 --- a/lib/models_new/space/space/entrance_button.dart +++ /dev/null @@ -1,13 +0,0 @@ -class EntranceButton { - String? uri; - String? title; - - EntranceButton({this.uri, this.title}); - - factory EntranceButton.fromJson(Map json) { - return EntranceButton( - uri: json['uri'] as String?, - title: json['title'] as String?, - ); - } -} diff --git a/lib/models_new/space/space/entry.dart b/lib/models_new/space/space/entry.dart deleted file mode 100644 index 183e484349..0000000000 --- a/lib/models_new/space/space/entry.dart +++ /dev/null @@ -1,15 +0,0 @@ -class Entry { - String? icon; - String? jumpLink; - String? accessibility; - bool? needLogin; - - Entry({this.icon, this.jumpLink, this.accessibility, this.needLogin}); - - factory Entry.fromJson(Map json) => Entry( - icon: json['icon'] as String?, - jumpLink: json['jump_link'] as String?, - accessibility: json['accessibility'] as String?, - needLogin: json['need_login'] as bool?, - ); -} diff --git a/lib/models_new/space/space/episodic_button.dart b/lib/models_new/space/space/episodic_button.dart deleted file mode 100644 index c53c5184f2..0000000000 --- a/lib/models_new/space/space/episodic_button.dart +++ /dev/null @@ -1,13 +0,0 @@ -class EpisodicButton { - String? text; - String? uri; - - EpisodicButton({this.text, this.uri}); - - factory EpisodicButton.fromJson(Map json) { - return EpisodicButton( - text: json['text'] as String?, - uri: json['uri'] as String?, - ); - } -} diff --git a/lib/models_new/space/space/extra.dart b/lib/models_new/space/space/extra.dart deleted file mode 100644 index 4fa04ed160..0000000000 --- a/lib/models_new/space/space/extra.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/card.dart'; - -class Extra { - SpaceCard? card; - int? salesType; - int? upzoneEntranceType; - String? upzoneEntranceReportId; - - Extra({ - this.card, - this.salesType, - this.upzoneEntranceType, - this.upzoneEntranceReportId, - }); - - factory Extra.fromJson(Map json) => Extra( - card: json['card'] == null - ? null - : SpaceCard.fromJson(json['card'] as Map), - salesType: json['sales_type'] as int?, - upzoneEntranceType: json['upzone_entrance_type'] as int?, - upzoneEntranceReportId: json['upzone_entrance_report_id'] as String?, - ); -} diff --git a/lib/models_new/space/space/general_spec.dart b/lib/models_new/space/space/general_spec.dart deleted file mode 100644 index c69b0515b2..0000000000 --- a/lib/models_new/space/space/general_spec.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/pos_spec.dart'; -import 'package:PiliPlus/models_new/space/space/render_spec.dart'; -import 'package:PiliPlus/models_new/space/space/size_spec.dart'; - -class GeneralSpec { - PosSpec? posSpec; - SizeSpec? sizeSpec; - RenderSpec? renderSpec; - - GeneralSpec({this.posSpec, this.sizeSpec, this.renderSpec}); - - factory GeneralSpec.fromJson(Map json) => GeneralSpec( - posSpec: json['pos_spec'] == null - ? null - : PosSpec.fromJson(json['pos_spec'] as Map), - sizeSpec: json['size_spec'] == null - ? null - : SizeSpec.fromJson(json['size_spec'] as Map), - renderSpec: json['render_spec'] == null - ? null - : RenderSpec.fromJson(json['render_spec'] as Map), - ); -} diff --git a/lib/models_new/space/space/guard.dart b/lib/models_new/space/space/guard.dart index 0c2bace364..bbf4f8a45a 100644 --- a/lib/models_new/space/space/guard.dart +++ b/lib/models_new/space/space/guard.dart @@ -1,21 +1,18 @@ -import 'package:PiliPlus/models_new/space/space/item.dart'; +import 'package:PiliPlus/models/model_owner.dart'; class Guard { String? uri; - String? desc; - String? highLight; - List? item; - String? buttonMsg; + Object? count; + List? item; - Guard({this.uri, this.desc, this.highLight, this.item, this.buttonMsg}); - - factory Guard.fromJson(Map json) => Guard( - uri: json['uri'] as String?, - desc: json['desc'] as String?, - highLight: json['high_light'] as String?, - item: (json['item'] as List?) - ?.map((e) => Item.fromJson(e as Map)) - .toList(), - buttonMsg: json['button_msg'] as String?, - ); + Guard.fromJson(Map json) { + uri = json['uri'] as String?; + item = (json['item'] as List?) + ?.map((e) => Owner.fromJson(e as Map)) + .toList(); + final String? desc = json['desc']; + if (desc != null) { + count = RegExp(r'^(\d+)').firstMatch(desc)?.group(1); + } + } } diff --git a/lib/models_new/space/space/honours.dart b/lib/models_new/space/space/honours.dart deleted file mode 100644 index da7d9d573d..0000000000 --- a/lib/models_new/space/space/honours.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/colour.dart'; - -class Honours { - Colour? colour; - List? tags; - - Honours({this.colour, this.tags}); - - factory Honours.fromJson(Map json) => Honours( - colour: json['colour'] == null - ? null - : Colour.fromJson(json['colour'] as Map), - tags: json['tags'] as List?, - ); -} diff --git a/lib/models_new/space/space/images.dart b/lib/models_new/space/space/images.dart index 5ada250f19..7c35c27c34 100644 --- a/lib/models_new/space/space/images.dart +++ b/lib/models_new/space/space/images.dart @@ -1,47 +1,19 @@ import 'package:PiliPlus/models_new/space/space/collection_top_simple.dart'; -import 'package:PiliPlus/models_new/space/space/digital_info.dart'; -import 'package:PiliPlus/models_new/space/space/entrance_button.dart'; -import 'package:PiliPlus/models_new/space/space/purchase_button.dart'; class SpaceImages { String? imgUrl; String? nightImgurl; - bool? goodsAvailable; - PurchaseButton? purchaseButton; - EntranceButton? entranceButton; - DigitalInfo? digitalInfo; - bool? showDigital; CollectionTopSimple? collectionTopSimple; SpaceImages({ this.imgUrl, this.nightImgurl, - this.goodsAvailable, - this.purchaseButton, - this.entranceButton, - this.digitalInfo, - this.showDigital, this.collectionTopSimple, }); factory SpaceImages.fromJson(Map json) => SpaceImages( imgUrl: json['imgUrl'] as String?, nightImgurl: json['night_imgurl'] as String?, - goodsAvailable: json['goods_available'] as bool?, - purchaseButton: json['purchase_button'] == null - ? null - : PurchaseButton.fromJson( - json['purchase_button'] as Map, - ), - entranceButton: json['entrance_button'] == null - ? null - : EntranceButton.fromJson( - json['entrance_button'] as Map, - ), - digitalInfo: json['digital_info'] == null - ? null - : DigitalInfo.fromJson(json['digital_info'] as Map), - showDigital: json['show_digital'] as bool?, collectionTopSimple: json['collection_top_simple'] == null ? null : CollectionTopSimple.fromJson( diff --git a/lib/models_new/space/space/item.dart b/lib/models_new/space/space/item.dart index ccbd3aa9ed..0506d90bc5 100644 --- a/lib/models_new/space/space/item.dart +++ b/lib/models_new/space/space/item.dart @@ -1,5 +1,3 @@ -import 'package:PiliPlus/models_new/space/space/badge.dart'; - class Item { String? title; String? subtitle; @@ -24,7 +22,6 @@ class Item { int? danmaku; int? ctime; int? ugcPay; - List? badges; String? author; bool? state; String? bvid; @@ -58,7 +55,6 @@ class Item { this.danmaku, this.ctime, this.ugcPay, - this.badges, this.author, this.state, this.bvid, @@ -93,9 +89,6 @@ class Item { danmaku: json['danmaku'] as int?, ctime: json['ctime'] as int?, ugcPay: json['ugc_pay'] as int?, - badges: (json['badges'] as List?) - ?.map((e) => Badge.fromJson(e as Map)) - .toList(), author: json['author'] as String?, state: json['state'] as bool?, bvid: json['bvid'] as String?, diff --git a/lib/models_new/space/space/label.dart b/lib/models_new/space/space/label.dart deleted file mode 100644 index 3368bab8de..0000000000 --- a/lib/models_new/space/space/label.dart +++ /dev/null @@ -1,32 +0,0 @@ -class Label { - String? path; - String? text; - String? labelTheme; - String? textColor; - int? bgStyle; - String? bgColor; - String? borderColor; - String? image; - - Label({ - this.path, - this.text, - this.labelTheme, - this.textColor, - this.bgStyle, - this.bgColor, - this.borderColor, - this.image, - }); - - factory Label.fromJson(Map json) => Label( - path: json['path'] as String?, - text: json['text'] as String?, - labelTheme: json['label_theme'] as String?, - textColor: json['text_color'] as String?, - bgStyle: json['bg_style'] as int?, - bgColor: json['bg_color'] as String?, - borderColor: json['border_color'] as String?, - image: json['image'] as String?, - ); -} diff --git a/lib/models_new/space/space/level_info.dart b/lib/models_new/space/space/level_info.dart index 38f32ed7c1..0fa7c69989 100644 --- a/lib/models_new/space/space/level_info.dart +++ b/lib/models_new/space/space/level_info.dart @@ -1,32 +1,14 @@ -import 'package:PiliPlus/models_new/space/space/senior_inquiry.dart'; - class LevelInfo { int? currentLevel; - int? currentMin; - int? currentExp; - dynamic nextExp; int? identity; - SeniorInquiry? seniorInquiry; LevelInfo({ this.currentLevel, - this.currentMin, - this.currentExp, - this.nextExp, this.identity, - this.seniorInquiry, }); factory LevelInfo.fromJson(Map json) => LevelInfo( currentLevel: json['current_level'] as int?, - currentMin: json['current_min'] as int?, - currentExp: json['current_exp'] as int?, - nextExp: json['next_exp'] as dynamic, identity: json['identity'] as int?, - seniorInquiry: json['senior_inquiry'] == null - ? null - : SeniorInquiry.fromJson( - json['senior_inquiry'] as Map, - ), ); } diff --git a/lib/models_new/space/space/list.dart b/lib/models_new/space/space/list.dart index 33bad5ddb2..2798ec5ffc 100644 --- a/lib/models_new/space/space/list.dart +++ b/lib/models_new/space/space/list.dart @@ -4,7 +4,6 @@ class ListItem { int? trendType; String? message; int? mid; - dynamic vipInfo; String? uname; String? avatar; @@ -14,7 +13,6 @@ class ListItem { this.trendType, this.message, this.mid, - this.vipInfo, this.uname, this.avatar, }); @@ -25,7 +23,6 @@ class ListItem { trendType: json['trend_type'] as int?, message: json['message'] as String?, mid: json['mid'] as int?, - vipInfo: json['vip_info'] as dynamic, uname: json['uname'] as String?, avatar: json['avatar'] as String?, ); diff --git a/lib/models_new/space/space/media.dart b/lib/models_new/space/space/media.dart deleted file mode 100644 index 64a3ffabc6..0000000000 --- a/lib/models_new/space/space/media.dart +++ /dev/null @@ -1,32 +0,0 @@ -class Media { - int? score; - int? mediaId; - String? title; - String? cover; - String? area; - int? typeId; - String? typeName; - int? spoiler; - - Media({ - this.score, - this.mediaId, - this.title, - this.cover, - this.area, - this.typeId, - this.typeName, - this.spoiler, - }); - - factory Media.fromJson(Map json) => Media( - score: json['score'] as int?, - mediaId: json['media_id'] as int?, - title: json['title'] as String?, - cover: json['cover'] as String?, - area: json['area'] as String?, - typeId: json['type_id'] as int?, - typeName: json['type_name'] as String?, - spoiler: json['spoiler'] as int?, - ); -} diff --git a/lib/models_new/space/space/nameplate.dart b/lib/models_new/space/space/nameplate.dart deleted file mode 100644 index ccf0b89fcc..0000000000 --- a/lib/models_new/space/space/nameplate.dart +++ /dev/null @@ -1,26 +0,0 @@ -class Nameplate { - int? nid; - String? name; - String? image; - String? imageSmall; - String? level; - String? condition; - - Nameplate({ - this.nid, - this.name, - this.image, - this.imageSmall, - this.level, - this.condition, - }); - - factory Nameplate.fromJson(Map json) => Nameplate( - nid: json['nid'] as int?, - name: json['name'] as String?, - image: json['image'] as String?, - imageSmall: json['image_small'] as String?, - level: json['level'] as String?, - condition: json['condition'] as String?, - ); -} diff --git a/lib/models_new/space/space/nft.dart b/lib/models_new/space/space/nft.dart deleted file mode 100644 index 112337d589..0000000000 --- a/lib/models_new/space/space/nft.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/display.dart'; - -class Nft { - String? itemName; - String? issuer; - String? serialNumber; - String? detailUrl; - int? nftStatus; - Display? display; - - Nft({ - this.itemName, - this.issuer, - this.serialNumber, - this.detailUrl, - this.nftStatus, - this.display, - }); - - factory Nft.fromJson(Map json) => Nft( - itemName: json['item_name'] as String?, - issuer: json['issuer'] as String?, - serialNumber: json['serial_number'] as String?, - detailUrl: json['detail_url'] as String?, - nftStatus: json['nft_status'] as int?, - display: json['display'] == null - ? null - : Display.fromJson(json['display'] as Map), - ); -} diff --git a/lib/models_new/space/space/nft_certificate.dart b/lib/models_new/space/space/nft_certificate.dart deleted file mode 100644 index 328abbd4c1..0000000000 --- a/lib/models_new/space/space/nft_certificate.dart +++ /dev/null @@ -1,11 +0,0 @@ -class NftCertificate { - String? detailUrl; - - NftCertificate({this.detailUrl}); - - factory NftCertificate.fromJson(Map json) { - return NftCertificate( - detailUrl: json['detail_url'] as String?, - ); - } -} diff --git a/lib/models_new/space/space/nft_show_module.dart b/lib/models_new/space/space/nft_show_module.dart deleted file mode 100644 index 8cb0cc34aa..0000000000 --- a/lib/models_new/space/space/nft_show_module.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/nft.dart'; - -class NftShowModule { - int? total; - String? artsMoreJump; - List? nfts; - String? floorTitle; - - NftShowModule({ - this.total, - this.artsMoreJump, - this.nfts, - this.floorTitle, - }); - - factory NftShowModule.fromJson(Map json) => NftShowModule( - total: json['total'] as int?, - artsMoreJump: json['arts_more_jump'] as String?, - nfts: (json['nfts'] as List?) - ?.map((e) => Nft.fromJson(e as Map)) - .toList(), - floorTitle: json['floor_title'] as String?, - ); -} diff --git a/lib/models_new/space/space/night.dart b/lib/models_new/space/space/night.dart deleted file mode 100644 index c9fb6c9a73..0000000000 --- a/lib/models_new/space/space/night.dart +++ /dev/null @@ -1,9 +0,0 @@ -class Night { - String? argb; - - Night({this.argb}); - - factory Night.fromJson(Map json) => Night( - argb: json['argb'] as String?, - ); -} diff --git a/lib/models_new/space/space/official_verify.dart b/lib/models_new/space/space/official_verify.dart index b6230da686..eb6ae21c3e 100644 --- a/lib/models_new/space/space/official_verify.dart +++ b/lib/models_new/space/space/official_verify.dart @@ -1,28 +1,9 @@ -class OfficialVerify { - int? type; - String? desc; - int? role; - String? title; - String? icon; - String? spliceTitle; +import 'package:PiliPlus/models/model_avatar.dart'; - OfficialVerify({ - this.type, - this.desc, - this.role, - this.title, - this.icon, - this.spliceTitle, - }); +class OfficialVerify extends BaseOfficialVerify { + String? spliceTitle; - factory OfficialVerify.fromJson(Map json) { - return OfficialVerify( - type: json['type'] as int?, - desc: json['desc'] as String?, - role: json['role'] as int?, - title: json['title'] as String?, - icon: json['icon'] as String?, - spliceTitle: json['splice_title'] as String?, - ); + OfficialVerify.fromJson(Map json) : super.fromJson(json) { + spliceTitle = json['splice_title'] as String?; } } diff --git a/lib/models_new/space/space/play_game.dart b/lib/models_new/space/space/play_game.dart deleted file mode 100644 index b0d7b0eb86..0000000000 --- a/lib/models_new/space/space/play_game.dart +++ /dev/null @@ -1,11 +0,0 @@ -class PlayGame { - int? count; - List? item; - - PlayGame({this.count, this.item}); - - factory PlayGame.fromJson(Map json) => PlayGame( - count: json['count'] as int?, - item: json['item'] as List?, - ); -} diff --git a/lib/models_new/space/space/pos_spec.dart b/lib/models_new/space/space/pos_spec.dart deleted file mode 100644 index 140da9c10d..0000000000 --- a/lib/models_new/space/space/pos_spec.dart +++ /dev/null @@ -1,13 +0,0 @@ -class PosSpec { - int? coordinatePos; - double? axisX; - double? axisY; - - PosSpec({this.coordinatePos, this.axisX, this.axisY}); - - factory PosSpec.fromJson(Map json) => PosSpec( - coordinatePos: json['coordinate_pos'] as int?, - axisX: (json['axis_x'] as num?)?.toDouble(), - axisY: (json['axis_y'] as num?)?.toDouble(), - ); -} diff --git a/lib/models_new/space/space/preference.dart b/lib/models_new/space/space/preference.dart deleted file mode 100644 index c7373363cb..0000000000 --- a/lib/models_new/space/space/preference.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Preference { - bool? collectionPublic; - String? garbFirstGain; - int? orderType; - - Preference({this.collectionPublic, this.garbFirstGain, this.orderType}); - - factory Preference.fromJson(Map json) => Preference( - collectionPublic: json['collection_public'] as bool?, - garbFirstGain: json['garb_first_gain'] as String?, - orderType: json['order_type'] as int?, - ); -} diff --git a/lib/models_new/space/space/profession_verify.dart b/lib/models_new/space/space/profession_verify.dart deleted file mode 100644 index 64fc96c463..0000000000 --- a/lib/models_new/space/space/profession_verify.dart +++ /dev/null @@ -1,13 +0,0 @@ -class ProfessionVerify { - String? icon; - String? showDesc; - - ProfessionVerify({this.icon, this.showDesc}); - - factory ProfessionVerify.fromJson(Map json) { - return ProfessionVerify( - icon: json['icon'] as String?, - showDesc: json['show_desc'] as String?, - ); - } -} diff --git a/lib/models_new/space/space/purchase_button.dart b/lib/models_new/space/space/purchase_button.dart deleted file mode 100644 index 5f67e31b6f..0000000000 --- a/lib/models_new/space/space/purchase_button.dart +++ /dev/null @@ -1,13 +0,0 @@ -class PurchaseButton { - String? uri; - String? title; - - PurchaseButton({this.uri, this.title}); - - factory PurchaseButton.fromJson(Map json) { - return PurchaseButton( - uri: json['uri'] as String?, - title: json['title'] as String?, - ); - } -} diff --git a/lib/models_new/space/space/render_spec.dart b/lib/models_new/space/space/render_spec.dart deleted file mode 100644 index 022738977c..0000000000 --- a/lib/models_new/space/space/render_spec.dart +++ /dev/null @@ -1,9 +0,0 @@ -class RenderSpec { - int? opacity; - - RenderSpec({this.opacity}); - - factory RenderSpec.fromJson(Map json) => RenderSpec( - opacity: json['opacity'] as int?, - ); -} diff --git a/lib/models_new/space/space/res_native_draw.dart b/lib/models_new/space/space/res_native_draw.dart deleted file mode 100644 index 4e057efe26..0000000000 --- a/lib/models_new/space/space/res_native_draw.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/draw_src.dart'; - -class ResNativeDraw { - DrawSrc? drawSrc; - - ResNativeDraw({this.drawSrc}); - - factory ResNativeDraw.fromJson(Map json) => ResNativeDraw( - drawSrc: json['draw_src'] == null - ? null - : DrawSrc.fromJson(json['draw_src'] as Map), - ); -} diff --git a/lib/models_new/space/space/reservation_card_list.dart b/lib/models_new/space/space/reservation_card_list.dart new file mode 100644 index 0000000000..3fe8e65926 --- /dev/null +++ b/lib/models_new/space/space/reservation_card_list.dart @@ -0,0 +1,53 @@ +import 'package:PiliPlus/utils/parse_string.dart'; + +class ReservationCardItem { + int? sid; + String? name; + int total; + bool isFollow; + int? livePlanStartTime; + String? descText1; + String? dynamicId; + LotteryPrizeInfo? lotteryPrizeInfo; + + ReservationCardItem({ + this.sid, + this.name, + required this.total, + required this.isFollow, + this.livePlanStartTime, + this.descText1, + this.dynamicId, + this.lotteryPrizeInfo, + }); + + factory ReservationCardItem.fromJson(Map json) => + ReservationCardItem( + sid: json['sid'] as int?, + name: json['name'] as String?, + total: json['total'] ?? 0, + isFollow: json['is_follow'] == 1, + livePlanStartTime: json['live_plan_start_time'] as int?, + descText1: nonNullOrEmptyString(json['desc_text_1']?['text']), + dynamicId: json['dynamic_id'] as String?, + lotteryPrizeInfo: json['lottery_prize_info'] == null + ? null + : LotteryPrizeInfo.fromJson( + json['lottery_prize_info'] as Map, + ), + ); +} + +class LotteryPrizeInfo { + String? text; + String? jumpUrl; + + LotteryPrizeInfo({this.text, this.jumpUrl}); + + factory LotteryPrizeInfo.fromJson(Map json) { + return LotteryPrizeInfo( + text: json['text'] as String?, + jumpUrl: json['jump_url'] as String?, + ); + } +} diff --git a/lib/models_new/space/space/resource.dart b/lib/models_new/space/space/resource.dart deleted file mode 100644 index fef0f52ff2..0000000000 --- a/lib/models_new/space/space/resource.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:PiliPlus/models_new/space/space/res_native_draw.dart'; - -class Resource { - int? resType; - ResNativeDraw? resNativeDraw; - - Resource({this.resType, this.resNativeDraw}); - - factory Resource.fromJson(Map json) => Resource( - resType: json['res_type'] as int?, - resNativeDraw: json['res_native_draw'] == null - ? null - : ResNativeDraw.fromJson( - json['res_native_draw'] as Map, - ), - ); -} diff --git a/lib/models_new/space/space/senior_inquiry.dart b/lib/models_new/space/space/senior_inquiry.dart deleted file mode 100644 index 94a9518b91..0000000000 --- a/lib/models_new/space/space/senior_inquiry.dart +++ /dev/null @@ -1,11 +0,0 @@ -class SeniorInquiry { - String? inquiryText; - String? inquiryUrl; - - SeniorInquiry({this.inquiryText, this.inquiryUrl}); - - factory SeniorInquiry.fromJson(Map json) => SeniorInquiry( - inquiryText: json['inquiry_text'] as String?, - inquiryUrl: json['inquiry_url'] as String?, - ); -} diff --git a/lib/models_new/space/space/size_spec.dart b/lib/models_new/space/space/size_spec.dart deleted file mode 100644 index 07ec504416..0000000000 --- a/lib/models_new/space/space/size_spec.dart +++ /dev/null @@ -1,11 +0,0 @@ -class SizeSpec { - double? width; - double? height; - - SizeSpec({this.width, this.height}); - - factory SizeSpec.fromJson(Map json) => SizeSpec( - width: (json['width'] as num?)?.toDouble(), - height: (json['height'] as num?)?.toDouble(), - ); -} diff --git a/lib/models_new/space/space/space_button_list.dart b/lib/models_new/space/space/space_button_list.dart deleted file mode 100644 index f095efd4a0..0000000000 --- a/lib/models_new/space/space/space_button_list.dart +++ /dev/null @@ -1,43 +0,0 @@ -class SpaceButtonList { - String? icon; - String? title; - String? subTitle; - String? url; - String? moduleType; - String? titleDarkColor; - String? titleLightColor; - String? subTitleDarkColor; - String? subTitleLightColor; - String? backgroundDarkColor; - String? backgroundLightColor; - - SpaceButtonList({ - this.icon, - this.title, - this.subTitle, - this.url, - this.moduleType, - this.titleDarkColor, - this.titleLightColor, - this.subTitleDarkColor, - this.subTitleLightColor, - this.backgroundDarkColor, - this.backgroundLightColor, - }); - - factory SpaceButtonList.fromJson(Map json) { - return SpaceButtonList( - icon: json['icon'] as String?, - title: json['title'] as String?, - subTitle: json['sub_title'] as String?, - url: json['url'] as String?, - moduleType: json['module_type'] as String?, - titleDarkColor: json['title_dark_color'] as String?, - titleLightColor: json['title_light_color'] as String?, - subTitleDarkColor: json['sub_title_dark_color'] as String?, - subTitleLightColor: json['sub_title_light_color'] as String?, - backgroundDarkColor: json['background_dark_color'] as String?, - backgroundLightColor: json['background_light_color'] as String?, - ); - } -} diff --git a/lib/models_new/space/space/stats.dart b/lib/models_new/space/space/stats.dart deleted file mode 100644 index 590b8c79a7..0000000000 --- a/lib/models_new/space/space/stats.dart +++ /dev/null @@ -1,32 +0,0 @@ -class Stats { - int? view; - int? favorite; - int? like; - int? dislike; - int? reply; - int? share; - num? coin; - int? dynam1c; - - Stats({ - this.view, - this.favorite, - this.like, - this.dislike, - this.reply, - this.share, - this.coin, - this.dynam1c, - }); - - factory Stats.fromJson(Map json) => Stats( - view: json['view'] as int?, - favorite: json['favorite'] as int?, - like: json['like'] as int?, - dislike: json['dislike'] as int?, - reply: json['reply'] as int?, - share: json['share'] as int?, - coin: json['coin'] as num?, - dynam1c: json['dynamic'] as int?, - ); -} diff --git a/lib/models_new/space/space/top.dart b/lib/models_new/space/space/top.dart index d87410d40c..bc66cf7de0 100644 --- a/lib/models_new/space/space/top.dart +++ b/lib/models_new/space/space/top.dart @@ -28,7 +28,7 @@ class TopImage { final item = json['item']; final img = item['image']; title = json['title'] == null ? null : TopTitle.fromJson(json['title']); - _defaultImage = noneNullOrEmptyString(img?['default_image']); + _defaultImage = nonNullOrEmptyString(img?['default_image']); fullCover = json['cover']; double dy = 0; try { diff --git a/lib/models_new/space/space_archive/badge.dart b/lib/models_new/space/space_archive/badge.dart index 8e814f8176..1a73dc1550 100644 --- a/lib/models_new/space/space_archive/badge.dart +++ b/lib/models_new/space/space_archive/badge.dart @@ -1,32 +1,11 @@ class Badge { String? text; - String? textColor; - String? textColorNight; - String? bgColor; - String? bgColorNight; - String? borderColor; - String? borderColorNight; - int? bgStyle; Badge({ this.text, - this.textColor, - this.textColorNight, - this.bgColor, - this.bgColorNight, - this.borderColor, - this.borderColorNight, - this.bgStyle, }); factory Badge.fromJson(Map json) => Badge( text: json['text'] as String?, - textColor: json['text_color'] as String?, - textColorNight: json['text_color_night'] as String?, - bgColor: json['bg_color'] as String?, - bgColorNight: json['bg_color_night'] as String?, - borderColor: json['border_color'] as String?, - borderColorNight: json['border_color_night'] as String?, - bgStyle: json['bg_style'] as int?, ); } diff --git a/lib/models_new/space/space_archive/cursor_attr.dart b/lib/models_new/space/space_archive/cursor_attr.dart deleted file mode 100644 index 37abe1d5e4..0000000000 --- a/lib/models_new/space/space_archive/cursor_attr.dart +++ /dev/null @@ -1,11 +0,0 @@ -class CursorAttr { - bool? isLastWatchedArc; - int? rank; - - CursorAttr({this.isLastWatchedArc, this.rank}); - - factory CursorAttr.fromJson(Map json) => CursorAttr( - isLastWatchedArc: json['is_last_watched_arc'] as bool?, - rank: json['rank'] as int?, - ); -} diff --git a/lib/models_new/space/space_archive/data.dart b/lib/models_new/space/space_archive/data.dart index bdd2b22936..2310d7167b 100644 --- a/lib/models_new/space/space_archive/data.dart +++ b/lib/models_new/space/space_archive/data.dart @@ -1,24 +1,18 @@ import 'package:PiliPlus/models_new/space/space_archive/episodic_button.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; -import 'package:PiliPlus/models_new/space/space_archive/last_watched_locator.dart'; -import 'package:PiliPlus/models_new/space/space_archive/order.dart'; class SpaceArchiveData { EpisodicButton? episodicButton; - List? order; int? count; List? item; - LastWatchedLocator? lastWatchedLocator; bool? hasNext; bool? hasPrev; int? next; SpaceArchiveData({ this.episodicButton, - this.order, this.count, this.item, - this.lastWatchedLocator, this.hasNext, this.hasPrev, this.next, @@ -31,18 +25,10 @@ class SpaceArchiveData { : EpisodicButton.fromJson( json['episodic_button'] as Map, ), - order: (json['order'] as List?) - ?.map((e) => Order.fromJson(e as Map)) - .toList(), count: json['count'] as int?, item: (json['item'] as List?) ?.map((e) => SpaceArchiveItem.fromJson(e as Map)) .toList(), - lastWatchedLocator: json['last_watched_locator'] == null - ? null - : LastWatchedLocator.fromJson( - json['last_watched_locator'] as Map, - ), hasNext: json['has_next'] as bool?, hasPrev: json['has_prev'] as bool?, next: json['next'], diff --git a/lib/models_new/space/space_archive/item.dart b/lib/models_new/space/space_archive/item.dart index 8940f20698..5d03c7db20 100644 --- a/lib/models_new/space/space_archive/item.dart +++ b/lib/models_new/space/space_archive/item.dart @@ -1,33 +1,18 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/models_new/space/space_archive/badge.dart'; -import 'package:PiliPlus/models_new/space/space_archive/cursor_attr.dart'; import 'package:PiliPlus/models_new/space/space_archive/history.dart'; import 'package:PiliPlus/models_new/space/space_archive/season.dart'; class SpaceArchiveItem extends BaseSimpleVideoItemModel { - String? subtitle; - String? tname; - String? coverIcon; String? uri; String? param; String? goto; String? length; - bool? isPopular; bool? isSteins; - bool? isUgcpay; bool? isCooperation; bool? isPgc; - bool? isLivePlayback; bool? isPugv; - bool? isFold; - bool? isOneself; - int? ctime; - int? ugcPay; - bool? state; - int? videos; - CursorAttr? cursorAttr; - int? iconType; String? publishTimeText; List? badges; SpaceArchiveSeason? season; @@ -37,34 +22,18 @@ class SpaceArchiveItem extends BaseSimpleVideoItemModel { SpaceArchiveItem.fromJson(Map json) { title = json['title']; - subtitle = json['subtitle']; - tname = json['tname']; cover = json['cover']; - coverIcon = json['cover_icon']; uri = json['uri']; param = json['param']; goto = json['goto']; length = json['length']; duration = json['duration'] ?? -1; - isPopular = json['is_popular']; isSteins = json['is_steins']; - isUgcpay = json['is_ugcpay']; isCooperation = json['is_cooperation']; isPgc = json['is_pgc']; - isLivePlayback = json['is_live_playback']; isPugv = json['is_pugv']; - isFold = json['is_fold']; - isOneself = json['is_oneself']; - ctime = json['ctime']; - ugcPay = json['ugc_pay']; - state = json['state']; bvid = json['bvid']; - videos = json['videos']; cid = json['first_cid']; - cursorAttr = json['cursor_attr'] == null - ? null - : CursorAttr.fromJson(json['cursor_attr'] as Map); - iconType = json['icon_type']; publishTimeText = json['publish_time_text']; badges = (json['badges'] as List?) ?.map((e) => Badge.fromJson(e as Map)) diff --git a/lib/models_new/space/space_archive/last_watched_locator.dart b/lib/models_new/space/space_archive/last_watched_locator.dart deleted file mode 100644 index 7c020eeac3..0000000000 --- a/lib/models_new/space/space_archive/last_watched_locator.dart +++ /dev/null @@ -1,19 +0,0 @@ -class LastWatchedLocator { - int? displayThreshold; - int? insertRanking; - String? text; - - LastWatchedLocator({ - this.displayThreshold, - this.insertRanking, - this.text, - }); - - factory LastWatchedLocator.fromJson(Map json) { - return LastWatchedLocator( - displayThreshold: json['display_threshold'] as int?, - insertRanking: json['insert_ranking'] as int?, - text: json['text'] as String?, - ); - } -} diff --git a/lib/models_new/space/space_archive/order.dart b/lib/models_new/space/space_archive/order.dart deleted file mode 100644 index f0bb1b5eb2..0000000000 --- a/lib/models_new/space/space_archive/order.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Order { - String? title; - String? value; - - Order({this.title, this.value}); - - factory Order.fromJson(Map json) => Order( - title: json['title'] as String?, - value: json['value'] as String?, - ); -} diff --git a/lib/models_new/space/space_archive/stats.dart b/lib/models_new/space/space_archive/stats.dart deleted file mode 100644 index 9e8997cf4f..0000000000 --- a/lib/models_new/space/space_archive/stats.dart +++ /dev/null @@ -1,15 +0,0 @@ -class SpaceArchiveStat { - String? viewStr; - String? danmuStr; - - SpaceArchiveStat({ - this.viewStr, - this.danmuStr, - }); - - factory SpaceArchiveStat.fromJson(Map json) => - SpaceArchiveStat( - viewStr: json['view_str'], - danmuStr: json['danmu_str'], - ); -} diff --git a/lib/models_new/space/space_article/author.dart b/lib/models_new/space/space_article/author.dart deleted file mode 100644 index 1a23e45dde..0000000000 --- a/lib/models_new/space/space_article/author.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:PiliPlus/models/model_avatar.dart'; - -class Author { - int? mid; - String? name; - String? face; - Pendant? pendant; - BaseOfficialVerify? officialVerify; - Vip? vip; - - Author({ - this.mid, - this.name, - this.face, - this.pendant, - this.officialVerify, - this.vip, - }); - - factory Author.fromJson(Map json) => Author( - mid: json['mid'] as int?, - name: json['name'] as String?, - face: json['face'] as String?, - pendant: json['pendant'] == null - ? null - : Pendant.fromJson(json['pendant'] as Map), - officialVerify: json['official_verify'] == null - ? null - : BaseOfficialVerify.fromJson( - json['official_verify'] as Map, - ), - vip: json['vip'] == null - ? null - : Vip.fromJson(json['vip'] as Map), - ); -} diff --git a/lib/models_new/space/space_article/category.dart b/lib/models_new/space/space_article/category.dart deleted file mode 100644 index 7b202e7013..0000000000 --- a/lib/models_new/space/space_article/category.dart +++ /dev/null @@ -1,13 +0,0 @@ -class Category { - int? id; - int? parentId; - String? name; - - Category({this.id, this.parentId, this.name}); - - factory Category.fromJson(Map json) => Category( - id: json['id'] as int?, - parentId: json['parent_id'] as int?, - name: json['name'] as String?, - ); -} diff --git a/lib/models_new/space/space_article/data.dart b/lib/models_new/space/space_article/data.dart index 2cf360426d..7972879b17 100644 --- a/lib/models_new/space/space_article/data.dart +++ b/lib/models_new/space/space_article/data.dart @@ -1,13 +1,11 @@ import 'package:PiliPlus/models_new/space/space_article/item.dart'; -import 'package:PiliPlus/models_new/space/space_article/list.dart'; class SpaceArticleData { int? count; List? item; int? listsCount; - List? lists; - SpaceArticleData({this.count, this.item, this.listsCount, this.lists}); + SpaceArticleData({this.count, this.item, this.listsCount}); factory SpaceArticleData.fromJson(Map json) => SpaceArticleData( @@ -16,8 +14,5 @@ class SpaceArticleData { ?.map((e) => SpaceArticleItem.fromJson(e as Map)) .toList(), listsCount: json['lists_count'] as int?, - lists: (json['lists'] as List?) - ?.map((e) => SpaceArticleList.fromJson(e as Map)) - .toList(), ); } diff --git a/lib/models_new/space/space_article/item.dart b/lib/models_new/space/space_article/item.dart index 94237d2dd8..828b5f807c 100644 --- a/lib/models_new/space/space_article/item.dart +++ b/lib/models_new/space/space_article/item.dart @@ -1,136 +1,29 @@ -import 'package:PiliPlus/models_new/space/space_article/author.dart'; -import 'package:PiliPlus/models_new/space/space_article/category.dart'; -import 'package:PiliPlus/models_new/space/space_article/media.dart'; import 'package:PiliPlus/models_new/space/space_article/stats.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class SpaceArticleItem { - int? id; - Category? category; - List? categories; String? title; - String? summary; - String? bannerUrl; - int? templateId; - int? state; - Author? author; - int? reprint; - List? imageUrls; - int? publishTime; - int? ctime; - int? mtime; Stats? stats; - int? attributes; - int? words; List? originImageUrls; - dynamic list; - bool? isLike; - Media? media; - String? applyTime; - String? checkTime; - int? original; - int? actId; - dynamic dispute; - dynamic authenMark; - int? coverAvid; - dynamic topVideoInfo; - int? type; - int? checkState; - int? originTemplateId; String? uri; - String? param; - String? goto; String? publishTimeText; - String? dynam1c; SpaceArticleItem({ - this.id, - this.category, - this.categories, this.title, - this.summary, - this.bannerUrl, - this.templateId, - this.state, - this.author, - this.reprint, - this.imageUrls, - this.publishTime, - this.ctime, - this.mtime, this.stats, - this.attributes, - this.words, this.originImageUrls, - this.list, - this.isLike, - this.media, - this.applyTime, - this.checkTime, - this.original, - this.actId, - this.dispute, - this.authenMark, - this.coverAvid, - this.topVideoInfo, - this.type, - this.checkState, - this.originTemplateId, this.uri, - this.param, - this.goto, this.publishTimeText, - this.dynam1c, }); factory SpaceArticleItem.fromJson(Map json) => SpaceArticleItem( - id: json['id'] as int?, - category: json['category'] == null - ? null - : Category.fromJson(json['category'] as Map), - categories: (json['categories'] as List?) - ?.map((e) => Category.fromJson(e as Map)) - .toList(), title: json['title'] as String?, - summary: json['summary'] as String?, - bannerUrl: json['banner_url'] as String?, - templateId: json['template_id'] as int?, - state: json['state'] as int?, - author: json['author'] == null - ? null - : Author.fromJson(json['author'] as Map), - reprint: json['reprint'] as int?, - imageUrls: (json['image_urls'] as List?)?.fromCast(), - publishTime: json['publish_time'] as int?, - ctime: json['ctime'] as int?, - mtime: json['mtime'] as int?, stats: json['stats'] == null ? null : Stats.fromJson(json['stats'] as Map), - attributes: json['attributes'] as int?, - words: json['words'] as int?, originImageUrls: (json['origin_image_urls'] as List?)?.fromCast(), - list: json['list'] as dynamic, - isLike: json['is_like'] as bool?, - media: json['media'] == null - ? null - : Media.fromJson(json['media'] as Map), - applyTime: json['apply_time'] as String?, - checkTime: json['check_time'] as String?, - original: json['original'] as int?, - actId: json['act_id'] as int?, - dispute: json['dispute'] as dynamic, - authenMark: json['authenMark'] as dynamic, - coverAvid: json['cover_avid'] as int?, - topVideoInfo: json['top_video_info'] as dynamic, - type: json['type'] as int?, - checkState: json['check_state'] as int?, - originTemplateId: json['origin_template_id'] as int?, uri: json['uri'] as String?, - param: json['param'] as String?, - goto: json['goto'] as String?, publishTimeText: json['publish_time_text'] as String?, - dynam1c: json['dynamic'] as String?, ); } diff --git a/lib/models_new/space/space_article/list.dart b/lib/models_new/space/space_article/list.dart deleted file mode 100644 index 11b8659a19..0000000000 --- a/lib/models_new/space/space_article/list.dart +++ /dev/null @@ -1,45 +0,0 @@ -class SpaceArticleList { - int? id; - int? mid; - String? name; - String? imageUrl; - int? updateTime; - int? ctime; - int? publishTime; - String? summary; - int? words; - int? read; - int? articlesCount; - String? updateTimeText; - - SpaceArticleList({ - this.id, - this.mid, - this.name, - this.imageUrl, - this.updateTime, - this.ctime, - this.publishTime, - this.summary, - this.words, - this.read, - this.articlesCount, - this.updateTimeText, - }); - - factory SpaceArticleList.fromJson(Map json) => - SpaceArticleList( - id: json['id'] as int?, - mid: json['mid'] as int?, - name: json['name'] as String?, - imageUrl: json['image_url'] as String?, - updateTime: json['update_time'] as int?, - ctime: json['ctime'] as int?, - publishTime: json['publish_time'] as int?, - summary: json['summary'] as String?, - words: json['words'] as int?, - read: json['read'] as int?, - articlesCount: json['articles_count'] as int?, - updateTimeText: json['update_time_text'] as String?, - ); -} diff --git a/lib/models_new/space/space_article/media.dart b/lib/models_new/space/space_article/media.dart deleted file mode 100644 index 64a3ffabc6..0000000000 --- a/lib/models_new/space/space_article/media.dart +++ /dev/null @@ -1,32 +0,0 @@ -class Media { - int? score; - int? mediaId; - String? title; - String? cover; - String? area; - int? typeId; - String? typeName; - int? spoiler; - - Media({ - this.score, - this.mediaId, - this.title, - this.cover, - this.area, - this.typeId, - this.typeName, - this.spoiler, - }); - - factory Media.fromJson(Map json) => Media( - score: json['score'] as int?, - mediaId: json['media_id'] as int?, - title: json['title'] as String?, - cover: json['cover'] as String?, - area: json['area'] as String?, - typeId: json['type_id'] as int?, - typeName: json['type_name'] as String?, - spoiler: json['spoiler'] as int?, - ); -} diff --git a/lib/models_new/space/space_article/stats.dart b/lib/models_new/space/space_article/stats.dart index 590b8c79a7..58471864c6 100644 --- a/lib/models_new/space/space_article/stats.dart +++ b/lib/models_new/space/space_article/stats.dart @@ -1,32 +1,14 @@ class Stats { int? view; - int? favorite; - int? like; - int? dislike; int? reply; - int? share; - num? coin; - int? dynam1c; Stats({ this.view, - this.favorite, - this.like, - this.dislike, this.reply, - this.share, - this.coin, - this.dynam1c, }); factory Stats.fromJson(Map json) => Stats( view: json['view'] as int?, - favorite: json['favorite'] as int?, - like: json['like'] as int?, - dislike: json['dislike'] as int?, reply: json['reply'] as int?, - share: json['share'] as int?, - coin: json['coin'] as num?, - dynam1c: json['dynamic'] as int?, ); } diff --git a/lib/models_new/space/space_audio/data.dart b/lib/models_new/space/space_audio/data.dart index d6ba1999e7..a173e915c5 100644 --- a/lib/models_new/space/space_audio/data.dart +++ b/lib/models_new/space/space_audio/data.dart @@ -1,25 +1,16 @@ import 'package:PiliPlus/models_new/space/space_audio/item.dart'; class SpaceAudioData { - int? curPage; - int? pageCount; int? totalSize; - int? pageSize; List? items; SpaceAudioData({ - this.curPage, - this.pageCount, this.totalSize, - this.pageSize, this.items, }); factory SpaceAudioData.fromJson(Map json) => SpaceAudioData( - curPage: json['curPage'] as int?, - pageCount: json['pageCount'] as int?, totalSize: json['totalSize'] as int?, - pageSize: json['pageSize'] as int?, items: (json['data'] as List?) ?.map((e) => SpaceAudioItem.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/space/space_audio/item.dart b/lib/models_new/space/space_audio/item.dart index 9017064197..9fff691bfb 100644 --- a/lib/models_new/space/space_audio/item.dart +++ b/lib/models_new/space/space_audio/item.dart @@ -3,88 +3,37 @@ import 'package:PiliPlus/models_new/space/space_audio/statistic.dart'; class SpaceAudioItem { int? id; int? uid; - String? uname; - String? author; String? title; String? cover; - String? intro; - String? lyric; - int? crtype; - int? duration; - int? passtime; - int? curtime; int? aid; String? bvid; int? cid; - int? msid; - int? attr; - int? limit; - int? activityId; - String? limitdesc; - num? coinNum; int? ctime; Statistic? statistic; - dynamic vipInfo; - dynamic collectIds; - int? isCooper; SpaceAudioItem({ this.id, this.uid, - this.uname, - this.author, this.title, this.cover, - this.intro, - this.lyric, - this.crtype, - this.duration, - this.passtime, - this.curtime, this.aid, this.bvid, this.cid, - this.msid, - this.attr, - this.limit, - this.activityId, - this.limitdesc, - this.coinNum, this.ctime, this.statistic, - this.vipInfo, - this.collectIds, - this.isCooper, }); factory SpaceAudioItem.fromJson(Map json) => SpaceAudioItem( id: json['id'] as int?, uid: json['uid'] as int?, - uname: json['uname'] as String?, - author: json['author'] as String?, title: json['title'] as String?, cover: json['cover'] as String?, - intro: json['intro'] as String?, - lyric: json['lyric'] as String?, - crtype: json['crtype'] as int?, - duration: json['duration'] as int?, - passtime: json['passtime'] as int?, - curtime: json['curtime'] as int?, aid: json['aid'] as int?, bvid: json['bvid'] as String?, cid: json['cid'] as int?, - msid: json['msid'] as int?, - attr: json['attr'] as int?, - limit: json['limit'] as int?, - activityId: json['activityId'] as int?, - limitdesc: json['limitdesc'] as String?, - coinNum: json['coin_num'] as num?, ctime: json['ctime'] as int?, statistic: json['statistic'] == null ? null : Statistic.fromJson(json['statistic'] as Map), - vipInfo: json['vipInfo'] as dynamic, - collectIds: json['collectIds'] as dynamic, - isCooper: json['is_cooper'] as int?, ); } diff --git a/lib/models_new/space/space_audio/statistic.dart b/lib/models_new/space/space_audio/statistic.dart index fc4e5bb14a..ea9539bd85 100644 --- a/lib/models_new/space/space_audio/statistic.dart +++ b/lib/models_new/space/space_audio/statistic.dart @@ -1,17 +1,11 @@ class Statistic { - int? sid; int? play; - int? collect; int? comment; - int? share; - Statistic({this.sid, this.play, this.collect, this.comment, this.share}); + Statistic({this.play, this.comment}); factory Statistic.fromJson(Map json) => Statistic( - sid: json['sid'] as int?, play: json['play'] as int?, - collect: json['collect'] as int?, comment: json['comment'] as int?, - share: json['share'] as int?, ); } diff --git a/lib/models_new/space/space_cheese/item.dart b/lib/models_new/space/space_cheese/item.dart index d5bc1a4813..e818209314 100644 --- a/lib/models_new/space/space_cheese/item.dart +++ b/lib/models_new/space/space_cheese/item.dart @@ -1,49 +1,28 @@ import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class SpaceCheeseItem { - bool? cooperated; - String? cooperationMark; String? cover; - int? epCount; - String? link; List? marks; - int? page; - int? play; int? seasonId; String? status; - String? subtitle; String? title; String? ctime; SpaceCheeseItem({ - this.cooperated, - this.cooperationMark, this.cover, - this.epCount, - this.link, this.marks, - this.page, - this.play, this.seasonId, this.status, - this.subtitle, this.title, this.ctime, }); factory SpaceCheeseItem.fromJson(Map json) => SpaceCheeseItem( - cooperated: json['cooperated'] as bool?, - cooperationMark: json['cooperation_mark'] as String?, cover: json['cover'] as String?, - epCount: json['ep_count'] as int?, - link: json['link'] as String?, marks: (json['marks'] as List?)?.fromCast(), - page: json['page'] as int?, - play: json['play'] as int?, seasonId: json['season_id'] as int?, status: json['status'] as String?, - subtitle: json['subtitle'] as String?, title: json['title'] as String?, ctime: json['ctime'] as String?, ); diff --git a/lib/models_new/space/space_cheese/page.dart b/lib/models_new/space/space_cheese/page.dart index a2dd06ed7d..3d5bb34c7f 100644 --- a/lib/models_new/space/space_cheese/page.dart +++ b/lib/models_new/space/space_cheese/page.dart @@ -1,16 +1,10 @@ class SpaceCheesePage { bool? next; - int? num; - int? size; - int? total; - SpaceCheesePage({this.next, this.num, this.size, this.total}); + SpaceCheesePage({this.next}); factory SpaceCheesePage.fromJson(Map json) => SpaceCheesePage( next: json['next'] as bool?, - num: json['num'] as int?, - size: json['size'] as int?, - total: json['total'] as int?, ); } diff --git a/lib/models_new/space/space_fav/data.dart b/lib/models_new/space/space_fav/data.dart index ab13fcd10e..744963b7a4 100644 --- a/lib/models_new/space/space_fav/data.dart +++ b/lib/models_new/space/space_fav/data.dart @@ -4,9 +4,8 @@ class SpaceFavData { int? id; String? name; MediaListResponse? mediaListResponse; - String? uri; - SpaceFavData({this.id, this.name, this.mediaListResponse, this.uri}); + SpaceFavData({this.id, this.name, this.mediaListResponse}); factory SpaceFavData.fromJson(Map json) => SpaceFavData( id: json['id'] as int?, @@ -16,6 +15,5 @@ class SpaceFavData { : MediaListResponse.fromJson( json['mediaListResponse'] as Map, ), - uri: json['uri'] as String?, ); } diff --git a/lib/models_new/space/space_fav/list.dart b/lib/models_new/space/space_fav/list.dart index df53dfba68..474db45b3c 100644 --- a/lib/models_new/space/space_fav/list.dart +++ b/lib/models_new/space/space_fav/list.dart @@ -14,7 +14,6 @@ class SpaceFavItemModel extends SubItemModel { super.fid, super.mid, super.attr, - super.attrDesc, super.title, super.cover, super.upper, @@ -26,13 +25,7 @@ class SpaceFavItemModel extends SubItemModel { super.favState, super.mediaCount, super.viewCount, - super.vt, - super.isTop, - super.recentFav, - super.playSwitch, super.type, - super.link, - super.bvid, }); factory SpaceFavItemModel.fromJson(Map json) => @@ -44,7 +37,6 @@ class SpaceFavItemModel extends SubItemModel { fid: json['fid'] as int?, mid: json['mid'] as int?, attr: json['attr'] as int?, - attrDesc: json['attr_desc'] as String?, title: json['title'] as String?, cover: json['cover'] as String?, upper: json['upper'] == null @@ -58,12 +50,6 @@ class SpaceFavItemModel extends SubItemModel { favState: json['fav_state'] as int?, mediaCount: json['media_count'] as int?, viewCount: json['view_count'] as int?, - vt: json['vt'] as int?, - isTop: json['is_top'] as bool?, - recentFav: json['recent_fav'] as dynamic, - playSwitch: json['play_switch'] as int?, type: json['type'] as int?, - link: json['link'] as String?, - bvid: json['bvid'] as String?, ); } diff --git a/lib/models_new/space/space_fav/media_list_response.dart b/lib/models_new/space/space_fav/media_list_response.dart index 09e2bc9c76..8d5abb0922 100644 --- a/lib/models_new/space/space_fav/media_list_response.dart +++ b/lib/models_new/space/space_fav/media_list_response.dart @@ -3,9 +3,8 @@ import 'package:PiliPlus/models_new/space/space_fav/list.dart'; class MediaListResponse { int? count; List? list; - bool? hasMore; - MediaListResponse({this.count, this.list, this.hasMore}); + MediaListResponse({this.count, this.list}); factory MediaListResponse.fromJson(Map json) { return MediaListResponse( @@ -13,7 +12,6 @@ class MediaListResponse { list: (json['list'] as List?) ?.map((e) => SpaceFavItemModel.fromJson(e as Map)) .toList(), - hasMore: json['has_more'] as bool?, ); } } diff --git a/lib/models_new/space/space_opus/cover.dart b/lib/models_new/space/space_opus/cover.dart index f2692b7fd0..e0045c84a3 100644 --- a/lib/models_new/space/space_opus/cover.dart +++ b/lib/models_new/space/space_opus/cover.dart @@ -1,19 +1,17 @@ import 'package:flutter/foundation.dart'; class Cover { - int? height; String? url; - int? width; late double ratio; - Cover({this.height, this.url, this.width, required this.ratio}); + Cover({required this.ratio}); Cover.fromJson(Map json) { - height = json['height'] as int?; url = json['url'] as String?; - width = json['width'] as int?; + final height = json['height'] as int?; + final width = json['width'] as int?; if (height != null && width != null) { - ratio = clampDouble(height! / width!, 0.68, 2.7); + ratio = clampDouble(height / width, 0.68, 2.7); } else { ratio = 1; } diff --git a/lib/models_new/space/space_opus/data.dart b/lib/models_new/space/space_opus/data.dart index 75a6d34565..19faf92a71 100644 --- a/lib/models_new/space/space_opus/data.dart +++ b/lib/models_new/space/space_opus/data.dart @@ -4,9 +4,8 @@ class SpaceOpusData { bool? hasMore; List? items; String? offset; - int? updateNum; - SpaceOpusData({this.hasMore, this.items, this.offset, this.updateNum}); + SpaceOpusData({this.hasMore, this.items, this.offset}); factory SpaceOpusData.fromJson(Map json) => SpaceOpusData( hasMore: json['has_more'] as bool?, @@ -14,6 +13,5 @@ class SpaceOpusData { ?.map((e) => SpaceOpusItemModel.fromJson(e as Map)) .toList(), offset: json['offset'] as String?, - updateNum: json['update_num'] as int?, ); } diff --git a/lib/models_new/space/space_opus/item.dart b/lib/models_new/space/space_opus/item.dart index 820792fded..6fe9cd7c3d 100644 --- a/lib/models_new/space/space_opus/item.dart +++ b/lib/models_new/space/space_opus/item.dart @@ -3,14 +3,12 @@ import 'package:PiliPlus/models_new/space/space_opus/stat.dart'; class SpaceOpusItemModel { String? content; - String? jumpUrl; String? opusId; Stat? stat; Cover? cover; SpaceOpusItemModel({ this.content, - this.jumpUrl, this.opusId, this.stat, this.cover, @@ -19,7 +17,6 @@ class SpaceOpusItemModel { factory SpaceOpusItemModel.fromJson(Map json) => SpaceOpusItemModel( content: json['content'] as String?, - jumpUrl: json['jump_url'] as String?, opusId: json['opus_id'] as String?, stat: json['stat'] == null ? null diff --git a/lib/models_new/space/space_season_series/archive.dart b/lib/models_new/space/space_season_series/archive.dart deleted file mode 100644 index 41a9f693dc..0000000000 --- a/lib/models_new/space/space_season_series/archive.dart +++ /dev/null @@ -1,55 +0,0 @@ -import 'package:PiliPlus/models_new/space/space_season_series/stat.dart'; - -class SpaceSsArchive { - int? aid; - String? bvid; - int? ctime; - int? duration; - bool? enableVt; - bool? interactiveVideo; - String? pic; - int? playbackPosition; - int? pubdate; - SpaceSsStat? stat; - int? state; - String? title; - int? ugcPay; - String? vtDisplay; - int? isLessonVideo; - - SpaceSsArchive({ - this.aid, - this.bvid, - this.ctime, - this.duration, - this.enableVt, - this.interactiveVideo, - this.pic, - this.playbackPosition, - this.pubdate, - this.stat, - this.state, - this.title, - this.ugcPay, - this.vtDisplay, - this.isLessonVideo, - }); - - factory SpaceSsArchive.fromJson(Map json) => SpaceSsArchive( - aid: json["aid"], - bvid: json["bvid"], - ctime: json["ctime"], - duration: json["duration"], - enableVt: json["enable_vt"], - interactiveVideo: json["interactive_video"], - pic: json["pic"], - playbackPosition: json["playback_position"], - pubdate: json["pubdate"], - stat: json["stat"] == null ? null : SpaceSsStat.fromJson(json["stat"]), - state: json["state"], - title: json["title"], - ugcPay: json["ugc_pay"], - vtDisplay: json["vt_display"], - isLessonVideo: json["is_lesson_video"], - ); -} diff --git a/lib/models_new/space/space_season_series/stat.dart b/lib/models_new/space/space_season_series/meta.dart similarity index 54% rename from lib/models_new/space/space_season_series/stat.dart rename to lib/models_new/space/space_season_series/meta.dart index a9dfe50732..69e2eab2fd 100644 --- a/lib/models_new/space/space_season_series/stat.dart +++ b/lib/models_new/space/space_season_series/meta.dart @@ -1,23 +1,5 @@ -class SpaceSsStat { - int? view; - int? vt; - - SpaceSsStat({ - this.view, - this.vt, - }); - - factory SpaceSsStat.fromJson(Map json) => SpaceSsStat( - view: json["view"], - vt: json["vt"], - ); -} - class SpaceSsMeta { - int? category; String? cover; - String? description; - int? mid; String? name; int? ptime; int? total; @@ -25,10 +7,7 @@ class SpaceSsMeta { dynamic seriesId; SpaceSsMeta({ - this.category, this.cover, - this.description, - this.mid, this.name, this.ptime, this.total, @@ -37,10 +16,7 @@ class SpaceSsMeta { }); factory SpaceSsMeta.fromJson(Map json) => SpaceSsMeta( - category: json["category"], cover: json["cover"], - description: json["description"], - mid: json["mid"], name: json["name"], ptime: json["ptime"], total: json["total"], diff --git a/lib/models_new/space/space_season_series/page.dart b/lib/models_new/space/space_season_series/page.dart index 8ad41acd37..794213d426 100644 --- a/lib/models_new/space/space_season_series/page.dart +++ b/lib/models_new/space/space_season_series/page.dart @@ -1,17 +1,11 @@ class SpaceSsPage { - int? pageNum; - int? pageSize; int? total; SpaceSsPage({ - this.pageNum, - this.pageSize, this.total, }); factory SpaceSsPage.fromJson(Map json) => SpaceSsPage( - pageNum: json["page_num"], - pageSize: json["page_size"], total: json["total"], ); } diff --git a/lib/models_new/space/space_season_series/season.dart b/lib/models_new/space/space_season_series/season.dart index 03ead644b5..1cb2635699 100644 --- a/lib/models_new/space/space_season_series/season.dart +++ b/lib/models_new/space/space_season_series/season.dart @@ -1,23 +1,11 @@ -import 'package:PiliPlus/models_new/space/space_season_series/archive.dart'; -import 'package:PiliPlus/models_new/space/space_season_series/stat.dart'; -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; +import 'package:PiliPlus/models_new/space/space_season_series/meta.dart'; class SpaceSsModel { - List? archives; SpaceSsMeta? meta; - List? recentAids; - SpaceSsModel({ - this.archives, - this.meta, - this.recentAids, - }); + SpaceSsModel({this.meta}); factory SpaceSsModel.fromJson(Map json) => SpaceSsModel( - archives: (json["archives"] as List?) - ?.map((e) => SpaceSsArchive.fromJson(e)) - .toList(), meta: json["meta"] == null ? null : SpaceSsMeta.fromJson(json["meta"]), - recentAids: (json["recent_aids"] as List?)?.fromCast(), ); } diff --git a/lib/models_new/space/space_shop/below_label.dart b/lib/models_new/space/space_shop/below_label.dart index a016458f50..c54b428a37 100644 --- a/lib/models_new/space/space_shop/below_label.dart +++ b/lib/models_new/space/space_shop/below_label.dart @@ -1,44 +1,11 @@ class BelowLabel { - int? tagType; String? title; - String? titleDayColor1; - String? titleDayColor2; - String? titleNightColor1; - String? titleNightColor2; - int? cornerRadius; - int? useBoard; - String? backDayColor1; - String? backDayColor2; - String? backNightColor1; - String? backNightColor2; BelowLabel({ - this.tagType, this.title, - this.titleDayColor1, - this.titleDayColor2, - this.titleNightColor1, - this.titleNightColor2, - this.cornerRadius, - this.useBoard, - this.backDayColor1, - this.backDayColor2, - this.backNightColor1, - this.backNightColor2, }); factory BelowLabel.fromJson(Map json) => BelowLabel( - tagType: json['tagType'] as int?, title: json['title'] as String?, - titleDayColor1: json['titleDayColor1'] as String?, - titleDayColor2: json['titleDayColor2'] as String?, - titleNightColor1: json['titleNightColor1'] as String?, - titleNightColor2: json['titleNightColor2'] as String?, - cornerRadius: json['cornerRadius'] as int?, - useBoard: json['useBoard'] as int?, - backDayColor1: json['backDayColor1'] as String?, - backDayColor2: json['backDayColor2'] as String?, - backNightColor1: json['backNightColor1'] as String?, - backNightColor2: json['backNightColor2'] as String?, ); } diff --git a/lib/models_new/space/space_shop/cover.dart b/lib/models_new/space/space_shop/cover.dart index be2fe00966..6463e37862 100644 --- a/lib/models_new/space/space_shop/cover.dart +++ b/lib/models_new/space/space_shop/cover.dart @@ -1,17 +1,9 @@ class Cover { String? url; - String? imgWh; - int? height; - int? width; - dynamic size; - Cover({this.url, this.imgWh, this.height, this.width, this.size}); + Cover({this.url}); factory Cover.fromJson(Map json) => Cover( url: json['url'] as String?, - imgWh: json['imgWH'] as String?, - height: json['height'] as int?, - width: json['width'] as int?, - size: json['size'] as dynamic, ); } diff --git a/lib/models_new/space/space_shop/data.dart b/lib/models_new/space/space_shop/data.dart index 16f4f4d56c..543050ed44 100644 --- a/lib/models_new/space/space_shop/data.dart +++ b/lib/models_new/space/space_shop/data.dart @@ -6,7 +6,6 @@ class SpaceShopData { String? clickUrl; String? showMoreDesc; bool? haveNextPage; - int? nextSearchAfter; SpaceShopData({ this.data, @@ -14,7 +13,6 @@ class SpaceShopData { this.clickUrl, this.showMoreDesc, this.haveNextPage, - this.nextSearchAfter, }); factory SpaceShopData.fromJson(Map json) => SpaceShopData( @@ -25,6 +23,5 @@ class SpaceShopData { clickUrl: json['clickUrl'] as String?, showMoreDesc: json['showMoreDesc'] as String?, haveNextPage: json['haveNextPage'] as bool?, - nextSearchAfter: json['nextSearchAfter'] as int?, ); } diff --git a/lib/models_new/space/space_shop/item.dart b/lib/models_new/space/space_shop/item.dart index c9b20c0eaa..c4115d6a1e 100644 --- a/lib/models_new/space/space_shop/item.dart +++ b/lib/models_new/space/space_shop/item.dart @@ -2,143 +2,41 @@ import 'package:PiliPlus/models_new/space/space_shop/below_label.dart'; import 'package:PiliPlus/models_new/space/space_shop/benefit_info.dart'; import 'package:PiliPlus/models_new/space/space_shop/cover.dart'; import 'package:PiliPlus/models_new/space/space_shop/net_price.dart'; -import 'package:PiliPlus/models_new/space/space_shop/report_params.dart'; -import 'package:PiliPlus/models_new/space/space_shop/source_desc.dart'; -import 'package:PiliPlus/models_new/space/space_shop/source_front_tag.dart'; -import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class SpaceShopItem { - String? contentId; - int? contentType; - dynamic contentSubType; - dynamic trackId; Cover? cover; String? title; - dynamic subTitle; String? cardUrl; List? belowLabels; - dynamic topRightLabels; - dynamic bottomRightLabels; - dynamic topLeftLabels; - dynamic bottomLeftLabels; - List? titleFrontLabels; - dynamic priceBehindLabels; NetPrice? netPrice; - dynamic userInteractInfos; List? benefitInfos; - ReportParams? reportParams; - dynamic ichibanItem; - bool? isMarketItem; - dynamic remainBoxStr; - dynamic surpriseTips; - String? outSchemaUrl; - int? itemCode; - dynamic merchantId; - int? itemSource; String? itemSourceName; - SourceDesc? sourceDesc; - SourceFrontTag? sourceFrontTag; - List? openWhiteList; - bool? sellOut; - int? status; - bool? preSaleEnd; - bool? preSaleNotStart; - int? jumpType; - dynamic lrpriceStr; SpaceShopItem({ - this.contentId, - this.contentType, - this.contentSubType, - this.trackId, this.cover, this.title, - this.subTitle, this.cardUrl, this.belowLabels, - this.topRightLabels, - this.bottomRightLabels, - this.topLeftLabels, - this.bottomLeftLabels, - this.titleFrontLabels, - this.priceBehindLabels, this.netPrice, - this.userInteractInfos, this.benefitInfos, - this.reportParams, - this.ichibanItem, - this.isMarketItem, - this.remainBoxStr, - this.surpriseTips, - this.outSchemaUrl, - this.itemCode, - this.merchantId, - this.itemSource, this.itemSourceName, - this.sourceDesc, - this.sourceFrontTag, - this.openWhiteList, - this.sellOut, - this.status, - this.preSaleEnd, - this.preSaleNotStart, - this.jumpType, - this.lrpriceStr, }); factory SpaceShopItem.fromJson(Map json) => SpaceShopItem( - contentId: json['contentId'] as String?, - contentType: json['contentType'] as int?, - contentSubType: json['contentSubType'] as dynamic, - trackId: json['trackId'] as dynamic, cover: json['cover'] == null ? null : Cover.fromJson(json['cover'] as Map), title: json['title'] as String?, - subTitle: json['subTitle'] as dynamic, cardUrl: json['cardUrl'] as String?, belowLabels: (json['belowLabels'] as List?) ?.map((e) => BelowLabel.fromJson(e as Map)) .toList(), - topRightLabels: json['topRightLabels'] as dynamic, - bottomRightLabels: json['bottomRightLabels'] as dynamic, - topLeftLabels: json['topLeftLabels'] as dynamic, - bottomLeftLabels: json['bottomLeftLabels'] as dynamic, - titleFrontLabels: json['titleFrontLabels'] as List?, - priceBehindLabels: json['priceBehindLabels'] as dynamic, netPrice: json['netPrice'] == null ? null : NetPrice.fromJson(json['netPrice'] as Map), - userInteractInfos: json['userInteractInfos'] as dynamic, benefitInfos: (json['benefitInfos'] as List?) ?.map((e) => BenefitInfo.fromJson(e as Map)) .toList(), - reportParams: json['reportParams'] == null - ? null - : ReportParams.fromJson(json['reportParams'] as Map), - ichibanItem: json['ichibanItem'] as dynamic, - isMarketItem: json['isMarketItem'] as bool?, - remainBoxStr: json['remainBoxStr'] as dynamic, - surpriseTips: json['surpriseTips'] as dynamic, - outSchemaUrl: json['outSchemaUrl'] as String?, - itemCode: json['itemCode'] as int?, - merchantId: json['merchantId'] as dynamic, - itemSource: json['itemSource'] as int?, itemSourceName: json['itemSourceName'] as String?, - sourceDesc: json['sourceDesc'] == null - ? null - : SourceDesc.fromJson(json['sourceDesc'] as Map), - sourceFrontTag: json['sourceFrontTag'] == null - ? null - : SourceFrontTag.fromJson( - json['sourceFrontTag'] as Map, - ), - openWhiteList: (json['openWhiteList'] as List?)?.fromCast(), - sellOut: json['sellOut'] as bool?, - status: json['status'] as int?, - preSaleEnd: json['preSaleEnd'] as bool?, - preSaleNotStart: json['preSaleNotStart'] as bool?, - jumpType: json['jumpType'] as int?, - lrpriceStr: json['lrpriceStr'] as dynamic, ); } diff --git a/lib/models_new/space/space_shop/report_params.dart b/lib/models_new/space/space_shop/report_params.dart deleted file mode 100644 index 41a20f346e..0000000000 --- a/lib/models_new/space/space_shop/report_params.dart +++ /dev/null @@ -1,11 +0,0 @@ -class ReportParams { - String? trail; - String? trackId; - - ReportParams({this.trail, this.trackId}); - - factory ReportParams.fromJson(Map json) => ReportParams( - trail: json['trail'] as String?, - trackId: json['track_id'] as String?, - ); -} diff --git a/lib/models_new/space/space_shop/source_desc.dart b/lib/models_new/space/space_shop/source_desc.dart deleted file mode 100644 index 659fff6c25..0000000000 --- a/lib/models_new/space/space_shop/source_desc.dart +++ /dev/null @@ -1,44 +0,0 @@ -class SourceDesc { - int? tagType; - String? title; - String? titleDayColor1; - String? titleDayColor2; - String? titleNightColor1; - String? titleNightColor2; - int? cornerRadius; - int? useBoard; - String? backDayColor1; - String? backDayColor2; - String? backNightColor1; - String? backNightColor2; - - SourceDesc({ - this.tagType, - this.title, - this.titleDayColor1, - this.titleDayColor2, - this.titleNightColor1, - this.titleNightColor2, - this.cornerRadius, - this.useBoard, - this.backDayColor1, - this.backDayColor2, - this.backNightColor1, - this.backNightColor2, - }); - - factory SourceDesc.fromJson(Map json) => SourceDesc( - tagType: json['tagType'] as int?, - title: json['title'] as String?, - titleDayColor1: json['titleDayColor1'] as String?, - titleDayColor2: json['titleDayColor2'] as String?, - titleNightColor1: json['titleNightColor1'] as String?, - titleNightColor2: json['titleNightColor2'] as String?, - cornerRadius: json['cornerRadius'] as int?, - useBoard: json['useBoard'] as int?, - backDayColor1: json['backDayColor1'] as String?, - backDayColor2: json['backDayColor2'] as String?, - backNightColor1: json['backNightColor1'] as String?, - backNightColor2: json['backNightColor2'] as String?, - ); -} diff --git a/lib/models_new/space/space_shop/source_front_tag.dart b/lib/models_new/space/space_shop/source_front_tag.dart deleted file mode 100644 index a79c6e2369..0000000000 --- a/lib/models_new/space/space_shop/source_front_tag.dart +++ /dev/null @@ -1,46 +0,0 @@ -class SourceFrontTag { - int? tagType; - String? title; - String? titleDayColor1; - String? titleDayColor2; - String? titleNightColor1; - String? titleNightColor2; - int? cornerRadius; - int? useBoard; - String? backDayColor1; - String? backDayColor2; - String? backNightColor1; - String? backNightColor2; - - SourceFrontTag({ - this.tagType, - this.title, - this.titleDayColor1, - this.titleDayColor2, - this.titleNightColor1, - this.titleNightColor2, - this.cornerRadius, - this.useBoard, - this.backDayColor1, - this.backDayColor2, - this.backNightColor1, - this.backNightColor2, - }); - - factory SourceFrontTag.fromJson(Map json) { - return SourceFrontTag( - tagType: json['tagType'] as int?, - title: json['title'] as String?, - titleDayColor1: json['titleDayColor1'] as String?, - titleDayColor2: json['titleDayColor2'] as String?, - titleNightColor1: json['titleNightColor1'] as String?, - titleNightColor2: json['titleNightColor2'] as String?, - cornerRadius: json['cornerRadius'] as int?, - useBoard: json['useBoard'] as int?, - backDayColor1: json['backDayColor1'] as String?, - backDayColor2: json['backDayColor2'] as String?, - backNightColor1: json['backNightColor1'] as String?, - backNightColor2: json['backNightColor2'] as String?, - ); - } -} diff --git a/lib/models_new/space_setting/data.dart b/lib/models_new/space_setting/data.dart index 7dd0a46127..75e94ff515 100644 --- a/lib/models_new/space_setting/data.dart +++ b/lib/models_new/space_setting/data.dart @@ -2,17 +2,13 @@ import 'package:PiliPlus/models_new/space_setting/privacy.dart'; class SpaceSettingData { Privacy? privacy; - bool? showNftSwitch; - String? exclusiveUrl; - SpaceSettingData({this.privacy, this.showNftSwitch, this.exclusiveUrl}); + SpaceSettingData({this.privacy}); factory SpaceSettingData.fromJson(Map json) => SpaceSettingData( privacy: json['privacy'] == null ? null : Privacy.fromJson(json['privacy'] as Map), - showNftSwitch: json['show_nft_switch'] as bool?, - exclusiveUrl: json['exclusive_url'] as String?, ); } diff --git a/lib/models_new/sub/sub/data.dart b/lib/models_new/sub/sub/data.dart index 67c8e77f5a..878e689b32 100644 --- a/lib/models_new/sub/sub/data.dart +++ b/lib/models_new/sub/sub/data.dart @@ -1,14 +1,12 @@ import 'package:PiliPlus/models_new/sub/sub/list.dart'; class SubData { - int? count; List? list; bool? hasMore; - SubData({this.count, this.list, this.hasMore}); + SubData({this.list, this.hasMore}); factory SubData.fromJson(Map json) => SubData( - count: json['count'] as int?, list: (json['list'] as List?) ?.map((e) => SubItemModel.fromJson(e as Map)) .toList(), diff --git a/lib/models_new/sub/sub/list.dart b/lib/models_new/sub/sub/list.dart index 4d3a586025..b5f8eaaf39 100644 --- a/lib/models_new/sub/sub/list.dart +++ b/lib/models_new/sub/sub/list.dart @@ -6,7 +6,6 @@ class SubItemModel { int? fid; int? mid; int? attr; - String? attrDesc; String? title; String? cover; Owner? upper; @@ -18,13 +17,7 @@ class SubItemModel { int? favState; int? mediaCount; int? viewCount; - int? vt; - bool? isTop; - dynamic recentFav; - int? playSwitch; int? type; - String? link; - String? bvid; CntInfo? cntInfo; SubItemModel({ @@ -32,7 +25,6 @@ class SubItemModel { this.fid, this.mid, this.attr, - this.attrDesc, this.title, this.cover, this.upper, @@ -44,13 +36,7 @@ class SubItemModel { this.favState, this.mediaCount, this.viewCount, - this.vt, - this.isTop, - this.recentFav, - this.playSwitch, this.type, - this.link, - this.bvid, this.cntInfo, }); @@ -59,7 +45,6 @@ class SubItemModel { fid: json['fid'] as int?, mid: json['mid'] as int?, attr: json['attr'] as int?, - attrDesc: json['attr_desc'] as String?, title: json['title'] as String?, cover: json['cover'] as String?, upper: json['upper'] == null @@ -73,13 +58,7 @@ class SubItemModel { favState: json['fav_state'] as int?, mediaCount: json['media_count'] as int?, viewCount: json['view_count'] as int?, - vt: json['vt'] as int?, - isTop: json['is_top'] as bool?, - recentFav: json['recent_fav'] as dynamic, - playSwitch: json['play_switch'] as int?, type: json['type'] as int?, - link: json['link'] as String?, - bvid: json['bvid'] as String?, cntInfo: json['cnt_info'] == null ? null : CntInfo.fromJson(json['cnt_info']), diff --git a/lib/models_new/sub/sub_detail/media.dart b/lib/models_new/sub/sub_detail/media.dart index 5e46ee428e..0567089d13 100644 --- a/lib/models_new/sub/sub_detail/media.dart +++ b/lib/models_new/sub/sub_detail/media.dart @@ -1,4 +1,3 @@ -import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/fav/fav_detail/cnt_info.dart'; class SubDetailItemModel { @@ -8,11 +7,7 @@ class SubDetailItemModel { int? duration; int? pubtime; String? bvid; - Owner? upper; CntInfo? cntInfo; - int? enableVt; - String? vtDisplay; - bool? isSelfView; SubDetailItemModel({ this.id, @@ -21,11 +16,7 @@ class SubDetailItemModel { this.duration, this.pubtime, this.bvid, - this.upper, this.cntInfo, - this.enableVt, - this.vtDisplay, - this.isSelfView, }); factory SubDetailItemModel.fromJson(Map json) => @@ -36,14 +27,8 @@ class SubDetailItemModel { duration: json['duration'] as int?, pubtime: json['pubtime'] as int?, bvid: json['bvid'] as String?, - upper: json['upper'] == null - ? null - : Owner.fromJson(json['upper'] as Map), cntInfo: json['cnt_info'] == null ? null : CntInfo.fromJson(json['cnt_info'] as Map), - enableVt: json['enable_vt'] as int?, - vtDisplay: json['vt_display'] as String?, - isSelfView: json['is_self_view'] as bool?, ); } diff --git a/lib/models_new/triple/ugc_triple.dart b/lib/models_new/triple/ugc_triple.dart index 80425d6789..f59e9f99c1 100644 --- a/lib/models_new/triple/ugc_triple.dart +++ b/lib/models_new/triple/ugc_triple.dart @@ -3,18 +3,12 @@ class UgcTriple { bool? coin; bool? fav; int? multiply; - bool? isRisk; - int? gaiaResType; - dynamic gaiaData; UgcTriple({ this.like, this.coin, this.fav, this.multiply, - this.isRisk, - this.gaiaResType, - this.gaiaData, }); factory UgcTriple.fromJson(Map json) => UgcTriple( @@ -22,8 +16,5 @@ class UgcTriple { coin: json["coin"], fav: json["fav"], multiply: json["multiply"], - isRisk: json["is_risk"], - gaiaResType: json["gaia_res_type"], - gaiaData: json["gaia_data"], ); } diff --git a/lib/models_new/upower_rank/data.dart b/lib/models_new/upower_rank/data.dart index 54d4e3bb77..c75ffb65d4 100644 --- a/lib/models_new/upower_rank/data.dart +++ b/lib/models_new/upower_rank/data.dart @@ -1,43 +1,25 @@ import 'package:PiliPlus/models_new/upower_rank/level_info.dart'; import 'package:PiliPlus/models_new/upower_rank/rank_info.dart'; -import 'package:PiliPlus/models_new/upower_rank/up_info.dart'; -import 'package:PiliPlus/models_new/upower_rank/user_info.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; class UpowerRankData { - UpInfo? upInfo; List? rankInfo; - UserInfo? userInfo; - int? memberTotal; int? privilegeType; - bool? isCharge; List? tabs; List? levelInfo; UpowerRankData({ - this.upInfo, this.rankInfo, - this.userInfo, - this.memberTotal, this.privilegeType, - this.isCharge, this.tabs, this.levelInfo, }); factory UpowerRankData.fromJson(Map json) => UpowerRankData( - upInfo: json['up_info'] == null - ? null - : UpInfo.fromJson(json['up_info'] as Map), rankInfo: (json['rank_info'] as List?) ?.map((e) => UpowerRankInfo.fromJson(e as Map)) .toList(), - userInfo: json['user_info'] == null - ? null - : UserInfo.fromJson(json['user_info'] as Map), - memberTotal: json['member_total'] as int?, privilegeType: json['privilege_type'] as int?, - isCharge: json['is_charge'] as bool?, tabs: (json['tabs'] as List?)?.fromCast(), levelInfo: (json['level_info'] as List?) ?.map((e) => LevelInfo.fromJson(e as Map)) diff --git a/lib/models_new/upower_rank/level_info.dart b/lib/models_new/upower_rank/level_info.dart index 4b0868c80b..ad1547e407 100644 --- a/lib/models_new/upower_rank/level_info.dart +++ b/lib/models_new/upower_rank/level_info.dart @@ -1,15 +1,13 @@ class LevelInfo { int? privilegeType; String? name; - int? price; int? memberTotal; - LevelInfo({this.privilegeType, this.name, this.price, this.memberTotal}); + LevelInfo({this.privilegeType, this.name, this.memberTotal}); factory LevelInfo.fromJson(Map json) => LevelInfo( privilegeType: json['privilege_type'] as int?, name: json['name'] as String?, - price: json['price'] as int?, memberTotal: json['member_total'] as int?, ); } diff --git a/lib/models_new/upower_rank/rank_info.dart b/lib/models_new/upower_rank/rank_info.dart index 96923927d1..ffe609838d 100644 --- a/lib/models_new/upower_rank/rank_info.dart +++ b/lib/models_new/upower_rank/rank_info.dart @@ -2,28 +2,19 @@ class UpowerRankInfo { int? mid; String? nickname; String? avatar; - int? rank; int? day; - int? expireAt; - int? remainDays; UpowerRankInfo({ this.mid, this.nickname, this.avatar, - this.rank, this.day, - this.expireAt, - this.remainDays, }); factory UpowerRankInfo.fromJson(Map json) => UpowerRankInfo( mid: json['mid'] as int?, nickname: json['nickname'] as String?, avatar: json['avatar'] as String?, - rank: json['rank'] as int?, day: json['day'] as int?, - expireAt: json['expire_at'] as int?, - remainDays: json['remain_days'] as int?, ); } diff --git a/lib/models_new/upower_rank/up_info.dart b/lib/models_new/upower_rank/up_info.dart deleted file mode 100644 index 6a6afe97bf..0000000000 --- a/lib/models_new/upower_rank/up_info.dart +++ /dev/null @@ -1,26 +0,0 @@ -class UpInfo { - int? mid; - String? nickname; - String? avatar; - int? type; - String? title; - int? upowerState; - - UpInfo({ - this.mid, - this.nickname, - this.avatar, - this.type, - this.title, - this.upowerState, - }); - - factory UpInfo.fromJson(Map json) => UpInfo( - mid: json['mid'] as int?, - nickname: json['nickname'] as String?, - avatar: json['avatar'] as String?, - type: json['type'] as int?, - title: json['title'] as String?, - upowerState: json['upower_state'] as int?, - ); -} diff --git a/lib/models_new/upower_rank/user_info.dart b/lib/models_new/upower_rank/user_info.dart deleted file mode 100644 index 3d3f67924f..0000000000 --- a/lib/models_new/upower_rank/user_info.dart +++ /dev/null @@ -1,29 +0,0 @@ -class UserInfo { - int? mid; - String? nickname; - String? avatar; - int? rank; - int? day; - int? expireAt; - int? remainDays; - - UserInfo({ - this.mid, - this.nickname, - this.avatar, - this.rank, - this.day, - this.expireAt, - this.remainDays, - }); - - factory UserInfo.fromJson(Map json) => UserInfo( - mid: json['mid'] as int?, - nickname: json['nickname'] as String?, - avatar: json['avatar'] as String?, - rank: json['rank'] as int?, - day: json['day'] as int?, - expireAt: json['expire_at'] as int?, - remainDays: json['remain_days'] as int?, - ); -} diff --git a/lib/models_new/user_real_name/data.dart b/lib/models_new/user_real_name/data.dart index 250020405f..7ea64237ff 100644 --- a/lib/models_new/user_real_name/data.dart +++ b/lib/models_new/user_real_name/data.dart @@ -2,17 +2,13 @@ import 'package:PiliPlus/models_new/user_real_name/reject_page.dart'; class UserRealNameData { String? name; - String? namePrefix; - bool? show; RejectPage? rejectPage; - UserRealNameData({this.name, this.namePrefix, this.show, this.rejectPage}); + UserRealNameData({this.name, this.rejectPage}); factory UserRealNameData.fromJson(Map json) => UserRealNameData( name: json['name'] as String?, - namePrefix: json['name_prefix'] as String?, - show: json['show'] as bool?, rejectPage: json['reject_page'] == null ? null : RejectPage.fromJson(json['reject_page'] as Map), diff --git a/lib/models_new/user_real_name/reject_page.dart b/lib/models_new/user_real_name/reject_page.dart index 574171cf26..6696b399d5 100644 --- a/lib/models_new/user_real_name/reject_page.dart +++ b/lib/models_new/user_real_name/reject_page.dart @@ -1,13 +1,11 @@ class RejectPage { String? title; String? text; - String? img; - RejectPage({this.title, this.text, this.img}); + RejectPage({this.title, this.text}); factory RejectPage.fromJson(Map json) => RejectPage( title: json['title'] as String?, text: json['text'] as String?, - img: json['img'] as String?, ); } diff --git a/lib/models_new/video/video_ai_conclusion/data.dart b/lib/models_new/video/video_ai_conclusion/data.dart index 9c38494b3e..e289f7f587 100644 --- a/lib/models_new/video/video_ai_conclusion/data.dart +++ b/lib/models_new/video/video_ai_conclusion/data.dart @@ -1,33 +1,18 @@ import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart'; class AiConclusionData { - int? code; AiConclusionResult? modelResult; - String? stid; - int? status; - int? likeNum; - int? dislikeNum; AiConclusionData({ - this.code, this.modelResult, - this.stid, - this.status, - this.likeNum, - this.dislikeNum, }); factory AiConclusionData.fromJson(Map json) => AiConclusionData( - code: json['code'] as int?, modelResult: json['model_result'] == null ? null : AiConclusionResult.fromJson( json['model_result'] as Map, ), - stid: json['stid'] as String?, - status: json['status'] as int?, - likeNum: json['like_num'] as int?, - dislikeNum: json['dislike_num'] as int?, ); } diff --git a/lib/models_new/video/video_ai_conclusion/model_result.dart b/lib/models_new/video/video_ai_conclusion/model_result.dart index d6caee5633..646557bb1a 100644 --- a/lib/models_new/video/video_ai_conclusion/model_result.dart +++ b/lib/models_new/video/video_ai_conclusion/model_result.dart @@ -1,28 +1,19 @@ import 'package:PiliPlus/models_new/video/video_ai_conclusion/outline.dart'; -import 'package:PiliPlus/models_new/video/video_ai_conclusion/subtitle.dart'; class AiConclusionResult { - int? resultType; String? summary; List? outline; - List? subtitle; AiConclusionResult({ - this.resultType, this.summary, this.outline, - this.subtitle, }); factory AiConclusionResult.fromJson(Map json) => AiConclusionResult( - resultType: json['result_type'] as int?, summary: json['summary'] as String?, outline: (json['outline'] as List?) ?.map((e) => Outline.fromJson(e as Map)) .toList(), - subtitle: (json['subtitle'] as List?) - ?.map((e) => Subtitle.fromJson(e as Map)) - .toList(), ); } diff --git a/lib/models_new/video/video_ai_conclusion/outline.dart b/lib/models_new/video/video_ai_conclusion/outline.dart index 724656b8c0..893e0334fd 100644 --- a/lib/models_new/video/video_ai_conclusion/outline.dart +++ b/lib/models_new/video/video_ai_conclusion/outline.dart @@ -3,15 +3,13 @@ import 'package:PiliPlus/models_new/video/video_ai_conclusion/part_outline.dart' class Outline { String? title; List? partOutline; - int? timestamp; - Outline({this.title, this.partOutline, this.timestamp}); + Outline({this.title, this.partOutline}); factory Outline.fromJson(Map json) => Outline( title: json['title'] as String?, partOutline: (json['part_outline'] as List?) ?.map((e) => PartOutline.fromJson(e as Map)) .toList(), - timestamp: json['timestamp'] as int?, ); } diff --git a/lib/models_new/video/video_ai_conclusion/part_subtitle.dart b/lib/models_new/video/video_ai_conclusion/part_subtitle.dart deleted file mode 100644 index 18e4f7f519..0000000000 --- a/lib/models_new/video/video_ai_conclusion/part_subtitle.dart +++ /dev/null @@ -1,13 +0,0 @@ -class PartSubtitle { - int? startTimestamp; - int? endTimestamp; - String? content; - - PartSubtitle({this.startTimestamp, this.endTimestamp, this.content}); - - factory PartSubtitle.fromJson(Map json) => PartSubtitle( - startTimestamp: json['start_timestamp'] as int?, - endTimestamp: json['end_timestamp'] as int?, - content: json['content'] as String?, - ); -} diff --git a/lib/models_new/video/video_ai_conclusion/subtitle.dart b/lib/models_new/video/video_ai_conclusion/subtitle.dart deleted file mode 100644 index 24e5d37b87..0000000000 --- a/lib/models_new/video/video_ai_conclusion/subtitle.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:PiliPlus/models_new/video/video_ai_conclusion/part_subtitle.dart'; - -class Subtitle { - String? title; - List? partSubtitle; - int? timestamp; - - Subtitle({this.title, this.partSubtitle, this.timestamp}); - - factory Subtitle.fromJson(Map json) => Subtitle( - title: json['title'] as String?, - partSubtitle: (json['part_subtitle'] as List?) - ?.map((e) => PartSubtitle.fromJson(e as Map)) - .toList(), - timestamp: json['timestamp'] as int?, - ); -} diff --git a/lib/models_new/video/video_detail/arc.dart b/lib/models_new/video/video_detail/arc.dart index f199319385..f945e6d4e4 100644 --- a/lib/models_new/video/video_detail/arc.dart +++ b/lib/models_new/video/video_detail/arc.dart @@ -1,93 +1,42 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; -import 'package:PiliPlus/models_new/video/video_detail/rights.dart'; import 'package:PiliPlus/models_new/video/video_detail/stat.dart'; class Arc { int? aid; - int? videos; - int? typeId; - String? typeName; - int? copyright; String? pic; String? title; int? pubdate; - int? ctime; - String? desc; - int? state; int? duration; - Rights? rights; Owner? author; VideoStat? stat; - String? dynam1c; Dimension? dimension; - bool? isChargeableSeason; - bool? isBlooper; - int? enableVt; - String? vtDisplay; - int? typeIdV2; - String? typeNameV2; - int? isLessonVideo; Arc({ this.aid, - this.videos, - this.typeId, - this.typeName, - this.copyright, this.pic, this.title, this.pubdate, - this.ctime, - this.desc, - this.state, this.duration, - this.rights, this.author, this.stat, - this.dynam1c, this.dimension, - this.isChargeableSeason, - this.isBlooper, - this.enableVt, - this.vtDisplay, - this.typeIdV2, - this.typeNameV2, - this.isLessonVideo, }); factory Arc.fromJson(Map json) => Arc( aid: json['aid'] as int?, - videos: json['videos'] as int?, - typeId: json['type_id'] as int?, - typeName: json['type_name'] as String?, - copyright: json['copyright'] as int?, pic: json['pic'] as String?, title: json['title'] as String?, pubdate: json['pubdate'] as int?, - ctime: json['ctime'] as int?, - desc: json['desc'] as String?, - state: json['state'] as int?, duration: json['duration'] as int?, - rights: json['rights'] == null - ? null - : Rights.fromJson(json['rights'] as Map), author: json['author'] == null ? null : Owner.fromJson(json['author'] as Map), stat: json['stat'] == null ? null : VideoStat.fromJson(json['stat'] as Map), - dynam1c: json['dynamic'] as String?, dimension: json['dimension'] == null ? null : Dimension.fromJson(json['dimension'] as Map), - isChargeableSeason: json['is_chargeable_season'] as bool?, - isBlooper: json['is_blooper'] as bool?, - enableVt: json['enable_vt'] as int?, - vtDisplay: json['vt_display'] as String?, - typeIdV2: json['type_id_v2'] as int?, - typeNameV2: json['type_name_v2'] as String?, - isLessonVideo: json['is_lesson_video'] as int?, ); } diff --git a/lib/models_new/video/video_detail/argue_info.dart b/lib/models_new/video/video_detail/argue_info.dart index 58faca8601..ddbc85ef98 100644 --- a/lib/models_new/video/video_detail/argue_info.dart +++ b/lib/models_new/video/video_detail/argue_info.dart @@ -1,13 +1,9 @@ class ArgueInfo { String? argueMsg; - int? argueType; - String? argueLink; - ArgueInfo({this.argueMsg, this.argueType, this.argueLink}); + ArgueInfo({this.argueMsg}); factory ArgueInfo.fromJson(Map json) => ArgueInfo( argueMsg: json['argue_msg'] as String?, - argueType: json['argue_type'] as int?, - argueLink: json['argue_link'] as String?, ); } diff --git a/lib/models_new/video/video_detail/data.dart b/lib/models_new/video/video_detail/data.dart index 9f49712c23..aac04c2bde 100644 --- a/lib/models_new/video/video_detail/data.dart +++ b/lib/models_new/video/video_detail/data.dart @@ -6,19 +6,13 @@ import 'package:PiliPlus/models_new/video/video_detail/page.dart'; import 'package:PiliPlus/models_new/video/video_detail/rights.dart'; import 'package:PiliPlus/models_new/video/video_detail/staff.dart'; import 'package:PiliPlus/models_new/video/video_detail/stat.dart'; -import 'package:PiliPlus/models_new/video/video_detail/subtitle.dart'; import 'package:PiliPlus/models_new/video/video_detail/ugc_season.dart'; -import 'package:PiliPlus/models_new/video/video_detail/user_garb.dart'; import 'package:PiliPlus/utils/parse_string.dart'; class VideoDetailData { String? bvid; int? aid; int? videos; - int? tid; - int? tidV2; - String? tname; - String? tnameV2; int? copyright; String? pic; String? title; @@ -26,36 +20,17 @@ class VideoDetailData { int? ctime; String? desc; List? descV2; - int? state; int? duration; Rights? rights; Owner? owner; VideoStat? stat; ArgueInfo? argueInfo; - String? dynam1c; int? cid; Dimension? dimension; int? seasonId; - int? teenageMode; - bool? isChargeableSeason; - bool? isStory; bool? isUpowerExclusive; - bool? isUpowerPlay; - bool? isUpowerPreview; - int? enableVt; - String? vtDisplay; - bool? isUpowerExclusiveWithQa; - bool? noCache; List? pages; - Subtitle? subtitle; UgcSeason? ugcSeason; - bool? isSeasonDisplay; - UserGarb? userGarb; - String? likeIcon; - bool? needJumpBv; - bool? disableShowUpInfo; - int? isStoryPlay; - bool? isViewSelf; List? staff; String? redirectUrl; bool isPageReversed = false; @@ -64,10 +39,6 @@ class VideoDetailData { this.bvid, this.aid, this.videos, - this.tid, - this.tidV2, - this.tname, - this.tnameV2, this.copyright, this.pic, this.title, @@ -75,36 +46,17 @@ class VideoDetailData { this.ctime, this.desc, this.descV2, - this.state, this.duration, this.rights, this.owner, this.stat, this.argueInfo, - this.dynam1c, this.cid, this.dimension, this.seasonId, - this.teenageMode, - this.isChargeableSeason, - this.isStory, this.isUpowerExclusive, - this.isUpowerPlay, - this.isUpowerPreview, - this.enableVt, - this.vtDisplay, - this.isUpowerExclusiveWithQa, - this.noCache, this.pages, - this.subtitle, this.ugcSeason, - this.isSeasonDisplay, - this.userGarb, - this.likeIcon, - this.needJumpBv, - this.disableShowUpInfo, - this.isStoryPlay, - this.isViewSelf, this.staff, this.redirectUrl, }); @@ -114,10 +66,6 @@ class VideoDetailData { bvid: json['bvid'] as String?, aid: json['aid'] as int?, videos: json['videos'] as int?, - tid: json['tid'] as int?, - tidV2: json['tid_v2'] as int?, - tname: json['tname'] as String?, - tnameV2: json['tname_v2'] as String?, copyright: json['copyright'] as int?, pic: json['pic'] as String?, title: json['title'] as String?, @@ -127,7 +75,6 @@ class VideoDetailData { descV2: (json['desc_v2'] as List?) ?.map((e) => DescV2.fromJson(e as Map)) .toList(), - state: json['state'] as int?, duration: json['duration'] as int?, rights: json['rights'] == null ? null @@ -141,43 +88,21 @@ class VideoDetailData { argueInfo: json['argue_info'] == null ? null : ArgueInfo.fromJson(json['argue_info'] as Map), - dynam1c: json['dynamic'] as String?, cid: json['cid'] as int?, dimension: json['dimension'] == null ? null : Dimension.fromJson(json['dimension'] as Map), seasonId: json['season_id'] as int?, - teenageMode: json['teenage_mode'] as int?, - isChargeableSeason: json['is_chargeable_season'] as bool?, - isStory: json['is_story'] as bool?, isUpowerExclusive: json['is_upower_exclusive'] as bool?, - isUpowerPlay: json['is_upower_play'] as bool?, - isUpowerPreview: json['is_upower_preview'] as bool?, - enableVt: json['enable_vt'] as int?, - vtDisplay: json['vt_display'] as String?, - isUpowerExclusiveWithQa: json['is_upower_exclusive_with_qa'] as bool?, - noCache: json['no_cache'] as bool?, pages: (json['pages'] as List?) ?.map((e) => Part.fromJson(e as Map)) .toList(), - subtitle: json['subtitle'] == null - ? null - : Subtitle.fromJson(json['subtitle'] as Map), ugcSeason: json['ugc_season'] == null ? null : UgcSeason.fromJson(json['ugc_season'] as Map), - isSeasonDisplay: json['is_season_display'] as bool?, - userGarb: json['user_garb'] == null - ? null - : UserGarb.fromJson(json['user_garb'] as Map), - likeIcon: json['like_icon'] as String?, - needJumpBv: json['need_jump_bv'] as bool?, - disableShowUpInfo: json['disable_show_up_info'] as bool?, - isStoryPlay: json['is_story_play'] as int?, - isViewSelf: json['is_view_self'] as bool?, staff: (json["staff"] as List?) ?.map((item) => Staff.fromJson(item)) .toList(), - redirectUrl: noneNullOrEmptyString(json['redirect_url']), + redirectUrl: nonNullOrEmptyString(json['redirect_url']), ); } diff --git a/lib/models_new/video/video_detail/dimension.dart b/lib/models_new/video/video_detail/dimension.dart index 5f83c037e3..989a0e9969 100644 --- a/lib/models_new/video/video_detail/dimension.dart +++ b/lib/models_new/video/video_detail/dimension.dart @@ -9,10 +9,21 @@ class Dimension { return null; } + bool get isVertical => + width != null && height != null ? height! > width! : false; + Dimension({this.width, this.height}); - factory Dimension.fromJson(Map json) => Dimension( - width: json['width'] as int?, - height: json['height'] as int?, - ); + Dimension.fromJson(Map json) { + if (json['rotate'] == 1) { + width = json['height'] as int?; + height = json['width'] as int?; + } else { + width = json['width'] as int?; + height = json['height'] as int?; + } + } + + @override + String toString() => 'width: $width, height: $height'; } diff --git a/lib/models_new/video/video_detail/rights.dart b/lib/models_new/video/video_detail/rights.dart index a35c3695a6..8a9b84f059 100644 --- a/lib/models_new/video/video_detail/rights.dart +++ b/lib/models_new/video/video_detail/rights.dart @@ -1,62 +1,11 @@ class Rights { - int? bp; - int? elec; - int? download; - int? movie; - int? pay; - int? hd5; - int? noReprint; - int? autoplay; - int? ugcPay; - int? isCooperation; - int? ugcPayPreview; - int? noBackground; - int? cleanMode; int? isSteinGate; - int? is360; - int? noShare; - int? arcPay; - int? freeWatch; Rights({ - this.bp, - this.elec, - this.download, - this.movie, - this.pay, - this.hd5, - this.noReprint, - this.autoplay, - this.ugcPay, - this.isCooperation, - this.ugcPayPreview, - this.noBackground, - this.cleanMode, this.isSteinGate, - this.is360, - this.noShare, - this.arcPay, - this.freeWatch, }); factory Rights.fromJson(Map json) => Rights( - bp: json['bp'] as int?, - elec: json['elec'] as int?, - download: json['download'] as int?, - movie: json['movie'] as int?, - pay: json['pay'] as int?, - hd5: json['hd5'] as int?, - noReprint: json['no_reprint'] as int?, - autoplay: json['autoplay'] as int?, - ugcPay: json['ugc_pay'] as int?, - isCooperation: json['is_cooperation'] as int?, - ugcPayPreview: json['ugc_pay_preview'] as int?, - noBackground: json['no_background'] as int?, - cleanMode: json['clean_mode'] as int?, isSteinGate: json['is_stein_gate'] as int?, - is360: json['is_360'] as int?, - noShare: json['no_share'] as int?, - arcPay: json['arc_pay'] as int?, - freeWatch: json['free_watch'] as int?, ); } diff --git a/lib/models_new/video/video_detail/stat.dart b/lib/models_new/video/video_detail/stat.dart index 427f022b77..7b711a3d0c 100644 --- a/lib/models_new/video/video_detail/stat.dart +++ b/lib/models_new/video/video_detail/stat.dart @@ -1,25 +1,13 @@ import 'package:PiliPlus/models_new/video/video_detail/stat_detail.dart'; class VideoStat extends StatDetail { - int? aid; - int? nowRank; - int? hisRank; - int? dislike; - String? evaluation; - VideoStat.fromJson(Map json) { - aid = json['aid'] as int?; view = json['view'] as int?; danmaku = json['danmaku'] as int?; reply = json['reply'] as int?; favorite = json['favorite'] as int? ?? 0; coin = json['coin'] as num? ?? 0; share = json['share'] as int?; - nowRank = json['now_rank'] as int?; - hisRank = json['his_rank'] as int?; like = json['like'] as int? ?? 0; - dislike = json['dislike'] as int?; - evaluation = json['evaluation'] as String?; - vt = json['vt'] as int?; } } diff --git a/lib/models_new/video/video_detail/stat_detail.dart b/lib/models_new/video/video_detail/stat_detail.dart index 5a82cc483b..1e8000f393 100644 --- a/lib/models_new/video/video_detail/stat_detail.dart +++ b/lib/models_new/video/video_detail/stat_detail.dart @@ -6,5 +6,4 @@ abstract class StatDetail { int? reply; int? share; int? view; - int? vt; } diff --git a/lib/models_new/video/video_detail/subtitle.dart b/lib/models_new/video/video_detail/subtitle.dart deleted file mode 100644 index c50efaf4fa..0000000000 --- a/lib/models_new/video/video_detail/subtitle.dart +++ /dev/null @@ -1,11 +0,0 @@ -class Subtitle { - bool? allowSubmit; - List? list; - - Subtitle({this.allowSubmit, this.list}); - - factory Subtitle.fromJson(Map json) => Subtitle( - allowSubmit: json['allow_submit'] as bool?, - list: json['list'] as List?, - ); -} diff --git a/lib/models_new/video/video_detail/ugc_season.dart b/lib/models_new/video/video_detail/ugc_season.dart index 8b6ca27d0b..084915432b 100644 --- a/lib/models_new/video/video_detail/ugc_season.dart +++ b/lib/models_new/video/video_detail/ugc_season.dart @@ -1,35 +1,18 @@ import 'package:PiliPlus/models_new/video/video_detail/section.dart'; -import 'package:PiliPlus/models_new/video/video_detail/stat.dart'; class UgcSeason { int? id; String? title; String? cover; int? mid; - String? intro; - int? signState; - int? attribute; List? sections; - VideoStat? stat; - int? epCount; - int? seasonType; - bool? isPaySeason; - int? enableVt; UgcSeason({ this.id, this.title, this.cover, this.mid, - this.intro, - this.signState, - this.attribute, this.sections, - this.stat, - this.epCount, - this.seasonType, - this.isPaySeason, - this.enableVt, }); factory UgcSeason.fromJson(Map json) => UgcSeason( @@ -37,18 +20,8 @@ class UgcSeason { title: json['title'] as String?, cover: json['cover'] as String?, mid: json['mid'] as int?, - intro: json['intro'] as String?, - signState: json['sign_state'] as int?, - attribute: json['attribute'] as int?, sections: (json['sections'] as List?) ?.map((e) => SectionItem.fromJson(e as Map)) .toList(), - stat: json['stat'] == null - ? null - : VideoStat.fromJson(json['stat'] as Map), - epCount: json['ep_count'] as int?, - seasonType: json['season_type'] as int?, - isPaySeason: json['is_pay_season'] as bool?, - enableVt: json['enable_vt'] as int?, ); } diff --git a/lib/models_new/video/video_detail/user_garb.dart b/lib/models_new/video/video_detail/user_garb.dart deleted file mode 100644 index 0c86befb63..0000000000 --- a/lib/models_new/video/video_detail/user_garb.dart +++ /dev/null @@ -1,9 +0,0 @@ -class UserGarb { - String? urlImageAniCut; - - UserGarb({this.urlImageAniCut}); - - factory UserGarb.fromJson(Map json) => UserGarb( - urlImageAniCut: json['url_image_ani_cut'] as String?, - ); -} diff --git a/lib/models_new/video/video_note_list/author.dart b/lib/models_new/video/video_note_list/author.dart index 22ce71edbc..808b6f8288 100644 --- a/lib/models_new/video/video_note_list/author.dart +++ b/lib/models_new/video/video_note_list/author.dart @@ -9,7 +9,6 @@ class Author { Vip? vipInfo; Pendant? pendant; BaseOfficialVerify? official; - int? follower; Author({ this.mid, @@ -20,7 +19,6 @@ class Author { this.vipInfo, this.pendant, this.official, - this.follower, }); factory Author.fromJson(Map json) => Author( @@ -38,6 +36,5 @@ class Author { official: json['official'] == null ? null : BaseOfficialVerify.fromJson(json['official'] as Map), - follower: json['follower'] as int?, ); } diff --git a/lib/models_new/video/video_note_list/data.dart b/lib/models_new/video/video_note_list/data.dart index 6c55271444..1b5af63a20 100644 --- a/lib/models_new/video/video_note_list/data.dart +++ b/lib/models_new/video/video_note_list/data.dart @@ -4,10 +4,8 @@ import 'package:PiliPlus/models_new/video/video_note_list/page.dart'; class VideoNoteData { List? list; Page? page; - bool? showPublicNote; - String? message; - VideoNoteData({this.list, this.page, this.showPublicNote, this.message}); + VideoNoteData({this.list, this.page}); factory VideoNoteData.fromJson(Map json) => VideoNoteData( list: (json['list'] as List?) @@ -16,7 +14,5 @@ class VideoNoteData { page: json['page'] == null ? null : Page.fromJson(json['page'] as Map), - showPublicNote: json['show_public_note'] as bool?, - message: json['message'] as String?, ); } diff --git a/lib/models_new/video/video_note_list/list.dart b/lib/models_new/video/video_note_list/list.dart index 8661c3607e..1f54c34a2e 100644 --- a/lib/models_new/video/video_note_list/list.dart +++ b/lib/models_new/video/video_note_list/list.dart @@ -2,39 +2,24 @@ import 'package:PiliPlus/models_new/video/video_note_list/author.dart'; class VideoNoteItemModel { int? cvid; - String? title; String? summary; String? pubtime; - String? webUrl; - String? message; Author? author; - int? likes; - bool? hasLike; VideoNoteItemModel({ this.cvid, - this.title, this.summary, this.pubtime, - this.webUrl, - this.message, this.author, - this.likes, - this.hasLike, }); factory VideoNoteItemModel.fromJson(Map json) => VideoNoteItemModel( cvid: json['cvid'] as int?, - title: json['title'] as String?, summary: json['summary'] as String?, pubtime: json['pubtime'] as String?, - webUrl: json['web_url'] as String?, - message: json['message'] as String?, author: json['author'] == null ? null : Author.fromJson(json['author'] as Map), - likes: json['likes'] as int?, - hasLike: json['has_like'] as bool?, ); } diff --git a/lib/models_new/video/video_note_list/page.dart b/lib/models_new/video/video_note_list/page.dart index 22984a0714..748324093b 100644 --- a/lib/models_new/video/video_note_list/page.dart +++ b/lib/models_new/video/video_note_list/page.dart @@ -1,13 +1,9 @@ class Page { int? total; - int? size; - int? num; - Page({this.total, this.size, this.num}); + Page({this.total}); factory Page.fromJson(Map json) => Page( total: json['total'] as int?, - size: json['size'] as int?, - num: json['num'] as int?, ); } diff --git a/lib/models_new/video/video_play_info/subtitle.dart b/lib/models_new/video/video_play_info/subtitle.dart index 797adb963d..f3bf283065 100644 --- a/lib/models_new/video/video_play_info/subtitle.dart +++ b/lib/models_new/video/video_play_info/subtitle.dart @@ -1,4 +1,4 @@ -class Subtitle { +class Subtitle implements Comparable { late String lan; String? lanDoc; String? subtitleUrl; @@ -8,6 +8,8 @@ class Subtitle { Subtitle({ required this.lan, this.lanDoc, + this.subtitleUrl, + this.isAi = false, }); Subtitle.fromJson(Map json) { @@ -17,4 +19,13 @@ class Subtitle { subtitleUrl = json["subtitle_url"]; subtitleUrlV2 = json["subtitle_url_v2"]; } + + @override + int compareTo(Subtitle other) { + final thisHasZh = lan.contains('zh'); + final otherHasZh = other.lan.contains('zh'); + if (thisHasZh != otherHasZh) return thisHasZh ? -1 : 1; + if (isAi != other.isAi) return isAi ? 1 : -1; + return 0; + } } diff --git a/lib/models_new/video/video_play_info/subtitle_info.dart b/lib/models_new/video/video_play_info/subtitle_info.dart index d559c1697a..33e8861860 100644 --- a/lib/models_new/video/video_play_info/subtitle_info.dart +++ b/lib/models_new/video/video_play_info/subtitle_info.dart @@ -14,12 +14,6 @@ class SubtitleInfo { (json['subtitles'] as List?) ?.map((e) => Subtitle.fromJson(e as Map)) .toList() - ?..sort((a, b) { - final aHasZh = a.lan.contains('zh'); - final bHasZh = b.lan.contains('zh'); - if (aHasZh != bHasZh) return aHasZh ? -1 : 1; - if (a.isAi != b.isAi) return a.isAi ? 1 : -1; - return 0; - }), + ?..sort(), ); } diff --git a/lib/models_new/video/video_play_info/view_point.dart b/lib/models_new/video/video_play_info/view_point.dart index 41a69bb180..fe44ea79ea 100644 --- a/lib/models_new/video/video_play_info/view_point.dart +++ b/lib/models_new/video/video_play_info/view_point.dart @@ -4,9 +4,6 @@ class ViewPoint { int? to; String? content; String? imgUrl; - String? logoUrl; - String? teamType; - String? teamName; ViewPoint({ this.type, @@ -14,9 +11,6 @@ class ViewPoint { this.to, this.content, this.imgUrl, - this.logoUrl, - this.teamType, - this.teamName, }); factory ViewPoint.fromJson(Map json) => ViewPoint( @@ -25,8 +19,5 @@ class ViewPoint { to: json["to"], content: json["content"], imgUrl: json["imgUrl"], - logoUrl: json["logoUrl"], - teamType: json["team_type"], - teamName: json["team_name"], ); } diff --git a/lib/models_new/video/video_stein_edgeinfo/choice.dart b/lib/models_new/video/video_stein_edgeinfo/choice.dart index 2101ee8a74..28a9f39cae 100644 --- a/lib/models_new/video/video_stein_edgeinfo/choice.dart +++ b/lib/models_new/video/video_stein_edgeinfo/choice.dart @@ -1,29 +1,17 @@ import 'package:PiliPlus/models_new/video/video_detail/episode.dart'; class Choice extends BaseEpisodeItem { - String? platformAction; - String? nativeAction; - String? condition; String? option; - int? isDefault; Choice({ super.id, - this.platformAction, - this.nativeAction, - this.condition, super.cid, this.option, - this.isDefault, }); factory Choice.fromJson(Map json) => Choice( id: json['id'] as int?, - platformAction: json['platform_action'] as String?, - nativeAction: json['native_action'] as String?, - condition: json['condition'] as String?, cid: json['cid'] as int?, option: json['option'] as String?, - isDefault: json['is_default'] as int?, ); } diff --git a/lib/models_new/video/video_stein_edgeinfo/data.dart b/lib/models_new/video/video_stein_edgeinfo/data.dart index 1b3ed20e47..4db19de6f1 100644 --- a/lib/models_new/video/video_stein_edgeinfo/data.dart +++ b/lib/models_new/video/video_stein_edgeinfo/data.dart @@ -1,39 +1,15 @@ import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/edges.dart'; -import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/preload.dart'; -import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/story_list.dart'; class EdgeInfoData { - String? title; - int? edgeId; - List? storyList; Edges? edges; - String? buvid; - Preload? preload; - int? isLeaf; EdgeInfoData({ - this.title, - this.edgeId, - this.storyList, this.edges, - this.buvid, - this.preload, - this.isLeaf, }); factory EdgeInfoData.fromJson(Map json) => EdgeInfoData( - title: json['title'] as String?, - edgeId: json['edge_id'] as int?, - storyList: (json['story_list'] as List?) - ?.map((e) => StoryList.fromJson(e as Map)) - .toList(), edges: json['edges'] == null ? null : Edges.fromJson(json['edges'] as Map), - buvid: json['buvid'] as String?, - preload: json['preload'] == null - ? null - : Preload.fromJson(json['preload'] as Map), - isLeaf: json['is_leaf'] as int?, ); } diff --git a/lib/models_new/video/video_stein_edgeinfo/edges.dart b/lib/models_new/video/video_stein_edgeinfo/edges.dart index 4d7a93424d..a31946e102 100644 --- a/lib/models_new/video/video_stein_edgeinfo/edges.dart +++ b/lib/models_new/video/video_stein_edgeinfo/edges.dart @@ -1,23 +1,13 @@ -import 'package:PiliPlus/models_new/video/video_detail/dimension.dart'; import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/question.dart'; -import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/skin.dart'; class Edges { - Dimension? dimension; List? questions; - Skin? skin; - Edges({this.dimension, this.questions, this.skin}); + Edges({this.questions}); factory Edges.fromJson(Map json) => Edges( - dimension: json['dimension'] == null - ? null - : Dimension.fromJson(json['dimension'] as Map), questions: (json['questions'] as List?) ?.map((e) => Question.fromJson(e as Map)) .toList(), - skin: json['skin'] == null - ? null - : Skin.fromJson(json['skin'] as Map), ); } diff --git a/lib/models_new/video/video_stein_edgeinfo/preload.dart b/lib/models_new/video/video_stein_edgeinfo/preload.dart deleted file mode 100644 index c3ab32c2a5..0000000000 --- a/lib/models_new/video/video_stein_edgeinfo/preload.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:PiliPlus/models_new/video/video_stein_edgeinfo/video.dart'; - -class Preload { - List