Compare commits

...

30 Commits

Author SHA1 Message Date
dom
62c2c081d9 Revert "fix: macOS Media Control not activated & remove Background Play switch on desktop (#1872)"
This reverts commit 8f00ca5680.
2026-04-19 20:18:14 +08:00
dom
82483b33fc opt live emote
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-30 00:02:58 +08:00
My-Responsitories
886c53c7d8 opt: m3e loading (#1877)
* opt: loading

* feat: refresh m3e

* restore refreshIndicator

---------

Co-authored-by: dom <githubaccount56556@proton.me>
2026-03-29 23:34:04 +08:00
dom
f0050dd6e6 fix pendent offset
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-29 11:47:58 +08:00
dom
e6a2f65b4e fix reply up badge
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-29 11:47:58 +08:00
dom
2fc3f9864f opt ui
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-29 10:51:52 +08:00
dom
64c05a1b06 upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-29 10:02:00 +08:00
ninatan777
7c4e20f96c safely parse up list (#1876)
* fix: 兼容 mid/roomId/count 字段为字符串或数字类型

* update

---------

Co-authored-by: dom <githubaccount56556@proton.me>
2026-03-29 10:01:10 +08:00
dom
ace286753c flutter 3.41.6
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-27 10:33:15 +08:00
dom
f0430eba9f opt player bar
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-26 18:34:57 +08:00
dom
bbcceb72a7 upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-26 10:27:21 +08:00
dom
be4fa6ad2c build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-25 20:25:54 +08:00
dom
50e1f77e10 log player error instead of toast
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-25 20:22:07 +08:00
dom
ba56b45038 clamp archive page
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-25 15:01:35 +08:00
dom
b4b3764e5f web archive
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-25 12:59:29 +08:00
dom
2220372e4f tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-25 12:58:49 +08:00
dom
0957dfc66e fix ios build
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-23 14:50:57 +08:00
dom
9578f948b4 tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-23 13:47:20 +08:00
dom
1724f0d202 upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-23 11:14:31 +08:00
dom
2bebf200df show user medal
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-23 11:03:24 +08:00
dom
fc7fc18b14 tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-22 16:35:08 +08:00
0x535A
8f00ca5680 fix: macOS Media Control not activated & remove Background Play switch on desktop (#1872)
* fix: macOS Media Control not activated

* fix: remove Background Play switch on desktop

asdf

Update lib/main.dart

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-22 16:25:31 +08:00
dom
236b524445 opt image viewer gesture
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-22 13:53:49 +08:00
dom
ae59d257c3 show medal wall
show user follow time

show top image title

Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-22 13:53:44 +08:00
dom
662ccfcf0a upgrade deps
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-19 20:34:36 +08:00
dom
b7ab3655c4 flutter 3.41.5
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-19 20:34:36 +08:00
Starfallen
eda04b32a4 fix(player): clamp loudnorm measured_thresh parameters to valid range (#1871)
Co-authored-by: ci <example@example.com>
2026-03-19 08:30:17 +00:00
dom
9b1ae39922 m3e loading indicator
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-18 09:11:08 +08:00
dom
d1497115da tweaks
Signed-off-by: dom <githubaccount56556@proton.me>
2026-03-18 08:50:07 +08:00
HeXis-YS
7f2682bb7b opt(download): force cache downloads to use http/1.1 (#1870)
* opt(download): force cache downloads to use http/1.1

* refactor(http): lazily initialize fallback http/1.1 client

* fix(http): keep fallback client decision consistent at startup

* opt: use clone

* fix

* fix

---------

Co-authored-by: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com>
2026-03-17 12:43:03 +00:00
272 changed files with 4445 additions and 2326 deletions

2
.fvmrc
View File

@@ -1,3 +1,3 @@
{
"flutter": "3.41.4"
"flutter": "3.41.6"
}

View File

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

View File

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

View File

@@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>

View File

@@ -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
View 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',
];
}

View File

@@ -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 = 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,
);
}
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
View 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),
];
}

View File

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

View File

@@ -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: [

View File

@@ -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: [

View File

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

View File

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

View File

@@ -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)),
],

View File

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

View File

@@ -2,7 +2,7 @@ import 'dart:async' show FutureOr;
import 'dart:convert' show utf8, jsonDecode;
import 'dart:io' show File;
import 'package:PiliPlus/common/constants.dart' show StyleString;
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';
@@ -155,7 +155,7 @@ void importFromInput<T>(
context: context,
builder: (context) => AlertDialog(
title: Text('输入$title'),
constraints: StyleString.dialogFixedConstraints,
constraints: Style.dialogFixedConstraints,
content: TextFormField(
key: key,
minLines: 4,

View 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;
}
}

View File

@@ -217,8 +217,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
RefreshIndicatorStatus? _status;
late Future<void> _pendingRefreshFuture;
double? _dragOffset;
late Color _effectiveValueColor =
widget.color ?? Theme.of(context).colorScheme.primary;
late Color _effectiveValueColor;
// late Color _backgroundColor;
static final Animatable<double> _threeQuarterTween = Tween<double>(
begin: 0.0,
@@ -274,9 +274,10 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
void _setupColorTween() {
final colorScheme = ColorScheme.of(context);
// _backgroundColor = colorScheme.surfaceContainerHighest;
// Reset the current value color.
_effectiveValueColor =
widget.color ?? Theme.of(context).colorScheme.primary;
_effectiveValueColor = widget.color ?? colorScheme.primary;
final Color color = _effectiveValueColor;
if (color.a == 0) {
// Set an always stopped animation instead of a driven tween.
@@ -558,14 +559,52 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
bool _onDrag(double offset, double viewportDimension) {
if (_positionController.value > 0.0 &&
_status == RefreshIndicatorStatus.drag) {
if (_positionController.value > 0.0 && _status == .drag) {
_dragOffset = _dragOffset! + offset;
_checkDragOffset(viewportDimension);
return true;
}
return false;
}
// late final _refreshKey = GlobalKey();
// Widget _m3eRefreshProgressIndicator(bool showIndeterminateIndicator) {
// const indicatorMargin = EdgeInsets.all(4);
// const indicatorPadding = EdgeInsets.all(6);
// const indicatorSize = 41.0;
// final progress = _value.value;
// return Padding(
// padding: indicatorMargin,
// child: SizedBox(
// width: indicatorSize,
// height: indicatorSize,
// child: Material(
// type: MaterialType.circle,
// color: _backgroundColor,
// elevation: widget.elevation,
// child: Padding(
// padding: indicatorPadding,
// child: showIndeterminateIndicator
// ? M3ELoadingIndicator(
// childKey: _refreshKey,
// color: _effectiveValueColor,
// morphs: Morphs.refreshMorphs,
// size: null,
// )
// : RawM3ELoadingIndicator(
// key: _refreshKey,
// morph: Morphs.manualMorph,
// progress: progress,
// angle: -progress * math.pi,
// color: _valueColor.value!,
// size: null,
// ),
// ),
// ),
// ),
// );
// }
}
// ignore: camel_case_types

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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';
@@ -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;
@@ -116,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;
@@ -213,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),

View File

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

View File

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

View File

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

View File

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

View File

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

View 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);
}
}

View 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,
// );
}

View File

@@ -1,168 +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;
final bool isMemberAvatar;
const PendantAvatar({
const PendantAvatar(
this.url, {
super.key,
required this.avatar,
required this.size,
this.isMemberAvatar = false,
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 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 + (isMemberAvatar ? 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(

View File

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

View File

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

View File

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

View File

@@ -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(),

View File

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

View File

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

3
lib/http/error_msg.dart Normal file
View File

@@ -0,0 +1,3 @@
const errorMsg = {
-352: '风控校验失败,请检查登录状态',
};

View File

@@ -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']);
}
}
}

View File

@@ -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']);
}
}
}

View File

@@ -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) {

View File

@@ -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';
@@ -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']);
}
}
}

View File

@@ -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']);
}
}
@@ -551,7 +588,7 @@ abstract final class MemberHttp {
),
);
} else {
return Error(res.data['message']);
return Error(errorMsg[res.data['code']] ?? res.data['message']);
}
}

View File

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

View File

@@ -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';
@@ -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']);
}

View File

@@ -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);
}
@@ -564,7 +564,6 @@ abstract final class VideoHttp {
replyInfo.id.toString(),
(replyInfo.deepCopy()
..unknownFields.clear()
..clearMemberV2()
..clearTrackInfo())
.writeToBuffer(),
);

View 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);
}

View 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);
}

View 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);
}

View File

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

View 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);
}

View File

@@ -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'];
}
}

View File

@@ -424,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']);

View File

@@ -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'];
}

View File

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

View File

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

View File

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

View File

@@ -377,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';
}

View File

@@ -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']),
);
}

View File

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

View File

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

View File

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

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

View 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>),
);
}

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

View 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,
};
}

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -2,31 +2,16 @@ import 'package:PiliPlus/models/model_video.dart';
import 'package:PiliPlus/utils/duration_utils.dart';
class VListItemModel extends BaseVideoItemModel {
int? comment;
int? typeid;
String? subtitle;
String? copyright;
int? review;
bool? hideClick;
bool? isChargingSrc;
VListItemModel.fromJson(Map<String, dynamic> json) {
comment = json['comment'];
typeid = json['typeid'];
cover = json['pic'];
subtitle = json['subtitle'];
desc = json['description'];
copyright = json['copyright'];
title = json['title'];
review = json['review'];
pubdate = json['created'];
if (json['length'] != null) {
duration = DurationUtils.parseDuration(json['length']);
}
aid = json['aid'];
bvid = json['bvid'];
hideClick = json['hide_click'];
isChargingSrc = json['is_charging_arc'];
stat = VListStat.fromJson(json);
owner = VListOwner.fromJson(json);
}

View File

@@ -0,0 +1,28 @@
import 'package:PiliPlus/models/model_video.dart';
class SeasonArchive extends BaseVideoItemModel {
SeasonArchive.fromJson(Map<String, dynamic> json) {
aid = json['aid'];
bvid = json['bvid'];
cover = json['pic'];
title = json['title'];
pubdate = json['pubdate'];
duration = json['duration'];
stat = ArchiveStat.fromJson(json['stat']);
owner = ArchiveOwner.fromJson(json);
}
}
class ArchiveOwner extends BaseOwner {
ArchiveOwner.fromJson(Map<String, dynamic> json) {
mid = json['upMid'];
name = '';
}
}
class ArchiveStat extends BaseStat {
ArchiveStat.fromJson(Map<String, dynamic> json) {
view = json['view'];
danmu = json['danmaku'];
}
}

View File

@@ -0,0 +1,18 @@
import 'package:PiliPlus/models_new/member/season_web/archive.dart';
import 'package:PiliPlus/models_new/member/season_web/page.dart';
class SeasonWebData {
List<SeasonArchive>? archives;
Page? page;
SeasonWebData({this.archives, this.page});
factory SeasonWebData.fromJson(Map<String, dynamic> json) => SeasonWebData(
archives: (json['archives'] as List<dynamic>?)
?.map((e) => SeasonArchive.fromJson(e as Map<String, dynamic>))
.toList(),
page: json['page'] == null
? null
: Page.fromJson(json['page'] as Map<String, dynamic>),
);
}

View File

@@ -0,0 +1,13 @@
class Page {
int? pageNum;
int? pageSize;
int? total;
Page({this.pageNum, this.pageSize, this.total});
factory Page.fromJson(Map<String, dynamic> json) => Page(
pageNum: json['page_num'] ?? json['num'],
pageSize: json['page_size'] ?? json['size'],
total: json['total'] as int?,
);
}

View File

@@ -0,0 +1,25 @@
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
class RelationData {
int? mid;
int? attribute;
int? mtime;
List<int>? tag;
int? special;
RelationData({
this.mid,
this.attribute,
this.mtime,
this.tag,
this.special,
});
factory RelationData.fromJson(Map<String, dynamic> json) => RelationData(
mid: json['mid'] as int?,
attribute: json['attribute'] as int?,
mtime: json['mtime'] as int?,
tag: (json['tag'] as List<dynamic>?)?.fromCast(),
special: json['special'] as int?,
);
}

View File

@@ -1,28 +1,44 @@
class LiveFansWearing {
int? level;
String? medalName;
int? medalColorStart;
int? medalColorEnd;
int? medalColorBorder;
String? medalJumpUrl;
DetailV2? detailV2;
LiveFansWearing({
this.level,
this.medalName,
this.medalColorStart,
this.medalColorEnd,
this.medalColorBorder,
this.medalJumpUrl,
this.detailV2,
});
factory LiveFansWearing.fromJson(Map<String, dynamic> json) {
return LiveFansWearing(
level: json['level'] as int?,
medalName: json['medal_name'] as String?,
medalColorStart: json['medal_color_start'] as int?,
medalColorEnd: json['medal_color_end'] as int?,
medalColorBorder: json['medal_color_border'] as int?,
medalJumpUrl: json['medal_jump_url'] as String?,
detailV2: json['detail_v2'] == null
? null
: DetailV2.fromJson(json['detail_v2']),
);
}
}
class DetailV2 {
int? uid;
int? level;
String? medalColorName;
String? medalName;
int? medalId;
String? medalColor;
DetailV2({
this.uid,
this.level,
this.medalColorName,
this.medalName,
this.medalId,
this.medalColor,
});
factory DetailV2.fromJson(Map<String, dynamic> json) {
return DetailV2(
uid: json["uid"],
level: json["level"],
medalColorName: json["medal_color_name"],
medalName: json["medal_name"],
medalId: json["medal_id"],
medalColor: json["medal_color"],
);
}
}

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/parse_string.dart';
class Top {
@@ -20,16 +21,18 @@ class TopImage {
late final String fullCover;
String get header => _defaultImage ?? fullCover;
late final double dy;
TopTitle? title;
@pragma('vm:notify-debugger-on-exception')
TopImage.fromJson(Map<String, dynamic> json) {
_defaultImage = noneNullOrEmptyString(
json['item']['image']?['default_image'],
);
final item = json['item'];
final img = item['image'];
title = json['title'] == null ? null : TopTitle.fromJson(json['title']);
_defaultImage = noneNullOrEmptyString(img?['default_image']);
fullCover = json['cover'];
double dy = 0;
try {
final Map image = json['item']['image'] ?? json['item']['animation'];
final Map image = img ?? item['animation'];
if (image['location'] case String locStr when (locStr.isNotEmpty)) {
final location = locStr
.split('-')
@@ -48,3 +51,36 @@ class TopImage {
this.dy = dy;
}
}
class TopTitle {
String? title;
String? subTitle;
SubTitleColorFormat? subTitleColorFormat;
TopTitle({
this.title,
this.subTitle,
this.subTitleColorFormat,
});
factory TopTitle.fromJson(Map<String, dynamic> json) => TopTitle(
title: json["title"],
subTitle: json["sub_title"],
subTitleColorFormat: json["sub_title_color_format"] == null
? null
: SubTitleColorFormat.fromJson(json["sub_title_color_format"]),
);
}
class SubTitleColorFormat {
List<String>? colors;
SubTitleColorFormat({
this.colors,
});
factory SubTitleColorFormat.fromJson(Map<String, dynamic> json) =>
SubTitleColorFormat(
colors: (json["colors"] as List?)?.fromCast(),
);
}

View File

@@ -2,7 +2,9 @@ import 'dart:async';
import 'dart:io';
import 'package:PiliPlus/build_config.dart';
import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/dialog/export_import.dart';
import 'package:PiliPlus/common/widgets/flutter/list_tile.dart';
@@ -64,7 +66,7 @@ class _AboutPageState extends State<AboutPage> {
void _showDialog() => showDialog(
context: context,
builder: (context) => AlertDialog(
constraints: StyleString.dialogFixedConstraints,
constraints: Style.dialogFixedConstraints,
content: TextField(
autofocus: true,
onSubmitted: (value) {
@@ -108,7 +110,7 @@ class _AboutPageState extends State<AboutPage> {
height: 150,
excludeFromSemantics: true,
cacheWidth: 150.cacheSize(context),
'assets/images/logo/logo.png',
Assets.logo,
),
),
ListTile(
@@ -211,8 +213,8 @@ Commit Hash: ${BuildConfig.commitHash}''',
if (cacheSize.value.isNotEmpty) {
showConfirmDialog(
context: context,
title: '提示',
content: '该操作将清除图片及网络请求缓存数据,确认清除?',
title: const Text('提示'),
content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'),
onConfirm: () async {
SmartDialog.showLoading(msg: '正在清除...');
try {

View File

@@ -1,6 +1,6 @@
import 'dart:math' as math;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/models_new/article/article_view/ops.dart';
import 'package:PiliPlus/pages/dynamics/widgets/vote.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
@@ -68,7 +68,7 @@ class ArticleOpus extends StatelessWidget {
}
},
child: ClipRRect(
borderRadius: StyleString.mdRadius,
borderRadius: Style.mdRadius,
child: CachedNetworkImage(
width: width,
height: height,

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/widgets/image_viewer/hero.dart';
import 'package:PiliPlus/models/common/image_preview_type.dart';
import 'package:PiliPlus/utils/extension/num_ext.dart';
@@ -64,8 +65,7 @@ Widget htmlRender({
imageUrl: ImageUtils.thumbnailUrl(imgUrl, 60),
fadeInDuration: const Duration(milliseconds: 120),
fadeOutDuration: const Duration(milliseconds: 120),
placeholder: (context, url) =>
Image.asset('assets/images/loading.png'),
placeholder: (context, url) => Image.asset(Assets.loading),
),
),
);

View File

@@ -1,5 +1,6 @@
import 'dart:math' as math;
import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
import 'package:PiliPlus/common/widgets/image/cached_network_svg_image.dart';
@@ -231,8 +232,7 @@ class OpusContent extends StatelessWidget {
imageUrl: ImageUtils.thumbnailUrl(pic.url!, 60),
fadeInDuration: const Duration(milliseconds: 120),
fadeOutDuration: const Duration(milliseconds: 120),
placeholder: (_, _) =>
Image.asset('assets/images/loading.png'),
placeholder: (_, _) => Image.asset(Assets.loading),
);
if (!(pic.isLongPic ?? false)) {
child = fromHero(
@@ -715,18 +715,21 @@ Widget moduleBlockedItem(
) {
late final isDarkMode = theme.brightness.isDark;
BoxDecoration? bgImg() {
BoxDecoration? bgImg(double width) {
return moduleBlocked.bgImg == null
? null
: BoxDecoration(
image: DecorationImage(
fit: BoxFit.fill,
image: CachedNetworkImageProvider(
ImageUtils.thumbnailUrl(
isDarkMode
? moduleBlocked.bgImg!.imgDark
: moduleBlocked.bgImg!.imgDay,
image: ResizeImage(
CachedNetworkImageProvider(
ImageUtils.thumbnailUrl(
isDarkMode
? moduleBlocked.bgImg!.imgDark
: moduleBlocked.bgImg!.imgDay,
),
),
width: width.cacheSize(context),
),
),
);
@@ -755,9 +758,7 @@ Widget moduleBlockedItem(
padding: padding,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: visualDensity,
backgroundColor: isDarkMode
? const Color(0xFF8F0030)
: const Color(0xFFFF6699),
backgroundColor: theme.colorScheme.btnColor,
foregroundColor: Colors.white,
shape: shape,
),
@@ -793,7 +794,7 @@ Widget moduleBlockedItem(
return Container(
width: maxWidth,
height: maxWidth,
decoration: bgImg(),
decoration: bgImg(maxWidth),
padding: const EdgeInsets.all(12),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -822,43 +823,50 @@ Widget moduleBlockedItem(
),
);
}
return Container(
decoration: bgImg(),
padding: const EdgeInsets.all(12),
child: Row(
spacing: 8,
children: [
if (moduleBlocked.icon != null) icon(42),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
spacing: 2,
children: [
if (moduleBlocked.title?.isNotEmpty == true)
Text(moduleBlocked.title!),
if (moduleBlocked.hintMessage?.isNotEmpty == true)
Text(
moduleBlocked.hintMessage!,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.outline,
),
),
],
),
),
if (moduleBlocked.button != null)
btn(
context,
visualDensity: const VisualDensity(vertical: -3, horizontal: -4),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
return LayoutBuilder(
builder: (context, constraints) {
return Container(
decoration: bgImg(constraints.maxWidth),
padding: const EdgeInsets.all(12),
child: Row(
spacing: 8,
children: [
if (moduleBlocked.icon != null) icon(42),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
spacing: 2,
children: [
if (moduleBlocked.title?.isNotEmpty == true)
Text(moduleBlocked.title!),
if (moduleBlocked.hintMessage?.isNotEmpty == true)
Text(
moduleBlocked.hintMessage!,
style: TextStyle(
fontSize: 13,
color: theme.colorScheme.outline,
),
),
],
),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
],
),
if (moduleBlocked.button != null)
btn(
context,
visualDensity: const VisualDensity(
vertical: -3,
horizontal: -4,
),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
),
padding: const EdgeInsets.symmetric(horizontal: 10),
),
],
),
);
},
);
}

View File

@@ -1,4 +1,4 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
@@ -33,7 +33,7 @@ class ArticleListItem extends StatelessWidget {
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
horizontal: Style.safeSpace,
vertical: 5,
),
child: Row(
@@ -42,7 +42,7 @@ class ArticleListItem extends StatelessWidget {
children: [
if (item.imageUrls?.isNotEmpty == true)
AspectRatio(
aspectRatio: StyleString.aspectRatio,
aspectRatio: Style.aspectRatio,
child: LayoutBuilder(
builder: (context, boxConstraints) {
return NetworkImgLayer(

View File

@@ -1,6 +1,7 @@
import 'dart:math' show min;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/button/icon_button.dart';
import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
@@ -292,7 +293,7 @@ class _AudioPageState extends State<AudioPage> {
WidgetSpan(
alignment: .bottom,
child: Image.asset(
'assets/images/live.gif',
Assets.livingChart,
width: 16,
height: 16,
cacheWidth: 16.cacheSize(
@@ -336,7 +337,7 @@ class _AudioPageState extends State<AudioPage> {
WidgetSpan(
alignment: .bottom,
child: Image.asset(
'assets/images/live.gif',
Assets.livingChart,
width: 16,
height: 16,
cacheWidth: 16.cacheSize(
@@ -391,7 +392,7 @@ class _AudioPageState extends State<AudioPage> {
children: [
InkWell(
onTap: Get.back,
borderRadius: StyleString.bottomSheetRadius,
borderRadius: Style.bottomSheetRadius,
child: SizedBox(
height: 35,
child: Center(
@@ -460,7 +461,7 @@ class _AudioPageState extends State<AudioPage> {
children: [
InkWell(
onTap: Get.back,
borderRadius: StyleString.bottomSheetRadius,
borderRadius: Style.bottomSheetRadius,
child: SizedBox(
height: 35,
child: Center(
@@ -595,7 +596,7 @@ class _AudioPageState extends State<AudioPage> {
children: [
InkWell(
onTap: Get.back,
borderRadius: StyleString.bottomSheetRadius,
borderRadius: Style.bottomSheetRadius,
child: SizedBox(
height: 35,
child: Center(

View File

@@ -35,7 +35,7 @@ class BlackListController
void onRemove(BuildContext context, int index, name, mid) {
showConfirmDialog(
context: context,
title: '确定将 $name 移出黑名单?',
title: Text('确定将 $name 移出黑名单?'),
onConfirm: () async {
final result = await VideoHttp.relationMod(mid: mid, act: 6, reSrc: 11);
if (result.isSuccess) {

View File

@@ -1,4 +1,4 @@
import 'package:PiliPlus/common/constants.dart' show StyleString;
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/pages/home/controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:flutter/foundation.dart' show clampDouble;
@@ -58,7 +58,7 @@ abstract class CommonPageState<T extends StatefulWidget> extends State<T> {
_barOffset!.value = clampDouble(
_barOffset!.value + scrollDelta,
0.0,
StyleString.topBarHeight,
Style.topBarHeight,
);
}
@@ -78,7 +78,7 @@ abstract class CommonPageState<T extends StatefulWidget> extends State<T> {
final newValue = clampDouble(
value + scrollDelta,
0.0,
StyleString.topBarHeight,
Style.topBarHeight,
);
final offset = value - newValue;
if (offset != 0) {

View File

@@ -1,7 +1,7 @@
import 'dart:math' show pi;
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/skeleton/video_reply.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart';
import 'package:PiliPlus/common/widgets/view_safe_area.dart';
@@ -89,7 +89,7 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
},
),
TextButton.icon(
style: StyleString.buttonStyle,
style: Style.buttonStyle,
onPressed: controller.queryBySort,
icon: Icon(Icons.sort, size: 16, color: secondary),
label: Obx(
@@ -188,6 +188,7 @@ abstract class CommonDynPageState<T extends StatefulWidget> extends State<T>
isVideoDetail: !showBackBtn,
replyType: controller.replyType,
firstFloor: replyItem,
upMid: controller.upMid,
),
);
if (showBackBtn) {

View File

@@ -82,7 +82,7 @@ class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
Widget tabViewBuilder(final int tabIndex, List<SimpleRule> list) {
if (list.isEmpty) {
return scrollErrorWidget();
return scrollableError;
}
return ListView.builder(
itemCount: list.length,
@@ -97,7 +97,7 @@ class _DanmakuBlockPageState extends State<DanmakuBlockPage> {
icon: const Icon(Icons.delete_outlined),
onPressed: () => showConfirmDialog(
context: context,
title: '确定删除该规则?',
title: const Text('确定删除该规则?'),
onConfirm: () => _controller.danmakuFilterDel(
tabIndex,
itemIndex,

View File

@@ -7,6 +7,7 @@ import 'package:PiliPlus/pages/common/multi_select/base.dart'
import 'package:PiliPlus/services/download/download_service.dart';
import 'package:PiliPlus/utils/extension/iterable_ext.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/widgets.dart' show Text;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -76,7 +77,7 @@ class DownloadPageController extends GetxController
void onRemove() {
showConfirmDialog(
context: Get.context!,
title: '确定删除选中视频?',
title: const Text('确定删除选中视频?'),
onConfirm: () async {
SmartDialog.showLoading();
final watchProgress = GStorage.watchProgress;

View File

@@ -198,7 +198,7 @@ class _DownloadDetailPageState extends State<DownloadDetailPage>
void onRemove() {
showConfirmDialog(
context: context,
title: '确定删除选中视频?',
title: const Text('确定删除选中视频?'),
onConfirm: () async {
SmartDialog.showLoading();
final watchProgress = GStorage.watchProgress;

View File

@@ -1,6 +1,6 @@
import 'dart:io';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
@@ -73,7 +73,7 @@ class DetailItem extends StatelessWidget {
Get.back();
showConfirmDialog(
context: context,
title: '确定删除该视频?',
title: const Text('确定删除该视频?'),
onConfirm: onDelete,
);
},
@@ -158,7 +158,7 @@ class DetailItem extends StatelessWidget {
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
horizontal: Style.safeSpace,
vertical: 5,
),
child: Row(
@@ -168,7 +168,7 @@ class DetailItem extends StatelessWidget {
clipBehavior: Clip.none,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
aspectRatio: Style.aspectRatio,
child: LayoutBuilder(
builder: (context, constraints) {
final cover = File(
@@ -184,7 +184,7 @@ class DetailItem extends StatelessWidget {
}
return cover.existsSync()
? ClipRRect(
borderRadius: StyleString.mdRadius,
borderRadius: Style.mdRadius,
child: Image.file(
cover,
width: maxWidth,
@@ -443,7 +443,7 @@ class DetailItem extends StatelessWidget {
// ignore: deprecated_member_use
year2023: true,
minHeight: 2.5,
borderRadius: StyleString.mdRadius,
borderRadius: Style.mdRadius,
color: color,
backgroundColor: highlightColor,
value: progress,

View File

@@ -109,7 +109,7 @@ class _DownloadingPageState extends State<DownloadingPage>
void onRemove() {
showConfirmDialog(
context: context,
title: '确定删除选中视频?',
title: const Text('确定删除选中视频?'),
onConfirm: () async {
SmartDialog.showLoading();
final allChecked = this.allChecked.toSet();

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/pages/common/multi_select/base.dart'
import 'package:PiliPlus/pages/common/search/common_search_controller.dart';
import 'package:PiliPlus/services/download/download_service.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/widgets.dart' show Text;
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -52,7 +53,7 @@ class DownloadSearchController
void onRemove() {
showConfirmDialog(
context: Get.context!,
title: '确定删除选中视频?',
title: const Text('确定删除选中视频?'),
onConfirm: () async {
SmartDialog.showLoading();
final allChecked = this.allChecked.toSet();

View File

@@ -1,6 +1,6 @@
import 'dart:async';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/appbar/appbar.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
@@ -248,7 +248,7 @@ class _DownloadPageState extends State<DownloadPage> {
Get.back();
showConfirmDialog(
context: context,
title: '确定删除?',
title: const Text('确定删除?'),
onConfirm: () async {
await GStorage.watchProgress.deleteAll(
pageInfo.entries.map((e) => e.cid.toString()),
@@ -313,7 +313,7 @@ class _DownloadPageState extends State<DownloadPage> {
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
horizontal: Style.safeSpace,
vertical: 5,
),
child: Row(
@@ -323,7 +323,7 @@ class _DownloadPageState extends State<DownloadPage> {
clipBehavior: Clip.none,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
aspectRatio: Style.aspectRatio,
child: LayoutBuilder(
builder: (context, constraints) => NetworkImgLayer(
src: pageInfo.cover,

View File

@@ -1,4 +1,4 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/http/dynamics.dart';
@@ -21,7 +21,7 @@ Widget? addWidget(
late final Color bgColor = floor == 1
? theme.dividerColor.withValues(alpha: 0.08)
: theme.colorScheme.surface;
late final borderRadius = floor == 1 ? null : StyleString.mdRadius;
late final borderRadius = floor == 1 ? null : Style.mdRadius;
Widget? child;
try {
switch (type) {

View File

@@ -1,7 +1,9 @@
import 'dart:math';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/assets.dart';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/dialog/report.dart';
import 'package:PiliPlus/common/widgets/extra_hit_test_widget.dart';
import 'package:PiliPlus/common/widgets/pendant_avatar.dart';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/loading_state.dart';
@@ -52,21 +54,6 @@ class AuthorPanel extends StatelessWidget {
this.onSetReplySubject,
});
Widget _buildAvatar(ModuleAuthorModel moduleAuthor) {
final pendant = moduleAuthor.pendant?.image;
final hasPendant = pendant != null && pendant.isNotEmpty;
Widget avatar = PendantAvatar(
avatar: moduleAuthor.face,
size: hasPendant ? 34 : 40,
officialType: null, // 已被注释
garbPendantImage: pendant,
);
if (hasPendant) {
avatar = Padding(padding: const EdgeInsets.all(3), child: avatar);
}
return avatar;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@@ -105,147 +92,148 @@ class AuthorPanel extends StatelessWidget {
);
}
}
final moduleTagText = !isDetail ? item.modules.moduleTag?.text : null;
return Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Align(
alignment: Alignment.centerLeft,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: moduleAuthor.type == 'AUTHOR_TYPE_NORMAL'
? () {
feedBack();
Get.toNamed('/member?mid=${moduleAuthor.mid}');
}
: null,
child: Row(
spacing: 10,
mainAxisSize: MainAxisSize.min,
children: [
_buildAvatar(moduleAuthor),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
moduleAuthor.name ?? '',
style: TextStyle(
color:
moduleAuthor.vip != null &&
moduleAuthor.vip!.status > 0 &&
moduleAuthor.vip!.type == 2
? theme.colorScheme.vipColor
: theme.colorScheme.onSurface,
fontSize: theme.textTheme.titleSmall!.fontSize,
),
),
?pubTs,
],
),
],
Widget header = GestureDetector(
onTap: moduleAuthor.type == 'AUTHOR_TYPE_NORMAL'
? () {
feedBack();
Get.toNamed('/member?mid=${moduleAuthor.mid}');
}
: null,
child: ExtraHitTestWidget(
width: 50,
child: Row(
spacing: 10,
children: [
PendantAvatar(
size: 40,
moduleAuthor.face,
pendantImage: moduleAuthor.pendant?.image,
),
),
),
Align(
alignment: Alignment.centerRight,
child: moduleTagText != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(
horizontal: 4,
vertical: 2,
),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(4),
),
border: Border.all(
width: 1.25,
color: theme.colorScheme.primary,
),
),
child: Text(
moduleTagText,
style: TextStyle(
height: 1,
fontSize: 12,
color: theme.colorScheme.primary,
),
strutStyle: const StrutStyle(
height: 1,
leading: 0,
fontSize: 12,
),
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
moduleAuthor.name!,
maxLines: 1,
overflow: .ellipsis,
style: TextStyle(
color:
moduleAuthor.vip != null &&
moduleAuthor.vip!.status > 0 &&
moduleAuthor.vip!.type == 2
? theme.colorScheme.vipColor
: theme.colorScheme.onSurface,
fontSize: theme.textTheme.titleSmall!.fontSize,
),
_moreWidget(context),
],
)
: moduleAuthor.decorate != null
? Row(
mainAxisSize: MainAxisSize.min,
children: [
Stack(
clipBehavior: Clip.none,
alignment: Alignment.centerRight,
children: [
CachedNetworkImage(
height: 32,
memCacheHeight: 32.cacheSize(context),
imageUrl: ImageUtils.safeThumbnailUrl(
moduleAuthor.decorate!.cardUrl,
),
placeholder: (_, _) => const SizedBox.shrink(),
),
if (moduleAuthor.decorate!.fan?.numStr?.isNotEmpty ==
true)
Padding(
padding: const EdgeInsets.only(right: 32),
child: Text(
'${moduleAuthor.decorate!.fan!.numStr}',
style: TextStyle(
height: 1,
fontSize: 11,
fontFamily: 'digital_id_num',
color:
moduleAuthor.decorate!.fan?.color
?.startsWith('#') ==
true
? Utils.parseColor(
moduleAuthor.decorate!.fan!.color!,
)
: null,
),
),
),
],
),
_moreWidget(context),
],
)
: _moreWidget(context),
),
?pubTs,
],
),
),
],
),
],
),
);
}
Widget _moreWidget(BuildContext context) => isSave
? const SizedBox.shrink()
: SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
Widget? moreBtn = isSave
? null
: SizedBox(
width: 32,
height: 32,
child: IconButton(
tooltip: '更多',
style: const ButtonStyle(
padding: WidgetStatePropertyAll(EdgeInsets.zero),
),
onPressed: () => morePanel(context),
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
);
final moduleTagText = !isDetail ? item.modules.moduleTag?.text : null;
if (moduleTagText != null) {
header = Row(
children: [
Expanded(child: header),
Container(
padding: const .symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
borderRadius: const .all(.circular(4)),
border: .all(width: 1.25, color: theme.colorScheme.primary),
),
child: Text(
moduleTagText,
style: TextStyle(
height: 1,
fontSize: 12,
color: theme.colorScheme.primary,
),
strutStyle: const StrutStyle(height: 1, leading: 0, fontSize: 12),
),
onPressed: () => morePanel(context),
icon: const Icon(Icons.more_vert_outlined, size: 18),
),
?moreBtn,
],
);
} else if (moduleAuthor.decorate != null) {
const height = 32.0;
header = Stack(
clipBehavior: .none,
children: [
Positioned(
top: 0,
right: 0,
height: height,
child: CachedNetworkImage(
height: height,
memCacheHeight: height.cacheSize(context),
imageUrl: ImageUtils.safeThumbnailUrl(
moduleAuthor.decorate!.cardUrl,
),
placeholder: (_, _) => const SizedBox.shrink(),
),
),
if (moduleAuthor.decorate!.fan?.numStr?.isNotEmpty == true)
Positioned(
top: 0,
right: height,
height: height,
child: Center(
child: Text(
moduleAuthor.decorate!.fan!.numStr!.toString(),
style: TextStyle(
height: 1,
fontSize: 11,
fontFamily: Assets.digitalNum,
color: Utils.parseColor(
moduleAuthor.decorate!.fan!.color!,
),
),
),
),
),
Padding(
padding: const .only(right: 80),
child: header,
),
],
);
if (moreBtn != null) {
header = Row(
children: [
Expanded(child: header),
moreBtn,
],
);
}
} else if (moreBtn != null) {
header = Row(
children: [
Expanded(child: header),
moreBtn,
],
);
}
return header;
}
void morePanel(BuildContext context) {
String? bvid;
@@ -283,7 +271,7 @@ class AuthorPanel extends StatelessWidget {
children: [
InkWell(
onTap: Get.back,
borderRadius: StyleString.bottomSheetRadius,
borderRadius: Style.bottomSheetRadius,
child: SizedBox(
height: 35,
child: Center(

View File

@@ -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/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
@@ -42,7 +43,7 @@ Widget livePanelSub(
LayoutBuilder(
builder: (context, constraints) => NetworkImgLayer(
width: constraints.maxWidth,
height: constraints.maxWidth / StyleString.aspectRatio,
height: constraints.maxWidth / Style.aspectRatio,
src: live.cover,
quality: 40,
),
@@ -61,7 +62,7 @@ Widget livePanelSub(
child: Image.asset(
height: 16,
cacheHeight: 16.cacheSize(context),
'assets/images/live/live.gif',
Assets.livingRect,
filterQuality: FilterQuality.low,
),
)
@@ -90,7 +91,7 @@ Widget livePanelSub(
],
),
borderRadius: BorderRadius.vertical(
bottom: StyleString.imgRadius,
bottom: Style.imgRadius,
),
),
child: Text(

View File

@@ -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/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
@@ -36,7 +37,7 @@ Widget liveRcmdPanel(
LayoutBuilder(
builder: (context, constraints) => NetworkImgLayer(
width: constraints.maxWidth,
height: constraints.maxWidth / StyleString.aspectRatio,
height: constraints.maxWidth / Style.aspectRatio,
src: liveRcmd.cover,
quality: 40,
),
@@ -55,7 +56,7 @@ Widget liveRcmdPanel(
child: Image.asset(
height: 16,
cacheHeight: 16.cacheSize(context),
'assets/images/live/live.gif',
Assets.livingRect,
filterQuality: FilterQuality.low,
),
)
@@ -85,7 +86,7 @@ Widget liveRcmdPanel(
],
),
borderRadius: BorderRadius.vertical(
bottom: StyleString.imgRadius,
bottom: Style.imgRadius,
),
),
child: Text(

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