mirror of
https://github.com/bggRGjQaUbCoE/PiliPlus.git
synced 2026-04-20 03:06:59 +08:00
Compare commits
65 Commits
f825f87dc1
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62c2c081d9 | ||
|
|
82483b33fc | ||
|
|
886c53c7d8 | ||
|
|
f0050dd6e6 | ||
|
|
e6a2f65b4e | ||
|
|
2fc3f9864f | ||
|
|
64c05a1b06 | ||
|
|
7c4e20f96c | ||
|
|
ace286753c | ||
|
|
f0430eba9f | ||
|
|
bbcceb72a7 | ||
|
|
be4fa6ad2c | ||
|
|
50e1f77e10 | ||
|
|
ba56b45038 | ||
|
|
b4b3764e5f | ||
|
|
2220372e4f | ||
|
|
0957dfc66e | ||
|
|
9578f948b4 | ||
|
|
1724f0d202 | ||
|
|
2bebf200df | ||
|
|
fc7fc18b14 | ||
|
|
8f00ca5680 | ||
|
|
236b524445 | ||
|
|
ae59d257c3 | ||
|
|
662ccfcf0a | ||
|
|
b7ab3655c4 | ||
|
|
eda04b32a4 | ||
|
|
9b1ae39922 | ||
|
|
d1497115da | ||
|
|
7f2682bb7b | ||
|
|
d6579b29ae | ||
|
|
8a8aa6c1e0 | ||
|
|
ed66a4655b | ||
|
|
e04affd0fe | ||
|
|
e293083492 | ||
|
|
7f39f36c75 | ||
|
|
565819febe | ||
|
|
af150118a1 | ||
|
|
470e519a2b | ||
|
|
d73588f1fd | ||
|
|
ffbbd8e702 | ||
|
|
a1815c4cc7 | ||
|
|
b9e543f26b | ||
|
|
0788a4de2d | ||
|
|
b0c6e2f5cd | ||
|
|
9489d8a7ca | ||
|
|
aee4424dbf | ||
|
|
96f9972895 | ||
|
|
6ddf282555 | ||
|
|
e98b2b69bb | ||
|
|
448192b635 | ||
|
|
6cda3a1880 | ||
|
|
99128b2641 | ||
|
|
b8098fe067 | ||
|
|
9fef3284db | ||
|
|
f2b0a3a5ed | ||
|
|
3090cfc6f9 | ||
|
|
98ce99202e | ||
|
|
fddf46a90a | ||
|
|
a5231a55b8 | ||
|
|
b8cae015d7 | ||
|
|
3b09534320 | ||
|
|
702cf988d3 | ||
|
|
5586d12b1f | ||
|
|
4683939364 |
2
.github/workflows/ios.yml
vendored
2
.github/workflows/ios.yml
vendored
@@ -13,7 +13,7 @@ on:
|
||||
jobs:
|
||||
build-macos-app:
|
||||
name: Release IOS
|
||||
runs-on: macos-latest
|
||||
runs-on: macos-26
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
@@ -72,5 +72,11 @@ linter:
|
||||
- use_truncating_division
|
||||
- use_string_buffers
|
||||
- unnecessary_statements
|
||||
- unnecessary_nullable_for_final_variable_declarations
|
||||
- tighten_type_of_initializing_formals
|
||||
- prefer_void_to_null
|
||||
- prefer_spread_collections
|
||||
- unnecessary_to_list_in_spreads
|
||||
- prefer_for_elements_to_map_fromIterable
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
|
||||
@@ -18,8 +18,10 @@ android {
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
@@ -62,10 +64,10 @@ android {
|
||||
value = "PiliPlus dev",
|
||||
)
|
||||
}
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
// proguardFiles(
|
||||
// getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
// "proguard-rules.pro"
|
||||
// )
|
||||
}
|
||||
debug {
|
||||
applicationIdSuffix = ".debug"
|
||||
|
||||
@@ -20,7 +20,5 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>13.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -8,6 +8,8 @@ PODS:
|
||||
- Flutter
|
||||
- auto_orientation (0.0.1):
|
||||
- Flutter
|
||||
- battery_plus (1.0.0):
|
||||
- Flutter
|
||||
- chat_bottom_container (0.0.1):
|
||||
- Flutter
|
||||
- connectivity_plus (0.0.1):
|
||||
@@ -68,9 +70,9 @@ PODS:
|
||||
- Flutter
|
||||
- GT3Captcha-iOS
|
||||
- GT3Captcha-iOS (0.15.8.3)
|
||||
- image_cropper (0.0.4):
|
||||
- image_cropper (0.0.5):
|
||||
- Flutter
|
||||
- TOCropViewController (~> 2.8.0)
|
||||
- TOCropViewController (~> 3.1.1)
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- live_photo_maker (0.0.3):
|
||||
@@ -84,9 +86,6 @@ PODS:
|
||||
- OrderedSet (6.0.3)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- saver_gallery (0.0.1):
|
||||
@@ -105,7 +104,7 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SwiftyGif (5.4.5)
|
||||
- TOCropViewController (2.8.0)
|
||||
- TOCropViewController (3.1.1)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- wakelock_plus (0.0.1):
|
||||
@@ -116,6 +115,7 @@ DEPENDENCIES:
|
||||
- audio_service (from `.symlinks/plugins/audio_service/darwin`)
|
||||
- audio_session (from `.symlinks/plugins/audio_session/ios`)
|
||||
- auto_orientation (from `.symlinks/plugins/auto_orientation/ios`)
|
||||
- battery_plus (from `.symlinks/plugins/battery_plus/ios`)
|
||||
- chat_bottom_container (from `.symlinks/plugins/chat_bottom_container/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
@@ -134,7 +134,6 @@ DEPENDENCIES:
|
||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- saver_gallery (from `.symlinks/plugins/saver_gallery/ios`)
|
||||
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||
@@ -163,6 +162,8 @@ EXTERNAL SOURCES:
|
||||
: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:
|
||||
:path: ".symlinks/plugins/chat_bottom_container/ios"
|
||||
connectivity_plus:
|
||||
@@ -199,8 +200,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
saver_gallery:
|
||||
@@ -219,44 +218,44 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 6d01271b3907b0ee7325c5297c75d697c4226c4d
|
||||
audio_service: cab6c1a0eaf01b5a35b567e11fa67d3cc1956910
|
||||
audio_session: 19e9480dbdd4e5f6c4543826b2e8b0e4ab6145fe
|
||||
auto_orientation: 102ed811a5938d52c86520ddd7ecd3a126b5d39d
|
||||
chat_bottom_container: d8b077152c91b0ab90001e900748ea50353a5520
|
||||
connectivity_plus: 2a701ffec2c0ae28a48cf7540e279787e77c447d
|
||||
device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342
|
||||
app_links: a754cbec3c255bd4bbb4d236ecc06f28cd9a7ce8
|
||||
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
|
||||
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
|
||||
auto_orientation: a1600c9ed72e6e96982fb4e1214463343342432a
|
||||
battery_plus: b42253f6d2dde71712f8c36fef456d99121c5977
|
||||
chat_bottom_container: f1eb8323db77a87db50f361142c679f11e892d1b
|
||||
connectivity_plus: cb623214f4e1f6ef8fe7403d580fdad517d2f7dd
|
||||
device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: b159e0c068aef54932bb15dc9fd1571818edaf49
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_mailer: 2ef5a67087bc8c6c4cefd04a178bf1ae2c94cd83
|
||||
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
|
||||
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
|
||||
fluttertoast: 21eecd6935e7064cc1fcb733a4c5a428f3f24f0f
|
||||
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_mailer: 3a8cd4f36c960fb04528d5471097270c19fec1c4
|
||||
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
|
||||
flutter_volume_controller: c2be490cb0487e8b88d0d9fc2b7e1c139a4ebccb
|
||||
fluttertoast: 2c67e14dce98bbdb200df9e1acf610d7a6264ea1
|
||||
gt3_flutter_plugin: 37090e5fa66ff2a52939eb9d208fc36fa49d36e5
|
||||
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
|
||||
image_cropper: b8ef14d3fcff4040b0f9da2ca28d98219a5cba0e
|
||||
image_picker_ios: 4f2f91b01abdb52842a8e277617df877e40f905b
|
||||
live_photo_maker: 7d57bfc70a120b4673c10871f354f4b1b6fde5fd
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
image_cropper: e405d3e44183f8e8edbec2e49b01ff9c819c7ac8
|
||||
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
|
||||
live_photo_maker: 29280ca88323bd5a33aafd00d98624d5cf522176
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
saver_gallery: 76172dc4bf6b40e66d694948ada9ff402304dd87
|
||||
screen_brightness_ios: 6a6f7794b67f07c4f1e24f6374b2d8ad367ffb39
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
saver_gallery: af2d0c762dafda254e0ad025ef0dabd6506cd490
|
||||
screen_brightness_ios: 9953fd7da5bd480f1a93990daeec2eb42d4f3b52
|
||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||
share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f
|
||||
shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888
|
||||
url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa
|
||||
wakelock_plus: 76957ab028e12bfa4e66813c99e46637f367fc7e
|
||||
TOCropViewController: 9002a9b12d8104d7478cdc306d80f0efea7fe2c5
|
||||
url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b
|
||||
wakelock_plus: e29112ab3ef0b318e58cfa5c32326458be66b556
|
||||
|
||||
PODFILE CHECKSUM: f62db4fb414ebdecb264109948f76dfef35fdc3d
|
||||
|
||||
|
||||
56
lib/common/assets.dart
Normal file
56
lib/common/assets.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
abstract final class Assets {
|
||||
static const digitalNum = 'digital_id_num';
|
||||
|
||||
static const logo = 'assets/images/logo/logo.png';
|
||||
static const logo2 = 'assets/images/logo/logo_2.png';
|
||||
static const logoIco = 'assets/images/logo/ico/app_icon.ico';
|
||||
static const logoLarge = 'assets/images/logo/desktop/logo_large.png';
|
||||
|
||||
static const vipIcon = 'assets/images/big-vip.png';
|
||||
static const avatarPlaceHolder = 'assets/images/noface.jpeg';
|
||||
static const loading = 'assets/images/loading.png';
|
||||
static const buffering = 'assets/images/loading.webp';
|
||||
static const play = 'assets/images/play.png';
|
||||
static const topicHeader = 'assets/images/topic-header-bg.png';
|
||||
static const trendingBanner = 'assets/images/trending_banner.png';
|
||||
static const ai = 'assets/images/ai.png';
|
||||
|
||||
static const livingChart = 'assets/images/live.gif';
|
||||
static const livingStatic = 'assets/images/live.png';
|
||||
static const livingRect = 'assets/images/live/live.gif';
|
||||
static const livingBackground = 'assets/images/live/default_bg.webp';
|
||||
|
||||
static const thunder1 = 'assets/images/paycoins/ic_thunder_1.png';
|
||||
static const thunder2 = 'assets/images/paycoins/ic_thunder_2.png';
|
||||
static const thunder3 = 'assets/images/paycoins/ic_thunder_3.png';
|
||||
static const notEnough = 'assets/images/paycoins/ic_22_not_enough_pay.png';
|
||||
static const mario = 'assets/images/paycoins/ic_22_mario.png';
|
||||
static const gunSister = 'assets/images/paycoins/ic_22_gun_sister.png';
|
||||
static const payBox = 'assets/images/paycoins/ic_pay_coins_box.png';
|
||||
static const coinsOne = 'assets/images/paycoins/ic_coins_one.png';
|
||||
static const coinsTwo = 'assets/images/paycoins/ic_coins_two.png';
|
||||
static const left = 'assets/images/paycoins/ic_left.png';
|
||||
static const leftDisable = 'assets/images/paycoins/ic_left_disable.png';
|
||||
static const right = 'assets/images/paycoins/ic_right.png';
|
||||
static const rightDisable = 'assets/images/paycoins/ic_right_disable.png';
|
||||
static const panelClose = 'assets/images/paycoins/ic_panel_close.png';
|
||||
|
||||
static const List<String> mpvAnime4KShaders = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_VL.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_VL.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
];
|
||||
|
||||
static const mpvAnime4KShadersLite = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_M.glsl',
|
||||
'Anime4K_Restore_CNN_S.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_S.glsl',
|
||||
];
|
||||
}
|
||||
@@ -1,30 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract final class StyleString {
|
||||
static const double cardSpace = 8;
|
||||
static const double safeSpace = 12;
|
||||
static const BorderRadius mdRadius = BorderRadius.all(imgRadius);
|
||||
static const Radius imgRadius = Radius.circular(10);
|
||||
static const double aspectRatio = 16 / 10;
|
||||
static const double aspectRatio16x9 = 16 / 9;
|
||||
static const double imgMaxRatio = 3.0;
|
||||
static const bottomSheetRadius = BorderRadius.vertical(
|
||||
top: Radius.circular(18),
|
||||
);
|
||||
static const dialogFixedConstraints = BoxConstraints(
|
||||
minWidth: 420,
|
||||
maxWidth: 420,
|
||||
);
|
||||
static const topBarHeight = 52.0;
|
||||
static const buttonStyle = ButtonStyle(
|
||||
visualDensity: VisualDensity(
|
||||
horizontal: -2,
|
||||
vertical: -1.25,
|
||||
),
|
||||
tapTargetSize: .shrinkWrap,
|
||||
);
|
||||
}
|
||||
|
||||
abstract final class Constants {
|
||||
static const appName = 'PiliPlus';
|
||||
static const sourceCodeUrl = 'https://github.com/bggRGjQaUbCoE/PiliPlus';
|
||||
@@ -68,244 +41,4 @@ abstract final class Constants {
|
||||
|
||||
// 'itemOpusStyle,opusBigCover,onlyfansVote,endFooterHidden,decorationCard,onlyfansAssetsV2,ugcDelete,onlyfansQaCard,editable,opusPrivateVisible,avatarAutoTheme,sunflowerStyle,cardsEnhance,eva3CardOpus,eva3CardVideo,eva3CardComment,eva3CardVote,eva3CardUser'
|
||||
static const dynFeatures = 'itemOpusStyle,listOnlyfans,onlyfansQaCard';
|
||||
|
||||
// 超分辨率滤镜
|
||||
static const List<String> mpvAnime4KShaders = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_VL.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_VL.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
];
|
||||
|
||||
// 超分辨率滤镜 (轻量)
|
||||
static const mpvAnime4KShadersLite = [
|
||||
'Anime4K_Clamp_Highlights.glsl',
|
||||
'Anime4K_Restore_CNN_M.glsl',
|
||||
'Anime4K_Restore_CNN_S.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_M.glsl',
|
||||
'Anime4K_AutoDownscalePre_x2.glsl',
|
||||
'Anime4K_AutoDownscalePre_x4.glsl',
|
||||
'Anime4K_Upscale_CNN_x2_S.glsl',
|
||||
];
|
||||
|
||||
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
||||
static const internationalDialingPrefix = [
|
||||
(id: 1, cname: "中国大陆", countryId: 86),
|
||||
(id: 5, cname: "中国香港特别行政区", countryId: 852),
|
||||
(id: 2, cname: "中国澳门特别行政区", countryId: 853),
|
||||
(id: 3, cname: "中国台湾", countryId: 886),
|
||||
(id: 4, cname: "美国", countryId: 1),
|
||||
(id: 6, cname: "比利时", countryId: 32),
|
||||
(id: 7, cname: "澳大利亚", countryId: 61),
|
||||
(id: 8, cname: "法国", countryId: 33),
|
||||
(id: 9, cname: "加拿大", countryId: 1),
|
||||
(id: 10, cname: "日本", countryId: 81),
|
||||
(id: 11, cname: "新加坡", countryId: 65),
|
||||
(id: 12, cname: "韩国", countryId: 82),
|
||||
(id: 13, cname: "马来西亚", countryId: 60),
|
||||
(id: 14, cname: "英国", countryId: 44),
|
||||
(id: 15, cname: "意大利", countryId: 39),
|
||||
(id: 16, cname: "德国", countryId: 49),
|
||||
(id: 18, cname: "俄罗斯", countryId: 7),
|
||||
(id: 19, cname: "新西兰", countryId: 64),
|
||||
(id: 153, cname: "瓦利斯群岛和富图纳群岛", countryId: 1681),
|
||||
(id: 152, cname: "葡萄牙", countryId: 351),
|
||||
(id: 151, cname: "帕劳", countryId: 680),
|
||||
(id: 150, cname: "诺福克岛", countryId: 672),
|
||||
(id: 149, cname: "挪威", countryId: 47),
|
||||
(id: 148, cname: "纽埃岛", countryId: 683),
|
||||
(id: 147, cname: "尼日利亚", countryId: 234),
|
||||
(id: 146, cname: "尼日尔", countryId: 227),
|
||||
(id: 145, cname: "尼加拉瓜", countryId: 505),
|
||||
(id: 144, cname: "尼泊尔", countryId: 977),
|
||||
(id: 143, cname: "瑙鲁", countryId: 674),
|
||||
(id: 154, cname: "格鲁吉亚", countryId: 995),
|
||||
(id: 155, cname: "瑞典", countryId: 46),
|
||||
(id: 165, cname: "沙特阿拉伯", countryId: 966),
|
||||
(id: 164, cname: "桑给巴尔岛", countryId: 259),
|
||||
(id: 163, cname: "塞舌尔共和国", countryId: 248),
|
||||
(id: 162, cname: "塞浦路斯", countryId: 357),
|
||||
(id: 161, cname: "塞内加尔", countryId: 221),
|
||||
(id: 160, cname: "塞拉利昂", countryId: 232),
|
||||
(id: 159, cname: "萨摩亚,东部", countryId: 684),
|
||||
(id: 158, cname: "萨摩亚,西部", countryId: 685),
|
||||
(id: 157, cname: "萨尔瓦多", countryId: 503),
|
||||
(id: 156, cname: "瑞士", countryId: 41),
|
||||
(id: 166, cname: "圣多美和普林西比", countryId: 239),
|
||||
(id: 142, cname: "塞尔维亚", countryId: 381),
|
||||
(id: 141, cname: "南非", countryId: 27),
|
||||
(id: 128, cname: "毛里塔尼亚", countryId: 222),
|
||||
(id: 127, cname: "毛里求斯", countryId: 230),
|
||||
(id: 126, cname: "马歇尔岛", countryId: 692),
|
||||
(id: 125, cname: "马提尼克岛", countryId: 596),
|
||||
(id: 124, cname: "马其顿", countryId: 389),
|
||||
(id: 123, cname: "马里亚纳岛", countryId: 1670),
|
||||
(id: 122, cname: "马里", countryId: 223),
|
||||
(id: 121, cname: "马拉维", countryId: 265),
|
||||
(id: 120, cname: "马耳他", countryId: 356),
|
||||
(id: 119, cname: "马尔代夫", countryId: 960),
|
||||
(id: 129, cname: "蒙古", countryId: 976),
|
||||
(id: 130, cname: "蒙特塞拉特岛", countryId: 1664),
|
||||
(id: 140, cname: "纳米比亚", countryId: 264),
|
||||
(id: 139, cname: "墨西哥", countryId: 52),
|
||||
(id: 138, cname: "莫桑比克", countryId: 258),
|
||||
(id: 137, cname: "摩纳哥", countryId: 377),
|
||||
(id: 136, cname: "摩洛哥", countryId: 212),
|
||||
(id: 135, cname: "摩尔多瓦", countryId: 373),
|
||||
(id: 134, cname: "缅甸", countryId: 95),
|
||||
(id: 133, cname: "密克罗尼西亚", countryId: 691),
|
||||
(id: 132, cname: "秘鲁", countryId: 51),
|
||||
(id: 131, cname: "孟加拉国", countryId: 880),
|
||||
(id: 118, cname: "马达加斯加", countryId: 261),
|
||||
(id: 167, cname: "圣卢西亚", countryId: 1784),
|
||||
(id: 216, cname: "智利", countryId: 56),
|
||||
(id: 203, cname: "牙买加", countryId: 1876),
|
||||
(id: 202, cname: "叙利亚", countryId: 963),
|
||||
(id: 201, cname: "匈牙利", countryId: 36),
|
||||
(id: 200, cname: "科特迪瓦", countryId: 225),
|
||||
(id: 199, cname: "希腊", countryId: 30),
|
||||
(id: 198, cname: "西班牙", countryId: 34),
|
||||
(id: 197, cname: "乌兹别克斯坦", countryId: 998),
|
||||
(id: 196, cname: "乌拉圭", countryId: 598),
|
||||
(id: 195, cname: "乌克兰", countryId: 380),
|
||||
(id: 194, cname: "乌干达", countryId: 256),
|
||||
(id: 204, cname: "亚美尼亚", countryId: 374),
|
||||
(id: 205, cname: "也门", countryId: 967),
|
||||
(id: 215, cname: "直布罗陀", countryId: 350),
|
||||
(id: 214, cname: "乍得", countryId: 235),
|
||||
(id: 213, cname: "赞比亚", countryId: 260),
|
||||
(id: 212, cname: "越南", countryId: 84),
|
||||
(id: 211, cname: "约旦", countryId: 962),
|
||||
(id: 210, cname: "印尼", countryId: 62),
|
||||
(id: 209, cname: "印度", countryId: 91),
|
||||
(id: 208, cname: "以色列", countryId: 972),
|
||||
(id: 207, cname: "伊朗", countryId: 98),
|
||||
(id: 206, cname: "伊拉克", countryId: 964),
|
||||
(id: 193, cname: "文莱", countryId: 673),
|
||||
(id: 192, cname: "委内瑞拉", countryId: 58),
|
||||
(id: 191, cname: "维珍群岛(英属)", countryId: 1284),
|
||||
(id: 178, cname: "泰国", countryId: 66),
|
||||
(id: 177, cname: "索马里", countryId: 252),
|
||||
(id: 176, cname: "所罗门群岛", countryId: 677),
|
||||
(id: 175, cname: "苏里南", countryId: 597),
|
||||
(id: 174, cname: "苏丹", countryId: 249),
|
||||
(id: 173, cname: "斯威士兰", countryId: 268),
|
||||
(id: 172, cname: "斯洛文尼亚", countryId: 386),
|
||||
(id: 171, cname: "斯洛伐克", countryId: 421),
|
||||
(id: 170, cname: "斯里兰卡", countryId: 94),
|
||||
(id: 169, cname: "圣皮埃尔和密克隆群岛", countryId: 508),
|
||||
(id: 179, cname: "坦桑尼亚", countryId: 255),
|
||||
(id: 180, cname: "汤加", countryId: 676),
|
||||
(id: 190, cname: "维珍群岛(美属)", countryId: 1340),
|
||||
(id: 189, cname: "瓦努阿图", countryId: 678),
|
||||
(id: 188, cname: "托克劳岛", countryId: 690),
|
||||
(id: 187, cname: "土库曼斯坦", countryId: 993),
|
||||
(id: 186, cname: "土耳其", countryId: 90),
|
||||
(id: 185, cname: "图瓦卢", countryId: 688),
|
||||
(id: 184, cname: "突尼斯", countryId: 216),
|
||||
(id: 183, cname: "阿森松岛", countryId: 247),
|
||||
(id: 182, cname: "特立尼达和多巴哥", countryId: 1868),
|
||||
(id: 181, cname: "特克斯和凯科斯", countryId: 1649),
|
||||
(id: 168, cname: "圣马力诺", countryId: 378),
|
||||
(id: 67, cname: "法属圭亚那", countryId: 594),
|
||||
(id: 54, cname: "不丹", countryId: 975),
|
||||
(id: 53, cname: "博茨瓦纳", countryId: 267),
|
||||
(id: 52, cname: "伯利兹", countryId: 501),
|
||||
(id: 51, cname: "玻利维亚", countryId: 591),
|
||||
(id: 50, cname: "波兰", countryId: 48),
|
||||
(id: 49, cname: "波黑", countryId: 387),
|
||||
(id: 48, cname: "波多黎各", countryId: 1787),
|
||||
(id: 47, cname: "冰岛", countryId: 354),
|
||||
(id: 46, cname: "贝宁", countryId: 229),
|
||||
(id: 45, cname: "保加利亚", countryId: 359),
|
||||
(id: 55, cname: "布基纳法索", countryId: 226),
|
||||
(id: 56, cname: "布隆迪", countryId: 257),
|
||||
(id: 66, cname: "法属波利尼西亚", countryId: 689),
|
||||
(id: 65, cname: "法罗岛", countryId: 298),
|
||||
(id: 64, cname: "厄立特里亚", countryId: 291),
|
||||
(id: 63, cname: "厄瓜多尔", countryId: 593),
|
||||
(id: 62, cname: "多米尼加代表", countryId: 1809),
|
||||
(id: 61, cname: "多米尼加", countryId: 1767),
|
||||
(id: 60, cname: "多哥", countryId: 228),
|
||||
(id: 59, cname: "迪戈加西亚岛", countryId: 246),
|
||||
(id: 58, cname: "丹麦", countryId: 45),
|
||||
(id: 57, cname: "赤道几内亚", countryId: 240),
|
||||
(id: 44, cname: "百慕大群岛", countryId: 1441),
|
||||
(id: 43, cname: "白俄罗斯", countryId: 375),
|
||||
(id: 42, cname: "巴西", countryId: 55),
|
||||
(id: 29, cname: "爱尔兰", countryId: 353),
|
||||
(id: 28, cname: "埃塞俄比亚", countryId: 251),
|
||||
(id: 27, cname: "埃及", countryId: 20),
|
||||
(id: 26, cname: "阿塞拜疆", countryId: 994),
|
||||
(id: 25, cname: "阿曼", countryId: 968),
|
||||
(id: 24, cname: "阿联酋", countryId: 971),
|
||||
(id: 23, cname: "阿根廷", countryId: 54),
|
||||
(id: 22, cname: "阿富汗", countryId: 93),
|
||||
(id: 21, cname: "阿尔及利亚", countryId: 213),
|
||||
(id: 20, cname: "阿尔巴尼亚", countryId: 355),
|
||||
(id: 30, cname: "爱沙尼亚", countryId: 372),
|
||||
(id: 31, cname: "安道尔", countryId: 376),
|
||||
(id: 41, cname: "巴拿马", countryId: 507),
|
||||
(id: 40, cname: "巴林", countryId: 973),
|
||||
(id: 39, cname: "巴拉圭", countryId: 595),
|
||||
(id: 38, cname: "巴基斯坦", countryId: 92),
|
||||
(id: 37, cname: "巴哈马群岛", countryId: 1242),
|
||||
(id: 36, cname: "巴布亚新几内亚", countryId: 675),
|
||||
(id: 35, cname: "巴巴多斯", countryId: 1246),
|
||||
(id: 34, cname: "奥地利", countryId: 43),
|
||||
(id: 33, cname: "安提瓜岛和巴布达", countryId: 1268),
|
||||
(id: 32, cname: "安哥拉", countryId: 244),
|
||||
(id: 68, cname: "非洲中部", countryId: 236),
|
||||
(id: 117, cname: "罗马尼亚", countryId: 40),
|
||||
(id: 104, cname: "科威特", countryId: 965),
|
||||
(id: 103, cname: "科摩罗", countryId: 269),
|
||||
(id: 102, cname: "开曼群岛", countryId: 1345),
|
||||
(id: 101, cname: "卡塔尔", countryId: 974),
|
||||
(id: 100, cname: "喀麦隆", countryId: 237),
|
||||
(id: 99, cname: "聚会岛", countryId: 262),
|
||||
(id: 98, cname: "津巴布韦", countryId: 263),
|
||||
(id: 97, cname: "捷克", countryId: 420),
|
||||
(id: 96, cname: "柬埔寨", countryId: 855),
|
||||
(id: 95, cname: "加蓬", countryId: 241),
|
||||
(id: 105, cname: "克罗地亚", countryId: 385),
|
||||
(id: 106, cname: "肯尼亚", countryId: 254),
|
||||
(id: 116, cname: "卢旺达", countryId: 250),
|
||||
(id: 115, cname: "卢森堡", countryId: 352),
|
||||
(id: 114, cname: "利比亚", countryId: 218),
|
||||
(id: 113, cname: "利比里亚", countryId: 231),
|
||||
(id: 112, cname: "立陶宛", countryId: 370),
|
||||
(id: 111, cname: "黎巴嫩", countryId: 961),
|
||||
(id: 110, cname: "老挝", countryId: 856),
|
||||
(id: 109, cname: "莱索托", countryId: 266),
|
||||
(id: 108, cname: "拉脱维亚", countryId: 371),
|
||||
(id: 107, cname: "库克岛", countryId: 682),
|
||||
(id: 94, cname: "加纳", countryId: 233),
|
||||
(id: 93, cname: "几内亚比绍", countryId: 245),
|
||||
(id: 92, cname: "几内亚", countryId: 224),
|
||||
(id: 79, cname: "格林纳达", countryId: 1473),
|
||||
(id: 78, cname: "哥斯达黎加", countryId: 506),
|
||||
(id: 77, cname: "哥伦比亚", countryId: 57),
|
||||
(id: 76, cname: "刚果(金)", countryId: 243),
|
||||
(id: 75, cname: "刚果", countryId: 242),
|
||||
(id: 74, cname: "冈比亚", countryId: 220),
|
||||
(id: 73, cname: "福克兰岛", countryId: 500),
|
||||
(id: 72, cname: "佛得角", countryId: 238),
|
||||
(id: 71, cname: "芬兰", countryId: 358),
|
||||
(id: 70, cname: "斐济", countryId: 679),
|
||||
(id: 80, cname: "格陵兰岛", countryId: 299),
|
||||
(id: 81, cname: "古巴", countryId: 53),
|
||||
(id: 91, cname: "吉尔吉斯斯坦", countryId: 996),
|
||||
(id: 90, cname: "吉布提", countryId: 253),
|
||||
(id: 89, cname: "基里巴斯", countryId: 686),
|
||||
(id: 88, cname: "维克岛", countryId: 1808),
|
||||
(id: 87, cname: "洪都拉斯", countryId: 504),
|
||||
(id: 86, cname: "荷兰", countryId: 31),
|
||||
(id: 85, cname: "朝鲜", countryId: 850),
|
||||
(id: 84, cname: "海地", countryId: 509),
|
||||
(id: 83, cname: "关岛", countryId: 1671),
|
||||
(id: 82, cname: "瓜德罗普岛", countryId: 590),
|
||||
(id: 69, cname: "菲律宾", countryId: 63),
|
||||
];
|
||||
}
|
||||
|
||||
220
lib/common/dial_prefix.dart
Normal file
220
lib/common/dial_prefix.dart
Normal file
@@ -0,0 +1,220 @@
|
||||
abstract final class Login {
|
||||
//内容来自 https://passport.bilibili.com/web/generic/country/list
|
||||
static const dialPrefix = [
|
||||
(id: 1, cname: "中国大陆", countryId: 86),
|
||||
(id: 5, cname: "中国香港特别行政区", countryId: 852),
|
||||
(id: 2, cname: "中国澳门特别行政区", countryId: 853),
|
||||
(id: 3, cname: "中国台湾", countryId: 886),
|
||||
(id: 4, cname: "美国", countryId: 1),
|
||||
(id: 6, cname: "比利时", countryId: 32),
|
||||
(id: 7, cname: "澳大利亚", countryId: 61),
|
||||
(id: 8, cname: "法国", countryId: 33),
|
||||
(id: 9, cname: "加拿大", countryId: 1),
|
||||
(id: 10, cname: "日本", countryId: 81),
|
||||
(id: 11, cname: "新加坡", countryId: 65),
|
||||
(id: 12, cname: "韩国", countryId: 82),
|
||||
(id: 13, cname: "马来西亚", countryId: 60),
|
||||
(id: 14, cname: "英国", countryId: 44),
|
||||
(id: 15, cname: "意大利", countryId: 39),
|
||||
(id: 16, cname: "德国", countryId: 49),
|
||||
(id: 18, cname: "俄罗斯", countryId: 7),
|
||||
(id: 19, cname: "新西兰", countryId: 64),
|
||||
(id: 153, cname: "瓦利斯群岛和富图纳群岛", countryId: 1681),
|
||||
(id: 152, cname: "葡萄牙", countryId: 351),
|
||||
(id: 151, cname: "帕劳", countryId: 680),
|
||||
(id: 150, cname: "诺福克岛", countryId: 672),
|
||||
(id: 149, cname: "挪威", countryId: 47),
|
||||
(id: 148, cname: "纽埃岛", countryId: 683),
|
||||
(id: 147, cname: "尼日利亚", countryId: 234),
|
||||
(id: 146, cname: "尼日尔", countryId: 227),
|
||||
(id: 145, cname: "尼加拉瓜", countryId: 505),
|
||||
(id: 144, cname: "尼泊尔", countryId: 977),
|
||||
(id: 143, cname: "瑙鲁", countryId: 674),
|
||||
(id: 154, cname: "格鲁吉亚", countryId: 995),
|
||||
(id: 155, cname: "瑞典", countryId: 46),
|
||||
(id: 165, cname: "沙特阿拉伯", countryId: 966),
|
||||
(id: 164, cname: "桑给巴尔岛", countryId: 259),
|
||||
(id: 163, cname: "塞舌尔共和国", countryId: 248),
|
||||
(id: 162, cname: "塞浦路斯", countryId: 357),
|
||||
(id: 161, cname: "塞内加尔", countryId: 221),
|
||||
(id: 160, cname: "塞拉利昂", countryId: 232),
|
||||
(id: 159, cname: "萨摩亚,东部", countryId: 684),
|
||||
(id: 158, cname: "萨摩亚,西部", countryId: 685),
|
||||
(id: 157, cname: "萨尔瓦多", countryId: 503),
|
||||
(id: 156, cname: "瑞士", countryId: 41),
|
||||
(id: 166, cname: "圣多美和普林西比", countryId: 239),
|
||||
(id: 142, cname: "塞尔维亚", countryId: 381),
|
||||
(id: 141, cname: "南非", countryId: 27),
|
||||
(id: 128, cname: "毛里塔尼亚", countryId: 222),
|
||||
(id: 127, cname: "毛里求斯", countryId: 230),
|
||||
(id: 126, cname: "马歇尔岛", countryId: 692),
|
||||
(id: 125, cname: "马提尼克岛", countryId: 596),
|
||||
(id: 124, cname: "马其顿", countryId: 389),
|
||||
(id: 123, cname: "马里亚纳岛", countryId: 1670),
|
||||
(id: 122, cname: "马里", countryId: 223),
|
||||
(id: 121, cname: "马拉维", countryId: 265),
|
||||
(id: 120, cname: "马耳他", countryId: 356),
|
||||
(id: 119, cname: "马尔代夫", countryId: 960),
|
||||
(id: 129, cname: "蒙古", countryId: 976),
|
||||
(id: 130, cname: "蒙特塞拉特岛", countryId: 1664),
|
||||
(id: 140, cname: "纳米比亚", countryId: 264),
|
||||
(id: 139, cname: "墨西哥", countryId: 52),
|
||||
(id: 138, cname: "莫桑比克", countryId: 258),
|
||||
(id: 137, cname: "摩纳哥", countryId: 377),
|
||||
(id: 136, cname: "摩洛哥", countryId: 212),
|
||||
(id: 135, cname: "摩尔多瓦", countryId: 373),
|
||||
(id: 134, cname: "缅甸", countryId: 95),
|
||||
(id: 133, cname: "密克罗尼西亚", countryId: 691),
|
||||
(id: 132, cname: "秘鲁", countryId: 51),
|
||||
(id: 131, cname: "孟加拉国", countryId: 880),
|
||||
(id: 118, cname: "马达加斯加", countryId: 261),
|
||||
(id: 167, cname: "圣卢西亚", countryId: 1784),
|
||||
(id: 216, cname: "智利", countryId: 56),
|
||||
(id: 203, cname: "牙买加", countryId: 1876),
|
||||
(id: 202, cname: "叙利亚", countryId: 963),
|
||||
(id: 201, cname: "匈牙利", countryId: 36),
|
||||
(id: 200, cname: "科特迪瓦", countryId: 225),
|
||||
(id: 199, cname: "希腊", countryId: 30),
|
||||
(id: 198, cname: "西班牙", countryId: 34),
|
||||
(id: 197, cname: "乌兹别克斯坦", countryId: 998),
|
||||
(id: 196, cname: "乌拉圭", countryId: 598),
|
||||
(id: 195, cname: "乌克兰", countryId: 380),
|
||||
(id: 194, cname: "乌干达", countryId: 256),
|
||||
(id: 204, cname: "亚美尼亚", countryId: 374),
|
||||
(id: 205, cname: "也门", countryId: 967),
|
||||
(id: 215, cname: "直布罗陀", countryId: 350),
|
||||
(id: 214, cname: "乍得", countryId: 235),
|
||||
(id: 213, cname: "赞比亚", countryId: 260),
|
||||
(id: 212, cname: "越南", countryId: 84),
|
||||
(id: 211, cname: "约旦", countryId: 962),
|
||||
(id: 210, cname: "印尼", countryId: 62),
|
||||
(id: 209, cname: "印度", countryId: 91),
|
||||
(id: 208, cname: "以色列", countryId: 972),
|
||||
(id: 207, cname: "伊朗", countryId: 98),
|
||||
(id: 206, cname: "伊拉克", countryId: 964),
|
||||
(id: 193, cname: "文莱", countryId: 673),
|
||||
(id: 192, cname: "委内瑞拉", countryId: 58),
|
||||
(id: 191, cname: "维珍群岛(英属)", countryId: 1284),
|
||||
(id: 178, cname: "泰国", countryId: 66),
|
||||
(id: 177, cname: "索马里", countryId: 252),
|
||||
(id: 176, cname: "所罗门群岛", countryId: 677),
|
||||
(id: 175, cname: "苏里南", countryId: 597),
|
||||
(id: 174, cname: "苏丹", countryId: 249),
|
||||
(id: 173, cname: "斯威士兰", countryId: 268),
|
||||
(id: 172, cname: "斯洛文尼亚", countryId: 386),
|
||||
(id: 171, cname: "斯洛伐克", countryId: 421),
|
||||
(id: 170, cname: "斯里兰卡", countryId: 94),
|
||||
(id: 169, cname: "圣皮埃尔和密克隆群岛", countryId: 508),
|
||||
(id: 179, cname: "坦桑尼亚", countryId: 255),
|
||||
(id: 180, cname: "汤加", countryId: 676),
|
||||
(id: 190, cname: "维珍群岛(美属)", countryId: 1340),
|
||||
(id: 189, cname: "瓦努阿图", countryId: 678),
|
||||
(id: 188, cname: "托克劳岛", countryId: 690),
|
||||
(id: 187, cname: "土库曼斯坦", countryId: 993),
|
||||
(id: 186, cname: "土耳其", countryId: 90),
|
||||
(id: 185, cname: "图瓦卢", countryId: 688),
|
||||
(id: 184, cname: "突尼斯", countryId: 216),
|
||||
(id: 183, cname: "阿森松岛", countryId: 247),
|
||||
(id: 182, cname: "特立尼达和多巴哥", countryId: 1868),
|
||||
(id: 181, cname: "特克斯和凯科斯", countryId: 1649),
|
||||
(id: 168, cname: "圣马力诺", countryId: 378),
|
||||
(id: 67, cname: "法属圭亚那", countryId: 594),
|
||||
(id: 54, cname: "不丹", countryId: 975),
|
||||
(id: 53, cname: "博茨瓦纳", countryId: 267),
|
||||
(id: 52, cname: "伯利兹", countryId: 501),
|
||||
(id: 51, cname: "玻利维亚", countryId: 591),
|
||||
(id: 50, cname: "波兰", countryId: 48),
|
||||
(id: 49, cname: "波黑", countryId: 387),
|
||||
(id: 48, cname: "波多黎各", countryId: 1787),
|
||||
(id: 47, cname: "冰岛", countryId: 354),
|
||||
(id: 46, cname: "贝宁", countryId: 229),
|
||||
(id: 45, cname: "保加利亚", countryId: 359),
|
||||
(id: 55, cname: "布基纳法索", countryId: 226),
|
||||
(id: 56, cname: "布隆迪", countryId: 257),
|
||||
(id: 66, cname: "法属波利尼西亚", countryId: 689),
|
||||
(id: 65, cname: "法罗岛", countryId: 298),
|
||||
(id: 64, cname: "厄立特里亚", countryId: 291),
|
||||
(id: 63, cname: "厄瓜多尔", countryId: 593),
|
||||
(id: 62, cname: "多米尼加代表", countryId: 1809),
|
||||
(id: 61, cname: "多米尼加", countryId: 1767),
|
||||
(id: 60, cname: "多哥", countryId: 228),
|
||||
(id: 59, cname: "迪戈加西亚岛", countryId: 246),
|
||||
(id: 58, cname: "丹麦", countryId: 45),
|
||||
(id: 57, cname: "赤道几内亚", countryId: 240),
|
||||
(id: 44, cname: "百慕大群岛", countryId: 1441),
|
||||
(id: 43, cname: "白俄罗斯", countryId: 375),
|
||||
(id: 42, cname: "巴西", countryId: 55),
|
||||
(id: 29, cname: "爱尔兰", countryId: 353),
|
||||
(id: 28, cname: "埃塞俄比亚", countryId: 251),
|
||||
(id: 27, cname: "埃及", countryId: 20),
|
||||
(id: 26, cname: "阿塞拜疆", countryId: 994),
|
||||
(id: 25, cname: "阿曼", countryId: 968),
|
||||
(id: 24, cname: "阿联酋", countryId: 971),
|
||||
(id: 23, cname: "阿根廷", countryId: 54),
|
||||
(id: 22, cname: "阿富汗", countryId: 93),
|
||||
(id: 21, cname: "阿尔及利亚", countryId: 213),
|
||||
(id: 20, cname: "阿尔巴尼亚", countryId: 355),
|
||||
(id: 30, cname: "爱沙尼亚", countryId: 372),
|
||||
(id: 31, cname: "安道尔", countryId: 376),
|
||||
(id: 41, cname: "巴拿马", countryId: 507),
|
||||
(id: 40, cname: "巴林", countryId: 973),
|
||||
(id: 39, cname: "巴拉圭", countryId: 595),
|
||||
(id: 38, cname: "巴基斯坦", countryId: 92),
|
||||
(id: 37, cname: "巴哈马群岛", countryId: 1242),
|
||||
(id: 36, cname: "巴布亚新几内亚", countryId: 675),
|
||||
(id: 35, cname: "巴巴多斯", countryId: 1246),
|
||||
(id: 34, cname: "奥地利", countryId: 43),
|
||||
(id: 33, cname: "安提瓜岛和巴布达", countryId: 1268),
|
||||
(id: 32, cname: "安哥拉", countryId: 244),
|
||||
(id: 68, cname: "非洲中部", countryId: 236),
|
||||
(id: 117, cname: "罗马尼亚", countryId: 40),
|
||||
(id: 104, cname: "科威特", countryId: 965),
|
||||
(id: 103, cname: "科摩罗", countryId: 269),
|
||||
(id: 102, cname: "开曼群岛", countryId: 1345),
|
||||
(id: 101, cname: "卡塔尔", countryId: 974),
|
||||
(id: 100, cname: "喀麦隆", countryId: 237),
|
||||
(id: 99, cname: "聚会岛", countryId: 262),
|
||||
(id: 98, cname: "津巴布韦", countryId: 263),
|
||||
(id: 97, cname: "捷克", countryId: 420),
|
||||
(id: 96, cname: "柬埔寨", countryId: 855),
|
||||
(id: 95, cname: "加蓬", countryId: 241),
|
||||
(id: 105, cname: "克罗地亚", countryId: 385),
|
||||
(id: 106, cname: "肯尼亚", countryId: 254),
|
||||
(id: 116, cname: "卢旺达", countryId: 250),
|
||||
(id: 115, cname: "卢森堡", countryId: 352),
|
||||
(id: 114, cname: "利比亚", countryId: 218),
|
||||
(id: 113, cname: "利比里亚", countryId: 231),
|
||||
(id: 112, cname: "立陶宛", countryId: 370),
|
||||
(id: 111, cname: "黎巴嫩", countryId: 961),
|
||||
(id: 110, cname: "老挝", countryId: 856),
|
||||
(id: 109, cname: "莱索托", countryId: 266),
|
||||
(id: 108, cname: "拉脱维亚", countryId: 371),
|
||||
(id: 107, cname: "库克岛", countryId: 682),
|
||||
(id: 94, cname: "加纳", countryId: 233),
|
||||
(id: 93, cname: "几内亚比绍", countryId: 245),
|
||||
(id: 92, cname: "几内亚", countryId: 224),
|
||||
(id: 79, cname: "格林纳达", countryId: 1473),
|
||||
(id: 78, cname: "哥斯达黎加", countryId: 506),
|
||||
(id: 77, cname: "哥伦比亚", countryId: 57),
|
||||
(id: 76, cname: "刚果(金)", countryId: 243),
|
||||
(id: 75, cname: "刚果", countryId: 242),
|
||||
(id: 74, cname: "冈比亚", countryId: 220),
|
||||
(id: 73, cname: "福克兰岛", countryId: 500),
|
||||
(id: 72, cname: "佛得角", countryId: 238),
|
||||
(id: 71, cname: "芬兰", countryId: 358),
|
||||
(id: 70, cname: "斐济", countryId: 679),
|
||||
(id: 80, cname: "格陵兰岛", countryId: 299),
|
||||
(id: 81, cname: "古巴", countryId: 53),
|
||||
(id: 91, cname: "吉尔吉斯斯坦", countryId: 996),
|
||||
(id: 90, cname: "吉布提", countryId: 253),
|
||||
(id: 89, cname: "基里巴斯", countryId: 686),
|
||||
(id: 88, cname: "维克岛", countryId: 1808),
|
||||
(id: 87, cname: "洪都拉斯", countryId: 504),
|
||||
(id: 86, cname: "荷兰", countryId: 31),
|
||||
(id: 85, cname: "朝鲜", countryId: 850),
|
||||
(id: 84, cname: "海地", countryId: 509),
|
||||
(id: 83, cname: "关岛", countryId: 1671),
|
||||
(id: 82, cname: "瓜德罗普岛", countryId: 590),
|
||||
(id: 69, cname: "菲律宾", countryId: 63),
|
||||
];
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/skeleton.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
|
||||
import 'package:flutter/material.dart' hide LayoutBuilder;
|
||||
|
||||
@@ -12,7 +12,7 @@ class FavPgcItemSkeleton extends StatelessWidget {
|
||||
return Skeleton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
horizontal: Style.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/skeleton.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MediaPgcSkeleton extends StatefulWidget {
|
||||
@@ -15,11 +15,9 @@ class _MediaPgcSkeletonState extends State<MediaPgcSkeleton> {
|
||||
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||
return Skeleton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.safeSpace,
|
||||
7,
|
||||
StyleString.safeSpace,
|
||||
7,
|
||||
padding: const .symmetric(
|
||||
horizontal: Style.safeSpace,
|
||||
vertical: 7,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/skeleton.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class VideoCardHSkeleton extends StatelessWidget {
|
||||
@@ -10,25 +10,25 @@ class VideoCardHSkeleton extends StatelessWidget {
|
||||
final color = Theme.of(context).colorScheme.onInverseSurface;
|
||||
return Skeleton(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
padding: const .symmetric(
|
||||
horizontal: Style.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
aspectRatio: Style.aspectRatio,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: StyleString.mdRadius,
|
||||
borderRadius: Style.mdRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 4, 6, 4),
|
||||
padding: const .fromLTRB(10, 4, 6, 4),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/skeleton/skeleton.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class VideoCardVSkeleton extends StatelessWidget {
|
||||
@@ -13,11 +13,11 @@ class VideoCardVSkeleton extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
aspectRatio: Style.aspectRatio,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
borderRadius: StyleString.mdRadius,
|
||||
borderRadius: Style.mdRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
24
lib/common/style.dart
Normal file
24
lib/common/style.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart'
|
||||
show BorderRadius, Radius, BoxConstraints, ButtonStyle, VisualDensity;
|
||||
|
||||
abstract final class Style {
|
||||
static const cardSpace = 8.0;
|
||||
static const safeSpace = 12.0;
|
||||
static const mdRadius = BorderRadius.all(imgRadius);
|
||||
static const imgRadius = Radius.circular(10);
|
||||
static const aspectRatio = 16 / 10;
|
||||
static const aspectRatio16x9 = 16 / 9;
|
||||
static const imgMaxRatio = 2.6;
|
||||
static const bottomSheetRadius = BorderRadius.vertical(
|
||||
top: Radius.circular(18),
|
||||
);
|
||||
static const dialogFixedConstraints = BoxConstraints(
|
||||
minWidth: 420,
|
||||
maxWidth: 420,
|
||||
);
|
||||
static const topBarHeight = 52.0;
|
||||
static const buttonStyle = ButtonStyle(
|
||||
visualDensity: VisualDensity(horizontal: -2, vertical: -1.25),
|
||||
tapTargetSize: .shrinkWrap,
|
||||
);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ColorPalette extends StatelessWidget {
|
||||
@@ -62,7 +62,7 @@ class ColorPalette extends StatelessWidget {
|
||||
padding: const EdgeInsets.all(6),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.onInverseSurface,
|
||||
borderRadius: StyleString.mdRadius,
|
||||
borderRadius: Style.mdRadius,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart' show RenderProxyBox;
|
||||
import 'package:flutter/rendering.dart' show RenderProxyBox, BoxHitTestResult;
|
||||
|
||||
class CustomHeightWidget extends SingleChildRenderObjectWidget {
|
||||
const CustomHeightWidget({
|
||||
super.key,
|
||||
required this.height,
|
||||
this.height,
|
||||
this.offset = .zero,
|
||||
required super.child,
|
||||
required Widget super.child,
|
||||
});
|
||||
|
||||
final double height;
|
||||
final double? height;
|
||||
|
||||
final Offset offset;
|
||||
|
||||
@@ -34,14 +34,14 @@ class CustomHeightWidget extends SingleChildRenderObjectWidget {
|
||||
|
||||
class RenderCustomHeightWidget extends RenderProxyBox {
|
||||
RenderCustomHeightWidget({
|
||||
required double height,
|
||||
double? height,
|
||||
required Offset offset,
|
||||
}) : _height = height,
|
||||
_offset = offset;
|
||||
|
||||
double _height;
|
||||
double get height => _height;
|
||||
set height(double value) {
|
||||
double? _height;
|
||||
double? get height => _height;
|
||||
set height(double? value) {
|
||||
if (_height == value) return;
|
||||
_height = value;
|
||||
markNeedsLayout();
|
||||
@@ -57,12 +57,40 @@ class RenderCustomHeightWidget extends RenderProxyBox {
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
child!.layout(constraints);
|
||||
size = constraints.constrainDimensions(constraints.maxWidth, height);
|
||||
if (height != null) {
|
||||
child!.layout(constraints.copyWith(maxHeight: .infinity));
|
||||
size = constraints.constrainDimensions(constraints.maxWidth, height!);
|
||||
} else {
|
||||
child!.layout(
|
||||
constraints.copyWith(maxHeight: .infinity),
|
||||
parentUsesSize: true,
|
||||
);
|
||||
size = constraints.constrainDimensions(
|
||||
constraints.maxWidth,
|
||||
child!.size.height,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
context.paintChild(child!, offset + _offset);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hitTest(BoxHitTestResult result, {required Offset position}) {
|
||||
return result.addWithPaintOffset(
|
||||
offset: _offset,
|
||||
position: position,
|
||||
hitTest: (BoxHitTestResult result, Offset transformed) {
|
||||
assert(transformed == position - _offset);
|
||||
return child!.hitTest(result, position: transformed);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void applyPaintTransform(covariant RenderObject child, Matrix4 transform) {
|
||||
transform.translateByDouble(_offset.dx, _offset.dy, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)),
|
||||
],
|
||||
|
||||
@@ -3,21 +3,16 @@ import 'package:get/get.dart';
|
||||
|
||||
Future<bool> showConfirmDialog({
|
||||
required BuildContext context,
|
||||
required String title,
|
||||
Object? content,
|
||||
required Widget title,
|
||||
Widget? content,
|
||||
// @Deprecated('use `bool result = await showConfirmDialog()` instead')
|
||||
VoidCallback? onConfirm,
|
||||
}) async {
|
||||
assert(content is String? || content is Widget);
|
||||
return await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(title),
|
||||
content: content is String
|
||||
? Text(content)
|
||||
: content is Widget
|
||||
? content
|
||||
: null,
|
||||
title: title,
|
||||
content: content,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
|
||||
277
lib/common/widgets/dialog/export_import.dart
Normal file
277
lib/common/widgets/dialog/export_import.dart
Normal file
@@ -0,0 +1,277 @@
|
||||
import 'dart:async' show FutureOr;
|
||||
import 'dart:convert' show utf8, jsonDecode;
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/utils/extension/context_ext.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show Clipboard;
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||
import 'package:intl/intl.dart' show DateFormat;
|
||||
import 'package:re_highlight/languages/json.dart';
|
||||
import 'package:re_highlight/re_highlight.dart';
|
||||
import 'package:re_highlight/styles/base16/github.dart';
|
||||
import 'package:re_highlight/styles/github-dark.dart';
|
||||
|
||||
void exportToClipBoard({
|
||||
required ValueGetter<String> onExport,
|
||||
}) {
|
||||
Utils.copyText(onExport());
|
||||
}
|
||||
|
||||
void exportToLocalFile({
|
||||
required ValueGetter<String> onExport,
|
||||
required ValueGetter<String> localFileName,
|
||||
}) {
|
||||
final res = utf8.encode(onExport());
|
||||
Utils.saveBytes2File(
|
||||
name:
|
||||
'piliplus_${localFileName()}_'
|
||||
'${DateFormat('yyyyMMddHHmmss').format(DateTime.now())}.json',
|
||||
bytes: res,
|
||||
allowedExtensions: const ['json'],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> importFromClipBoard<T>(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required ValueGetter<String> onExport,
|
||||
required FutureOr<void> Function(T json) onImport,
|
||||
bool showConfirmDialog = true,
|
||||
}) async {
|
||||
final data = await Clipboard.getData('text/plain');
|
||||
if (data?.text?.isNotEmpty != true) {
|
||||
SmartDialog.showToast('剪贴板无数据');
|
||||
return;
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
final text = data!.text!;
|
||||
late final T json;
|
||||
late final String formatText;
|
||||
try {
|
||||
json = jsonDecode(text);
|
||||
formatText = Utils.jsonEncoder.convert(json);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('解析json失败:$e');
|
||||
return;
|
||||
}
|
||||
bool? executeImport;
|
||||
if (showConfirmDialog) {
|
||||
final highlight = Highlight()..registerLanguage('json', langJson);
|
||||
final result = highlight.highlight(
|
||||
code: formatText,
|
||||
language: 'json',
|
||||
);
|
||||
late TextSpanRenderer renderer;
|
||||
bool? isDarkMode;
|
||||
executeImport = await showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final isDark = context.isDarkMode;
|
||||
if (isDark != isDarkMode) {
|
||||
isDarkMode = isDark;
|
||||
renderer = TextSpanRenderer(
|
||||
const TextStyle(),
|
||||
isDark ? githubDarkTheme : githubTheme,
|
||||
);
|
||||
result.render(renderer);
|
||||
}
|
||||
return AlertDialog(
|
||||
title: Text('是否导入如下$title?'),
|
||||
content: SingleChildScrollView(
|
||||
child: Text.rich(renderer.span!),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Get.back(result: true),
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
} else {
|
||||
executeImport = true;
|
||||
}
|
||||
if (executeImport ?? false) {
|
||||
try {
|
||||
await onImport(json);
|
||||
SmartDialog.showToast('导入成功');
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> importFromLocalFile<T>({
|
||||
required FutureOr<void> Function(T json) onImport,
|
||||
}) async {
|
||||
final result = await FilePicker.pickFiles();
|
||||
if (result != null) {
|
||||
final path = result.files.first.path;
|
||||
if (path != null) {
|
||||
final data = await File(path).readAsString();
|
||||
late final T json;
|
||||
try {
|
||||
json = jsonDecode(data);
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('解析json失败:$e');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await onImport(json);
|
||||
SmartDialog.showToast('导入成功');
|
||||
} catch (e) {
|
||||
SmartDialog.showToast('导入失败:$e');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void importFromInput<T>(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required FutureOr<void> Function(T json) onImport,
|
||||
}) {
|
||||
final key = GlobalKey<FormFieldState<String>>();
|
||||
late T json;
|
||||
String? forceErrorText;
|
||||
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text('输入$title'),
|
||||
constraints: Style.dialogFixedConstraints,
|
||||
content: TextFormField(
|
||||
key: key,
|
||||
minLines: 4,
|
||||
maxLines: 12,
|
||||
autofocus: true,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
errorMaxLines: 3,
|
||||
),
|
||||
validator: (value) {
|
||||
if (forceErrorText != null) return forceErrorText;
|
||||
try {
|
||||
json = jsonDecode(value!) as T;
|
||||
return null;
|
||||
} catch (e) {
|
||||
if (e is FormatException) {}
|
||||
return '解析json失败:$e';
|
||||
}
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: Get.back,
|
||||
child: Text(
|
||||
'取消',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
if (key.currentState?.validate() == true) {
|
||||
try {
|
||||
await onImport(json);
|
||||
Get.back();
|
||||
SmartDialog.showToast('导入成功');
|
||||
return;
|
||||
} catch (e) {
|
||||
forceErrorText = '导入失败:$e';
|
||||
}
|
||||
key.currentState?.validate();
|
||||
forceErrorText = null;
|
||||
}
|
||||
},
|
||||
child: const Text('确定'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> showImportExportDialog<T>(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required ValueGetter<String> onExport,
|
||||
required FutureOr<void> Function(T json) onImport,
|
||||
required ValueGetter<String> localFileName,
|
||||
}) => showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
const style = TextStyle(fontSize: 15);
|
||||
return SimpleDialog(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
title: Text('导入/导出$title'),
|
||||
children: [
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('导出至剪贴板', style: style),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
exportToClipBoard(onExport: onExport);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('导出文件至本地', style: style),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
exportToLocalFile(onExport: onExport, localFileName: localFileName);
|
||||
},
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
color: ColorScheme.of(context).outline.withValues(alpha: 0.1),
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('输入', style: style),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
importFromInput<T>(context, title: title, onImport: onImport);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('从剪贴板导入', style: style),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
importFromClipBoard<T>(
|
||||
context,
|
||||
title: title,
|
||||
onExport: onExport,
|
||||
onImport: onImport,
|
||||
);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
dense: true,
|
||||
title: const Text('从本地文件导入', style: style),
|
||||
onTap: () {
|
||||
Get.back();
|
||||
importFromLocalFile<T>(onImport: onImport);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -20,20 +20,16 @@ class DisabledIcon extends SingleChildRenderObjectWidget {
|
||||
final StrokeCap strokeCap;
|
||||
final double lineLengthScale;
|
||||
|
||||
Icon? get _icon => child is Icon ? child as Icon : null;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
late final iconTheme = IconTheme.of(context);
|
||||
final icon = _icon;
|
||||
return RenderMaskedIcon(
|
||||
disable: disable,
|
||||
iconSize:
|
||||
iconSize ??
|
||||
(child is Icon ? (child as Icon).size : null) ??
|
||||
iconTheme.size ??
|
||||
24.0,
|
||||
color:
|
||||
color ??
|
||||
(child is Icon ? (child as Icon).color : null) ??
|
||||
iconTheme.color!,
|
||||
iconSize: iconSize ?? icon?.size ?? iconTheme.size ?? 24.0,
|
||||
color: color ?? icon?.color ?? iconTheme.color!,
|
||||
strokeCap: strokeCap,
|
||||
lineLengthScale: lineLengthScale,
|
||||
);
|
||||
@@ -42,17 +38,11 @@ class DisabledIcon extends SingleChildRenderObjectWidget {
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderMaskedIcon renderObject) {
|
||||
late final iconTheme = IconTheme.of(context);
|
||||
final icon = _icon;
|
||||
renderObject
|
||||
..disable = disable
|
||||
..iconSize =
|
||||
iconSize ??
|
||||
(child is Icon ? (child as Icon).size : null) ??
|
||||
iconTheme.size ??
|
||||
24.0
|
||||
..color =
|
||||
color ??
|
||||
(child is Icon ? (child as Icon).color : null) ??
|
||||
iconTheme.color!
|
||||
..iconSize = iconSize ?? icon?.size ?? iconTheme.size ?? 24.0
|
||||
..color = color ?? icon?.color ?? iconTheme.color!
|
||||
..strokeCap = strokeCap
|
||||
..lineLengthScale = lineLengthScale;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/widgets/custom_height_widget.dart';
|
||||
import 'package:PiliPlus/common/widgets/dynamic_sliver_app_bar/rendering/sliver_persistent_header.dart';
|
||||
import 'package:PiliPlus/common/widgets/dynamic_sliver_app_bar/sliver_persistent_header.dart';
|
||||
import 'package:PiliPlus/common/widgets/only_layout_widget.dart'
|
||||
@@ -24,6 +25,7 @@ import 'package:PiliPlus/common/widgets/only_layout_widget.dart'
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart'
|
||||
hide SliverPersistentHeader, SliverPersistentHeaderDelegate;
|
||||
import 'package:flutter/rendering.dart' show RenderOpacity, OpacityLayer;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
/// ref [SliverAppBar]
|
||||
@@ -133,12 +135,10 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
title: effectiveTitle,
|
||||
actions: actions,
|
||||
automaticallyImplyActions: automaticallyImplyActions,
|
||||
flexibleSpace: maxExtent == .infinity
|
||||
? flexibleSpace
|
||||
: IgnorePointer(
|
||||
ignoring: isScrolledUnder,
|
||||
child: FlexibleSpaceBar(background: flexibleSpace),
|
||||
),
|
||||
flexibleSpace: IgnorePointer(
|
||||
ignoring: isScrolledUnder,
|
||||
child: DynamicFlexibleSpaceBar(background: flexibleSpace),
|
||||
),
|
||||
bottom: bottom,
|
||||
elevation: isScrolledUnder ? elevation : 0.0,
|
||||
scrolledUnderElevation: scrolledUnderElevation,
|
||||
@@ -202,7 +202,7 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
class DynamicSliverAppBar extends StatefulWidget {
|
||||
class DynamicSliverAppBar extends StatelessWidget {
|
||||
const DynamicSliverAppBar.medium({
|
||||
super.key,
|
||||
this.leading,
|
||||
@@ -297,60 +297,196 @@ class DynamicSliverAppBar extends StatefulWidget {
|
||||
|
||||
final EdgeInsetsGeometry? actionsPadding;
|
||||
|
||||
@override
|
||||
State<DynamicSliverAppBar> createState() => _DynamicSliverAppBarState();
|
||||
}
|
||||
|
||||
class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double bottomHeight = widget.bottom?.preferredSize.height ?? 0.0;
|
||||
final double topPadding = widget.primary
|
||||
final double bottomHeight = bottom?.preferredSize.height ?? 0.0;
|
||||
final double topPadding = primary
|
||||
? MediaQuery.viewPaddingOf(context).top
|
||||
: 0.0;
|
||||
final double effectiveCollapsedHeight =
|
||||
topPadding + kToolbarHeight + bottomHeight + 1;
|
||||
|
||||
return MediaQuery.removePadding(
|
||||
context: context,
|
||||
removeBottom: true,
|
||||
child: SliverPinnedHeader(
|
||||
onPerformLayout: widget.onPerformLayout,
|
||||
delegate: _SliverAppBarDelegate(
|
||||
leading: widget.leading,
|
||||
automaticallyImplyLeading: widget.automaticallyImplyLeading,
|
||||
title: widget.title,
|
||||
actions: widget.actions,
|
||||
automaticallyImplyActions: widget.automaticallyImplyActions,
|
||||
flexibleSpace: widget.flexibleSpace,
|
||||
bottom: widget.bottom,
|
||||
elevation: widget.elevation,
|
||||
scrolledUnderElevation: widget.scrolledUnderElevation,
|
||||
shadowColor: widget.shadowColor,
|
||||
surfaceTintColor: widget.surfaceTintColor,
|
||||
forceElevated: widget.forceElevated,
|
||||
backgroundColor: widget.backgroundColor,
|
||||
foregroundColor: widget.foregroundColor,
|
||||
iconTheme: widget.iconTheme,
|
||||
actionsIconTheme: widget.actionsIconTheme,
|
||||
primary: widget.primary,
|
||||
centerTitle: widget.centerTitle,
|
||||
excludeHeaderSemantics: widget.excludeHeaderSemantics,
|
||||
titleSpacing: widget.titleSpacing,
|
||||
collapsedHeight: effectiveCollapsedHeight,
|
||||
topPadding: topPadding,
|
||||
shape: widget.shape,
|
||||
toolbarHeight: kToolbarHeight,
|
||||
leadingWidth: widget.leadingWidth,
|
||||
toolbarTextStyle: widget.toolbarTextStyle,
|
||||
titleTextStyle: widget.titleTextStyle,
|
||||
systemOverlayStyle: widget.systemOverlayStyle,
|
||||
forceMaterialTransparency: widget.forceMaterialTransparency,
|
||||
useDefaultSemanticsOrder: widget.useDefaultSemanticsOrder,
|
||||
clipBehavior: widget.clipBehavior,
|
||||
actionsPadding: widget.actionsPadding,
|
||||
return SliverPinnedHeader(
|
||||
onPerformLayout: onPerformLayout,
|
||||
delegate: _SliverAppBarDelegate(
|
||||
leading: leading,
|
||||
automaticallyImplyLeading: automaticallyImplyLeading,
|
||||
title: title,
|
||||
actions: actions,
|
||||
automaticallyImplyActions: automaticallyImplyActions,
|
||||
flexibleSpace: flexibleSpace,
|
||||
bottom: bottom,
|
||||
elevation: elevation,
|
||||
scrolledUnderElevation: scrolledUnderElevation,
|
||||
shadowColor: shadowColor,
|
||||
surfaceTintColor: surfaceTintColor,
|
||||
forceElevated: forceElevated,
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: foregroundColor,
|
||||
iconTheme: iconTheme,
|
||||
actionsIconTheme: actionsIconTheme,
|
||||
primary: primary,
|
||||
centerTitle: centerTitle,
|
||||
excludeHeaderSemantics: excludeHeaderSemantics,
|
||||
titleSpacing: titleSpacing,
|
||||
collapsedHeight: effectiveCollapsedHeight,
|
||||
topPadding: topPadding,
|
||||
shape: shape,
|
||||
toolbarHeight: kToolbarHeight,
|
||||
leadingWidth: leadingWidth,
|
||||
toolbarTextStyle: toolbarTextStyle,
|
||||
titleTextStyle: titleTextStyle,
|
||||
systemOverlayStyle: systemOverlayStyle,
|
||||
forceMaterialTransparency: forceMaterialTransparency,
|
||||
useDefaultSemanticsOrder: useDefaultSemanticsOrder,
|
||||
clipBehavior: clipBehavior,
|
||||
actionsPadding: actionsPadding,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// ref [FlexibleSpaceBar]
|
||||
class DynamicFlexibleSpaceBar extends StatelessWidget {
|
||||
const DynamicFlexibleSpaceBar({
|
||||
super.key,
|
||||
required this.background,
|
||||
this.collapseMode = CollapseMode.parallax,
|
||||
});
|
||||
|
||||
final Widget background;
|
||||
|
||||
final CollapseMode collapseMode;
|
||||
|
||||
static double _getCollapsePadding(
|
||||
CollapseMode collapseMode,
|
||||
double t,
|
||||
FlexibleSpaceBarSettings settings,
|
||||
) {
|
||||
switch (collapseMode) {
|
||||
case CollapseMode.pin:
|
||||
return -(settings.maxExtent - settings.currentExtent);
|
||||
case CollapseMode.none:
|
||||
return 0.0;
|
||||
case CollapseMode.parallax:
|
||||
final double deltaExtent = settings.maxExtent - settings.minExtent;
|
||||
return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).transform(t);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final FlexibleSpaceBarSettings settings = context
|
||||
.dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>()!;
|
||||
|
||||
double? height;
|
||||
final double opacity;
|
||||
final double topPadding;
|
||||
if (settings.maxExtent == .infinity) {
|
||||
opacity = 1.0;
|
||||
topPadding = 0.0;
|
||||
} else {
|
||||
height = settings.maxExtent;
|
||||
|
||||
final double deltaExtent = settings.maxExtent - settings.minExtent;
|
||||
|
||||
// 0.0 -> Expanded
|
||||
// 1.0 -> Collapsed to toolbar
|
||||
final double t = clampDouble(
|
||||
1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
|
||||
final double fadeStart = math.max(
|
||||
0.0,
|
||||
1.0 - kToolbarHeight / deltaExtent,
|
||||
);
|
||||
const fadeEnd = 1.0;
|
||||
assert(fadeStart <= fadeEnd);
|
||||
// If the min and max extent are the same, the app bar cannot collapse
|
||||
// and the content should be visible, so opacity = 1.
|
||||
opacity = settings.maxExtent == settings.minExtent
|
||||
? 1.0
|
||||
: 1.0 - Interval(fadeStart, fadeEnd).transform(t);
|
||||
|
||||
topPadding = _getCollapsePadding(collapseMode, t, settings);
|
||||
}
|
||||
|
||||
return ClipRect(
|
||||
child: CustomHeightWidget(
|
||||
height: height,
|
||||
offset: Offset(0.0, topPadding),
|
||||
child: _FlexibleSpaceHeaderOpacity(
|
||||
// IOS is relying on this semantics node to correctly traverse
|
||||
// through the app bar when it is collapsed.
|
||||
alwaysIncludeSemantics: true,
|
||||
opacity: opacity,
|
||||
child: background,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// [_FlexibleSpaceHeaderOpacity]
|
||||
class _FlexibleSpaceHeaderOpacity extends SingleChildRenderObjectWidget {
|
||||
const _FlexibleSpaceHeaderOpacity({
|
||||
required this.opacity,
|
||||
required super.child,
|
||||
required this.alwaysIncludeSemantics,
|
||||
});
|
||||
|
||||
final double opacity;
|
||||
final bool alwaysIncludeSemantics;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderFlexibleSpaceHeaderOpacity(
|
||||
opacity: opacity,
|
||||
alwaysIncludeSemantics: alwaysIncludeSemantics,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
covariant _RenderFlexibleSpaceHeaderOpacity renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..alwaysIncludeSemantics = alwaysIncludeSemantics
|
||||
..opacity = opacity;
|
||||
}
|
||||
}
|
||||
|
||||
class _RenderFlexibleSpaceHeaderOpacity extends RenderOpacity {
|
||||
_RenderFlexibleSpaceHeaderOpacity({
|
||||
super.opacity,
|
||||
super.alwaysIncludeSemantics,
|
||||
});
|
||||
|
||||
@override
|
||||
bool get isRepaintBoundary => false;
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (child == null) {
|
||||
return;
|
||||
}
|
||||
if ((opacity * 255).roundToDouble() <= 0) {
|
||||
layer = null;
|
||||
return;
|
||||
}
|
||||
assert(needsCompositing);
|
||||
layer = context.pushOpacity(
|
||||
offset,
|
||||
(opacity * 255).round(),
|
||||
super.paint,
|
||||
oldLayer: layer as OpacityLayer?,
|
||||
);
|
||||
assert(() {
|
||||
layer!.debugCreator = debugCreator;
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide PopScope;
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||
|
||||
abstract class PopScopeState<T extends StatefulWidget> extends State<T>
|
||||
implements PopEntry<T> {
|
||||
implements PopEntry<Object> {
|
||||
ModalRoute<dynamic>? _route;
|
||||
|
||||
@override
|
||||
@@ -14,31 +16,60 @@ abstract class PopScopeState<T extends StatefulWidget> extends State<T>
|
||||
@override
|
||||
late final ValueNotifier<bool> canPopNotifier;
|
||||
|
||||
void initCanPopNotifier() {
|
||||
canPopNotifier = ValueNotifier<bool>(false);
|
||||
}
|
||||
bool get initCanPop => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initCanPopNotifier();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context);
|
||||
if (nextRoute != _route) {
|
||||
_route?.unregisterPopEntry(this);
|
||||
_route = nextRoute;
|
||||
_route?.registerPopEntry(this);
|
||||
}
|
||||
canPopNotifier = ValueNotifier<bool>(initCanPop);
|
||||
_route = (Get.routing.route as ModalRoute)..registerPopEntry(this);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_route?.unregisterPopEntry(this);
|
||||
_route = null;
|
||||
canPopNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: camel_case_types
|
||||
typedef popScope = PopScope;
|
||||
|
||||
class PopScope extends StatefulWidget {
|
||||
const PopScope({
|
||||
super.key,
|
||||
required this.child,
|
||||
this.canPop = true,
|
||||
required this.onPopInvokedWithResult,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
final PopInvokedWithResultCallback<Object> onPopInvokedWithResult;
|
||||
|
||||
final bool canPop;
|
||||
|
||||
@override
|
||||
State<PopScope> createState() => _PopScopeState();
|
||||
}
|
||||
|
||||
class _PopScopeState<T extends PopScope> extends PopScopeState<T> {
|
||||
@override
|
||||
bool get initCanPop => widget.canPop;
|
||||
|
||||
@override
|
||||
void onPopInvokedWithResult(bool didPop, Object? result) {
|
||||
widget.onPopInvokedWithResult(didPop, result);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(T oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
canPopNotifier.value = widget.canPop;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => widget.child;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,13 @@ import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'
|
||||
import 'package:flutter/foundation.dart' show clampDouble;
|
||||
import 'package:flutter/material.dart' hide RefreshIndicator;
|
||||
|
||||
/// The distance from the child's top or bottom [edgeOffset] where
|
||||
/// the refresh indicator will settle. During the drag that exposes the refresh
|
||||
/// indicator, its actual displacement may significantly exceed this value.
|
||||
///
|
||||
/// In most cases, [displacement] distance starts counting from the parent's
|
||||
/// edges. However, if [edgeOffset] is larger than zero then the [displacement]
|
||||
/// value is calculated from that offset instead of the parent's edge.
|
||||
double displacement = Pref.refreshDisplacement;
|
||||
|
||||
// The over-scroll distance that moves the indicator to its maximum
|
||||
@@ -125,7 +132,6 @@ class RefreshIndicator extends StatefulWidget {
|
||||
/// The [semanticsValue] may be used to specify progress on the widget.
|
||||
const RefreshIndicator({
|
||||
super.key,
|
||||
this.displacement = 40.0,
|
||||
this.edgeOffset = 0.0,
|
||||
required this.onRefresh,
|
||||
this.color,
|
||||
@@ -145,15 +151,6 @@ class RefreshIndicator extends StatefulWidget {
|
||||
/// Typically a [ListView] or [CustomScrollView].
|
||||
final Widget child;
|
||||
|
||||
/// The distance from the child's top or bottom [edgeOffset] where
|
||||
/// the refresh indicator will settle. During the drag that exposes the refresh
|
||||
/// indicator, its actual displacement may significantly exceed this value.
|
||||
///
|
||||
/// In most cases, [displacement] distance starts counting from the parent's
|
||||
/// edges. However, if [edgeOffset] is larger than zero then the [displacement]
|
||||
/// value is calculated from that offset instead of the parent's edge.
|
||||
final double displacement;
|
||||
|
||||
/// The offset where [RefreshProgressIndicator] starts to appear on drag start.
|
||||
///
|
||||
/// Depending whether the indicator is showing on the top or bottom, the value
|
||||
@@ -220,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,
|
||||
@@ -277,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.
|
||||
@@ -522,7 +520,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
|
||||
axisAlignment: 1.0,
|
||||
sizeFactor: _positionFactor, // This is what brings it down.
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: widget.displacement),
|
||||
padding: EdgeInsets.only(top: displacement),
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ScaleTransition(
|
||||
@@ -561,28 +559,56 @@ 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,
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
}
|
||||
|
||||
Widget refreshIndicator({
|
||||
required RefreshCallback onRefresh,
|
||||
required Widget child,
|
||||
bool isClampingScrollPhysics = false,
|
||||
}) {
|
||||
return RefreshIndicator(
|
||||
displacement: displacement,
|
||||
onRefresh: onRefresh,
|
||||
isClampingScrollPhysics: isClampingScrollPhysics,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
// ignore: camel_case_types
|
||||
typedef refreshIndicator = RefreshIndicator;
|
||||
|
||||
class RefreshScrollBehavior extends CustomScrollBehavior {
|
||||
const RefreshScrollBehavior(
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
class CustomHorizontalDragGestureRecognizer
|
||||
extends HorizontalDragGestureRecognizer {
|
||||
CustomHorizontalDragGestureRecognizer({
|
||||
super.debugOwner,
|
||||
super.supportedDevices,
|
||||
super.allowedButtonsFilter,
|
||||
});
|
||||
|
||||
mixin InitialPositionMixin on GestureRecognizer {
|
||||
Offset? _initialPosition;
|
||||
Offset? get initialPosition => _initialPosition;
|
||||
|
||||
@override
|
||||
DeviceGestureSettings get gestureSettings => _gestureSettings;
|
||||
final _gestureSettings = DeviceGestureSettings(touchSlop: touchSlopH);
|
||||
|
||||
@override
|
||||
void addAllowedPointer(PointerDownEvent event) {
|
||||
super.addAllowedPointer(event);
|
||||
_initialPosition = event.position;
|
||||
}
|
||||
}
|
||||
|
||||
class CustomHorizontalDragGestureRecognizer
|
||||
extends HorizontalDragGestureRecognizer
|
||||
with InitialPositionMixin {
|
||||
CustomHorizontalDragGestureRecognizer({
|
||||
super.debugOwner,
|
||||
super.supportedDevices,
|
||||
super.allowedButtonsFilter,
|
||||
});
|
||||
|
||||
@override
|
||||
DeviceGestureSettings get gestureSettings => _gestureSettings;
|
||||
final _gestureSettings = DeviceGestureSettings(touchSlop: touchSlopH);
|
||||
|
||||
@override
|
||||
bool hasSufficientGlobalDistanceToAccept(
|
||||
@@ -41,7 +44,7 @@ double touchSlopH = Pref.touchSlopH;
|
||||
|
||||
bool _computeHitSlop(
|
||||
double globalDistanceMoved,
|
||||
DeviceGestureSettings? settings,
|
||||
DeviceGestureSettings settings,
|
||||
PointerDeviceKind kind,
|
||||
Offset? initialPosition,
|
||||
Offset lastPosition,
|
||||
@@ -53,10 +56,10 @@ bool _computeHitSlop(
|
||||
case PointerDeviceKind.invertedStylus:
|
||||
case PointerDeviceKind.unknown:
|
||||
case PointerDeviceKind.touch:
|
||||
return globalDistanceMoved > touchSlopH &&
|
||||
return globalDistanceMoved > settings.touchSlop! &&
|
||||
_calc(initialPosition!, lastPosition);
|
||||
case PointerDeviceKind.trackpad:
|
||||
return globalDistanceMoved > (settings?.touchSlop ?? kTouchSlop);
|
||||
return globalDistanceMoved > settings.touchSlop!;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
|
||||
mixin ImageGestureRecognizerMixin on GestureRecognizer {
|
||||
int? _pointer;
|
||||
|
||||
@override
|
||||
void addPointer(PointerDownEvent event) {
|
||||
void addPointer(PointerDownEvent event, {bool isPointerAllowed = true}) {
|
||||
if (_pointer == event.pointer) {
|
||||
return;
|
||||
}
|
||||
_pointer = event.pointer;
|
||||
super.addPointer(event);
|
||||
if (isPointerAllowed) {
|
||||
super.addPointer(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef IsBoundaryAllowed =
|
||||
bool Function(Offset? initialPosition, OffsetPair lastPosition);
|
||||
|
||||
class ImageHorizontalDragGestureRecognizer
|
||||
extends CustomHorizontalDragGestureRecognizer
|
||||
with ImageGestureRecognizerMixin {
|
||||
@@ -26,23 +26,62 @@ class ImageHorizontalDragGestureRecognizer
|
||||
super.allowedButtonsFilter,
|
||||
});
|
||||
|
||||
IsBoundaryAllowed? isBoundaryAllowed;
|
||||
static final double _touchSlop = PlatformUtils.isDesktop
|
||||
? kPrecisePointerHitSlop
|
||||
: 3.0;
|
||||
|
||||
@override
|
||||
bool hasSufficientGlobalDistanceToAccept(
|
||||
PointerDeviceKind pointerDeviceKind,
|
||||
double? deviceTouchSlop,
|
||||
) {
|
||||
return super.hasSufficientGlobalDistanceToAccept(
|
||||
pointerDeviceKind,
|
||||
deviceTouchSlop,
|
||||
) &&
|
||||
(isBoundaryAllowed?.call(initialPosition, lastPosition) ?? true);
|
||||
DeviceGestureSettings get gestureSettings => _gestureSettings;
|
||||
final _gestureSettings = DeviceGestureSettings(touchSlop: _touchSlop);
|
||||
|
||||
bool isAtLeftEdge = false;
|
||||
bool isAtRightEdge = false;
|
||||
|
||||
void setAtBothEdges() {
|
||||
isAtLeftEdge = isAtRightEdge = true;
|
||||
}
|
||||
|
||||
bool _isEdgeAllowed(double dx) {
|
||||
if ((initialPosition!.dx - dx).abs() < _touchSlop) return true;
|
||||
if (isAtLeftEdge) {
|
||||
if (isAtRightEdge) {
|
||||
return _hasAcceptedOrChecked = true;
|
||||
}
|
||||
_hasAcceptedOrChecked = true;
|
||||
return initialPosition!.dx < dx;
|
||||
} else if (isAtRightEdge) {
|
||||
_hasAcceptedOrChecked = true;
|
||||
return initialPosition!.dx > dx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
isBoundaryAllowed = null;
|
||||
super.dispose();
|
||||
void handleEvent(PointerEvent event) {
|
||||
if (!_hasAcceptedOrChecked &&
|
||||
event is PointerMoveEvent &&
|
||||
_pointer == event.pointer) {
|
||||
if (!_isEdgeAllowed(event.position.dx)) {
|
||||
rejectGesture(event.pointer);
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.handleEvent(event);
|
||||
}
|
||||
|
||||
bool _hasAcceptedOrChecked = false;
|
||||
|
||||
@override
|
||||
void acceptGesture(int pointer) {
|
||||
_hasAcceptedOrChecked = true;
|
||||
super.acceptGesture(pointer);
|
||||
}
|
||||
|
||||
@override
|
||||
void stopTrackingPointer(int pointer) {
|
||||
_hasAcceptedOrChecked = false;
|
||||
isAtLeftEdge = false;
|
||||
isAtRightEdge = false;
|
||||
super.stopTrackingPointer(pointer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/http/user.dart';
|
||||
@@ -22,10 +22,10 @@ void imageSaveDialog({
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
width: imgWidth,
|
||||
margin: const .symmetric(horizontal: StyleString.safeSpace),
|
||||
margin: const .symmetric(horizontal: Style.safeSpace),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.surface,
|
||||
borderRadius: StyleString.mdRadius,
|
||||
borderRadius: Style.mdRadius,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -39,8 +39,8 @@ void imageSaveDialog({
|
||||
src: cover,
|
||||
quality: 100,
|
||||
width: imgWidth,
|
||||
height: imgWidth / StyleString.aspectRatio16x9,
|
||||
borderRadius: const .vertical(top: StyleString.imgRadius),
|
||||
height: imgWidth / Style.aspectRatio16x9,
|
||||
borderRadius: const .vertical(top: Style.imgRadius),
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/assets.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/models/common/image_type.dart';
|
||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||
import 'package:PiliPlus/utils/image_utils.dart';
|
||||
@@ -16,7 +17,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
this.fadeOutDuration = const Duration(milliseconds: 120),
|
||||
this.fadeInDuration = const Duration(milliseconds: 120),
|
||||
this.quality = 1,
|
||||
this.borderRadius = StyleString.mdRadius,
|
||||
this.borderRadius = Style.mdRadius,
|
||||
this.getPlaceHolder,
|
||||
this.fit = .cover,
|
||||
this.alignment = .center,
|
||||
@@ -108,7 +109,7 @@ class NetworkImgLayer extends StatelessWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Image.asset(
|
||||
isAvatar ? 'assets/images/noface.jpeg' : 'assets/images/loading.png',
|
||||
isAvatar ? Assets.avatarPlaceHolder : Assets.loading,
|
||||
width: width,
|
||||
height: height,
|
||||
cacheWidth: width.cacheSize(context),
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
import 'dart:collection' show HashSet;
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart' show StyleString;
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_grid/image_grid_view.dart'
|
||||
show ImageModel;
|
||||
import 'package:flutter/foundation.dart' show kDebugMode;
|
||||
@@ -505,7 +505,7 @@ class ImageGridRenderObjectElement extends RenderObjectElement {
|
||||
if (width != 1) {
|
||||
imageWidth = math.min(imageWidth, width.toDouble());
|
||||
}
|
||||
imageHeight = imageWidth * math.min(ratioHW, StyleString.imgMaxRatio);
|
||||
imageHeight = imageWidth * math.min(ratioHW, Style.imgMaxRatio);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
|
||||
import 'dart:io' show Platform;
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/assets.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_grid/image_grid_builder.dart';
|
||||
@@ -33,7 +34,7 @@ import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show HapticFeedback;
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:get/get_navigation/get_navigation.dart';
|
||||
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||
|
||||
class ImageModel {
|
||||
ImageModel({
|
||||
@@ -53,8 +54,7 @@ class ImageModel {
|
||||
bool? _isLongPic;
|
||||
bool? _isLivePhoto;
|
||||
|
||||
bool get isLongPic =>
|
||||
_isLongPic ??= (height / width) > StyleString.imgMaxRatio;
|
||||
bool get isLongPic => _isLongPic ??= (height / width) > Style.imgMaxRatio;
|
||||
bool get isLivePhoto =>
|
||||
_isLivePhoto ??= enableLivePhoto && liveUrl?.isNotEmpty == true;
|
||||
|
||||
@@ -74,14 +74,14 @@ class ImageGridView extends StatelessWidget {
|
||||
final bool fullScreen;
|
||||
|
||||
static bool horizontalPreview = Pref.horizontalPreview;
|
||||
static const _routes = ['/videoV', '/dynamicDetail'];
|
||||
static final _regex = RegExp(r'/videoV|/dynamicDetail$|/articlePage');
|
||||
|
||||
void _onTap(BuildContext context, int index) {
|
||||
final imgList = picArr.map(
|
||||
(item) {
|
||||
bool isLive = item.isLivePhoto;
|
||||
return SourceModel(
|
||||
sourceType: isLive ? SourceType.livePhoto : SourceType.networkImage,
|
||||
sourceType: isLive ? .livePhoto : .networkImage,
|
||||
url: item.url,
|
||||
liveUrl: isLive ? item.liveUrl : null,
|
||||
width: isLive ? item.width.toInt() : null,
|
||||
@@ -92,7 +92,7 @@ class ImageGridView extends StatelessWidget {
|
||||
).toList();
|
||||
if (horizontalPreview &&
|
||||
!fullScreen &&
|
||||
_routes.contains(Get.currentRoute) &&
|
||||
Get.currentRoute.startsWith(_regex) &&
|
||||
!context.mediaQuerySize.isPortrait) {
|
||||
final scaffoldState = Scaffold.maybeOf(context);
|
||||
if (scaffoldState != null) {
|
||||
@@ -108,6 +108,7 @@ class ImageGridView extends StatelessWidget {
|
||||
PageUtils.imageView(
|
||||
initialPage: index,
|
||||
imgList: imgList,
|
||||
tag: hashCode.toString(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -115,9 +116,9 @@ class ImageGridView extends StatelessWidget {
|
||||
int col,
|
||||
int length,
|
||||
int index, {
|
||||
Radius r = StyleString.imgRadius,
|
||||
Radius r = Style.imgRadius,
|
||||
}) {
|
||||
if (length == 1) return StyleString.mdRadius;
|
||||
if (length == 1) return Style.mdRadius;
|
||||
|
||||
final bool hasUp = index - col >= 0;
|
||||
final bool hasDown = index + col < length;
|
||||
@@ -212,7 +213,7 @@ class ImageGridView extends StatelessWidget {
|
||||
).colorScheme.onInverseSurface.withValues(alpha: 0.4),
|
||||
),
|
||||
child: Image.asset(
|
||||
'assets/images/loading.png',
|
||||
Assets.loading,
|
||||
width: width,
|
||||
height: height,
|
||||
cacheWidth: width.cacheSize(context),
|
||||
@@ -255,7 +256,7 @@ class ImageGridView extends StatelessWidget {
|
||||
);
|
||||
if (!item.isLongPic) {
|
||||
child = Hero(
|
||||
tag: item.url,
|
||||
tag: '${item.url}$hashCode',
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ class GalleryViewer extends StatefulWidget {
|
||||
required this.quality,
|
||||
required this.sources,
|
||||
this.initIndex = 0,
|
||||
this.onPageChanged,
|
||||
this.tag = '',
|
||||
});
|
||||
|
||||
final double minScale;
|
||||
@@ -61,6 +63,8 @@ class GalleryViewer extends StatefulWidget {
|
||||
final int quality;
|
||||
final List<SourceModel> sources;
|
||||
final int initIndex;
|
||||
final ValueChanged<int>? onPageChanged;
|
||||
final String tag;
|
||||
|
||||
@override
|
||||
State<GalleryViewer> createState() => _GalleryViewerState();
|
||||
@@ -346,6 +350,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
_player?.pause();
|
||||
_playIfNeeded(widget.sources[index]);
|
||||
_currIndex.value = index;
|
||||
widget.onPageChanged?.call(index);
|
||||
}
|
||||
|
||||
late final ValueChanged<int>? _onChangePage = widget.sources.length == 1
|
||||
@@ -469,7 +474,7 @@ class _GalleryViewerState extends State<GalleryViewer>
|
||||
: const SizedBox.shrink(),
|
||||
);
|
||||
}
|
||||
return Hero(tag: item.url, child: child);
|
||||
return Hero(tag: '${item.url}${widget.tag}', child: child);
|
||||
}
|
||||
|
||||
void _onTap() {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import 'dart:io' show File;
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart';
|
||||
import 'package:PiliPlus/common/widgets/image_viewer/viewer.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -603,7 +603,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
|
||||
final imgHeight = _imageInfo!.image.height.toDouble();
|
||||
final imgRatio = imgHeight / imgWidth;
|
||||
isLongPic =
|
||||
imgRatio > StyleString.imgMaxRatio &&
|
||||
imgRatio > Style.imgMaxRatio &&
|
||||
imgHeight > widget.containerSize.height;
|
||||
if (isLongPic) {
|
||||
final compatWidth = math.min(650.0, widget.containerSize.width);
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
* along with PiliPlus. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import 'dart:io' show Platform;
|
||||
import 'dart:math' show pi;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/semantics.dart' show SemanticsConfiguration;
|
||||
|
||||
///
|
||||
/// created by dom on 2026/02/14
|
||||
@@ -74,6 +74,7 @@ class RenderLoadingIndicator extends RenderBox {
|
||||
if (_progress == value) return;
|
||||
_progress = value;
|
||||
markNeedsPaint();
|
||||
markNeedsSemanticsUpdate();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -95,53 +96,39 @@ class RenderLoadingIndicator extends RenderBox {
|
||||
final radius = size.width / 2 - strokeWidth;
|
||||
final center = size.center(.zero);
|
||||
|
||||
// TODO: remove
|
||||
// https://github.com/flutter/flutter/issues/182708
|
||||
// https://github.com/flutter/flutter/issues/183083
|
||||
if (Platform.isIOS) {
|
||||
context.canvas
|
||||
..drawCircle(
|
||||
center,
|
||||
radius,
|
||||
paint..color = Colors.white,
|
||||
)
|
||||
..drawCircle(
|
||||
center,
|
||||
radius - strokeWidth,
|
||||
paint..color = const Color(0x80000000),
|
||||
)
|
||||
..drawArc(
|
||||
Rect.fromCircle(center: center, radius: radius - padding),
|
||||
startAngle,
|
||||
progress * 2 * pi,
|
||||
true,
|
||||
paint..color = Colors.white,
|
||||
);
|
||||
} else {
|
||||
context.canvas
|
||||
..drawCircle(
|
||||
center,
|
||||
radius,
|
||||
paint
|
||||
..style = .fill
|
||||
..color = const Color(0x80000000),
|
||||
)
|
||||
..drawCircle(
|
||||
center,
|
||||
radius,
|
||||
paint
|
||||
..style = .stroke
|
||||
..strokeWidth = strokeWidth
|
||||
..color = Colors.white,
|
||||
)
|
||||
..drawArc(
|
||||
Rect.fromCircle(center: center, radius: radius - padding),
|
||||
startAngle,
|
||||
progress * 2 * pi,
|
||||
true,
|
||||
paint..style = .fill,
|
||||
);
|
||||
}
|
||||
context.canvas
|
||||
..drawCircle(
|
||||
center,
|
||||
radius,
|
||||
paint
|
||||
..style = .fill
|
||||
..color = const Color(0x80000000),
|
||||
)
|
||||
..drawCircle(
|
||||
center,
|
||||
radius,
|
||||
paint
|
||||
..style = .stroke
|
||||
..strokeWidth = strokeWidth
|
||||
..color = Colors.white,
|
||||
)
|
||||
..drawArc(
|
||||
Rect.fromCircle(center: center, radius: radius - padding),
|
||||
startAngle,
|
||||
progress * 2 * pi,
|
||||
true,
|
||||
paint..style = .fill,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void describeSemanticsConfiguration(SemanticsConfiguration config) {
|
||||
super.describeSemanticsConfiguration(config);
|
||||
config
|
||||
..role = .progressBar
|
||||
..minValue = '0'
|
||||
..maxValue = '100'
|
||||
..value = (_progress * 100).round().toString();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -26,6 +26,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/physics.dart' show FrictionSimulation;
|
||||
import 'package:flutter/scheduler.dart' show SchedulerBinding;
|
||||
import 'package:flutter/services.dart' show HardwareKeyboard;
|
||||
|
||||
///
|
||||
@@ -69,7 +70,6 @@ class Viewer extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
double get _interactionEndFrictionCoefficient => 0.0001 * _scale; // 0.0000135
|
||||
static const double _scaleFactor = kDefaultMouseScrollToScaleFactor;
|
||||
|
||||
_GestureType? _gestureType;
|
||||
@@ -171,6 +171,7 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_stopFling();
|
||||
_animationController
|
||||
..removeListener(_listener)
|
||||
..dispose();
|
||||
@@ -234,6 +235,7 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
void _handleDoubleTap() {
|
||||
if (!mounted) return;
|
||||
if (_animationController.isAnimating) return;
|
||||
_stopFling();
|
||||
_scaleFrom = _scale;
|
||||
_positionFrom = _position;
|
||||
|
||||
@@ -265,6 +267,8 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
void _onScaleStart(ScaleStartDetails details) {
|
||||
_stopFling();
|
||||
|
||||
if (_animationController.isAnimating) {
|
||||
_animationController.stop();
|
||||
}
|
||||
@@ -329,40 +333,106 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// ref https://github.com/ahnaineh/custom_interactive_viewer
|
||||
int? _flingFrameCallbackId;
|
||||
Simulation? _flingSimulation;
|
||||
Duration? _flingStartTime;
|
||||
double _lastFlingElapsedSeconds = 0.0;
|
||||
Offset _flingDirection = Offset.zero;
|
||||
|
||||
/// Calculate appropriate friction based on velocity magnitude
|
||||
double _calculateDynamicFriction(double velocityMagnitude) {
|
||||
// Use higher friction for faster flicks
|
||||
// These values can be tuned for the feel you want
|
||||
if (velocityMagnitude > 5000) {
|
||||
return 0.03; // Higher friction for very fast flicks
|
||||
} else if (velocityMagnitude > 3000) {
|
||||
return 0.02; // Medium friction for moderate flicks
|
||||
} else {
|
||||
return 0.01; // Lower friction for gentle movements
|
||||
}
|
||||
}
|
||||
|
||||
void _startFling(Velocity velocity) {
|
||||
_stopFling();
|
||||
|
||||
final double velocityMagnitude = velocity.pixelsPerSecond.distance;
|
||||
final double frictionCoefficient = _calculateDynamicFriction(
|
||||
velocityMagnitude,
|
||||
);
|
||||
|
||||
_flingSimulation = FrictionSimulation(
|
||||
frictionCoefficient,
|
||||
0.0,
|
||||
velocityMagnitude,
|
||||
);
|
||||
|
||||
_flingDirection = velocityMagnitude > 0
|
||||
? velocity.pixelsPerSecond / velocityMagnitude
|
||||
: Offset.zero;
|
||||
|
||||
_flingStartTime = null;
|
||||
_lastFlingElapsedSeconds = 0.0;
|
||||
_scheduleFlingFrame();
|
||||
}
|
||||
|
||||
void _scheduleFlingFrame() {
|
||||
_flingFrameCallbackId = SchedulerBinding.instance.scheduleFrameCallback(
|
||||
_handleFlingFrame,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleFlingFrame(Duration timeStamp) {
|
||||
if (_flingSimulation == null) return;
|
||||
|
||||
_flingStartTime ??= timeStamp;
|
||||
final double elapsedSeconds =
|
||||
(timeStamp - _flingStartTime!).inMicroseconds / 1e6;
|
||||
|
||||
final double distance = _flingSimulation!.x(elapsedSeconds);
|
||||
final double prevDistance = _flingSimulation!.x(_lastFlingElapsedSeconds);
|
||||
final double delta = distance - prevDistance;
|
||||
_lastFlingElapsedSeconds = elapsedSeconds;
|
||||
|
||||
if ((prevDistance != 0.0 && delta.abs() < 0.1) ||
|
||||
_flingSimulation!.isDone(elapsedSeconds)) {
|
||||
_stopFling();
|
||||
return;
|
||||
}
|
||||
|
||||
final Offset movement = _flingDirection * delta;
|
||||
_position = _clampPosition(_position + movement, _scale);
|
||||
setState(() {});
|
||||
|
||||
if (_flingSimulation!.isDone(elapsedSeconds)) {
|
||||
_stopFling();
|
||||
} else {
|
||||
_scheduleFlingFrame();
|
||||
}
|
||||
}
|
||||
|
||||
void _stopFling() {
|
||||
if (_flingFrameCallbackId != null) {
|
||||
SchedulerBinding.instance.cancelFrameCallbackWithId(
|
||||
_flingFrameCallbackId!,
|
||||
);
|
||||
_flingFrameCallbackId = null;
|
||||
}
|
||||
_flingStartTime = null;
|
||||
_lastFlingElapsedSeconds = 0.0;
|
||||
_flingSimulation = null;
|
||||
}
|
||||
|
||||
/// ref [InteractiveViewer]
|
||||
void _onScaleEnd(ScaleEndDetails details) {
|
||||
switch (_gestureType) {
|
||||
case _GestureType.pan:
|
||||
if (details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
|
||||
return;
|
||||
final double velocityMagnitude =
|
||||
details.velocity.pixelsPerSecond.distance;
|
||||
if (velocityMagnitude >= 200.0) {
|
||||
_startFling(details.velocity);
|
||||
}
|
||||
final drag = _interactionEndFrictionCoefficient;
|
||||
final FrictionSimulation frictionSimulationX = FrictionSimulation(
|
||||
drag,
|
||||
_position.dx,
|
||||
details.velocity.pixelsPerSecond.dx,
|
||||
);
|
||||
final FrictionSimulation frictionSimulationY = FrictionSimulation(
|
||||
drag,
|
||||
_position.dy,
|
||||
details.velocity.pixelsPerSecond.dy,
|
||||
);
|
||||
final double tFinal = _getFinalTime(
|
||||
details.velocity.pixelsPerSecond.distance,
|
||||
drag,
|
||||
);
|
||||
final position = _clampPosition(
|
||||
Offset(frictionSimulationX.finalX, frictionSimulationY.finalX),
|
||||
_scale,
|
||||
);
|
||||
|
||||
_scaleFrom = _scaleTo = _scale;
|
||||
_positionFrom = _position;
|
||||
_positionTo = position;
|
||||
|
||||
_animationController
|
||||
..duration = Duration(milliseconds: (tFinal * 1000).round())
|
||||
..forward(from: 0);
|
||||
case _GestureType.scale:
|
||||
// if (details.scaleVelocity.abs() < 0.1) {
|
||||
// return;
|
||||
@@ -417,9 +487,10 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
_doubleTapGestureRecognizer
|
||||
..onDoubleTapDown = _onDoubleTapDown
|
||||
..onDoubleTap = _onDoubleTap;
|
||||
_horizontalDragGestureRecognizer
|
||||
..isBoundaryAllowed = _isBoundaryAllowed
|
||||
..addPointer(event);
|
||||
_horizontalDragGestureRecognizer.addPointer(
|
||||
event,
|
||||
isPointerAllowed: _isAtEdge(event.localPosition),
|
||||
);
|
||||
_scaleGestureRecognizer.addPointer(event);
|
||||
}
|
||||
|
||||
@@ -427,25 +498,28 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
_scaleGestureRecognizer.addPointerPanZoom(event);
|
||||
}
|
||||
|
||||
bool _isBoundaryAllowed(Offset? initialPosition, OffsetPair lastPosition) {
|
||||
if (initialPosition == null) {
|
||||
return true;
|
||||
}
|
||||
bool _isAtEdge(Offset position) {
|
||||
if (_scale <= widget.minScale) {
|
||||
_horizontalDragGestureRecognizer.setAtBothEdges();
|
||||
return true;
|
||||
}
|
||||
final containerWidth = widget.containerSize.width;
|
||||
final imageWidth = _imageSize.width * _scale;
|
||||
if (imageWidth <= containerWidth) {
|
||||
_horizontalDragGestureRecognizer.setAtBothEdges();
|
||||
return true;
|
||||
}
|
||||
final dx = (1 - _scale) * containerWidth / 2;
|
||||
final dxOffset = (imageWidth - containerWidth) / 2;
|
||||
if (initialPosition.dx < lastPosition.global.dx) {
|
||||
return _position.dx.equals(dx + dxOffset, 1e-6);
|
||||
} else {
|
||||
return _position.dx.equals(dx - dxOffset, 1e-6);
|
||||
if (_position.dx.equals(dx + dxOffset, 1e-6)) {
|
||||
_horizontalDragGestureRecognizer.isAtLeftEdge = true;
|
||||
return true;
|
||||
}
|
||||
if (_position.dx.equals(dx - dxOffset, 1e-6)) {
|
||||
_horizontalDragGestureRecognizer.isAtRightEdge = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _onPointerSignal(PointerSignalEvent event) {
|
||||
@@ -455,6 +529,7 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
widget.onChangePage!.call(event.scrollDelta.dy < 0 ? -1 : 1);
|
||||
return;
|
||||
}
|
||||
_stopFling();
|
||||
final double scaleChange = math.exp(-event.scrollDelta.dy / _scaleFactor);
|
||||
final Offset local = event.localPosition;
|
||||
final Offset focalPointScene = _toScene(local);
|
||||
@@ -471,11 +546,3 @@ class _ViewerState extends State<Viewer> with SingleTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
enum _GestureType { pan, scale, drag }
|
||||
|
||||
double _getFinalTime(
|
||||
double velocity,
|
||||
double drag, {
|
||||
double effectivelyMotionless = 10,
|
||||
}) {
|
||||
return math.log(effectivelyMotionless / velocity) / math.log(drag / 100);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
|
||||
class KeepAliveWrapper extends StatefulWidget {
|
||||
const KeepAliveWrapper({
|
||||
super.key,
|
||||
required this.builder,
|
||||
required this.child,
|
||||
this.wantKeepAlive = true,
|
||||
});
|
||||
|
||||
final WidgetBuilder builder;
|
||||
final Widget child;
|
||||
final bool wantKeepAlive;
|
||||
|
||||
@override
|
||||
@@ -19,7 +19,7 @@ class _KeepAliveWrapperState extends State<KeepAliveWrapper>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return widget.builder(context);
|
||||
return widget.child;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -19,14 +19,8 @@ class HttpError extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return isSliver
|
||||
? SliverToBoxAdapter(child: content(context))
|
||||
: SizedBox(width: double.infinity, child: content(context));
|
||||
}
|
||||
|
||||
Widget content(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
final child = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -63,5 +57,9 @@ class HttpError extends StatelessWidget {
|
||||
SizedBox(height: 40 + MediaQuery.viewPaddingOf(context).bottom),
|
||||
],
|
||||
);
|
||||
|
||||
return isSliver
|
||||
? SliverToBoxAdapter(child: child)
|
||||
: SizedBox(width: double.infinity, child: child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
|
||||
import 'package:PiliPlus/common/widgets/loading_widget/m3e_loading_indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget get loadingWidget => const Center(child: CircularProgressIndicator());
|
||||
const Widget m3eLoading = Center(child: M3ELoadingIndicator());
|
||||
|
||||
Widget get linearLoading =>
|
||||
const SliverToBoxAdapter(child: LinearProgressIndicator());
|
||||
|
||||
Widget errorWidget({String? errMsg, VoidCallback? onReload}) => HttpError(
|
||||
isSliver: false,
|
||||
errMsg: errMsg,
|
||||
onReload: onReload,
|
||||
const Widget linearLoading = SliverToBoxAdapter(
|
||||
child: LinearProgressIndicator(),
|
||||
);
|
||||
|
||||
const Widget scrollableError = CustomScrollView(slivers: [HttpError()]);
|
||||
|
||||
Widget scrollErrorWidget({
|
||||
String? errMsg,
|
||||
VoidCallback? onReload,
|
||||
|
||||
260
lib/common/widgets/loading_widget/m3e_loading_indicator.dart
Normal file
260
lib/common/widgets/loading_widget/m3e_loading_indicator.dart
Normal file
@@ -0,0 +1,260 @@
|
||||
/*
|
||||
* This file is part of PiliPlus
|
||||
*
|
||||
* PiliPlus is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* PiliPlus is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with PiliPlus. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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,
|
||||
// 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();
|
||||
}
|
||||
|
||||
class _M3ELoadingIndicatorState extends State<M3ELoadingIndicator>
|
||||
with SingleTickerProviderStateMixin {
|
||||
static const int _morphIntervalMs = 650;
|
||||
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 _morphRotationTarget = _quarterRotation;
|
||||
|
||||
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) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
_startAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
void _startAnimation() {
|
||||
_morphIndex++;
|
||||
_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)
|
||||
..animateWith(_morphAnimationSpec);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller
|
||||
..removeStatusListener(_statusListener)
|
||||
..dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
double _calcAngle(double progress) {
|
||||
final elapsedInMs =
|
||||
_morphIntervalMs * (_morphIndex - 1) +
|
||||
(_controller.lastElapsedDuration?.inMilliseconds ?? 0);
|
||||
final globalRotation =
|
||||
(elapsedInMs % _globalRotationDurationMs) /
|
||||
_globalRotationDurationMs *
|
||||
_fullRotation;
|
||||
|
||||
return progress * _quarterRotation + _morphRotationTarget + globalRotation;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = widget.color ?? ColorScheme.of(context).secondaryFixedDim;
|
||||
return AnimatedBuilder(
|
||||
animation: _controller,
|
||||
builder: (context, child) {
|
||||
final progress = _controller.value;
|
||||
return RawM3ELoadingIndicator(
|
||||
// key: widget.childKey,
|
||||
morph: _morphs[_morphIndex % _morphs.length],
|
||||
progress: progress,
|
||||
angle: _calcAngle(progress),
|
||||
color: color,
|
||||
size: widget.size,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
morph: morph,
|
||||
progress: progress,
|
||||
angle: angle,
|
||||
color: color,
|
||||
size: size,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderM3ELoadingIndicator renderObject,
|
||||
) {
|
||||
renderObject
|
||||
..morph = morph
|
||||
..progress = progress
|
||||
..angle = angle
|
||||
..color = color
|
||||
..preferredSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
..color = color;
|
||||
|
||||
Morph _morph;
|
||||
Morph get morph => _morph;
|
||||
set morph(Morph value) {
|
||||
if (_morph == value) return;
|
||||
_morph = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _progress;
|
||||
double get progress => _progress;
|
||||
set progress(double value) {
|
||||
if (_progress == value) return;
|
||||
_progress = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
double _angle;
|
||||
double get angle => _angle;
|
||||
set angle(double value) {
|
||||
if (_angle == value) return;
|
||||
_angle = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
Color _color;
|
||||
final Paint _paint;
|
||||
set color(Color value) {
|
||||
if (_color == value) return;
|
||||
_paint.color = _color = value;
|
||||
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 = 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.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,
|
||||
// );
|
||||
}
|
||||
@@ -43,42 +43,3 @@ class NoRenderLayoutBox extends RenderProxyBox {
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {}
|
||||
}
|
||||
|
||||
class LayoutSizeWidget extends SingleChildRenderObjectWidget {
|
||||
const LayoutSizeWidget({
|
||||
super.key,
|
||||
super.child,
|
||||
required this.onPerformLayout,
|
||||
});
|
||||
|
||||
final LayoutCallback onPerformLayout;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) =>
|
||||
RenderLayoutBox(onPerformLayout: onPerformLayout);
|
||||
|
||||
@override
|
||||
void updateRenderObject(
|
||||
BuildContext context,
|
||||
RenderLayoutBox renderObject,
|
||||
) {
|
||||
super.updateRenderObject(context, renderObject);
|
||||
renderObject.onPerformLayout = onPerformLayout;
|
||||
}
|
||||
}
|
||||
|
||||
class RenderLayoutBox extends RenderProxyBox {
|
||||
RenderLayoutBox({required this.onPerformLayout});
|
||||
|
||||
LayoutCallback onPerformLayout;
|
||||
|
||||
Size? _size;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
super.performLayout();
|
||||
if (_size != size) {
|
||||
onPerformLayout(_size = size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,167 +1,163 @@
|
||||
import 'package:PiliPlus/common/assets.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
|
||||
import 'package:PiliPlus/models/common/avatar_badge_type.dart';
|
||||
import 'package:PiliPlus/models/common/image_type.dart';
|
||||
import 'package:PiliPlus/utils/extension/num_ext.dart';
|
||||
import 'package:PiliPlus/utils/extension/string_ext.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/storage_pref.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PendantAvatar extends StatelessWidget {
|
||||
final BadgeType _badgeType;
|
||||
final String? avatar;
|
||||
final double size;
|
||||
final double badgeSize;
|
||||
final String? garbPendantImage;
|
||||
final int? roomId;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
const PendantAvatar({
|
||||
const PendantAvatar(
|
||||
this.url, {
|
||||
super.key,
|
||||
required this.avatar,
|
||||
this.size = 80,
|
||||
required double size,
|
||||
double? badgeSize,
|
||||
bool isVip = false,
|
||||
int? vipStatus,
|
||||
int? officialType,
|
||||
this.garbPendantImage,
|
||||
this.pendantImage,
|
||||
this.pendentOffset = 6,
|
||||
this.roomId,
|
||||
this.liveBottom,
|
||||
this.liveFontSize,
|
||||
this.onTap,
|
||||
}) : _badgeType = officialType == null || officialType < 0
|
||||
? isVip
|
||||
? BadgeType.vip
|
||||
: BadgeType.none
|
||||
}) : preferredSize = size,
|
||||
badgeSize = badgeSize ?? size / 3,
|
||||
badgeType = officialType == null || officialType < 0
|
||||
? vipStatus != null && vipStatus > 0
|
||||
? .vip
|
||||
: .none
|
||||
: officialType == 0
|
||||
? BadgeType.person
|
||||
? .person
|
||||
: officialType == 1
|
||||
? BadgeType.institution
|
||||
: BadgeType.none,
|
||||
badgeSize = badgeSize ?? size / 3;
|
||||
? .institution
|
||||
: .none;
|
||||
|
||||
static bool showDynDecorate = Pref.showDynDecorate;
|
||||
static bool showDecorate = Pref.showDecorate;
|
||||
|
||||
final BadgeType badgeType;
|
||||
final String? url;
|
||||
final double preferredSize;
|
||||
final double badgeSize;
|
||||
final String? pendantImage;
|
||||
final double pendentOffset;
|
||||
final int? roomId;
|
||||
final double? liveBottom;
|
||||
final double? liveFontSize;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final isMemberAvatar = size == 80;
|
||||
final showPendant = showDecorate && pendantImage?.isNotEmpty == true;
|
||||
final size = showPendant ? preferredSize - pendentOffset : preferredSize;
|
||||
Widget? pendant;
|
||||
if (showDynDecorate && !garbPendantImage.isNullOrEmpty) {
|
||||
if (showPendant) {
|
||||
final pendantSize = size * 1.75;
|
||||
pendant = Positioned(
|
||||
// -(size * 1.75 - size) / 2
|
||||
top: -0.375 * size + (size == 80 ? 2 : 0),
|
||||
top: -0.375 * size + pendentOffset / 2,
|
||||
child: IgnorePointer(
|
||||
child: NetworkImgLayer(
|
||||
type: .emote,
|
||||
width: pendantSize,
|
||||
height: pendantSize,
|
||||
src: garbPendantImage,
|
||||
src: pendantImage,
|
||||
getPlaceHolder: () => const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
clipBehavior: Clip.none,
|
||||
Widget avatar = NetworkImgLayer(
|
||||
src: url,
|
||||
width: size,
|
||||
height: size,
|
||||
type: ImageType.avatar,
|
||||
);
|
||||
if (onTap != null) {
|
||||
avatar = GestureDetector(
|
||||
behavior: .opaque,
|
||||
onTap: onTap,
|
||||
child: avatar,
|
||||
);
|
||||
}
|
||||
Widget child = Stack(
|
||||
clipBehavior: .none,
|
||||
alignment: .center,
|
||||
children: [
|
||||
onTap == null
|
||||
? _buildAvatar(colorScheme, isMemberAvatar)
|
||||
: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
child: _buildAvatar(colorScheme, isMemberAvatar),
|
||||
),
|
||||
avatar,
|
||||
?pendant,
|
||||
if (roomId != null)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
child: InkWell(
|
||||
onTap: () => PageUtils.toLiveRoom(roomId),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.secondaryContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(36)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
size: 16,
|
||||
applyTextScaling: true,
|
||||
Icons.equalizer_rounded,
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
Text(
|
||||
'直播中',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: 13,
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
_buildLive(colorScheme)
|
||||
else if (badgeType != .none)
|
||||
_buildBadge(context, colorScheme),
|
||||
],
|
||||
);
|
||||
if (showPendant) {
|
||||
return SizedBox.square(
|
||||
dimension: preferredSize,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildLive(ColorScheme colorScheme) {
|
||||
final fontSize = liveFontSize ?? 13.0;
|
||||
return Positioned(
|
||||
bottom: liveBottom ?? 0.0,
|
||||
child: GestureDetector(
|
||||
onTap: () => PageUtils.toLiveRoom(roomId),
|
||||
child: Container(
|
||||
padding: const .symmetric(horizontal: 5, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.secondaryContainer,
|
||||
borderRadius: Style.mdRadius,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: .min,
|
||||
children: [
|
||||
Icon(
|
||||
size: fontSize + 3,
|
||||
applyTextScaling: true,
|
||||
Icons.equalizer_rounded,
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
Text(
|
||||
'直播中',
|
||||
style: TextStyle(
|
||||
height: 1,
|
||||
fontSize: fontSize,
|
||||
color: colorScheme.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
else if (_badgeType != BadgeType.none)
|
||||
_buildBadge(context, colorScheme, isMemberAvatar),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAvatar(ColorScheme colorScheme, bool isMemberAvatar) =>
|
||||
isMemberAvatar
|
||||
? DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
width: 2,
|
||||
color: colorScheme.surface,
|
||||
),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: NetworkImgLayer(
|
||||
src: avatar,
|
||||
width: size,
|
||||
height: size,
|
||||
type: ImageType.avatar,
|
||||
),
|
||||
),
|
||||
)
|
||||
: NetworkImgLayer(
|
||||
src: avatar,
|
||||
width: size,
|
||||
height: size,
|
||||
type: ImageType.avatar,
|
||||
);
|
||||
|
||||
Widget _buildBadge(
|
||||
BuildContext context,
|
||||
ColorScheme colorScheme,
|
||||
bool isMemberAvatar,
|
||||
) {
|
||||
final child = switch (_badgeType) {
|
||||
BadgeType.vip => Image.asset(
|
||||
'assets/images/big-vip.png',
|
||||
Widget _buildBadge(BuildContext context, ColorScheme colorScheme) {
|
||||
final child = switch (badgeType) {
|
||||
.vip => Image.asset(
|
||||
Assets.vipIcon,
|
||||
width: badgeSize,
|
||||
height: badgeSize,
|
||||
cacheWidth: badgeSize.cacheSize(context),
|
||||
semanticLabel: _badgeType.desc,
|
||||
semanticLabel: badgeType.desc,
|
||||
),
|
||||
_ => Icon(
|
||||
Icons.offline_bolt,
|
||||
color: _badgeType.color,
|
||||
color: badgeType.color,
|
||||
size: badgeSize,
|
||||
semanticLabel: _badgeType.desc,
|
||||
semanticLabel: badgeType.desc,
|
||||
),
|
||||
};
|
||||
final offset = isMemberAvatar ? 2.0 : 0.0;
|
||||
return Positioned(
|
||||
right: offset,
|
||||
bottom: offset,
|
||||
right: 0.0,
|
||||
bottom: 0.0,
|
||||
child: IgnorePointer(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
|
||||
@@ -23,7 +23,8 @@ import 'package:flutter/rendering.dart'
|
||||
ContainerRenderObjectMixin,
|
||||
MultiChildLayoutParentData,
|
||||
RenderBoxContainerDefaultsMixin,
|
||||
BoxHitTestResult;
|
||||
BoxHitTestResult,
|
||||
TransformLayer;
|
||||
|
||||
class PlayerBar extends MultiChildRenderObjectWidget {
|
||||
const PlayerBar({
|
||||
@@ -92,14 +93,16 @@ class RenderBottomBar extends RenderBox
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (_transform != null) {
|
||||
context.pushTransform(
|
||||
layer = context.pushTransform(
|
||||
needsCompositing,
|
||||
offset,
|
||||
_transform!,
|
||||
defaultPaint,
|
||||
oldLayer: layer as TransformLayer?,
|
||||
);
|
||||
} else {
|
||||
defaultPaint(context, offset);
|
||||
layer = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
21
lib/common/widgets/route_aware_mixin.dart
Normal file
21
lib/common/widgets/route_aware_mixin.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get_core/src/get_main.dart';
|
||||
import 'package:get/get_navigation/src/extension_navigation.dart';
|
||||
import 'package:get/get_navigation/src/routes/default_route.dart'
|
||||
show GetPageRoute;
|
||||
|
||||
final routeObserver = RouteObserver<GetPageRoute>();
|
||||
|
||||
mixin RouteAwareMixin<T extends StatefulWidget> on State<T>, RouteAware {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
routeObserver.subscribe(this, Get.routing.route as GetPageRoute);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
routeObserver.unsubscribe(this);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
Widget selectMask(
|
||||
ThemeData theme,
|
||||
bool checked, {
|
||||
BorderRadiusGeometry borderRadius = StyleString.mdRadius,
|
||||
BorderRadiusGeometry borderRadius = Style.mdRadius,
|
||||
}) {
|
||||
return AnimatedOpacity(
|
||||
opacity: checked ? 1 : 0,
|
||||
|
||||
@@ -96,7 +96,6 @@ class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor {
|
||||
set runSpacing(double value) {
|
||||
if (_runSpacing == value) return;
|
||||
_runSpacing = value;
|
||||
markRowsDirty();
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@@ -168,20 +167,20 @@ class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor {
|
||||
}
|
||||
}
|
||||
|
||||
bool _buildNextRow(int start, BoxConstraints childConstraints) {
|
||||
bool _buildNextRow(int start, BoxConstraints constraints) {
|
||||
final int childCount = childManager.childCount;
|
||||
|
||||
if (start >= childCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final crossAxisExtent = constraints.crossAxisExtent;
|
||||
final crossAxisExtent = this.constraints.crossAxisExtent;
|
||||
|
||||
final List<double> widths = [];
|
||||
int idx = start;
|
||||
RenderBox? child;
|
||||
for (var totalWidth = -_spacing; idx < childCount; idx++) {
|
||||
child = _getOrCreateChildAtIndex(idx, childConstraints, child);
|
||||
child = _getOrCreateChildAtIndex(idx, constraints, child);
|
||||
final childWidth = _childCrossExtent(child);
|
||||
totalWidth += childWidth + _spacing;
|
||||
|
||||
@@ -215,24 +214,20 @@ class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor {
|
||||
final firstNeededRow = math.max(0, firstCacheOffset ~/ rowHeight);
|
||||
final lastNeededRow = math.max(0, lastCacheOffset ~/ rowHeight);
|
||||
|
||||
final childConstraints = constraints.toFixedConstraints(_mainAxisExtent);
|
||||
|
||||
if (firstChild == null) {
|
||||
if (!addInitialChild()) {
|
||||
geometry = SliverGeometry.zero;
|
||||
childManager.didFinishLayout();
|
||||
return;
|
||||
}
|
||||
firstChild!.layout(
|
||||
constraints.toFixedConstraints(_mainAxisExtent),
|
||||
parentUsesSize: true,
|
||||
);
|
||||
firstChild!.layout(childConstraints, parentUsesSize: true);
|
||||
}
|
||||
|
||||
while (_rows.length <= lastNeededRow) {
|
||||
final int startIndex = _rows.isEmpty ? 0 : _rows.last.endIndex + 1;
|
||||
if (!_buildNextRow(
|
||||
startIndex,
|
||||
constraints.toFixedConstraints(_mainAxisExtent),
|
||||
)) {
|
||||
if (!_buildNextRow(startIndex, childConstraints)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -256,11 +251,7 @@ class RenderSliverFixedWrap extends RenderSliverMultiBoxAdaptor {
|
||||
final rowStartOffset = r * rowHeight;
|
||||
double crossOffset = 0.0;
|
||||
for (var i = row.startIndex; i <= row.endIndex; i++) {
|
||||
child = _getOrCreateChildAtIndex(
|
||||
i,
|
||||
constraints.toFixedConstraints(_mainAxisExtent),
|
||||
child,
|
||||
);
|
||||
child = _getOrCreateChildAtIndex(i, childConstraints, child);
|
||||
(child.parentData as SliverWrapParentData)
|
||||
..layoutOffset = rowStartOffset
|
||||
..crossAxisOffset = crossOffset;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
||||
@@ -120,14 +120,14 @@ class VideoCardH extends StatelessWidget {
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: StyleString.safeSpace,
|
||||
horizontal: Style.safeSpace,
|
||||
vertical: 5,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
aspectRatio: Style.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
final double maxWidth = boxConstraints.maxWidth;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart';
|
||||
import 'package:PiliPlus/common/widgets/badge.dart';
|
||||
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
|
||||
import 'package:PiliPlus/common/widgets/image/image_save.dart';
|
||||
@@ -22,7 +22,7 @@ import 'package:intl/intl.dart';
|
||||
|
||||
// 视频卡片 - 垂直布局
|
||||
class VideoCardV extends StatelessWidget {
|
||||
final BaseRecVideoItemModel videoItem;
|
||||
final BaseRcmdVideoItemModel videoItem;
|
||||
final VoidCallback? onRemove;
|
||||
|
||||
const VideoCardV({
|
||||
@@ -87,7 +87,7 @@ class VideoCardV extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
aspectRatio: Style.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
@@ -229,7 +229,7 @@ class VideoCardV extends StatelessWidget {
|
||||
value: videoItem.stat.danmu,
|
||||
),
|
||||
],
|
||||
if (videoItem is RecVideoItemModel) ...[
|
||||
if (videoItem is RcmdVideoItemModel) ...[
|
||||
const Spacer(),
|
||||
Text.rich(
|
||||
maxLines: 1,
|
||||
@@ -248,7 +248,7 @@ class VideoCardV extends StatelessWidget {
|
||||
const SizedBox(width: 2),
|
||||
],
|
||||
// deprecated
|
||||
// else if (videoItem is RecVideoItemAppModel &&
|
||||
// else if (videoItem is RcmdVideoItemAppModel &&
|
||||
// videoItem.desc != null &&
|
||||
// videoItem.desc!.contains(' · ')) ...[
|
||||
// const Spacer(),
|
||||
|
||||
@@ -135,7 +135,7 @@ class VideoPopupMenu extends StatelessWidget {
|
||||
SmartDialog.showToast("请退出账号后重新登录");
|
||||
return;
|
||||
}
|
||||
if (videoItem case final RecVideoItemAppModel item) {
|
||||
if (videoItem case final RcmdVideoItemAppModel item) {
|
||||
ThreePoint? tp = item.threePoint;
|
||||
if (tp == null) {
|
||||
SmartDialog.showToast("未能获取threePoint");
|
||||
|
||||
@@ -649,14 +649,9 @@ abstract final class Api {
|
||||
/// mid
|
||||
static const getMemberViewApi = '/x/space/upstat';
|
||||
|
||||
/// 查询某个专栏
|
||||
/// mid
|
||||
/// season_id
|
||||
/// sort_reverse
|
||||
/// page_num
|
||||
/// page_size
|
||||
static const getSeasonDetailApi =
|
||||
'/x/polymer/web-space/seasons_archives_list';
|
||||
static const seasonArchives = '/x/polymer/web-space/seasons_archives_list';
|
||||
|
||||
static const seriesArchives = '/x/series/archives';
|
||||
|
||||
/// 获取未读动态数
|
||||
static const getUnreadDynamic = '/x/web-interface/dynamic/entrance';
|
||||
@@ -999,4 +994,7 @@ abstract final class Api {
|
||||
static const String replySubjectModify = '/x/v2/reply/subject/modify';
|
||||
|
||||
static const String videoshot = '/x/player/videoshot';
|
||||
|
||||
static const String liveMedalWall =
|
||||
'${HttpString.liveBaseUrl}/xlive/web-ucenter/user/MedalWall';
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ abstract final class DanmakuHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> danmakuLike({
|
||||
static Future<LoadingState<void>> danmakuLike({
|
||||
required bool isLike,
|
||||
required int cid,
|
||||
required int id,
|
||||
@@ -89,7 +89,7 @@ abstract final class DanmakuHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> danmakuReport({
|
||||
static Future<LoadingState<void>> danmakuReport({
|
||||
required int reason,
|
||||
required int cid,
|
||||
required int id,
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract final class DanmakuFilterHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> danmakuFilterDel({required int ids}) async {
|
||||
static Future<LoadingState<void>> danmakuFilterDel({required int ids}) async {
|
||||
final res = await Request().post(
|
||||
Api.danmakuFilterDel,
|
||||
data: {
|
||||
|
||||
@@ -161,6 +161,7 @@ abstract final class DownloadHttp {
|
||||
dashDrmType: 0,
|
||||
),
|
||||
];
|
||||
entry.hasDashAudio = true;
|
||||
}
|
||||
return Type2(
|
||||
duration: dash.duration!,
|
||||
|
||||
@@ -127,7 +127,7 @@ abstract final class DynamicsHttp {
|
||||
// }
|
||||
|
||||
// 动态点赞
|
||||
static Future<LoadingState<Null>> thumbDynamic({
|
||||
static Future<LoadingState<void>> thumbDynamic({
|
||||
required String? dynamicId,
|
||||
required int? up,
|
||||
}) async {
|
||||
@@ -275,7 +275,7 @@ abstract final class DynamicsHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> setTop({
|
||||
static Future<LoadingState<void>> setTop({
|
||||
required Object dynamicId,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
@@ -294,7 +294,7 @@ abstract final class DynamicsHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> rmTop({
|
||||
static Future<LoadingState<void>> rmTop({
|
||||
required Object dynamicId,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
@@ -672,7 +672,7 @@ abstract final class DynamicsHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> dynPrivatePubSetting({
|
||||
static Future<LoadingState<void>> dynPrivatePubSetting({
|
||||
required Object dynId,
|
||||
int? dynType,
|
||||
required String action,
|
||||
@@ -699,7 +699,7 @@ abstract final class DynamicsHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> editDyn({
|
||||
static Future<LoadingState<void>> editDyn({
|
||||
required Object dynId,
|
||||
Object? repostDynId,
|
||||
dynamic rawText,
|
||||
|
||||
3
lib/http/error_msg.dart
Normal file
3
lib/http/error_msg.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
const errorMsg = {
|
||||
-352: '风控校验失败,请检查登录状态',
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/error_msg.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models_new/follow/data.dart';
|
||||
@@ -23,7 +24,7 @@ abstract final class FanHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return Success(FollowData.fromJson(res.data['data']));
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
return Error(errorMsg[res.data['code']] ?? res.data['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import 'package:PiliPlus/utils/app_sign.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
abstract final class FavHttp {
|
||||
static Future<LoadingState<Null>> favFavFolder(Object mediaId) async {
|
||||
static Future<LoadingState<void>> favFavFolder(Object mediaId) async {
|
||||
final res = await Request().post(
|
||||
Api.favFavFolder,
|
||||
data: {
|
||||
@@ -34,7 +34,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> unfavFavFolder(Object mediaId) async {
|
||||
static Future<LoadingState<void>> unfavFavFolder(Object mediaId) async {
|
||||
final res = await Request().post(
|
||||
Api.unfavFavFolder,
|
||||
data: {
|
||||
@@ -79,7 +79,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
|
||||
// 取消订阅
|
||||
static Future<LoadingState<Null>> cancelSub({
|
||||
static Future<LoadingState<void>> cancelSub({
|
||||
required int id,
|
||||
required int type,
|
||||
}) async {
|
||||
@@ -148,7 +148,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> addFavPugv(Object seasonId) async {
|
||||
static Future<LoadingState<void>> addFavPugv(Object seasonId) async {
|
||||
final res = await Request().post(
|
||||
Api.addFavPugv,
|
||||
data: {
|
||||
@@ -164,7 +164,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delFavPugv(Object seasonId) async {
|
||||
static Future<LoadingState<void>> delFavPugv(Object seasonId) async {
|
||||
final res = await Request().post(
|
||||
Api.delFavPugv,
|
||||
data: {
|
||||
@@ -198,7 +198,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> addFavTopic(Object topicId) async {
|
||||
static Future<LoadingState<void>> addFavTopic(Object topicId) async {
|
||||
final res = await Request().post(
|
||||
Api.addFavTopic,
|
||||
data: {
|
||||
@@ -214,7 +214,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delFavTopic(Object topicId) async {
|
||||
static Future<LoadingState<void>> delFavTopic(Object topicId) async {
|
||||
final res = await Request().post(
|
||||
Api.delFavTopic,
|
||||
data: {
|
||||
@@ -230,7 +230,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> likeTopic(
|
||||
static Future<LoadingState<void>> likeTopic(
|
||||
Object topicId,
|
||||
bool isLike,
|
||||
) async {
|
||||
@@ -269,7 +269,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> addFavArticle({
|
||||
static Future<LoadingState<void>> addFavArticle({
|
||||
required Object id,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
@@ -289,7 +289,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delFavArticle({
|
||||
static Future<LoadingState<void>> delFavArticle({
|
||||
required Object id,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
@@ -351,7 +351,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delNote({
|
||||
static Future<LoadingState<void>> delNote({
|
||||
required bool isPublish,
|
||||
required String noteIds,
|
||||
}) async {
|
||||
@@ -415,7 +415,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> sortFavFolder({
|
||||
static Future<LoadingState<void>> sortFavFolder({
|
||||
required String sort,
|
||||
}) async {
|
||||
Map<String, dynamic> data = {
|
||||
@@ -437,7 +437,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> sortFav({
|
||||
static Future<LoadingState<void>> sortFav({
|
||||
required Object mediaId,
|
||||
required String sort,
|
||||
}) async {
|
||||
@@ -461,7 +461,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> cleanFav({
|
||||
static Future<LoadingState<void>> cleanFav({
|
||||
required Object mediaId,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
@@ -482,7 +482,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> deleteFolder({
|
||||
static Future<LoadingState<void>> deleteFolder({
|
||||
required String mediaIds,
|
||||
}) async {
|
||||
final res = await Request().post(
|
||||
@@ -548,7 +548,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> seasonFav({
|
||||
static Future<LoadingState<void>> seasonFav({
|
||||
required bool isFav,
|
||||
required dynamic seasonId,
|
||||
}) async {
|
||||
@@ -605,7 +605,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> communityAction({
|
||||
static Future<LoadingState<void>> communityAction({
|
||||
required Object opusId,
|
||||
required Object action,
|
||||
}) async {
|
||||
@@ -630,7 +630,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
|
||||
// (取消)收藏
|
||||
static Future<LoadingState<Null>> favVideo({
|
||||
static Future<LoadingState<void>> favVideo({
|
||||
required String resources,
|
||||
String? addIds,
|
||||
String? delIds,
|
||||
@@ -653,7 +653,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
|
||||
// (取消)收藏
|
||||
static Future<LoadingState<Null>> unfavAll({
|
||||
static Future<LoadingState<void>> unfavAll({
|
||||
required Object rid,
|
||||
required Object type,
|
||||
}) async {
|
||||
@@ -673,7 +673,7 @@ abstract final class FavHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> copyOrMoveFav({
|
||||
static Future<LoadingState<void>> copyOrMoveFav({
|
||||
required bool isCopy,
|
||||
required bool isFav,
|
||||
required dynamic srcMediaId,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/error_msg.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models_new/follow/data.dart';
|
||||
@@ -23,7 +24,7 @@ abstract final class FollowHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return Success(FollowData.fromJson(res.data['data']));
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
return Error(errorMsg[res.data['code']] ?? res.data['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@ class Request {
|
||||
|
||||
static final Request _instance = Request._internal();
|
||||
static late AccountManager accountManager;
|
||||
static final _enableHttp2 = Pref.enableHttp2;
|
||||
static late final Dio dio;
|
||||
static Dio? _http11Dio;
|
||||
static Dio get http11Dio =>
|
||||
_http11Dio ??= _enableHttp2 ? _cloneHttp11Dio() : dio;
|
||||
factory Request() => _instance;
|
||||
|
||||
/// 设置cookie
|
||||
@@ -95,11 +99,26 @@ class Request {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
static Dio _cloneHttp11Dio() {
|
||||
final h11 = dio.clone(
|
||||
httpClientAdapter:
|
||||
(dio.httpClientAdapter as Http2Adapter).fallbackAdapter,
|
||||
);
|
||||
final interceptors = h11.interceptors;
|
||||
for (var i = 0; i < interceptors.length; i++) {
|
||||
final elem = interceptors[i];
|
||||
if (elem is RetryInterceptor) {
|
||||
interceptors[i] = elem.copyWith(client: h11);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return h11;
|
||||
}
|
||||
|
||||
/*
|
||||
* config it and create
|
||||
*/
|
||||
Request._internal() {
|
||||
final enableHttp2 = Pref.enableHttp2;
|
||||
//BaseOptions、Options、RequestOptions 都可以配置参数,优先级别依次递增,且可以根据优先级别覆盖参数
|
||||
BaseOptions options = BaseOptions(
|
||||
//请求基地址,可以包含子路径
|
||||
@@ -111,7 +130,7 @@ class Request {
|
||||
//Http请求头.
|
||||
headers: {
|
||||
'user-agent': 'Dart/3.6 (dart:io)', // Http2Adapter不会自动添加标头
|
||||
if (!enableHttp2) 'connection': 'keep-alive',
|
||||
if (!_enableHttp2) 'connection': 'keep-alive',
|
||||
'accept-encoding': 'br,gzip',
|
||||
},
|
||||
responseDecoder: _responseDecoder, // Http2Adapter没有自动解压
|
||||
@@ -142,7 +161,7 @@ class Request {
|
||||
);
|
||||
|
||||
dio = Dio(options)
|
||||
..httpClientAdapter = enableHttp2
|
||||
..httpClientAdapter = _enableHttp2
|
||||
? Http2Adapter(
|
||||
ConnectionManager(
|
||||
idleTimeout: const Duration(seconds: 15),
|
||||
@@ -167,7 +186,11 @@ class Request {
|
||||
: http11Adapter;
|
||||
|
||||
// 先于其他Interceptor
|
||||
dio.interceptors.add(RetryInterceptor(Pref.retryCount, Pref.retryDelay));
|
||||
if (Pref.retryCount != 0) {
|
||||
dio.interceptors.add(
|
||||
RetryInterceptor(dio, Pref.retryCount, Pref.retryDelay),
|
||||
);
|
||||
}
|
||||
|
||||
// 日志拦截器 输出请求、响应内容
|
||||
if (kDebugMode) {
|
||||
|
||||
@@ -19,6 +19,7 @@ import 'package:PiliPlus/models_new/live/live_emote/data.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_emote/datum.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_feed_index/data.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_follow/data.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/data.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_room_play_info/data.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_search/data.dart';
|
||||
@@ -33,7 +34,7 @@ import 'package:dio/dio.dart';
|
||||
abstract final class LiveHttp {
|
||||
static Account get recommend => Accounts.get(AccountType.recommend);
|
||||
|
||||
static Future<LoadingState<Null>> sendLiveMsg({
|
||||
static Future<LoadingState<void>> sendLiveMsg({
|
||||
required Object roomId,
|
||||
required Object msg,
|
||||
Object? dmType,
|
||||
@@ -385,7 +386,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> setLiveFavTag({
|
||||
static Future<LoadingState<void>> setLiveFavTag({
|
||||
required String ids,
|
||||
}) async {
|
||||
final data = {
|
||||
@@ -505,7 +506,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> liveSetSilent({
|
||||
static Future<LoadingState<void>> liveSetSilent({
|
||||
required String type,
|
||||
required int level,
|
||||
}) async {
|
||||
@@ -527,7 +528,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> addShieldKeyword({
|
||||
static Future<LoadingState<void>> addShieldKeyword({
|
||||
required String keyword,
|
||||
}) async {
|
||||
final csrf = Accounts.main.csrf;
|
||||
@@ -547,7 +548,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delShieldKeyword({
|
||||
static Future<LoadingState<void>> delShieldKeyword({
|
||||
required String keyword,
|
||||
}) async {
|
||||
final csrf = Accounts.main.csrf;
|
||||
@@ -591,7 +592,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> liveLikeReport({
|
||||
static Future<LoadingState<void>> liveLikeReport({
|
||||
required int clickTime,
|
||||
required Object roomId,
|
||||
required Object uid,
|
||||
@@ -637,7 +638,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> liveDmReport({
|
||||
static Future<LoadingState<void>> liveDmReport({
|
||||
required int roomId,
|
||||
required Object mid,
|
||||
required String msg,
|
||||
@@ -707,7 +708,7 @@ abstract final class LiveHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> superChatReport({
|
||||
static Future<LoadingState<void>> superChatReport({
|
||||
required int id,
|
||||
required Object roomId,
|
||||
required Object uid,
|
||||
@@ -742,4 +743,18 @@ abstract final class LiveHttp {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<MedalWallData>> liveMedalWall({
|
||||
required Object mid,
|
||||
}) async {
|
||||
final res = await Request().get(
|
||||
Api.liveMedalWall,
|
||||
queryParameters: {'target_id': mid},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return Success(MedalWallData.fromJson(res.data['data']));
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,14 @@ import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
import 'package:PiliPlus/http/browser_ua.dart';
|
||||
import 'package:PiliPlus/http/constants.dart';
|
||||
import 'package:PiliPlus/http/error_msg.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:PiliPlus/http/loading_state.dart';
|
||||
import 'package:PiliPlus/models/common/member/archive_order_type_app.dart';
|
||||
import 'package:PiliPlus/models/common/member/archive_order_type_web.dart';
|
||||
import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart';
|
||||
import 'package:PiliPlus/models/common/member/contribute_type.dart';
|
||||
import 'package:PiliPlus/models/common/member/web_ss_type.dart';
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/models/member/info.dart';
|
||||
import 'package:PiliPlus/models/member/tags.dart';
|
||||
@@ -14,6 +19,7 @@ import 'package:PiliPlus/models_new/follow/data.dart';
|
||||
import 'package:PiliPlus/models_new/follow/list.dart';
|
||||
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/space/space/data.dart';
|
||||
import 'package:PiliPlus/models_new/space/space_archive/data.dart';
|
||||
@@ -113,8 +119,8 @@ abstract final class MemberHttp {
|
||||
required ContributeType type,
|
||||
required int? mid,
|
||||
String? aid,
|
||||
String? order,
|
||||
String? sort,
|
||||
ArchiveOrderTypeApp? order,
|
||||
ArchiveSortTypeApp? sort,
|
||||
int? pn,
|
||||
int? next,
|
||||
int? seasonId,
|
||||
@@ -135,22 +141,15 @@ abstract final class MemberHttp {
|
||||
'next': ?next,
|
||||
'season_id': ?seasonId,
|
||||
'series_id': ?seriesId,
|
||||
'qn': type == ContributeType.video ? 80 : 32,
|
||||
'order': ?order,
|
||||
'sort': ?sort,
|
||||
'qn': type == .video ? 80 : 32,
|
||||
'order': ?order?.name,
|
||||
'sort': ?sort?.name,
|
||||
'include_cursor': ?includeCursor,
|
||||
'statistics': Constants.statisticsApp,
|
||||
'vmid': mid,
|
||||
};
|
||||
final res = await Request().get(
|
||||
switch (type) {
|
||||
ContributeType.video => Api.spaceArchive,
|
||||
ContributeType.charging => Api.spaceChargingArchive,
|
||||
ContributeType.season => Api.spaceSeason,
|
||||
ContributeType.series => Api.spaceSeries,
|
||||
ContributeType.bangumi => Api.spaceBangumi,
|
||||
ContributeType.comic => Api.spaceComic,
|
||||
},
|
||||
type.api,
|
||||
queryParameters: params,
|
||||
options: Options(
|
||||
headers: {
|
||||
@@ -348,12 +347,12 @@ abstract final class MemberHttp {
|
||||
|
||||
static Future<LoadingState<SearchArchiveData>> searchArchive({
|
||||
required Object mid,
|
||||
int tid = 0, // e.g. pugv: 196
|
||||
int ps = 30,
|
||||
int tid = 0,
|
||||
int? pn,
|
||||
required int pn,
|
||||
String? keyword,
|
||||
String order = 'pubdate',
|
||||
bool orderAvoided = true,
|
||||
String? specialType, // e.g. 'charging'
|
||||
ArchiveOrderTypeWeb order = .pubdate,
|
||||
}) async {
|
||||
String dmImgStr = Utils.base64EncodeRandomString(16, 64);
|
||||
String dmCoverImgStr = Utils.base64EncodeRandomString(32, 128);
|
||||
@@ -361,12 +360,13 @@ abstract final class MemberHttp {
|
||||
'mid': mid,
|
||||
'ps': ps,
|
||||
'tid': tid,
|
||||
'pn': ?pn,
|
||||
'pn': pn,
|
||||
'keyword': ?keyword,
|
||||
'order': order,
|
||||
'special_type': ?specialType,
|
||||
'order': order.name,
|
||||
'platform': 'web',
|
||||
'web_location': 1550101,
|
||||
'order_avoided': orderAvoided,
|
||||
'web_location': 333.1387,
|
||||
'order_avoided': true,
|
||||
'dm_img_list': '[]',
|
||||
'dm_img_str': dmImgStr,
|
||||
'dm_cover_img_str': dmCoverImgStr,
|
||||
@@ -386,10 +386,50 @@ abstract final class MemberHttp {
|
||||
if (res.data['code'] == 0) {
|
||||
return Success(SearchArchiveData.fromJson(res.data['data']));
|
||||
} else {
|
||||
Map errMap = const {
|
||||
-352: '风控校验失败,请检查登录状态',
|
||||
};
|
||||
return Error(errMap[res.data['code']] ?? res.data['message']);
|
||||
return Error(errorMsg[res.data['code']] ?? res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<SeasonWebData>> seasonSeriesWeb({
|
||||
required WebSsType type,
|
||||
required Object mid,
|
||||
required Object id,
|
||||
int ps = 30,
|
||||
required int pn,
|
||||
ArchiveSortTypeApp sort = .desc,
|
||||
}) async {
|
||||
final res = await Request().get(
|
||||
type.api,
|
||||
queryParameters: switch (type) {
|
||||
.season => {
|
||||
'mid': mid,
|
||||
'season_id': id,
|
||||
'sort_reverse': sort == .asc,
|
||||
'page_size': ps,
|
||||
'page_num': pn,
|
||||
'web_location': 333.1387,
|
||||
},
|
||||
.series => {
|
||||
'mid': mid,
|
||||
'series_id': id,
|
||||
'sort': sort.name,
|
||||
'ps': ps,
|
||||
'pn': pn,
|
||||
'web_location': 333.1387,
|
||||
},
|
||||
},
|
||||
options: Options(
|
||||
headers: {
|
||||
HttpHeaders.userAgentHeader: BrowserUa.pc,
|
||||
HttpHeaders.refererHeader: '${HttpString.spaceBaseUrl}/$mid',
|
||||
'origin': HttpString.spaceBaseUrl,
|
||||
},
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return Success(SeasonWebData.fromJson(res.data['data']));
|
||||
} else {
|
||||
return Error(errorMsg[res.data['code']] ?? res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,10 +477,7 @@ abstract final class MemberHttp {
|
||||
return Error('$e\n\n$s');
|
||||
}
|
||||
} else {
|
||||
Map errMap = const {
|
||||
-352: '风控校验失败,请检查登录状态',
|
||||
};
|
||||
return Error(errMap[res.data['code']] ?? res.data['message']);
|
||||
return Error(errorMsg[res.data['code']] ?? res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,7 +519,7 @@ abstract final class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> specialAction({
|
||||
static Future<LoadingState<void>> specialAction({
|
||||
int? fid,
|
||||
bool isAdd = true,
|
||||
}) async {
|
||||
@@ -502,7 +539,7 @@ abstract final class MemberHttp {
|
||||
}
|
||||
|
||||
// 设置分组
|
||||
static Future<LoadingState<Null>> addUsers(String fids, String tagids) async {
|
||||
static Future<LoadingState<void>> addUsers(String fids, String tagids) async {
|
||||
final res = await Request().post(
|
||||
Api.addUsers,
|
||||
queryParameters: {
|
||||
@@ -551,11 +588,11 @@ abstract final class MemberHttp {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
return Error(errorMsg[res.data['code']] ?? res.data['message']);
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> createFollowTag(Object tagName) async {
|
||||
static Future<LoadingState<void>> createFollowTag(Object tagName) async {
|
||||
final res = await Request().post(
|
||||
Api.createFollowTag,
|
||||
queryParameters: {
|
||||
@@ -575,7 +612,7 @@ abstract final class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> updateFollowTag(
|
||||
static Future<LoadingState<void>> updateFollowTag(
|
||||
Object tagid,
|
||||
Object name,
|
||||
) async {
|
||||
@@ -599,7 +636,7 @@ abstract final class MemberHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delFollowTag(Object tagid) async {
|
||||
static Future<LoadingState<void>> delFollowTag(Object tagid) async {
|
||||
final res = await Request().post(
|
||||
Api.delFollowTag,
|
||||
queryParameters: {
|
||||
|
||||
@@ -18,7 +18,6 @@ import 'package:PiliPlus/models_new/upload_bfs/data.dart';
|
||||
import 'package:PiliPlus/utils/accounts.dart';
|
||||
import 'package:PiliPlus/utils/wbi_sign.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:uuid/v4.dart';
|
||||
|
||||
abstract final class MsgHttp {
|
||||
static Future<LoadingState<MsgReplyData>> msgFeedReplyMe({
|
||||
@@ -136,7 +135,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> msgSysUpdateCursor(int cursor) async {
|
||||
static Future<LoadingState<void>> msgSysUpdateCursor(int cursor) async {
|
||||
String csrf = Accounts.main.csrf;
|
||||
final res = await Request().get(
|
||||
Api.msgSysUpdateCursor,
|
||||
@@ -196,7 +195,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> createTextDynamic(
|
||||
static Future<LoadingState<void>> createTextDynamic(
|
||||
Object content,
|
||||
) async {
|
||||
String csrf = Accounts.main.csrf;
|
||||
@@ -220,7 +219,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> removeDynamic({
|
||||
static Future<LoadingState<void>> removeDynamic({
|
||||
required Object dynIdStr,
|
||||
Object? dynType,
|
||||
Object? ridStr,
|
||||
@@ -244,7 +243,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> removeMsg(
|
||||
static Future<LoadingState<void>> removeMsg(
|
||||
Object talkerId,
|
||||
) async {
|
||||
String csrf = Accounts.main.csrf;
|
||||
@@ -268,7 +267,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delMsgfeed(
|
||||
static Future<LoadingState<void>> delMsgfeed(
|
||||
int tp,
|
||||
dynamic id,
|
||||
) async {
|
||||
@@ -292,7 +291,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> delSysMsg(
|
||||
static Future<LoadingState<void>> delSysMsg(
|
||||
Object id,
|
||||
) async {
|
||||
String csrf = Accounts.main.csrf;
|
||||
@@ -317,7 +316,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> setTop({
|
||||
static Future<LoadingState<void>> setTop({
|
||||
required Object talkerId,
|
||||
required int opType,
|
||||
}) async {
|
||||
@@ -344,7 +343,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
|
||||
// 消息标记已读
|
||||
static Future<LoadingState<Null>> ackSessionMsg({
|
||||
static Future<LoadingState<void>> ackSessionMsg({
|
||||
required int talkerId,
|
||||
required int ackSeqno,
|
||||
}) async {
|
||||
@@ -370,60 +369,60 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送私信
|
||||
static Future<LoadingState<Null>> sendMsg({
|
||||
int? senderUid,
|
||||
int? receiverId,
|
||||
int? msgType,
|
||||
dynamic content,
|
||||
}) async {
|
||||
String csrf = Accounts.main.csrf;
|
||||
final devId = getDevId();
|
||||
final data = {
|
||||
'msg': {
|
||||
'sender_uid': senderUid,
|
||||
'receiver_id': receiverId,
|
||||
'receiver_type': 1,
|
||||
'msg_type': msgType ?? 1,
|
||||
'msg_status': 0,
|
||||
'dev_id': devId,
|
||||
'timestamp': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
'new_face_version': 1,
|
||||
'content': content,
|
||||
},
|
||||
'from_firework': 0,
|
||||
'build': 0,
|
||||
'mobi_app': 'web',
|
||||
'csrf_token': csrf,
|
||||
'csrf': csrf,
|
||||
};
|
||||
Map<String, dynamic> params = await WbiSign.makSign(data);
|
||||
final res = await Request().post(
|
||||
Api.sendMsg,
|
||||
queryParameters: <String, dynamic>{
|
||||
'w_sender_uid': senderUid,
|
||||
'w_receiver_id': receiverId,
|
||||
'w_dev_id': devId,
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
},
|
||||
data: data,
|
||||
options: Options(
|
||||
contentType: Headers.formUrlEncodedContentType,
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return const Success(null);
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
}
|
||||
// // 发送私信
|
||||
// static Future<LoadingState<void>> sendMsg({
|
||||
// required int senderUid,
|
||||
// required int receiverId,
|
||||
// int? msgType,
|
||||
// dynamic content,
|
||||
// }) async {
|
||||
// String csrf = Accounts.main.csrf;
|
||||
// final devId = getDevId();
|
||||
// final data = {
|
||||
// 'msg': {
|
||||
// 'sender_uid': senderUid,
|
||||
// 'receiver_id': receiverId,
|
||||
// 'receiver_type': 1,
|
||||
// 'msg_type': msgType ?? 1,
|
||||
// 'msg_status': 0,
|
||||
// 'dev_id': devId,
|
||||
// 'timestamp': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||
// 'new_face_version': 1,
|
||||
// 'content': content,
|
||||
// },
|
||||
// 'from_firework': 0,
|
||||
// 'build': 0,
|
||||
// 'mobi_app': 'web',
|
||||
// 'csrf_token': csrf,
|
||||
// 'csrf': csrf,
|
||||
// };
|
||||
// Map<String, dynamic> params = await WbiSign.makSign(data);
|
||||
// final res = await Request().post(
|
||||
// Api.sendMsg,
|
||||
// queryParameters: <String, dynamic>{
|
||||
// 'w_sender_uid': senderUid,
|
||||
// 'w_receiver_id': receiverId,
|
||||
// 'w_dev_id': devId,
|
||||
// 'w_rid': params['w_rid'],
|
||||
// 'wts': params['wts'],
|
||||
// },
|
||||
// data: data,
|
||||
// options: Options(
|
||||
// contentType: Headers.formUrlEncodedContentType,
|
||||
// ),
|
||||
// );
|
||||
// if (res.data['code'] == 0) {
|
||||
// return const Success(null);
|
||||
// } else {
|
||||
// return Error(res.data['message']);
|
||||
// }
|
||||
// }
|
||||
|
||||
static String getDevId() {
|
||||
return const UuidV4().generate();
|
||||
}
|
||||
// static String getDevId() {
|
||||
// return const UuidV4().generate();
|
||||
// }
|
||||
|
||||
static Future<LoadingState<Null>> msgSetNotice({
|
||||
static Future<LoadingState<void>> msgSetNotice({
|
||||
required Object id,
|
||||
required int noticeState,
|
||||
}) async {
|
||||
@@ -451,7 +450,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> setMsgDnd({
|
||||
static Future<LoadingState<void>> setMsgDnd({
|
||||
required Object uid,
|
||||
required int setting,
|
||||
required dndUid,
|
||||
@@ -477,7 +476,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> setPushSs({
|
||||
static Future<LoadingState<void>> setPushSs({
|
||||
required int setting,
|
||||
required talkerUid,
|
||||
}) async {
|
||||
@@ -606,7 +605,7 @@ abstract final class MsgHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> imMsgReport({
|
||||
static Future<LoadingState<void>> imMsgReport({
|
||||
required int accusedUid,
|
||||
required int reasonType,
|
||||
required String reasonDesc,
|
||||
|
||||
@@ -23,7 +23,7 @@ abstract final class MusicHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> wishUpdate(
|
||||
static Future<LoadingState<void>> wishUpdate(
|
||||
String musicId,
|
||||
bool hasLike,
|
||||
) async {
|
||||
|
||||
@@ -133,7 +133,7 @@ abstract final class PgcHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> pgcReviewLike({
|
||||
static Future<LoadingState<void>> pgcReviewLike({
|
||||
required Object mediaId,
|
||||
required Object reviewId,
|
||||
}) async {
|
||||
@@ -154,7 +154,7 @@ abstract final class PgcHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> pgcReviewDislike({
|
||||
static Future<LoadingState<void>> pgcReviewDislike({
|
||||
required Object mediaId,
|
||||
required Object reviewId,
|
||||
}) async {
|
||||
@@ -175,7 +175,7 @@ abstract final class PgcHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> pgcReviewPost({
|
||||
static Future<LoadingState<void>> pgcReviewPost({
|
||||
required Object mediaId,
|
||||
required int score,
|
||||
required String content,
|
||||
@@ -199,7 +199,7 @@ abstract final class PgcHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> pgcReviewMod({
|
||||
static Future<LoadingState<void>> pgcReviewMod({
|
||||
required Object mediaId,
|
||||
required int score,
|
||||
required String content,
|
||||
@@ -223,7 +223,7 @@ abstract final class PgcHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> pgcReviewDel({
|
||||
static Future<LoadingState<void>> pgcReviewDel({
|
||||
required Object mediaId,
|
||||
required Object reviewId,
|
||||
}) async {
|
||||
|
||||
@@ -88,7 +88,7 @@ abstract final class ReplyHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> hateReply({
|
||||
static Future<LoadingState<void>> hateReply({
|
||||
required int type,
|
||||
required int action,
|
||||
required int oid,
|
||||
@@ -113,7 +113,7 @@ abstract final class ReplyHttp {
|
||||
}
|
||||
|
||||
// 评论点赞
|
||||
static Future<LoadingState<Null>> likeReply({
|
||||
static Future<LoadingState<void>> likeReply({
|
||||
required int type,
|
||||
required int oid,
|
||||
required int rpid,
|
||||
@@ -154,7 +154,7 @@ abstract final class ReplyHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> replyTop({
|
||||
static Future<LoadingState<void>> replyTop({
|
||||
required Object oid,
|
||||
required Object type,
|
||||
required Object rpid,
|
||||
@@ -178,7 +178,7 @@ abstract final class ReplyHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> report({
|
||||
static Future<LoadingState<void>> report({
|
||||
required Object rpid,
|
||||
required Object oid,
|
||||
required int reasonType,
|
||||
@@ -232,7 +232,7 @@ abstract final class ReplyHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> replySubjectModify({
|
||||
static Future<LoadingState<void>> replySubjectModify({
|
||||
required int oid,
|
||||
required int type,
|
||||
required int action,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:http2/http2.dart';
|
||||
|
||||
class RetryInterceptor extends Interceptor {
|
||||
final Dio _client;
|
||||
final int _count;
|
||||
final int _delay;
|
||||
|
||||
RetryInterceptor(this._count, this._delay);
|
||||
RetryInterceptor(this._client, this._count, this._delay);
|
||||
|
||||
@override
|
||||
void onError(DioException err, ErrorInterceptorHandler handler) {
|
||||
@@ -31,7 +31,7 @@ class RetryInterceptor extends Interceptor {
|
||||
..data = null
|
||||
..method = 'GET';
|
||||
}
|
||||
Request.dio
|
||||
_client
|
||||
.fetch(options)
|
||||
.then(
|
||||
(i) => handler.resolve(
|
||||
@@ -62,7 +62,7 @@ class RetryInterceptor extends Interceptor {
|
||||
Duration(
|
||||
milliseconds: ++err.requestOptions.extra['_rt'] * _delay,
|
||||
),
|
||||
() => Request.dio
|
||||
() => _client
|
||||
.fetch(err.requestOptions)
|
||||
.then(handler.resolve)
|
||||
.onError<DioException>((error, _) => handler.reject(error)),
|
||||
@@ -76,4 +76,7 @@ class RetryInterceptor extends Interceptor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RetryInterceptor copyWith({Dio? client, int? count, int? delay}) =>
|
||||
.new(client ?? _client, count ?? _count, delay ?? _delay);
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ abstract final class SponsorBlock {
|
||||
return getErrMsg(res);
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> voteOnSponsorTime({
|
||||
static Future<LoadingState<void>> voteOnSponsorTime({
|
||||
required String uuid,
|
||||
int? type,
|
||||
SegmentType? category,
|
||||
@@ -93,7 +93,7 @@ abstract final class SponsorBlock {
|
||||
return res.statusCode == 200 ? const Success(null) : getErrMsg(res);
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> viewedVideoSponsorTime(String uuid) async {
|
||||
static Future<LoadingState<void>> viewedVideoSponsorTime(String uuid) async {
|
||||
final res = await Request().post(
|
||||
_api(SponsorBlockApi.viewedVideoSponsorTime),
|
||||
data: {'UUID': uuid},
|
||||
@@ -102,7 +102,7 @@ abstract final class SponsorBlock {
|
||||
return res.statusCode == 200 ? const Success(null) : getErrMsg(res);
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> uptimeStatus() async {
|
||||
static Future<LoadingState<void>> uptimeStatus() async {
|
||||
final res = await Request().get(
|
||||
_api(SponsorBlockApi.uptimeStatus),
|
||||
options: options,
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:PiliPlus/models_new/history/data.dart';
|
||||
import 'package:PiliPlus/models_new/later/data.dart';
|
||||
import 'package:PiliPlus/models_new/login_log/data.dart';
|
||||
import 'package:PiliPlus/models_new/media_list/data.dart';
|
||||
import 'package:PiliPlus/models_new/relation/data.dart';
|
||||
import 'package:PiliPlus/models_new/space_setting/data.dart';
|
||||
import 'package:PiliPlus/models_new/sub/sub/data.dart';
|
||||
import 'package:PiliPlus/models_new/user_real_name/data.dart';
|
||||
@@ -105,7 +106,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
|
||||
// 暂停观看历史
|
||||
static Future<LoadingState<Null>> pauseHistory(
|
||||
static Future<LoadingState<void>> pauseHistory(
|
||||
bool switchStatus, {
|
||||
Account? account,
|
||||
}) async {
|
||||
@@ -144,7 +145,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
|
||||
// 清空历史记录
|
||||
static Future<LoadingState<Null>> clearHistory({Account? account}) async {
|
||||
static Future<LoadingState<void>> clearHistory({Account? account}) async {
|
||||
account ??= Accounts.history;
|
||||
final res = await Request().post(
|
||||
Api.clearHistory,
|
||||
@@ -165,7 +166,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
|
||||
// 稍后再看
|
||||
static Future<LoadingState<Null>> toViewLater({
|
||||
static Future<LoadingState<void>> toViewLater({
|
||||
String? bvid,
|
||||
Object? aid,
|
||||
}) async {
|
||||
@@ -189,7 +190,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
|
||||
// 移除已观看
|
||||
static Future<LoadingState<Null>> toViewDel({required String aids}) async {
|
||||
static Future<LoadingState<void>> toViewDel({required String aids}) async {
|
||||
final Map<String, dynamic> params = {
|
||||
'csrf': Accounts.main.csrf,
|
||||
'resources': aids,
|
||||
@@ -228,7 +229,7 @@ abstract final class UserHttp {
|
||||
// }
|
||||
|
||||
// 清空稍后再看 // clean_type: null->all, 1->invalid, 2->viewed
|
||||
static Future<LoadingState<Null>> toViewClear([int? cleanType]) async {
|
||||
static Future<LoadingState<void>> toViewClear([int? cleanType]) async {
|
||||
final res = await Request().post(
|
||||
Api.toViewClear,
|
||||
data: {
|
||||
@@ -245,7 +246,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
|
||||
// 删除历史记录
|
||||
static Future<LoadingState<Null>> delHistory(
|
||||
static Future<LoadingState<void>> delHistory(
|
||||
String kid, {
|
||||
Account? account,
|
||||
}) async {
|
||||
@@ -269,7 +270,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Map>> hasFollow(int mid) async {
|
||||
static Future<LoadingState<RelationData>> userRelation(int mid) async {
|
||||
final res = await Request().get(
|
||||
Api.relation,
|
||||
queryParameters: {
|
||||
@@ -277,7 +278,7 @@ abstract final class UserHttp {
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return Success(res.data['data']);
|
||||
return Success(RelationData.fromJson(res.data['data']));
|
||||
} else {
|
||||
return Error(res.data['message']);
|
||||
}
|
||||
@@ -390,7 +391,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> dynamicReport({
|
||||
static Future<LoadingState<void>> dynamicReport({
|
||||
required Object mid,
|
||||
required Object dynId,
|
||||
required int reasonType,
|
||||
@@ -430,7 +431,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> spaceSettingMod(Map data) async {
|
||||
static Future<LoadingState<void>> spaceSettingMod(Map data) async {
|
||||
final res = await Request().post(
|
||||
Api.spaceSettingMod,
|
||||
queryParameters: {
|
||||
@@ -446,7 +447,7 @@ abstract final class UserHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> vipExpAdd() async {
|
||||
static Future<LoadingState<void>> vipExpAdd() async {
|
||||
final res = await Request().post(
|
||||
Api.vipExpAdd,
|
||||
queryParameters: {
|
||||
|
||||
@@ -49,7 +49,7 @@ abstract final class VideoHttp {
|
||||
static bool enableFilter = zoneRegExp.pattern.isNotEmpty;
|
||||
|
||||
// 首页推荐视频
|
||||
static Future<LoadingState<List<RecVideoItemModel>>> rcmdVideoList({
|
||||
static Future<LoadingState<List<RcmdVideoItemModel>>> rcmdVideoList({
|
||||
required int ps,
|
||||
required int freshIdx,
|
||||
}) async {
|
||||
@@ -66,13 +66,13 @@ abstract final class VideoHttp {
|
||||
}),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemModel> list = <RecVideoItemModel>[];
|
||||
List<RcmdVideoItemModel> list = <RcmdVideoItemModel>[];
|
||||
for (final i in res.data['data']['item']) {
|
||||
//过滤掉live与ad,以及拉黑用户
|
||||
if (i['goto'] == 'av' &&
|
||||
(i['owner'] != null &&
|
||||
!GlobalData().blackMids.contains(i['owner']['mid']))) {
|
||||
RecVideoItemModel videoItem = RecVideoItemModel.fromJson(i);
|
||||
RcmdVideoItemModel videoItem = RcmdVideoItemModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
@@ -85,7 +85,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
|
||||
// 添加额外的loginState变量模拟未登录状态
|
||||
static Future<LoadingState<List<RecVideoItemAppModel>>> rcmdVideoListApp({
|
||||
static Future<LoadingState<List<RcmdVideoItemAppModel>>> rcmdVideoListApp({
|
||||
required int freshIdx,
|
||||
}) async {
|
||||
final params = {
|
||||
@@ -139,7 +139,7 @@ abstract final class VideoHttp {
|
||||
),
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
List<RecVideoItemAppModel> list = <RecVideoItemAppModel>[];
|
||||
List<RcmdVideoItemAppModel> list = <RcmdVideoItemAppModel>[];
|
||||
for (final i in res.data['data']['items']) {
|
||||
// 屏蔽推广和拉黑用户
|
||||
if (i['card_goto'] != 'ad_av' &&
|
||||
@@ -152,7 +152,7 @@ abstract final class VideoHttp {
|
||||
zoneRegExp.hasMatch(i['args']['tname'])) {
|
||||
continue;
|
||||
}
|
||||
RecVideoItemAppModel videoItem = RecVideoItemAppModel.fromJson(i);
|
||||
RcmdVideoItemAppModel videoItem = RcmdVideoItemAppModel.fromJson(i);
|
||||
if (!RecommendFilter.filter(videoItem)) {
|
||||
list.add(videoItem);
|
||||
}
|
||||
@@ -345,7 +345,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
|
||||
// 投币
|
||||
static Future<LoadingState<Null>> coinVideo({
|
||||
static Future<LoadingState<void>> coinVideo({
|
||||
required String bvid,
|
||||
required int multiply,
|
||||
int selectLike = 0,
|
||||
@@ -443,7 +443,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
|
||||
// (取消)点踩
|
||||
static Future<LoadingState<Null>> dislikeVideo({
|
||||
static Future<LoadingState<void>> dislikeVideo({
|
||||
required String bvid,
|
||||
required bool type,
|
||||
}) async {
|
||||
@@ -466,7 +466,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
|
||||
// 推送不感兴趣反馈
|
||||
static Future<LoadingState<Null>> feedDislike({
|
||||
static Future<LoadingState<void>> feedDislike({
|
||||
required String goto,
|
||||
required int id,
|
||||
int? reasonId,
|
||||
@@ -495,7 +495,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
|
||||
// 推送不感兴趣取消
|
||||
static Future<LoadingState<Null>> feedDislikeCancel({
|
||||
static Future<LoadingState<void>> feedDislikeCancel({
|
||||
required String goto,
|
||||
required int id,
|
||||
int? reasonId,
|
||||
@@ -564,7 +564,6 @@ abstract final class VideoHttp {
|
||||
replyInfo.id.toString(),
|
||||
(replyInfo.deepCopy()
|
||||
..unknownFields.clear()
|
||||
..clearMemberV2()
|
||||
..clearTrackInfo())
|
||||
.writeToBuffer(),
|
||||
);
|
||||
@@ -578,7 +577,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<LoadingState<Null>> replyDel({
|
||||
static Future<LoadingState<void>> replyDel({
|
||||
required int type, //replyType
|
||||
required int oid,
|
||||
required int rpid,
|
||||
@@ -602,7 +601,7 @@ abstract final class VideoHttp {
|
||||
}
|
||||
|
||||
// 操作用户关系
|
||||
static Future<LoadingState<Null>> relationMod({
|
||||
static Future<LoadingState<void>> relationMod({
|
||||
required int mid,
|
||||
required int act,
|
||||
required int reSrc,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:PiliPlus/build_config.dart';
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/widgets/back_detector.dart';
|
||||
import 'package:PiliPlus/common/widgets/custom_toast.dart';
|
||||
import 'package:PiliPlus/common/widgets/route_aware_mixin.dart';
|
||||
import 'package:PiliPlus/common/widgets/scale_app.dart';
|
||||
import 'package:PiliPlus/common/widgets/scroll_behavior.dart';
|
||||
import 'package:PiliPlus/http/init.dart';
|
||||
@@ -18,7 +19,6 @@ import 'package:PiliPlus/utils/date_utils.dart';
|
||||
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
|
||||
import 'package:PiliPlus/utils/extension/theme_ext.dart';
|
||||
import 'package:PiliPlus/utils/json_file_handler.dart';
|
||||
import 'package:PiliPlus/utils/page_utils.dart';
|
||||
import 'package:PiliPlus/utils/path_utils.dart';
|
||||
import 'package:PiliPlus/utils/platform_utils.dart';
|
||||
import 'package:PiliPlus/utils/request_utils.dart';
|
||||
@@ -250,26 +250,34 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dynamicColor = Pref.dynamicColor && _light != null && _dark != null;
|
||||
static (ThemeData, ThemeData) getAllTheme() {
|
||||
final dynamicColor = _light != null && _dark != null && Pref.dynamicColor;
|
||||
late final brandColor = colorThemeTypes[Pref.customColor].color;
|
||||
late final variant = Pref.schemeVariant;
|
||||
return GetMaterialApp(
|
||||
title: Constants.appName,
|
||||
theme: ThemeUtils.getThemeData(
|
||||
return (
|
||||
ThemeUtils.getThemeData(
|
||||
colorScheme: dynamicColor
|
||||
? _light!
|
||||
: brandColor.asColorSchemeSeed(variant, .light),
|
||||
isDynamic: dynamicColor,
|
||||
),
|
||||
darkTheme: ThemeUtils.getThemeData(
|
||||
ThemeUtils.getThemeData(
|
||||
isDark: true,
|
||||
colorScheme: dynamicColor
|
||||
? _dark!
|
||||
: brandColor.asColorSchemeSeed(variant, .dark),
|
||||
isDynamic: dynamicColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final (light, dark) = getAllTheme();
|
||||
return GetMaterialApp(
|
||||
title: Constants.appName,
|
||||
theme: light,
|
||||
darkTheme: dark,
|
||||
themeMode: Pref.themeMode,
|
||||
localizationsDelegates: const [
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
@@ -288,7 +296,7 @@ class MyApp extends StatelessWidget {
|
||||
builder: _builder,
|
||||
),
|
||||
navigatorObservers: [
|
||||
PageUtils.routeObserver,
|
||||
routeObserver,
|
||||
FlutterSmartDialog.observer,
|
||||
],
|
||||
scrollBehavior: PlatformUtils.isDesktop
|
||||
|
||||
11
lib/models/common/member/archive_order_type_app.dart
Normal file
11
lib/models/common/member/archive_order_type_app.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
|
||||
enum ArchiveOrderTypeApp with EnumWithLabel {
|
||||
pubdate('最新发布'),
|
||||
click('最多播放'),
|
||||
;
|
||||
|
||||
@override
|
||||
final String label;
|
||||
const ArchiveOrderTypeApp(this.label);
|
||||
}
|
||||
12
lib/models/common/member/archive_order_type_web.dart
Normal file
12
lib/models/common/member/archive_order_type_web.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
|
||||
enum ArchiveOrderTypeWeb with EnumWithLabel {
|
||||
pubdate('最新发布'),
|
||||
click('最多播放'),
|
||||
stow('最多收藏'),
|
||||
;
|
||||
|
||||
@override
|
||||
final String label;
|
||||
const ArchiveOrderTypeWeb(this.label);
|
||||
}
|
||||
11
lib/models/common/member/archive_sort_type_app.dart
Normal file
11
lib/models/common/member/archive_sort_type_app.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
import 'package:PiliPlus/models/common/enum_with_label.dart';
|
||||
|
||||
enum ArchiveSortTypeApp with EnumWithLabel {
|
||||
desc('默认'),
|
||||
asc('倒序'),
|
||||
;
|
||||
|
||||
@override
|
||||
final String label;
|
||||
const ArchiveSortTypeApp(this.label);
|
||||
}
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
|
||||
enum ContributeType {
|
||||
video,
|
||||
charging,
|
||||
season,
|
||||
series,
|
||||
bangumi,
|
||||
comic,
|
||||
video(Api.spaceArchive),
|
||||
charging(Api.spaceChargingArchive),
|
||||
season(Api.spaceSeason),
|
||||
series(Api.spaceSeries),
|
||||
bangumi(Api.spaceBangumi),
|
||||
comic(Api.spaceComic)
|
||||
;
|
||||
|
||||
final String api;
|
||||
const ContributeType(this.api);
|
||||
}
|
||||
|
||||
10
lib/models/common/member/web_ss_type.dart
Normal file
10
lib/models/common/member/web_ss_type.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
import 'package:PiliPlus/http/api.dart';
|
||||
|
||||
enum WebSsType {
|
||||
season(Api.seasonArchives),
|
||||
series(Api.seriesArchives),
|
||||
;
|
||||
|
||||
final String api;
|
||||
const WebSsType(this.api);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/common/constants.dart';
|
||||
import 'package:PiliPlus/common/style.dart' as common_style;
|
||||
import 'package:PiliPlus/models/dynamics/result.dart';
|
||||
import 'package:PiliPlus/models/dynamics/vote_model.dart';
|
||||
|
||||
@@ -49,7 +49,7 @@ class Pic {
|
||||
style = json['style'];
|
||||
liveUrl = json['live_url'];
|
||||
if (width != null && height != null) {
|
||||
isLongPic = (height! / width!) > StyleString.imgMaxRatio;
|
||||
isLongPic = (height! / width!) > common_style.Style.imgMaxRatio;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -340,6 +340,7 @@ class Common {
|
||||
int? style;
|
||||
String? subType;
|
||||
String? title;
|
||||
String? titlePrefix;
|
||||
|
||||
Common.fromJson(Map<String, dynamic> json) {
|
||||
cover = json['cover'];
|
||||
@@ -352,6 +353,7 @@ class Common {
|
||||
style = json['style'];
|
||||
subType = json['sub_type'];
|
||||
title = json['title'];
|
||||
titlePrefix = json['title_prefix'];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,8 @@ class DynamicItemModel {
|
||||
String? type;
|
||||
bool? visible;
|
||||
|
||||
late bool linkFolded = false;
|
||||
|
||||
// opus
|
||||
Fallback? fallback;
|
||||
|
||||
@@ -422,7 +424,7 @@ class ModuleAuthorModel extends Avatar {
|
||||
pubTime = json['pub_time'];
|
||||
pubTs = json['pub_ts'] == 0 ? null : Utils.safeToInt(json['pub_ts']);
|
||||
type = json['type'];
|
||||
if (PendantAvatar.showDynDecorate) {
|
||||
if (PendantAvatar.showDecorate) {
|
||||
decorate = json['decorate'] == null
|
||||
? null
|
||||
: Decorate.fromJson(json['decorate']);
|
||||
|
||||
@@ -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'];
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import 'package:PiliPlus/models/model_video.dart';
|
||||
import 'package:PiliPlus/utils/id_utils.dart';
|
||||
import 'package:PiliPlus/utils/num_utils.dart';
|
||||
|
||||
class RecVideoItemAppModel extends BaseRecVideoItemModel {
|
||||
class RcmdVideoItemAppModel extends BaseRcmdVideoItemModel {
|
||||
int? get id => aid;
|
||||
String? talkBack;
|
||||
|
||||
String? cardType;
|
||||
ThreePoint? threePoint;
|
||||
|
||||
RecVideoItemAppModel.fromJson(Map<String, dynamic> json) {
|
||||
RcmdVideoItemAppModel.fromJson(Map<String, dynamic> json) {
|
||||
aid = json['player_args']?['aid'] ?? int.tryParse(json['param'] ?? '0');
|
||||
bvid = json['bvid'] ?? IdUtils.av2bv(aid!);
|
||||
cid = json['player_args']?['cid'];
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:PiliPlus/models_new/video/video_detail/dimension.dart';
|
||||
import 'package:PiliPlus/pages/common/multi_select/base.dart';
|
||||
|
||||
// 稍后再看, 排行榜等网页返回也使用该类
|
||||
class HotVideoItemModel extends BaseRecVideoItemModel with MultiSelectData {
|
||||
class HotVideoItemModel extends BaseRcmdVideoItemModel with MultiSelectData {
|
||||
int? videos;
|
||||
int? tid;
|
||||
String? tname;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:PiliPlus/models/model_video.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
part 'model_owner.g.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:PiliPlus/models/model_owner.dart';
|
||||
import 'package:PiliPlus/models/model_video.dart';
|
||||
|
||||
abstract class BaseRecVideoItemModel extends BaseVideoItemModel {
|
||||
abstract class BaseRcmdVideoItemModel extends BaseVideoItemModel {
|
||||
String? goto;
|
||||
String? uri;
|
||||
String? rcmdReason;
|
||||
@@ -11,8 +11,8 @@ abstract class BaseRecVideoItemModel extends BaseVideoItemModel {
|
||||
String? pgcBadge;
|
||||
}
|
||||
|
||||
class RecVideoItemModel extends BaseRecVideoItemModel {
|
||||
RecVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||
class RcmdVideoItemModel extends BaseRcmdVideoItemModel {
|
||||
RcmdVideoItemModel.fromJson(Map<String, dynamic> json) {
|
||||
aid = json["id"];
|
||||
bvid = json["bvid"];
|
||||
cid = json["cid"];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/models/user/danmaku_rule.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
class RuleFilterAdapter extends TypeAdapter<RuleFilter> {
|
||||
@override
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:PiliPlus/utils/extension/map_ext.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
part 'info.g.dart';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:hive_ce/hive.dart';
|
||||
|
||||
part 'stat.g.dart';
|
||||
|
||||
|
||||
@@ -163,19 +163,15 @@ class Dash {
|
||||
video = (json['video'] as List?)
|
||||
?.map<VideoItem>((e) => VideoItem.fromJson(e))
|
||||
.toList();
|
||||
audio = (json['audio'] as List?)
|
||||
?.map<AudioItem>((e) => AudioItem.fromJson(e))
|
||||
.toList();
|
||||
if (json['dolby']?['audio'] case List list) {
|
||||
(audio ??= <AudioItem>[]).insertAll(
|
||||
0,
|
||||
list.map((e) => AudioItem.fromJson(e)),
|
||||
);
|
||||
}
|
||||
final flacAudio = json['flac']?['audio'];
|
||||
if (flacAudio != null) {
|
||||
(audio ??= <AudioItem>[]).insert(0, AudioItem.fromJson(flacAudio));
|
||||
}
|
||||
final audio = [
|
||||
if (json['flac']?['audio'] case Map<String, dynamic> flac)
|
||||
AudioItem.fromJson(flac),
|
||||
if (json['dolby']?['audio'] case List list)
|
||||
...list.map((e) => AudioItem.fromJson(e)),
|
||||
if (json['audio'] case List list)
|
||||
...list.map((e) => AudioItem.fromJson(e)),
|
||||
];
|
||||
this.audio = audio.isEmpty ? null : audio;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +377,11 @@ class Volume {
|
||||
i -= measuredI;
|
||||
measuredI = 0;
|
||||
}
|
||||
num measuredThreshold = this.measuredThreshold;
|
||||
if (measuredThreshold > 0) {
|
||||
measuredThreshold = 0;
|
||||
}
|
||||
|
||||
return 'LRA=$lra:I=$i:TP=$tp:offset=$offset:linear=true:measured_I=$measuredI:measured_LRA=$measuredLra:measured_TP=$measuredTp:measured_thresh=$measuredThreshold';
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:get/route_manager.dart';
|
||||
|
||||
class BiliDownloadEntryInfo with MultiSelectData {
|
||||
int mediaType;
|
||||
final bool hasDashAudio;
|
||||
bool hasDashAudio;
|
||||
bool isCompleted;
|
||||
int totalBytes;
|
||||
int downloadedBytes;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:PiliPlus/models_new/live/live_contribution_rank/medal_info.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart';
|
||||
|
||||
class LiveContributionRankItem {
|
||||
int? uid;
|
||||
@@ -6,7 +6,7 @@ class LiveContributionRankItem {
|
||||
String? face;
|
||||
int? rank;
|
||||
int? score;
|
||||
MedalInfo? medalInfo;
|
||||
UinfoMedal? uinfoMedal;
|
||||
|
||||
LiveContributionRankItem({
|
||||
this.uid,
|
||||
@@ -14,7 +14,7 @@ class LiveContributionRankItem {
|
||||
this.face,
|
||||
this.rank,
|
||||
this.score,
|
||||
this.medalInfo,
|
||||
this.uinfoMedal,
|
||||
});
|
||||
|
||||
factory LiveContributionRankItem.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -24,8 +24,8 @@ class LiveContributionRankItem {
|
||||
face: json['face'] as String?,
|
||||
rank: json['rank'] as int?,
|
||||
score: json['score'] as int?,
|
||||
medalInfo: json['medal_info'] == null
|
||||
uinfoMedal: json['uinfo']?['medal'] == null
|
||||
? null
|
||||
: MedalInfo.fromJson(json['medal_info'] as Map<String, dynamic>),
|
||||
: UinfoMedal.fromJson(json['uinfo']?['medal']),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
class MedalInfo {
|
||||
String? medalName;
|
||||
int? level;
|
||||
|
||||
MedalInfo({
|
||||
this.medalName,
|
||||
this.level,
|
||||
});
|
||||
|
||||
factory MedalInfo.fromJson(Map<String, dynamic> json) => MedalInfo(
|
||||
medalName: json['medal_name'] as String?,
|
||||
level: json['level'] as int?,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:PiliPlus/models/model_owner.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_danmaku/live_emote.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart';
|
||||
import 'package:PiliPlus/pages/danmaku/danmaku_model.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
|
||||
class DanmakuMsg {
|
||||
final String name;
|
||||
@@ -9,6 +11,7 @@ class DanmakuMsg {
|
||||
final BaseEmote? uemote;
|
||||
final Owner? reply;
|
||||
final LiveDanmaku extra;
|
||||
final UinfoMedal? medalInfo;
|
||||
|
||||
const DanmakuMsg({
|
||||
required this.name,
|
||||
@@ -17,6 +20,7 @@ class DanmakuMsg {
|
||||
this.uemote,
|
||||
this.reply,
|
||||
required this.extra,
|
||||
this.medalInfo,
|
||||
});
|
||||
|
||||
factory DanmakuMsg.fromPrefetch(Map<String, dynamic> obj) {
|
||||
@@ -36,6 +40,7 @@ class DanmakuMsg {
|
||||
);
|
||||
}
|
||||
}
|
||||
final medal = user['medal'];
|
||||
return DanmakuMsg(
|
||||
name: user['base']['name'],
|
||||
text: obj['text'],
|
||||
@@ -51,6 +56,9 @@ class DanmakuMsg {
|
||||
ts: checkInfo['ts'],
|
||||
ct: checkInfo['ct'],
|
||||
),
|
||||
medalInfo: !GlobalData().showMedal || medal == null
|
||||
? null
|
||||
: UinfoMedal.fromJson(medal),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -61,5 +69,6 @@ class DanmakuMsg {
|
||||
'uemote': ?uemote?.toJson(),
|
||||
'reply': ?reply?.toJson(),
|
||||
'extra': extra.toJson(),
|
||||
'medal': ?medalInfo?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ class BaseEmote {
|
||||
late String emoticonUnique;
|
||||
late double width;
|
||||
late double height;
|
||||
late final isUpower = emoticonUnique.startsWith('upower_');
|
||||
late final isOfficial = emoticonUnique.startsWith('official_');
|
||||
|
||||
BaseEmote.fromJson(Map<String, dynamic> json) {
|
||||
url = json['url'];
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:PiliPlus/models_new/live/live_feed_index/watched_show.dart';
|
||||
import 'package:PiliPlus/utils/parse_string.dart';
|
||||
|
||||
class CardLiveItem {
|
||||
int? roomid;
|
||||
@@ -6,6 +7,8 @@ class CardLiveItem {
|
||||
String? uname;
|
||||
String? face;
|
||||
String? cover;
|
||||
String? _systemCover;
|
||||
String? get systemCover => _systemCover ?? cover;
|
||||
String? title;
|
||||
int? liveTime;
|
||||
String? areaName;
|
||||
@@ -21,6 +24,7 @@ class CardLiveItem {
|
||||
this.uname,
|
||||
this.face,
|
||||
this.cover,
|
||||
String? systemCover,
|
||||
this.title,
|
||||
this.liveTime,
|
||||
this.areaName,
|
||||
@@ -29,7 +33,7 @@ class CardLiveItem {
|
||||
this.areaV2ParentName,
|
||||
this.areaV2ParentId,
|
||||
this.watchedShow,
|
||||
});
|
||||
}) : _systemCover = noneNullOrEmptyString(systemCover);
|
||||
|
||||
factory CardLiveItem.fromJson(Map<String, dynamic> json) => CardLiveItem(
|
||||
roomid: json['roomid'] ?? json['id'],
|
||||
@@ -37,6 +41,7 @@ class CardLiveItem {
|
||||
uname: json['uname'] as String?,
|
||||
face: json['face'] as String?,
|
||||
cover: json['cover'] as String?,
|
||||
systemCover: json['system_cover'],
|
||||
title: json['title'] as String?,
|
||||
liveTime: json['live_time'] as int?,
|
||||
areaName: json['area_name'] as String?,
|
||||
|
||||
30
lib/models_new/live/live_medal_wall/data.dart
Normal file
30
lib/models_new/live/live_medal_wall/data.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/item.dart';
|
||||
|
||||
class MedalWallData {
|
||||
List<MedalWallItem>? list;
|
||||
int? count;
|
||||
String? name;
|
||||
String? icon;
|
||||
int? uid;
|
||||
int? level;
|
||||
|
||||
MedalWallData({
|
||||
this.list,
|
||||
this.count,
|
||||
this.name,
|
||||
this.icon,
|
||||
this.uid,
|
||||
this.level,
|
||||
});
|
||||
|
||||
factory MedalWallData.fromJson(Map<String, dynamic> json) => MedalWallData(
|
||||
list: (json['list'] as List<dynamic>?)
|
||||
?.map((e) => MedalWallItem.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
count: json['count'] as int?,
|
||||
name: json['name'] as String?,
|
||||
icon: json['icon'] as String?,
|
||||
uid: json['uid'] as int?,
|
||||
level: json['level'] as int?,
|
||||
);
|
||||
}
|
||||
36
lib/models_new/live/live_medal_wall/item.dart
Normal file
36
lib/models_new/live/live_medal_wall/item.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/medal_info.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart';
|
||||
|
||||
class MedalWallItem {
|
||||
MedalInfo? medalInfo;
|
||||
String? targetName;
|
||||
String? targetIcon;
|
||||
String? link;
|
||||
int? liveStatus;
|
||||
int? official;
|
||||
UinfoMedal? uinfoMedal;
|
||||
|
||||
MedalWallItem({
|
||||
this.medalInfo,
|
||||
this.targetName,
|
||||
this.targetIcon,
|
||||
this.link,
|
||||
this.liveStatus,
|
||||
this.official,
|
||||
this.uinfoMedal,
|
||||
});
|
||||
|
||||
factory MedalWallItem.fromJson(Map<String, dynamic> json) => MedalWallItem(
|
||||
medalInfo: json['medal_info'] == null
|
||||
? null
|
||||
: MedalInfo.fromJson(json['medal_info'] as Map<String, dynamic>),
|
||||
targetName: json['target_name'] as String?,
|
||||
targetIcon: json['target_icon'] as String?,
|
||||
link: json['link'] as String?,
|
||||
liveStatus: json['live_status'] as int?,
|
||||
official: json['official'] as int?,
|
||||
uinfoMedal: json['uinfo_medal'] == null
|
||||
? null
|
||||
: UinfoMedal.fromJson(json['uinfo_medal'] as Map<String, dynamic>),
|
||||
);
|
||||
}
|
||||
11
lib/models_new/live/live_medal_wall/medal_info.dart
Normal file
11
lib/models_new/live/live_medal_wall/medal_info.dart
Normal file
@@ -0,0 +1,11 @@
|
||||
class MedalInfo {
|
||||
int? wearingStatus;
|
||||
|
||||
MedalInfo({
|
||||
this.wearingStatus,
|
||||
});
|
||||
|
||||
factory MedalInfo.fromJson(Map<String, dynamic> json) => MedalInfo(
|
||||
wearingStatus: json['wearing_status'] as int?,
|
||||
);
|
||||
}
|
||||
35
lib/models_new/live/live_medal_wall/uinfo_medal.dart
Normal file
35
lib/models_new/live/live_medal_wall/uinfo_medal.dart
Normal file
@@ -0,0 +1,35 @@
|
||||
class UinfoMedal {
|
||||
String? name;
|
||||
int? level;
|
||||
int? id;
|
||||
int? ruid;
|
||||
String? v2MedalColorStart;
|
||||
String? v2MedalColorText;
|
||||
|
||||
UinfoMedal({
|
||||
this.name,
|
||||
this.level,
|
||||
this.id,
|
||||
this.ruid,
|
||||
this.v2MedalColorStart,
|
||||
this.v2MedalColorText,
|
||||
});
|
||||
|
||||
factory UinfoMedal.fromJson(Map<String, dynamic> json) => UinfoMedal(
|
||||
name: json['name'] as String?,
|
||||
level: json['level'] as int?,
|
||||
id: json['id'] as int?,
|
||||
ruid: json['ruid'] as int?,
|
||||
v2MedalColorStart: json['v2_medal_color_start'] as String?,
|
||||
v2MedalColorText: json['v2_medal_color_text'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'level': level,
|
||||
'id': id,
|
||||
'ruid': ruid,
|
||||
'v2_medal_color_start': v2MedalColorStart,
|
||||
'v2_medal_color_text': v2MedalColorText,
|
||||
};
|
||||
}
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart';
|
||||
import 'package:PiliPlus/models_new/live/live_superchat/user_info.dart';
|
||||
import 'package:PiliPlus/utils/global_data.dart';
|
||||
import 'package:PiliPlus/utils/parse_string.dart';
|
||||
import 'package:PiliPlus/utils/utils.dart';
|
||||
|
||||
class SuperChatItem {
|
||||
int id;
|
||||
int uid;
|
||||
int price;
|
||||
String? backgroundImage;
|
||||
String backgroundColor;
|
||||
String backgroundBottomColor;
|
||||
String backgroundPriceColor;
|
||||
@@ -16,11 +20,13 @@ class SuperChatItem {
|
||||
UserInfo userInfo;
|
||||
late bool expired = false;
|
||||
late bool deleted = false;
|
||||
UinfoMedal? medalInfo;
|
||||
|
||||
SuperChatItem({
|
||||
required this.id,
|
||||
required this.uid,
|
||||
required this.price,
|
||||
this.backgroundImage,
|
||||
required this.backgroundColor,
|
||||
required this.backgroundBottomColor,
|
||||
required this.backgroundPriceColor,
|
||||
@@ -30,6 +36,7 @@ class SuperChatItem {
|
||||
required this.token,
|
||||
required this.ts,
|
||||
required this.userInfo,
|
||||
this.medalInfo,
|
||||
});
|
||||
|
||||
static SuperChatItem get random => SuperChatItem.fromJson({
|
||||
@@ -44,12 +51,23 @@ class SuperChatItem {
|
||||
},
|
||||
'token': '',
|
||||
'ts': 0,
|
||||
'uinfo': {
|
||||
'medal': {
|
||||
"name": "Medal",
|
||||
"level": Utils.random.nextInt(40),
|
||||
"id": 123,
|
||||
"ruid": 456,
|
||||
"v2_medal_color_start": "#4C7DFF99",
|
||||
"v2_medal_color_text": "#FFFFFF",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
factory SuperChatItem.fromJson(Map<String, dynamic> json) => SuperChatItem(
|
||||
id: Utils.safeToInt(json['id']) ?? Utils.random.nextInt(2147483647),
|
||||
uid: Utils.safeToInt(json['uid'])!,
|
||||
price: json['price'],
|
||||
backgroundImage: noneNullOrEmptyString(json['background_image']),
|
||||
backgroundColor: json['background_color'] ?? '#EDF5FF',
|
||||
backgroundBottomColor: json['background_bottom_color'] ?? '#2A60B2',
|
||||
backgroundPriceColor: json['background_price_color'] ?? '#7497CD',
|
||||
@@ -59,6 +77,9 @@ class SuperChatItem {
|
||||
token: json['token'],
|
||||
ts: Utils.safeToInt(json['ts'])!,
|
||||
userInfo: UserInfo.fromJson(json['user_info'] as Map<String, dynamic>),
|
||||
medalInfo: !GlobalData().showMedal || json['uinfo']?['medal'] == null
|
||||
? null
|
||||
: UinfoMedal.fromJson(json['uinfo']['medal']),
|
||||
);
|
||||
|
||||
SuperChatItem copyWith({
|
||||
@@ -75,6 +96,7 @@ class SuperChatItem {
|
||||
int? ts,
|
||||
UserInfo? userInfo,
|
||||
bool? expired,
|
||||
UinfoMedal? medalInfo,
|
||||
}) {
|
||||
return SuperChatItem(
|
||||
id: id ?? this.id,
|
||||
@@ -90,6 +112,7 @@ class SuperChatItem {
|
||||
token: token ?? this.token,
|
||||
ts: ts ?? this.ts,
|
||||
userInfo: userInfo ?? this.userInfo,
|
||||
medalInfo: medalInfo ?? this.medalInfo,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -97,6 +120,7 @@ class SuperChatItem {
|
||||
'id': id,
|
||||
'uid': uid,
|
||||
'price': price,
|
||||
'background_image': backgroundImage,
|
||||
'background_color': backgroundColor,
|
||||
'background_bottom_color': backgroundBottomColor,
|
||||
'background_price_color': backgroundPriceColor,
|
||||
@@ -106,5 +130,6 @@ class SuperChatItem {
|
||||
'token': token,
|
||||
'ts': ts,
|
||||
'user_info': userInfo.toJson(),
|
||||
'medal': ?medalInfo?.toJson(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,22 +1,13 @@
|
||||
import 'package:PiliPlus/models_new/member/search_archive/episodic_button.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/list.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/page.dart';
|
||||
|
||||
class SearchArchiveData {
|
||||
SearchArchiveList? list;
|
||||
Page? page;
|
||||
EpisodicButton? episodicButton;
|
||||
bool? isRisk;
|
||||
int? gaiaResType;
|
||||
dynamic gaiaData;
|
||||
|
||||
SearchArchiveData({
|
||||
this.list,
|
||||
this.page,
|
||||
this.episodicButton,
|
||||
this.isRisk,
|
||||
this.gaiaResType,
|
||||
this.gaiaData,
|
||||
});
|
||||
|
||||
factory SearchArchiveData.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -27,13 +18,5 @@ class SearchArchiveData {
|
||||
page: json['page'] == null
|
||||
? null
|
||||
: Page.fromJson(json['page'] as Map<String, dynamic>),
|
||||
episodicButton: json['episodic_button'] == null
|
||||
? null
|
||||
: EpisodicButton.fromJson(
|
||||
json['episodic_button'] as Map<String, dynamic>,
|
||||
),
|
||||
isRisk: json['is_risk'] as bool?,
|
||||
gaiaResType: json['gaia_res_type'] as int?,
|
||||
gaiaData: json['gaia_data'] as dynamic,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
class EpisodicButton {
|
||||
String? text;
|
||||
String? uri;
|
||||
|
||||
EpisodicButton({this.text, this.uri});
|
||||
|
||||
factory EpisodicButton.fromJson(Map<String, dynamic> json) {
|
||||
return EpisodicButton(
|
||||
text: json['text'] as String?,
|
||||
uri: json['uri'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,26 @@
|
||||
import 'package:PiliPlus/models_new/member/search_archive/slist.dart';
|
||||
import 'package:PiliPlus/models_new/member/search_archive/vlist.dart';
|
||||
|
||||
class SearchArchiveList {
|
||||
List<ListTag>? tags;
|
||||
List<VListItemModel>? vlist;
|
||||
|
||||
SearchArchiveList({this.vlist});
|
||||
|
||||
factory SearchArchiveList.fromJson(Map<String, dynamic> json) =>
|
||||
SearchArchiveList(
|
||||
vlist: (json['vlist'] as List<dynamic>?)
|
||||
?.map((e) => VListItemModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
SearchArchiveList.fromJson(Map<String, dynamic> json) {
|
||||
vlist = (json['vlist'] as List<dynamic>?)
|
||||
?.map((e) => VListItemModel.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
tags = (json['slist'] as List<dynamic>?)
|
||||
?.map((e) => ListTag.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
(json['tlist'] as Map<String, dynamic>?)?.forEach((k, v) {
|
||||
if (k == '196') {
|
||||
if (tags == null) {
|
||||
tags = [ListTag.fromJson(v)];
|
||||
} else {
|
||||
tags!.add(ListTag.fromJson(v));
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
15
lib/models_new/member/search_archive/slist.dart
Normal file
15
lib/models_new/member/search_archive/slist.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
class ListTag {
|
||||
int? tid;
|
||||
int? count;
|
||||
String? name;
|
||||
String? specialType;
|
||||
|
||||
ListTag({this.tid, this.count, this.name, this.specialType});
|
||||
|
||||
factory ListTag.fromJson(Map<String, dynamic> json) => ListTag(
|
||||
tid: json['tid'] as int?,
|
||||
count: json['count'] as int?,
|
||||
name: json['name'] as String?,
|
||||
specialType: json['special_type'] as String?,
|
||||
);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user