Compare commits

...

25 Commits

Author SHA1 Message Date
dom
6341660788 Release 2.0.3
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-08 13:05:08 +08:00
dom
a1dbcae93e upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-08 12:50:22 +08:00
dom
1526137a64 fix AppSign
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-07 17:24:44 +08:00
dom
3097b56816 refactor device orientation
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-07 12:56:29 +08:00
dom
db74eccf77 android shortcut
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-06 15:34:07 +08:00
dom
14890d342a tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-06 00:13:35 +08:00
dom
51163dd985 create user shorcut on Android
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 20:11:13 +08:00
dom
f0d9b3a9a7 upgrade dep
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 20:11:13 +08:00
dom
8f3707fbf1 opt reserve btn
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 13:35:19 +08:00
dom
f52bbe9804 upgrade dep
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 12:12:10 +08:00
dom
3ec54868d0 show reserve btn in space page
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 12:11:47 +08:00
dom
c0b55f9af3 show member guard
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 12:11:36 +08:00
dom
279f21857d clean up models
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-05 12:11:22 +08:00
dom
b897103af0 Upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:39:03 +08:00
dom
353664fbd4 Open offline dir on desktop
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:35:32 +08:00
dom
de3505ce07 Floating NavBar
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:35:32 +08:00
dom
cdc1720358 Reformat
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-03 09:35:25 +08:00
dom
904d210ba2 build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-04-02 12:45:08 +08:00
HCha
db8dd85b63 fix: revert commit 7ee6d1e element.weight (#1879) 2026-04-02 12:44:35 +08:00
dom
8ad130567e Release 2.0.2
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 18:25:39 +08:00
dom
7eb21bc5a2 build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 18:24:02 +08:00
dom
ea4316a847 opt ui
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 16:57:12 +08:00
dom
2bbc97a950 fix macOS build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 11:51:22 +08:00
dom
0178d105ba add Local Network permissions for iOS & macOS
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 11:17:32 +08:00
dom
771fa75f48 upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-31 11:17:09 +08:00
409 changed files with 2737 additions and 7032 deletions

View File

@@ -13,7 +13,7 @@ on:
jobs:
build-mac-app:
name: Release Mac
runs-on: macos-latest
runs-on: macos-26
steps:
- name: Checkout code
uses: actions/checkout@v6

View File

@@ -57,7 +57,6 @@ jobs:
mv dist/**/*.exe PiliPlus-Win-Setup/PiliPlus_windows_${{env.version}}_x64_setup.exe
- name: Compress
if: ${{ github.event.inputs.tag != '' }}
run: |
Compress-Archive -Path "Release/PiliPlus-Win" -DestinationPath "PiliPlus_windows_${{env.version}}_x64_portable.zip"
shell: pwsh
@@ -75,9 +74,9 @@ jobs:
- name: Upload windows file release
uses: actions/upload-artifact@v7
with:
archive: true
name: PiliPlus_windows_${{env.version}}_x64_portable
path: Release
archive: false
name: Windows-file-x64-release
path: PiliPlus_windows_*.zip
- name: Upload windows setup release
uses: actions/upload-artifact@v7

View File

@@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.piliplus">
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
@@ -16,8 +17,7 @@
</queries>
<queries>
<intent>
<action android:name=
"android.support.customtabs.action.CustomTabsService" />
<action android:name="android.support.customtabs.action.CustomTabsService" />
</intent>
</queries>
@@ -35,56 +35,62 @@
</intent>
</queries>
<application
android:label="@string/app_name"
<application xmlns:tools="http://schemas.android.com/tools"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
xmlns:tools="http://schemas.android.com/tools"
android:enableOnBackInvokedCallback="false"
android:allowBackup="false"
android:enableOnBackInvokedCallback="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
tools:replace="android:allowBackup">
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:supportsPictureInPicture="true"
android:launchMode="singleTask"
android:resizeableActivity="true"
>
android:supportsPictureInPicture="true"
android:theme="@style/LaunchTheme"
android:windowSoftInputMode="adjustResize">
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
<meta-data
android:name="flutter_deeplinking_enabled"
android:value="false" />
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter android:label="PiliPlus">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="http"/>
<data android:scheme="https"/>
<data android:host="*.bilibili.com"/>
<data android:host="*.bilibili.cn"/>
<data android:host="*.bilibili.tv"/>
<data android:host="bilibili.com"/>
<data android:host="bilibili.cn"/>
<data android:host="bilibili.tv"/>
<data android:scheme="http" />
<data android:scheme="https" />
<data android:host="*.bilibili.com" />
<data android:host="*.bilibili.cn" />
<data android:host="*.bilibili.tv" />
<data android:host="bilibili.com" />
<data android:host="bilibili.cn" />
<data android:host="bilibili.tv" />
<data android:host="b23.tv" />
<!--<data android:host="live.bilibili.com"/>-->
<!--<data android:host="www.bilibili.com"/>-->
@@ -100,36 +106,56 @@
<intent-filter android:label="PiliPlus">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" />
<action android:name="com.example.piliplus.SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="bilibili"/>
<data android:scheme="bilibili" />
<data android:host="download" />
<data android:host="forward" />
<data android:host="comment"
<data
android:host="comment"
android:pathPattern="/detail/.*/.*/.*" />
<data android:host="uper" />
<data android:host="article"
<data
android:host="article"
android:pathPattern="/readlist" />
<data android:host="opus" />
<data android:host="advertise" android:path="/home" />
<data
android:host="advertise"
android:path="/home" />
<data android:host="clip" />
<data android:host="search" android:pathPattern=".*" />
<data
android:host="search"
android:pathPattern=".*" />
<data android:host="stardust-search" />
<data android:host="music" />
<data android:host="cheese" />
<data android:host="bangumi"
<data
android:host="bangumi"
android:pathPattern="/season.*" />
<data android:host="bangumi" android:pathPattern="/.*" />
<data android:host="pictureshow"
<data
android:host="bangumi"
android:pathPattern="/.*" />
<data
android:host="pictureshow"
android:pathPrefix="/creative_center" />
<data android:host="cliparea" />
<data android:host="im" />
<data android:host="im" android:path="/notifications" />
<data
android:host="im"
android:path="/notifications" />
<data android:host="following" />
<data android:host="following"
<data
android:host="following"
android:pathPattern="/detail/.*" />
<data android:host="following"
<data
android:host="following"
android:path="/publishInfo/" />
<data android:host="laser" android:pathPattern="/.*" />
<data
android:host="laser"
android:pathPattern="/.*" />
<data android:host="livearea" />
<data android:host="live" />
<data android:host="catalog" />
@@ -147,28 +173,44 @@
<data android:host="video" />
<data android:host="story" />
<data android:host="podcast" />
<data android:host="main" android:path="/favorite" />
<data android:host="pgc" android:path="/theater/match" />
<data android:host="pgc" android:path="/theater/square" />
<data android:host="m.bilibili.com"
<data
android:host="main"
android:path="/favorite" />
<data
android:host="pgc"
android:path="/theater/match" />
<data
android:host="pgc"
android:path="/theater/square" />
<data
android:host="m.bilibili.com"
android:path="/topic-detail" />
<data android:host="article" />
<data android:host="pegasus"
<data
android:host="pegasus"
android:pathPattern="/channel/v2/.*" />
<data android:host="feed" android:pathPattern="/channel" />
<data
android:host="feed"
android:pathPattern="/channel" />
<data android:host="vip" />
<data android:host="user_center" android:path="/vip" />
<data
android:host="user_center"
android:path="/vip" />
<data android:host="history" />
<data android:host="charge" android:path="/rank" />
<data
android:host="charge"
android:path="/rank" />
<data android:host="assistant" />
<data android:host="feedback" />
<data android:host="auth" android:path="/launch" />
<data
android:host="auth"
android:path="/launch" />
</intent-filter>
</activity>
<service
<service
android:name="com.ryanheise.audioservice.AudioService"
android:exported="true"
android:foregroundServiceType="mediaPlayback"
android:exported="true"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
@@ -177,32 +219,37 @@
<activity
android:name="com.yalantis.ucrop.UCropActivity"
android:theme="@style/Ucrop.CropTheme"/>
android:theme="@style/Ucrop.CropTheme" />
<receiver
<receiver
android:name="com.ryanheise.audioservice.MediaButtonReceiver"
android:exported="true"
android:exported="true"
tools:ignore="Instantiatable">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</receiver>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<!--
Media access permissions.
Android 13 or higher.
@@ -210,5 +257,5 @@
-->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.WRITE_SETTINGS"/>
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
</manifest>

View File

@@ -1,11 +1,16 @@
package com.example.piliplus
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.SearchManager
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.content.res.Configuration
import android.graphics.BitmapFactory
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
@@ -16,6 +21,7 @@ import com.ryanheise.audioservice.AudioServiceActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import kotlin.system.exitProcess
import java.io.File
class MainActivity : AudioServiceActivity() {
private lateinit var methodChannel: MethodChannel
@@ -133,6 +139,38 @@ class MainActivity : AudioServiceActivity() {
}
}
"createShortcut" -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
val shortcutManager =
context.getSystemService(ShortcutManager::class.java)
if (shortcutManager.isRequestPinShortcutSupported) {
val id = call.argument<String>("id")!!
val uri = call.argument<String>("uri")!!
val label = call.argument<String>("label")!!
val icon = call.argument<String>("icon")!!
val bitmap = BitmapFactory.decodeFile(icon)
val shortcut =
ShortcutInfo.Builder(context, id)
.setShortLabel(label)
.setIcon(Icon.createWithAdaptiveBitmap(bitmap))
.setIntent(Intent(Intent.ACTION_VIEW, uri.toUri()))
.build()
val pinIntent =
shortcutManager.createShortcutResultIntent(shortcut)
val pendingIntent = PendingIntent.getBroadcast(
context, 0, pinIntent, PendingIntent.FLAG_IMMUTABLE
)
shortcutManager.requestPinShortcut(
shortcut,
pendingIntent.intentSender
)
}
} catch (e: Exception) {
}
}
}
else -> result.notImplemented()
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#515151"
android:pathData="M16.59 9H15V4c0-.55-.45-1-1-1h-4c-.55 0-1 .45-1 1v5H7.41c-.89 0-1.34 1.08-.71 1.71l4.59 4.59c.39.39 1.02.39 1.41 0l4.59-4.59c.63-.63.19-1.71-.7-1.71zM5 19c0 .55.45 1 1 1h12c.55 0 1-.45 1-1s-.45-1-1-1H6c-.55 0-1 .45-1 1z" />
</vector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#515151"
android:pathData="M15.5 14h-.79l-.28-.27c1.2-1.4 1.82-3.31 1.48-5.34-.47-2.78-2.79-5-5.59-5.34-4.23-.52-7.79 3.04-7.27 7.27.34 2.8 2.56 5.12 5.34 5.59 2.03.34 3.94-.28 5.34-1.48l.27.28v.79l4.25 4.25c.41.41 1.08.41 1.49 0 .41-.41.41-1.08 0-1.49L15.5 14zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</vector>

View File

@@ -1,3 +1,5 @@
<resources>
<string name="app_name">PiliPlus</string>
<string name="search">搜索</string>
<string name="offline_video">离线视频</string>
</resources>

View File

@@ -0,0 +1,20 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<shortcut
android:icon="@drawable/search"
android:shortcutId="search"
android:shortcutLongLabel="@string/search"
android:shortcutShortLabel="@string/search">
<intent
android:action="com.example.piliplus.SHORTCUT"
android:data="bilibili://search" />
</shortcut>
<shortcut
android:icon="@drawable/download"
android:shortcutId="offline_video"
android:shortcutLongLabel="@string/offline_video"
android:shortcutShortLabel="@string/offline_video">
<intent
android:action="com.example.piliplus.SHORTCUT"
android:data="bilibili://download" />
</shortcut>
</shortcuts>

View File

@@ -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):
@@ -83,6 +81,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
@@ -114,7 +114,6 @@ 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`)
@@ -133,6 +132,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`)
@@ -160,8 +160,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:
@@ -198,6 +196,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,7 +221,6 @@ SPEC CHECKSUMS:
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
auto_orientation: a1600c9ed72e6e96982fb4e1214463343342432a
battery_plus: b42253f6d2dde71712f8c36fef456d99121c5977
chat_bottom_container: f1eb8323db77a87db50f361142c679f11e892d1b
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
@@ -243,6 +242,7 @@ SPEC CHECKSUMS:
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

View File

@@ -131,5 +131,13 @@
</array>
<key>UIStatusBarHidden</key>
<false/>
<key>NSLocalNetworkUsageDescription</key>
<string>需要访问本地网络以发现和连接 DLNA 投屏设备</string>
<key>NSBonjourServices</key>
<array>
<string>_ssdp._udp</string>
<string>_upnp._tcp</string>
<string>_http._tcp</string>
</array>
</dict>
</plist>

View File

@@ -5,11 +5,11 @@ import 'package:flutter/material.dart';
Widget avatars({
required ColorScheme colorScheme,
required Iterable<Owner> 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(

View File

@@ -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<Widget> destinations;
final ValueChanged<int>? onDestinationSelected;
final Color? backgroundColor;
final double? elevation;
final Color? shadowColor;
final Color? surfaceTintColor;
final Color? indicatorColor;
final ShapeBorder? indicatorShape;
final NavigationDestinationLabelBehavior? labelBehavior;
final WidgetStateProperty<Color?>? overlayColor;
final WidgetStateProperty<TextStyle?>? 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: <Widget>[
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>{WidgetState.selected};
const unselectedState = <WidgetState>{};
const disabledState = <WidgetState>{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<double> selectedAnimation;
final NavigationDestinationLabelBehavior labelBehavior;
final Color? indicatorColor;
final ShapeBorder? indicatorShape;
final WidgetStateProperty<Color?>? overlayColor;
final VoidCallback onTap;
final WidgetStateProperty<TextStyle?>? 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<double> animation;
final Color? color;
final double width;
final double height;
static final _anim = Tween<double>(
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: <Widget>[
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<double>) 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<double> 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<double>(
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<double>) 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<double> animation;
final Curve curve;
final Curve reverseCurve;
final Widget Function(BuildContext, Animation<double>) 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<double> 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.brightness.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<IconThemeData?>? get iconTheme {
return WidgetStateProperty.resolveWith((Set<WidgetState> 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.brightness.isDark ? _indicatorDark : _indicatorLight;
@override
ShapeBorder? get indicatorShape => const StadiumBorder();
@override
WidgetStateProperty<TextStyle?>? get labelTextStyle {
return WidgetStateProperty.resolveWith((Set<WidgetState> 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);
}

View File

@@ -830,6 +830,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 +1001,7 @@ 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';
}

View File

@@ -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';
@@ -830,4 +831,23 @@ abstract final class MemberHttp {
return Error(res.data['message']);
}
}
static Future<LoadingState<MemberGuardData>> 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']);
}
}
}

View File

@@ -431,7 +431,9 @@ abstract final class UserHttp {
}
}
static Future<LoadingState<void>> spaceSettingMod(Map data) async {
static Future<LoadingState<void>> spaceSettingMod(
Map<String, dynamic> 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<LoadingState<void>> 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']);
}
}
}

View File

@@ -9,6 +9,7 @@ import 'package:PiliPlus/common/widgets/scale_app.dart';
import 'package:PiliPlus/common/widgets/scroll_behavior.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/models/common/theme/theme_color_type.dart';
import 'package:PiliPlus/plugin/pl_player/utils/fullscreen.dart';
import 'package:PiliPlus/router/app_pages.dart';
import 'package:PiliPlus/services/account_service.dart';
import 'package:PiliPlus/services/download/download_service.dart';
@@ -28,6 +29,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:PiliPlus/utils/theme_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:catcher_2/catcher_2.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@@ -82,6 +84,10 @@ Future<void> _initAppPath() async {
appSupportDirPath = (await getApplicationSupportDirectory()).path;
}
Future<void> _initSdkInt() async {
Utils.sdkInt = (await DeviceInfoPlugin().androidInfo).version.sdkInt;
}
void main() async {
ScaledWidgetsFlutterBinding.ensureInitialized();
MediaKit.ensureInitialized();
@@ -104,15 +110,8 @@ void main() async {
if (PlatformUtils.isMobile) {
await Future.wait([
SystemChrome.setPreferredOrientations(
[
DeviceOrientation.portraitUp,
if (Pref.horizontalScreen) ...[
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
],
],
),
if (Platform.isAndroid) _initSdkInt(),
if (Pref.horizontalScreen) ?fullMode() else ?portraitUpMode(),
setupServiceLocator(),
]);
} else if (Platform.isWindows) {
@@ -131,12 +130,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,

View File

@@ -2,7 +2,7 @@ enum AccountType {
main('主账号'),
heartbeat('记录观看'),
recommend('推荐'),
video('视频取流')
video('视频取流'),
;
final String title;

View File

@@ -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;

View File

@@ -4,7 +4,7 @@ enum BadgeType {
none(),
vip('大会员'),
person('认证个人', Color(0xFFFFCC00)),
institution('认证机构', Colors.lightBlueAccent)
institution('认证机构', Colors.lightBlueAccent),
;
final String? desc;

View File

@@ -2,7 +2,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
enum BarHideType with EnumWithLabel {
instant('即时'),
sync('同步')
sync('同步'),
;
@override

View File

@@ -1,7 +1,7 @@
enum DmBlockType {
keyword('关键词'),
regex('正则'),
uid('用户')
uid('用户'),
;
final String label;

View File

@@ -1,7 +1,7 @@
enum DynamicBadgeMode {
hidden('隐藏'),
point('红点'),
number('数字')
number('数字'),
;
final String desc;

View File

@@ -3,7 +3,7 @@ enum DynamicsTabType {
video('投稿'),
pgc('番剧'),
article('专栏'),
up('UP')
up('UP'),
;
final String label;

View File

@@ -3,7 +3,7 @@ enum UpPanelPosition {
leftFixed('左侧常驻'),
rightFixed('右侧常驻'),
leftDrawer('左侧抽屉'),
rightDrawer('右侧抽屉')
rightDrawer('右侧抽屉'),
;
final String label;

View File

@@ -1,7 +1,7 @@
enum EpisodeType {
part('分P'),
season('合集'),
pgc('剧集')
pgc('剧集'),
;
final String title;

View File

@@ -1,7 +1,7 @@
enum FavOrderType {
mtime('最近收藏'),
view('最多播放'),
pubtime('最近投稿')
pubtime('最近投稿'),
;
final String label;

View File

@@ -13,7 +13,7 @@ enum FavTabType {
article('专栏', FavArticlePage()),
note('笔记', FavNotePage()),
topic('话题', FavTopicPage()),
cheese('课堂', FavCheesePage())
cheese('课堂', FavCheesePage()),
;
final String title;

View File

@@ -1,6 +1,6 @@
enum FollowOrderType {
def('', '最近关注'),
attention('attention', '最常访问')
attention('attention', '最常访问'),
;
final String type;

View File

@@ -19,7 +19,7 @@ enum HomeTabType implements EnumWithLabel {
hot('热门'),
rank('分区'),
bangumi('番剧'),
cinema('影视')
cinema('影视'),
;
@override

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
enum LaterViewType {
all(0, '全部'),
// toView(1, '未看'),
unfinished(2, '未看完')
unfinished(2, '未看完'),
// viewed(3, '已看完'),
;

View File

@@ -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;

View File

@@ -6,7 +6,7 @@ enum ContributeType {
season(Api.spaceSeason),
series(Api.spaceSeries),
bangumi(Api.spaceBangumi),
comic(Api.spaceComic)
comic(Api.spaceComic),
;
final String api;

View File

@@ -8,7 +8,7 @@ enum MemberTabType {
favorite('收藏'),
bangumi('番剧'),
cheese('课堂'),
shop('小店')
shop('小店'),
;
static bool showMemberShop = Pref.showMemberShop;

View File

@@ -3,7 +3,7 @@ enum MsgUnReadType {
reply('回复我的'),
at('@我'),
like('收到的赞'),
sysMsg('系统通知')
sysMsg('系统通知'),
;
final String title;

View File

@@ -7,8 +7,8 @@ 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, size: 24),
Icon(Icons.home, size: 24),
HomePage(),
),
dynamics(
@@ -19,10 +19,10 @@ enum NavigationBarType implements EnumWithLabel {
),
mine(
'我的',
Icon(Icons.person_outline, size: 21),
Icon(Icons.person, size: 21),
Icon(Icons.person_outline, size: 24),
Icon(Icons.person, size: 24),
MinePage(),
)
),
;
@override

View File

@@ -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;

View File

@@ -19,7 +19,7 @@ enum RankType {
documentary('记录', seasonType: 3),
movie('电影', seasonType: 2),
tv('剧集', seasonType: 5),
variety('综艺', seasonType: 7)
variety('综艺', seasonType: 7),
;
final String label;

View File

@@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
enum ReplyOptionType {
allow('允许评论'),
close('关闭评论'),
choose('精选评论')
choose('精选评论'),
;
final String title;

View File

@@ -1,7 +1,7 @@
enum ReplySortType {
time('最新评论', '最新'),
hot('最热评论', '最热'),
select('精选评论', '精选')
select('精选评论', '精选'),
;
final String title;

View File

@@ -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;

View File

@@ -18,7 +18,7 @@ enum SearchType {
// 用户bili_user
bili_user('用户'),
// 专栏article
article('专栏')
article('专栏'),
;
// 相簿photo
// photo

View File

@@ -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;

View File

@@ -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('最多喜欢'),

View File

@@ -6,7 +6,7 @@ enum SettingType {
styleSetting('外观设置'),
extraSetting('其它设置'),
webdavSetting('WebDAV 设置'),
about('关于')
about('关于'),
;
final String title;

View File

@@ -2,7 +2,7 @@ enum ActionType {
skip('跳过'),
mute('静音'),
full('整个视频'),
poi('精彩时刻')
poi('精彩时刻'),
;
final String title;

View File

@@ -5,7 +5,7 @@ enum SkipType implements EnumWithLabel {
skipOnce('跳过一次'),
skipManually('手动跳过'),
showOnly('仅显示'),
disable('禁用')
disable('禁用'),
;
@override

View File

@@ -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;

View File

@@ -3,7 +3,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
enum SuperResolutionType with EnumWithLabel {
disable('禁用'),
efficiency('效率'),
quality('画质')
quality('画质'),
;
@override

View File

@@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
enum ThemeType {
light('浅色'),
dark('深色'),
system('跟随系统')
system('跟随系统'),
;
final String desc;

View File

@@ -7,7 +7,7 @@ enum AudioQuality {
dolby_30255(30255, '杜比全景声'),
k192(30280, '192K'),
k132(30232, '132K'),
k64(30216, '64K')
k64(30216, '64K'),
;
final int code;

View File

@@ -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_bcacheBilibili海外', 'cn-hk-eq-bcache-01.bilivideo.com')
hk_bcache('hk_bcacheBilibili海外', 'cn-hk-eq-bcache-01.bilivideo.com'),
;
final String desc;

View File

@@ -7,7 +7,7 @@ enum LiveQuality {
bluRay(400, '蓝光'),
superHD(250, '超清'),
smooth(150, '高清'),
flunt(80, '流畅')
flunt(80, '流畅'),
;
final int code;

View File

@@ -26,7 +26,7 @@ enum SourceType {
extraId: 4,
playlistSource: PlaylistSource.MEDIA_LIST,
),
file
file,
;
final int? mediaType;

View File

@@ -2,7 +2,7 @@ enum SubtitlePrefType {
off('默认不显示字幕'),
on('优先选择非自动生成(ai)字幕'),
withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'),
auto('静音时等同第二项,非静音时等同第三项')
auto('静音时等同第二项,非静音时等同第三项'),
;
final String desc;

View File

@@ -4,7 +4,7 @@ enum VideoDecodeFormatType {
DVH1(['dvh1']),
AV1(['av01']),
HEVC(['hev1', 'hvc1']),
AVC(['avc1'])
AVC(['avc1']),
;
String get description => name;

View File

@@ -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;

View File

@@ -13,7 +13,7 @@ enum VideoType {
type: 10,
replyType: 33,
api: Api.pugvUrl,
)
),
;
final int type;

View File

@@ -4,7 +4,7 @@ enum WebviewMenuItem {
openInBrowser('浏览器中打开'),
clearCache('清除缓存'),
resetCookie('重新设置Cookie'),
goBack('返回')
goBack('返回'),
;
final String title;

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -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<String>? imageUrls;
List<String>? originImageUrls;
bool? shareable;
bool? showLaterWatch;
bool? showSmallWindow;
bool? inList;
int? pre;
int? next;
List<ShareChannel>? 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<String, dynamic> 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<String, dynamic>),
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<dynamic>?)
?.map((e) => ShareChannel.fromJson(e as Map<String, dynamic>))
.toList(),
type: json['type'] as int?,
videoUrl: json['video_url'] as String?,
location: json['location'] as String?,
disableShare: json['disable_share'] as bool?,
);
}

View File

@@ -1,13 +0,0 @@
class ShareChannel {
String? name;
String? picture;
String? shareChannel;
ShareChannel({this.name, this.picture, this.shareChannel});
factory ShareChannel.fromJson(Map<String, dynamic> json) => ShareChannel(
name: json['name'] as String?,
picture: json['picture'] as String?,
shareChannel: json['share_channel'] as String?,
);
}

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -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<String>? imageUrls;
Category? category;
List<Category>? 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<String, dynamic> 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<String, dynamic>),
categories: (json['categories'] as List<dynamic>?)
?.map((e) => Category.fromJson(e as Map<String, dynamic>))
.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<String, dynamic>),
likeState: json['like_state'] as int?,
);
}

View File

@@ -1,13 +0,0 @@
class Category {
int? id;
int? parentId;
String? name;
Category({this.id, this.parentId, this.name});
factory Category.fromJson(Map<String, dynamic> json) => Category(
id: json['id'] as int?,
parentId: json['parent_id'] as int?,
name: json['name'] as String?,
);
}

View File

@@ -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<ArticleListItemModel>? articles;
Owner? author;
Last? last;
bool? attention;
ArticleListData({
this.list,
this.articles,
this.author,
this.last,
this.attention,
});
factory ArticleListData.fromJson(Map<String, dynamic> json) =>
@@ -31,9 +26,5 @@ class ArticleListData {
author: json['author'] == null
? null
: Owner.fromJson(json['author'] as Map<String, dynamic>),
last: json['last'] == null
? null
: Last.fromJson(json['last'] as Map<String, dynamic>),
attention: json['attention'] as bool?,
);
}

View File

@@ -1,13 +0,0 @@
class Label {
String? path;
String? text;
String? labelTheme;
Label({this.path, this.text, this.labelTheme});
factory Label.fromJson(Map<String, dynamic> json) => Label(
path: json['path'] as String?,
text: json['text'] as String?,
labelTheme: json['label_theme'] as String?,
);
}

View File

@@ -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<String>? 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<String, dynamic> 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<String, dynamic>),
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?,
);
}

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -1,13 +0,0 @@
class Category {
int? id;
int? parentId;
String? name;
Category({this.id, this.parentId, this.name});
factory Category.fromJson(Map<String, dynamic> json) => Category(
id: json['id'] as int?,
parentId: json['parent_id'] as int?,
name: json['name'] as String?,
);
}

View File

@@ -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<Category>? categories;
String? title;
String? summary;
String? bannerUrl;
int? templateId;
int? state;
Avatar? author;
int? reprint;
List<String>? imageUrls;
int? publishTime;
int? ctime;
int? mtime;
Stats? stats;
List<Tag>? tags;
int? words;
List<String>? 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<ArticleOps>? 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<String, dynamic> json) =>
ArticleViewData(
id: json['id'] as int?,
category: json['category'] == null
? null
: Category.fromJson(json['category'] as Map<String, dynamic>),
categories: (json['categories'] as List<dynamic>?)
?.map((e) => Category.fromJson(e as Map<String, dynamic>))
.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<String, dynamic>),
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<String, dynamic>),
tags: (json['tags'] as List<dynamic>?)
?.map((e) => Tag.fromJson(e as Map<String, dynamic>))
.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<String, dynamic>),
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<String, dynamic>),

View File

@@ -1,13 +0,0 @@
class Label {
String? path;
String? text;
String? labelTheme;
Label({this.path, this.text, this.labelTheme});
factory Label.fromJson(Map<String, dynamic> json) => Label(
path: json['path'] as String?,
text: json['text'] as String?,
labelTheme: json['label_theme'] as String?,
);
}

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -1,15 +1,9 @@
import 'package:PiliPlus/models/dynamics/article_content_model.dart';
class ArticleOpus {
int? opusid;
int? opussource;
String? title;
List<ArticleContentModel>? content;
ArticleOpus.fromJson(Map<String, dynamic> 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();
}

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -1,11 +0,0 @@
class Tag {
int? tid;
String? name;
Tag({this.tid, this.name});
factory Tag.fromJson(Map<String, dynamic> json) => Tag(
tid: json['tid'] as int?,
name: json['name'] as String?,
);
}

View File

@@ -2,16 +2,14 @@ import 'package:PiliPlus/models_new/blacklist/list.dart';
class BlackListData {
List<BlackListItem>? list;
int? reVersion;
int? total;
BlackListData({this.list, this.reVersion, this.total});
BlackListData({this.list, this.total});
factory BlackListData.fromJson(Map<String, dynamic> json) => BlackListData(
list: (json['list'] as List<dynamic>?)
?.map((e) => BlackListItem.fromJson(e as Map<String, dynamic>))
.toList(),
reVersion: json['re_version'] as int?,
total: json['total'] as int?,
);
}

View File

@@ -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<String, dynamic> 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<String, dynamic>,
),
vip: json['vip'] == null
? null
: Vip.fromJson(json['vip'] as Map<String, dynamic>),
nftIcon: json['nft_icon'] as String?,
recReason: json['rec_reason'] as String?,
trackId: json['track_id'] as String?,
followTime: json['follow_time'] as String?,
);
}

View File

@@ -1,15 +1,13 @@
import 'package:PiliPlus/models_new/coin_log/list.dart';
class CoinLogData {
List<CoinLogItem>? list;
int? count;
CoinLogData({this.list});
CoinLogData({this.list, this.count});
List<CoinLogItem>? list;
factory CoinLogData.fromJson(Map<String, dynamic> json) => CoinLogData(
list: (json['list'] as List<dynamic>?)
?.map((e) => CoinLogItem.fromJson(e as Map<String, dynamic>))
.toList(),
count: json['count'] as int?,
);
}

View File

@@ -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<String, dynamic> json) => CoinLogItem(
time: json['time'],
delta: (json['delta'] as num).toString(),

View File

@@ -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<String, dynamic> 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"],
);
}
}

View File

@@ -1,8 +1,12 @@
import 'dart:io' show Platform, Process;
import 'package:PiliPlus/models/common/video/video_type.dart';
import 'package:PiliPlus/pages/common/multi_select/base.dart'
show MultiSelectData;
import 'package:PiliPlus/utils/page_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/route_manager.dart';
class BiliDownloadEntryInfo with MultiSelectData {
@@ -68,10 +72,7 @@ class BiliDownloadEntryInfo with MultiSelectData {
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) {
@@ -97,14 +98,34 @@ class BiliDownloadEntryInfo with MultiSelectData {
);
},
),
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'),
),
@@ -395,7 +416,7 @@ enum DownloadStatus {
failDanmaku('获取弹幕失败'),
failPlayUrl('获取播放地址失败'),
pause('暂停中'),
wait('等待中')
wait('等待中'),
;
final String message;

View File

@@ -2,14 +2,12 @@ import 'package:PiliPlus/models_new/dynamic/dyn_mention/item.dart';
class MentionGroup {
String? groupName;
int? groupType;
List<MentionItem>? items;
MentionGroup({this.groupName, this.groupType, this.items});
MentionGroup({this.groupName, this.items});
factory MentionGroup.fromJson(Map<String, dynamic> json) => MentionGroup(
groupName: json['group_name'] as String?,
groupType: json['group_type'] as int?,
items: (json['items'] as List<dynamic>?)
?.map((e) => MentionItem.fromJson(e as Map<String, dynamic>))
.toList(),

View File

@@ -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?,
);

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -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<String, dynamic> 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<String, dynamic> 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,
};
}

View File

@@ -2,17 +2,15 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/all_sort_by.dart';
class TopicSortByConf {
List<AllSortBy>? allSortBy;
int? defaultSortBy;
int? showSortBy;
TopicSortByConf({this.allSortBy, this.defaultSortBy, this.showSortBy});
TopicSortByConf({this.allSortBy, this.showSortBy});
factory TopicSortByConf.fromJson(Map<String, dynamic> json) {
return TopicSortByConf(
allSortBy: (json['all_sort_by'] as List<dynamic>?)
?.map((e) => AllSortBy.fromJson(e as Map<String, dynamic>))
.toList(),
defaultSortBy: json['default_sort_by'] as int?,
showSortBy: json['show_sort_by'] as int?,
);
}

View File

@@ -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<TopicItem>? topicItems;
String? requestId;
PageInfo? pageInfo;
TopicPubSearchData({
this.newTopic,
this.hasCreateJurisdiction,
this.topicItems,
this.requestId,
this.pageInfo,
});
factory TopicPubSearchData.fromJson(Map<String, dynamic> json) =>
TopicPubSearchData(
newTopic: json['new_topic'] == null
? null
: NewTopic.fromJson(json['new_topic'] as Map<String, dynamic>),
hasCreateJurisdiction: json['has_create_jurisdiction'] as bool?,
topicItems: (json['topic_items'] as List<dynamic>?)
?.map((e) => TopicItem.fromJson(e as Map<String, dynamic>))
.toList(),
requestId: json['request_id'] as String?,
pageInfo: json['page_info'] == null
? null
: PageInfo.fromJson(json['page_info'] as Map<String, dynamic>),

View File

@@ -1,9 +0,0 @@
class NewTopic {
String? name;
NewTopic({this.name});
factory NewTopic.fromJson(Map<String, dynamic> json) => NewTopic(
name: json['name'] as String?,
);
}

View File

@@ -1,11 +1,9 @@
class PageInfo {
int? offset;
bool? hasMore;
PageInfo({this.offset, this.hasMore});
PageInfo({this.hasMore});
factory PageInfo.fromJson(Map<String, dynamic> json) => PageInfo(
offset: json['offset'] as int?,
hasMore: json['has_more'] as bool?,
);
}

View File

@@ -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<String, dynamic> json) => TopDetails(
@@ -23,8 +17,5 @@ class TopDetails {
topicCreator: json['topic_creator'] == null
? null
: TopicCreator.fromJson(json['topic_creator'] as Map<String, dynamic>),
hasCreateJurisdiction: json['has_create_jurisdiction'] as bool?,
wordColor: json['word_color'] as int?,
closePubLayerEntry: json['close_pub_layer_entry'] as bool?,
);
}

View File

@@ -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?,
);

View File

@@ -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<String, dynamic> json) => Author(
name: json['name'] as String?,
face: json['face'] as String?,
mid: json['mid'] as String?,
);
}

View File

@@ -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<String, dynamic> json) => Cover(
url: json['url'] as String?,
width: json['width'] as int?,
height: json['height'] as int?,
);
}

View File

@@ -3,16 +3,10 @@ import 'package:PiliPlus/models_new/fav/fav_article/item.dart';
class FavArticleData {
List<FavArticleItemModel>? items;
bool? hasMore;
String? offset;
String? updateNum;
String? updateBaseline;
FavArticleData({
this.items,
this.hasMore,
this.offset,
this.updateNum,
this.updateBaseline,
});
factory FavArticleData.fromJson(Map<String, dynamic> json) => FavArticleData(
@@ -20,8 +14,5 @@ class FavArticleData {
?.map((e) => FavArticleItemModel.fromJson(e as Map<String, dynamic>))
.toList(),
hasMore: json['has_more'] as bool?,
offset: json['offset'] as String?,
updateNum: json['update_num'] as String?,
updateBaseline: json['update_baseline'] as String?,
);
}

View File

@@ -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<String, dynamic> 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<String, dynamic>),

View File

@@ -1,11 +1,9 @@
class Stat {
String? view;
String? like;
Stat({this.view, this.like});
Stat({this.like});
factory Stat.fromJson(Map<String, dynamic> json) => Stat(
view: json['view'] as String?,
like: json['like'] as String?,
);
}

View File

@@ -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<String, dynamic> 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?,
);
}

View File

@@ -5,9 +5,8 @@ class FavDetailData {
FavFolderInfo? info;
List<FavDetailItemModel>? 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<String, dynamic> json) => FavDetailData(
info: json['info'] == null
@@ -17,6 +16,5 @@ class FavDetailData {
?.map((e) => FavDetailItemModel.fromJson(e as Map<String, dynamic>))
.toList(),
hasMore: json['has_more'] as bool?,
ttl: json['ttl'] as int?,
);
}

View File

@@ -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<String, dynamic> 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<String, dynamic>),
coverType: json['cover_type'] as int?,
cntInfo: json['cnt_info'] == null
? null
: CntInfo.fromJson(json['cnt_info'] as Map<String, dynamic>),
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?,
);
}

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