mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
Compare commits
40 Commits
ace286753c
...
2.0.3.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbc4f58323 | ||
|
|
b553e7554d | ||
|
|
68724c8a9e | ||
|
|
85baf8e0e6 | ||
|
|
222c9d01a0 | ||
|
|
db30aa8041 | ||
|
|
6f95456d20 | ||
|
|
de6e402d97 | ||
|
|
6341660788 | ||
|
|
a1dbcae93e | ||
|
|
1526137a64 | ||
|
|
3097b56816 | ||
|
|
db74eccf77 | ||
|
|
14890d342a | ||
|
|
51163dd985 | ||
|
|
f0d9b3a9a7 | ||
|
|
8f3707fbf1 | ||
|
|
f52bbe9804 | ||
|
|
3ec54868d0 | ||
|
|
c0b55f9af3 | ||
|
|
279f21857d | ||
|
|
b897103af0 | ||
|
|
353664fbd4 | ||
|
|
de3505ce07 | ||
|
|
cdc1720358 | ||
|
|
904d210ba2 | ||
|
|
db8dd85b63 | ||
|
|
8ad130567e | ||
|
|
7eb21bc5a2 | ||
|
|
ea4316a847 | ||
|
|
2bbc97a950 | ||
|
|
0178d105ba | ||
|
|
771fa75f48 | ||
|
|
82483b33fc | ||
|
|
886c53c7d8 | ||
|
|
f0050dd6e6 | ||
|
|
e6a2f65b4e | ||
|
|
2fc3f9864f | ||
|
|
64c05a1b06 | ||
|
|
7c4e20f96c |
2
.github/workflows/mac.yml
vendored
2
.github/workflows/mac.yml
vendored
@@ -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
|
||||
|
||||
7
.github/workflows/win_x64.yml
vendored
7
.github/workflows/win_x64.yml
vendored
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
10
android/app/src/main/res/drawable/download.xml
Normal file
10
android/app/src/main/res/drawable/download.xml
Normal 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>
|
||||
10
android/app/src/main/res/drawable/search.xml
Normal file
10
android/app/src/main/res/drawable/search.xml
Normal 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>
|
||||
@@ -1,3 +1,5 @@
|
||||
<resources>
|
||||
<string name="app_name">PiliPlus</string>
|
||||
<string name="search">搜索</string>
|
||||
<string name="offline_video">离线视频</string>
|
||||
</resources>
|
||||
20
android/app/src/main/res/xml-v25/shortcuts.xml
Normal file
20
android/app/src/main/res/xml-v25/shortcuts.xml
Normal 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>
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -10,23 +10,21 @@ class CustomToast extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
return Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: MediaQuery.viewPaddingOf(context).bottom + 30,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primaryContainer.withValues(
|
||||
alpha: toastOpacity,
|
||||
),
|
||||
color: colorScheme.primaryContainer.withValues(alpha: toastOpacity),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(20)),
|
||||
),
|
||||
child: Text(
|
||||
msg,
|
||||
style: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.colorScheme.onPrimaryContainer,
|
||||
color: colorScheme.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -41,7 +39,7 @@ class LoadingWidget extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
final theme = Theme.of(context);
|
||||
final onSurfaceVariant = theme.colorScheme.onSurfaceVariant;
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
|
||||
@@ -58,7 +56,6 @@ class LoadingWidget extends StatelessWidget {
|
||||
strokeWidth: 3,
|
||||
valueColor: AlwaysStoppedAnimation(onSurfaceVariant),
|
||||
),
|
||||
|
||||
//msg
|
||||
Text(msg, style: TextStyle(color: onSurfaceVariant)),
|
||||
],
|
||||
|
||||
31
lib/common/widgets/extra_hit_test_widget.dart
Normal file
31
lib/common/widgets/extra_hit_test_widget.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'package:flutter/rendering.dart' show RenderProxyBox, BoxHitTestResult;
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class ExtraHitTestWidget extends SingleChildRenderObjectWidget {
|
||||
const ExtraHitTestWidget({
|
||||
super.key,
|
||||
required this.width,
|
||||
required Widget super.child,
|
||||
});
|
||||
|
||||
final double width;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return RenderExtraHitTestWidget(width: width);
|
||||
}
|
||||
}
|
||||
|
||||
class RenderExtraHitTestWidget extends RenderProxyBox {
|
||||
RenderExtraHitTestWidget({
|
||||
required double width,
|
||||
}) : _width = width;
|
||||
|
||||
final double _width;
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, {required Offset position}) {
|
||||
return super.hitTestChildren(result, position: position) ||
|
||||
position.dx <= _width;
|
||||
}
|
||||
}
|
||||
777
lib/common/widgets/floating_navigation_bar.dart
Normal file
777
lib/common/widgets/floating_navigation_bar.dart
Normal 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);
|
||||
}
|
||||
@@ -217,8 +217,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
|
||||
RefreshIndicatorStatus? _status;
|
||||
late Future<void> _pendingRefreshFuture;
|
||||
double? _dragOffset;
|
||||
late Color _effectiveValueColor =
|
||||
widget.color ?? Theme.of(context).colorScheme.primary;
|
||||
late Color _effectiveValueColor;
|
||||
// late Color _backgroundColor;
|
||||
|
||||
static final Animatable<double> _threeQuarterTween = Tween<double>(
|
||||
begin: 0.0,
|
||||
@@ -274,9 +274,10 @@ class RefreshIndicatorState extends State<RefreshIndicator>
|
||||
}
|
||||
|
||||
void _setupColorTween() {
|
||||
final colorScheme = ColorScheme.of(context);
|
||||
// _backgroundColor = colorScheme.surfaceContainerHighest;
|
||||
// Reset the current value color.
|
||||
_effectiveValueColor =
|
||||
widget.color ?? Theme.of(context).colorScheme.primary;
|
||||
_effectiveValueColor = widget.color ?? colorScheme.primary;
|
||||
final Color color = _effectiveValueColor;
|
||||
if (color.a == 0) {
|
||||
// Set an always stopped animation instead of a driven tween.
|
||||
@@ -558,14 +559,52 @@ class RefreshIndicatorState extends State<RefreshIndicator>
|
||||
}
|
||||
|
||||
bool _onDrag(double offset, double viewportDimension) {
|
||||
if (_positionController.value > 0.0 &&
|
||||
_status == RefreshIndicatorStatus.drag) {
|
||||
if (_positionController.value > 0.0 && _status == .drag) {
|
||||
_dragOffset = _dragOffset! + offset;
|
||||
_checkDragOffset(viewportDimension);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// late final _refreshKey = GlobalKey();
|
||||
// Widget _m3eRefreshProgressIndicator(bool showIndeterminateIndicator) {
|
||||
// const indicatorMargin = EdgeInsets.all(4);
|
||||
// const indicatorPadding = EdgeInsets.all(6);
|
||||
// const indicatorSize = 41.0;
|
||||
|
||||
// final progress = _value.value;
|
||||
// return Padding(
|
||||
// padding: indicatorMargin,
|
||||
// child: SizedBox(
|
||||
// width: indicatorSize,
|
||||
// height: indicatorSize,
|
||||
// child: Material(
|
||||
// type: MaterialType.circle,
|
||||
// color: _backgroundColor,
|
||||
// elevation: widget.elevation,
|
||||
// child: Padding(
|
||||
// padding: indicatorPadding,
|
||||
// child: showIndeterminateIndicator
|
||||
// ? M3ELoadingIndicator(
|
||||
// childKey: _refreshKey,
|
||||
// color: _effectiveValueColor,
|
||||
// morphs: Morphs.refreshMorphs,
|
||||
// size: null,
|
||||
// )
|
||||
// : RawM3ELoadingIndicator(
|
||||
// key: _refreshKey,
|
||||
// morph: Morphs.manualMorph,
|
||||
// progress: progress,
|
||||
// angle: -progress * math.pi,
|
||||
// color: _valueColor.value!,
|
||||
// size: null,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
// ignore: camel_case_types
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import 'dart:math' show pi;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/semantics.dart' show SemanticsConfiguration;
|
||||
|
||||
///
|
||||
/// created by dom on 2026/02/14
|
||||
@@ -73,6 +74,7 @@ class RenderLoadingIndicator extends RenderBox {
|
||||
if (_progress == value) return;
|
||||
_progress = value;
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -119,6 +121,16 @@ class RenderLoadingIndicator extends RenderBox {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config
|
||||
..role = .progressBar
|
||||
..minValue = '0'
|
||||
..maxValue = '100'
|
||||
..value = (_progress * 100).round().toString();
|
||||
}
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => true;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@ import 'package:flutter/material.dart';
|
||||
|
||||
const Widget m3eLoading = Center(child: M3ELoadingIndicator());
|
||||
|
||||
const Widget circularLoading = Center(child: CircularProgressIndicator());
|
||||
|
||||
const Widget linearLoading = SliverToBoxAdapter(
|
||||
child: LinearProgressIndicator(),
|
||||
);
|
||||
|
||||
@@ -17,14 +17,27 @@
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/morphs.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/physics.dart' show SpringSimulation;
|
||||
import 'package:flutter/semantics.dart';
|
||||
import 'package:material_new_shapes/material_new_shapes.dart';
|
||||
|
||||
/// reimplement of https://github.com/EmilyMoonstone/material_3_expressive/tree/main/packages/loading_indicator_m3e
|
||||
|
||||
class M3ELoadingIndicator extends StatefulWidget {
|
||||
const M3ELoadingIndicator({super.key});
|
||||
const M3ELoadingIndicator({
|
||||
super.key,
|
||||
// this.childKey,
|
||||
this.morphs,
|
||||
this.color,
|
||||
this.size = const Size.square(40),
|
||||
});
|
||||
final List<Morph>? morphs;
|
||||
|
||||
final Color? color;
|
||||
final Size size;
|
||||
// final Key? childKey;
|
||||
|
||||
@override
|
||||
State<M3ELoadingIndicator> createState() => _M3ELoadingIndicatorState();
|
||||
@@ -32,42 +45,25 @@ class M3ELoadingIndicator extends StatefulWidget {
|
||||
|
||||
class _M3ELoadingIndicatorState extends State<M3ELoadingIndicator>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static final List<Morph> _morphs = () {
|
||||
final List<RoundedPolygon> shapes = [
|
||||
MaterialShapes.softBurst,
|
||||
MaterialShapes.cookie9Sided,
|
||||
MaterialShapes.pentagon,
|
||||
MaterialShapes.pill,
|
||||
MaterialShapes.sunny,
|
||||
MaterialShapes.cookie4Sided,
|
||||
MaterialShapes.oval,
|
||||
];
|
||||
return [
|
||||
for (var i = 0; i < shapes.length; i++)
|
||||
Morph(
|
||||
shapes[i],
|
||||
shapes[(i + 1) % shapes.length],
|
||||
),
|
||||
];
|
||||
}();
|
||||
|
||||
static const int _morphIntervalMs = 650;
|
||||
static const double _fullRotation = 360.0;
|
||||
static const double _fullRotation = 2 * math.pi;
|
||||
static const int _globalRotationDurationMs = 4666;
|
||||
static const double _quarterRotation = _fullRotation / 4;
|
||||
|
||||
late final List<Morph> _morphs;
|
||||
late final AnimationController _controller;
|
||||
|
||||
int _morphIndex = 1;
|
||||
|
||||
double _morphRotationTargetAngle = _quarterRotation;
|
||||
double _morphRotationTarget = _quarterRotation;
|
||||
|
||||
final _morphAnimationSpec = SpringSimulation(
|
||||
static final _morphAnimationSpec = SpringSimulation(
|
||||
SpringDescription.withDampingRatio(ratio: 0.6, stiffness: 200.0, mass: 1.0),
|
||||
0.0,
|
||||
1.0,
|
||||
5.0,
|
||||
snapToEnd: true,
|
||||
// tolerance: const Tolerance(velocity: 0.1, distance: 0.1),
|
||||
);
|
||||
|
||||
void _statusListener(AnimationStatus status) {
|
||||
@@ -78,23 +74,21 @@ class _M3ELoadingIndicatorState extends State<M3ELoadingIndicator>
|
||||
|
||||
void _startAnimation() {
|
||||
_morphIndex++;
|
||||
_morphRotationTargetAngle =
|
||||
(_morphRotationTargetAngle + _quarterRotation) % _fullRotation;
|
||||
_controller
|
||||
..value = 0.0
|
||||
..animateWith(_morphAnimationSpec);
|
||||
_morphRotationTarget =
|
||||
(_morphRotationTarget + _quarterRotation) % _fullRotation;
|
||||
_controller.animateWith(_morphAnimationSpec);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_morphs = widget.morphs ?? Morphs.loadingMorphs;
|
||||
_controller =
|
||||
AnimationController(
|
||||
vsync: this,
|
||||
duration: const Duration(milliseconds: _morphIntervalMs),
|
||||
)
|
||||
..addStatusListener(_statusListener)
|
||||
..value = 0.0
|
||||
..animateWith(_morphAnimationSpec);
|
||||
}
|
||||
|
||||
@@ -110,82 +104,86 @@ class _M3ELoadingIndicatorState extends State<M3ELoadingIndicator>
|
||||
final elapsedInMs =
|
||||
_morphIntervalMs * (_morphIndex - 1) +
|
||||
(_controller.lastElapsedDuration?.inMilliseconds ?? 0);
|
||||
final globalRotationControllerValue =
|
||||
(elapsedInMs % _globalRotationDurationMs) / _globalRotationDurationMs;
|
||||
final globalRotationDegrees = globalRotationControllerValue * _fullRotation;
|
||||
final totalRotationDegrees =
|
||||
progress * _quarterRotation +
|
||||
_morphRotationTargetAngle +
|
||||
globalRotationDegrees;
|
||||
return totalRotationDegrees * (math.pi / 180.0);
|
||||
final globalRotation =
|
||||
(elapsedInMs % _globalRotationDurationMs) /
|
||||
_globalRotationDurationMs *
|
||||
_fullRotation;
|
||||
|
||||
return progress * _quarterRotation + _morphRotationTarget + globalRotation;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = Theme.of(context).colorScheme.secondaryFixedDim;
|
||||
final color = widget.color ?? ColorScheme.of(context).secondaryFixedDim;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
final progress = _controller.value;
|
||||
return _M3ELoadingIndicator(
|
||||
return RawM3ELoadingIndicator(
|
||||
// key: widget.childKey,
|
||||
morph: _morphs[_morphIndex % _morphs.length],
|
||||
progress: progress,
|
||||
angle: _calcAngle(progress),
|
||||
color: color,
|
||||
size: widget.size,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _M3ELoadingIndicator extends LeafRenderObjectWidget {
|
||||
const _M3ELoadingIndicator({
|
||||
class RawM3ELoadingIndicator extends LeafRenderObjectWidget {
|
||||
const RawM3ELoadingIndicator({
|
||||
super.key,
|
||||
required this.morph,
|
||||
required this.progress,
|
||||
required this.angle,
|
||||
required this.color,
|
||||
required this.size,
|
||||
});
|
||||
|
||||
final Morph morph;
|
||||
|
||||
final double progress;
|
||||
|
||||
final double angle;
|
||||
|
||||
final Color color;
|
||||
final Size size;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderM3ELoadingIndicator(
|
||||
return RenderM3ELoadingIndicator(
|
||||
morph: morph,
|
||||
progress: progress,
|
||||
angle: angle,
|
||||
color: color,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
_RenderM3ELoadingIndicator renderObject,
|
||||
RenderM3ELoadingIndicator renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..morph = morph
|
||||
..progress = progress
|
||||
..angle = angle
|
||||
..color = color;
|
||||
..color = color
|
||||
..preferredSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderM3ELoadingIndicator extends RenderBox {
|
||||
_RenderM3ELoadingIndicator({
|
||||
class RenderM3ELoadingIndicator extends RenderBox {
|
||||
RenderM3ELoadingIndicator({
|
||||
required Morph morph,
|
||||
required double progress,
|
||||
required double angle,
|
||||
required Color color,
|
||||
required Size size,
|
||||
}) : _morph = morph,
|
||||
_progress = progress,
|
||||
_angle = angle,
|
||||
_preferredSize = size,
|
||||
_color = color,
|
||||
_paint = Paint()
|
||||
..style = PaintingStyle.fill
|
||||
@@ -223,20 +221,38 @@ class _RenderM3ELoadingIndicator extends RenderBox {
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Size _preferredSize;
|
||||
set preferredSize(Size value) {
|
||||
if (_preferredSize == value) return;
|
||||
_preferredSize = size;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
Size computeDryLayout(covariant BoxConstraints constraints) {
|
||||
return constraints.constrain(_preferredSize);
|
||||
}
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
size = constraints.constrainDimensions(40, 40);
|
||||
size = computeDryLayout(constraints);
|
||||
}
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config.role = .loadingSpinner;
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
final width = size.width;
|
||||
final value = size.width / 2;
|
||||
final matrix = Matrix4.identity()
|
||||
..translateByDouble(offset.dx + value, offset.dy + value, 0.0, 1.0)
|
||||
..rotateZ(angle)
|
||||
..translateByDouble(-value, -value, 0.0, 1.0)
|
||||
..scaleByDouble(width, width, width, 1.0);
|
||||
final matrix =
|
||||
Matrix4.translationValues(offset.dx + value, offset.dy + value, 0.0)
|
||||
..rotateZ(angle)
|
||||
..translateByDouble(-value, -value, 0.0, 1.0)
|
||||
..scaleByDouble(width, width, width, 1.0);
|
||||
final path = morph.toPath(progress: progress).transform(matrix.storage);
|
||||
|
||||
context.canvas.drawPath(path, _paint);
|
||||
|
||||
41
lib/common/widgets/loading_widget/morphs.dart
Normal file
41
lib/common/widgets/loading_widget/morphs.dart
Normal file
@@ -0,0 +1,41 @@
|
||||
import 'package:material_new_shapes/material_new_shapes.dart';
|
||||
|
||||
abstract final class Morphs {
|
||||
static List<Morph> buildMorph(
|
||||
List<RoundedPolygon> shapes, {
|
||||
bool loop = true,
|
||||
}) {
|
||||
assert(shapes.length >= 2);
|
||||
return [
|
||||
for (var i = 0; i < shapes.length - 1; i++)
|
||||
Morph(shapes[i], shapes[i + 1]),
|
||||
if (loop) Morph(shapes[shapes.length - 1], shapes[0]),
|
||||
];
|
||||
}
|
||||
|
||||
static final loadingMorphs = buildMorph([
|
||||
MaterialShapes.softBurst,
|
||||
MaterialShapes.cookie9Sided,
|
||||
MaterialShapes.pentagon,
|
||||
MaterialShapes.pill,
|
||||
MaterialShapes.sunny,
|
||||
MaterialShapes.cookie4Sided,
|
||||
MaterialShapes.oval,
|
||||
]);
|
||||
|
||||
// static final refreshMorphs = buildMorph([
|
||||
// MaterialShapes.softBurst,
|
||||
// MaterialShapes.cookie9Sided,
|
||||
// MaterialShapes.gem,
|
||||
// MaterialShapes.flower,
|
||||
// MaterialShapes.sunny,
|
||||
// MaterialShapes.cookie4Sided,
|
||||
// MaterialShapes.oval,
|
||||
// MaterialShapes.cookie12Sided,
|
||||
// ]);
|
||||
|
||||
// static final manualMorph = Morph(
|
||||
// MaterialShapes.circle,
|
||||
// MaterialShapes.softBurst,
|
||||
// );
|
||||
}
|
||||
@@ -57,7 +57,7 @@ class PendantAvatar extends StatelessWidget {
|
||||
final pendantSize = size * 1.75;
|
||||
pendant = Positioned(
|
||||
// -(size * 1.75 - size) / 2
|
||||
top: -0.375 * size + pendentOffset,
|
||||
top: -0.375 * size + pendentOffset / 2,
|
||||
child: IgnorePointer(
|
||||
child: NetworkImgLayer(
|
||||
type: .emote,
|
||||
|
||||
@@ -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,9 @@ 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';
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ 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_reserve/data.dart';
|
||||
@@ -777,4 +778,30 @@ abstract final class DynamicsHttp {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<BubbleData>> 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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -2,7 +2,7 @@ enum AccountType {
|
||||
main('主账号'),
|
||||
heartbeat('记录观看'),
|
||||
recommend('推荐'),
|
||||
video('视频取流')
|
||||
video('视频取流'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,7 +4,7 @@ enum BadgeType {
|
||||
none(),
|
||||
vip('大会员'),
|
||||
person('认证个人', Color(0xFFFFCC00)),
|
||||
institution('认证机构', Colors.lightBlueAccent)
|
||||
institution('认证机构', Colors.lightBlueAccent),
|
||||
;
|
||||
|
||||
final String? desc;
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
|
||||
enum BarHideType with EnumWithLabel {
|
||||
instant('即时'),
|
||||
sync('同步')
|
||||
sync('同步'),
|
||||
;
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
enum DmBlockType {
|
||||
keyword('关键词'),
|
||||
regex('正则'),
|
||||
uid('用户')
|
||||
uid('用户'),
|
||||
;
|
||||
|
||||
final String label;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
enum DynamicBadgeMode {
|
||||
hidden('隐藏'),
|
||||
point('红点'),
|
||||
number('数字')
|
||||
number('数字'),
|
||||
;
|
||||
|
||||
final String desc;
|
||||
|
||||
@@ -3,7 +3,7 @@ enum DynamicsTabType {
|
||||
video('投稿'),
|
||||
pgc('番剧'),
|
||||
article('专栏'),
|
||||
up('UP')
|
||||
up('UP'),
|
||||
;
|
||||
|
||||
final String label;
|
||||
|
||||
@@ -3,7 +3,7 @@ enum UpPanelPosition {
|
||||
leftFixed('左侧常驻'),
|
||||
rightFixed('右侧常驻'),
|
||||
leftDrawer('左侧抽屉'),
|
||||
rightDrawer('右侧抽屉')
|
||||
rightDrawer('右侧抽屉'),
|
||||
;
|
||||
|
||||
final String label;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
enum EpisodeType {
|
||||
part('分P'),
|
||||
season('合集'),
|
||||
pgc('剧集')
|
||||
pgc('剧集'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
enum FavOrderType {
|
||||
mtime('最近收藏'),
|
||||
view('最多播放'),
|
||||
pubtime('最近投稿')
|
||||
pubtime('最近投稿'),
|
||||
;
|
||||
|
||||
final String label;
|
||||
|
||||
@@ -13,7 +13,7 @@ enum FavTabType {
|
||||
article('专栏', FavArticlePage()),
|
||||
note('笔记', FavNotePage()),
|
||||
topic('话题', FavTopicPage()),
|
||||
cheese('课堂', FavCheesePage())
|
||||
cheese('课堂', FavCheesePage()),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
enum FollowOrderType {
|
||||
def('', '最近关注'),
|
||||
attention('attention', '最常访问')
|
||||
attention('attention', '最常访问'),
|
||||
;
|
||||
|
||||
final String type;
|
||||
|
||||
@@ -19,7 +19,7 @@ enum HomeTabType implements EnumWithLabel {
|
||||
hot('热门'),
|
||||
rank('分区'),
|
||||
bangumi('番剧'),
|
||||
cinema('影视')
|
||||
cinema('影视'),
|
||||
;
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
|
||||
enum LaterViewType {
|
||||
all(0, '全部'),
|
||||
// toView(1, '未看'),
|
||||
unfinished(2, '未看完')
|
||||
unfinished(2, '未看完'),
|
||||
// viewed(3, '已看完'),
|
||||
;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -6,7 +6,7 @@ enum ContributeType {
|
||||
season(Api.spaceSeason),
|
||||
series(Api.spaceSeries),
|
||||
bangumi(Api.spaceBangumi),
|
||||
comic(Api.spaceComic)
|
||||
comic(Api.spaceComic),
|
||||
;
|
||||
|
||||
final String api;
|
||||
|
||||
@@ -8,7 +8,7 @@ enum MemberTabType {
|
||||
favorite('收藏'),
|
||||
bangumi('番剧'),
|
||||
cheese('课堂'),
|
||||
shop('小店')
|
||||
shop('小店'),
|
||||
;
|
||||
|
||||
static bool showMemberShop = Pref.showMemberShop;
|
||||
|
||||
@@ -3,7 +3,7 @@ enum MsgUnReadType {
|
||||
reply('回复我的'),
|
||||
at('@我'),
|
||||
like('收到的赞'),
|
||||
sysMsg('系统通知')
|
||||
sysMsg('系统通知'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,7 +19,7 @@ enum RankType {
|
||||
documentary('记录', seasonType: 3),
|
||||
movie('电影', seasonType: 2),
|
||||
tv('剧集', seasonType: 5),
|
||||
variety('综艺', seasonType: 7)
|
||||
variety('综艺', seasonType: 7),
|
||||
;
|
||||
|
||||
final String label;
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
|
||||
enum ReplyOptionType {
|
||||
allow('允许评论'),
|
||||
close('关闭评论'),
|
||||
choose('精选评论')
|
||||
choose('精选评论'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
enum ReplySortType {
|
||||
time('最新评论', '最新'),
|
||||
hot('最热评论', '最热'),
|
||||
select('精选评论', '精选')
|
||||
select('精选评论', '精选'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -18,7 +18,7 @@ enum SearchType {
|
||||
// 用户:bili_user
|
||||
bili_user('用户'),
|
||||
// 专栏:article
|
||||
article('专栏')
|
||||
article('专栏'),
|
||||
;
|
||||
// 相簿:photo
|
||||
// photo
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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('最多喜欢'),
|
||||
|
||||
@@ -6,7 +6,7 @@ enum SettingType {
|
||||
styleSetting('外观设置'),
|
||||
extraSetting('其它设置'),
|
||||
webdavSetting('WebDAV 设置'),
|
||||
about('关于')
|
||||
about('关于'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -2,7 +2,7 @@ enum ActionType {
|
||||
skip('跳过'),
|
||||
mute('静音'),
|
||||
full('整个视频'),
|
||||
poi('精彩时刻')
|
||||
poi('精彩时刻'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -5,7 +5,7 @@ enum SkipType implements EnumWithLabel {
|
||||
skipOnce('跳过一次'),
|
||||
skipManually('手动跳过'),
|
||||
showOnly('仅显示'),
|
||||
disable('禁用')
|
||||
disable('禁用'),
|
||||
;
|
||||
|
||||
@override
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
enum SuperResolutionType with EnumWithLabel {
|
||||
disable('禁用'),
|
||||
efficiency('效率'),
|
||||
quality('画质')
|
||||
quality('画质'),
|
||||
;
|
||||
|
||||
@override
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:material_design_icons_flutter/material_design_icons_flutter.dart
|
||||
enum ThemeType {
|
||||
light('浅色'),
|
||||
dark('深色'),
|
||||
system('跟随系统')
|
||||
system('跟随系统'),
|
||||
;
|
||||
|
||||
final String desc;
|
||||
|
||||
@@ -7,7 +7,7 @@ enum AudioQuality {
|
||||
dolby_30255(30255, '杜比全景声'),
|
||||
k192(30280, '192K'),
|
||||
k132(30232, '132K'),
|
||||
k64(30216, '64K')
|
||||
k64(30216, '64K'),
|
||||
;
|
||||
|
||||
final int code;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -7,7 +7,7 @@ enum LiveQuality {
|
||||
bluRay(400, '蓝光'),
|
||||
superHD(250, '超清'),
|
||||
smooth(150, '高清'),
|
||||
flunt(80, '流畅')
|
||||
flunt(80, '流畅'),
|
||||
;
|
||||
|
||||
final int code;
|
||||
|
||||
@@ -26,7 +26,7 @@ enum SourceType {
|
||||
extraId: 4,
|
||||
playlistSource: PlaylistSource.MEDIA_LIST,
|
||||
),
|
||||
file
|
||||
file,
|
||||
;
|
||||
|
||||
final int? mediaType;
|
||||
|
||||
@@ -2,7 +2,7 @@ enum SubtitlePrefType {
|
||||
off('默认不显示字幕'),
|
||||
on('优先选择非自动生成(ai)字幕'),
|
||||
withoutAi('跳过自动生成(ai)字幕,选择第一个可用字幕'),
|
||||
auto('静音时等同第二项,非静音时等同第三项')
|
||||
auto('静音时等同第二项,非静音时等同第三项'),
|
||||
;
|
||||
|
||||
final String desc;
|
||||
|
||||
@@ -4,7 +4,7 @@ enum VideoDecodeFormatType {
|
||||
DVH1(['dvh1']),
|
||||
AV1(['av01']),
|
||||
HEVC(['hev1', 'hvc1']),
|
||||
AVC(['avc1'])
|
||||
AVC(['avc1']),
|
||||
;
|
||||
|
||||
String get description => name;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -13,7 +13,7 @@ enum VideoType {
|
||||
type: 10,
|
||||
replyType: 33,
|
||||
api: Api.pugvUrl,
|
||||
)
|
||||
),
|
||||
;
|
||||
|
||||
final int type;
|
||||
|
||||
@@ -4,7 +4,7 @@ enum WebviewMenuItem {
|
||||
openInBrowser('浏览器中打开'),
|
||||
clearCache('清除缓存'),
|
||||
resetCookie('重新设置Cookie'),
|
||||
goBack('返回')
|
||||
goBack('返回'),
|
||||
;
|
||||
|
||||
final String title;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
class FollowUpModel {
|
||||
FollowUpModel({
|
||||
this.liveUsers,
|
||||
@@ -49,7 +51,7 @@ class LiveUsers {
|
||||
List<LiveUserItem>? items;
|
||||
|
||||
LiveUsers.fromJson(Map<String, dynamic> json) {
|
||||
count = json['count'] ?? 0;
|
||||
count = Utils.safeToInt(json['count']) ?? 0;
|
||||
group = json['group'];
|
||||
items = (json['items'] as List?)
|
||||
?.map<LiveUserItem>((e) => LiveUserItem.fromJson(e))
|
||||
@@ -63,14 +65,11 @@ class LiveUserItem extends UpItem {
|
||||
int? roomId;
|
||||
String? title;
|
||||
|
||||
LiveUserItem.fromJson(Map<String, dynamic> json)
|
||||
: super(mid: json['mid'] ?? 0) {
|
||||
face = json['face'];
|
||||
LiveUserItem.fromJson(Map<String, dynamic> json) : super.fromJson(json) {
|
||||
isReserveRecall = json['is_reserve_recall'];
|
||||
jumpUrl = json['jump_url'];
|
||||
roomId = json['room_id'];
|
||||
roomId = Utils.safeToInt(json['room_id']);
|
||||
title = json['title'];
|
||||
uname = json['uname'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +89,7 @@ class UpItem {
|
||||
UpItem.fromJson(Map<String, dynamic> json) {
|
||||
face = json['face'];
|
||||
hasUpdate = json['has_update'];
|
||||
mid = json['mid'] ?? 0;
|
||||
mid = Utils.safeToInt(json['mid']) ?? 0;
|
||||
uname = json['uname'];
|
||||
}
|
||||
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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>),
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
15
lib/models_new/bubble/base_info.dart
Normal file
15
lib/models_new/bubble/base_info.dart
Normal file
@@ -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<String, dynamic> json) => BaseInfo(
|
||||
tribeInfo: json['tribee_info'] == null
|
||||
? null
|
||||
: TribeInfo.fromJson(json['tribee_info'] as Map<String, dynamic>),
|
||||
isJoined: json['is_joined'] as bool?,
|
||||
);
|
||||
}
|
||||
13
lib/models_new/bubble/basic_info.dart
Normal file
13
lib/models_new/bubble/basic_info.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class BasicInfo {
|
||||
String? icon;
|
||||
String? title;
|
||||
String? jumpUri;
|
||||
|
||||
BasicInfo({this.icon, this.title, this.jumpUri});
|
||||
|
||||
factory BasicInfo.fromJson(Map<String, dynamic> json) => BasicInfo(
|
||||
icon: json['icon'] as String?,
|
||||
title: json['title'] as String?,
|
||||
jumpUri: json['jump_uri'] as String?,
|
||||
);
|
||||
}
|
||||
13
lib/models_new/bubble/category.dart
Normal file
13
lib/models_new/bubble/category.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:PiliPlus/models_new/bubble/category_list.dart';
|
||||
|
||||
class Category {
|
||||
List<CategoryList>? categoryList;
|
||||
|
||||
Category({this.categoryList});
|
||||
|
||||
factory Category.fromJson(Map<String, dynamic> json) => Category(
|
||||
categoryList: (json['category_list'] as List<dynamic>?)
|
||||
?.map((e) => CategoryList.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
13
lib/models_new/bubble/category_list.dart
Normal file
13
lib/models_new/bubble/category_list.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class CategoryList {
|
||||
String? id;
|
||||
String? name;
|
||||
int? type;
|
||||
|
||||
CategoryList({this.id, this.name, this.type});
|
||||
|
||||
factory CategoryList.fromJson(Map<String, dynamic> json) => CategoryList(
|
||||
id: json['id'] as String?,
|
||||
name: json['name'] as String?,
|
||||
type: json['type'] as int?,
|
||||
);
|
||||
}
|
||||
15
lib/models_new/bubble/content.dart
Normal file
15
lib/models_new/bubble/content.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:PiliPlus/models_new/bubble/dyn_list.dart';
|
||||
|
||||
class Content {
|
||||
String? count;
|
||||
List<DynList>? dynList;
|
||||
|
||||
Content({this.count, this.dynList});
|
||||
|
||||
factory Content.fromJson(Map<String, dynamic> json) => Content(
|
||||
count: json['count'] as String?,
|
||||
dynList: (json['dyn_list'] as List<dynamic>?)
|
||||
?.map((e) => DynList.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
33
lib/models_new/bubble/data.dart
Normal file
33
lib/models_new/bubble/data.dart
Normal file
@@ -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<String, dynamic> json) => BubbleData(
|
||||
baseInfo: json['base_info'] == null
|
||||
? null
|
||||
: BaseInfo.fromJson(json['base_info'] as Map<String, dynamic>),
|
||||
content: json['content'] == null
|
||||
? null
|
||||
: Content.fromJson(json['content'] as Map<String, dynamic>),
|
||||
category: json['category'] == null
|
||||
? null
|
||||
: Category.fromJson(json['category'] as Map<String, dynamic>),
|
||||
sortInfo: json['sort_info'] == null
|
||||
? null
|
||||
: SortInfo.fromJson(json['sort_info'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
21
lib/models_new/bubble/dyn_list.dart
Normal file
21
lib/models_new/bubble/dyn_list.dart
Normal file
@@ -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<String, dynamic> json) => DynList(
|
||||
dynId: json['dyn_id'] as String?,
|
||||
title: json['title'] as String?,
|
||||
meta: json['meta'] == null
|
||||
? null
|
||||
: Meta.fromJson(json['meta'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
20
lib/models_new/bubble/meta.dart
Normal file
20
lib/models_new/bubble/meta.dart
Normal file
@@ -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<String, dynamic> 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?,
|
||||
);
|
||||
}
|
||||
21
lib/models_new/bubble/sort_info.dart
Normal file
21
lib/models_new/bubble/sort_info.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:PiliPlus/models_new/bubble/sort_item.dart';
|
||||
|
||||
class SortInfo {
|
||||
bool? showSort;
|
||||
List<SortItem>? sortItems;
|
||||
int? curSortType;
|
||||
|
||||
SortInfo({
|
||||
this.showSort,
|
||||
this.sortItems,
|
||||
this.curSortType,
|
||||
});
|
||||
|
||||
factory SortInfo.fromJson(Map<String, dynamic> json) => SortInfo(
|
||||
showSort: json['show_sort'] as bool?,
|
||||
sortItems: (json['sort_items'] as List<dynamic>?)
|
||||
?.map((e) => SortItem.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
curSortType: json['cur_sort_type'] as int?,
|
||||
);
|
||||
}
|
||||
11
lib/models_new/bubble/sort_item.dart
Normal file
11
lib/models_new/bubble/sort_item.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
class SortItem {
|
||||
int? sortType;
|
||||
String? text;
|
||||
|
||||
SortItem({this.sortType, this.text});
|
||||
|
||||
factory SortItem.fromJson(Map<String, dynamic> json) => SortItem(
|
||||
sortType: json['sort_type'] as int?,
|
||||
text: json['text'] as String?,
|
||||
);
|
||||
}
|
||||
26
lib/models_new/bubble/tribee_info.dart
Normal file
26
lib/models_new/bubble/tribee_info.dart
Normal file
@@ -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<String, dynamic> 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?,
|
||||
);
|
||||
}
|
||||
@@ -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?,
|
||||
);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user