diff --git a/.fvmrc b/.fvmrc index bf47b4e322..19e8577e95 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,3 +1,3 @@ { - "flutter": "3.41.4" + "flutter": "3.41.6" } \ No newline at end of file diff --git a/.gitignore b/.gitignore index d59d18da45..9b9fcd935c 100644 --- a/.gitignore +++ b/.gitignore @@ -150,4 +150,7 @@ dist test*.dart -ohos/build-profile.json5 \ No newline at end of file +ohos/build-profile.json5 + +.agents +skills-lock.json diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 5e8c02344a..8128334d62 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -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" diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 1dc6cf7652..391a902b2b 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -20,7 +20,5 @@ ???? CFBundleVersion 1.0 - MinimumOSVersion - 13.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4de46aa111..fdadabedd7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 8b0ece2667..63c1578bed 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -131,5 +131,13 @@ UIStatusBarHidden + NSLocalNetworkUsageDescription + 需要访问本地网络以发现和连接 DLNA 投屏设备 + NSBonjourServices + + _ssdp._udp + _upnp._tcp + _http._tcp + diff --git a/lib/common/assets.dart b/lib/common/assets.dart new file mode 100644 index 0000000000..97996d3640 --- /dev/null +++ b/lib/common/assets.dart @@ -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 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', + ]; +} diff --git a/lib/common/constants.dart b/lib/common/constants.dart index cc00d4ee7d..2524471608 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -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/dev4harmony/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 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), - ]; } diff --git a/lib/common/dial_prefix.dart b/lib/common/dial_prefix.dart new file mode 100644 index 0000000000..144fac9c1d --- /dev/null +++ b/lib/common/dial_prefix.dart @@ -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), + ]; +} diff --git a/lib/common/skeleton/fav_pgc_item.dart b/lib/common/skeleton/fav_pgc_item.dart index 86fa755eed..2704b3ee00 100644 --- a/lib/common/skeleton/fav_pgc_item.dart +++ b/lib/common/skeleton/fav_pgc_item.dart @@ -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( diff --git a/lib/common/skeleton/media_bangumi.dart b/lib/common/skeleton/media_bangumi.dart index eb26f30c2a..eeeabcfda0 100644 --- a/lib/common/skeleton/media_bangumi.dart +++ b/lib/common/skeleton/media_bangumi.dart @@ -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 { 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: [ diff --git a/lib/common/skeleton/video_card_h.dart b/lib/common/skeleton/video_card_h.dart index b25a6a9ddb..e328a6cd1c 100644 --- a/lib/common/skeleton/video_card_h.dart +++ b/lib/common/skeleton/video_card_h.dart @@ -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: [ diff --git a/lib/common/skeleton/video_card_v.dart b/lib/common/skeleton/video_card_v.dart index faa33a92b3..7a36f4d6c3 100644 --- a/lib/common/skeleton/video_card_v.dart +++ b/lib/common/skeleton/video_card_v.dart @@ -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, ), ), ), diff --git a/lib/common/style.dart b/lib/common/style.dart new file mode 100644 index 0000000000..a50f36f798 --- /dev/null +++ b/lib/common/style.dart @@ -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, + ); +} diff --git a/lib/common/widgets/color_palette.dart b/lib/common/widgets/color_palette.dart index 3dda88cfbf..ed6fe0a47a 100644 --- a/lib/common/widgets/color_palette.dart +++ b/lib/common/widgets/color_palette.dart @@ -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, ); diff --git a/lib/common/widgets/custom_toast.dart b/lib/common/widgets/custom_toast.dart index 30d0e84b51..edc416fb84 100644 --- a/lib/common/widgets/custom_toast.dart +++ b/lib/common/widgets/custom_toast.dart @@ -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)), ], diff --git a/lib/common/widgets/dialog/dialog.dart b/lib/common/widgets/dialog/dialog.dart index 23ffcf14fa..9d9ff7b227 100644 --- a/lib/common/widgets/dialog/dialog.dart +++ b/lib/common/widgets/dialog/dialog.dart @@ -3,21 +3,16 @@ import 'package:get/get.dart'; Future 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( 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, diff --git a/lib/common/widgets/dialog/export_import.dart b/lib/common/widgets/dialog/export_import.dart index 5fd17661fb..53b2fdd693 100644 --- a/lib/common/widgets/dialog/export_import.dart +++ b/lib/common/widgets/dialog/export_import.dart @@ -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( context: context, builder: (context) => AlertDialog( title: Text('输入$title'), - constraints: StyleString.dialogFixedConstraints, + constraints: Style.dialogFixedConstraints, content: TextFormField( key: key, minLines: 4, @@ -211,7 +211,6 @@ void importFromInput( Future showImportExportDialog( BuildContext context, { required String title, - String? label, required ValueGetter onExport, required FutureOr Function(T json) onImport, required ValueGetter localFileName, diff --git a/lib/common/widgets/extra_hit_test_widget.dart b/lib/common/widgets/extra_hit_test_widget.dart new file mode 100644 index 0000000000..356510f756 --- /dev/null +++ b/lib/common/widgets/extra_hit_test_widget.dart @@ -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; + } +} diff --git a/lib/common/widgets/flutter/pop_scope.dart b/lib/common/widgets/flutter/pop_scope.dart index 85401dc0f6..33cffe2e1c 100644 --- a/lib/common/widgets/flutter/pop_scope.dart +++ b/lib/common/widgets/flutter/pop_scope.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart' hide PopScope; +import 'package:get/get_core/src/get_main.dart'; +import 'package:get/get_navigation/src/extension_navigation.dart'; abstract class PopScopeState extends State - implements PopEntry { + implements PopEntry { ModalRoute? _route; @override @@ -14,31 +16,60 @@ abstract class PopScopeState extends State @override late final ValueNotifier canPopNotifier; - void initCanPopNotifier() { - canPopNotifier = ValueNotifier(false); - } + bool get initCanPop => true; @override void initState() { super.initState(); - initCanPopNotifier(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final ModalRoute? nextRoute = ModalRoute.of(context); - if (nextRoute != _route) { - _route?.unregisterPopEntry(this); - _route = nextRoute; - _route?.registerPopEntry(this); - } + canPopNotifier = ValueNotifier(initCanPop); + _route = (Get.routing.route as ModalRoute)..registerPopEntry(this); } @override void dispose() { _route?.unregisterPopEntry(this); + _route = null; canPopNotifier.dispose(); super.dispose(); } } + +// ignore: camel_case_types +typedef popScope = PopScope; + +class PopScope extends StatefulWidget { + const PopScope({ + super.key, + required this.child, + this.canPop = true, + required this.onPopInvokedWithResult, + }); + + final Widget child; + + final PopInvokedWithResultCallback onPopInvokedWithResult; + + final bool canPop; + + @override + State createState() => _PopScopeState(); +} + +class _PopScopeState extends PopScopeState { + @override + bool get initCanPop => widget.canPop; + + @override + void onPopInvokedWithResult(bool didPop, Object? result) { + widget.onPopInvokedWithResult(didPop, result); + } + + @override + void didUpdateWidget(T oldWidget) { + super.didUpdateWidget(oldWidget); + canPopNotifier.value = widget.canPop; + } + + @override + Widget build(BuildContext context) => widget.child; +} diff --git a/lib/common/widgets/flutter/refresh_indicator.dart b/lib/common/widgets/flutter/refresh_indicator.dart index 68b226ad4f..d1a5fe9440 100644 --- a/lib/common/widgets/flutter/refresh_indicator.dart +++ b/lib/common/widgets/flutter/refresh_indicator.dart @@ -217,8 +217,8 @@ class RefreshIndicatorState extends State RefreshIndicatorStatus? _status; late Future _pendingRefreshFuture; double? _dragOffset; - late Color _effectiveValueColor = - widget.color ?? Theme.of(context).colorScheme.primary; + late Color _effectiveValueColor; + // late Color _backgroundColor; static final Animatable _threeQuarterTween = Tween( begin: 0.0, @@ -274,9 +274,10 @@ class RefreshIndicatorState extends State } 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 } 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 diff --git a/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart b/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart index 040feb8a8e..5a90e67a6e 100644 --- a/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart +++ b/lib/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart @@ -1,27 +1,30 @@ import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/gestures.dart'; +mixin InitialPositionMixin on GestureRecognizer { + Offset? _initialPosition; + Offset? get initialPosition => _initialPosition; + + @override + void addAllowedPointer(PointerDownEvent event) { + super.addAllowedPointer(event); + _initialPosition = event.position; + } +} + class CustomHorizontalDragGestureRecognizer - extends HorizontalDragGestureRecognizer { + extends HorizontalDragGestureRecognizer + with InitialPositionMixin { CustomHorizontalDragGestureRecognizer({ super.debugOwner, super.supportedDevices, super.allowedButtonsFilter, }); - 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; - } - @override bool hasSufficientGlobalDistanceToAccept( PointerDeviceKind pointerDeviceKind, @@ -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!; } } diff --git a/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart b/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart index 19c1b8c094..f4170ab941 100644 --- a/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart +++ b/lib/common/widgets/gesture/image_horizontal_drag_gesture_recognizer.dart @@ -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 + 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 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 - bool hasSufficientGlobalDistanceToAccept( - PointerDeviceKind pointerDeviceKind, - double? deviceTouchSlop, - ) { - return super.hasSufficientGlobalDistanceToAccept( - pointerDeviceKind, - deviceTouchSlop, - ) && - (isBoundaryAllowed?.call(initialPosition, lastPosition) ?? true); + void acceptGesture(int pointer) { + _hasAcceptedOrChecked = true; + super.acceptGesture(pointer); } @override - void dispose() { - isBoundaryAllowed = null; - super.dispose(); + void stopTrackingPointer(int pointer) { + _hasAcceptedOrChecked = false; + isAtLeftEdge = false; + isAtRightEdge = false; + super.stopTrackingPointer(pointer); } } diff --git a/lib/common/widgets/image/image_save.dart b/lib/common/widgets/image/image_save.dart index acfa58c603..3a232d45b9 100644 --- a/lib/common/widgets/image/image_save.dart +++ b/lib/common/widgets/image/image_save.dart @@ -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( diff --git a/lib/common/widgets/image/network_img_layer.dart b/lib/common/widgets/image/network_img_layer.dart index 0119f7de22..69d7500846 100644 --- a/lib/common/widgets/image/network_img_layer.dart +++ b/lib/common/widgets/image/network_img_layer.dart @@ -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 = Duration.zero, this.fadeInDuration = Duration.zero, this.quality = 1, - this.borderRadius = StyleString.mdRadius, + this.borderRadius = Style.mdRadius, this.getPlaceHolder, this.fit = BoxFit.cover, this.alignment = Alignment.center, @@ -118,7 +119,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), diff --git a/lib/common/widgets/image_grid/image_grid_builder.dart b/lib/common/widgets/image_grid/image_grid_builder.dart index 0732fe07ba..ddd65f9fe7 100644 --- a/lib/common/widgets/image_grid/image_grid_builder.dart +++ b/lib/common/widgets/image_grid/image_grid_builder.dart @@ -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); } } diff --git a/lib/common/widgets/image_grid/image_grid_view.dart b/lib/common/widgets/image_grid/image_grid_view.dart index a29fb6297c..b3a49f072f 100644 --- a/lib/common/widgets/image_grid/image_grid_view.dart +++ b/lib/common/widgets/image_grid/image_grid_view.dart @@ -17,7 +17,8 @@ import 'dart:io' show Platform; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/assets.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_grid/image_grid_builder.dart'; @@ -33,7 +34,7 @@ import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show HapticFeedback; import 'package:get/get_core/src/get_main.dart'; -import 'package:get/get_navigation/get_navigation.dart'; +import 'package:get/get_navigation/src/extension_navigation.dart'; class ImageModel { ImageModel({ @@ -53,8 +54,7 @@ class ImageModel { bool? _isLongPic; bool? _isLivePhoto; - bool get isLongPic => - _isLongPic ??= (height / width) > StyleString.imgMaxRatio; + bool get isLongPic => _isLongPic ??= (height / width) > Style.imgMaxRatio; bool get isLivePhoto => _isLivePhoto ??= enableLivePhoto && liveUrl?.isNotEmpty == true; @@ -74,14 +74,14 @@ class ImageGridView extends StatelessWidget { final bool fullScreen; static bool horizontalPreview = Pref.horizontalPreview; - static const _routes = ['/videoV', '/dynamicDetail']; + static final _regex = RegExp(r'/videoV|/dynamicDetail$|/articlePage'); void _onTap(BuildContext context, int index) { final imgList = picArr.map( (item) { bool isLive = item.isLivePhoto; return SourceModel( - sourceType: isLive ? SourceType.livePhoto : SourceType.networkImage, + sourceType: isLive ? .livePhoto : .networkImage, url: item.url, liveUrl: isLive ? item.liveUrl : null, width: isLive ? item.width.toInt() : null, @@ -92,7 +92,7 @@ class ImageGridView extends StatelessWidget { ).toList(); if (horizontalPreview && !fullScreen && - _routes.contains(Get.currentRoute) && + Get.currentRoute.startsWith(_regex) && !context.mediaQuerySize.isPortrait) { final scaffoldState = Scaffold.maybeOf(context); if (scaffoldState != null) { @@ -108,6 +108,7 @@ class ImageGridView extends StatelessWidget { PageUtils.imageView( initialPage: index, imgList: imgList, + tag: hashCode.toString(), ); } @@ -115,9 +116,9 @@ class ImageGridView extends StatelessWidget { int col, int length, int index, { - Radius r = StyleString.imgRadius, + Radius r = Style.imgRadius, }) { - if (length == 1) return StyleString.mdRadius; + if (length == 1) return Style.mdRadius; final bool hasUp = index - col >= 0; final bool hasDown = index + col < length; @@ -212,7 +213,7 @@ class ImageGridView extends StatelessWidget { ).colorScheme.onInverseSurface.withValues(alpha: 0.4), ), child: Image.asset( - 'assets/images/loading.png', + Assets.loading, width: width, height: height, cacheWidth: width.cacheSize(context), @@ -255,7 +256,7 @@ class ImageGridView extends StatelessWidget { ); if (!item.isLongPic) { child = Hero( - tag: item.url, + tag: '${item.url}$hashCode', child: child, ); } diff --git a/lib/common/widgets/image_viewer/gallery_viewer.dart b/lib/common/widgets/image_viewer/gallery_viewer.dart index 6f2afe65cc..1117cc8d08 100644 --- a/lib/common/widgets/image_viewer/gallery_viewer.dart +++ b/lib/common/widgets/image_viewer/gallery_viewer.dart @@ -55,6 +55,7 @@ class GalleryViewer extends StatefulWidget { required this.sources, this.initIndex = 0, this.onPageChanged, + this.tag = '', }); final double minScale; @@ -63,6 +64,7 @@ class GalleryViewer extends StatefulWidget { final List sources; final int initIndex; final ValueChanged? onPageChanged; + final String tag; @override State createState() => _GalleryViewerState(); @@ -472,7 +474,7 @@ class _GalleryViewerState extends State : const SizedBox.shrink(), ); } - return Hero(tag: item.url, child: child); + return Hero(tag: '${item.url}${widget.tag}', child: child); } void _onTap() { diff --git a/lib/common/widgets/image_viewer/image.dart b/lib/common/widgets/image_viewer/image.dart index 8dbba0d293..61a5e0fc2c 100644 --- a/lib/common/widgets/image_viewer/image.dart +++ b/lib/common/widgets/image_viewer/image.dart @@ -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 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); diff --git a/lib/common/widgets/image_viewer/loading_indicator.dart b/lib/common/widgets/image_viewer/loading_indicator.dart index e2516bd3dd..54efaba401 100644 --- a/lib/common/widgets/image_viewer/loading_indicator.dart +++ b/lib/common/widgets/image_viewer/loading_indicator.dart @@ -15,10 +15,10 @@ * along with PiliPlus. If not, see . */ -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 diff --git a/lib/common/widgets/image_viewer/viewer.dart b/lib/common/widgets/image_viewer/viewer.dart index 4b97274e85..77748a0ed9 100644 --- a/lib/common/widgets/image_viewer/viewer.dart +++ b/lib/common/widgets/image_viewer/viewer.dart @@ -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 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 with SingleTickerProviderStateMixin { @override void dispose() { + _stopFling(); _animationController ..removeListener(_listener) ..dispose(); @@ -234,6 +235,7 @@ class _ViewerState extends State with SingleTickerProviderStateMixin { void _handleDoubleTap() { if (!mounted) return; if (_animationController.isAnimating) return; + _stopFling(); _scaleFrom = _scale; _positionFrom = _position; @@ -265,6 +267,8 @@ class _ViewerState extends State with SingleTickerProviderStateMixin { } void _onScaleStart(ScaleStartDetails details) { + _stopFling(); + if (_animationController.isAnimating) { _animationController.stop(); } @@ -329,40 +333,106 @@ class _ViewerState extends State 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 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 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 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 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); -} diff --git a/lib/common/widgets/keep_alive_wrapper.dart b/lib/common/widgets/keep_alive_wrapper.dart index a7e76076e9..5fdb25cf61 100644 --- a/lib/common/widgets/keep_alive_wrapper.dart +++ b/lib/common/widgets/keep_alive_wrapper.dart @@ -3,11 +3,11 @@ import 'package:flutter/material.dart'; class KeepAliveWrapper extends StatefulWidget { const KeepAliveWrapper({ super.key, - required this.builder, + required this.child, this.wantKeepAlive = true, }); - final WidgetBuilder builder; + final Widget child; final bool wantKeepAlive; @override @@ -19,7 +19,7 @@ class _KeepAliveWrapperState extends State @override Widget build(BuildContext context) { super.build(context); - return widget.builder(context); + return widget.child; } @override diff --git a/lib/common/widgets/loading_widget/http_error.dart b/lib/common/widgets/loading_widget/http_error.dart index e774ab12ae..7b2a7a7b67 100644 --- a/lib/common/widgets/loading_widget/http_error.dart +++ b/lib/common/widgets/loading_widget/http_error.dart @@ -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); } } diff --git a/lib/common/widgets/loading_widget/loading_widget.dart b/lib/common/widgets/loading_widget/loading_widget.dart index ce9ec642ee..d1df73e605 100644 --- a/lib/common/widgets/loading_widget/loading_widget.dart +++ b/lib/common/widgets/loading_widget/loading_widget.dart @@ -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, diff --git a/lib/common/widgets/loading_widget/m3e_loading_indicator.dart b/lib/common/widgets/loading_widget/m3e_loading_indicator.dart new file mode 100644 index 0000000000..52ebffa619 --- /dev/null +++ b/lib/common/widgets/loading_widget/m3e_loading_indicator.dart @@ -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 . + */ + +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? morphs; + + final Color? color; + final Size size; + // final Key? childKey; + + @override + State createState() => _M3ELoadingIndicatorState(); +} + +class _M3ELoadingIndicatorState extends State + 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 _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); + } +} diff --git a/lib/common/widgets/loading_widget/morphs.dart b/lib/common/widgets/loading_widget/morphs.dart new file mode 100644 index 0000000000..f153bc80ce --- /dev/null +++ b/lib/common/widgets/loading_widget/morphs.dart @@ -0,0 +1,41 @@ +import 'package:material_new_shapes/material_new_shapes.dart'; + +abstract final class Morphs { + static List buildMorph( + List 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, + // ); +} diff --git a/lib/common/widgets/pendant_avatar.dart b/lib/common/widgets/pendant_avatar.dart index 9c333d6f85..f24def1cd1 100644 --- a/lib/common/widgets/pendant_avatar.dart +++ b/lib/common/widgets/pendant_avatar.dart @@ -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: ImageType .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, - ), - ), - ], - ), - ), - ), - ) - else if (_badgeType != BadgeType.none) - _buildBadge(context, colorScheme, isMemberAvatar), + _buildLive(colorScheme) + else if (badgeType != .none) + _buildBadge(context, colorScheme), ], ); + if (showPendant) { + return SizedBox.square( + dimension: preferredSize, + child: child, + ); + } + return child; } - Widget _buildAvatar(ColorScheme colorScheme, bool isMemberAvatar) => - isMemberAvatar - ? DecoratedBox( + 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( - border: Border.all( - width: 2, - color: colorScheme.surface, - ), - shape: BoxShape.circle, + color: colorScheme.secondaryContainer, + borderRadius: Style.mdRadius, ), - child: Padding( - padding: const EdgeInsets.all(2), - child: NetworkImgLayer( - src: avatar, - width: size, - height: size, - type: ImageType.avatar, - ), + 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, + ), + ), + ], ), - ) - : 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( diff --git a/lib/common/widgets/player_bar.dart b/lib/common/widgets/player_bar.dart index a44ffe09b1..0d721e6e42 100644 --- a/lib/common/widgets/player_bar.dart +++ b/lib/common/widgets/player_bar.dart @@ -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; } } diff --git a/lib/common/widgets/route_aware_mixin.dart b/lib/common/widgets/route_aware_mixin.dart new file mode 100644 index 0000000000..3fa04481b2 --- /dev/null +++ b/lib/common/widgets/route_aware_mixin.dart @@ -0,0 +1,21 @@ +import 'package:flutter/widgets.dart'; +import 'package:get/get_core/src/get_main.dart'; +import 'package:get/get_navigation/src/extension_navigation.dart'; +import 'package:get/get_navigation/src/routes/default_route.dart' + show GetPageRoute; + +final routeObserver = RouteObserver(); + +mixin RouteAwareMixin on State, RouteAware { + @override + void initState() { + super.initState(); + routeObserver.subscribe(this, Get.routing.route as GetPageRoute); + } + + @override + void dispose() { + routeObserver.unsubscribe(this); + super.dispose(); + } +} diff --git a/lib/common/widgets/select_mask.dart b/lib/common/widgets/select_mask.dart index cf71ff0324..5c964bc870 100644 --- a/lib/common/widgets/select_mask.dart +++ b/lib/common/widgets/select_mask.dart @@ -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, diff --git a/lib/common/widgets/video_card/video_card_h.dart b/lib/common/widgets/video_card/video_card_h.dart index 436c436bde..82c1dc5026 100644 --- a/lib/common/widgets/video_card/video_card_h.dart +++ b/lib/common/widgets/video_card/video_card_h.dart @@ -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: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: _CoverBuilderH( cover: videoItem.cover, badge: badge, diff --git a/lib/common/widgets/video_card/video_card_v.dart b/lib/common/widgets/video_card/video_card_v.dart index 9b2f1db77e..14446af03d 100644 --- a/lib/common/widgets/video_card/video_card_v.dart +++ b/lib/common/widgets/video_card/video_card_v.dart @@ -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({ @@ -199,7 +199,7 @@ class VideoCardV extends StatelessWidget { value: videoItem.stat.danmu, ), ], - if (videoItem is RecVideoItemModel) ...[ + if (videoItem is RcmdVideoItemModel) ...[ const Spacer(), Text( DateFormatUtils.dateFormat( @@ -217,7 +217,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(), @@ -334,7 +334,7 @@ class _CachedLayoutBuilderState extends State<_CachedLayoutBuilder> { @override Widget build(BuildContext context) { return AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder(builder: _builder), ); } diff --git a/lib/common/widgets/video_popup_menu.dart b/lib/common/widgets/video_popup_menu.dart index 60f0016177..75deb62439 100644 --- a/lib/common/widgets/video_popup_menu.dart +++ b/lib/common/widgets/video_popup_menu.dart @@ -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"); diff --git a/lib/http/api.dart b/lib/http/api.dart index f80aec4730..33d1f0b9fc 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -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'; } diff --git a/lib/http/download.dart b/lib/http/download.dart index 63364c6b6f..c05093cb5e 100644 --- a/lib/http/download.dart +++ b/lib/http/download.dart @@ -161,6 +161,7 @@ abstract final class DownloadHttp { dashDrmType: 0, ), ]; + entry.hasDashAudio = true; } return Type2( duration: dash.duration!, diff --git a/lib/http/error_msg.dart b/lib/http/error_msg.dart new file mode 100644 index 0000000000..e78230cee5 --- /dev/null +++ b/lib/http/error_msg.dart @@ -0,0 +1,3 @@ +const errorMsg = { + -352: '风控校验失败,请检查登录状态', +}; diff --git a/lib/http/fan.dart b/lib/http/fan.dart index 337732343d..947eabf45e 100644 --- a/lib/http/fan.dart +++ b/lib/http/fan.dart @@ -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']); } } } diff --git a/lib/http/follow.dart b/lib/http/follow.dart index 880a9adfe3..d3ed1c715c 100644 --- a/lib/http/follow.dart +++ b/lib/http/follow.dart @@ -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']); } } } diff --git a/lib/http/init.dart b/lib/http/init.dart index 0f8a2e1500..9537e2ca9d 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -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) { diff --git a/lib/http/live.dart b/lib/http/live.dart index 16fe87e4d7..c7b0a154fb 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -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> 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']); + } + } } diff --git a/lib/http/member.dart b/lib/http/member.dart index dd4cc008fb..df8ae83ce1 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -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> 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> 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']); } } diff --git a/lib/http/retry_interceptor.dart b/lib/http/retry_interceptor.dart index 7bf1d8918b..1584d52c81 100644 --- a/lib/http/retry_interceptor.dart +++ b/lib/http/retry_interceptor.dart @@ -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((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); } diff --git a/lib/http/user.dart b/lib/http/user.dart index 5c10958d9b..d8d76743a7 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -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> hasFollow(int mid) async { + static Future> 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']); } diff --git a/lib/http/video.dart b/lib/http/video.dart index 76ad7e8d43..737806c8ec 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -49,7 +49,7 @@ abstract final class VideoHttp { static bool enableFilter = zoneRegExp.pattern.isNotEmpty; // 首页推荐视频 - static Future>> rcmdVideoList({ + static Future>> rcmdVideoList({ required int ps, required int freshIdx, }) async { @@ -66,13 +66,13 @@ abstract final class VideoHttp { }), ); if (res.data['code'] == 0) { - List list = []; + List list = []; 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>> rcmdVideoListApp({ + static Future>> rcmdVideoListApp({ required int freshIdx, }) async { final params = { @@ -139,7 +139,7 @@ abstract final class VideoHttp { ), ); if (res.data['code'] == 0) { - List list = []; + List list = []; 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(), ); diff --git a/lib/main.dart b/lib/main.dart index d7152b5f18..d8fa477289 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/build_config.dart'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/back_detector.dart'; import 'package:PiliPlus/common/widgets/custom_toast.dart'; +import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; import 'package:PiliPlus/common/widgets/scale_app.dart'; import 'package:PiliPlus/common/widgets/scroll_behavior.dart'; import 'package:PiliPlus/harmony_adapt/harmony_channel.dart'; @@ -19,7 +20,6 @@ import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/json_file_handler.dart'; -import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/request_utils.dart'; @@ -146,6 +146,8 @@ void main() async { ), ); } + } else if (Platform.isMacOS) { + await setupServiceLocator(); } Request(); @@ -330,7 +332,7 @@ class MyApp extends StatelessWidget { builder: _builder, ), navigatorObservers: [ - PageUtils.routeObserver, + routeObserver, FlutterSmartDialog.observer, ], scrollBehavior: PlatformUtils.isDesktop diff --git a/lib/models/common/member/archive_order_type_app.dart b/lib/models/common/member/archive_order_type_app.dart new file mode 100644 index 0000000000..0182f8e8f2 --- /dev/null +++ b/lib/models/common/member/archive_order_type_app.dart @@ -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); +} diff --git a/lib/models/common/member/archive_order_type_web.dart b/lib/models/common/member/archive_order_type_web.dart new file mode 100644 index 0000000000..040a68c4ee --- /dev/null +++ b/lib/models/common/member/archive_order_type_web.dart @@ -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); +} diff --git a/lib/models/common/member/archive_sort_type_app.dart b/lib/models/common/member/archive_sort_type_app.dart new file mode 100644 index 0000000000..dd8e6084d4 --- /dev/null +++ b/lib/models/common/member/archive_sort_type_app.dart @@ -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); +} diff --git a/lib/models/common/member/contribute_type.dart b/lib/models/common/member/contribute_type.dart index a3b08a8f46..e0076673a5 100644 --- a/lib/models/common/member/contribute_type.dart +++ b/lib/models/common/member/contribute_type.dart @@ -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); } diff --git a/lib/models/common/member/web_ss_type.dart b/lib/models/common/member/web_ss_type.dart new file mode 100644 index 0000000000..e4d1616246 --- /dev/null +++ b/lib/models/common/member/web_ss_type.dart @@ -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); +} diff --git a/lib/models/dynamics/article_content_model.dart b/lib/models/dynamics/article_content_model.dart index b0a9666806..1412e152d8 100644 --- a/lib/models/dynamics/article_content_model.dart +++ b/lib/models/dynamics/article_content_model.dart @@ -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'; import 'package:PiliPlus/utils/utils.dart'; @@ -50,7 +50,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; } } } @@ -341,6 +341,7 @@ class Common { int? style; String? subType; String? title; + String? titlePrefix; Common.fromJson(Map json) { cover = json['cover']; @@ -353,6 +354,7 @@ class Common { style = json['style']; subType = json['sub_type']; title = json['title']; + titlePrefix = json['title_prefix']; } } diff --git a/lib/models/dynamics/result.dart b/lib/models/dynamics/result.dart index cc29abd36d..4aff0b96d8 100644 --- a/lib/models/dynamics/result.dart +++ b/lib/models/dynamics/result.dart @@ -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']); diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index 5788c13ae4..62585f9ba5 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -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 json) { + RcmdVideoItemAppModel.fromJson(Map json) { aid = json['player_args']?['aid'] ?? int.tryParse(json['param'] ?? '0'); bvid = json['bvid'] ?? IdUtils.av2bv(aid!); cid = json['player_args']?['cid']; diff --git a/lib/models/model_hot_video_item.dart b/lib/models/model_hot_video_item.dart index 9a48e78e95..dcdccec6e7 100644 --- a/lib/models/model_hot_video_item.dart +++ b/lib/models/model_hot_video_item.dart @@ -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; diff --git a/lib/models/model_owner.dart b/lib/models/model_owner.dart index 2a091ba515..d7db6871cb 100644 --- a/lib/models/model_owner.dart +++ b/lib/models/model_owner.dart @@ -1,6 +1,6 @@ import 'package:PiliPlus/models/model_video.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; part 'model_owner.g.dart'; diff --git a/lib/models/model_rec_video_item.dart b/lib/models/model_rec_video_item.dart index a0e916c085..c91bc7163a 100644 --- a/lib/models/model_rec_video_item.dart +++ b/lib/models/model_rec_video_item.dart @@ -1,7 +1,7 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models/model_video.dart'; -abstract class BaseRecVideoItemModel extends BaseVideoItemModel { +abstract class BaseRcmdVideoItemModel extends BaseVideoItemModel { String? goto; String? uri; String? rcmdReason; @@ -11,8 +11,8 @@ abstract class BaseRecVideoItemModel extends BaseVideoItemModel { String? pgcBadge; } -class RecVideoItemModel extends BaseRecVideoItemModel { - RecVideoItemModel.fromJson(Map json) { +class RcmdVideoItemModel extends BaseRcmdVideoItemModel { + RcmdVideoItemModel.fromJson(Map json) { aid = json["id"]; bvid = json["bvid"]; cid = json["cid"]; diff --git a/lib/models/user/danmaku_rule_adapter.dart b/lib/models/user/danmaku_rule_adapter.dart index 232a89d19b..bd2488f595 100644 --- a/lib/models/user/danmaku_rule_adapter.dart +++ b/lib/models/user/danmaku_rule_adapter.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/models/user/danmaku_rule.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class RuleFilterAdapter extends TypeAdapter { @override diff --git a/lib/models/user/info.dart b/lib/models/user/info.dart index 334e3f80a6..b4eb0bc928 100644 --- a/lib/models/user/info.dart +++ b/lib/models/user/info.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/utils/extension/map_ext.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; part 'info.g.dart'; diff --git a/lib/models/user/stat.dart b/lib/models/user/stat.dart index e45fb99500..ac9394ca94 100644 --- a/lib/models/user/stat.dart +++ b/lib/models/user/stat.dart @@ -1,4 +1,4 @@ -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; part 'stat.g.dart'; diff --git a/lib/models/video/play/url.dart b/lib/models/video/play/url.dart index 7bfab6f01b..a8234de282 100644 --- a/lib/models/video/play/url.dart +++ b/lib/models/video/play/url.dart @@ -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'; } diff --git a/lib/models_new/download/bili_download_entry_info.dart b/lib/models_new/download/bili_download_entry_info.dart index fc47b157dd..b92835e5d6 100644 --- a/lib/models_new/download/bili_download_entry_info.dart +++ b/lib/models_new/download/bili_download_entry_info.dart @@ -13,7 +13,7 @@ import 'package:share_plus/share_plus.dart'; class BiliDownloadEntryInfo with MultiSelectData { int mediaType; - final bool hasDashAudio; + bool hasDashAudio; bool isCompleted; int totalBytes; int downloadedBytes; diff --git a/lib/models_new/live/live_contribution_rank/item.dart b/lib/models_new/live/live_contribution_rank/item.dart index e4f43c707c..4cc838f0c1 100644 --- a/lib/models_new/live/live_contribution_rank/item.dart +++ b/lib/models_new/live/live_contribution_rank/item.dart @@ -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 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), + : UinfoMedal.fromJson(json['uinfo']?['medal']), ); } diff --git a/lib/models_new/live/live_contribution_rank/medal_info.dart b/lib/models_new/live/live_contribution_rank/medal_info.dart deleted file mode 100644 index 2c4c167167..0000000000 --- a/lib/models_new/live/live_contribution_rank/medal_info.dart +++ /dev/null @@ -1,14 +0,0 @@ -class MedalInfo { - String? medalName; - int? level; - - MedalInfo({ - this.medalName, - this.level, - }); - - factory MedalInfo.fromJson(Map json) => MedalInfo( - medalName: json['medal_name'] as String?, - level: json['level'] as int?, - ); -} diff --git a/lib/models_new/live/live_danmaku/danmaku_msg.dart b/lib/models_new/live/live_danmaku/danmaku_msg.dart index 8c972c41c7..2d6af86770 100644 --- a/lib/models_new/live/live_danmaku/danmaku_msg.dart +++ b/lib/models_new/live/live_danmaku/danmaku_msg.dart @@ -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 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(), }; } diff --git a/lib/models_new/live/live_danmaku/live_emote.dart b/lib/models_new/live/live_danmaku/live_emote.dart index 1c91981fa9..a2302a60fd 100644 --- a/lib/models_new/live/live_danmaku/live_emote.dart +++ b/lib/models_new/live/live_danmaku/live_emote.dart @@ -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 json) { url = json['url']; diff --git a/lib/models_new/live/live_medal_wall/data.dart b/lib/models_new/live/live_medal_wall/data.dart new file mode 100644 index 0000000000..9c37865aeb --- /dev/null +++ b/lib/models_new/live/live_medal_wall/data.dart @@ -0,0 +1,30 @@ +import 'package:PiliPlus/models_new/live/live_medal_wall/item.dart'; + +class MedalWallData { + List? 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 json) => MedalWallData( + list: (json['list'] as List?) + ?.map((e) => MedalWallItem.fromJson(e as Map)) + .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?, + ); +} diff --git a/lib/models_new/live/live_medal_wall/item.dart b/lib/models_new/live/live_medal_wall/item.dart new file mode 100644 index 0000000000..5f525b8279 --- /dev/null +++ b/lib/models_new/live/live_medal_wall/item.dart @@ -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 json) => MedalWallItem( + medalInfo: json['medal_info'] == null + ? null + : MedalInfo.fromJson(json['medal_info'] as Map), + 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), + ); +} diff --git a/lib/models_new/live/live_medal_wall/medal_info.dart b/lib/models_new/live/live_medal_wall/medal_info.dart new file mode 100644 index 0000000000..40185a9688 --- /dev/null +++ b/lib/models_new/live/live_medal_wall/medal_info.dart @@ -0,0 +1,11 @@ +class MedalInfo { + int? wearingStatus; + + MedalInfo({ + this.wearingStatus, + }); + + factory MedalInfo.fromJson(Map json) => MedalInfo( + wearingStatus: json['wearing_status'] as int?, + ); +} diff --git a/lib/models_new/live/live_medal_wall/uinfo_medal.dart b/lib/models_new/live/live_medal_wall/uinfo_medal.dart new file mode 100644 index 0000000000..28014acefa --- /dev/null +++ b/lib/models_new/live/live_medal_wall/uinfo_medal.dart @@ -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 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 toJson() => { + 'name': name, + 'level': level, + 'id': id, + 'ruid': ruid, + 'v2_medal_color_start': v2MedalColorStart, + 'v2_medal_color_text': v2MedalColorText, + }; +} diff --git a/lib/models_new/live/live_superchat/item.dart b/lib/models_new/live/live_superchat/item.dart index c45039be72..689a047e08 100644 --- a/lib/models_new/live/live_superchat/item.dart +++ b/lib/models_new/live/live_superchat/item.dart @@ -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 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), + 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(), }; } diff --git a/lib/models_new/member/search_archive/data.dart b/lib/models_new/member/search_archive/data.dart index 1408b99ae1..7ef866208a 100644 --- a/lib/models_new/member/search_archive/data.dart +++ b/lib/models_new/member/search_archive/data.dart @@ -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 json) => @@ -27,13 +18,5 @@ class SearchArchiveData { page: json['page'] == null ? null : Page.fromJson(json['page'] as Map), - episodicButton: json['episodic_button'] == null - ? null - : EpisodicButton.fromJson( - json['episodic_button'] as Map, - ), - isRisk: json['is_risk'] as bool?, - gaiaResType: json['gaia_res_type'] as int?, - gaiaData: json['gaia_data'] as dynamic, ); } diff --git a/lib/models_new/member/search_archive/episodic_button.dart b/lib/models_new/member/search_archive/episodic_button.dart deleted file mode 100644 index c53c5184f2..0000000000 --- a/lib/models_new/member/search_archive/episodic_button.dart +++ /dev/null @@ -1,13 +0,0 @@ -class EpisodicButton { - String? text; - String? uri; - - EpisodicButton({this.text, this.uri}); - - factory EpisodicButton.fromJson(Map json) { - return EpisodicButton( - text: json['text'] as String?, - uri: json['uri'] as String?, - ); - } -} diff --git a/lib/models_new/member/search_archive/list.dart b/lib/models_new/member/search_archive/list.dart index 3af512933c..60eace95c7 100644 --- a/lib/models_new/member/search_archive/list.dart +++ b/lib/models_new/member/search_archive/list.dart @@ -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? tags; List? vlist; - SearchArchiveList({this.vlist}); - - factory SearchArchiveList.fromJson(Map json) => - SearchArchiveList( - vlist: (json['vlist'] as List?) - ?.map((e) => VListItemModel.fromJson(e as Map)) - .toList(), - ); + SearchArchiveList.fromJson(Map json) { + vlist = (json['vlist'] as List?) + ?.map((e) => VListItemModel.fromJson(e as Map)) + .toList(); + tags = (json['slist'] as List?) + ?.map((e) => ListTag.fromJson(e as Map)) + .toList(); + (json['tlist'] as Map?)?.forEach((k, v) { + if (k == '196') { + if (tags == null) { + tags = [ListTag.fromJson(v)]; + } else { + tags!.add(ListTag.fromJson(v)); + } + return; + } + }); + } } diff --git a/lib/models_new/member/search_archive/slist.dart b/lib/models_new/member/search_archive/slist.dart new file mode 100644 index 0000000000..418255249a --- /dev/null +++ b/lib/models_new/member/search_archive/slist.dart @@ -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 json) => ListTag( + tid: json['tid'] as int?, + count: json['count'] as int?, + name: json['name'] as String?, + specialType: json['special_type'] as String?, + ); +} diff --git a/lib/models_new/member/search_archive/vlist.dart b/lib/models_new/member/search_archive/vlist.dart index 2d67c43cc1..95f93d1fff 100644 --- a/lib/models_new/member/search_archive/vlist.dart +++ b/lib/models_new/member/search_archive/vlist.dart @@ -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 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); } diff --git a/lib/models_new/member/season_web/archive.dart b/lib/models_new/member/season_web/archive.dart new file mode 100644 index 0000000000..4d11ab98ab --- /dev/null +++ b/lib/models_new/member/season_web/archive.dart @@ -0,0 +1,28 @@ +import 'package:PiliPlus/models/model_video.dart'; + +class SeasonArchive extends BaseVideoItemModel { + SeasonArchive.fromJson(Map 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 json) { + mid = json['upMid']; + name = ''; + } +} + +class ArchiveStat extends BaseStat { + ArchiveStat.fromJson(Map json) { + view = json['view']; + danmu = json['danmaku']; + } +} diff --git a/lib/models_new/member/season_web/data.dart b/lib/models_new/member/season_web/data.dart new file mode 100644 index 0000000000..c6fbfde32b --- /dev/null +++ b/lib/models_new/member/season_web/data.dart @@ -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? archives; + Page? page; + + SeasonWebData({this.archives, this.page}); + + factory SeasonWebData.fromJson(Map json) => SeasonWebData( + archives: (json['archives'] as List?) + ?.map((e) => SeasonArchive.fromJson(e as Map)) + .toList(), + page: json['page'] == null + ? null + : Page.fromJson(json['page'] as Map), + ); +} diff --git a/lib/models_new/member/season_web/page.dart b/lib/models_new/member/season_web/page.dart new file mode 100644 index 0000000000..84f03b43a7 --- /dev/null +++ b/lib/models_new/member/season_web/page.dart @@ -0,0 +1,13 @@ +class Page { + int? pageNum; + int? pageSize; + int? total; + + Page({this.pageNum, this.pageSize, this.total}); + + factory Page.fromJson(Map json) => Page( + pageNum: json['page_num'] ?? json['num'], + pageSize: json['page_size'] ?? json['size'], + total: json['total'] as int?, + ); +} diff --git a/lib/models_new/relation/data.dart b/lib/models_new/relation/data.dart new file mode 100644 index 0000000000..1a061965e2 --- /dev/null +++ b/lib/models_new/relation/data.dart @@ -0,0 +1,25 @@ +import 'package:PiliPlus/utils/extension/iterable_ext.dart'; + +class RelationData { + int? mid; + int? attribute; + int? mtime; + List? tag; + int? special; + + RelationData({ + this.mid, + this.attribute, + this.mtime, + this.tag, + this.special, + }); + + factory RelationData.fromJson(Map json) => RelationData( + mid: json['mid'] as int?, + attribute: json['attribute'] as int?, + mtime: json['mtime'] as int?, + tag: (json['tag'] as List?)?.fromCast(), + special: json['special'] as int?, + ); +} diff --git a/lib/models_new/space/space/live_fans_wearing.dart b/lib/models_new/space/space/live_fans_wearing.dart index 89c39797ff..5cbbf15739 100644 --- a/lib/models_new/space/space/live_fans_wearing.dart +++ b/lib/models_new/space/space/live_fans_wearing.dart @@ -1,28 +1,44 @@ class LiveFansWearing { + DetailV2? detailV2; + + LiveFansWearing({ + this.detailV2, + }); + + factory LiveFansWearing.fromJson(Map json) { + return LiveFansWearing( + detailV2: json['detail_v2'] == null + ? null + : DetailV2.fromJson(json['detail_v2']), + ); + } +} + +class DetailV2 { + int? uid; int? level; + String? medalColorName; String? medalName; - int? medalColorStart; - int? medalColorEnd; - int? medalColorBorder; - String? medalJumpUrl; + int? medalId; + String? medalColor; - LiveFansWearing({ + DetailV2({ + this.uid, this.level, + this.medalColorName, this.medalName, - this.medalColorStart, - this.medalColorEnd, - this.medalColorBorder, - this.medalJumpUrl, + this.medalId, + this.medalColor, }); - factory LiveFansWearing.fromJson(Map 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?, + factory DetailV2.fromJson(Map 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"], ); } } diff --git a/lib/models_new/space/space/top.dart b/lib/models_new/space/space/top.dart index 8bbc252e05..d87410d40c 100644 --- a/lib/models_new/space/space/top.dart +++ b/lib/models_new/space/space/top.dart @@ -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 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 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? colors; + + SubTitleColorFormat({ + this.colors, + }); + + factory SubTitleColorFormat.fromJson(Map json) => + SubTitleColorFormat( + colors: (json["colors"] as List?)?.fromCast(), + ); +} diff --git a/lib/pages/about/view.dart b/lib/pages/about/view.dart index 07acde7a28..37a74ef2fb 100644 --- a/lib/pages/about/view.dart +++ b/lib/pages/about/view.dart @@ -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'; @@ -66,7 +68,7 @@ class _AboutPageState extends State { void _showDialog() => showDialog( context: context, builder: (context) => AlertDialog( - constraints: StyleString.dialogFixedConstraints, + constraints: Style.dialogFixedConstraints, content: TextField( autofocus: true, onSubmitted: (value) { @@ -110,7 +112,7 @@ class _AboutPageState extends State { height: 150, excludeFromSemantics: true, cacheWidth: 150.cacheSize(context), - 'assets/images/logo/logo.png', + Assets.logo, ), ), ListTile( @@ -224,8 +226,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 { @@ -280,7 +282,6 @@ Commit Hash: ${BuildConfig.commitHash}''', context, title: '设置', localFileName: () => 'setting_${context.platformName}', - label: GStorage.setting.name, onExport: GStorage.exportAllSettings, onImport: GStorage.importAllJsonSettings, ), diff --git a/lib/pages/article/controller.dart b/lib/pages/article/controller.dart index edd0a62157..77bf310cb2 100644 --- a/lib/pages/article/controller.dart +++ b/lib/pages/article/controller.dart @@ -33,8 +33,6 @@ class ArticleController extends CommonDynController { late final RxInt topIndex = 0.obs; - late final showDynActionBar = Pref.showDynActionBar; - @override dynamic get sourceId => commentType == 12 ? 'cv$commentId' : id; diff --git a/lib/pages/article/view.dart b/lib/pages/article/view.dart index 31d2959f2c..2dddba3506 100644 --- a/lib/pages/article/view.dart +++ b/lib/pages/article/view.dart @@ -68,13 +68,12 @@ class _ArticlePageState extends CommonDynPageState { appBar: _buildAppBar(), body: Padding( padding: EdgeInsets.only(left: padding.left, right: padding.right), - child: Stack( - clipBehavior: Clip.none, - children: [ - _buildPage(theme), - _buildBottom(theme), - ], - ), + child: _buildPage(theme), + ), + floatingActionButtonLocation: floatingActionButtonLocation, + floatingActionButton: SlideTransition( + position: fabAnimation, + child: _buildBottom(theme), ), ); } @@ -494,26 +493,10 @@ class _ArticlePageState extends CommonDynPageState { ], ); - Widget _buildBottom(ThemeData theme) => Positioned( - left: 0, - bottom: 0, - right: 0, - child: SlideTransition( - position: fabAnim, - child: Builder( - builder: (context) { - if (!controller.showDynActionBar) { - return Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: padding.bottom + kFloatingActionButtonMargin, - ), - child: replyButton, - ), - ); - } + Widget _buildBottom(ThemeData theme) { + if (!controller.showDynActionBar) { + return fabButton; + } late final primary = theme.colorScheme.primary; late final outline = theme.colorScheme.outline; @@ -523,148 +506,147 @@ class _ArticlePageState extends CommonDynPageState { foregroundColor: outline, ); - Widget textIconButton({ - required IconData icon, - required String text, - required DynamicStat? stat, - required VoidCallback onPressed, - IconData? activatedIcon, - }) { - final status = stat?.status == true; - final color = status ? primary : outline; - return TextButton.icon( - onPressed: onPressed, - icon: Icon( - status ? activatedIcon : icon, - size: 16, - color: color, - ), - style: btnStyle, - label: Text( - stat?.count != null ? NumUtils.numFormat(stat!.count) : text, - style: TextStyle(color: color), - ), - ); - } + Widget textIconButton({ + required IconData icon, + required String text, + required DynamicStat? stat, + required VoidCallback onPressed, + IconData? activatedIcon, + }) { + final status = stat?.status == true; + final color = status ? primary : outline; + return TextButton.icon( + onPressed: onPressed, + icon: Icon( + status ? activatedIcon : icon, + size: 16, + color: color, + ), + style: btnStyle, + label: Text( + stat?.count != null ? NumUtils.numFormat(stat!.count) : text, + style: TextStyle(color: color), + ), + ); + } - return Obx(() { - final stats = controller.stats.value; + return Padding( + padding: .only(left: padding.left, right: padding.right), + child: Obx(() { + final stats = controller.stats.value; - Widget btn = Padding( - padding: EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: - kFloatingActionButtonMargin + - (stats != null ? 0 : padding.bottom), - ), - child: replyButton, - ); + Widget btn = Padding( + padding: EdgeInsets.only( + right: kFloatingActionButtonMargin, + bottom: + kFloatingActionButtonMargin + + (stats != null ? 0 : padding.bottom), + ), + child: replyButton, + ); - if (stats == null) { - return Align( - alignment: Alignment.centerRight, - child: btn, - ); - } + if (stats == null) { + return Align( + alignment: Alignment.bottomRight, + child: btn, + ); + } - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - btn, - Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - border: Border( - top: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.08, - ), - ), + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + btn, + Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + border: Border( + top: BorderSide( + color: theme.colorScheme.outline.withValues( + alpha: 0.08, ), ), - padding: EdgeInsets.only(bottom: padding.bottom), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: Builder( - builder: (btnContext) { - final forward = stats.forward; - return textIconButton( - text: '转发', - icon: FontAwesomeIcons.shareFromSquare, - stat: forward, - onPressed: () { - if (controller.opusData == null && - controller.articleData?.dynIdStr == null) { - SmartDialog.showToast( - 'err: ${controller.id}', - ); - return; - } - final summary = controller.summary; - showModalBottomSheet( - context: context, - isScrollControlled: true, - useSafeArea: true, - builder: (context) => RepostPanel( - item: controller.opusData, - dynIdStr: controller.articleData?.dynIdStr, - pic: summary.cover, - title: summary.title, - uname: summary.author?.name, - onSuccess: () { - if (forward != null) { - int count = forward.count ?? 0; - forward.count = count + 1; - if (btnContext.mounted) { - (btnContext as Element?) - ?.markNeedsBuild(); - } - } - }, - ), - ); - }, + ), + ), + padding: EdgeInsets.only(bottom: padding.bottom), + child: Row( + children: [ + Expanded( + child: Builder( + builder: (btnContext) { + final forward = stats.forward; + return textIconButton( + text: '转发', + icon: FontAwesomeIcons.shareFromSquare, + stat: forward, + onPressed: () { + if (controller.opusData == null && + controller.articleData?.dynIdStr == null) { + SmartDialog.showToast( + 'err: ${controller.id}', + ); + return; + } + final summary = controller.summary; + showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (context) => RepostPanel( + item: controller.opusData, + dynIdStr: controller.articleData?.dynIdStr, + pic: summary.cover, + title: summary.title, + uname: summary.author?.name, + onSuccess: () { + if (forward != null) { + int count = forward.count ?? 0; + forward.count = count + 1; + if (btnContext.mounted) { + (btnContext as Element?) + ?.markNeedsBuild(); + } + } + }, + ), ); }, - ), - ), - Expanded( - child: textIconButton( - text: '分享', - icon: CustomIcons.share_node, - stat: null, - onPressed: () => Utils.shareText(controller.url), - ), - ), - Expanded( - child: textIconButton( - icon: FontAwesomeIcons.star, - activatedIcon: FontAwesomeIcons.solidStar, - text: '收藏', - stat: stats.favorite, - onPressed: controller.onFav, - ), - ), - Expanded( - child: textIconButton( - icon: FontAwesomeIcons.thumbsUp, - activatedIcon: FontAwesomeIcons.solidThumbsUp, - text: '点赞', - stat: stats.like, - onPressed: controller.onLike, - ), - ), - ], + ); + }, + ), ), - ), - ], - ); - }); - }, - ), - ), - ); + Expanded( + child: textIconButton( + text: '分享', + icon: CustomIcons.share_node, + stat: null, + onPressed: () => Utils.shareText(controller.url), + ), + ), + Expanded( + child: textIconButton( + icon: FontAwesomeIcons.star, + activatedIcon: FontAwesomeIcons.solidStar, + text: '收藏', + stat: stats.favorite, + onPressed: controller.onFav, + ), + ), + Expanded( + child: textIconButton( + icon: FontAwesomeIcons.thumbsUp, + activatedIcon: FontAwesomeIcons.solidThumbsUp, + text: '点赞', + stat: stats.like, + onPressed: controller.onLike, + ), + ), + ], + ), + ), + ], + ); + }), + ); + } } diff --git a/lib/pages/article/widgets/article_ops.dart b/lib/pages/article/widgets/article_ops.dart index d1f5d7c95c..25a4990612 100644 --- a/lib/pages/article/widgets/article_ops.dart +++ b/lib/pages/article/widgets/article_ops.dart @@ -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, diff --git a/lib/pages/article/widgets/html_render.dart b/lib/pages/article/widgets/html_render.dart index a2187afcf6..68b27fa680 100644 --- a/lib/pages/article/widgets/html_render.dart +++ b/lib/pages/article/widgets/html_render.dart @@ -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), ), ), ); diff --git a/lib/pages/article/widgets/opus_content.dart b/lib/pages/article/widgets/opus_content.dart index 88dd209742..82705fbd48 100644 --- a/lib/pages/article/widgets/opus_content.dart +++ b/lib/pages/article/widgets/opus_content.dart @@ -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( @@ -723,18 +723,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), ), ), ); @@ -763,9 +766,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, ), @@ -801,7 +802,7 @@ Widget moduleBlockedItem( return Container( width: maxWidth, height: maxWidth, - decoration: bgImg(), + decoration: bgImg(maxWidth), padding: const EdgeInsets.all(12), child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -830,43 +831,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, - ), + 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, + ), + ), + ], + ), + ), + 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), + ), + ], ), - 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), - ), - ], - ), + ); + }, ); } diff --git a/lib/pages/article_list/widgets/item.dart b/lib/pages/article_list/widgets/item.dart index a7e45fa0b4..c6222f10a2 100644 --- a/lib/pages/article_list/widgets/item.dart +++ b/lib/pages/article_list/widgets/item.dart @@ -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( diff --git a/lib/pages/audio/view.dart b/lib/pages/audio/view.dart index f6a19f6f8c..50225a0682 100644 --- a/lib/pages/audio/view.dart +++ b/lib/pages/audio/view.dart @@ -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'; @@ -294,7 +295,7 @@ class _AudioPageState extends State { WidgetSpan( alignment: .bottom, child: Image.asset( - 'assets/images/live.gif', + Assets.livingChart, width: 16, height: 16, cacheWidth: 16.cacheSize( @@ -338,7 +339,7 @@ class _AudioPageState extends State { WidgetSpan( alignment: .bottom, child: Image.asset( - 'assets/images/live.gif', + Assets.livingChart, width: 16, height: 16, cacheWidth: 16.cacheSize( @@ -393,7 +394,7 @@ class _AudioPageState extends State { children: [ InkWell( onTap: Get.back, - borderRadius: StyleString.bottomSheetRadius, + borderRadius: Style.bottomSheetRadius, child: SizedBox( height: 35, child: Center( @@ -462,7 +463,7 @@ class _AudioPageState extends State { children: [ InkWell( onTap: Get.back, - borderRadius: StyleString.bottomSheetRadius, + borderRadius: Style.bottomSheetRadius, child: SizedBox( height: 35, child: Center( @@ -597,7 +598,7 @@ class _AudioPageState extends State { children: [ InkWell( onTap: Get.back, - borderRadius: StyleString.bottomSheetRadius, + borderRadius: Style.bottomSheetRadius, child: SizedBox( height: 35, child: Center( diff --git a/lib/pages/blacklist/controller.dart b/lib/pages/blacklist/controller.dart index 9884e20c26..615b0d8be4 100644 --- a/lib/pages/blacklist/controller.dart +++ b/lib/pages/blacklist/controller.dart @@ -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) { diff --git a/lib/pages/common/common_page.dart b/lib/pages/common/common_page.dart index c5f3ba4f00..f94ee6153e 100644 --- a/lib/pages/common/common_page.dart +++ b/lib/pages/common/common_page.dart @@ -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 extends State { _barOffset!.value = clampDouble( _barOffset!.value + scrollDelta, 0.0, - StyleString.topBarHeight, + Style.topBarHeight, ); } @@ -78,7 +78,7 @@ abstract class CommonPageState extends State { final newValue = clampDouble( value + scrollDelta, 0.0, - StyleString.topBarHeight, + Style.topBarHeight, ); final offset = value - newValue; if (offset != 0) { diff --git a/lib/pages/common/dyn/common_dyn_controller.dart b/lib/pages/common/dyn/common_dyn_controller.dart index 99de930023..8aefd2fd27 100644 --- a/lib/pages/common/dyn/common_dyn_controller.dart +++ b/lib/pages/common/dyn/common_dyn_controller.dart @@ -14,6 +14,8 @@ abstract class CommonDynController extends ReplyController { late final horizontalPreview = Pref.horizontalPreview; late final List ratio = Pref.dynamicDetailRatio; + late final showDynActionBar = Pref.showDynActionBar; + @override Future> customGetData() => ReplyGrpc.mainList( type: replyType, diff --git a/lib/pages/common/dyn/common_dyn_page.dart b/lib/pages/common/dyn/common_dyn_page.dart index f792d9ec1d..eff1621158 100644 --- a/lib/pages/common/dyn/common_dyn_page.dart +++ b/lib/pages/common/dyn/common_dyn_page.dart @@ -1,8 +1,8 @@ import 'dart:math' show pi; -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_reply.dart'; import 'package:PiliPlus/common/utils/status_bar_tap.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'; @@ -10,6 +10,7 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; +import 'package:PiliPlus/pages/common/fab_mixin.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; @@ -23,7 +24,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:get/get.dart'; abstract class CommonDynPageState extends State - with SingleTickerProviderStateMixin { + with SingleTickerProviderStateMixin, FabMixin { CommonDynController get controller; late final ScrollController scrollController; @@ -37,28 +38,11 @@ abstract class CommonDynPageState extends State late double maxWidth; late double maxHeight; - bool _showFab = true; - - final fabOffset = const Offset(0, 1); - - late final AnimationController _fabAnimationCtr; - late final Animation fabAnim; - late final StatusBarTapObserver _statusBarTap; @override void initState() { super.initState(); - _fabAnimationCtr = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 200), - )..forward(); - fabAnim = _fabAnimationCtr.drive( - Tween( - begin: fabOffset, - end: Offset.zero, - ).chain(CurveTween(curve: Curves.easeInOut)), - ); scrollController = ScrollController()..addListener(listener); _statusBarTap = StatusBarTapObserver( scrollController: scrollController, @@ -87,20 +71,6 @@ abstract class CommonDynPageState extends State } } - void showFab() { - if (!_showFab) { - _showFab = true; - _fabAnimationCtr.forward(); - } - } - - void hideFab() { - if (_showFab) { - _showFab = false; - _fabAnimationCtr.reverse(); - } - } - @override void didChangeDependencies() { super.didChangeDependencies(); @@ -117,7 +87,6 @@ abstract class CommonDynPageState extends State scrollController ..removeListener(listener) ..dispose(); - _fabAnimationCtr.dispose(); super.dispose(); } @@ -139,7 +108,7 @@ abstract class CommonDynPageState extends State }, ), TextButton.icon( - style: StyleString.buttonStyle, + style: Style.buttonStyle, onPressed: controller.queryBySort, icon: Icon(Icons.sort, size: 16, color: secondary), label: Obx( @@ -238,6 +207,7 @@ abstract class CommonDynPageState extends State isVideoDetail: !showBackBtn, replyType: controller.replyType, firstFloor: replyItem, + upMid: controller.upMid, ), ); if (showBackBtn) { @@ -323,6 +293,16 @@ abstract class CommonDynPageState extends State ), ); + FloatingActionButtonLocation get floatingActionButtonLocation => + controller.showDynActionBar + ? const ActionBarLocation() + : const NoBottomPaddingFabLocation(); + + Widget get fabButton => Padding( + padding: .only(bottom: padding.bottom + kFloatingActionButtonMargin), + child: replyButton, + ); + Widget get replyButton => FloatingActionButton( heroTag: null, onPressed: () { diff --git a/lib/pages/common/fab_mixin.dart b/lib/pages/common/fab_mixin.dart new file mode 100644 index 0000000000..50255f8c7d --- /dev/null +++ b/lib/pages/common/fab_mixin.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +mixin FabMixin on State, TickerProvider { + bool _isFabVisible = true; + late final AnimationController _fabAnimationCtr; + late final Animation fabAnimation; + + @override + void initState() { + super.initState(); + _fabAnimationCtr = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 100), + ); + fabAnimation = _fabAnimationCtr.drive( + Tween( + begin: Offset.zero, + end: const Offset(0.0, 1.0), + ).chain(CurveTween(curve: Curves.easeInOut)), + ); + } + + void showFab() { + if (!_isFabVisible) { + _isFabVisible = true; + _fabAnimationCtr.reverse(); + } + } + + void hideFab() { + if (_isFabVisible) { + _isFabVisible = false; + _fabAnimationCtr.forward(); + } + } + + @override + void dispose() { + _fabAnimationCtr.dispose(); + super.dispose(); + } +} + +mixin _NoRightMarginMixin on StandardFabLocation { + @override + double getOffsetX(scaffoldGeometry, _) { + return scaffoldGeometry.scaffoldSize.width - + scaffoldGeometry.minInsets.right - + scaffoldGeometry.floatingActionButtonSize.width; + } +} + +mixin _NoBottomPaddingMixin on StandardFabLocation { + @override + double getOffsetY(scaffoldGeometry, _) { + return scaffoldGeometry.contentBottom - + scaffoldGeometry.floatingActionButtonSize.height; + } +} + +class NoRightMarginFabLocation extends StandardFabLocation + with FabFloatOffsetY, _NoRightMarginMixin { + const NoRightMarginFabLocation(); +} + +class NoBottomPaddingFabLocation extends StandardFabLocation + with FabEndOffsetX, _NoBottomPaddingMixin { + const NoBottomPaddingFabLocation(); +} + +class ActionBarLocation extends StandardFabLocation with _NoBottomPaddingMixin { + const ActionBarLocation(); + + @override + double getOffsetX(scaffoldGeometry, _) { + return 0.0; + } +} diff --git a/lib/pages/common/search/common_search_page.dart b/lib/pages/common/search/common_search_page.dart index 85273b276f..40023f731c 100644 --- a/lib/pages/common/search/common_search_page.dart +++ b/lib/pages/common/search/common_search_page.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -20,7 +21,7 @@ abstract class CommonSearchPageState if (controller case final MultiSelectBase multiCtr) { return Obx(() { final enableMultiSelect = multiCtr.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { diff --git a/lib/pages/danmaku_block/view.dart b/lib/pages/danmaku_block/view.dart index 5d85af8df7..6e4e349dee 100644 --- a/lib/pages/danmaku_block/view.dart +++ b/lib/pages/danmaku_block/view.dart @@ -64,7 +64,7 @@ class _DanmakuBlockPageState extends State { children: DmBlockType.values .map( (e) => KeepAliveWrapper( - builder: (context) => Obx( + child: Obx( () => tabViewBuilder(e.index, _controller.rules[e.index]), ), ), @@ -82,7 +82,7 @@ class _DanmakuBlockPageState extends State { Widget tabViewBuilder(final int tabIndex, List list) { if (list.isEmpty) { - return scrollErrorWidget(); + return scrollableError; } return ListView.builder( itemCount: list.length, @@ -97,7 +97,7 @@ class _DanmakuBlockPageState extends State { icon: const Icon(Icons.delete_outlined), onPressed: () => showConfirmDialog( context: context, - title: '确定删除该规则?', + title: const Text('确定删除该规则?'), onConfirm: () => _controller.danmakuFilterDel( tabIndex, itemIndex, diff --git a/lib/pages/download/controller.dart b/lib/pages/download/controller.dart index 96808fa9a3..3bb57c1a71 100644 --- a/lib/pages/download/controller.dart +++ b/lib/pages/download/controller.dart @@ -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; diff --git a/lib/pages/download/detail/view.dart b/lib/pages/download/detail/view.dart index 71d4a00a59..be1312dda9 100644 --- a/lib/pages/download/detail/view.dart +++ b/lib/pages/download/detail/view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart'; @@ -85,7 +86,7 @@ class _DownloadDetailPageState extends State final colorScheme = ColorScheme.of(context); return Obx(() { final enableMultiSelect = this.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { @@ -197,7 +198,7 @@ class _DownloadDetailPageState extends State void onRemove() { showConfirmDialog( context: context, - title: '确定删除选中视频?', + title: const Text('确定删除选中视频?'), onConfirm: () async { SmartDialog.showLoading(); final watchProgress = GStorage.watchProgress; diff --git a/lib/pages/download/detail/widgets/item.dart b/lib/pages/download/detail/widgets/item.dart index ea887135ab..f8aae931a0 100644 --- a/lib/pages/download/detail/widgets/item.dart +++ b/lib/pages/download/detail/widgets/item.dart @@ -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, ); }, @@ -169,7 +169,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( @@ -179,7 +179,7 @@ class DetailItem extends StatelessWidget { clipBehavior: Clip.none, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, constraints) { final cover = File( @@ -195,7 +195,7 @@ class DetailItem extends StatelessWidget { } return cover.existsSync() ? ClipRRect( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: Image.file( cover, width: maxWidth, @@ -454,7 +454,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, diff --git a/lib/pages/download/downloading/view.dart b/lib/pages/download/downloading/view.dart index e84255b09c..001da6ea90 100644 --- a/lib/pages/download/downloading/view.dart +++ b/lib/pages/download/downloading/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart'; @@ -33,7 +34,7 @@ class _DownloadingPageState extends State Widget build(BuildContext context) { return Obx(() { final enableMultiSelect = this.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { @@ -108,7 +109,7 @@ class _DownloadingPageState extends State void onRemove() { showConfirmDialog( context: context, - title: '确定删除选中视频?', + title: const Text('确定删除选中视频?'), onConfirm: () async { SmartDialog.showLoading(); final allChecked = this.allChecked.toSet(); diff --git a/lib/pages/download/search/controller.dart b/lib/pages/download/search/controller.dart index d15697d3ab..e291fa77b1 100644 --- a/lib/pages/download/search/controller.dart +++ b/lib/pages/download/search/controller.dart @@ -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(); diff --git a/lib/pages/download/view.dart b/lib/pages/download/view.dart index 2fb4e8ff60..803abc80d7 100644 --- a/lib/pages/download/view.dart +++ b/lib/pages/download/view.dart @@ -1,11 +1,12 @@ import 'dart:async'; import 'dart:io'; -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/cached_layout_builder.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/select_mask.dart'; @@ -83,7 +84,7 @@ class _DownloadPageState extends State { final padding = MediaQuery.viewPaddingOf(context); return Obx(() { final enableMultiSelect = _controller.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { @@ -298,7 +299,7 @@ class _DownloadPageState extends State { Get.back(); showConfirmDialog( context: context, - title: '确定删除?', + title: const Text('确定删除?'), onConfirm: () async { await GStorage.watchProgress.deleteAll( pageInfo.entries.map((e) => e.cid.toString()), @@ -363,7 +364,7 @@ class _DownloadPageState extends State { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -373,7 +374,7 @@ class _DownloadPageState extends State { clipBehavior: Clip.none, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: CachedLayoutBuilder( builder: (context, constraints) => NetworkImgLayer( src: pageInfo.cover, diff --git a/lib/pages/dynamics/widgets/additional_panel.dart b/lib/pages/dynamics/widgets/additional_panel.dart index f707dec74d..f817eae780 100644 --- a/lib/pages/dynamics/widgets/additional_panel.dart +++ b/lib/pages/dynamics/widgets/additional_panel.dart @@ -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'; @@ -22,7 +22,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) { diff --git a/lib/pages/dynamics/widgets/author_panel.dart b/lib/pages/dynamics/widgets/author_panel.dart index eed73155c3..9e493f7dda 100644 --- a/lib/pages/dynamics/widgets/author_panel.dart +++ b/lib/pages/dynamics/widgets/author_panel.dart @@ -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,150 @@ 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, - ), + 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, + ), + 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, ), - ?pubTs, - ], + ), + ?pubTs, + ], + ), + ), + ], + ), + ), + ); + 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), + ), + ), + ?moreBtn, + ], + ); + } else if (moduleAuthor.decorate != null) { + const height = 32.0; + header = Stack( + clipBehavior: .none, + children: [ + Positioned( + top: 0, + right: 0, + bottom: 0, + child: Center( + child: CachedNetworkImage( + height: height, + memCacheHeight: height.cacheSize(context), + imageUrl: ImageUtils.safeThumbnailUrl( + moduleAuthor.decorate!.cardUrl, ), - ], + placeholder: (_, _) => const SizedBox.shrink(), + ), ), ), - ), - 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, - ), - ), - ), - _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, - ), - ), - ), - ], + if (moduleAuthor.decorate!.fan?.numStr?.isNotEmpty == true) + Positioned( + top: 0, + bottom: 0, + right: 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!, ), - _moreWidget(context), - ], - ) - : _moreWidget(context), - ), - ], - ); - } - - Widget _moreWidget(BuildContext context) => isSave - ? const SizedBox.shrink() - : 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), + 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 +273,7 @@ class AuthorPanel extends StatelessWidget { children: [ InkWell( onTap: Get.back, - borderRadius: StyleString.bottomSheetRadius, + borderRadius: Style.bottomSheetRadius, child: SizedBox( height: 35, child: Center( diff --git a/lib/pages/dynamics/widgets/content_panel.dart b/lib/pages/dynamics/widgets/content_panel.dart index b794d66c66..2a7ea0547b 100644 --- a/lib/pages/dynamics/widgets/content_panel.dart +++ b/lib/pages/dynamics/widgets/content_panel.dart @@ -90,6 +90,7 @@ Widget content( ), if (pics != null && pics.isNotEmpty) ImageGridView( + fullScreen: true, picArr: pics .map( (item) => ImageModel( @@ -100,7 +101,6 @@ Widget content( ), ) .toList(), - fullScreen: true, ), ], ), diff --git a/lib/pages/dynamics/widgets/live_panel_sub.dart b/lib/pages/dynamics/widgets/live_panel_sub.dart index 8efe08e140..e28fd78650 100644 --- a/lib/pages/dynamics/widgets/live_panel_sub.dart +++ b/lib/pages/dynamics/widgets/live_panel_sub.dart @@ -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/cached_layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -42,7 +43,7 @@ Widget livePanelSub( CachedLayoutBuilder( 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( diff --git a/lib/pages/dynamics/widgets/live_rcmd_panel.dart b/lib/pages/dynamics/widgets/live_rcmd_panel.dart index a36ffe6929..4ff1a2a17f 100644 --- a/lib/pages/dynamics/widgets/live_rcmd_panel.dart +++ b/lib/pages/dynamics/widgets/live_rcmd_panel.dart @@ -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/cached_layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -36,7 +37,7 @@ Widget liveRcmdPanel( CachedLayoutBuilder( 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( diff --git a/lib/pages/dynamics/widgets/module_panel.dart b/lib/pages/dynamics/widgets/module_panel.dart index 6f267d3c28..a9d3c1a0df 100644 --- a/lib/pages/dynamics/widgets/module_panel.dart +++ b/lib/pages/dynamics/widgets/module_panel.dart @@ -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/image/network_img_layer.dart'; import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pbenum.dart' @@ -114,9 +114,9 @@ Widget module( : theme.colorScheme.surface, shape: floor == 1 ? null - : const RoundedRectangleBorder(borderRadius: StyleString.mdRadius), + : const RoundedRectangleBorder(borderRadius: Style.mdRadius), child: InkWell( - borderRadius: floor == 1 ? null : StyleString.mdRadius, + borderRadius: floor == 1 ? null : Style.mdRadius, onTap: () { try { String url = common.jumpUrl!; @@ -153,13 +153,12 @@ Widget module( spacing: 2, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (common.title?.isNotEmpty ?? false) - Text( - common.title!, - style: TextStyle(color: theme.colorScheme.primary), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), + Text( + '${common.titlePrefix ?? ''}${common.title ?? ''}', + style: TextStyle(color: theme.colorScheme.primary), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), if (common.desc?.isNotEmpty ?? false) Text( common.desc!, @@ -180,7 +179,7 @@ Widget module( ); case 'DYNAMIC_TYPE_MUSIC': final music = major!.music!; - final borderRadius = floor == 1 ? null : StyleString.mdRadius; + final borderRadius = floor == 1 ? null : Style.mdRadius; final Color bgColor = floor == 1 ? theme.dividerColor.withValues(alpha: 0.08) : theme.colorScheme.surface; diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index d40c055316..3d8daaebb6 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -162,7 +163,7 @@ class _UpPanelState extends State { width: 38, height: 38, cacheWidth: 38.cacheSize(context), - 'assets/images/logo/logo.png', + Assets.logo, ), ); } else { diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index af83995459..2641dd704e 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -1,5 +1,6 @@ // 视频or合集 -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/cached_layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -53,7 +54,7 @@ Widget videoSeasonWidget( CachedLayoutBuilder( builder: (context, constraints) => NetworkImgLayer( width: constraints.maxWidth, - height: constraints.maxWidth / StyleString.aspectRatio, + height: constraints.maxWidth / Style.aspectRatio, src: cover, quality: 40, ), @@ -88,7 +89,7 @@ Widget videoSeasonWidget( ], ), borderRadius: BorderRadius.vertical( - bottom: StyleString.imgRadius, + bottom: Style.imgRadius, ), ), child: DefaultTextStyle.merge( @@ -122,7 +123,7 @@ Widget videoSeasonWidget( ], const Spacer(), Image.asset( - 'assets/images/play.png', + Assets.play, width: 50, height: 50, cacheHeight: 50.cacheSize(context), diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index fae1ad0416..51f4c437a7 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -1,6 +1,6 @@ import 'dart:math' show max; -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/button/toolbar_icon_button.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; @@ -299,9 +299,9 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { if (imageList.length != limit) SliverToBoxAdapter( child: Material( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: InkWell( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, onTap: () => onPickImage(() { if (imageList.isNotEmpty && !enablePublish.value) { enablePublish.value = true; @@ -311,7 +311,7 @@ class _CreateDynPanelState extends CommonRichTextPubPageState { width: 100, height: 100, decoration: BoxDecoration( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, color: theme.colorScheme.secondaryContainer, ), child: const Center(child: Icon(Icons.add, size: 35)), diff --git a/lib/pages/dynamics_create_vote/controller.dart b/lib/pages/dynamics_create_vote/controller.dart index 4e57cf3e0e..db4c9e463d 100644 --- a/lib/pages/dynamics_create_vote/controller.dart +++ b/lib/pages/dynamics_create_vote/controller.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/dynamics/vote_model.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/widgets.dart' show Text; import 'package:get/get.dart'; class CreateVoteController extends GetxController { @@ -71,7 +72,7 @@ class CreateVoteController extends GetxController { } else { showConfirmDialog( context: Get.context!, - title: res.toString(), + title: Text(res.toString()), onConfirm: Get.back, ); } diff --git a/lib/pages/dynamics_detail/controller.dart b/lib/pages/dynamics_detail/controller.dart index 1d5a31f7c2..261ea8155e 100644 --- a/lib/pages/dynamics_detail/controller.dart +++ b/lib/pages/dynamics_detail/controller.dart @@ -4,7 +4,6 @@ import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; import 'package:PiliPlus/utils/id_utils.dart'; -import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -15,8 +14,6 @@ class DynamicDetailController extends CommonDynController { late int replyType; late DynamicItemModel dynItem; - late final showDynActionBar = Pref.showDynActionBar; - @override dynamic get sourceId => replyType == 1 ? IdUtils.av2bv(oid) : oid; diff --git a/lib/pages/dynamics_detail/view.dart b/lib/pages/dynamics_detail/view.dart index d83365487e..869ff577e5 100644 --- a/lib/pages/dynamics_detail/view.dart +++ b/lib/pages/dynamics_detail/view.dart @@ -70,6 +70,11 @@ class _DynamicDetailPageState extends CommonDynPageState { ) : _buildBody(theme), ), + floatingActionButtonLocation: floatingActionButtonLocation, + floatingActionButton: SlideTransition( + position: fabAnimation, + child: _buildBottom(theme), + ), ); } @@ -319,166 +324,139 @@ class _DynamicDetailPageState extends CommonDynPageState { ], ); } - return Stack( - clipBehavior: Clip.none, - children: [ - child, - _buildBottom(theme), - ], - ); + return child; } Widget _buildBottom(ThemeData theme) { - return Positioned( - left: 0, - right: 0, - bottom: 0, - child: SlideTransition( - position: fabAnim, - child: Builder( - builder: (context) { - if (!controller.showDynActionBar) { - return Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: padding.bottom + kFloatingActionButtonMargin, - ), - child: replyButton, - ), - ); - } + if (!controller.showDynActionBar) { + return fabButton; + } - final moduleStat = controller.dynItem.modules.moduleStat; - final primary = theme.colorScheme.primary; - final outline = theme.colorScheme.outline; - final btnStyle = TextButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.padded, - padding: const EdgeInsets.symmetric(horizontal: 15), - foregroundColor: outline, - ); + final primary = theme.colorScheme.primary; + final outline = theme.colorScheme.outline; + final btnStyle = TextButton.styleFrom( + tapTargetSize: .padded, + padding: const EdgeInsets.symmetric(horizontal: 15), + foregroundColor: outline, + ); - Widget textIconButton({ - required IconData icon, - required String text, - required DynamicStat? stat, - required ValueChanged onPressed, - IconData? activatedIcon, - }) { - final status = stat?.status == true; - final color = status ? primary : outline; - final iconWidget = Icon( - status ? activatedIcon : icon, - size: 16, - color: color, - ); - return TextButton.icon( - onPressed: () => onPressed(iconWidget.color!), - icon: iconWidget, - style: btnStyle, - label: Text( - stat?.count != null ? NumUtils.numFormat(stat!.count) : text, - style: TextStyle(color: color), - ), - ); - } + Widget textIconButton({ + required IconData icon, + required String text, + required DynamicStat? stat, + required ValueChanged onPressed, + IconData? activatedIcon, + }) { + final status = stat?.status == true; + final color = status ? primary : outline; + final iconWidget = Icon( + status ? activatedIcon : icon, + size: 16, + color: color, + ); + return TextButton.icon( + onPressed: () => onPressed(iconWidget.color!), + icon: iconWidget, + style: btnStyle, + label: Text( + stat?.count != null ? NumUtils.numFormat(stat!.count) : text, + style: TextStyle(color: color), + ), + ); + } - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Padding( - padding: const EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin, + final moduleStat = controller.dynItem.modules.moduleStat; + return Padding( + padding: .only(left: padding.left, right: padding.right), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin, + ), + child: replyButton, + ), + Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + border: Border( + top: BorderSide( + color: theme.colorScheme.outline.withValues( + alpha: 0.08, ), - child: replyButton, ), - Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - border: Border( - top: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.08, + ), + ), + padding: EdgeInsets.only(bottom: padding.bottom), + child: Row( + children: [ + Expanded( + child: Builder( + builder: (btnContext) { + final forward = moduleStat?.forward; + return textIconButton( + icon: FontAwesomeIcons.shareFromSquare, + text: '转发', + stat: forward, + onPressed: (_) => showModalBottomSheet( + context: context, + isScrollControlled: true, + useSafeArea: true, + builder: (context) => RepostPanel( + item: controller.dynItem, + onSuccess: () { + if (forward != null) { + int count = forward.count ?? 0; + forward.count = count + 1; + if (btnContext.mounted) { + (btnContext as Element).markNeedsBuild(); + } + } + }, + ), ), - ), + ); + }, + ), + ), + Expanded( + child: textIconButton( + icon: CustomIcons.share_node, + text: '分享', + stat: null, + onPressed: (_) => Utils.shareText( + '${HttpString.dynamicShareBaseUrl}/${controller.dynItem.idStr}', ), ), - padding: EdgeInsets.only(bottom: padding.bottom), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Expanded( - child: Builder( - builder: (btnContext) { - final forward = moduleStat?.forward; - return textIconButton( - icon: FontAwesomeIcons.shareFromSquare, - text: '转发', - stat: forward, - onPressed: (_) => showModalBottomSheet( - context: context, - isScrollControlled: true, - useSafeArea: true, - builder: (context) => RepostPanel( - item: controller.dynItem, - onSuccess: () { - if (forward != null) { - int count = forward.count ?? 0; - forward.count = count + 1; - if (btnContext.mounted) { - (btnContext as Element) - .markNeedsBuild(); - } - } - }, - ), - ), - ); - }, - ), - ), - Expanded( - child: textIconButton( - icon: CustomIcons.share_node, - text: '分享', - stat: null, - onPressed: (_) => Utils.shareText( - '${HttpString.dynamicShareBaseUrl}/${controller.dynItem.idStr}', - ), - ), - ), - Expanded( - child: Builder( - builder: (context) { - return textIconButton( - icon: FontAwesomeIcons.thumbsUp, - activatedIcon: FontAwesomeIcons.solidThumbsUp, - text: '点赞', - stat: moduleStat?.like, - onPressed: (iconColor) => - RequestUtils.onLikeDynamic( - controller.dynItem, - iconColor == primary, - () { - if (context.mounted) { - (context as Element).markNeedsBuild(); - } - }, - ), - ); + ), + Expanded( + child: Builder( + builder: (context) { + return textIconButton( + icon: FontAwesomeIcons.thumbsUp, + activatedIcon: FontAwesomeIcons.solidThumbsUp, + text: '点赞', + stat: moduleStat?.like, + onPressed: (iconColor) => RequestUtils.onLikeDynamic( + controller.dynItem, + iconColor == primary, + () { + if (context.mounted) { + (context as Element).markNeedsBuild(); + } }, ), - ), - ], + ); + }, ), ), ], - ); - }, - ), + ), + ), + ], ), ); } diff --git a/lib/pages/dynamics_mention/view.dart b/lib/pages/dynamics_mention/view.dart index 0ec7a86a0e..f42cab636f 100644 --- a/lib/pages/dynamics_mention/view.dart +++ b/lib/pages/dynamics_mention/view.dart @@ -234,8 +234,8 @@ class _DynMentionPanelState LoadingState?> loadingState, ) { return switch (loadingState) { - Loading() => SliverPadding( - padding: const EdgeInsets.only(top: 8), + Loading() => const SliverPadding( + padding: EdgeInsets.only(top: 8), sliver: linearLoading, ), Success?>(:final response) => diff --git a/lib/pages/dynamics_select_topic/view.dart b/lib/pages/dynamics_select_topic/view.dart index 97d780fbab..ee6c714223 100644 --- a/lib/pages/dynamics_select_topic/view.dart +++ b/lib/pages/dynamics_select_topic/view.dart @@ -188,7 +188,7 @@ class _SelectTopicPanelState LoadingState?> loadingState, ) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success?>(:final response) => response != null && response.isNotEmpty ? ListView.builder( diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index 545e529010..544046c856 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/dynamic_sliver_app_bar/dynamic_sliver_app_bar.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; @@ -12,6 +13,7 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/top_details.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; import 'package:PiliPlus/pages/dynamics_create/view.dart'; import 'package:PiliPlus/pages/dynamics_topic/controller.dart'; +import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/num_utils.dart'; @@ -157,9 +159,10 @@ class _DynTopicPageState extends State with DynMixin { flexibleSpace: Container( decoration: BoxDecoration( image: DecorationImage( - image: Image.asset( - 'assets/images/topic-header-bg.png', - ).image, + image: ResizeImage( + width: MediaQuery.widthOf(context).cacheSize(context), + const AssetImage(Assets.topicHeader), + ), filterQuality: FilterQuality.low, fit: BoxFit.cover, ), diff --git a/lib/pages/emote/view.dart b/lib/pages/emote/view.dart index 3be2ff6aa7..8e8a35f941 100644 --- a/lib/pages/emote/view.dart +++ b/lib/pages/emote/view.dart @@ -50,7 +50,7 @@ class _EmotePanelState extends State Get.currentRoute.startsWith('/whisperDetail') ? 8 : 2, ); return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => response != null && response.isNotEmpty ? Column( diff --git a/lib/pages/episode_panel/view.dart b/lib/pages/episode_panel/view.dart index 2ae1072a3c..ba742459f5 100644 --- a/lib/pages/episode_panel/view.dart +++ b/lib/pages/episode_panel/view.dart @@ -1,6 +1,7 @@ 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/badge.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart'; @@ -279,7 +280,7 @@ class _EpisodePanelState extends State ) { final isCurrTab = tabIndex == widget.initialTabIndex; return KeepAliveWrapper( - builder: (context) => CustomScrollView( + child: CustomScrollView( reverse: _isReversed[tabIndex], physics: const AlwaysScrollableScrollPhysics(), controller: _itemScrollController[tabIndex], @@ -460,7 +461,7 @@ class _EpisodePanelState extends State onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -505,7 +506,7 @@ class _EpisodePanelState extends State ) else if (isCurrentIndex) Image.asset( - 'assets/images/live.png', + Assets.livingStatic, color: primary, height: 12, cacheHeight: 12.cacheSize(context), diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index 81df03c1fb..d10ec5d19c 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -67,7 +67,7 @@ class _FansPageState extends FollowTypePageState { Widget buildItem(int index, FollowItemModel item) { void onRemove() => showConfirmDialog( context: context, - title: '确定移除 ${item.uname} ?', + title: Text('确定移除 ${item.uname} ?'), onConfirm: () => controller.onRemoveFan(index, item.mid), ); diff --git a/lib/pages/fav/article/view.dart b/lib/pages/fav/article/view.dart index 1bef596178..f2b74a0176 100644 --- a/lib/pages/fav/article/view.dart +++ b/lib/pages/fav/article/view.dart @@ -64,7 +64,7 @@ class _FavArticlePageState extends State item: item, onDelete: () => showConfirmDialog( context: context, - title: '确定取消收藏?', + title: const Text('确定取消收藏?'), onConfirm: () => _favArticleController.onRemove(index, item.opusId!), ), diff --git a/lib/pages/fav/article/widget/item.dart b/lib/pages/fav/article/widget/item.dart index 934df096ef..abf6ba187a 100644 --- a/lib/pages/fav/article/widget/item.dart +++ b/lib/pages/fav/article/widget/item.dart @@ -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/flutter/layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -36,7 +36,7 @@ class FavArticleItem extends StatelessWidget { ), child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -44,7 +44,7 @@ class FavArticleItem extends StatelessWidget { children: [ if (item.cover != null) ...[ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { return NetworkImgLayer( diff --git a/lib/pages/fav/cheese/view.dart b/lib/pages/fav/cheese/view.dart index c1a3e4fef5..c41616520d 100644 --- a/lib/pages/fav/cheese/view.dart +++ b/lib/pages/fav/cheese/view.dart @@ -66,7 +66,7 @@ class _FavCheesePageState extends State item: item, onRemove: () => showConfirmDialog( context: context, - title: '确定取消收藏该课堂?', + title: const Text('确定取消收藏该课堂?'), onConfirm: () => _controller.onRemove(index, item.seasonId!), ), diff --git a/lib/pages/fav/note/child_view.dart b/lib/pages/fav/note/child_view.dart index 7a42cbba73..4c9efa84ed 100644 --- a/lib/pages/fav/note/child_view.dart +++ b/lib/pages/fav/note/child_view.dart @@ -125,7 +125,7 @@ class _FavNoteChildPageState extends State if (_favNoteController.checkedCount != 0) { showConfirmDialog( context: context, - title: '确定删除已选中的笔记吗?', + title: const Text('确定删除已选中的笔记吗?'), onConfirm: _favNoteController.onRemove, ); } diff --git a/lib/pages/fav/note/widget/item.dart b/lib/pages/fav/note/widget/item.dart index 6135e61d7a..61396624e7 100644 --- a/lib/pages/fav/note/widget/item.dart +++ b/lib/pages/fav/note/widget/item.dart @@ -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/select_mask.dart'; @@ -49,7 +49,7 @@ class FavNoteItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -58,7 +58,7 @@ class FavNoteItem extends StatelessWidget { children: [ if (item.pic?.isNotEmpty == true) AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { return Stack( diff --git a/lib/pages/fav/pgc/widget/item.dart b/lib/pages/fav/pgc/widget/item.dart index db59d8af78..3e4bd32478 100644 --- a/lib/pages/fav/pgc/widget/item.dart +++ b/lib/pages/fav/pgc/widget/item.dart @@ -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/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; @@ -52,7 +52,7 @@ class FavPgcItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( diff --git a/lib/pages/fav/topic/view.dart b/lib/pages/fav/topic/view.dart index 0a35f2c338..69f75c3c61 100644 --- a/lib/pages/fav/topic/view.dart +++ b/lib/pages/fav/topic/view.dart @@ -1,7 +1,9 @@ -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/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart' + show m3eLoading; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models_new/fav/fav_topic/topic_item.dart'; import 'package:PiliPlus/pages/fav/topic/controller.dart'; @@ -37,9 +39,9 @@ class _FavTopicPageState extends State slivers: [ SliverPadding( padding: EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - top: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, + top: Style.safeSpace, bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), sliver: Obx( @@ -66,9 +68,7 @@ class _FavTopicPageState extends State Loading() => const SliverToBoxAdapter( child: SizedBox( height: 125, - child: Center( - child: CircularProgressIndicator(), - ), + child: m3eLoading, ), ), Success(:final response) => @@ -83,7 +83,7 @@ class _FavTopicPageState extends State void onLongPress() => showConfirmDialog( context: context, - title: '确定取消收藏?', + title: const Text('确定取消收藏?'), onConfirm: () => _controller.onRemove(index, item.id!), ); diff --git a/lib/pages/fav/video/widgets/item.dart b/lib/pages/fav/video/widgets/item.dart index 76401d9b0a..6174490ea4 100644 --- a/lib/pages/fav/video/widgets/item.dart +++ b/lib/pages/fav/video/widgets/item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -40,7 +40,7 @@ class FavVideoItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { return Hero( diff --git a/lib/pages/fav_create/view.dart b/lib/pages/fav_create/view.dart index aa79621c44..b341eafe68 100644 --- a/lib/pages/fav_create/view.dart +++ b/lib/pages/fav_create/view.dart @@ -107,7 +107,7 @@ class _CreateFavPageState extends State { ? _buildBody(theme) : _errMsg?.isNotEmpty == true ? scrollErrorWidget(errMsg: _errMsg, onReload: _getFolderInfo) - : const Center(child: CircularProgressIndicator()) + : m3eLoading : _buildBody(theme), ); } diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 36245bfb50..8f23ec9d9d 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -16,7 +16,7 @@ import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; -import 'package:flutter/services.dart' show ValueChanged; +import 'package:flutter/widgets.dart' show Text, ValueChanged; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -51,8 +51,8 @@ mixin BaseFavController void onRemove() { showConfirmDialog( context: Get.context!, - content: '确认删除所选收藏吗?', - title: '提示', + title: const Text('提示'), + content: const Text('确认删除所选收藏吗?'), onConfirm: () async { final removeList = allChecked.toSet(); final res = await FavHttp.favVideo( diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index bc60e9732f..a083e49cc8 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; @@ -8,6 +9,8 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/fav_order_type.dart'; import 'package:PiliPlus/models_new/fav/fav_detail/media.dart'; import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; +import 'package:PiliPlus/pages/common/fab_mixin.dart' + show NoRightMarginFabLocation; import 'package:PiliPlus/pages/dynamics_repost/view.dart'; import 'package:PiliPlus/pages/fav_detail/controller.dart'; import 'package:PiliPlus/pages/fav_detail/widget/fav_video_card.dart'; @@ -49,7 +52,7 @@ class _FavDetailPageState extends State with GridMixin { return Obx( () { final enableMultiSelect = _favDetailController.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { @@ -58,7 +61,7 @@ class _FavDetailPageState extends State with GridMixin { }, child: Scaffold( resizeToAvoidBottomInset: false, - floatingActionButtonLocation: const CustomFabLocation(), + floatingActionButtonLocation: const NoRightMarginFabLocation(), floatingActionButton: Padding( padding: const EdgeInsets.only( right: kFloatingActionButtonMargin, @@ -277,7 +280,7 @@ class _FavDetailPageState extends State with GridMixin { PopupMenuItem( onTap: () => showConfirmDialog( context: context, - title: '确定删除该收藏夹?', + title: const Text('确定删除该收藏夹?'), onConfirm: () => FavHttp.deleteFolder(mediaIds: mediaId).then((res) { if (res.isSuccess) { @@ -507,18 +510,3 @@ class _FavDetailPageState extends State with GridMixin { }; } } - -class CustomFabLocation extends StandardFabLocation with FabFloatOffsetY { - const CustomFabLocation(); - - @override - double getOffsetX( - ScaffoldPrelayoutGeometry scaffoldGeometry, - double adjustment, - ) { - return scaffoldGeometry.scaffoldSize.width - - scaffoldGeometry.minInsets.right - - scaffoldGeometry.floatingActionButtonSize.width + - adjustment; - } -} diff --git a/lib/pages/fav_detail/widget/fav_video_card.dart b/lib/pages/fav_detail/widget/fav_video_card.dart index 6a70481249..1cbe344446 100644 --- a/lib/pages/fav_detail/widget/fav_video_card.dart +++ b/lib/pages/fav_detail/widget/fav_video_card.dart @@ -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/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; @@ -91,14 +91,14 @@ class FavVideoCardH extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/fav_panel/view.dart b/lib/pages/fav_panel/view.dart index ea3b91e946..bb2fc822f2 100644 --- a/lib/pages/fav_panel/view.dart +++ b/lib/pages/fav_panel/view.dart @@ -41,7 +41,7 @@ class _FavPanelState extends State { Widget get _buildBody { late final list = widget.ctr.favFolderData.value.list!; return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success() => ListView.builder( controller: widget.scrollController, itemCount: list.length, diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart index eacb2e9d6a..3401b387da 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -109,7 +109,7 @@ class _FollowPageState extends State { Widget _buildBody(LoadingState loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success() => Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -191,7 +191,7 @@ class _FollowPageState extends State { String tagName = item.name!; showConfirmDialog( context: context, - title: '编辑分组名称', + title: const Text('编辑分组名称'), content: TextFormField( autofocus: true, initialValue: tagName, @@ -221,8 +221,8 @@ class _FollowPageState extends State { Get.back(); showConfirmDialog( context: context, - title: '删除分组', - content: '删除后,该分组下的用户依旧保留?', + title: const Text('删除分组'), + content: const Text('删除后,该分组下的用户依旧保留?'), onConfirm: () => _followController.onDelTag(item.tagid!), ); }, @@ -242,7 +242,7 @@ class _FollowPageState extends State { String tagName = ''; showConfirmDialog( context: context, - title: '新建分组', + title: const Text('新建分组'), content: TextFormField( autofocus: true, initialValue: tagName, diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart index 61da5a7561..203283dc49 100644 --- a/lib/pages/follow/widgets/follow_item.dart +++ b/lib/pages/follow/widgets/follow_item.dart @@ -51,7 +51,7 @@ class FollowItem extends StatelessWidget { PendantAvatar( size: 45, badgeSize: 14, - avatar: item.face, + item.face, officialType: item.officialVerify?.type, ), const SizedBox(width: 10), diff --git a/lib/pages/group_panel/view.dart b/lib/pages/group_panel/view.dart index 69173e8a69..4dab7f44e8 100644 --- a/lib/pages/group_panel/view.dart +++ b/lib/pages/group_panel/view.dart @@ -11,7 +11,7 @@ import 'package:get/get.dart'; class GroupPanel extends StatefulWidget { final int mid; - final List? tags; + final List? tags; final ScrollController? scrollController; const GroupPanel({ super.key, @@ -68,7 +68,7 @@ class _GroupPanelState extends State { Widget get _buildBody { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => ListView.builder( controller: widget.scrollController, itemCount: response.length, diff --git a/lib/pages/history/controller.dart b/lib/pages/history/controller.dart index 2e95e6872e..e7e418c7b0 100644 --- a/lib/pages/history/controller.dart +++ b/lib/pages/history/controller.dart @@ -126,8 +126,8 @@ class HistoryController void onRemove() { showConfirmDialog( context: Get.context!, - content: '确认删除所选历史记录吗?', - title: '提示', + title: const Text('提示'), + content: const Text('确认删除所选历史记录吗?'), onConfirm: () => _onDelete(allChecked.toSet()), ); } diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 610998d029..8a4219c517 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -1,5 +1,6 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; @@ -84,7 +85,7 @@ class _HistoryPageState extends State () { final enableMultiSelect = _historyController.baseCtr.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { @@ -141,7 +142,7 @@ class _HistoryPageState extends State horizontalDragGestureRecognizer: CustomHorizontalDragGestureRecognizer.new, children: [ - KeepAliveWrapper(builder: (context) => child), + KeepAliveWrapper(child: child), ..._historyController.tabs.map( (item) => HistoryPage(type: item.type), ), diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 6f660ecf8a..08c9cf1497 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -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/network_img_layer.dart'; @@ -104,14 +104,14 @@ class HistoryItem extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/history_search/controller.dart b/lib/pages/history_search/controller.dart index 2d67344f3d..1122664169 100644 --- a/lib/pages/history_search/controller.dart +++ b/lib/pages/history_search/controller.dart @@ -6,6 +6,7 @@ import 'package:PiliPlus/models_new/history/list.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; import 'package:PiliPlus/pages/common/search/common_search_controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; +import 'package:flutter/widgets.dart' show Text; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -45,8 +46,8 @@ class HistorySearchController void onRemove() { showConfirmDialog( context: Get.context!, - content: '确认删除所选历史记录吗?', - title: '提示', + title: const Text('提示'), + content: const Text('确认删除所选历史记录吗?'), onConfirm: () async { SmartDialog.showLoading(msg: '请求中'); final removeList = allChecked.toSet(); diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 1642377897..bc2862e03c 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/custom_height_widget.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; @@ -48,7 +48,7 @@ class _HomePageState extends CommonPageState isScrollable: true, dividerColor: Colors.transparent, dividerHeight: 0, - splashBorderRadius: StyleString.mdRadius, + splashBorderRadius: Style.mdRadius, tabAlignment: TabAlignment.center, onTap: (_) { feedBack(); @@ -105,7 +105,7 @@ class _HomePageState extends CommonPageState final offset = barOffset.value; return CustomHeightWidget( offset: Offset(0, -offset), - height: StyleString.topBarHeight - offset, + height: Style.topBarHeight - offset, child: Padding( padding: padding, child: child, @@ -123,7 +123,7 @@ class _HomePageState extends CommonPageState child: AnimatedContainer( curve: Curves.easeInOutCubicEmphasized, duration: const Duration(milliseconds: 500), - height: showSearchBar ? StyleString.topBarHeight : 0, + height: showSearchBar ? Style.topBarHeight : 0, padding: padding, child: child, ), @@ -132,7 +132,7 @@ class _HomePageState extends CommonPageState } } return Container( - height: StyleString.topBarHeight, + height: Style.topBarHeight, padding: padding, child: child, ); diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index 3799a80988..69e1e561f6 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -28,8 +28,8 @@ mixin BaseLaterController void onRemove() { showConfirmDialog( context: Get.context!, - content: '确认删除所选稍后再看吗?', - title: '提示', + title: const Text('提示'), + content: const Text('确认删除所选稍后再看吗?'), onConfirm: () async { final removeList = allChecked.toSet(); SmartDialog.showLoading(msg: '请求中'); @@ -135,8 +135,8 @@ class LaterController extends MultiSelectController }; showConfirmDialog( context: context, - title: '确认', - content: content, + title: const Text('确认'), + content: Text(content), onConfirm: () async { final res = await UserHttp.toViewClear(cleanType); if (res.isSuccess) { diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index c38eeadd9f..db37be02a2 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -1,11 +1,13 @@ import 'package:PiliPlus/common/widgets/appbar/appbar.dart'; import 'package:PiliPlus/common/widgets/flutter/page/tabs.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/models/common/later_view_type.dart'; import 'package:PiliPlus/models_new/later/list.dart'; -import 'package:PiliPlus/pages/fav_detail/view.dart'; +import 'package:PiliPlus/pages/common/fab_mixin.dart' + show NoRightMarginFabLocation; import 'package:PiliPlus/pages/later/base_controller.dart'; import 'package:PiliPlus/pages/later/controller.dart'; import 'package:PiliPlus/utils/accounts.dart'; @@ -64,7 +66,7 @@ class _LaterPageState extends State return Obx( () { final enableMultiSelect = _baseCtr.enableMultiSelect.value; - return PopScope( + return popScope( canPop: !enableMultiSelect, onPopInvokedWithResult: (didPop, result) { if (enableMultiSelect) { @@ -74,7 +76,7 @@ class _LaterPageState extends State child: Scaffold( resizeToAvoidBottomInset: false, appBar: _buildAppbar(enableMultiSelect), - floatingActionButtonLocation: const CustomFabLocation(), + floatingActionButtonLocation: const NoRightMarginFabLocation(), floatingActionButton: Padding( padding: const .only(right: kFloatingActionButtonMargin), child: Obx( diff --git a/lib/pages/later/widgets/video_card_h_later.dart b/lib/pages/later/widgets/video_card_h_later.dart index 9647a22caa..f4cad6b693 100644 --- a/lib/pages/later/widgets/video_card_h_later.dart +++ b/lib/pages/later/widgets/video_card_h_later.dart @@ -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/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; @@ -78,14 +78,14 @@ class VideoCardHLater extends StatelessWidget { }, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { final double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index 10687ae27d..8f10a79e57 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/button/more_btn.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; @@ -52,8 +52,8 @@ class _LivePageState extends State final ThemeData theme = Theme.of(context); return Container( clipBehavior: Clip.hardEdge, - margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), - decoration: const BoxDecoration(borderRadius: StyleString.mdRadius), + margin: const EdgeInsets.symmetric(horizontal: Style.safeSpace), + decoration: const BoxDecoration(borderRadius: Style.mdRadius), child: refreshIndicator( onRefresh: controller.onRefresh, child: CustomScrollView( @@ -62,7 +62,7 @@ class _LivePageState extends State slivers: [ SliverPadding( padding: const EdgeInsets.only( - top: StyleString.cardSpace, + top: Style.cardSpace, bottom: 100, ), sliver: SliverMainAxisGroup( @@ -177,10 +177,10 @@ class _LivePageState extends State } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: textScaler.scale(90), ); diff --git a/lib/pages/live/widgets/live_item_app.dart b/lib/pages/live/widgets/live_item_app.dart index a2d8ef4e4a..b83309e93b 100644 --- a/lib/pages/live/widgets/live_item_app.dart +++ b/lib/pages/live/widgets/live_item_app.dart @@ -1,8 +1,7 @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_feed_index/card_data_list_item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -34,7 +33,7 @@ class LiveCardVApp extends StatelessWidget { child: Column( children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/live_area/view.dart b/lib/pages/live_area/view.dart index 51858e1d09..7ea4458a4a 100644 --- a/lib/pages/live_area/view.dart +++ b/lib/pages/live_area/view.dart @@ -95,57 +95,57 @@ class _LiveAreaPageState extends State { children: response .map( (e) => KeepAliveWrapper( - builder: (context) { - if (e.areaList.isNullOrEmpty) { - return const SizedBox.shrink(); - } - return GridView.builder( - padding: EdgeInsets.only( - top: 12, - bottom: bottom + 100, - ), - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 100, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - mainAxisExtent: 80, + child: e.areaList.isNullOrEmpty + ? const SizedBox.shrink() + : GridView.builder( + padding: EdgeInsets.only( + top: 12, + bottom: bottom + 100, ), - itemCount: e.areaList!.length, - itemBuilder: (context, index) { - final item = e.areaList![index]; - return _tagItem( - theme: theme, - item: item, - onPressed: () { - // success - bool? isFav = - _controller.favInfo[item.id]; - if (isFav == true) { - _controller.favInfo[item.id] = false; - _controller.favState - ..value.data.remove(item) - ..refresh(); - (context as Element).markNeedsBuild(); - } else { - // check - if (_controller - .favState - .value - .isSuccess) { - _controller.favInfo[item.id] = true; - _controller.favState - ..value.data.add(item) - ..refresh(); - (context as Element) - .markNeedsBuild(); - } - } + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 100, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + mainAxisExtent: 80, + ), + itemCount: e.areaList!.length, + itemBuilder: (context, index) { + final item = e.areaList![index]; + return _tagItem( + theme: theme, + item: item, + onPressed: () { + // success + bool? isFav = + _controller.favInfo[item.id]; + if (isFav == true) { + _controller.favInfo[item.id] = + false; + _controller.favState + ..value.data.remove(item) + ..refresh(); + (context as Element) + .markNeedsBuild(); + } else { + // check + if (_controller + .favState + .value + .isSuccess) { + _controller.favInfo[item.id] = + true; + _controller.favState + ..value.data.add(item) + ..refresh(); + (context as Element) + .markNeedsBuild(); + } + } + }, + ); }, - ); - }, - ); - }, + ), ), ) .toList(), diff --git a/lib/pages/live_area_detail/child/view.dart b/lib/pages/live_area_detail/child/view.dart index 532ff8ed93..04d84bda67 100644 --- a/lib/pages/live_area_detail/child/view.dart +++ b/lib/pages/live_area_detail/child/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; @@ -53,9 +53,9 @@ class _LiveAreaChildPageState extends State slivers: [ SliverPadding( padding: EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - top: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, + top: Style.safeSpace, bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), sliver: Obx( @@ -68,10 +68,10 @@ class _LiveAreaChildPageState extends State } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), ); diff --git a/lib/pages/live_dm_block/view.dart b/lib/pages/live_dm_block/view.dart index 0271c28233..e79b27cff4 100644 --- a/lib/pages/live_dm_block/view.dart +++ b/lib/pages/live_dm_block/view.dart @@ -47,12 +47,10 @@ class _LiveDmBlockPageState extends State { controller: _controller.tabController, children: [ KeepAliveWrapper( - builder: (context) => - Obx(() => _buildKeyword(_controller.keywordList)), + child: Obx(() => _buildKeyword(_controller.keywordList)), ), KeepAliveWrapper( - builder: (context) => - Obx(() => _buildKeyword(_controller.shieldUserList)), + child: Obx(() => _buildKeyword(_controller.shieldUserList)), ), ], ); @@ -159,7 +157,7 @@ class _LiveDmBlockPageState extends State { Widget _buildKeyword(List list) { if (list.isEmpty) { - return scrollErrorWidget(); + return scrollableError; } return SingleChildScrollView( padding: EdgeInsets.only( @@ -178,7 +176,7 @@ class _LiveDmBlockPageState extends State { text: item is ShieldUserList ? item.uname! : item as String, onTap: (value) => showConfirmDialog( context: context, - title: '确定删除该规则?', + title: const Text('确定删除该规则?'), onConfirm: () => _controller.onRemove(e.$1, item), ), ); @@ -350,7 +348,7 @@ class _LiveDmBlockPageState extends State { String value = ''; showConfirmDialog( context: context, - title: '${isKeyword ? '关键词' : '用户'}屏蔽', + title: Text('${isKeyword ? '关键词' : '用户'}屏蔽'), content: TextFormField( autofocus: true, initialValue: value, diff --git a/lib/pages/live_emote/view.dart b/lib/pages/live_emote/view.dart index e5f2bd83fa..3b92439cdb 100644 --- a/lib/pages/live_emote/view.dart +++ b/lib/pages/live_emote/view.dart @@ -57,7 +57,7 @@ class _LiveEmotePanelState extends State 2, ); return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => response != null && response.isNotEmpty ? Column( diff --git a/lib/pages/live_follow/view.dart b/lib/pages/live_follow/view.dart index 2864b2ad7b..0ea623ebb0 100644 --- a/lib/pages/live_follow/view.dart +++ b/lib/pages/live_follow/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -40,8 +40,8 @@ class _LiveFollowPageState extends State { slivers: [ SliverPadding( padding: EdgeInsets.only( - left: StyleString.safeSpace + padding.left, - right: StyleString.safeSpace + padding.right, + left: Style.safeSpace + padding.left, + right: Style.safeSpace + padding.right, bottom: padding.bottom + 100, ), sliver: Obx(() => _buildBody(_controller.loadingState.value)), @@ -53,10 +53,10 @@ class _LiveFollowPageState extends State { } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), ); diff --git a/lib/pages/live_follow/widgets/live_item_follow.dart b/lib/pages/live_follow/widgets/live_item_follow.dart index 7037e0ab44..6aa36dc886 100644 --- a/lib/pages/live_follow/widgets/live_item_follow.dart +++ b/lib/pages/live_follow/widgets/live_item_follow.dart @@ -1,8 +1,7 @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_follow/item.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -32,7 +31,7 @@ class LiveCardVFollow extends StatelessWidget { child: Column( children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/live_room/contribution_rank/view.dart b/lib/pages/live_room/contribution_rank/view.dart index 6a4f3c25b3..ff6d835a89 100644 --- a/lib/pages/live_room/contribution_rank/view.dart +++ b/lib/pages/live_room/contribution_rank/view.dart @@ -1,16 +1,17 @@ -import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; -import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/live/live_contribution_rank_type.dart'; import 'package:PiliPlus/models_new/live/live_contribution_rank/item.dart'; -import 'package:PiliPlus/models_new/live/live_contribution_rank/medal_info.dart'; import 'package:PiliPlus/pages/live_room/contribution_rank/controller.dart'; +import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart'; import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -191,6 +192,26 @@ class _Item extends StatelessWidget { @override Widget build(BuildContext context) { late final colorScheme = ColorScheme.of(context); + Widget child = Text(item.name!); + if (item.uinfoMedal case final uinfoMedal?) { + try { + child = Column( + spacing: 4, + crossAxisAlignment: .start, + children: [ + child, + MedalWidget.fromMedalInfo( + medal: uinfoMedal, + padding: MedalWidget.mediumPadding, + ), + ], + ); + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + } + } return InkWell( onTap: () => Get.toNamed('/member?mid=${item.uid}'), child: Padding( @@ -214,34 +235,13 @@ class _Item extends StatelessWidget { ), ), ), - PendantAvatar( - avatar: item.face, - size: 42, - ), - Expanded( - child: Column( - spacing: 3, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(item.name!), - Row( - children: [ - if (item.medalInfo case MedalInfo( - :final medalName, - :final level, - )) - Text( - '$medalName$level', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, - ), - ), - ], - ), - ], - ), + NetworkImgLayer( + src: item.face, + width: 42, + height: 42, + type: .avatar, ), + Expanded(child: child), if (showScore) Text( item.score.toString(), diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index ddf6884bf1..d052441183 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -13,6 +13,7 @@ import 'package:PiliPlus/models/model_owner.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/danmaku_msg.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/live_emote.dart'; import 'package:PiliPlus/models_new/live/live_dm_info/data.dart'; +import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart'; import 'package:PiliPlus/models_new/live/live_room_info_h5/data.dart'; import 'package:PiliPlus/models_new/live/live_room_play_info/codec.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; @@ -31,6 +32,7 @@ import 'package:PiliPlus/utils/danmaku_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/extension/iterable_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; +import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; @@ -38,8 +40,8 @@ import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/video_utils.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:easy_debounce/easy_throttle.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; @@ -351,6 +353,10 @@ class LiveRoomController extends GetxController { messages.addAll(response); scrollToBottom(); } + } else { + if (kDebugMode) { + Utils.reportError(res.toString()); + } } } @@ -388,9 +394,9 @@ class LiveRoomController extends GetxController { void listener() { final userScrollDirection = scrollController.position.userScrollDirection; - if (userScrollDirection == ScrollDirection.forward) { + if (userScrollDirection == .forward) { disableAutoScroll.value = true; - } else if (userScrollDirection == ScrollDirection.reverse) { + } else if (userScrollDirection == .reverse) { final pos = scrollController.position; if (pos.maxScrollExtent - pos.pixels <= 100) { disableAutoScroll.value = false; @@ -507,6 +513,9 @@ class LiveRoomController extends GetxController { uemote: uemote, extra: liveExtra, reply: reply, + medalInfo: !GlobalData().showMedal || user['medal'] == null + ? null + : UinfoMedal.fromJson(user['medal']), ), DanmakuContentItem( msg, @@ -530,27 +539,27 @@ class LiveRoomController extends GetxController { } addDm(item); break; - case 'SUPER_CHAT_MESSAGE_DELETE' when showSuperChat: - if (obj['roomid'] == roomId) { - final ids = obj['data']?['ids'] as List?; - if (ids != null && ids.isNotEmpty) { - if (superChatType == SuperChatType.valid) { - superChatMsg.removeWhere((e) => ids.contains(e.id)); - } else { - bool? refresh; - for (final id in ids) { - if (superChatMsg.firstWhereOrNull((e) => e.id == id) - case final item?) { - item.deleted = true; - refresh ??= true; - } - } - if (refresh ?? false) { - superChatMsg.refresh(); - } - } - } - } + // case 'SUPER_CHAT_MESSAGE_DELETE' when showSuperChat: + // if (obj['roomid'] == roomId) { + // final ids = obj['data']?['ids'] as List?; + // if (ids != null && ids.isNotEmpty) { + // if (superChatType == .valid) { + // superChatMsg.removeWhere((e) => ids.contains(e.id)); + // } else { + // bool? refresh; + // for (final id in ids) { + // if (superChatMsg.firstWhereOrNull((e) => e.id == id) + // case final item?) { + // item.deleted = true; + // refresh ??= true; + // } + // } + // if (refresh ?? false) { + // superChatMsg.refresh(); + // } + // } + // } + // } case 'WATCHED_CHANGE': watchedShow.value = obj['data']['text_large']; break; @@ -561,7 +570,11 @@ class LiveRoomController extends GetxController { title.value = obj['data']['title']; break; } - } catch (_) {} + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + } } final RxInt likeClickTime = 0.obs; diff --git a/lib/pages/live_room/superchat/superchat_card.dart b/lib/pages/live_room/superchat/superchat_card.dart index d8534274df..c26681214e 100644 --- a/lib/pages/live_room/superchat/superchat_card.dart +++ b/lib/pages/live_room/superchat/superchat_card.dart @@ -4,9 +4,13 @@ import 'package:PiliPlus/common/widgets/flutter/selectable_text/selection_area.d import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; +import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; +import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart' hide SelectionArea; import 'package:get/get.dart'; @@ -135,6 +139,34 @@ class _SuperChatCardState extends State { final bottomColor = Utils.parseColor(item.backgroundBottomColor); final border = BorderSide(color: bottomColor); void showMenu(TapUpDetails e) => _showMenu(e.globalPosition, item); + + Widget name = Text( + item.userInfo.uname, + maxLines: 1, + overflow: .ellipsis, + style: TextStyle( + color: Utils.parseColor(item.userInfo.nameColor), + ), + ); + if (item.medalInfo case final medal?) { + try { + name = Row( + spacing: 5, + children: [ + MedalWidget.fromMedalInfo( + medal: medal, + padding: MedalWidget.mediumPadding, + ), + Flexible(child: name), + ], + ); + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + } + } + return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ @@ -146,6 +178,13 @@ class _SuperChatCardState extends State { borderRadius: const BorderRadiusGeometry.vertical(top: Radius.circular(8)), color: Utils.parseColor(item.backgroundColor), border: Border(top: border, left: border, right: border), + image: item.backgroundImage == null + ? null + : DecorationImage( + image: CachedNetworkImageProvider( + ImageUtils.safeThumbnailUrl(item.backgroundImage), + ), + ), ), padding: const EdgeInsets.all(8), child: Row( @@ -162,12 +201,7 @@ class _SuperChatCardState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - item.userInfo.uname, - style: TextStyle( - color: Utils.parseColor(item.userInfo.nameColor), - ), - ), + name, Text( "¥${item.price}", style: TextStyle( diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index aa1bfa89c6..f2c1db150d 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -2,14 +2,17 @@ import 'dart:math'; import 'dart:ui'; -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/custom_icon.dart'; import 'package:PiliPlus/common/widgets/flutter/page/page_view.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/controller.dart'; import 'package:PiliPlus/common/widgets/gesture/horizontal_drag_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; +import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/harmony_adapt/harmony_channel.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -35,6 +38,7 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; +import 'package:PiliPlus/utils/mobile_observer.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; @@ -58,7 +62,7 @@ class LiveRoomPage extends StatefulWidget { } class _LiveRoomPageState extends State - with WidgetsBindingObserver, RouteAware { + with WidgetsBindingObserver, RouteAware, RouteAwareMixin { final String heroTag = Utils.generateRandomString(6); late final LiveRoomController _liveRoomController; late final PlPlayerController plPlayerController; @@ -94,10 +98,6 @@ class _LiveRoomPageState extends State @override void didChangeDependencies() { super.didChangeDependencies(); - PageUtils.routeObserver.subscribe( - this, - ModalRoute.of(context)! as PageRoute, - ); padding = MediaQuery.viewPaddingOf(context); final size = MediaQuery.sizeOf(context); maxWidth = size.width; @@ -163,7 +163,7 @@ class _LiveRoomPageState extends State @override void dispose() { - _pipModeWorker?.dispose(); + removeObserverMobile(this); videoPlayerServiceHandler?.onVideoDetailDispose(heroTag); HarmonyChannel.releaseDecorDark(this); WidgetsBinding.instance.removeObserver(this); @@ -174,7 +174,6 @@ class _LiveRoomPageState extends State plPlayerController ..removeStatusLister(playerListener) ..dispose(); - PageUtils.routeObserver.unsubscribe(this); for (final e in LiveContributionRankType.values) { Get.delete( tag: '${_liveRoomController.roomId}${e.name}', @@ -367,7 +366,7 @@ class _LiveRoomPageState extends State ], ); } - return PopScope( + return popScope( canPop: !isFullScreen && !plPlayerController.isDesktopPip, onPopInvokedWithResult: plPlayerController.onPopInvokedWithResult, child: player, @@ -401,7 +400,7 @@ class _LiveRoomPageState extends State ); } else { child = Image.asset( - 'assets/images/live/default_bg.webp', + Assets.livingBackground, fit: BoxFit.cover, width: maxWidth, height: maxHeight, @@ -434,7 +433,7 @@ class _LiveRoomPageState extends State } Widget _buildPH(bool isFullScreen) { - final height = maxWidth / StyleString.aspectRatio16x9; + final height = maxWidth / Style.aspectRatio16x9; final videoHeight = isFullScreen ? maxHeight - padding.top : height; final bottomHeight = maxHeight - padding.top - height - kToolbarHeight; return Column( @@ -746,7 +745,7 @@ class _LiveRoomPageState extends State horizontalDragGestureRecognizer: CustomHorizontalDragGestureRecognizer.new, children: [ - KeepAliveWrapper(builder: (context) => chat()), + KeepAliveWrapper(child: chat()), SuperChatPanel( key: scKey, controller: _liveRoomController, diff --git a/lib/pages/live_room/widgets/chat_panel.dart b/lib/pages/live_room/widgets/chat_panel.dart index a6d42d2830..cd37b90436 100644 --- a/lib/pages/live_room/widgets/chat_panel.dart +++ b/lib/pages/live_room/widgets/chat_panel.dart @@ -2,11 +2,11 @@ import 'package:PiliPlus/common/widgets/flutter/popup_menu.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/live.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_danmaku/danmaku_msg.dart'; import 'package:PiliPlus/models_new/live/live_superchat/item.dart'; import 'package:PiliPlus/pages/live_room/controller.dart'; import 'package:PiliPlus/pages/live_room/superchat/superchat_card.dart'; +import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; import 'package:PiliPlus/pages/video/widgets/header_control.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -57,24 +57,38 @@ class LiveRoomChatPanel extends StatelessWidget { itemBuilder: (_, index) { final item = liveRoomController.messages[index]; if (item is DanmakuMsg) { + WidgetSpan? medal; + if (item.medalInfo case final medalInfo?) { + try { + medal = WidgetSpan( + child: Padding( + padding: const .only(right: 4), + child: MedalWidget.fromMedalInfo( + medal: medalInfo, + padding: MedalWidget.mediumPadding, + ), + ), + ); + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + } + } return Align( alignment: Alignment.centerLeft, child: Builder( builder: (itemContext) { return Container( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 4, - ), + padding: const .symmetric(horizontal: 10, vertical: 4), decoration: BoxDecoration( color: bg, - borderRadius: const BorderRadius.all( - Radius.circular(14), - ), + borderRadius: const .all(.circular(14)), ), child: Text.rich( TextSpan( children: [ + ?medal, TextSpan( text: '${item.name}: ', style: TextStyle( @@ -226,14 +240,20 @@ class LiveRoomChatPanel extends StatelessWidget { InlineSpan _buildMsg(double devicePixelRatio, DanmakuMsg obj) { final uemote = obj.uemote; if (uemote != null) { - // "room_{{room_id}}_{{int}}" or "upower_[{{emote}}]" - final isUpower = uemote.isUpower; + // "room_{{room_id}}_{{int}}" , "upower_[{{emote}}]" , "official_{{int}}" + final double width, height; + if (uemote.isOfficial) { + width = uemote.width / devicePixelRatio; + height = uemote.height / devicePixelRatio; + } else { + width = height = 162.0 / devicePixelRatio; + } return WidgetSpan( child: NetworkImgLayer( src: uemote.url, - type: ImageType.emote, - width: isUpower ? uemote.width : uemote.width / devicePixelRatio, - height: isUpower ? uemote.height : uemote.height / devicePixelRatio, + type: .emote, + width: width, + height: height, ), ); } @@ -250,7 +270,7 @@ class LiveRoomChatPanel extends StatelessWidget { WidgetSpan( child: NetworkImgLayer( src: emote.url, - type: ImageType.emote, + type: .emote, width: emote.width, height: emote.height, ), diff --git a/lib/pages/live_room/widgets/header_control.dart b/lib/pages/live_room/widgets/header_control.dart index f76746722b..9d59060f1e 100644 --- a/lib/pages/live_room/widgets/header_control.dart +++ b/lib/pages/live_room/widgets/header_control.dart @@ -204,26 +204,27 @@ class _LiveHeaderControlState extends State ); }, ), - Obx(() { - final continuePlayInBackground = - plPlayerController.continuePlayInBackground.value; - return ComBtn( - height: 30, - tooltip: '${continuePlayInBackground ? '关闭' : ''}后台播放', - onTap: plPlayerController.setContinuePlayInBackground, - icon: continuePlayInBackground - ? const Icon( - size: 18, - Icons.play_circle, - color: Colors.white, - ) - : const Icon( - size: 18, - Icons.play_circle_outline, - color: Colors.white, - ), - ); - }), + if (PlatformUtils.isMobile) + Obx(() { + final continuePlayInBackground = + plPlayerController.continuePlayInBackground.value; + return ComBtn( + height: 30, + tooltip: '${continuePlayInBackground ? '关闭' : ''}后台播放', + onTap: plPlayerController.setContinuePlayInBackground, + icon: continuePlayInBackground + ? const Icon( + size: 18, + Icons.play_circle, + color: Colors.white, + ) + : const Icon( + size: 18, + Icons.play_circle_outline, + color: Colors.white, + ), + ); + }), ComBtn( height: 30, tooltip: '定时关闭', diff --git a/lib/pages/live_search/child/view.dart b/lib/pages/live_search/child/view.dart index 232a9ed8c8..2c44470221 100644 --- a/lib/pages/live_search/child/view.dart +++ b/lib/pages/live_search/child/view.dart @@ -1,6 +1,6 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -71,10 +71,10 @@ class _LiveSearchChildPageState extends State } late final roomDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(60), ); diff --git a/lib/pages/live_search/widgets/live_search_room.dart b/lib/pages/live_search/widgets/live_search_room.dart index 4de544f151..0312f7f867 100644 --- a/lib/pages/live_search/widgets/live_search_room.dart +++ b/lib/pages/live_search/widgets/live_search_room.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -32,7 +32,7 @@ class LiveCardVSearch extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/live_search/widgets/live_search_user.dart b/lib/pages/live_search/widgets/live_search_user.dart index 358824ae5c..d201349a36 100644 --- a/lib/pages/live_search/widgets/live_search_user.dart +++ b/lib/pages/live_search/widgets/live_search_user.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/live/live_search/user_item.dart'; @@ -53,7 +54,7 @@ class LiveSearchUserItem extends StatelessWidget { Image.asset( height: 14, cacheHeight: 14.cacheSize(context), - 'assets/images/live/live.gif', + Assets.livingRect, ), ], ], diff --git a/lib/pages/login/controller.dart b/lib/pages/login/controller.dart index f252b87ba9..8cf8a3bd5b 100644 --- a/lib/pages/login/controller.dart +++ b/lib/pages/login/controller.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/dial_prefix.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/radio_widget.dart'; import 'package:PiliPlus/http/init.dart'; @@ -40,7 +40,7 @@ class LoginPageController extends GetxController late final RxInt qrCodeLeftTime = 180.obs; late final RxString statusQRCode = ''.obs; - late var selectedCountryCodeId = Constants.internationalDialingPrefix.first; + late var selectedCountryCodeId = Login.dialPrefix.first; late String captchaKey = ''; late final RxInt smsSendCooldown = 0.obs; late int smsSendTimestamp = 0; diff --git a/lib/pages/login/view.dart b/lib/pages/login/view.dart index a83112859b..df62625c5a 100644 --- a/lib/pages/login/view.dart +++ b/lib/pages/login/view.dart @@ -2,6 +2,8 @@ import 'dart:io'; import 'dart:ui'; import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/dial_prefix.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -96,15 +98,12 @@ class _LoginPageState extends State { ), RepaintBoundary( key: globalKey, - child: Obx(() { - return switch (_loginPageCtr.codeInfo.value) { - Loading() => Container( + child: Obx( + () => switch (_loginPageCtr.codeInfo.value) { + Loading() => const SizedBox( height: 200, width: 200, - alignment: Alignment.center, - child: const CircularProgressIndicator( - semanticsLabel: '二维码加载中', - ), + child: m3eLoading, ), Success(:final response) => Container( width: 200, @@ -120,12 +119,13 @@ class _LoginPageState extends State { ), ), ), - Error(:final errMsg) => errorWidget( + Error(:final errMsg) => HttpError( + isSliver: false, errMsg: errMsg, onReload: _loginPageCtr.refreshQRCode, ), - }; - }), + }, + ), ), const SizedBox(height: 10), Obx( @@ -391,19 +391,18 @@ class _LoginPageState extends State { (context as Element).markNeedsBuild(); }, initialValue: _loginPageCtr.selectedCountryCodeId, - itemBuilder: (_) => - Constants.internationalDialingPrefix.map((item) { - return PopupMenuItem( - value: item, - child: Row( - children: [ - Text(item.cname), - const Spacer(), - Text("+${item.countryId}"), - ], - ), - ); - }).toList(), + itemBuilder: (_) => Login.dialPrefix.map((item) { + return PopupMenuItem( + value: item, + child: Row( + children: [ + Text(item.cname), + const Spacer(), + Text("+${item.countryId}"), + ], + ), + ); + }).toList(), child: Row( children: [ Icon( diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 9051e01e04..951f1d0d75 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -1,9 +1,12 @@ import 'dart:io'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/flutter/tabs.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; import 'package:PiliPlus/models/common/nav_bar_config.dart'; import 'package:PiliPlus/pages/home/view.dart'; import 'package:PiliPlus/pages/main/controller.dart'; @@ -13,7 +16,7 @@ import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/size_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; -import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:PiliPlus/utils/mobile_observer.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -34,15 +37,23 @@ class MainApp extends StatefulWidget { } class _MainAppState extends PopScopeState - with RouteAware, WidgetsBindingObserver, WindowListener, TrayListener { + with + RouteAware, + RouteAwareMixin, + WidgetsBindingObserver, + WindowListener, + TrayListener { final _mainController = Get.put(MainController()); late final _setting = GStorage.setting; late EdgeInsets _padding; + @override + bool get initCanPop => false; + @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); + addObserverMobile(this); if (PlatformUtils.isDesktop) { windowManager ..addListener(this) @@ -67,10 +78,6 @@ class _MainAppState extends PopScopeState if (PlatformUtils.isDesktop) { windowManager.setBrightness(brightness); } - PageUtils.routeObserver.subscribe( - this, - ModalRoute.of(context) as PageRoute, - ); if (!_mainController.useSideBar) { _mainController.useBottomNav = MediaQuery.sizeOf(context).isPortrait; } @@ -78,7 +85,7 @@ class _MainAppState extends PopScopeState @override void didPopNext() { - WidgetsBinding.instance.addObserver(this); + addObserverMobile(this); _mainController ..checkUnreadDynamic() ..checkDefaultSearch(true) @@ -88,7 +95,7 @@ class _MainAppState extends PopScopeState @override void didPushNext() { - WidgetsBinding.instance.removeObserver(this); + removeObserverMobile(this); super.didPushNext(); } @@ -108,8 +115,7 @@ class _MainAppState extends PopScopeState trayManager.removeListener(this); windowManager.removeListener(this); } - PageUtils.routeObserver.unsubscribe(this); - WidgetsBinding.instance.removeObserver(this); + removeObserverMobile(this); PiliScheme.listener?.cancel(); GStorage.close(); super.dispose(); @@ -224,9 +230,9 @@ class _MainAppState extends PopScopeState Future _handleTray() async { if (Platform.isWindows) { - await trayManager.setIcon('assets/images/logo/ico/app_icon.ico'); + await trayManager.setIcon(Assets.logoIco); } else { - await trayManager.setIcon('assets/images/logo/desktop/logo_large.png'); + await trayManager.setIcon(Assets.logoLarge); } if (!Platform.isLinux) { await trayManager.setToolTip(Constants.appName); @@ -360,7 +366,7 @@ class _MainAppState extends PopScopeState () => FractionalTranslation( translation: Offset( 0.0, - barOffset.value / StyleString.topBarHeight, + barOffset.value / Style.topBarHeight, ), child: bottomNav, ), diff --git a/lib/pages/main_reply/controller.dart b/lib/pages/main_reply/controller.dart index b650f135fc..b12ac75ce9 100644 --- a/lib/pages/main_reply/controller.dart +++ b/lib/pages/main_reply/controller.dart @@ -3,35 +3,18 @@ import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' import 'package:PiliPlus/grpc/reply.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class MainReplyController extends ReplyController - with GetSingleTickerProviderStateMixin { +class MainReplyController extends ReplyController { late final int oid; late final int replyType; @override int get sourceId => oid; - bool _showFab = true; - - late final AnimationController _fabAnimationCtr; - late final Animation fabAnim; - @override void onInit() { super.onInit(); - _fabAnimationCtr = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 300), - )..forward(); - fabAnim = _fabAnimationCtr.drive( - Tween( - begin: const Offset(0.0, 2.0), - end: Offset.zero, - ).chain(CurveTween(curve: Curves.easeInOut)), - ); final args = Get.arguments; oid = args['oid']; replyType = args['replyType']; @@ -39,20 +22,6 @@ class MainReplyController extends ReplyController queryData(); } - void showFab() { - if (!_showFab) { - _showFab = true; - _fabAnimationCtr.forward(); - } - } - - void hideFab() { - if (_showFab) { - _showFab = false; - _fabAnimationCtr.reverse(); - } - } - @override Future> customGetData() => ReplyGrpc.mainList( type: replyType, @@ -64,10 +33,4 @@ class MainReplyController extends ReplyController @override List? getDataList(MainListReply response) => response.replies; - - @override - void onClose() { - _fabAnimationCtr.dispose(); - super.onClose(); - } } diff --git a/lib/pages/main_reply/view.dart b/lib/pages/main_reply/view.dart index 097d91a888..40b305c104 100644 --- a/lib/pages/main_reply/view.dart +++ b/lib/pages/main_reply/view.dart @@ -1,5 +1,5 @@ -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/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; @@ -7,6 +7,7 @@ import 'package:PiliPlus/common/widgets/view_safe_area.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/fab_mixin.dart'; import 'package:PiliPlus/pages/main_reply/controller.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; @@ -38,7 +39,8 @@ class MainReplyPage extends StatefulWidget { } } -class _MainReplyPageState extends State { +class _MainReplyPageState extends State + with SingleTickerProviderStateMixin, FabMixin { final _controller = Get.put( MainReplyController(), tag: Utils.generateRandomString(8), @@ -62,9 +64,9 @@ class _MainReplyPageState extends State { onNotification: (notification) { final direction = notification.direction; if (direction == .forward) { - _controller.showFab(); + showFab(); } else if (direction == .reverse) { - _controller.hideFab(); + hideFab(); } return false; }, @@ -87,22 +89,26 @@ class _MainReplyPageState extends State { ), ).constraintWidth(), ), + floatingActionButtonLocation: const NoBottomPaddingFabLocation(), floatingActionButton: SlideTransition( - position: _controller.fabAnim, - child: FloatingActionButton( - heroTag: null, - onPressed: () { - try { - feedBack(); - _controller.onReply( - null, - oid: _controller.oid, - replyType: _controller.replyType, - ); - } catch (_) {} - }, - tooltip: '评论', - child: const Icon(Icons.reply), + position: fabAnimation, + child: Padding( + padding: .only(bottom: padding.bottom + kFloatingActionButtonMargin), + child: FloatingActionButton( + heroTag: null, + onPressed: () { + try { + feedBack(); + _controller.onReply( + null, + oid: _controller.oid, + replyType: _controller.replyType, + ); + } catch (_) {} + }, + tooltip: '评论', + child: const Icon(Icons.reply), + ), ), ), ); @@ -188,7 +194,7 @@ class _MainReplyPageState extends State { }, ), TextButton.icon( - style: StyleString.buttonStyle, + style: Style.buttonStyle, onPressed: _controller.queryBySort, icon: Icon(Icons.sort, size: 16, color: secondary), label: Obx( @@ -233,6 +239,7 @@ class _MainReplyPageState extends State { isVideoDetail: false, replyType: _controller.replyType, firstFloor: replyItem, + upMid: _controller.upMid, ), ).constraintWidth(), ), diff --git a/lib/pages/match_info/view.dart b/lib/pages/match_info/view.dart index cea72e3734..c903a44560 100644 --- a/lib/pages/match_info/view.dart +++ b/lib/pages/match_info/view.dart @@ -8,6 +8,8 @@ import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/match/match_info/contest.dart'; import 'package:PiliPlus/models_new/match/match_info/team.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_page.dart'; +import 'package:PiliPlus/pages/common/fab_mixin.dart' + show NoBottomPaddingFabLocation; import 'package:PiliPlus/pages/match_info/controller.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -36,9 +38,6 @@ class _MatchInfoPageState extends CommonDynPageState { @override dynamic get arguments => null; - @override - Offset get fabOffset => const Offset(0, 2); - @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -59,9 +58,10 @@ class _MatchInfoPageState extends CommonDynPageState { ), ), ).constraintWidth(), + floatingActionButtonLocation: const NoBottomPaddingFabLocation(), floatingActionButton: SlideTransition( - position: fabAnim, - child: replyButton, + position: fabAnimation, + child: fabButton, ), ); } diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index f5e7e009cd..8b64e936f5 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -43,6 +43,8 @@ class MemberController extends CommonDataController bool? hasSeasonOrSeries; + late bool hasCharge = false; + final fromViewAid = Get.parameters['from_view_aid']; final key = GlobalKey(); @@ -58,6 +60,7 @@ class MemberController extends CommonDataController final data = response.response; username = data.card?.name ?? ''; isFollowed = data.card?.relation?.isFollowed; + hasCharge = (data.elec?.total ?? 0) > 0; if (data.relation == -1) { relation.value = 128; } else { diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 283d393221..6b9fd6bbdc 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -4,27 +4,35 @@ import 'package:PiliPlus/common/widgets/dialog/report_member.dart'; import 'package:PiliPlus/common/widgets/dynamic_sliver_app_bar/dynamic_sliver_app_bar.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/http/live.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models_new/space/space/data.dart'; +import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/models_new/live/live_medal_wall/data.dart'; import 'package:PiliPlus/pages/coin_log/controller.dart'; import 'package:PiliPlus/pages/exp_log/controller.dart'; import 'package:PiliPlus/pages/log_table/view.dart'; import 'package:PiliPlus/pages/login_devices/view.dart'; import 'package:PiliPlus/pages/login_log/controller.dart'; import 'package:PiliPlus/pages/member/controller.dart'; +import 'package:PiliPlus/pages/member/widget/medal_wall.dart'; import 'package:PiliPlus/pages/member/widget/user_info_card.dart'; import 'package:PiliPlus/pages/member_cheese/view.dart'; +import 'package:PiliPlus/pages/member_contribute/controller.dart'; import 'package:PiliPlus/pages/member_contribute/view.dart'; import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_favorite/view.dart'; import 'package:PiliPlus/pages/member_home/view.dart'; import 'package:PiliPlus/pages/member_pgc/view.dart'; import 'package:PiliPlus/pages/member_shop/view.dart'; +import 'package:PiliPlus/pages/member_video_web/archive/view.dart'; +import 'package:PiliPlus/pages/member_video_web/season_series/view.dart'; +import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -61,7 +69,8 @@ class _MemberPageState extends State with WidgetsBindingObserver { void dispose() { _headerController?.dispose(); _headerController = null; - WidgetsBinding.instance.removeObserver(this); + _cacheFollowTime = null; + _cacheMedalData = null; super.dispose(); } @@ -85,48 +94,75 @@ class _MemberPageState extends State with WidgetsBindingObserver { return Material( color: theme.surface, child: Obx( - () { - if (_userController.loadingState.value.isSuccess) { - return ExtendedNestedScrollView( - key: _userController.key, - onlyOneScrollInBody: true, - pinnedHeaderSliverHeightBuilder: () => - kToolbarHeight + MediaQuery.viewPaddingOf(context).top, - headerSliverBuilder: (context, innerBoxIsScrolled) { + () => switch (_userController.loadingState.value) { + Loading() => m3eLoading, + Success(:final response) => ExtendedNestedScrollView( + key: _userController.key, + onlyOneScrollInBody: true, + pinnedHeaderSliverHeightBuilder: () => + kToolbarHeight + MediaQuery.viewPaddingOf(context).top, + headerSliverBuilder: (context, innerBoxIsScrolled) { + if (response != null) { return [ - _buildUserInfo(theme, _userController.loadingState.value), - ]; - }, - body: _userController.tab2?.isNotEmpty == true - ? Padding( - padding: EdgeInsets.only( - left: padding.left, - right: padding.right, + DynamicSliverAppBar.medium( + actions: _actions(theme), + title: Text(_userController.username ?? ''), + flexibleSpace: Obx( + () => UserInfoCard( + isOwner: + _userController.mid == _userController.account.mid, + relation: _userController.relation.value, + card: response.card!, + images: response.images!, + onFollow: () => _userController.onFollow(context), + live: _userController.live, + silence: _userController.silence, + headerControllerBuilder: getHeaderController, + showLiveMedalWall: _showLiveMedalWall, ), - child: Column( - children: [ - if ((_userController.tab2?.length ?? 0) > 1) - SizedBox( - height: 45, - child: TabBar( - controller: _userController.tabController, - tabs: _userController.tabs, - onTap: _userController.onTapTab, - dividerColor: theme.outline.withValues( - alpha: 0.2, - ), + ), + ), + ]; + } + return [ + SliverAppBar( + pinned: true, + actions: _actions(theme), + title: GestureDetector( + onTap: _userController.onReload, + behavior: HitTestBehavior.opaque, + child: Text(_userController.username ?? ''), + ), + ), + ]; + }, + body: _userController.tab2?.isNotEmpty == true + ? Padding( + padding: .only(left: padding.left, right: padding.right), + child: Column( + children: [ + if ((_userController.tab2?.length ?? 0) > 1) + SizedBox( + height: 45, + child: TabBar( + controller: _userController.tabController, + tabs: _userController.tabs, + onTap: _userController.onTapTab, + dividerColor: theme.outline.withValues( + alpha: 0.2, ), ), - Expanded(child: _buildBody), - ], - ), - ) - : const Center(child: Text('EMPTY')), - ); - } - return Center( - child: _buildUserInfo(theme, _userController.loadingState.value), - ); + ), + Expanded(child: _buildBody), + ], + ), + ) + : scrollableError, + ), + Error(:final errMsg) => scrollErrorWidget( + errMsg: errMsg, + onReload: _userController.onReload, + ), }, ), ); @@ -198,22 +234,35 @@ class _MemberPageState extends State with WidgetsBindingObserver { ], ), ), - PopupMenuItem( - onTap: () => Get.toNamed( - '/upowerRank', - parameters: { - 'mid': _userController.mid.toString(), - }, + if (_userController.hasCharge) + PopupMenuItem( + onTap: () => Get.toNamed( + '/upowerRank', + parameters: { + 'mid': _userController.mid.toString(), + }, + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.electric_bolt, size: 19), + SizedBox(width: 10), + Text('充电排行榜'), + ], + ), ), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.electric_bolt, size: 19), - SizedBox(width: 10), - Text('充电排行榜'), - ], + if (Get.isRegistered(tag: _heroTag)) + PopupMenuItem( + onTap: _toWebArchive, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.extension_outlined, size: 19), + SizedBox(width: 10), + Text('网页投稿'), + ], + ), ), - ), if (_userController.account.isLogin) if (_userController.mid == _userController.account.mid) ...[ if ((_userController @@ -301,6 +350,18 @@ class _MemberPageState extends State with WidgetsBindingObserver { ), ), ] else ...[ + if (_userController.isFollow) + PopupMenuItem( + onTap: _showFollowTime, + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.more_time_outlined, size: 19), + SizedBox(width: 10), + Text('关注时间'), + ], + ), + ), const PopupMenuDivider(), PopupMenuItem( onTap: () => showMemberReportDialog( @@ -364,46 +425,92 @@ class _MemberPageState extends State with WidgetsBindingObserver { }).toList(), ); - Widget _buildUserInfo( - ColorScheme theme, - LoadingState userState, - ) { - switch (userState) { - case Loading(): - return const CircularProgressIndicator(); - case Success(:final response): - if (response != null) { - return DynamicSliverAppBar.medium( - actions: _actions(theme), - title: Text(_userController.username ?? ''), - flexibleSpace: Obx( - () => UserInfoCard( - isOwner: _userController.mid == _userController.account.mid, - relation: _userController.relation.value, - card: response.card!, - images: response.images!, - onFollow: () => _userController.onFollow(context), - live: _userController.live, - silence: _userController.silence, - headerControllerBuilder: getHeaderController, + String? _cacheFollowTime; + Future _showFollowTime() async { + void onShow() { + if (!mounted) return; + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(_userController.username ?? ''), + content: Text(_cacheFollowTime!), + actions: [ + TextButton( + onPressed: Get.back, + child: Text( + '关闭', + style: TextStyle(color: ColorScheme.of(context).outline), ), ), + ], + ), + ); + } + + if (_cacheFollowTime != null) { + onShow(); + return; + } + final res = await UserHttp.userRelation(_mid); + if (res case Success(:final response)) { + if (response.mtime == null) return; + _cacheFollowTime = + '关注时间: ${DateFormatUtils.longFormatDs.format( + DateTime.fromMillisecondsSinceEpoch(response.mtime! * 1000), + )}'; + onShow(); + } else { + res.toast(); + } + } + + MedalWallData? _cacheMedalData; + Future _showLiveMedalWall() async { + void onShow() { + if (!mounted) return; + showDialog( + context: context, + builder: (context) => MedalWall(response: _cacheMedalData!), + ); + } + + if (_cacheMedalData != null) { + onShow(); + return; + } + SmartDialog.showLoading(); + final res = await LiveHttp.liveMedalWall(mid: _mid); + SmartDialog.dismiss(); + if (res case Success(:final response)) { + _cacheMedalData = response; + onShow(); + } else { + res.toast(); + } + } + + void _toWebArchive() { + try { + final ctr = Get.find(tag: _heroTag); + final item = ctr.items?[ctr.tabController?.index ?? 0]; + if (item != null) { + final id = item.seasonId ?? item.seriesId; + if (id != null) { + MemberSSWeb.toMemberSSWeb( + type: item.seasonId != null ? .season : .series, + id: id, + mid: _mid, + name: _userController.username ?? '', ); + return; } - return SliverAppBar( - pinned: true, - actions: _actions(theme), - title: GestureDetector( - onTap: _userController.onReload, - behavior: HitTestBehavior.opaque, - child: Text(_userController.username ?? ''), - ), - ); - case Error(:final errMsg): - return scrollErrorWidget( - errMsg: errMsg, - onReload: _userController.onReload, - ); + } + MemberVideoWeb.toMemberVideoWeb( + mid: _mid, + name: _userController.username ?? '', + ); + } catch (e) { + SmartDialog.showToast(e.toString()); } } } diff --git a/lib/pages/member/widget/header_layout_widget.dart b/lib/pages/member/widget/header_layout_widget.dart index de18a775b5..19a9221826 100644 --- a/lib/pages/member/widget/header_layout_widget.dart +++ b/lib/pages/member/widget/header_layout_widget.dart @@ -23,7 +23,6 @@ import 'package:flutter/rendering.dart' show BoxHitTestResult, BoxParentData; const double kHeaderHeight = 135.0; const double kAvatarSize = 80.0; -const double kPendantAvatarSize = 70.0; const double _kAvatarLeftPadding = 20.0; const double _kAvatarTopPadding = 115.0; const double _kAvatarEffectiveHeight = diff --git a/lib/pages/member/widget/medal_wall.dart b/lib/pages/member/widget/medal_wall.dart new file mode 100644 index 0000000000..3a0fcfafc0 --- /dev/null +++ b/lib/pages/member/widget/medal_wall.dart @@ -0,0 +1,151 @@ +import 'package:PiliPlus/common/assets.dart'; +import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; +import 'package:PiliPlus/models_new/live/live_medal_wall/data.dart'; +import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; +import 'package:PiliPlus/utils/app_scheme.dart'; +import 'package:PiliPlus/utils/extension/num_ext.dart'; +import 'package:PiliPlus/utils/extension/theme_ext.dart'; +import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:flutter/material.dart'; + +class MedalWall extends StatelessWidget { + const MedalWall({super.key, required this.response}); + + final MedalWallData response; + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + return AlertDialog( + clipBehavior: .hardEdge, + title: const Text('粉丝勋章墙'), + contentPadding: const .symmetric(vertical: 16), + constraints: const BoxConstraints.tightFor(width: 380), + content: CustomScrollView( + shrinkWrap: true, + slivers: [ + SliverToBoxAdapter( + child: Center( + child: NetworkImgLayer( + src: response.icon, + width: 50, + height: 50, + type: .avatar, + ), + ), + ), + SliverToBoxAdapter( + child: Padding( + padding: const .only(top: 5), + child: Center(child: Text(response.name!)), + ), + ), + SliverToBoxAdapter( + child: Center( + child: Padding( + padding: const .only(top: 5, bottom: 8), + child: Text.rich( + style: TextStyle(fontSize: 12, color: colorScheme.outline), + TextSpan( + children: [ + const TextSpan(text: '共拥有 '), + TextSpan( + text: response.count.toString(), + style: TextStyle( + fontSize: 13, + color: colorScheme.primary, + ), + ), + const TextSpan(text: ' 枚粉丝勋章'), + ], + ), + ), + ), + ), + ), + SliverList.builder( + itemCount: response.list!.length, + itemBuilder: (context, index) { + final item = response.list![index]; + final uinfoMedal = item.uinfoMedal!; + final isLiving = item.liveStatus == 1; + return ListTile( + onTap: () { + if (isLiving) { + PiliScheme.routePushFromUrl(item.link!); + } else { + PageUtils.toDupNamed('/member?mid=${uinfoMedal.ruid}'); + } + }, + visualDensity: .comfortable, + leading: PendantAvatar( + item.targetIcon, + size: 38, + officialType: switch (item.official) { + 1 => 0, + 2 => 1, + _ => null, + }, + onTap: isLiving + ? () => PageUtils.toDupNamed( + '/member?mid=${uinfoMedal.ruid}', + ) + : null, + ), + title: Row( + children: [ + Flexible( + child: Text( + item.targetName!, + maxLines: 1, + overflow: .ellipsis, + style: const TextStyle(height: 1, fontSize: 14), + ), + ), + if (isLiving) + Padding( + padding: const .only(left: 4), + child: Image.asset( + Assets.livingChart, + height: 16, + cacheHeight: 16.cacheSize(context), + color: colorScheme.primary, + ), + ), + Padding( + padding: const .only(left: 8), + child: MedalWidget.fromMedalInfo(medal: uinfoMedal), + ), + ], + ), + trailing: item.medalInfo!.wearingStatus == 1 + ? Container( + padding: const .symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + borderRadius: const .all(.circular(3)), + color: colorScheme.btnColor, + ), + child: const Text( + '佩戴中', + style: TextStyle( + height: 1, + fontSize: 10, + color: Colors.white, + ), + strutStyle: StrutStyle( + height: 1, + leading: 0, + fontSize: 10, + ), + ), + ) + : null, + ); + }, + ), + ], + ), + ); + } +} diff --git a/lib/pages/member/widget/medal_widget.dart b/lib/pages/member/widget/medal_widget.dart new file mode 100644 index 0000000000..5321792623 --- /dev/null +++ b/lib/pages/member/widget/medal_widget.dart @@ -0,0 +1,77 @@ +import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/models_new/live/live_medal_wall/uinfo_medal.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; + +const _kFontSize = 10.0; +const _kStrutStyle = StrutStyle(height: 1, leading: 0, fontSize: _kFontSize); +const _kPadding = EdgeInsets.symmetric(horizontal: 8, vertical: 3); + +class MedalWidget extends StatelessWidget { + const MedalWidget({ + super.key, + required this.medalName, + required this.level, + required this.backgroundColor, + required this.nameColor, + this.fontSize = _kFontSize, + this.strutStyle = _kStrutStyle, + this.padding = _kPadding, + }); + + MedalWidget.fromMedalInfo({ + super.key, + required UinfoMedal medal, + this.fontSize = _kFontSize, + this.strutStyle = _kStrutStyle, + this.padding = _kPadding, + }) : medalName = medal.name!, + level = medal.level!, + backgroundColor = Utils.parseMedalColor(medal.v2MedalColorStart!), + nameColor = Utils.parseColor(medal.v2MedalColorText!); + + final String medalName; + final int level; + final Color backgroundColor; + final Color nameColor; + final double fontSize; + final StrutStyle strutStyle; + final EdgeInsets padding; + + static const mediumPadding = EdgeInsets.symmetric(horizontal: 6, vertical: 3); + + @override + Widget build(BuildContext context) { + return Container( + padding: padding, + decoration: BoxDecoration( + borderRadius: Style.mdRadius, + color: backgroundColor, + ), + child: Text.rich( + strutStyle: strutStyle, + TextSpan( + children: [ + TextSpan( + text: medalName, + style: TextStyle( + height: 1, + fontSize: fontSize, + color: nameColor, + ), + ), + TextSpan( + text: ' $level', + style: TextStyle( + height: 1, + fontSize: fontSize, + color: nameColor, + fontWeight: .bold, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/member/widget/user_info_card.dart b/lib/pages/member/widget/user_info_card.dart index 2495dfdc9e..634472db20 100644 --- a/lib/pages/member/widget/user_info_card.dart +++ b/lib/pages/member/widget/user_info_card.dart @@ -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/avatars.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; @@ -16,6 +17,7 @@ import 'package:PiliPlus/pages/fan/view.dart'; import 'package:PiliPlus/pages/follow/view.dart'; import 'package:PiliPlus/pages/follow_type/followed/view.dart'; import 'package:PiliPlus/pages/member/widget/header_layout_widget.dart'; +import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; @@ -25,9 +27,12 @@ import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:cached_network_image_ce/cached_network_image.dart'; +import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class UserInfoCard extends StatelessWidget { @@ -41,6 +46,7 @@ class UserInfoCard extends StatelessWidget { this.live, this.silence, required this.headerControllerBuilder, + required this.showLiveMedalWall, }); final bool isOwner; @@ -51,6 +57,7 @@ class UserInfoCard extends StatelessWidget { final Live? live; final int? silence; final ValueGetter headerControllerBuilder; + final VoidCallback showLiveMedalWall; @override Widget build(BuildContext context) { @@ -88,9 +95,15 @@ class UserInfoCard extends StatelessWidget { case UserInfoType.like: count = card.likes?.likeNum; } + void onShowCount() => SmartDialog.showToast( + '${type.title}: $count', + alignment: const Alignment(0.0, -0.8), + ); return GestureDetector( behavior: HitTestBehavior.opaque, onTap: onTap, + onLongPress: PlatformUtils.isMobile ? onShowCount : null, + onSecondaryTap: PlatformUtils.isDesktop ? onShowCount : null, child: Align( alignment: type.alignment, widthFactor: 1.0, @@ -119,206 +132,238 @@ class UserInfoCard extends StatelessWidget { BuildContext context, ColorScheme colorScheme, bool isLight, - ) => [ - Padding( - padding: const EdgeInsets.only(left: 20, right: 20), - child: Wrap( - spacing: 8, - runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - GestureDetector( - onTap: () => Utils.copyText(card.name!), - child: Text( - card.name!, - strutStyle: const StrutStyle( - height: 1, - leading: 0, - fontSize: 17, - fontWeight: FontWeight.bold, - ), - style: TextStyle( - height: 1, - fontSize: 17, - fontWeight: FontWeight.bold, - color: (card.vip?.status ?? -1) > 0 && card.vip?.type == 2 - ? colorScheme.vipColor - : null, - ), - ), - ), - Image.asset( - Utils.levelName( - card.levelInfo!.currentLevel!, - isSeniorMember: card.levelInfo?.identity == 2, - ), - height: 11, - cacheHeight: 11.cacheSize(context), - semanticLabel: '等级${card.levelInfo?.currentLevel}', + ) { + Widget? liveMedal; + if (card.liveFansWearing?.detailV2 case final detailV2?) { + Color? nameColor; + Color? backgroundColor; + try { + nameColor = Utils.parseColor(detailV2.medalColorName!); + backgroundColor = Utils.parseColor(detailV2.medalColor!); + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + } + try { + liveMedal = GestureDetector( + onTap: showLiveMedalWall, + child: MedalWidget( + medalName: detailV2.medalName!, + level: detailV2.level!, + backgroundColor: backgroundColor ?? colorScheme.secondaryContainer, + nameColor: nameColor ?? colorScheme.onSecondaryContainer, ), - if (card.vip?.status == 1) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), - decoration: BoxDecoration( - borderRadius: StyleString.mdRadius, - color: colorScheme.vipColor, - ), + ); + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + } + } + return [ + Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: Wrap( + spacing: 8, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + GestureDetector( + onTap: () => Utils.copyText(card.name!), child: Text( - card.vip?.label?.text ?? '大会员', + card.name!, strutStyle: const StrutStyle( height: 1, - fontSize: 10, + leading: 0, + fontSize: 17, fontWeight: FontWeight.bold, ), - style: const TextStyle( + style: TextStyle( height: 1, + fontSize: 17, fontWeight: FontWeight.bold, - fontSize: 10, - color: Colors.white, + color: (card.vip?.status ?? -1) > 0 && card.vip?.type == 2 + ? colorScheme.vipColor + : null, ), ), ), - // if (card.nameplate?.imageSmall?.isNotEmpty == true) - // CachedNetworkImage( - // imageUrl: ImageUtils.thumbnailUrl(card.nameplate!.imageSmall!), - // height: 20, - // placeholder: (context, url) { - // return const SizedBox.shrink(); - // }, - // ), - ], - ), - ), - if (card.officialVerify?.desc?.isNotEmpty == true) - Container( - margin: const EdgeInsets.only(left: 20, top: 8, right: 20), - padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(12)), - color: colorScheme.onInverseSurface, + Image.asset( + Utils.levelName( + card.levelInfo!.currentLevel!, + isSeniorMember: card.levelInfo?.identity == 2, + ), + height: 11, + cacheHeight: 11.cacheSize(context), + semanticLabel: '等级${card.levelInfo?.currentLevel}', + ), + if (card.vip?.status == 1) + Container( + padding: const .symmetric(horizontal: 8, vertical: 3), + decoration: BoxDecoration( + borderRadius: Style.mdRadius, + color: colorScheme.vipColor, + ), + child: Text( + card.vip?.label?.text ?? '大会员', + strutStyle: const StrutStyle( + height: 1, + leading: 0, + fontSize: 10, + fontWeight: FontWeight.bold, + ), + style: const TextStyle( + height: 1, + fontSize: 10, + color: Colors.white, + fontWeight: FontWeight.bold, + ), + ), + ), + // if (card.nameplate?.imageSmall?.isNotEmpty == true) + // CachedNetworkImage( + // imageUrl: ImageUtils.thumbnailUrl(card.nameplate!.imageSmall!), + // height: 20, + // placeholder: (context, url) { + // return const SizedBox.shrink(); + // }, + // ), + ?liveMedal, + ], ), - child: Text.rich( - TextSpan( - children: [ - if (card.officialVerify?.icon?.isNotEmpty == true) ...[ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: DecoratedBox( - decoration: BoxDecoration( - shape: BoxShape.circle, - color: colorScheme.surface, - ), - child: Icon( - Icons.offline_bolt, - color: card.officialVerify?.type == 0 - ? const Color(0xFFFFCC00) - : Colors.lightBlueAccent, - size: 18, + ), + if (card.officialVerify?.desc?.isNotEmpty == true) + Container( + margin: const EdgeInsets.only(left: 20, top: 8, right: 20), + padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(12)), + color: colorScheme.onInverseSurface, + ), + child: Text.rich( + TextSpan( + children: [ + if (card.officialVerify?.icon?.isNotEmpty == true) ...[ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: DecoratedBox( + decoration: BoxDecoration( + shape: BoxShape.circle, + color: colorScheme.surface, + ), + child: Icon( + Icons.offline_bolt, + color: card.officialVerify?.type == 0 + ? const Color(0xFFFFCC00) + : Colors.lightBlueAccent, + size: 18, + ), ), ), - ), - const TextSpan( - text: ' ', + const TextSpan( + text: ' ', + ), + ], + TextSpan( + text: card.officialVerify!.spliceTitle!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface.withValues(alpha: 0.7), + ), ), ], - TextSpan( - text: card.officialVerify!.spliceTitle!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface.withValues(alpha: 0.7), - ), - ), - ], + ), ), ), - ), - if (card.sign?.isNotEmpty == true) + if (card.sign?.isNotEmpty == true) + Padding( + padding: const EdgeInsets.only(left: 20, top: 6, right: 20), + child: SelectableText( + card.sign!.trim().replaceAll(RegExp(r'\n{2,}'), '\n'), + style: const TextStyle(fontSize: 14), + ), + ), + if (card.followingsFollowedUpper?.items?.isNotEmpty == true) ...[ + const SizedBox(height: 6), + _buildFollowedUp(colorScheme, card.followingsFollowedUpper!), + ], Padding( padding: const EdgeInsets.only(left: 20, top: 6, right: 20), - child: SelectableText( - card.sign!.trim().replaceAll(RegExp(r'\n{2,}'), '\n'), - style: const TextStyle(fontSize: 14), - ), - ), - if (card.followingsFollowedUpper?.items?.isNotEmpty == true) ...[ - const SizedBox(height: 6), - _buildFollowedUp(colorScheme, card.followingsFollowedUpper!), - ], - Padding( - padding: const EdgeInsets.only(left: 20, top: 6, right: 20), - child: Wrap( - spacing: 10, - runSpacing: 8, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - GestureDetector( - onTap: () => Utils.copyText(card.mid.toString()), - child: Text( - 'UID: ${card.mid}', - style: TextStyle( - fontSize: 12, - color: colorScheme.outline, - ), - ), - ), - ...?card.spaceTag?.map( - (item) { - final hasUri = item.uri?.isNotEmpty == true; - final child = Text( - item.title ?? '', + child: Wrap( + spacing: 10, + runSpacing: 8, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + GestureDetector( + onTap: () => Utils.copyText(card.mid.toString()), + child: Text( + 'UID: ${card.mid}', style: TextStyle( fontSize: 12, - color: hasUri ? colorScheme.secondary : colorScheme.outline, + color: colorScheme.outline, ), - ); - if (hasUri) { - return GestureDetector( - onTap: () => PiliScheme.routePushFromUrl(item.uri!), - child: child, + ), + ), + ...?card.spaceTag?.map( + (item) { + final hasUri = item.uri?.isNotEmpty == true; + final child = Text( + item.title ?? '', + style: TextStyle( + fontSize: 12, + color: hasUri ? colorScheme.secondary : colorScheme.outline, + ), ); - } - return child; - }, - ), - ], - ), - ), - if (silence == 1) - Container( - width: double.infinity, - decoration: BoxDecoration( - borderRadius: const BorderRadius.all(Radius.circular(6)), - color: isLight ? colorScheme.errorContainer : colorScheme.error, + if (hasUri) { + return GestureDetector( + onTap: () => PiliScheme.routePushFromUrl(item.uri!), + child: child, + ); + } + return child; + }, + ), + ], ), - margin: const EdgeInsets.only(left: 20, top: 8, right: 20), - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), - child: Text.rich( - TextSpan( - children: [ - WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Icon( - Icons.info, - size: 17, - color: isLight - ? colorScheme.onErrorContainer - : colorScheme.onError, + ), + if (silence == 1) + Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(6)), + color: isLight ? colorScheme.errorContainer : colorScheme.error, + ), + margin: const EdgeInsets.only(left: 20, top: 8, right: 20), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + child: Text.rich( + TextSpan( + children: [ + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Icon( + Icons.info, + size: 17, + color: isLight + ? colorScheme.onErrorContainer + : colorScheme.onError, + ), ), - ), - TextSpan( - text: ' 该账号封禁中', - style: TextStyle( - color: isLight - ? colorScheme.onErrorContainer - : colorScheme.onError, + TextSpan( + text: ' 该账号封禁中', + style: TextStyle( + color: isLight + ? colorScheme.onErrorContainer + : colorScheme.onError, + ), ), - ), - ], + ], + ), ), ), - ), - ]; + ]; + } Column _buildRight(ColorScheme colorScheme) => Column( mainAxisSize: MainAxisSize.min, @@ -428,22 +473,36 @@ class UserInfoCard extends StatelessWidget { ], ); - Widget _buildAvatar(bool hasPendant) => fromHero( - tag: card.face ?? '', - child: PendantAvatar( - avatar: card.face, - size: hasPendant ? kPendantAvatarSize : kAvatarSize, - isMemberAvatar: true, + Widget _buildAvatar(ColorScheme scheme) { + final pendant = card.pendant?.image; + Widget child = PendantAvatar( + card.face, + size: kAvatarSize, + pendentOffset: 12, badgeSize: 20, officialType: card.officialVerify?.type, - isVip: (card.vip?.status ?? -1) > 0, - garbPendantImage: card.pendant?.image, + vipStatus: card.vip?.status, + pendantImage: pendant, roomId: live?.liveStatus == 1 ? live!.roomid : null, onTap: () => PageUtils.imageView( + tag: hashCode.toString(), imgList: [SourceModel(url: card.face.http2https)], ), - ), - ); + ); + if (pendant == null || pendant.isEmpty) { + child = DecoratedBox( + decoration: BoxDecoration( + border: .all(width: 2, color: scheme.surface), + shape: .circle, + ), + child: Padding(padding: const .all(2), child: child), + ); + } + return fromHero( + tag: '${card.face}$hashCode', + child: child, + ); + } Column _buildV( BuildContext context, @@ -451,7 +510,6 @@ class UserInfoCard extends StatelessWidget { bool isLight, double width, ) { - final hasPendant = card.pendant?.image?.isNotEmpty ?? false; final imgUrls = images.collectionTopSimple?.top?.imgUrls; return Column( mainAxisSize: MainAxisSize.min, @@ -471,7 +529,7 @@ class UserInfoCard extends StatelessWidget { : images.nightImgurl) .http2https, ), - avatar: _buildAvatar(hasPendant), + avatar: _buildAvatar(scheme), actions: _buildRight(scheme), ), const SizedBox(height: 5), @@ -540,6 +598,36 @@ class UserInfoCard extends StatelessWidget { }, ), ), + Positioned( + right: 0, + bottom: 3.5, + child: IgnorePointer( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 125), + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: .centerLeft, + end: .centerRight, + colors: [ + Colors.transparent, + Colors.black12, + Colors.black38, + Colors.black45, + ], + ), + ), + child: Padding( + padding: const .only(left: 15, right: 5, bottom: 2), + child: HeaderTitle( + images: imgUrls, + pageController: controller, + ), + ), + ), + ), + ), + ), Positioned( left: 0, right: 0, @@ -652,7 +740,7 @@ class UserInfoCard extends StatelessWidget { return child; } - Column _buildH(BuildContext context, ColorScheme colorScheme, bool isLight) => + Column _buildH(BuildContext context, ColorScheme scheme, bool isLight) => Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -667,7 +755,7 @@ class UserInfoCard extends StatelessWidget { top: 10, bottom: card.prInfo?.content?.isNotEmpty == true ? 0 : 10, ), - child: _buildAvatar(card.pendant?.image?.isNotEmpty ?? false), + child: _buildAvatar(scheme), ), const SizedBox(width: 10), Expanded( @@ -677,20 +765,20 @@ class UserInfoCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 10), - ..._buildLeft(context, colorScheme, isLight), + ..._buildLeft(context, scheme, isLight), const SizedBox(height: 5), ], ), ), Expanded( flex: 3, - child: _buildRight(colorScheme), + child: _buildRight(scheme), ), const SizedBox(width: 20), ], ), if (card.prInfo?.content?.isNotEmpty == true) - buildPrInfo(context, colorScheme, isLight, card.prInfo!), + buildPrInfo(context, scheme, isLight, card.prInfo!), ], ); @@ -787,3 +875,79 @@ class _HeaderIndicatorState extends State { ); } } + +class HeaderTitle extends StatefulWidget { + const HeaderTitle({ + super.key, + required this.images, + required this.pageController, + }); + + final List images; + final PageController pageController; + + @override + State createState() => _HeaderTitleState(); +} + +class _HeaderTitleState extends State { + late int _index; + + @override + void initState() { + super.initState(); + _updateIndex(); + widget.pageController.addListener(_listener); + } + + void _listener() { + _updateIndex(); + setState(() {}); + } + + void _updateIndex() { + _index = widget.pageController.page?.round() ?? 0; + } + + @override + void dispose() { + widget.pageController.removeListener(_listener); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final title = widget.images[_index].title; + if (title == null) return const SizedBox.shrink(); + try { + return Column( + crossAxisAlignment: .end, + children: [ + Text( + title.title!, + maxLines: 1, + overflow: .ellipsis, + style: const TextStyle(fontSize: 12, color: Colors.white), + ), + Text( + title.subTitle!, + style: TextStyle( + fontSize: 12, + fontFamily: Assets.digitalNum, + color: title.subTitleColorFormat?.colors?.isNotEmpty == true + ? Utils.parseMedalColor( + title.subTitleColorFormat!.colors!.last, + ) + : Colors.white, + ), + ), + ], + ); + } catch (e, s) { + if (kDebugMode) { + Utils.reportError(e, s); + } + return const SizedBox.shrink(); + } + } +} diff --git a/lib/pages/member_article/widget/item.dart b/lib/pages/member_article/widget/item.dart index 931048d581..19bda1ef5b 100644 --- a/lib/pages/member_article/widget/item.dart +++ b/lib/pages/member_article/widget/item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -34,7 +34,7 @@ class MemberArticleItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -42,7 +42,7 @@ class MemberArticleItem extends StatelessWidget { children: [ if (item.originImageUrls?.firstOrNull?.isNotEmpty == true) ...[ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints boxConstraints) { diff --git a/lib/pages/member_audio/view.dart b/lib/pages/member_audio/view.dart index 1e0f978f47..78ce6f1924 100644 --- a/lib/pages/member_audio/view.dart +++ b/lib/pages/member_audio/view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; @@ -66,7 +66,7 @@ class _MemberAudioState extends State late final gridDelegate = SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, maxCrossAxisExtent: Grid.smallCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.6, + childAspectRatio: Style.aspectRatio * 2.6, minHeight: MediaQuery.textScalerOf(context).scale(90), ); @@ -93,7 +93,7 @@ class _MemberAudioState extends State Padding( padding: const EdgeInsets.only(left: 6), child: TextButton.icon( - style: StyleString.buttonStyle, + style: Style.buttonStyle, onPressed: _controller.toViewPlayAll, icon: Icon( Icons.play_circle_outline_rounded, diff --git a/lib/pages/member_audio/widgets/item.dart b/lib/pages/member_audio/widgets/item.dart index 4892e4ed09..bc694086d5 100644 --- a/lib/pages/member_audio/widgets/item.dart +++ b/lib/pages/member_audio/widgets/item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -35,7 +35,7 @@ class MemberAudioItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( diff --git a/lib/pages/member_cheese/widgets/item.dart b/lib/pages/member_cheese/widgets/item.dart index d999e48f8a..ff1a720866 100644 --- a/lib/pages/member_cheese/widgets/item.dart +++ b/lib/pages/member_cheese/widgets/item.dart @@ -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/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/flutter/layout_builder.dart'; @@ -81,14 +81,14 @@ class MemberCheeseItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { Widget child = NetworkImgLayer( diff --git a/lib/pages/member_coin_arc/view.dart b/lib/pages/member_coin_arc/view.dart index 1c30638eba..43de1ecabe 100644 --- a/lib/pages/member_coin_arc/view.dart +++ b/lib/pages/member_coin_arc/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -57,8 +57,8 @@ class _MemberCoinArcPageState extends State { SliverPadding( padding: EdgeInsets.only( top: 7, - left: StyleString.safeSpace + padding.left, - right: StyleString.safeSpace + padding.right, + left: Style.safeSpace + padding.left, + right: Style.safeSpace + padding.right, bottom: padding.bottom + 100, ), sliver: Obx(() => _buildBody(_ctr.loadingState.value)), @@ -70,10 +70,10 @@ class _MemberCoinArcPageState extends State { } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(75), ); diff --git a/lib/pages/member_coin_arc/widgets/item.dart b/lib/pages/member_coin_arc/widgets/item.dart index c47b1a0da2..c5c46c00a2 100644 --- a/lib/pages/member_coin_arc/widgets/item.dart +++ b/lib/pages/member_coin_arc/widgets/item.dart @@ -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'; @@ -7,7 +7,6 @@ import 'package:PiliPlus/common/widgets/stat/stat.dart'; import 'package:PiliPlus/common/widgets/video_card/video_card_v.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models/common/stat_type.dart'; import 'package:PiliPlus/models_new/member/coin_like_arc/item.dart'; import 'package:PiliPlus/utils/date_utils.dart'; @@ -60,7 +59,7 @@ class MemberCoinLikeItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/member_comic/widgets/item.dart b/lib/pages/member_comic/widgets/item.dart index 8ac6a2b86a..c1e456440d 100644 --- a/lib/pages/member_comic/widgets/item.dart +++ b/lib/pages/member_comic/widgets/item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -35,7 +35,7 @@ class MemberComicItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( diff --git a/lib/pages/member_contribute/view.dart b/lib/pages/member_contribute/view.dart index 06a6443a6b..818c6e5a66 100644 --- a/lib/pages/member_contribute/view.dart +++ b/lib/pages/member_contribute/view.dart @@ -93,7 +93,7 @@ class _MemberContributeState extends State ) : _controller.items?.isNotEmpty == true ? _getPageFromType(_controller.items!.first) - : scrollErrorWidget(); + : scrollableError; } Widget _getPageFromType(SpaceTab2Item item) { diff --git a/lib/pages/member_favorite/widget/item.dart b/lib/pages/member_favorite/widget/item.dart index b1c9e44460..8da1a74809 100644 --- a/lib/pages/member_favorite/widget/item.dart +++ b/lib/pages/member_favorite/widget/item.dart @@ -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/cached_layout_builder.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; @@ -54,7 +54,7 @@ class MemberFavItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -64,7 +64,7 @@ class MemberFavItem extends StatelessWidget { clipBehavior: Clip.none, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: CachedLayoutBuilder( builder: (context, constraints) => NetworkImgLayer( src: item.cover, diff --git a/lib/pages/member_home/view.dart b/lib/pages/member_home/view.dart index 68cbd920ca..9a4181965b 100644 --- a/lib/pages/member_home/view.dart +++ b/lib/pages/member_home/view.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/button/more_btn.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -45,23 +45,23 @@ class _MemberHomeState extends State } late final gridDelegateV = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(55), ); late final gridDelegateAudio = SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, maxCrossAxisExtent: Grid.smallCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.6, + childAspectRatio: Style.aspectRatio * 2.6, minHeight: MediaQuery.textScalerOf(context).scale(90), ); late final gridDelegatePgc = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth * 0.6, childAspectRatio: 0.75, mainAxisExtent: MediaQuery.textScalerOf(context).scale(52), @@ -73,7 +73,7 @@ class _MemberHomeState extends State final isOwner = setting != null; final color = Theme.of(context).colorScheme.outline; return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(response: final res) => res != null ? CustomScrollView( @@ -88,7 +88,7 @@ class _MemberHomeState extends State ), SliverPadding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, ), sliver: SliverGrid.builder( gridDelegate: gridDelegateV, @@ -131,7 +131,7 @@ class _MemberHomeState extends State ), SliverPadding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, ), sliver: SliverGrid.builder( gridDelegate: gridDelegateV, @@ -157,7 +157,7 @@ class _MemberHomeState extends State ), SliverPadding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, ), sliver: SliverGrid.builder( gridDelegate: gridDelegateV, @@ -234,7 +234,7 @@ class _MemberHomeState extends State ), SliverPadding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, ), sliver: SliverGrid.builder( gridDelegate: gridDelegatePgc, @@ -257,7 +257,7 @@ class _MemberHomeState extends State ), ], ) - : scrollErrorWidget(), + : scrollableError, Error(:final errMsg) => scrollErrorWidget(errMsg: errMsg), }; } diff --git a/lib/pages/member_home/widgets/fav_item.dart b/lib/pages/member_home/widgets/fav_item.dart index 4247ad604f..138ba9540a 100644 --- a/lib/pages/member_home/widgets/fav_item.dart +++ b/lib/pages/member_home/widgets/fav_item.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/models_new/space/space_fav/list.dart'; @@ -34,7 +34,7 @@ class MemberFavItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( diff --git a/lib/pages/member_home/widgets/video_card_v_member_home.dart b/lib/pages/member_home/widgets/video_card_v_member_home.dart index c9c9029958..e7ef305259 100644 --- a/lib/pages/member_home/widgets/video_card_v_member_home.dart +++ b/lib/pages/member_home/widgets/video_card_v_member_home.dart @@ -1,11 +1,10 @@ -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'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; -import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; @@ -81,7 +80,7 @@ class VideoCardVMemberHome extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/member_like_arc/view.dart b/lib/pages/member_like_arc/view.dart index 6bc9f9d360..48ae86c2cd 100644 --- a/lib/pages/member_like_arc/view.dart +++ b/lib/pages/member_like_arc/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -57,8 +57,8 @@ class _MemberLikeArcPageState extends State { SliverPadding( padding: EdgeInsets.only( top: 7, - left: StyleString.safeSpace + padding.left, - right: StyleString.safeSpace + padding.right, + left: Style.safeSpace + padding.left, + right: Style.safeSpace + padding.right, bottom: padding.bottom + 100, ), sliver: Obx(() => _buildBody(_ctr.loadingState.value)), @@ -70,10 +70,10 @@ class _MemberLikeArcPageState extends State { } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(75), ); diff --git a/lib/pages/member_opus/view.dart b/lib/pages/member_opus/view.dart index e04998d429..bbc67d92bb 100644 --- a/lib/pages/member_opus/view.dart +++ b/lib/pages/member_opus/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/space_opus.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -59,8 +59,8 @@ class _MemberOpusState extends State SliverPadding( padding: EdgeInsets.only( top: widget.isSingle ? 12 : 0, - left: StyleString.safeSpace, - right: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, bottom: bottom + 100, ), sliver: Obx(() => _buildBody(_controller.loadingState.value)), @@ -123,8 +123,8 @@ class _MemberOpusState extends State late final gridDelegate = SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: Grid.smallCardWidth, - mainAxisSpacing: StyleString.safeSpace, - crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: Style.safeSpace, + crossAxisSpacing: Style.safeSpace, ); Widget _buildBody(LoadingState?> loadingState) { diff --git a/lib/pages/member_pgc/view.dart b/lib/pages/member_pgc/view.dart index c38be8de9f..1280415b79 100644 --- a/lib/pages/member_pgc/view.dart +++ b/lib/pages/member_pgc/view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -52,9 +52,9 @@ class _MemberBangumiState extends State slivers: [ SliverPadding( padding: EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - top: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, + top: Style.safeSpace, bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), sliver: Obx( @@ -67,8 +67,8 @@ class _MemberBangumiState extends State } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth * 0.6, childAspectRatio: 0.75, mainAxisExtent: MediaQuery.textScalerOf(context).scale(52), diff --git a/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart b/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart index bd41ee22f0..cd6c382491 100644 --- a/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart +++ b/lib/pages/member_pgc/widgets/pgc_card_v_member_pgc.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -23,9 +23,9 @@ class PgcCardVMemberPgc extends StatelessWidget { cover: item.cover, ); return Card( - shape: const RoundedRectangleBorder(borderRadius: StyleString.mdRadius), + shape: const RoundedRectangleBorder(borderRadius: Style.mdRadius), child: InkWell( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, onTap: () => PageUtils.viewPgc(seasonId: item.param), onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, diff --git a/lib/pages/member_profile/view.dart b/lib/pages/member_profile/view.dart index 5a727f8ea3..a6b8c4c7d6 100644 --- a/lib/pages/member_profile/view.dart +++ b/lib/pages/member_profile/view.dart @@ -128,7 +128,7 @@ class _EditProfilePageState extends State { ); return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => ListView( padding: EdgeInsets.only( bottom: MediaQuery.viewPaddingOf(context).bottom + 25, diff --git a/lib/pages/member_search/child/controller.dart b/lib/pages/member_search/child/controller.dart index 9b26c849c0..52dbec9e19 100644 --- a/lib/pages/member_search/child/controller.dart +++ b/lib/pages/member_search/child/controller.dart @@ -62,7 +62,6 @@ class MemberSearchChildController extends CommonListController { mid: controller.mid, pn: page, keyword: controller.editingController.text, - order: 'pubdate', ), MemberSearchType.dynamic => MemberHttp.dynSearch( mid: controller.mid, diff --git a/lib/pages/member_search/child/view.dart b/lib/pages/member_search/child/view.dart index 611ab56ccc..1a4cf5bf63 100644 --- a/lib/pages/member_search/child/view.dart +++ b/lib/pages/member_search/child/view.dart @@ -71,47 +71,43 @@ class _MemberSearchChildPageState extends State Loading() => _buildLoading, Success(:final response) => response != null && response.isNotEmpty - ? Builder( - builder: (context) { - return switch (widget.searchType) { - MemberSearchType.archive => SliverGrid.builder( - gridDelegate: gridDelegate, - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return VideoCardH( - videoItem: response[index], - ); - }, - itemCount: response.length, - ), - MemberSearchType.dynamic => - GlobalData().dynamicsWaterfallFlow - ? SliverWaterfallFlow( - gridDelegate: dynGridDelegate, - delegate: SliverChildBuilderDelegate( - (_, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanel(item: response[index]); - }, - childCount: response.length, - ), - ) - : SliverList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - return DynamicPanel(item: response[index]); - }, - itemCount: response.length, - ), - }; - }, - ) + ? switch (widget.searchType) { + MemberSearchType.archive => SliverGrid.builder( + gridDelegate: gridDelegate, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return VideoCardH( + videoItem: response[index], + ); + }, + itemCount: response.length, + ), + MemberSearchType.dynamic => + GlobalData().dynamicsWaterfallFlow + ? SliverWaterfallFlow( + gridDelegate: dynGridDelegate, + delegate: SliverChildBuilderDelegate( + (_, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return DynamicPanel(item: response[index]); + }, + childCount: response.length, + ), + ) + : SliverList.builder( + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return DynamicPanel(item: response[index]); + }, + itemCount: response.length, + ), + } : HttpError(onReload: _controller.onReload), Error(:final errMsg) => HttpError( errMsg: errMsg, diff --git a/lib/pages/member_season_series/widget/season_series_card.dart b/lib/pages/member_season_series/widget/season_series_card.dart index b6d9c854a9..a6400026ef 100644 --- a/lib/pages/member_season_series/widget/season_series_card.dart +++ b/lib/pages/member_season_series/widget/season_series_card.dart @@ -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'; @@ -31,14 +31,14 @@ class SeasonSeriesCard extends StatelessWidget { onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (BuildContext context, BoxConstraints boxConstraints) { final double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/member_shop/view.dart b/lib/pages/member_shop/view.dart index 08c79f5b83..8d8120f93f 100644 --- a/lib/pages/member_shop/view.dart +++ b/lib/pages/member_shop/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/space_opus.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -51,8 +51,8 @@ class _MemberShopState extends State SliverPadding( padding: EdgeInsets.only( top: 12, - left: StyleString.safeSpace, - right: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, bottom: MediaQuery.viewPaddingOf(context).bottom + 100, ), sliver: Obx(() => _buildBody(_controller.loadingState.value)), @@ -67,8 +67,8 @@ class _MemberShopState extends State late final gridDelegate = SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: Grid.smallCardWidth, - mainAxisSpacing: StyleString.safeSpace, - crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: Style.safeSpace, + crossAxisSpacing: Style.safeSpace, ); Widget _buildBody(LoadingState?> loadingState) { diff --git a/lib/pages/member_upower_rank/view.dart b/lib/pages/member_upower_rank/view.dart index e862fd61a5..191b5feece 100644 --- a/lib/pages/member_upower_rank/view.dart +++ b/lib/pages/member_upower_rank/view.dart @@ -136,9 +136,7 @@ class _UpowerRankPageState extends State Expanded( child: tabBarView( children: [ - KeepAliveWrapper( - builder: (context) => child, - ), + KeepAliveWrapper(child: child), ...tabs .skip(1) .map( diff --git a/lib/pages/member_video/controller.dart b/lib/pages/member_video/controller.dart index 32ae7968b3..f4c6201186 100644 --- a/lib/pages/member_video/controller.dart +++ b/lib/pages/member_video/controller.dart @@ -2,6 +2,8 @@ import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/http/search.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_app.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/video/source_type.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; @@ -23,17 +25,18 @@ class MemberVideoCtr required this.seriesId, this.username, this.title, - }); + }) : isVideo = type == .video; - ContributeType type; + final ContributeType type; + final bool isVideo; int? seasonId; int? seriesId; final int mid; - late RxString order = 'pubdate'.obs; - late RxString sort = 'desc'.obs; - RxInt count = (-1).obs; + late ArchiveOrderTypeApp order = .pubdate; + late ArchiveSortTypeApp sort = .desc; + int? count; int? next; - Rx episodicButton = EpisodicButton().obs; + EpisodicButton? episodicButton; final String? username; final String? title; @@ -65,7 +68,7 @@ class MemberVideoCtr @override void onInit() { super.onInit(); - if (type == ContributeType.video) { + if (isVideo) { fromViewAid = Get.parameters['from_view_aid']; } page = 0; @@ -78,24 +81,18 @@ class MemberVideoCtr Success response, ) { final data = response.response; - episodicButton - ..value = data.episodicButton ?? EpisodicButton() - ..refresh(); + episodicButton = data.episodicButton; next = data.next; if (page == 0 || isLoadPrevious) { hasPrev = data.hasPrev; } if (page == 0 || !isLoadPrevious) { - if ((type == ContributeType.video - ? data.hasNext == false - : data.next == 0) || + if ((isVideo ? data.hasNext == false : data.next == 0) || data.item.isNullOrEmpty) { isEnd = true; } } - count.value = type == ContributeType.season - ? (data.item?.length ?? -1) - : (data.count ?? -1); + count = type == .season ? data.item?.length : data.count; if (page != 0) { if (loadingState.value case Success(:final response)) { data.item ??= []; @@ -118,18 +115,18 @@ class MemberVideoCtr MemberHttp.spaceArchive( type: type, mid: mid, - aid: type == ContributeType.video + aid: isVideo ? isLoadPrevious ? firstAid : lastAid : null, - order: type == ContributeType.video ? order.value : null, - sort: type == ContributeType.video + order: isVideo ? order : null, + sort: isVideo ? isLoadPrevious - ? 'asc' + ? .asc : null - : sort.value, - pn: type == ContributeType.charging ? page : null, + : sort, + pn: type == .charging ? page : null, next: next, seasonId: seasonId, seriesId: seriesId, @@ -138,17 +135,17 @@ class MemberVideoCtr void queryBySort() { if (isLoading) return; - if (type == ContributeType.video) { + if (isVideo) { isLocating.value = false; - order.value = order.value == 'pubdate' ? 'click' : 'pubdate'; + order = order == .pubdate ? .click : .pubdate; } else { - sort.value = sort.value == 'desc' ? 'asc' : 'desc'; + sort = sort == .desc ? .asc : .desc; } onReload(); } Future toViewPlayAll() async { - final episodicButton = this.episodicButton.value; + final episodicButton = this.episodicButton!; if (episodicButton.text == '继续播放' && episodicButton.uri?.isNotEmpty == true) { final params = Uri.parse(episodicButton.uri!).queryParameters; @@ -167,7 +164,7 @@ class MemberVideoCtr 'oid': oid, 'favTitle': '$username: ${title ?? episodicButton.text ?? '播放全部'}', - if (seriesId == null) 'count': count.value, + if (seriesId == null) 'count': ?count, if (seasonId != null || seriesId != null) 'mediaType': params['page_type'], 'desc': params['desc'] == '1', @@ -190,9 +187,7 @@ class MemberVideoCtr bool desc = seasonId != null ? false : true; desc = (seasonId != null || seriesId != null) && - (type == ContributeType.video - ? order.value == 'click' - : sort.value == 'asc') + (isVideo ? order == .click : sort == .asc) ? !desc : desc; PageUtils.toVideoPage( @@ -206,14 +201,13 @@ class MemberVideoCtr 'oid': IdUtils.bv2av(element.bvid!), 'favTitle': '$username: ${title ?? episodicButton.text ?? '播放全部'}', - if (seriesId == null) 'count': count.value, + if (seriesId == null) 'count': ?count, if (seasonId != null || seriesId != null) 'mediaType': Uri.parse( episodicButton.uri!, ).queryParameters['page_type'], 'desc': desc, - if (type == ContributeType.video) - 'sortField': order.value == 'click' ? 2 : 1, + if (isVideo) 'sortField': order == .click ? 2 : 1, }, ); break; diff --git a/lib/pages/member_video/view.dart b/lib/pages/member_video/view.dart index 2a9096755d..1ec37c9d5e 100644 --- a/lib/pages/member_video/view.dart +++ b/lib/pages/member_video/view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; @@ -111,8 +111,7 @@ class _MemberVideoState extends State ], ), ); - if (widget.type == ContributeType.video && - _controller.fromViewAid?.isNotEmpty == true) { + if (_controller.isVideo && _controller.fromViewAid?.isNotEmpty == true) { if (_index == null) { _scrollController = PrimaryScrollController.of(this.context) @@ -174,85 +173,11 @@ class _MemberVideoState extends State response != null && response.isNotEmpty ? SliverMainAxisGroup( slivers: [ - SliverFloatingHeaderWidget( - backgroundColor: theme.colorScheme.surface, - child: Padding( - padding: const EdgeInsets.fromLTRB(14, 2.5, 8, 2.5), - child: Row( - children: [ - Obx( - () { - final count = _controller.count.value; - return Text( - count != -1 ? '共$count视频' : '', - style: const TextStyle(fontSize: 13), - ); - }, - ), - Obx( - () { - final episodicButton = - _controller.episodicButton.value; - return episodicButton.uri?.isNotEmpty == true - ? Padding( - padding: EdgeInsets.only( - left: _controller.count.value != -1 - ? 6 - : 0, - ), - child: TextButton.icon( - style: StyleString.buttonStyle, - onPressed: _controller.toViewPlayAll, - icon: Icon( - Icons.play_circle_outline_rounded, - size: 16, - color: theme.colorScheme.secondary, - ), - label: Text( - episodicButton.text ?? '播放全部', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.secondary, - ), - ), - ), - ) - : const SizedBox.shrink(); - }, - ), - const Spacer(), - TextButton.icon( - style: StyleString.buttonStyle, - onPressed: _controller.queryBySort, - icon: Icon( - Icons.sort, - size: 16, - color: theme.colorScheme.secondary, - ), - label: Obx( - () => Text( - widget.type == ContributeType.video - ? _controller.order.value == 'pubdate' - ? '最新发布' - : '最多播放' - : _controller.sort.value == 'desc' - ? '默认' - : '倒序', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.secondary, - ), - ), - ), - ), - ], - ), - ), - ), + _buildHeader(theme), SliverGrid.builder( gridDelegate: gridDelegate, itemBuilder: (context, index) { - if (widget.type != ContributeType.season && + if (widget.type != .season && index == response.length - 1) { _controller.onLoadMore(); } @@ -272,4 +197,79 @@ class _MemberVideoState extends State ), }; } + + Widget _buildHeader(ThemeData theme) { + return SliverFloatingHeaderWidget( + backgroundColor: theme.colorScheme.surface, + child: Padding( + padding: const EdgeInsets.fromLTRB(14, 2.5, 8, 2.5), + child: Row( + children: [ + ?_buildCount(), + ?_buildEpisodeBtn(theme), + const Spacer(), + _buildSortBtn(theme), + ], + ), + ), + ); + } + + Widget? _buildCount() { + final count = _controller.count; + if (count != null) { + return Text( + '共$count视频', + style: const TextStyle(fontSize: 13), + ); + } + return null; + } + + Widget? _buildEpisodeBtn(ThemeData theme) { + final episodicButton = _controller.episodicButton; + if (episodicButton?.uri?.isNotEmpty ?? false) { + return Padding( + padding: EdgeInsets.only( + left: _controller.count != null ? 6 : 0, + ), + child: TextButton.icon( + style: Style.buttonStyle, + onPressed: _controller.toViewPlayAll, + icon: Icon( + Icons.play_circle_outline_rounded, + size: 16, + color: theme.colorScheme.secondary, + ), + label: Text( + episodicButton?.text ?? '播放全部', + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.secondary, + ), + ), + ), + ); + } + return null; + } + + Widget _buildSortBtn(ThemeData theme) { + return TextButton.icon( + style: Style.buttonStyle, + onPressed: _controller.queryBySort, + icon: Icon( + Icons.sort, + size: 16, + color: theme.colorScheme.secondary, + ), + label: Text( + _controller.isVideo ? _controller.order.label : _controller.sort.label, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.secondary, + ), + ), + ); + } } diff --git a/lib/pages/member_video/widgets/video_card_h_member_video.dart b/lib/pages/member_video/widgets/video_card_h_member_video.dart index 2744d9d35e..e187acc6bc 100644 --- a/lib/pages/member_video/widgets/video_card_h_member_video.dart +++ b/lib/pages/member_video/widgets/video_card_h_member_video.dart @@ -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'; @@ -75,14 +75,14 @@ class VideoCardHMemberVideo extends StatelessWidget { }, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { final double maxWidth = boxConstraints.maxWidth; @@ -99,7 +99,7 @@ class VideoCardHMemberVideo extends StatelessWidget { const Positioned.fill( child: DecoratedBox( decoration: BoxDecoration( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, color: Colors.black54, ), child: Center( diff --git a/lib/pages/member_video_web/archive/controller.dart b/lib/pages/member_video_web/archive/controller.dart new file mode 100644 index 0000000000..104d8274d3 --- /dev/null +++ b/lib/pages/member_video_web/archive/controller.dart @@ -0,0 +1,63 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_web.dart'; +import 'package:PiliPlus/models_new/member/search_archive/data.dart'; +import 'package:PiliPlus/models_new/member/search_archive/slist.dart'; +import 'package:PiliPlus/models_new/member/search_archive/vlist.dart'; +import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; +import 'package:get/get.dart'; + +class MemberVideoWebCtr + extends + BaseVideoWebCtr< + SearchArchiveData, + VListItemModel, + ArchiveOrderTypeWeb + > { + int? _totalCount; + @override + final Rx order = Rx(.pubdate); + + int tid = 0; + String? specialType; + List? tags; + + @override + List? getDataList(SearchArchiveData response) { + return response.list?.vlist; + } + + @override + bool customHandleResponse( + bool isRefresh, + Success response, + ) { + if (isRefresh) { + final data = response.response; + if (data.page?.count case final count?) { + if (tid == 0 && specialType == null) { + _totalCount = count; + } + this.count = count; + totalPage = (count / ps).ceil(); + } + final tags = data.list?.tags; + if (tags?.isNotEmpty ?? false) { + this.tags = tags! + ..insert(0, ListTag(tid: 0, name: '全部类型', count: _totalCount)); + } + } + return false; + } + + @override + Future> customGetData() => + MemberHttp.searchArchive( + mid: mid, + ps: ps, + pn: page, + order: order.value, + tid: tid, + specialType: specialType, + ); +} diff --git a/lib/pages/member_video_web/archive/view.dart b/lib/pages/member_video_web/archive/view.dart new file mode 100644 index 0000000000..7554a877d3 --- /dev/null +++ b/lib/pages/member_video_web/archive/view.dart @@ -0,0 +1,85 @@ +import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; +import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_web.dart'; +import 'package:PiliPlus/models_new/member/search_archive/data.dart'; +import 'package:PiliPlus/models_new/member/search_archive/vlist.dart'; +import 'package:PiliPlus/pages/member_video_web/archive/controller.dart'; +import 'package:PiliPlus/pages/member_video_web/base/view.dart'; +import 'package:PiliPlus/pages/search/widgets/search_text.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MemberVideoWeb extends StatefulWidget { + const MemberVideoWeb({super.key}); + + @override + State createState() => _MemberVideoWebState(); + + static Future? toMemberVideoWeb({ + required Object mid, + required String name, + }) { + return Get.toNamed( + '/videoWeb', + arguments: { + 'mid': mid, + 'name': name, + }, + ); + } +} + +class _MemberVideoWebState + extends + BaseVideoWebState< + MemberVideoWeb, + SearchArchiveData, + VListItemModel, + ArchiveOrderTypeWeb + > { + @override + late final MemberVideoWebCtr controller; + + @override + void initState() { + super.initState(); + controller = Get.put(MemberVideoWebCtr(), tag: name); + } + + @override + List get values => ArchiveOrderTypeWeb.values; + + @override + Widget? buildTags(ColorScheme colorScheme) { + if (controller.tags case final tags?) { + return SliverPinnedHeader( + backgroundColor: colorScheme.surface, + child: SelfSizedHorizontalList( + itemCount: tags.length, + padding: const .fromLTRB(10, 0, 10, 8), + itemBuilder: (context, index) { + final item = tags[index]; + final isCurr = controller.specialType != null + ? item.specialType == controller.specialType + : item.tid == controller.tid; + return SearchText( + padding: const .symmetric(horizontal: 8, vertical: 4), + text: '${item.name!} ${item.count}', + bgColor: isCurr ? colorScheme.secondaryContainer : null, + textColor: isCurr ? colorScheme.onSecondaryContainer : null, + onTap: (_) { + if (isCurr) return; + controller + ..tid = item.tid ?? 0 + ..specialType = item.specialType + ..onReload(); + }, + ); + }, + separatorBuilder: (_, _) => const SizedBox(width: 10), + ), + ); + } + return null; + } +} diff --git a/lib/pages/member_video_web/base/controller.dart b/lib/pages/member_video_web/base/controller.dart new file mode 100644 index 0000000000..344b6e68f0 --- /dev/null +++ b/lib/pages/member_video_web/base/controller.dart @@ -0,0 +1,50 @@ +import 'package:PiliPlus/common/widgets/scroll_physics.dart' show ReloadMixin; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:get/get.dart'; + +const int ps = 30; + +abstract class BaseVideoWebCtr extends CommonListController + with ReloadMixin { + final Object mid = Get.arguments['mid']; + + int? totalPage; + int? count; + Rx get order; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + void checkIsEnd(int length) { + if (totalPage != null && page >= totalPage!) { + isEnd = true; + } else if (count != null && length >= count!) { + isEnd = true; + } + } + + void queryBySort(V value) { + if (isLoading) return; + order.value = value; + onReload(); + } + + void jumpToPage(int page) { + isEnd = false; + reload = true; + this.page = page; + loadingState.value = LoadingState.loading(); + queryData(); + } + + @override + Future onReload() { + reload = true; + return super.onReload(); + } +} diff --git a/lib/pages/member_video_web/base/view.dart b/lib/pages/member_video_web/base/view.dart new file mode 100644 index 0000000000..978ed474bd --- /dev/null +++ b/lib/pages/member_video_web/base/view.dart @@ -0,0 +1,219 @@ +import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; +import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/enum_with_label.dart'; +import 'package:PiliPlus/models/model_video.dart'; +import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; +import 'package:PiliPlus/pages/search/widgets/search_text.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; + +abstract class BaseVideoWebState< + S extends StatefulWidget, + R, + T extends BaseVideoItemModel, + V extends EnumWithLabel +> + extends State + with GridMixin { + late final String name; + BaseVideoWebCtr get controller; + + @override + void initState() { + super.initState(); + final args = Get.arguments; + name = args['name']; + } + + List get values; + + @override + Widget build(BuildContext context) { + final colorScheme = ColorScheme.of(context); + return Scaffold( + appBar: AppBar( + title: Text(name), + actions: [ + Obx( + () { + final order = controller.order.value; + return PopupMenuButton( + tooltip: '排序', + icon: const Icon(Icons.sort), + initialValue: order, + onSelected: controller.queryBySort, + itemBuilder: (_) => values + .map((e) => PopupMenuItem(value: e, child: Text(e.label))) + .toList(), + ); + }, + ), + const SizedBox(width: 6), + ], + ), + body: refreshIndicator( + onRefresh: controller.onRefresh, + child: CustomScrollView( + physics: ReloadScrollPhysics(controller: controller), + slivers: [ + SliverPadding( + padding: .only( + bottom: MediaQuery.viewPaddingOf(context).bottom + 100, + ), + sliver: Obx( + () => buildBody(colorScheme, controller.loadingState.value), + ), + ), + ], + ), + ), + ); + } + + Widget buildBody( + ColorScheme colorScheme, + LoadingState?> loadingState, + ) { + return switch (loadingState) { + Loading() => gridSkeleton, + Success(:final response) => + response != null && response.isNotEmpty + ? SliverMainAxisGroup( + slivers: [ + buildHeader(colorScheme), + ?buildTags(colorScheme), + SliverGrid.builder( + gridDelegate: gridDelegate, + itemCount: response.length, + itemBuilder: (context, index) { + if (index == response.length - 1) { + controller.onLoadMore(); + } + return VideoCardH(videoItem: response[index]); + }, + ), + ], + ) + : HttpError(onReload: controller.onReload), + Error(:final errMsg) => HttpError( + errMsg: errMsg, + onReload: controller.onReload, + ), + }; + } + + Widget? buildTags(ColorScheme colorScheme) => null; + + Widget buildHeader(ColorScheme colorScheme) { + return SliverPinnedHeader( + backgroundColor: colorScheme.surface, + child: Padding( + padding: const .fromLTRB(14, 0, 8, 4), + child: Stack( + alignment: .centerLeft, + children: [ + ?buildCount(), + Center(child: buildPageBtn(colorScheme)), + ], + ), + ), + ); + } + + Widget? buildCount() { + final count = controller.count; + if (count == null) return null; + return Text( + '共 $count 视频', + style: const TextStyle(height: 1), + strutStyle: const StrutStyle(leading: 0, height: 1), + ); + } + + Widget? buildPageBtn(ColorScheme colorScheme) { + final totalPage = controller.totalPage; + if (totalPage == null) return null; + final page = controller.page - 1; + final canBackward = page > 1; + final canForward = page < totalPage; + const size = 30.0; + const iconSize = 24.0; + + final backwardBtn = iconButton( + size: size, + iconSize: iconSize, + tooltip: canBackward ? '上一页' : null, + icon: const Icon(Icons.keyboard_arrow_left), + onPressed: canBackward ? () => controller.jumpToPage(page - 1) : null, + ); + + final forwardBtn = iconButton( + size: size, + iconSize: iconSize, + tooltip: canForward ? '下一页' : null, + icon: const Icon(Icons.keyboard_arrow_right), + onPressed: canForward ? () => controller.jumpToPage(page + 1) : null, + ); + + final pageIndicator = SearchText( + height: 1, + text: '$page / $totalPage', + borderRadius: const .all(.circular(4)), + padding: const .symmetric(horizontal: 10, vertical: 5), + onTap: (_) => showJumpDialog(page), + ); + + return Row( + spacing: 6, + mainAxisSize: .min, + children: [ + backwardBtn, + pageIndicator, + forwardBtn, + ], + ); + } + + void showJumpDialog(int page) { + var pageStr = page.toString(); + + void onSubmit([_]) { + try { + controller.jumpToPage( + int.parse(pageStr).clamp(1, controller.totalPage!), + ); + } catch (e) { + SmartDialog.showToast(e.toString()); + } + } + + showConfirmDialog( + context: context, + title: const Text('跳至: '), + content: TextFormField( + autofocus: true, + initialValue: pageStr, + onChanged: (value) => pageStr = value, + decoration: const InputDecoration( + labelText: '页数', + border: OutlineInputBorder(), + ), + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onFieldSubmitted: (_) { + Get.back(); + onSubmit(); + }, + ), + onConfirm: onSubmit, + ); + } +} diff --git a/lib/pages/member_video_web/season_series/controller.dart b/lib/pages/member_video_web/season_series/controller.dart new file mode 100644 index 0000000000..b3a14a3e41 --- /dev/null +++ b/lib/pages/member_video_web/season_series/controller.dart @@ -0,0 +1,55 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart'; +import 'package:PiliPlus/models/common/member/web_ss_type.dart'; +import 'package:PiliPlus/models_new/member/season_web/archive.dart'; +import 'package:PiliPlus/models_new/member/season_web/data.dart'; +import 'package:PiliPlus/pages/member_video_web/base/controller.dart'; +import 'package:get/get.dart'; + +class MemberSSWebCtr + extends BaseVideoWebCtr { + @override + final Rx order = Rx(.desc); + late final WebSsType _type; + late final Object _id; + + @override + void onInit() { + final args = Get.arguments; + _type = args['type']; + _id = args['id']; + super.onInit(); + } + + @override + List? getDataList(SeasonWebData response) { + return response.archives; + } + + @override + bool customHandleResponse( + bool isRefresh, + Success response, + ) { + if (isRefresh) { + final data = response.response; + if (data.page?.total case final total?) { + count = total; + totalPage = (total / ps).ceil(); + } + } + return false; + } + + @override + Future> customGetData() => + MemberHttp.seasonSeriesWeb( + type: _type, + mid: mid, + id: _id, + ps: ps, + pn: page, + sort: order.value, + ); +} diff --git a/lib/pages/member_video_web/season_series/view.dart b/lib/pages/member_video_web/season_series/view.dart new file mode 100644 index 0000000000..fcb99c9361 --- /dev/null +++ b/lib/pages/member_video_web/season_series/view.dart @@ -0,0 +1,53 @@ +import 'package:PiliPlus/models/common/member/archive_sort_type_app.dart'; +import 'package:PiliPlus/models/common/member/web_ss_type.dart'; +import 'package:PiliPlus/models_new/member/season_web/archive.dart'; +import 'package:PiliPlus/models_new/member/season_web/data.dart'; +import 'package:PiliPlus/pages/member_video_web/base/view.dart'; +import 'package:PiliPlus/pages/member_video_web/season_series/controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MemberSSWeb extends StatefulWidget { + const MemberSSWeb({super.key}); + + @override + State createState() => _MemberSSWebState(); + + static Future? toMemberSSWeb({ + required WebSsType type, + required Object id, + required Object mid, + required String name, + }) { + return Get.toNamed( + '/ssWeb', + arguments: { + 'type': type, + 'id': id, + 'mid': mid, + 'name': name, + }, + ); + } +} + +class _MemberSSWebState + extends + BaseVideoWebState< + MemberSSWeb, + SeasonWebData, + SeasonArchive, + ArchiveSortTypeApp + > { + @override + late final MemberSSWebCtr controller; + + @override + void initState() { + super.initState(); + controller = Get.put(MemberSSWebCtr(), tag: name); + } + + @override + List get values => ArchiveSortTypeApp.values; +} diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index dd47f33622..3aff285635 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/assets.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -106,7 +107,7 @@ class _MediaPageState extends CommonPageState (e) => Flexible( child: InkWell( onTap: e.onTap, - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 80), child: AspectRatio( @@ -273,7 +274,7 @@ class _MediaPageState extends CommonPageState right: -1, bottom: -2, child: Image.asset( - 'assets/images/big-vip.png', + Assets.vipIcon, height: 19, cacheHeight: 19.cacheSize(context), semanticLabel: "大会员", @@ -286,7 +287,7 @@ class _MediaPageState extends CommonPageState width: 55, height: 55, cacheHeight: 55.cacheSize(context), - 'assets/images/noface.jpeg', + Assets.avatarPlaceHolder, semanticLabel: "默认头像", ), ), @@ -415,7 +416,7 @@ class _MediaPageState extends CommonPageState return Flexible( child: InkWell( onTap: onTap, - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 80), child: AspectRatio( diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index dc3f1028fc..554888ee6b 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -95,7 +95,7 @@ class _AtMePageState extends State { final item = response[index]; void onLongPress() => showConfirmDialog( context: context, - title: '确定删除该通知?', + title: const Text('确定删除该通知?'), onConfirm: () => _atMeController.onRemove(item.id!, index), ); return ListTile( diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index 53c710fe3e..c5164002c9 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -211,8 +211,8 @@ class _LikeMePageState extends State { Get.back(); showConfirmDialog( context: context, - title: '删除', - content: '该条通知删除后,当有新点赞时会重新出现在列表,是否继续?', + title: const Text('删除'), + content: const Text('该条通知删除后,当有新点赞时会重新出现在列表,是否继续?'), onConfirm: () => onRemove(item.id), ); }, @@ -228,8 +228,8 @@ class _LikeMePageState extends State { if (isNotice) { showConfirmDialog( context: context, - title: '不再通知', - content: '这条内容的点赞将不再通知,但仍可在列表内查看,是否继续?', + title: const Text('不再通知'), + content: const Text('这条内容的点赞将不再通知,但仍可在列表内查看,是否继续?'), onConfirm: () => _likeMeController.onSetNotice(item, isNotice), ); diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index 40fec09364..ed941d6dbb 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -97,7 +97,7 @@ class _ReplyMePageState extends State { void onLongPress() => showConfirmDialog( context: context, - title: '确定删除该通知?', + title: const Text('确定删除该通知?'), onConfirm: () => _replyMeController.onRemove(item.id, index), ); diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index 9d9b3d1af5..e33e34a424 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -82,7 +82,7 @@ class _SysMsgPageState extends State { final item = response[index]; void onLongPress() => showConfirmDialog( context: context, - title: '确定删除该通知?', + title: const Text('确定删除该通知?'), onConfirm: () => _sysMsgController.onRemove(item.id, index), ); return ListTile( diff --git a/lib/pages/music/controller.dart b/lib/pages/music/controller.dart index b7e6c62af4..ac95011e9f 100644 --- a/lib/pages/music/controller.dart +++ b/lib/pages/music/controller.dart @@ -2,7 +2,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/music.dart'; import 'package:PiliPlus/models_new/music/bgm_detail.dart'; import 'package:PiliPlus/pages/common/dyn/common_dyn_controller.dart'; -import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:get/get.dart'; class MusicDetailController extends CommonDynController { @@ -18,8 +17,6 @@ class MusicDetailController extends CommonDynController { late final String musicId; - bool get showDynActionBar => Pref.showDynActionBar; - String get shareUrl => 'https://music.bilibili.com/h5/music-detail?music_id=$musicId'; diff --git a/lib/pages/music/view.dart b/lib/pages/music/view.dart index 13e4d1294f..9ae09792e7 100644 --- a/lib/pages/music/view.dart +++ b/lib/pages/music/view.dart @@ -204,7 +204,24 @@ class _MusicDetailPageState extends CommonDynPageState { }); Widget _buildBottom(ThemeData theme, MusicDetail item) { + if (!controller.showDynActionBar) { + return Positioned( + right: kFloatingActionButtonMargin, + bottom: 0, + child: SlideTransition( + position: fabAnimation, + child: fabButton, + ), + ); + } + + final primary = theme.colorScheme.primary; final outline = theme.colorScheme.outline; + final style = TextButton.styleFrom( + tapTargetSize: .padded, + padding: const EdgeInsets.symmetric(horizontal: 15), + foregroundColor: outline, + ); Widget textIconButton({ required IconData icon, @@ -214,7 +231,7 @@ class _MusicDetailPageState extends CommonDynPageState { required VoidCallback onPressed, IconData? activatedIcon, }) { - final color = status ? theme.colorScheme.primary : outline; + final color = status ? primary : outline; return TextButton.icon( onPressed: onPressed, icon: Icon( @@ -222,11 +239,7 @@ class _MusicDetailPageState extends CommonDynPageState { size: 16, color: color, ), - style: TextButton.styleFrom( - tapTargetSize: MaterialTapTargetSize.padded, - padding: const EdgeInsets.symmetric(horizontal: 15), - foregroundColor: outline, - ), + style: style, label: Text( count != null ? NumUtils.numFormat(count) : text, style: TextStyle(color: color), @@ -239,116 +252,104 @@ class _MusicDetailPageState extends CommonDynPageState { right: 0, bottom: 0, child: SlideTransition( - position: fabAnim, - child: controller.showDynActionBar - ? Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, + position: fabAnimation, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin, + ), + child: replyButton, + ), + Container( + decoration: BoxDecoration( + color: theme.colorScheme.surface, + border: Border( + top: BorderSide( + color: theme.colorScheme.outline.withValues( + alpha: 0.08, + ), + ), + ), + ), + padding: EdgeInsets.only(bottom: padding.bottom), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ - Padding( - padding: const EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin, + // TODO + // Expanded( + // child: textIconButton( + // icon: FontAwesomeIcons.shareFromSquare, + // text: '转发', + // count: item.musicShares, + // onPressed: () { + // final data = controller.infoState.value.dataOrNull; + // if (data != null) { + // showModalBottomSheet( + // context: context, + // isScrollControlled: true, + // useSafeArea: true, + // builder: (context) => RepostPanel( + // rid: controller.oid, + // dynType: null, + // pic: data.mvCover, + // title: data.musicTitle, + // ), + // ); + // } + // }, + // ), + // ), + Expanded( + child: textIconButton( + icon: CustomIcons.share_node, + text: '分享', + onPressed: () => Utils.shareText(controller.shareUrl), ), - child: replyButton, ), - Container( - decoration: BoxDecoration( - color: theme.colorScheme.surface, - border: Border( - top: BorderSide( - color: theme.colorScheme.outline.withValues( - alpha: 0.08, - ), - ), + Expanded( + child: Builder( + builder: (context) => textIconButton( + icon: FontAwesomeIcons.thumbsUp, + activatedIcon: FontAwesomeIcons.solidThumbsUp, + text: '点赞', + count: item.wishCount, + status: item.wishListen ?? false, + onPressed: () async { + if (!Accounts.main.isLogin) { + SmartDialog.showToast('请先登录'); + return; + } + final hasLike = item.wishListen ?? false; + final res = await MusicHttp.wishUpdate( + controller.musicId, + hasLike, + ); + if (res.isSuccess) { + if (hasLike) { + item.wishCount--; + } else { + item.wishCount++; + } + item.wishListen = !hasLike; + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + } else { + res.toast(); + } + }, ), ), - padding: EdgeInsets.only(bottom: padding.bottom), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - // TODO - // Expanded( - // child: textIconButton( - // icon: FontAwesomeIcons.shareFromSquare, - // text: '转发', - // count: item.musicShares, - // onPressed: () { - // final data = controller.infoState.value.dataOrNull; - // if (data != null) { - // showModalBottomSheet( - // context: context, - // isScrollControlled: true, - // useSafeArea: true, - // builder: (context) => RepostPanel( - // rid: controller.oid, - // dynType: null, - // pic: data.mvCover, - // title: data.musicTitle, - // ), - // ); - // } - // }, - // ), - // ), - Expanded( - child: textIconButton( - icon: CustomIcons.share_node, - text: '分享', - onPressed: () => - Utils.shareText(controller.shareUrl), - ), - ), - Expanded( - child: Builder( - builder: (context) => textIconButton( - icon: FontAwesomeIcons.thumbsUp, - activatedIcon: FontAwesomeIcons.solidThumbsUp, - text: '点赞', - count: item.wishCount, - status: item.wishListen ?? false, - onPressed: () async { - if (!Accounts.main.isLogin) { - SmartDialog.showToast('请先登录'); - return; - } - final hasLike = item.wishListen ?? false; - final res = await MusicHttp.wishUpdate( - controller.musicId, - hasLike, - ); - if (res.isSuccess) { - if (hasLike) { - item.wishCount--; - } else { - item.wishCount++; - } - item.wishListen = !hasLike; - if (context.mounted) { - (context as Element).markNeedsBuild(); - } - } else { - res.toast(); - } - }, - ), - ), - ), - ], - ), ), ], - ) - : Align( - alignment: Alignment.bottomRight, - child: Padding( - padding: EdgeInsets.only( - right: kFloatingActionButtonMargin, - bottom: padding.bottom + kFloatingActionButtonMargin, - ), - child: replyButton, - ), ), + ), + ], + ), ), ); } diff --git a/lib/pages/music/widget/music_video_card_h.dart b/lib/pages/music/widget/music_video_card_h.dart index 5e7b7f834d..3ee2757d15 100644 --- a/lib/pages/music/widget/music_video_card_h.dart +++ b/lib/pages/music/widget/music_video_card_h.dart @@ -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'; @@ -48,14 +48,14 @@ class MusicVideoCardH extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/my_reply/view.dart b/lib/pages/my_reply/view.dart index fea883b079..2f5412fad7 100644 --- a/lib/pages/my_reply/view.dart +++ b/lib/pages/my_reply/view.dart @@ -37,10 +37,9 @@ class _MyReplyState extends State with DynMixin { } void _initReply() { - _replies.assignAll( - GStorage.reply!.values.map(ReplyInfo.fromBuffer).toList() - ..sort((a, b) => b.ctime.compareTo(a.ctime)), // rpid not aligned - ); + _replies + ..assignAll(GStorage.reply!.values.map(ReplyInfo.fromBuffer)) + ..sort((a, b) => b.ctime.compareTo(a.ctime)); // rpid not aligned; } @override @@ -54,7 +53,7 @@ class _MyReplyState extends State with DynMixin { tooltip: 'Clear', onPressed: () => showConfirmDialog( context: context, - title: 'Clear Local Storage?', + title: const Text('Clear Local Storage?'), onConfirm: () { GStorage.reply!.clear(); _replies.clear(); diff --git a/lib/pages/pgc/view.dart b/lib/pages/pgc/view.dart index b1dfae1e33..d56e5fc4f3 100644 --- a/lib/pages/pgc/view.dart +++ b/lib/pages/pgc/view.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/button/more_btn.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; @@ -83,7 +83,7 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { ThemeData theme, LoadingState?> loadingState, ) => switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => response != null && response.isNotEmpty ? Builder( @@ -170,9 +170,9 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { return Container( width: Grid.smallCardWidth / 2, margin: EdgeInsets.only( - left: StyleString.safeSpace, + left: Style.safeSpace, right: index == item.episodes!.length - 1 - ? StyleString.safeSpace + ? Style.safeSpace : 0, ), child: PgcCardVTimeline( @@ -208,8 +208,8 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { _buildRcmdTitle(theme), SliverPadding( padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, bottom: 100, ), sliver: Obx( @@ -301,8 +301,8 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { ); late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth * 0.6, childAspectRatio: 0.75, mainAxisExtent: MediaQuery.textScalerOf(context).scale(50), @@ -397,7 +397,7 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { Widget _buildFollowBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => response != null && response.isNotEmpty ? ListView.builder( @@ -412,10 +412,8 @@ class _PgcPageState extends State with AutomaticKeepAliveClientMixin { return Container( width: Grid.smallCardWidth / 2, margin: EdgeInsets.only( - left: StyleString.safeSpace, - right: index == response.length - 1 - ? StyleString.safeSpace - : 0, + left: Style.safeSpace, + right: index == response.length - 1 ? Style.safeSpace : 0, ), child: PgcCardV(item: response[index]), ); diff --git a/lib/pages/pgc/widgets/pgc_card_v.dart b/lib/pages/pgc/widgets/pgc_card_v.dart index 2f3c6b5591..20774983d3 100644 --- a/lib/pages/pgc/widgets/pgc_card_v.dart +++ b/lib/pages/pgc/widgets/pgc_card_v.dart @@ -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'; @@ -25,9 +25,9 @@ class PgcCardV extends StatelessWidget { cover: item.cover, ); return Card( - shape: const RoundedRectangleBorder(borderRadius: StyleString.mdRadius), + shape: const RoundedRectangleBorder(borderRadius: Style.mdRadius), child: InkWell( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onTap: () => PageUtils.viewPgc(seasonId: item.seasonId), diff --git a/lib/pages/pgc/widgets/pgc_card_v_timeline.dart b/lib/pages/pgc/widgets/pgc_card_v_timeline.dart index 85930ecebf..d7b8410e56 100644 --- a/lib/pages/pgc/widgets/pgc_card_v_timeline.dart +++ b/lib/pages/pgc/widgets/pgc_card_v_timeline.dart @@ -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'; @@ -25,9 +25,9 @@ class PgcCardVTimeline extends StatelessWidget { cover: item.cover, ); return Card( - shape: const RoundedRectangleBorder(borderRadius: StyleString.mdRadius), + shape: const RoundedRectangleBorder(borderRadius: Style.mdRadius), child: InkWell( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onTap: () => diff --git a/lib/pages/pgc_index/view.dart b/lib/pages/pgc_index/view.dart index 31bffe0ae1..4da07b8108 100644 --- a/lib/pages/pgc_index/view.dart +++ b/lib/pages/pgc_index/view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/common/widgets/self_sized_horizontal_list.dart'; @@ -58,7 +58,7 @@ class _PgcIndexPageState extends State ) { final padding = MediaQuery.viewPaddingOf(context); return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => Builder( builder: (context) { int count = @@ -84,8 +84,8 @@ class _PgcIndexPageState extends State ), SliverPadding( padding: EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, top: 12, bottom: padding.bottom + 100, ), @@ -224,8 +224,8 @@ class _PgcIndexPageState extends State ); late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Grid.smallCardWidth * 0.6, childAspectRatio: 0.75, mainAxisExtent: MediaQuery.textScalerOf(context).scale(50), diff --git a/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart b/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart index 5e50491dd4..33ba163225 100644 --- a/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart +++ b/lib/pages/pgc_index/widgets/pgc_card_v_pgc_index.dart @@ -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'; @@ -25,9 +25,9 @@ class PgcCardVPgcIndex extends StatelessWidget { cover: item.cover, ); return Card( - shape: const RoundedRectangleBorder(borderRadius: StyleString.mdRadius), + shape: const RoundedRectangleBorder(borderRadius: Style.mdRadius), child: InkWell( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, onTap: () => PageUtils.viewPgc(seasonId: item.seasonId), onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, diff --git a/lib/pages/pgc_review/child/view.dart b/lib/pages/pgc_review/child/view.dart index c292b5d6db..7660e3c61a 100644 --- a/lib/pages/pgc_review/child/view.dart +++ b/lib/pages/pgc_review/child/view.dart @@ -1,5 +1,5 @@ -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/custom_icon.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; @@ -163,7 +163,7 @@ class _PgcReviewChildPageState extends State Get.back(); showConfirmDialog( context: context, - title: '删除短评,同时删除评分?', + title: const Text('删除短评,同时删除评分?'), onConfirm: () => _controller.onDel(index, item.reviewId!), ); }, @@ -403,7 +403,7 @@ class _PgcReviewChildPageState extends State }, ), TextButton.icon( - style: StyleString.buttonStyle, + style: Style.buttonStyle, onPressed: _controller.queryBySort, icon: Icon( Icons.sort, diff --git a/lib/pages/rank/zone/widget/pgc_rank_item.dart b/lib/pages/rank/zone/widget/pgc_rank_item.dart index f26234c800..d821e50651 100644 --- a/lib/pages/rank/zone/widget/pgc_rank_item.dart +++ b/lib/pages/rank/zone/widget/pgc_rank_item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -32,7 +32,7 @@ class PgcRankItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( diff --git a/lib/pages/rcmd/view.dart b/lib/pages/rcmd/view.dart index 1fa551adeb..e9ed84a6a0 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/video_card/video_card_v.dart'; @@ -51,10 +51,11 @@ class _RcmdPageState extends State @override Widget build(BuildContext context) { super.build(context); + final colorScheme = ColorScheme.of(context); return Container( clipBehavior: OS.isHarmony ? Clip.none : Clip.hardEdge, - margin: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), - decoration: const BoxDecoration(borderRadius: StyleString.mdRadius), + margin: const EdgeInsets.symmetric(horizontal: Style.safeSpace), + decoration: const BoxDecoration(borderRadius: Style.mdRadius), child: refreshIndicator( onRefresh: controller.onRefresh, child: CustomScrollView( @@ -63,8 +64,10 @@ class _RcmdPageState extends State cacheExtent: 800, slivers: [ SliverPadding( - padding: const .only(top: StyleString.cardSpace, bottom: 100), - sliver: Obx(() => _buildBody(controller.loadingState.value)), + padding: const .only(top: Style.cardSpace, bottom: 100), + sliver: Obx( + () => _buildBody(colorScheme, controller.loadingState.value), + ), ), ], ), @@ -73,14 +76,17 @@ class _RcmdPageState extends State } late final gridDelegate = SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.cardSpace, + mainAxisSpacing: Style.cardSpace, + crossAxisSpacing: Style.cardSpace, maxCrossAxisExtent: Pref.recommendCardWidth, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(90), ); - Widget _buildBody(LoadingState?> loadingState) { + Widget _buildBody( + ColorScheme colorScheme, + LoadingState?> loadingState, + ) { return switch (loadingState) { Loading() => _buildSkeleton, Success(:final response) => @@ -102,9 +108,7 @@ class _RcmdPageState extends State '上次看到这里\n点击刷新', textAlign: .center, style: TextStyle( - color: Theme.of( - context, - ).colorScheme.onSurfaceVariant, + color: colorScheme.onSurfaceVariant, ), ), ), diff --git a/lib/pages/save_panel/view.dart b/lib/pages/save_panel/view.dart index 4877670c4f..96ae7c5c53 100644 --- a/lib/pages/save_panel/view.dart +++ b/lib/pages/save_panel/view.dart @@ -1,4 +1,6 @@ +import 'package:PiliPlus/common/assets.dart'; 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/grpc/bilibili/main/community/reply/v1.pb.dart' @@ -334,266 +336,258 @@ class _SavePanelState extends State { final padding = MediaQuery.viewPaddingOf(context); final maxWidth = ContextExtensions(context).mediaQueryShortestSide; late final coverSize = MediaQuery.textScalerOf(context).scale(65); - return GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: Get.back, - child: Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - SingleChildScrollView( - padding: EdgeInsets.only( - top: 12 + padding.top, - bottom: 80 + padding.bottom, - ), - child: Listener( + return Stack( + clipBehavior: .none, + alignment: .center, + children: [ + SingleChildScrollView( + hitTestBehavior: .deferToChild, + padding: .only( + top: 12 + padding.top, + bottom: 80 + padding.bottom, + ), + child: Container( + width: maxWidth, + padding: const .symmetric(horizontal: 12), + child: RepaintBoundary( + key: boundaryKey, child: Container( - width: maxWidth, - padding: const EdgeInsets.symmetric(horizontal: 12), - child: RepaintBoundary( - key: boundaryKey, - child: Container( - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - color: theme.colorScheme.surface, - borderRadius: const .all(.circular(12)), - ), - child: AnimatedSize( - curve: Curves.easeInOut, - alignment: Alignment.topCenter, - duration: const Duration(milliseconds: 255), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (_item case final ReplyInfo reply) - IgnorePointer( - child: ReplyItemGrpc( - replyItem: reply, - replyLevel: 0, - needDivider: false, - upMid: widget.upMid, - ), - ) - else if (_item case final DynamicItemModel dyn) - IgnorePointer( - child: DynamicPanel( - item: dyn, - isDetail: true, - isSave: true, - ), - ), - if (cover?.isNotEmpty == true && - title?.isNotEmpty == true) - Container( - height: 81, - clipBehavior: Clip.hardEdge, - margin: const .symmetric(horizontal: 12), - padding: const .all(8), - decoration: BoxDecoration( - color: theme.colorScheme.onInverseSurface, - borderRadius: const .all(.circular(8)), + clipBehavior: .hardEdge, + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: const .all(.circular(12)), + ), + child: AnimatedSize( + curve: Curves.easeInOut, + alignment: .topCenter, + duration: const Duration(milliseconds: 255), + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + switch (_item) { + ReplyInfo reply => IgnorePointer( + child: ReplyItemGrpc( + replyItem: reply, + replyLevel: 0, + needDivider: false, + upMid: widget.upMid, + ), + ), + DynamicItemModel dyn => IgnorePointer( + child: DynamicPanel( + item: dyn, + isDetail: true, + isSave: true, + ), + ), + _ => throw UnsupportedError(_item.toString()), + }, + if (cover?.isNotEmpty == true && + title?.isNotEmpty == true) + Container( + height: 81, + clipBehavior: Clip.hardEdge, + margin: const .symmetric(horizontal: 12), + padding: const .all(8), + decoration: BoxDecoration( + color: theme.colorScheme.onInverseSurface, + borderRadius: const .all(.circular(8)), + ), + child: Row( + spacing: 10, + children: [ + NetworkImgLayer( + src: cover!, + height: coverSize, + width: coverType == .def16_9 + ? coverSize * Style.aspectRatio16x9 + : coverSize, + quality: 100, + borderRadius: const .all(.circular(6)), ), - child: Row( - children: [ - NetworkImgLayer( - src: cover!, - height: coverSize, - width: coverType == .def16_9 - ? coverSize * - StyleString.aspectRatio16x9 - : coverSize, - quality: 100, - borderRadius: const .all(.circular(6)), - ), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: .start, - children: [ - Text( - '$title\n', - maxLines: 2, - overflow: .ellipsis, - ), - if (pubdate != null) ...[ - const Spacer(), - Text( - DateFormatUtils.format( - pubdate, - format: dateFormat, - ), - style: TextStyle( - color: theme.colorScheme.outline, - ), - ), - ], - ], + Expanded( + child: Column( + crossAxisAlignment: .start, + children: [ + Expanded( + child: Text( + '$title\n', + maxLines: 2, + overflow: .ellipsis, + ), ), - ), - ], + if (pubdate != null) + Text( + DateFormatUtils.format( + pubdate, + format: dateFormat, + ), + style: TextStyle( + color: theme.colorScheme.outline, + ), + ), + ], + ), ), - ), - showBottom - ? Stack( - clipBehavior: Clip.none, - children: [ - if (uri.isNotEmpty) - Align( - alignment: Alignment.centerRight, - child: Row( - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: - CrossAxisAlignment.end, - spacing: 4, - children: [ - if (uname?.isNotEmpty == true) - Text( - '@$uname', - maxLines: 1, - overflow: .ellipsis, - style: TextStyle( - color: theme - .colorScheme - .primary, - ), - ), - Text( - '识别二维码,$viewType$itemType', - textAlign: TextAlign.end, - style: TextStyle( - color: theme - .colorScheme - .onSurfaceVariant, - ), - ), - Text( - DateFormatUtils.longFormatDs - .format(.now()), - textAlign: TextAlign.end, - style: TextStyle( - fontSize: 13, - color: theme - .colorScheme - .outline, - ), + ], + ), + ), + showBottom + ? Stack( + clipBehavior: .none, + children: [ + if (uri.isNotEmpty) + Align( + alignment: .centerRight, + child: Row( + children: [ + Expanded( + child: Column( + mainAxisSize: .min, + crossAxisAlignment: .end, + spacing: 4, + children: [ + if (uname?.isNotEmpty == true) + Text( + '@$uname', + maxLines: 1, + overflow: .ellipsis, + style: TextStyle( + color: theme + .colorScheme + .primary, ), - ], + ), + Text( + '识别二维码,$viewType$itemType', + textAlign: .end, + style: TextStyle( + color: theme + .colorScheme + .onSurfaceVariant, + ), ), - ), - GestureDetector( - onTap: () => Utils.copyText(uri), - child: Container( - width: 88, - height: 88, - margin: const .all(12), - padding: const .all(3), - color: theme.brightness.isDark - ? Colors.white - : theme.colorScheme.surface, - child: PrettyQrView.data( - data: uri, - decoration: - const PrettyQrDecoration( - shape: - PrettyQrSquaresSymbol(), - ), + Text( + DateFormatUtils.longFormatDs + .format(.now()), + textAlign: .end, + style: TextStyle( + fontSize: 13, + color: + theme.colorScheme.outline, ), ), + ], + ), + ), + GestureDetector( + onTap: () => Utils.copyText(uri), + child: Container( + width: 88, + height: 88, + margin: const .all(12), + padding: const .all(3), + color: theme.brightness.isDark + ? Colors.white + : theme.colorScheme.surface, + child: PrettyQrView.data( + data: uri, + decoration: + const PrettyQrDecoration( + shape: + PrettyQrSquaresSymbol(), + ), ), - ], + ), ), - ), - Align( - alignment: Alignment.centerLeft, - child: Image.asset( - 'assets/images/logo/logo_2.png', - width: 100, - cacheWidth: 100.cacheSize(context), - color: - theme.colorScheme.onSurfaceVariant, - ), + ], ), - ], - ) - : const SizedBox(height: 12), - ], - ), - ), + ), + Align( + alignment: .centerLeft, + child: Image.asset( + Assets.logo2, + width: 100, + cacheWidth: 100.cacheSize(context), + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + ) + : const SizedBox(height: 12), + ], ), ), ), ), ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: DecoratedBox( - decoration: const BoxDecoration( - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black54, - ], - ), + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: DecoratedBox( + decoration: const BoxDecoration( + gradient: LinearGradient( + begin: .topCenter, + end: .bottomCenter, + colors: [ + Colors.transparent, + Colors.black54, + ], ), - child: Padding( - padding: EdgeInsets.only( - left: padding.left, - right: padding.right, - bottom: 25 + padding.bottom, - ), - child: Row( - spacing: 40, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - iconButton( - size: 42, - tooltip: '关闭', - icon: const Icon(Icons.clear), - onPressed: Get.back, - bgColor: theme.colorScheme.onInverseSurface, - iconColor: theme.colorScheme.onSurfaceVariant, - ), - iconButton( - size: 42, - tooltip: showBottom ? '隐藏' : '显示', - context: context, - icon: showBottom - ? const Icon(Icons.visibility_off) - : const Icon(Icons.visibility), - onPressed: () => setState(() { - showBottom = !showBottom; - }), - ), - if (PlatformUtils.isMobile) - iconButton( - size: 42, - tooltip: '分享', - context: context, - icon: const Icon(Icons.share), - onPressed: () => _onSaveOrSharePic(true), - ), + ), + child: Padding( + padding: EdgeInsets.only( + left: padding.left, + right: padding.right, + bottom: 25 + padding.bottom, + ), + child: Row( + spacing: 40, + mainAxisAlignment: .center, + children: [ + iconButton( + size: 42, + tooltip: '关闭', + icon: const Icon(Icons.clear), + onPressed: Get.back, + bgColor: theme.colorScheme.onInverseSurface, + iconColor: theme.colorScheme.onSurfaceVariant, + ), + iconButton( + size: 42, + tooltip: showBottom ? '隐藏' : '显示', + context: context, + icon: showBottom + ? const Icon(Icons.visibility_off) + : const Icon(Icons.visibility), + onPressed: () => setState(() { + showBottom = !showBottom; + }), + ), + if (PlatformUtils.isMobile) iconButton( size: 42, - tooltip: '保存', + tooltip: '分享', context: context, - icon: const Icon(Icons.save_alt), - onPressed: _onSaveOrSharePic, + icon: const Icon(Icons.share), + onPressed: () => _onSaveOrSharePic(true), ), - ], - ), + iconButton( + size: 42, + tooltip: '保存', + context: context, + icon: const Icon(Icons.save_alt), + onPressed: _onSaveOrSharePic, + ), + ], ), ), ), - ], - ), + ), + ], ); } } diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 87616cdac4..729e445c76 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -54,7 +54,7 @@ abstract class DebounceStreamState extends State class BaseSearchController extends GetxController { final historyList = List.from( - GStorage.historyWord.get('cacheList') ?? [], + GStorage.historyWord.get('cacheList') ?? const [], ).obs; late final Rx> trendingState; @@ -231,7 +231,7 @@ class SSearchController extends GetxController void onClearHistory() { showConfirmDialog( context: Get.context!, - title: '确定清空搜索历史?', + title: const Text('确定清空搜索历史?'), onConfirm: () { historyList.clear(); GStorage.historyWord.delete('cacheList'); diff --git a/lib/pages/search/widgets/hot_keyword.dart b/lib/pages/search/widgets/hot_keyword.dart index 6bf2b88aa5..4d27d823d6 100644 --- a/lib/pages/search/widgets/hot_keyword.dart +++ b/lib/pages/search/widgets/hot_keyword.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/models_new/search/search_trending/list.dart'; import 'package:PiliPlus/utils/extension/string_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; @@ -73,7 +74,7 @@ class SliverHotKeyword extends StatelessWidget { Padding( padding: const EdgeInsets.only(left: 4), child: Image.asset( - 'assets/images/live/live.gif', + Assets.livingRect, width: 48, height: 15, cacheHeight: cacheHeight, diff --git a/lib/pages/search/widgets/search_text.dart b/lib/pages/search/widgets/search_text.dart index cd8958cc8e..9bd36f4b76 100644 --- a/lib/pages/search/widgets/search_text.dart +++ b/lib/pages/search/widgets/search_text.dart @@ -9,8 +9,9 @@ class SearchText extends StatelessWidget { final Color? bgColor; final Color? textColor; final TextAlign? textAlign; - final EdgeInsetsGeometry? padding; final double? height; + final EdgeInsets padding; + final BorderRadius borderRadius; const SearchText({ super.key, @@ -21,8 +22,9 @@ class SearchText extends StatelessWidget { this.bgColor, this.textColor, this.textAlign, - this.padding, this.height, + this.padding = const .symmetric(horizontal: 11, vertical: 5), + this.borderRadius = const .all(.circular(6)), }); @override @@ -31,18 +33,16 @@ class SearchText extends StatelessWidget { final hasLongPress = onLongPress != null; return Material( color: bgColor ?? colorScheme.onInverseSurface, - borderRadius: const BorderRadius.all(Radius.circular(6)), + borderRadius: borderRadius, child: InkWell( onTap: () => onTap?.call(text), onLongPress: hasLongPress ? () => onLongPress!(text) : null, onSecondaryTap: hasLongPress && !PlatformUtils.isMobile ? () => onLongPress!(text) : null, - borderRadius: const BorderRadius.all(Radius.circular(6)), + borderRadius: borderRadius, child: Padding( - padding: - padding ?? - const EdgeInsets.symmetric(horizontal: 11, vertical: 5), + padding: padding, child: Text( text, textAlign: textAlign, diff --git a/lib/pages/search_panel/all/view.dart b/lib/pages/search_panel/all/view.dart index 1a4c5f73e2..5e346822d9 100644 --- a/lib/pages/search_panel/all/view.dart +++ b/lib/pages/search_panel/all/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/pages/search_panel/all/controller.dart'; @@ -49,7 +49,7 @@ class _SearchAllPanelState return SliverWaterfallFlow( gridDelegate: SliverWaterfallFlowDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: Grid.smallCardWidth * 2, - crossAxisSpacing: StyleString.safeSpace, + crossAxisSpacing: Style.safeSpace, ), delegate: SliverChildBuilderDelegate( (_, index) { @@ -80,9 +80,9 @@ class _SearchAllPanelState return Container( width: Grid.smallCardWidth / 2, margin: EdgeInsets.only( - left: StyleString.safeSpace, + left: Style.safeSpace, right: index == e.length - 1 - ? StyleString.safeSpace + ? Style.safeSpace : 0, ), child: PgcCardVSearch(item: e[index]), diff --git a/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart b/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart index 4fe53f6960..6ef7c92925 100644 --- a/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart +++ b/lib/pages/search_panel/all/widgets/pgc_card_v_search.dart @@ -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'; @@ -24,9 +24,9 @@ class PgcCardVSearch extends StatelessWidget { cover: item.cover, ); return Card( - shape: const RoundedRectangleBorder(borderRadius: StyleString.mdRadius), + shape: const RoundedRectangleBorder(borderRadius: Style.mdRadius), child: InkWell( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, onLongPress: onLongPress, onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, onTap: () => PageUtils.viewPgc(seasonId: item.seasonId), diff --git a/lib/pages/search_panel/article/widgets/item.dart b/lib/pages/search_panel/article/widgets/item.dart index 40c707c0f7..0d42347b80 100644 --- a/lib/pages/search_panel/article/widgets/item.dart +++ b/lib/pages/search_panel/article/widgets/item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -38,7 +38,7 @@ class SearchArticleItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -46,7 +46,7 @@ class SearchArticleItem extends StatelessWidget { children: [ if (item.imageUrls?.isNotEmpty == true) AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/search_panel/live/view.dart b/lib/pages/search_panel/live/view.dart index d47f20ad65..67f5dc9737 100644 --- a/lib/pages/search_panel/live/view.dart +++ b/lib/pages/search_panel/live/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_v.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/pages/search_panel/controller.dart'; import 'package:PiliPlus/pages/search_panel/live/widgets/item.dart'; @@ -46,9 +46,9 @@ class _SearchLivePanelState late final gridDelegate = SliverGridDelegateWithExtentAndRatio( maxCrossAxisExtent: Grid.smallCardWidth, - crossAxisSpacing: StyleString.cardSpace, - mainAxisSpacing: StyleString.cardSpace, - childAspectRatio: StyleString.aspectRatio, + crossAxisSpacing: Style.cardSpace, + mainAxisSpacing: Style.cardSpace, + childAspectRatio: Style.aspectRatio, mainAxisExtent: MediaQuery.textScalerOf(context).scale(80), ); @@ -56,8 +56,8 @@ class _SearchLivePanelState Widget buildList(ThemeData theme, List list) { return SliverPadding( padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, ), sliver: SliverGrid.builder( gridDelegate: gridDelegate, diff --git a/lib/pages/search_panel/live/widgets/item.dart b/lib/pages/search_panel/live/widgets/item.dart index f4c711f205..1ecb7c7549 100644 --- a/lib/pages/search_panel/live/widgets/item.dart +++ b/lib/pages/search_panel/live/widgets/item.dart @@ -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/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -31,7 +31,7 @@ class LiveItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/search_panel/pgc/view.dart b/lib/pages/search_panel/pgc/view.dart index fe15e26285..154f41098e 100644 --- a/lib/pages/search_panel/pgc/view.dart +++ b/lib/pages/search_panel/pgc/view.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/media_bangumi.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/models/search/result.dart'; import 'package:PiliPlus/pages/search_panel/controller.dart'; import 'package:PiliPlus/pages/search_panel/pgc/widgets/item.dart'; @@ -69,7 +69,7 @@ class _SearchPgcPanelState gridDelegate: SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, maxCrossAxisExtent: Grid.smallCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 1.5, + childAspectRatio: Style.aspectRatio * 1.5, minHeight: MediaQuery.textScalerOf(context).scale(155), ), itemBuilder: (context, index) => const MediaPgcSkeleton(), diff --git a/lib/pages/search_panel/pgc/widgets/item.dart b/lib/pages/search_panel/pgc/widgets/item.dart index 7cdc7600db..4aa1844d6b 100644 --- a/lib/pages/search_panel/pgc/widgets/item.dart +++ b/lib/pages/search_panel/pgc/widgets/item.dart @@ -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/image/image_save.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -32,8 +32,8 @@ class SearchPgcItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, - vertical: StyleString.cardSpace, + horizontal: Style.safeSpace, + vertical: Style.cardSpace, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/pages/search_panel/user/widgets/item.dart b/lib/pages/search_panel/user/widgets/item.dart index 80d2fe2d5b..e109e85325 100644 --- a/lib/pages/search_panel/user/widgets/item.dart +++ b/lib/pages/search_panel/user/widgets/item.dart @@ -29,10 +29,11 @@ class SearchUserItem extends StatelessWidget { children: [ const SizedBox(width: 15), PendantAvatar( - avatar: item.upic, + item.upic, size: 42, - isVip: false, officialType: item.officialVerify?.type, + liveBottom: -5, + liveFontSize: 11, roomId: item.isLive == 1 ? item.roomId : null, ), const SizedBox(width: 10), diff --git a/lib/pages/search_trending/view.dart b/lib/pages/search_trending/view.dart index 8b2b7e982a..3ee49b1386 100644 --- a/lib/pages/search_trending/view.dart +++ b/lib/pages/search_trending/view.dart @@ -1,5 +1,6 @@ import 'dart:math'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; @@ -126,7 +127,7 @@ class _SearchTrendingPageState extends State { width: width, height: height, cacheWidth: width.cacheSize(context), - 'assets/images/trending_banner.png', + Assets.trendingBanner, filterQuality: FilterQuality.low, ), ), @@ -210,7 +211,7 @@ class _SearchTrendingPageState extends State { ] else if (item.showLiveIcon == true) ...[ const SizedBox(width: 4), Image.asset( - 'assets/images/live/live.gif', + Assets.livingRect, width: 51, height: 16, cacheHeight: 16.cacheSize(context), diff --git a/lib/pages/setting/models/extra_settings.dart b/lib/pages/setting/models/extra_settings.dart index 636e0c9e8c..be0bd07ac3 100644 --- a/lib/pages/setting/models/extra_settings.dart +++ b/lib/pages/setting/models/extra_settings.dart @@ -32,6 +32,7 @@ import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/cache_manager.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/feed_back.dart'; +import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -330,9 +331,16 @@ List get extraSettings => [ SwitchModel( title: '展示头像/评论/动态装饰', leading: const Icon(MdiIcons.stickerCircleOutline), - setKey: SettingBoxKey.showDynDecorate, + setKey: SettingBoxKey.showDecorate, defaultVal: true, - onChanged: (value) => PendantAvatar.showDynDecorate = value, + onChanged: (value) => PendantAvatar.showDecorate = value, + ), + SwitchModel( + title: '显示粉丝勋章', + leading: const Icon(MdiIcons.medalOutline), + setKey: SettingBoxKey.showMedal, + defaultVal: true, + onChanged: (value) => GlobalData().showMedal = value, ), SwitchModel( title: '预览 Live Photo', diff --git a/lib/pages/setting/models/model.dart b/lib/pages/setting/models/model.dart index 71124a5aaa..3cc4ab7718 100644 --- a/lib/pages/setting/models/model.dart +++ b/lib/pages/setting/models/model.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/models/common/enum_with_label.dart'; import 'package:PiliPlus/pages/setting/widgets/normal_item.dart'; import 'package:PiliPlus/pages/setting/widgets/popup_item.dart'; @@ -164,7 +164,7 @@ SettingsModel getBanWordModel({ showDialog( context: context, builder: (context) => AlertDialog( - constraints: StyleString.dialogFixedConstraints, + constraints: Style.dialogFixedConstraints, title: Text(title), content: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/setting/models/style_settings.dart b/lib/pages/setting/models/style_settings.dart index f921a83492..e99a9e3a14 100644 --- a/lib/pages/setting/models/style_settings.dart +++ b/lib/pages/setting/models/style_settings.dart @@ -818,9 +818,10 @@ void _showReduceColorDialog( if (color.computeLuminance() < 0.2) { showConfirmDialog( context: context, - title: - '确认使用#${(color.toARGB32() & 0xFFFFFF).toRadixString(16).toUpperCase().padLeft(6)}?', - content: '所选颜色过于昏暗,可能会影响图片观看', + title: Text( + '确认使用#${(color.toARGB32() & 0xFFFFFF).toRadixString(16).toUpperCase().padLeft(6)}?', + ), + content: const Text('所选颜色过于昏暗,可能会影响图片观看'), onConfirm: onConfirm, ); } else { diff --git a/lib/pages/setting/pages/display_mode.dart b/lib/pages/setting/pages/display_mode.dart index 05adb68cbd..7b5e25802d 100644 --- a/lib/pages/setting/pages/display_mode.dart +++ b/lib/pages/setting/pages/display_mode.dart @@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show PlatformException; import 'package:flutter_displaymode/flutter_displaymode.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class SetDisplayMode extends StatefulWidget { const SetDisplayMode({super.key}); diff --git a/lib/pages/setting/pages/logs.dart b/lib/pages/setting/pages/logs.dart index d2609c2198..8aaa5376e4 100644 --- a/lib/pages/setting/pages/logs.dart +++ b/lib/pages/setting/pages/logs.dart @@ -184,7 +184,7 @@ class _LogsPageState extends State { ], ), ) - : scrollErrorWidget(), + : scrollableError, ); } } diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index 9fafad7972..03bd0b7e95 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -11,7 +11,7 @@ import 'package:flutter/material.dart' hide ListTile; import 'package:flutter/services.dart' show FilteringTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class PlaySpeedPage extends StatefulWidget { const PlaySpeedPage({super.key}); diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index d4402c3c3a..25d2fc0baa 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -70,8 +70,8 @@ class _SetSwitchItemState extends State { if (widget.setKey == SettingBoxKey.badCertificateCallback && val) { val = await showConfirmDialog( context: context, - title: '确定禁用 SSL 证书验证?', - content: '禁用容易受到中间人攻击', + title: const Text('确定禁用 SSL 证书验证?'), + content: const Text('禁用容易受到中间人攻击'), ); } diff --git a/lib/pages/sponsor_block/view.dart b/lib/pages/sponsor_block/view.dart index a670bad8ea..985dcce968 100644 --- a/lib/pages/sponsor_block/view.dart +++ b/lib/pages/sponsor_block/view.dart @@ -17,7 +17,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart' show FilteringTextInputFormatter; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; class SponsorBlockPage extends StatefulWidget { diff --git a/lib/pages/subscription/widgets/item.dart b/lib/pages/subscription/widgets/item.dart index c85f495657..66bee5feb3 100644 --- a/lib/pages/subscription/widgets/item.dart +++ b/lib/pages/subscription/widgets/item.dart @@ -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'; @@ -64,7 +64,7 @@ class SubItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/subscription_detail/widget/sub_video_card.dart b/lib/pages/subscription_detail/widget/sub_video_card.dart index 9d4bc46d27..fb1d99cd5f 100644 --- a/lib/pages/subscription_detail/widget/sub_video_card.dart +++ b/lib/pages/subscription_detail/widget/sub_video_card.dart @@ -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'; @@ -50,14 +50,14 @@ class SubVideoCardH extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { double maxWidth = boxConstraints.maxWidth; diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index c3afed8c7f..dab4ad8c6c 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'dart:math' show min; import 'dart:ui'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/progress_bar/segment_progress_bar.dart'; import 'package:PiliPlus/grpc/bilibili/app/listener/v1.pbenum.dart' @@ -71,7 +71,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; import 'package:media_kit/media_kit.dart' hide Subtitle; import 'package:path/path.dart' as path; @@ -108,7 +108,7 @@ class VideoDetailController extends GetxController // 请求返回的视频信息 late PlayUrlModel data; - final Rx videoState = LoadingState.loading().obs; + final RxBool videoState = false.obs; /// 播放器配置 画质 音质 解码格式 final Rxn currentVideoQa = Rxn(); @@ -270,7 +270,7 @@ class VideoDetailController extends GetxController } bool imageview = false; - + final isLoginVideo = Accounts.get(AccountType.video).isLogin; late final watchProgress = GStorage.watchProgress; @@ -671,7 +671,9 @@ class VideoDetailController extends GetxController (isFileSource ? true : videoPlayerKey.currentState?.mounted == true)) { - return playerInit(autoFullScreenFlag: autoFullScreenFlag); + return playerInit( + autoFullScreenFlag: autoFullScreenFlag && _autoPlay.value, + ); } return null; } @@ -695,6 +697,7 @@ class VideoDetailController extends GetxController dir: args['dirPath'], typeTag: entry.typeTag!, isMp4: entry.mediaType == 1, + hasDashAudio: entry.hasDashAudio, ) : NetworkSource( videoSource: video ?? videoUrl!, @@ -716,9 +719,7 @@ class VideoDetailController extends GetxController pgcType: isUgc ? null : pgcType, videoType: videoType, onInit: () { - if (videoState.value is! Success) { - videoState.value = const Success(null); - } + videoState.value = true; setSubtitle(vttSubtitlesIndex.value); }, width: firstVideo.width, @@ -850,7 +851,7 @@ class VideoDetailController extends GetxController if (data.dash == null) { SmartDialog.showToast('视频资源不存在'); _autoPlay.value = false; - videoState.value = const Error('视频资源不存在'); + videoState.value = false; if (plPlayerController.isFullScreen.value) { plPlayerController.toggleFullScreen(false); } @@ -945,7 +946,7 @@ class VideoDetailController extends GetxController await _initPlayerIfNeeded(autoFullScreenFlag); } else { _autoPlay.value = false; - videoState.value = result..toast(); + videoState.value = false; if (plPlayerController.isFullScreen.value) { plPlayerController.toggleFullScreen(false); } @@ -1494,7 +1495,7 @@ class VideoDetailController extends GetxController showDialog( context: Get.context!, builder: (context) => AlertDialog( - constraints: StyleString.dialogFixedConstraints, + constraints: Style.dialogFixedConstraints, title: const Text('播放地址'), content: Column( spacing: 20, diff --git a/lib/pages/video/download_panel/view.dart b/lib/pages/video/download_panel/view.dart index 5159a4bcfa..7776d44010 100644 --- a/lib/pages/video/download_panel/view.dart +++ b/lib/pages/video/download_panel/view.dart @@ -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/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -398,7 +399,7 @@ class _DownloadPanelState extends State { }, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -443,7 +444,7 @@ class _DownloadPanelState extends State { ) else if (isCurrentIndex) Image.asset( - 'assets/images/live.png', + Assets.livingStatic, color: primary, height: 12, cacheHeight: 12.cacheSize(context), @@ -543,7 +544,7 @@ class _DownloadPanelState extends State { onTap: () { showConfirmDialog( context: context, - title: '确定缓存全部?', + title: const Text('确定缓存全部?'), onConfirm: () { for (int i = 0; i < widget.episodes.length; i++) { _onDownload( diff --git a/lib/pages/video/introduction/local/view.dart b/lib/pages/video/introduction/local/view.dart index 83f219d95d..bf8f16dd69 100644 --- a/lib/pages/video/introduction/local/view.dart +++ b/lib/pages/video/introduction/local/view.dart @@ -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/image/network_img_layer.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; @@ -71,7 +71,7 @@ class _LocalIntroPanelState extends State }, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( @@ -82,7 +82,7 @@ class _LocalIntroPanelState extends State children: [ cover.existsSync() ? ClipRRect( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: Image.file( cover, width: 140.8, diff --git a/lib/pages/video/introduction/pgc/view.dart b/lib/pages/video/introduction/pgc/view.dart index b753aff085..9dc7c196e0 100644 --- a/lib/pages/video/introduction/pgc/view.dart +++ b/lib/pages/video/introduction/pgc/view.dart @@ -1,6 +1,6 @@ import 'dart:math'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; @@ -104,7 +104,7 @@ class _PgcIntroPageState extends State { } return SliverPadding( padding: - const EdgeInsets.all(StyleString.safeSpace) + + const EdgeInsets.all(Style.safeSpace) + const EdgeInsets.only(bottom: 50), sliver: sliver, ); diff --git a/lib/pages/video/introduction/pgc/widgets/intro_detail.dart b/lib/pages/video/introduction/pgc/widgets/intro_detail.dart index f293095974..c8619e1e26 100644 --- a/lib/pages/video/introduction/pgc/widgets/intro_detail.dart +++ b/lib/pages/video/introduction/pgc/widgets/intro_detail.dart @@ -100,7 +100,7 @@ class _IntroDetailState extends State horizontalDragGestureRecognizer: () => TabBarDragGestureRecognizer(isDxAllowed: isDxAllowed), children: [ - KeepAliveWrapper(builder: (context) => _buildInfo(theme)), + KeepAliveWrapper(child: _buildInfo(theme)), PgcReviewPage( name: widget.item.title!, mediaId: widget.item.mediaId, diff --git a/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart b/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart index faf28dcc80..3a9fa0141a 100644 --- a/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart +++ b/lib/pages/video/introduction/pgc/widgets/pgc_panel.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/new_ep.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart' @@ -189,7 +190,7 @@ class _PgcPanelState extends State { child: Padding( padding: const EdgeInsets.only(right: 6), child: Image.asset( - 'assets/images/live.png', + Assets.livingStatic, color: theme.primary, height: 12, cacheHeight: 12.cacheSize(context), @@ -212,7 +213,7 @@ class _PgcPanelState extends State { const SizedBox(width: 2), if (item.badge == '会员') Image.asset( - 'assets/images/big-vip.png', + Assets.vipIcon, height: 16, cacheHeight: 16.cacheSize(context), semanticLabel: "大会员", diff --git a/lib/pages/video/introduction/ugc/controller.dart b/lib/pages/video/introduction/ugc/controller.dart index b98064ed24..2f59e0c562 100644 --- a/lib/pages/video/introduction/ugc/controller.dart +++ b/lib/pages/video/introduction/ugc/controller.dart @@ -13,6 +13,7 @@ import 'package:PiliPlus/http/user.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/video/source_type.dart'; import 'package:PiliPlus/models_new/member_card_info/data.dart'; +import 'package:PiliPlus/models_new/relation/data.dart'; import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart'; import 'package:PiliPlus/models_new/video/video_detail/page.dart'; @@ -52,7 +53,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { // up主粉丝数 final Rx userStat = MemberCardInfoData().obs; // 关注状态 默认未关注 - late final RxMap followStatus = {}.obs; + late final Rx followStatus = Rx(RelationData()); late final RxMap staffRelations = {}.obs; // 是否点踩 @@ -430,9 +431,9 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { if (videoDetail.owner == null || videoDetail.staff?.isNotEmpty == true) { return; } - final res = await UserHttp.hasFollow(videoDetail.owner!.mid!); + final res = await UserHttp.userRelation(videoDetail.owner!.mid!); if (res case Success(:final response)) { - if (response['special'] == 1) response['attribute'] = -10; + if (response.special == 1) response.attribute = -10; followStatus.value = response; } } @@ -451,7 +452,7 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { if (mid == null) { return; } - int attr = followStatus['attribute'] ?? 0; + int attr = followStatus.value.attribute ?? 0; if (attr == 128) { final res = await VideoHttp.relationMod( mid: mid, @@ -459,7 +460,9 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { reSrc: 11, ); if (res.isSuccess) { - followStatus['attribute'] = 0; + followStatus + ..value.attribute = 0 + ..refresh(); } return; } else { @@ -467,9 +470,11 @@ class UgcIntroController extends CommonIntroController with ReloadMixin { context: context, mid: mid, isFollow: attr != 0, - followStatus: followStatus, + followStatus: followStatus.value, afterMod: (attribute) { - followStatus['attribute'] = attribute; + followStatus + ..value.attribute = attribute + ..refresh(); Future.delayed(const Duration(milliseconds: 500), queryFollowStatus); }, ); diff --git a/lib/pages/video/introduction/ugc/view.dart b/lib/pages/video/introduction/ugc/view.dart index fd6ba1d8d1..48161b9d15 100644 --- a/lib/pages/video/introduction/ugc/view.dart +++ b/lib/pages/video/introduction/ugc/view.dart @@ -1,4 +1,6 @@ -import 'package:PiliPlus/common/constants.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/flutter/selectable_text/selection_area.dart'; import 'package:PiliPlus/common/widgets/flutter/selectable_text/text.dart'; @@ -92,8 +94,8 @@ class _UgcIntroPanelState extends State { final isHorizontal = !isPortrait && widget.isHorizontal; return SliverPadding( padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, + left: Style.safeSpace, + right: Style.safeSpace, top: 10, ), sliver: Obx( @@ -488,7 +490,7 @@ class _UgcIntroPanelState extends State { Widget followButton(BuildContext context, ThemeData t) { return Obx( () { - int attr = introController.followStatus['attribute'] ?? 0; + int attr = introController.followStatus.value.attribute ?? 0; return TextButton( onPressed: () => introController.actionRelationMod(context), style: TextButton.styleFrom( @@ -672,9 +674,10 @@ class _UgcIntroPanelState extends State { if (!mounted) return; final confirmed = await showConfirmDialog( context: context, - title: '空降助手:搬运视频同步', - content: - '${hasPortVideo ? "" : "是否将"}该视频${hasPortVideo ? "已" : ""}绑定到此YouTube视频($ytbId)', + title: const Text('空降助手:搬运视频同步'), + content: Text( + '${hasPortVideo ? "" : "是否将"}该视频${hasPortVideo ? "已" : ""}绑定到此YouTube视频($ytbId)', + ), ); if (!hasPortVideo && confirmed) { final res = await SponsorBlock.postPortVideo( @@ -913,10 +916,10 @@ class _UgcIntroPanelState extends State { mainAxisSize: MainAxisSize.min, children: [ PendantAvatar( - avatar: userStat.card?.face, + userStat.card?.face, size: 35, badgeSize: 14, - isVip: isVip, + vipStatus: userStat.card?.vip?.status, officialType: userStat.card?.official?.type, ), const SizedBox(width: 10), @@ -1012,7 +1015,7 @@ class _UgcIntroPanelState extends State { }, child: Image.asset( semanticLabel: 'AI总结', - 'assets/images/ai.png', + Assets.ai, height: 18, width: 18, cacheHeight: 18.cacheSize(context), diff --git a/lib/pages/video/introduction/ugc/widgets/page.dart b/lib/pages/video/introduction/ugc/widgets/page.dart index 54859d3373..5b249cc2e1 100644 --- a/lib/pages/video/introduction/ugc/widgets/page.dart +++ b/lib/pages/video/introduction/ugc/widgets/page.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:math'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/models_new/video/video_detail/page.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/introduction/ugc/controller.dart'; @@ -197,7 +198,7 @@ class _PagesPanelState extends State { children: [ if (isCurrentIndex) ...[ Image.asset( - 'assets/images/live.png', + Assets.livingStatic, color: theme.colorScheme.primary, height: 12, cacheHeight: 12.cacheSize(context), diff --git a/lib/pages/video/introduction/ugc/widgets/season.dart b/lib/pages/video/introduction/ugc/widgets/season.dart index cf0ee0fe05..4120d8638c 100644 --- a/lib/pages/video/introduction/ugc/widgets/season.dart +++ b/lib/pages/video/introduction/ugc/widgets/season.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/models_new/video/video_detail/data.dart'; import 'package:PiliPlus/models_new/video/video_detail/episode.dart'; import 'package:PiliPlus/models_new/video/video_detail/section.dart'; @@ -126,7 +127,7 @@ class _SeasonPanelState extends State { ), const SizedBox(width: 15), Image.asset( - 'assets/images/live.png', + Assets.livingStatic, color: theme.colorScheme.primary, height: 12, cacheHeight: 12.cacheSize(context), diff --git a/lib/pages/video/medialist/view.dart b/lib/pages/video/medialist/view.dart index 3a0b097ba9..d322d73161 100644 --- a/lib/pages/video/medialist/view.dart +++ b/lib/pages/video/medialist/view.dart @@ -288,7 +288,7 @@ class _MediaListPanelState extends State customBorder: const CircleBorder(), onTap: () => showConfirmDialog( context: context, - title: '确定移除该视频?', + title: const Text('确定移除该视频?'), onConfirm: () => widget.onDelete!(item, index), ), onLongPress: () => widget.onDelete!(item, index), diff --git a/lib/pages/video/member/controller.dart b/lib/pages/video/member/controller.dart index bac985e7a6..086eeb5cf9 100644 --- a/lib/pages/video/member/controller.dart +++ b/lib/pages/video/member/controller.dart @@ -1,6 +1,6 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; -import 'package:PiliPlus/models/common/member/contribute_type.dart'; +import 'package:PiliPlus/models/common/member/archive_order_type_app.dart'; import 'package:PiliPlus/models/member/info.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; import 'package:PiliPlus/models_new/space/space_archive/item.dart'; @@ -55,7 +55,7 @@ class HorizontalMemberPageController @override bool customHandleResponse(bool isRefresh, Success response) { SpaceArchiveData data = response.response; - count.value = data.count ?? -1; + count = data.count; if (isRefresh) { if (isLoadPrevious) { hasPrev = data.hasPrev ?? false; @@ -83,8 +83,8 @@ class HorizontalMemberPageController String? currAid; String? firstAid; String? lastAid; - RxString order = 'pubdate'.obs; - RxInt count = (-1).obs; + ArchiveOrderTypeApp order = .pubdate; + int? count; bool isLoadPrevious = false; bool hasPrev = true; bool hasNext = true; @@ -92,15 +92,15 @@ class HorizontalMemberPageController @override Future> customGetData() => MemberHttp.spaceArchive( - type: ContributeType.video, + type: .video, mid: mid, aid: page == 1 ? currAid : isLoadPrevious ? firstAid : lastAid, - order: order.value, - sort: page != 1 && isLoadPrevious ? 'asc' : null, + order: order, + sort: page != 1 && isLoadPrevious ? .asc : null, pn: null, next: null, seasonId: null, @@ -131,7 +131,7 @@ class HorizontalMemberPageController void queryBySort() { if (isLoading) return; - order.value = order.value == 'pubdate' ? 'click' : 'pubdate'; + order = order == .pubdate ? .click : .pubdate; onReload(); } } diff --git a/lib/pages/video/member/view.dart b/lib/pages/video/member/view.dart index d15def0da5..f8076e8692 100644 --- a/lib/pages/video/member/view.dart +++ b/lib/pages/video/member/view.dart @@ -1,9 +1,10 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_header.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/image_preview_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; @@ -81,11 +82,10 @@ class _HorizontalMemberPageState extends State { Widget _buildUserPage(ThemeData theme, LoadingState userState) { return switch (userState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => Column( children: [ _buildUserInfo(theme, response), - _buildHeader(theme), Expanded( child: refreshIndicator( onRefresh: _controller.onRefresh, @@ -122,42 +122,49 @@ class _HorizontalMemberPageState extends State { } Widget _buildHeader(ThemeData theme) { - return Container( - height: 40, - padding: const EdgeInsets.fromLTRB(12, 0, 6, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Obx( - () { - final count = _controller.count.value; - return Text( - count != -1 ? '共$count视频' : '', - style: const TextStyle(fontSize: 13), - ); - }, - ), - TextButton.icon( - style: StyleString.buttonStyle, - onPressed: () => _controller - ..lastAid = widget.videoDetailController.aid.toString() - ..queryBySort(), - icon: Icon( - Icons.sort, - size: 16, - color: theme.colorScheme.secondary, - ), - label: Obx( - () => Text( - _controller.order.value == 'pubdate' ? '最新发布' : '最多播放', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.secondary, - ), - ), - ), - ), - ], + return SliverPinnedHeader( + backgroundColor: theme.colorScheme.surface, + child: Padding( + padding: const EdgeInsets.fromLTRB(12, 4, 6, 4), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ?_buildCount(), + _buildSortBtn(theme), + ], + ), + ), + ); + } + + Widget? _buildCount() { + final count = _controller.count; + if (count != null) { + return Text( + '共$count视频', + style: const TextStyle(fontSize: 13), + ); + } + return null; + } + + Widget _buildSortBtn(ThemeData theme) { + return TextButton.icon( + style: Style.buttonStyle, + onPressed: () => _controller + ..lastAid = widget.videoDetailController.aid.toString() + ..queryBySort(), + icon: Icon( + Icons.sort, + size: 16, + color: theme.colorScheme.secondary, + ), + label: Text( + _controller.order.label, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.secondary, + ), ), ); } @@ -174,32 +181,37 @@ class _HorizontalMemberPageState extends State { ), Success(:final response) => response != null && response.isNotEmpty - ? SliverFixedExtentList.builder( - itemBuilder: (context, index) { - if (index == response.length - 1 && _controller.hasNext) { - _controller.onLoadMore(); - } - final SpaceArchiveItem videoItem = response[index]; - return Padding( - padding: const EdgeInsets.only(bottom: 2), - child: VideoCardHMemberVideo( - videoItem: videoItem, - bvid: _bvid, - onTap: () { - Get.back(); - widget.ugcIntroController.onChangeEpisode( - BaseEpisodeItem( - bvid: videoItem.bvid, - cid: videoItem.cid, - cover: videoItem.cover, - ), - ); - }, - ), - ); - }, - itemCount: response.length, - itemExtent: 100, + ? SliverMainAxisGroup( + slivers: [ + _buildHeader(theme), + SliverFixedExtentList.builder( + itemBuilder: (context, index) { + if (index == response.length - 1 && _controller.hasNext) { + _controller.onLoadMore(); + } + final videoItem = response[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 2), + child: VideoCardHMemberVideo( + videoItem: videoItem, + bvid: _bvid, + onTap: () { + Get.back(); + widget.ugcIntroController.onChangeEpisode( + BaseEpisodeItem( + bvid: videoItem.bvid, + cid: videoItem.cid, + cover: videoItem.cover, + ), + ); + }, + ), + ); + }, + itemCount: response.length, + itemExtent: 100, + ), + ], ) : HttpError(onReload: _controller.onReload), Error(:final errMsg) => HttpError( diff --git a/lib/pages/video/pay_coins/view.dart b/lib/pages/video/pay_coins/view.dart index edab88a594..025785d045 100644 --- a/lib/pages/video/pay_coins/view.dart +++ b/lib/pages/video/pay_coins/view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math' as math; import 'dart:math' show max; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/pages/common/publish/publish_route.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; @@ -76,9 +77,9 @@ class _PayCoinsPageState extends State Timer? _timer; late final RxInt _thunderIndex = (-1).obs; static const List _thunderImages = [ - 'assets/images/paycoins/ic_thunder_1.png', - 'assets/images/paycoins/ic_thunder_2.png', - 'assets/images/paycoins/ic_thunder_3.png', + Assets.thunder1, + Assets.thunder2, + Assets.thunder3, ]; void _cancelTimer() { _timer?.cancel(); @@ -99,11 +100,9 @@ class _PayCoinsPageState extends State String _getPayImage(int index, bool canPay) { if (!canPay) { - return 'assets/images/paycoins/ic_22_not_enough_pay.png'; + return Assets.notEnough; } - return index == 0 - ? 'assets/images/paycoins/ic_22_mario.png' - : 'assets/images/paycoins/ic_22_gun_sister.png'; + return index == 0 ? Assets.mario : Assets.gunSister; } late final color = Colors.black.withValues(alpha: 0.4); @@ -215,9 +214,7 @@ class _PayCoinsPageState extends State children: [ SlideTransition( position: _boxAnim, - child: Image.asset( - 'assets/images/paycoins/ic_pay_coins_box.png', - ), + child: Image.asset(Assets.payBox), ), SlideTransition( position: _coinSlideAnim, @@ -226,9 +223,7 @@ class _PayCoinsPageState extends State child: Image.asset( height: 35 + (factor * 15), width: 35 + (factor * 15), - index == 0 - ? 'assets/images/paycoins/ic_coins_one.png' - : 'assets/images/paycoins/ic_coins_two.png', + index == 0 ? Assets.coinsOne : Assets.coinsTwo, ), ), ), @@ -356,9 +351,7 @@ class _PayCoinsPageState extends State child: Image.asset( width: 16, height: 28, - index == 0 - ? 'assets/images/paycoins/ic_left_disable.png' - : 'assets/images/paycoins/ic_left.png', + index == 0 ? Assets.leftDisable : Assets.left, cacheWidth: 16.cacheSize(context), ), ), @@ -381,9 +374,7 @@ class _PayCoinsPageState extends State child: Image.asset( width: 16, height: 28, - index == 1 - ? 'assets/images/paycoins/ic_right_disable.png' - : 'assets/images/paycoins/ic_right.png', + index == 1 ? Assets.rightDisable : Assets.right, cacheWidth: 16.cacheSize(context), ), ), @@ -458,7 +449,7 @@ class _PayCoinsPageState extends State width: 30, height: 30, child: Image.asset( - 'assets/images/paycoins/ic_panel_close.png', + Assets.panelClose, width: 30, height: 30, cacheWidth: 30.cacheSize(context), diff --git a/lib/pages/video/post_panel/view.dart b/lib/pages/video/post_panel/view.dart index 5b18773cfb..b2b6c6153e 100644 --- a/lib/pages/video/post_panel/view.dart +++ b/lib/pages/video/post_panel/view.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:PiliPlus/common/widgets/button/icon_button.dart'; -import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/sponsor_block.dart'; @@ -249,7 +249,7 @@ class _PostPanelState extends State @override Widget buildList(ThemeData theme) { if (list.isEmpty) { - return errorWidget(); + return const HttpError(isSliver: false); } final bottom = MediaQuery.viewPaddingOf(context).bottom; Widget child = ListView.builder( diff --git a/lib/pages/video/reply/controller.dart b/lib/pages/video/reply/controller.dart index 61214c5956..be234c4a4d 100644 --- a/lib/pages/video/reply/controller.dart +++ b/lib/pages/video/reply/controller.dart @@ -6,11 +6,9 @@ import 'package:PiliPlus/models/common/video/video_type.dart'; import 'package:PiliPlus/pages/common/reply_controller.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/utils/id_utils.dart'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; -class VideoReplyController extends ReplyController - with GetSingleTickerProviderStateMixin { +class VideoReplyController extends ReplyController { VideoReplyController({ required this.aid, required this.videoType, @@ -26,39 +24,6 @@ class VideoReplyController extends ReplyController @override dynamic get sourceId => IdUtils.av2bv(aid); - bool _isFabVisible = true; - late final AnimationController _fabAnimationCtr; - late final Animation animation; - - @override - void onInit() { - super.onInit(); - _fabAnimationCtr = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 100), - )..forward(); - animation = _fabAnimationCtr.drive( - Tween( - begin: const Offset(0.0, 2.0), - end: Offset.zero, - ).chain(CurveTween(curve: Curves.easeInOut)), - ); - } - - void showFab() { - if (!_isFabVisible) { - _isFabVisible = true; - _fabAnimationCtr.forward(); - } - } - - void hideFab() { - if (_isFabVisible) { - _isFabVisible = false; - _fabAnimationCtr.reverse(); - } - } - @override List? getDataList(MainListReply response) { return response.replies; @@ -72,10 +37,4 @@ class VideoReplyController extends ReplyController cursorNext: cursorNext, offset: paginationReply?.nextOffset, ); - - @override - void onClose() { - _fabAnimationCtr.dispose(); - super.onClose(); - } } diff --git a/lib/pages/video/reply/view.dart b/lib/pages/video/reply/view.dart index 8a87d12e11..3bff781155 100644 --- a/lib/pages/video/reply/view.dart +++ b/lib/pages/video/reply/view.dart @@ -1,11 +1,12 @@ -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/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_floating_header.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo; import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/pages/common/fab_mixin.dart'; import 'package:PiliPlus/pages/video/reply/controller.dart'; import 'package:PiliPlus/pages/video/reply/widgets/reply_item_grpc.dart'; import 'package:PiliPlus/pages/video/reply_reply/view.dart'; @@ -34,7 +35,10 @@ class VideoReplyPanel extends StatefulWidget { } class _VideoReplyPanelState extends State - with AutomaticKeepAliveClientMixin { + with + AutomaticKeepAliveClientMixin, + SingleTickerProviderStateMixin, + FabMixin { late VideoReplyController _videoReplyController; String get heroTag => widget.heroTag; @@ -65,11 +69,12 @@ class _VideoReplyPanelState extends State final theme = Theme.of(context); final child = NotificationListener( onNotification: (notification) { - final direction = notification.direction; - if (direction == ScrollDirection.forward) { - _videoReplyController.showFab(); - } else if (direction == ScrollDirection.reverse) { - _videoReplyController.hideFab(); + switch (notification.direction) { + case .forward: + showFab(); + case .reverse: + hideFab(); + case _: } return false; }, @@ -100,7 +105,7 @@ class _VideoReplyPanelState extends State ), ), TextButton.icon( - style: StyleString.buttonStyle, + style: Style.buttonStyle, onPressed: _videoReplyController.queryBySort, icon: Icon( Icons.sort, @@ -130,22 +135,28 @@ class _VideoReplyPanelState extends State ], ), Positioned( - right: kFloatingActionButtonMargin, - bottom: kFloatingActionButtonMargin + bottom, + right: 0, + bottom: 0, child: SlideTransition( - position: _videoReplyController.animation, - child: FloatingActionButton( - heroTag: null, - onPressed: () { - feedBack(); - _videoReplyController.onReply( - null, - oid: _videoReplyController.aid, - replyType: _videoReplyController.videoType.replyType, - ); - }, - tooltip: '发表评论', - child: const Icon(Icons.reply), + position: fabAnimation, + child: Padding( + padding: .only( + right: kFloatingActionButtonMargin, + bottom: kFloatingActionButtonMargin + bottom, + ), + child: FloatingActionButton( + heroTag: null, + onPressed: () { + feedBack(); + _videoReplyController.onReply( + null, + oid: _videoReplyController.aid, + replyType: _videoReplyController.videoType.replyType, + ); + }, + tooltip: '发表评论', + child: const Icon(Icons.reply), + ), ), ), ), @@ -243,6 +254,7 @@ class _VideoReplyPanelState extends State replyType: _videoReplyController.videoType.replyType, isVideoDetail: true, isNested: widget.isNested, + upMid: _videoReplyController.upMid, ), ); }); diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index f04195db4a..fd4920e66c 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -1,8 +1,11 @@ import 'dart:math'; +import 'package:PiliPlus/common/assets.dart'; 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/report.dart'; +import 'package:PiliPlus/common/widgets/extra_hit_test_widget.dart'; import 'package:PiliPlus/common/widgets/flutter/text/text.dart' as custom_text; import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; @@ -15,17 +18,20 @@ import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; import 'package:PiliPlus/models/common/image_type.dart'; import 'package:PiliPlus/pages/dynamics/widgets/vote.dart'; +import 'package:PiliPlus/pages/member/widget/medal_widget.dart'; import 'package:PiliPlus/pages/save_panel/view.dart'; import 'package:PiliPlus/pages/video/controller.dart'; import 'package:PiliPlus/pages/video/reply/widgets/zan_grpc.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/app_scheme.dart'; +import 'package:PiliPlus/utils/danmaku_utils.dart'; import 'package:PiliPlus/utils/date_utils.dart'; import 'package:PiliPlus/utils/duration_utils.dart'; import 'package:PiliPlus/utils/extension/context_ext.dart'; import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/feed_back.dart'; +import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/image_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -98,129 +104,83 @@ class ReplyItemGrpc extends StatelessWidget { }, ); - return Material( - type: MaterialType.transparency, - child: InkWell( - onTap: () => replyReply?.call(replyItem, null), - onLongPress: showMore, - onSecondaryTap: PlatformUtils.isMobile ? null : showMore, - child: _buildContent(context, theme), - ), - ); - } - - Widget _buildContent(BuildContext context, ThemeData theme) { Widget child = Padding( padding: const .fromLTRB(12, 14, 8, 5), - child: content(context, theme), + child: _buildContent(context, theme), ); - const double top = 8.0; - const double right = 12.0; - const double height = 38.0; - if (PendantAvatar.showDynDecorate && replyItem.member.hasGarbCardImage()) { - child = Stack( - clipBehavior: .none, + if (needDivider) { + child = Column( + mainAxisSize: .min, children: [ child, - Positioned( - top: top, - right: right, - height: height, - child: CachedNetworkImage( - height: height, - memCacheHeight: height.cacheSize(context), - imageUrl: ImageUtils.safeThumbnailUrl( - replyItem.member.garbCardImage, - ), - placeholder: (_, _) => const SizedBox.shrink(), - ), - ), - if (replyItem.member.hasGarbCardNumber()) - Positioned( - top: top, - right: right, - height: height, - child: Center( - child: Text( - 'NO.\n${replyItem.member.garbCardNumber}', - style: TextStyle( - fontSize: 8, - fontFamily: 'digital_id_num', - color: replyItem.member.garbCardFanColor.startsWith('#') - ? Utils.parseColor(replyItem.member.garbCardFanColor) - : null, - ), - ), - ), - ), - ], - ); - } - return Column( - crossAxisAlignment: .stretch, - children: [ - child, - if (needDivider) Divider( indent: 55, endIndent: 15, height: 0.3, color: theme.colorScheme.outline.withValues(alpha: 0.08), ), - ], + ], + ); + } + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () => replyReply?.call(replyItem, null), + onLongPress: showMore, + onSecondaryTap: PlatformUtils.isMobile ? null : showMore, + child: child, + ), ); } - Widget content(BuildContext context, ThemeData theme) { - final padding = EdgeInsets.only(left: replyLevel == 0 ? 6 : 45, right: 6); - return Column( - mainAxisSize: .min, - crossAxisAlignment: .start, - children: [ - GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: () { - feedBack(); - Get.toNamed('/member?mid=${replyItem.mid}'); - }, - child: Row( - mainAxisSize: .min, - crossAxisAlignment: .center, - spacing: 12, - children: [ - PendantAvatar( - avatar: replyItem.member.face, - size: 34, - badgeSize: 14, - isVip: replyItem.member.vipStatus > 0, - officialType: replyItem.member.officialVerifyType.toInt(), - garbPendantImage: replyItem.member.hasGarbPendantImage() - ? replyItem.member.garbPendantImage - : null, - ), - Column( + Widget _buildHeader(BuildContext context, ThemeData theme) { + final member = replyItem.member; + Widget header = GestureDetector( + onTap: () { + feedBack(); + Get.toNamed('/member?mid=${replyItem.mid}'); + }, + child: ExtraHitTestWidget( + width: 46, + child: Row( + crossAxisAlignment: .center, + spacing: 12, + children: [ + PendantAvatar( + member.face, + size: 34, + badgeSize: 14, + vipStatus: member.vipStatus.toInt(), + officialType: member.officialVerifyType.toInt(), + pendantImage: member.hasGarbPendantImage() + ? member.garbPendantImage + : null, + ), + Expanded( + child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( - mainAxisSize: MainAxisSize.min, spacing: 6, children: [ - Text( - replyItem.member.name, - style: TextStyle( - color: - (replyItem.member.vipStatus > 0 && - replyItem.member.vipType == 2) - ? theme.colorScheme.vipColor - : theme.colorScheme.outline, - fontSize: 13, + Flexible( + child: Text( + member.name, + maxLines: 1, + overflow: .ellipsis, + style: TextStyle( + color: (member.vipStatus > 0 && member.vipType == 2) + ? theme.colorScheme.vipColor + : theme.colorScheme.outline, + fontSize: 13, + ), ), ), Image.asset( Utils.levelName( - replyItem.member.level, - isSeniorMember: replyItem.member.isSeniorMember == 1, + member.level, + isSeniorMember: member.isSeniorMember == 1, ), height: 11, cacheHeight: 11.cacheSize(context), @@ -231,12 +191,28 @@ class ReplyItemGrpc extends StatelessWidget { size: PBadgeSize.small, isStack: false, fontSize: 9, + ) + else if (GlobalData().showMedal && + member.hasFansMedalLevel()) + MedalWidget( + medalName: member.fansMedalName, + level: member.fansMedalLevel.toInt(), + backgroundColor: DmUtils.decimalToColor( + member.fansMedalColor.toInt(), + ), + nameColor: DmUtils.decimalToColor( + member.fansMedalColorName.toInt(), + ), + padding: const .symmetric( + horizontal: 6, + vertical: 1.5, + ), ), ], ), Row( mainAxisSize: MainAxisSize.min, - children: [ + children: [ Text( replyLevel == 0 ? DateFormatUtils.format( @@ -263,9 +239,63 @@ class ReplyItemGrpc extends StatelessWidget { ), ], ), - ], - ), + ), + ], ), + ), + ); + if (PendantAvatar.showDecorate) { + final garb = replyItem.memberV2.garb; + if (garb.hasCardImage()) { + const double height = 38.0; + return Stack( + clipBehavior: .none, + children: [ + Positioned( + top: 0, + right: 0, + height: height, + child: CachedNetworkImage( + height: height, + memCacheHeight: height.cacheSize(context), + imageUrl: ImageUtils.safeThumbnailUrl(garb.cardImage), + placeholder: (_, _) => const SizedBox.shrink(), + ), + ), + if (garb.hasCardNumber()) + Positioned( + top: 0, + right: 0, + height: height, + child: Center( + child: Text( + '${garb.fanNumPrefix}\n${garb.cardNumber}', + style: TextStyle( + fontSize: 8, + fontFamily: Assets.digitalNum, + color: Utils.parseColor(garb.cardFanColor), + ), + ), + ), + ), + Padding( + padding: const .only(right: 80), + child: header, + ), + ], + ); + } + } + return header; + } + + Widget _buildContent(BuildContext context, ThemeData theme) { + final padding = EdgeInsets.only(left: replyLevel == 0 ? 6 : 45, right: 6); + return Column( + mainAxisSize: .min, + crossAxisAlignment: .start, + children: [ + _buildHeader(context, theme), const SizedBox(height: 10), Padding( padding: padding, @@ -292,7 +322,7 @@ class ReplyItemGrpc extends StatelessWidget { ), const TextSpan(text: ' '), ], - buildContent(context, theme, replyItem), + _buildMessage(context, theme, replyItem), ], ), ), @@ -345,7 +375,7 @@ class ReplyItemGrpc extends StatelessWidget { visualDensity: VisualDensity.compact, ); return Row( - children: [ + children: [ const SizedBox(width: 36), SizedBox( height: 32, @@ -506,7 +536,7 @@ class ReplyItemGrpc extends StatelessWidget { ? '' : ' ', ), - buildContent(context, theme, childReply), + _buildMessage(context, theme, childReply), ], ), ), @@ -552,7 +582,7 @@ class ReplyItemGrpc extends StatelessWidget { ); } - InlineSpan buildContent( + InlineSpan _buildMessage( BuildContext context, ThemeData theme, ReplyInfo replyItem, @@ -852,7 +882,7 @@ class ReplyItemGrpc extends StatelessWidget { children: [ InkWell( onTap: Get.back, - borderRadius: StyleString.bottomSheetRadius, + borderRadius: Style.bottomSheetRadius, child: SizedBox( height: 35, child: Center( @@ -876,7 +906,6 @@ class ReplyItemGrpc extends StatelessWidget { (item.deepCopy() ..unknownFields.clear() ..replies.clear() - ..clearMemberV2() ..clearTrackInfo()) .writeToBuffer(), ); @@ -905,7 +934,6 @@ class ReplyItemGrpc extends StatelessWidget { (item.deepCopy() ..unknownFields.clear() ..replies.clear() - ..clearMemberV2() ..clearTrackInfo()) .writeToBuffer(); GStorage.reply!.putAll({ @@ -945,7 +973,7 @@ class ReplyItemGrpc extends StatelessWidget { ], ), ), - actions: [ + actions: [ TextButton( onPressed: () => Get.back(result: false), child: Text( diff --git a/lib/pages/video/reply_reply/view.dart b/lib/pages/video/reply_reply/view.dart index 45c491a3c0..e7049bf735 100644 --- a/lib/pages/video/reply_reply/view.dart +++ b/lib/pages/video/reply_reply/view.dart @@ -1,5 +1,5 @@ -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/colored_box_transition.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; @@ -16,6 +16,7 @@ import 'package:PiliPlus/utils/extension/widget_ext.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; +import 'package:fixnum/fixnum.dart' show Int64; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -34,6 +35,7 @@ class VideoReplyReplyPanel extends CommonSlidePage { required this.isVideoDetail, required this.replyType, this.isNested = false, + this.upMid, }); final int? id; final int oid; @@ -43,6 +45,7 @@ class VideoReplyReplyPanel extends CommonSlidePage { final bool isVideoDetail; final int replyType; final bool isNested; + final Int64? upMid; @override State createState() => _VideoReplyReplyPanelState(); @@ -225,7 +228,7 @@ class _VideoReplyReplyPanelState extends State replyLevel: 2, needDivider: false, onReply: (replyItem) => _controller.onReply(replyItem, index: -1), - upMid: _controller.upMid, + upMid: widget.upMid ?? _controller.upMid, onCheckReply: (item) => _controller.onCheckReply(item, isManual: true), ), @@ -261,7 +264,7 @@ class _VideoReplyReplyPanelState extends State }, ), TextButton.icon( - style: StyleString.buttonStyle, + style: Style.buttonStyle, onPressed: _controller.queryBySort, icon: Icon( Icons.sort, diff --git a/lib/pages/video/reply_search_item/child/widgets/item.dart b/lib/pages/video/reply_search_item/child/widgets/item.dart index ceac7bada8..ae6ac2fdd7 100644 --- a/lib/pages/video/reply_search_item/child/widgets/item.dart +++ b/lib/pages/video/reply_search_item/child/widgets/item.dart @@ -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'; @@ -61,14 +61,14 @@ class ReplySearchItem extends StatelessWidget { onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( - aspectRatio: StyleString.aspectRatio, + aspectRatio: Style.aspectRatio, child: LayoutBuilder( builder: (context, boxConstraints) { return Stack( diff --git a/lib/pages/video/view.dart b/lib/pages/video/view.dart index 36369b667a..04d42f2241 100644 --- a/lib/pages/video/view.dart +++ b/lib/pages/video/view.dart @@ -3,15 +3,17 @@ import 'dart:io'; import 'dart:math'; import 'dart:ui'; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/assets.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; +import 'package:PiliPlus/common/widgets/flutter/pop_scope.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/image_viewer/hero_dialog_route.dart'; import 'package:PiliPlus/common/widgets/keep_alive_wrapper.dart'; +import 'package:PiliPlus/common/widgets/route_aware_mixin.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/sliver/sliver_pinned_dynamic_header.dart'; import 'package:PiliPlus/harmony_adapt/harmony_channel.dart'; -import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/models/common/episode_panel_type.dart'; import 'package:PiliPlus/models_new/pgc/pgc_info_model/result.dart'; @@ -55,6 +57,7 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/scroll_controller_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/image_utils.dart'; +import 'package:PiliPlus/utils/mobile_observer.dart'; import 'package:PiliPlus/utils/num_utils.dart'; import 'package:PiliPlus/utils/page_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; @@ -80,7 +83,11 @@ class VideoDetailPageV extends StatefulWidget { } class _VideoDetailPageVState extends State - with TickerProviderStateMixin, RouteAware, WidgetsBindingObserver { + with + TickerProviderStateMixin, + RouteAware, + RouteAwareMixin, + WidgetsBindingObserver { final heroTag = Get.arguments['heroTag']; late final VideoDetailController videoDetailController; @@ -174,7 +181,7 @@ class _VideoDetailPageVState extends State videoSourceInit(); autoScreen(); - WidgetsBinding.instance.addObserver(this); + addObserverMobile(this); } // 获取视频资源,初始化播放器 @@ -333,13 +340,17 @@ class _VideoDetailPageVState extends State return null; } } - plPlayerController = videoDetailController.plPlayerController; + final plPlayerController = this.plPlayerController = + videoDetailController.plPlayerController; videoDetailController.autoPlay = true; - plPlayerController! + plPlayerController ..addStatusLister(playerListener) ..addPositionListener(positionListener); - if (videoDetailController.plPlayerController.preInitPlayer) { - return plPlayerController!.play(); + if (plPlayerController.preInitPlayer) { + if (plPlayerController.autoEnterFullScreen) { + plPlayerController.triggerFullScreen(); + } + return plPlayerController.play(); } else { return videoDetailController.playerInit( autoplay: true, @@ -390,8 +401,7 @@ class _VideoDetailPageVState extends State PlPlayerController.updatePlayCount(); } } - PageUtils.routeObserver.unsubscribe(this); - WidgetsBinding.instance.removeObserver(this); + removeObserverMobile(this); if (PlatformUtils.isMobile) { showStatusBar(); } @@ -413,11 +423,11 @@ class _VideoDetailPageVState extends State ScreenBrightnessPlatform.instance.resetApplicationScreenBrightness(); } - videoDetailController.cancelBlockListener(); - introController.cancelTimer(); videoDetailController + ..videoState.value = false + ..cancelBlockListener() ..playerStatus = plPlayerController?.playerStatus.value ..brightness = plPlayerController?.brightness.value; if (plPlayerController != null) { @@ -427,19 +437,14 @@ class _VideoDetailPageVState extends State ..removePositionListener(positionListener) ..pause(); } - isShowing = false; - super.didPushNext(); } @override // 返回当前页面时 void didPopNext() { - if (videoDetailController.imageview) { - videoDetailController.imageview = false; - return; - } + super.didPopNext(); - if (plPlayerController?.isCloseAll == true) { + if (plPlayerController?.isCloseAll ?? false) { return; } @@ -452,7 +457,6 @@ class _VideoDetailPageVState extends State videoDetailController.plPlayerController.pause(); } - isShowing = true; PlPlayerController.setPlayCallBack(playCallBack); introController.startTimer(); @@ -484,21 +488,14 @@ class _VideoDetailPageVState extends State ); } else if (videoDetailController.plPlayerController.preInitPlayer && !videoDetailController.isQuerying && - videoDetailController.videoState.value is! Error) { + videoDetailController.videoUrl != null) { videoDetailController.playerInit(); } - - super.didPopNext(); } @override void didChangeDependencies() { super.didChangeDependencies(); - PageUtils.routeObserver.subscribe( - this, - ModalRoute.of(context)! as PageRoute, - ); - padding = MediaQuery.viewPaddingOf(context); final size = MediaQuery.sizeOf(context); @@ -506,7 +503,7 @@ class _VideoDetailPageVState extends State maxHeight = size.height; final shortestSide = size.shortestSide; - final minVideoHeight = shortestSide / StyleString.aspectRatio16x9; + final minVideoHeight = shortestSide / Style.aspectRatio16x9; final maxVideoHeight = max(size.longestSide * 0.65, shortestSide); videoDetailController ..isPortrait = isPortrait = maxHeight >= maxWidth @@ -562,9 +559,7 @@ class _VideoDetailPageVState extends State ..addListener(animListener); if (PlatformUtils.isMobile && mounted && isShowing && !isFullScreen) { if (isPortrait) { - if (!videoDetailController.imageview) { - showStatusBar(); - } + showStatusBar(); } else if (!videoDetailController.horizontalScreen) { hideStatusBar(); } @@ -989,7 +984,7 @@ class _VideoDetailPageVState extends State enableVerticalExpand && !isPortrait) { final double videoHeight = maxHeight - padding.vertical; - final double width = videoHeight / StyleString.aspectRatio16x9; + final double width = videoHeight / Style.aspectRatio16x9; final videoWidth = isFullScreen ? maxWidth : width; final introWidth = (maxWidth - padding.horizontal - width) / 2; final introHeight = maxHeight - padding.top; @@ -1051,10 +1046,10 @@ class _VideoDetailPageVState extends State width = maxWidth - clampDouble(maxWidth - width, 280, 425); } final videoWidth = isFullScreen ? maxWidth : width; - final double height = width / StyleString.aspectRatio16x9; + final double height = width / Style.aspectRatio16x9; final videoHeight = isFullScreen ? maxHeight - padding.top : height; if (height > maxHeight) { - return childSplit(StyleString.aspectRatio16x9); + return childSplit(Style.aspectRatio16x9); } final introHeight = maxHeight - height - padding.top; final showIntro = @@ -1115,7 +1110,7 @@ class _VideoDetailPageVState extends State localIntroPanel() else if (showIntro) KeepAliveWrapper( - builder: (context) => CustomScrollView( + child: CustomScrollView( key: const PageStorageKey(CommonIntroController), controller: videoDetailController.effectiveIntroScrollCtr, @@ -1309,7 +1304,7 @@ class _VideoDetailPageVState extends State tooltip: '播放', onPressed: handlePlay, icon: Image.asset( - 'assets/images/play.png', + Assets.play, width: 60, height: 60, cacheHeight: 60.cacheSize(context), @@ -1372,7 +1367,7 @@ class _VideoDetailPageVState extends State required double width, required double height, bool isPipMode = false, - }) => PopScope( + }) => popScope( key: videoDetailController.videoPlayerKey, canPop: !isFullScreen && @@ -1381,7 +1376,7 @@ class _VideoDetailPageVState extends State onPopInvokedWithResult: _onPopInvokedWithResult, child: Obx( () => - videoDetailController.videoState.value is! Success || + !videoDetailController.videoState.value || !videoDetailController.autoPlay || plPlayerController?.videoController == null ? const SizedBox.shrink() @@ -1432,7 +1427,7 @@ class _VideoDetailPageVState extends State child = childWhenDisabled; } else if (maxWidth / maxHeight >= kScreenRatio) { child = childWhenDisabledLandscape; - } else if (maxWidth / StyleString.aspectRatio16x9 < 0.4 * maxHeight) { + } else if (maxWidth / Style.aspectRatio16x9 < 0.4 * maxHeight) { child = childWhenDisabled; } else { child = childWhenDisabledAlmostSquare; @@ -1619,7 +1614,7 @@ class _VideoDetailPageVState extends State children: [ const Positioned.fill(child: ColoredBox(color: Colors.black)), - if (isShowing) plPlayer(width: width, height: height), + plPlayer(width: width, height: height), Obx(() { if (!videoDetailController.autoPlay) { @@ -1636,7 +1631,7 @@ class _VideoDetailPageVState extends State height: height, cacheWidth: true, getPlaceHolder: () => Center( - child: Image.asset('assets/images/loading.png'), + child: Image.asset(Assets.loading), ), ), ), @@ -1803,75 +1798,76 @@ class _VideoDetailPageVState extends State if (videoDetailController.isFileSource) { return localIntroPanel(needCtr: needCtr); } - Widget introPanel() => KeepAliveWrapper( - builder: (context) { - final child = CustomScrollView( - key: const PageStorageKey(CommonIntroController), - controller: needCtr - ? videoDetailController.effectiveIntroScrollCtr - : null, - physics: !needCtr - ? const AlwaysScrollableScrollPhysics( - parent: ClampingScrollPhysics(), - ) - : null, - slivers: [ - if (videoDetailController.isUgc) ...[ - UgcIntroPanel( - key: videoIntroKey, - heroTag: heroTag, - showAiBottomSheet: showAiBottomSheet, - showEpisodes: showEpisodes, - onShowMemberPage: onShowMemberPage, - isPortrait: isPortrait, - isHorizontal: isHorizontal ?? width! / height! >= kScreenRatio, - ), - if (needRelated && videoDetailController.showRelatedVideo) ...[ - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.only(top: StyleString.safeSpace), - child: Divider( - height: 1, - indent: 12, - endIndent: 12, - color: themeData.colorScheme.outline.withValues( - alpha: 0.08, - ), + Widget introPanel() { + Widget child = CustomScrollView( + key: const PageStorageKey(CommonIntroController), + controller: needCtr + ? videoDetailController.effectiveIntroScrollCtr + : null, + physics: !needCtr + ? const AlwaysScrollableScrollPhysics( + parent: ClampingScrollPhysics(), + ) + : null, + slivers: [ + if (videoDetailController.isUgc) ...[ + UgcIntroPanel( + key: videoIntroKey, + heroTag: heroTag, + showAiBottomSheet: showAiBottomSheet, + showEpisodes: showEpisodes, + onShowMemberPage: onShowMemberPage, + isPortrait: isPortrait, + isHorizontal: isHorizontal ?? width! / height! >= kScreenRatio, + ), + if (needRelated && videoDetailController.showRelatedVideo) ...[ + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only( + top: Style.safeSpace, + ), + child: Divider( + height: 1, + indent: 12, + endIndent: 12, + color: themeData.colorScheme.outline.withValues( + alpha: 0.08, ), ), ), - RelatedVideoPanel(key: videoRelatedKey, heroTag: heroTag), - ], - ] else - PgcIntroPage( - key: videoIntroKey, - heroTag: heroTag, - cid: videoDetailController.cid.value, - showEpisodes: showEpisodes, - showIntroDetail: showIntroDetail, - maxWidth: width ?? maxWidth, - isLandscape: !isPortrait, - ), - SliverToBoxAdapter( - child: SizedBox( - height: - (videoDetailController.isPlayAll && !isPortrait - ? 80 - : StyleString.safeSpace) + - padding.bottom, ), + RelatedVideoPanel(key: videoRelatedKey, heroTag: heroTag), + ], + ] else + PgcIntroPage( + key: videoIntroKey, + heroTag: heroTag, + cid: videoDetailController.cid.value, + showEpisodes: showEpisodes, + showIntroDetail: showIntroDetail, + maxWidth: width ?? maxWidth, + isLandscape: !isPortrait, ), - ], + SliverToBoxAdapter( + child: SizedBox( + height: + (videoDetailController.isPlayAll && !isPortrait + ? 80 + : Style.safeSpace) + + padding.bottom, + ), + ), + ], + ); + if (isNested) { + child = ExtendedVisibilityDetector( + uniqueKey: const Key('intro-panel'), + child: child, ); - if (isNested) { - return ExtendedVisibilityDetector( - uniqueKey: const Key('intro-panel'), - child: child, - ); - } - return child; - }, - ); + } + return KeepAliveWrapper(child: child); + } + if (videoDetailController.isPlayAll) { return Stack( clipBehavior: Clip.none, @@ -1924,7 +1920,7 @@ class _VideoDetailPageVState extends State Widget get seasonPanel { final videoDetail = ugcIntroController.videoDetail.value; return KeepAliveWrapper( - builder: (context) => Column( + child: Column( children: [ if ((videoDetail.pages?.length ?? 0) > 1) if (videoDetail.ugcSeason != null) diff --git a/lib/pages/video/view_point/view.dart b/lib/pages/video/view_point/view.dart index d5d65fc5e2..26d54a555d 100644 --- a/lib/pages/video/view_point/view.dart +++ b/lib/pages/video/view_point/view.dart @@ -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/common/widgets/progress_bar/segment_progress_bar.dart'; @@ -138,7 +138,7 @@ class _ViewPointsPageState extends State : null, child: Padding( padding: const EdgeInsets.symmetric( - horizontal: StyleString.safeSpace, + horizontal: Style.safeSpace, vertical: 5, ), child: Row( diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index a1bb2d10ff..cb5487d35f 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -64,7 +64,7 @@ import 'package:flutter/material.dart' hide showBottomSheet; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:os_type/os_type.dart'; @@ -567,15 +567,18 @@ class HeaderControlState extends State ); }, ), - Obx( - () => ActionRowLineItem( - iconData: Icons.play_circle_outline, - onTap: plPlayerController.setContinuePlayInBackground, - text: " 后台播放 ", - selectStatus: - plPlayerController.continuePlayInBackground.value, + if (PlatformUtils.isMobile) + Obx( + () => ActionRowLineItem( + iconData: Icons.play_circle_outline, + onTap: + plPlayerController.setContinuePlayInBackground, + text: " 后台播放 ", + selectStatus: plPlayerController + .continuePlayInBackground + .value, + ), ), - ), ], ), ), diff --git a/lib/pages/webdav/view.dart b/lib/pages/webdav/view.dart index c9d16360b8..f71cf6c375 100644 --- a/lib/pages/webdav/view.dart +++ b/lib/pages/webdav/view.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/pages/webdav/webdav.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; @@ -98,7 +98,7 @@ class _WebDavSettingPageState extends State { child: FilledButton.tonal( style: FilledButton.styleFrom( shape: const RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, ), ), onPressed: WebDav().backup, @@ -110,7 +110,7 @@ class _WebDavSettingPageState extends State { child: FilledButton.tonal( style: FilledButton.styleFrom( shape: const RoundedRectangleBorder( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, ), ), onPressed: WebDav().restore, diff --git a/lib/pages/whisper/widgets/item.dart b/lib/pages/whisper/widgets/item.dart index e22e101bd0..86c5ad6546 100644 --- a/lib/pages/whisper/widgets/item.dart +++ b/lib/pages/whisper/widgets/item.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/flutter/list_tile.dart'; @@ -87,7 +88,7 @@ class WhisperSessionItem extends StatelessWidget { Get.back(); showConfirmDialog( context: context, - title: '确定删除该对话?', + title: const Text('确定删除该对话?'), onConfirm: () => onRemove(item.id.privateId.talkerUid.toInt()), ); @@ -121,7 +122,7 @@ class WhisperSessionItem extends StatelessWidget { height: 42, onTap: () => showConfirmDialog( context: context, - title: '确定删除该对话?', + title: const Text('确定删除该对话?'), onConfirm: () => onRemove(item.id.privateId.talkerUid.toInt()), ), @@ -184,6 +185,25 @@ class WhisperSessionItem extends StatelessWidget { } else { SmartDialog.showToast(item.id.foldId.type.name); } + return; + } + + if (item.id.hasSystemId()) { + switch (item.id.systemId.type) { + case SessionType.SESSION_TYPE_SYSTEM: + Get.toNamed('/sysMsg'); + case SessionType.SESSION_TYPE_AI_FOLD: + case SessionType.SESSION_TYPE_CUSTOMER_ACCOUNT: + case SessionType.SESSION_TYPE_CUSTOMER_FOLD: + case SessionType.SESSION_TYPE_DUSTBIN: + case SessionType.SESSION_TYPE_GROUP: + case SessionType.SESSION_TYPE_GROUP_FOLD: + case SessionType.SESSION_TYPE_PRIVATE: + case SessionType.SESSION_TYPE_STRANGER: + case SessionType.SESSION_TYPE_UNFOLLOWED: + case SessionType.SESSION_TYPE_UNKNOWN: + SmartDialog.showToast(item.id.systemId.type.name); + } } }, leading: Builder( @@ -207,14 +227,13 @@ class WhisperSessionItem extends StatelessWidget { Get.toNamed('/member?mid=${item.sessionInfo.avatar.mid}') : null, child: PendantAvatar( + avatar, size: 42, badgeSize: 14, - avatar: avatar, - garbPendantImage: - pendant?.resImage.imageSrc.remote.hasUrl() == true + pendantImage: pendant?.resImage.imageSrc.remote.hasUrl() == true ? pendant!.resImage.imageSrc.remote.url : pendant?.resAnimation.webpSrc.remote.url, - isVip: vipInfo?['status'] != null && vipInfo!['status'] > 0, + vipStatus: vipInfo?['status'], officialType: official?.hasLocalValue() == true ? switch (official!.localValue) { 3 => 0, @@ -260,7 +279,7 @@ class WhisperSessionItem extends StatelessWidget { ), if (item.sessionInfo.isLive) Image.asset( - 'assets/images/live/live.gif', + Assets.livingRect, height: 15, cacheHeight: 15.cacheSize(context), filterQuality: FilterQuality.low, diff --git a/lib/pages/whisper_block/view.dart b/lib/pages/whisper_block/view.dart index 47423cdebb..2a03930174 100644 --- a/lib/pages/whisper_block/view.dart +++ b/lib/pages/whisper_block/view.dart @@ -37,7 +37,7 @@ class _WhisperBlockPageState extends State { LoadingState?> loadingState, ) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => response != null && response.isNotEmpty ? Column( @@ -81,8 +81,8 @@ class _WhisperBlockPageState extends State { onTap: (keyword) { showConfirmDialog( context: context, - title: '删除屏蔽词?', - content: '该屏蔽词将不再生效', + title: const Text('删除屏蔽词?'), + content: const Text('该屏蔽词将不再生效'), onConfirm: () => _controller.onRemove(e), ); }, diff --git a/lib/pages/whisper_detail/view.dart b/lib/pages/whisper_detail/view.dart index ff57111ae7..9962334d8a 100644 --- a/lib/pages/whisper_detail/view.dart +++ b/lib/pages/whisper_detail/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io' show File; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/common/widgets/dialog/report.dart'; import 'package:PiliPlus/common/widgets/flutter/chat_list_view.dart'; import 'package:PiliPlus/common/widgets/flutter/text_field/text_field.dart'; @@ -92,7 +93,7 @@ class _WhisperDetailPageState if (_whisperDetailController.isLive) ...[ const SizedBox(width: 10), Image.asset( - 'assets/images/live/live.gif', + Assets.livingRect, height: 16, cacheHeight: 16.cacheSize(context), filterQuality: FilterQuality.low, @@ -151,7 +152,7 @@ class _WhisperDetailPageState Widget _buildBody(LoadingState?> loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => m3eLoading, Success(:final response) => response != null && response.isNotEmpty ? ChatListView.separated( diff --git a/lib/pages/whisper_detail/widget/chat_item.dart b/lib/pages/whisper_detail/widget/chat_item.dart index ad50c7ec09..5d5ee80250 100644 --- a/lib/pages/whisper_detail/widget/chat_item.dart +++ b/lib/pages/whisper_detail/widget/chat_item.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:math' as math; 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/gesture/tap_gesture_recognizer.dart'; @@ -408,7 +409,7 @@ class ChatItem extends StatelessWidget { clipBehavior: Clip.hardEdge, constraints: const BoxConstraints(maxWidth: 400.0), decoration: BoxDecoration( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, color: theme.colorScheme.onInverseSurface, ), child: LayoutBuilder( @@ -442,8 +443,7 @@ class ChatItem extends StatelessWidget { NetworkImgLayer( borderRadius: BorderRadius.zero, // 此处应为非表情类型,且默认不需要圆角 width: constrains.maxWidth, - height: - constrains.maxWidth / StyleString.aspectRatio16x9, + height: constrains.maxWidth / Style.aspectRatio16x9, src: content['cover'], ), PBadge( @@ -620,7 +620,7 @@ class ChatItem extends StatelessWidget { height: width * ratio, src: url, ); - if (ratio <= StyleString.imgMaxRatio) { + if (ratio <= Style.imgMaxRatio) { child = fromHero( tag: url, child: child, @@ -781,7 +781,7 @@ class ChatItem extends StatelessWidget { builder: (context, constraints) { final maxWidth = math.max(400.0, constraints.maxWidth); Widget child = ClipRRect( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: CachedNetworkImage( width: maxWidth, memCacheWidth: maxWidth.cacheSize(context), diff --git a/lib/pages/whisper_link_setting/controller.dart b/lib/pages/whisper_link_setting/controller.dart index 32679c05a0..5d745b9b65 100644 --- a/lib/pages/whisper_link_setting/controller.dart +++ b/lib/pages/whisper_link_setting/controller.dart @@ -12,6 +12,7 @@ import 'package:PiliPlus/models_new/msg/msg_dnd/uid_setting.dart'; import 'package:PiliPlus/models_new/msg/session_ss/data.dart'; import 'package:PiliPlus/utils/accounts.dart'; import 'package:fixnum/fixnum.dart'; +import 'package:flutter/widgets.dart' show Text; import 'package:get/get.dart'; class WhisperLinkSettingController extends GetxController { @@ -64,8 +65,8 @@ class WhisperLinkSettingController extends GetxController { if (isPush) { showConfirmDialog( context: Get.context!, - title: '确认关闭内容推送吗?', - content: '若关闭此开关,你将不再收到该账号的图文消息与稿件推送,但通知类消息不受影响', + title: const Text('确认关闭内容推送吗?'), + content: const Text('若关闭此开关,你将不再收到该账号的图文消息与稿件推送,但通知类消息不受影响'), onConfirm: () => _setPush(isPush), ); return; @@ -132,8 +133,8 @@ class WhisperLinkSettingController extends GetxController { } else { showConfirmDialog( context: Get.context!, - title: '确认拉黑该用户', - content: '加入黑名单后,将自动解除关注关系和对该用户的合集订阅关系,禁止该用户与我互动或查看我的空间', + title: const Text('确认拉黑该用户'), + content: const Text('加入黑名单后,将自动解除关注关系和对该用户的合集订阅关系,禁止该用户与我互动或查看我的空间'), onConfirm: () async { final res = await VideoHttp.relationMod( mid: talkerUid, diff --git a/lib/pages/whisper_link_setting/view.dart b/lib/pages/whisper_link_setting/view.dart index 5696f4d6da..a85f2a550b 100644 --- a/lib/pages/whisper_link_setting/view.dart +++ b/lib/pages/whisper_link_setting/view.dart @@ -125,12 +125,11 @@ class _WhisperLinkSettingPageState extends State { return ListTile( onTap: () => Get.toNamed('/member?mid=${item.mid}'), leading: PendantAvatar( - avatar: item.face, + item.face, size: 42, badgeSize: 14, - isVip: - item.vip?.status != null && item.vip!.status > 0, - garbPendantImage: item.pendant?.image, + vipStatus: item.vip?.status, + pendantImage: item.pendant?.image, officialType: item.official?.type, ), title: Text( diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 779738b903..d87301b00b 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -4,7 +4,7 @@ import 'dart:io' show Platform; import 'dart:math' show max, min; import 'dart:ui' as ui; -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/assets.dart'; import 'package:PiliPlus/harmony_adapt/harmony_channel.dart'; import 'package:PiliPlus/http/browser_ua.dart'; import 'package:PiliPlus/http/constants.dart'; @@ -56,7 +56,7 @@ import 'package:flutter/services.dart' show HapticFeedback; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; import 'package:media_kit/media_kit.dart'; import 'package:media_kit_video/media_kit_video.dart'; import 'package:os_type/os_type.dart'; @@ -414,7 +414,7 @@ class PlPlayerController with BlockConfigMixin { late final showFsLockBtn = Pref.showFsLockBtn; late final keyboardControl = Pref.keyboardControl; - late final bool _autoEnterFullScreen = Pref.autoEnterFullScreen; + late final bool autoEnterFullScreen = Pref.autoEnterFullScreen; late final bool autoExitFullscreen = Pref.autoExitFullscreen; late final bool autoPlayEnable = Pref.autoPlayEnable; late final bool enableVerticalExpand = Pref.enableVerticalExpand; @@ -685,7 +685,7 @@ class PlPlayerController with BlockConfigMixin { // 数据加载完成 dataStatus.value = DataStatus.loaded; - if (autoFullScreenFlag && _autoEnterFullScreen) { + if (autoFullScreenFlag && autoEnterFullScreen) { triggerFullScreen(status: true); } @@ -710,7 +710,7 @@ class PlPlayerController with BlockConfigMixin { return shadersDirPath = await AssetUtils.getOrCopy( 'assets/shaders', - Constants.mpvAnime4KShaders.followedBy(Constants.mpvAnime4KShadersLite), + Assets.mpvAnime4KShaders.followedBy(Assets.mpvAnime4KShadersLite), path.join(appSupportDirPath, 'anime_shaders'), ); } @@ -740,7 +740,7 @@ class PlPlayerController with BlockConfigMixin { 'set', PathUtils.buildShadersAbsolutePath( await copyShadersToExternalDirectory, - Constants.mpvAnime4KShadersLite, + Assets.mpvAnime4KShadersLite, ), ]); case SuperResolutionType.quality: @@ -750,7 +750,7 @@ class PlPlayerController with BlockConfigMixin { 'set', PathUtils.buildShadersAbsolutePath( await copyShadersToExternalDirectory, - Constants.mpvAnime4KShaders, + Assets.mpvAnime4KShaders, ), ]); } @@ -830,10 +830,11 @@ class PlPlayerController with BlockConfigMixin { return; } _videoPlayerController = player; + if (isAnim && superResolutionType.value != .disable) { + await setShader(); + } } - if (isAnim) await setShader(); - final Map extras = {}; String video = dataSource.videoSource; @@ -850,44 +851,35 @@ class PlPlayerController with BlockConfigMixin { audioUri, ); } - } else { - // 修复 Bug:从视频页返回直播间后,声音变成刚才视频的声音。 - // 原因:当前版本改用 NativePlayer.setProperty 直接设置 audio-files, - // 该属性会持久化在复用的 Player 实例上;而 2.0.1 及之前版本是通过 - // Media.extras 传入 audio-files,每次 player.open 都会随新 Media 重置。 - // 当切换到无单独音频源的内容(如直播、关闭听视频)时,必须主动清空 - // audio-files,否则旧视频的外部音轨会继续播放,导致画面与声音不一致。 - player.platform!.maybeAsNativePlayer.setProperty('audio-files', ''); - } - - if (kDebugMode || Platform.isAndroid) { - String audioNormalization = AudioNormalization.getParamFromConfig( - Pref.audioNormalization, - ); - if (volume != null && volume.isNotEmpty) { - audioNormalization = audioNormalization.replaceFirstMapped( - loudnormRegExp, - (i) => - 'loudnorm=${volume.format( - Map.fromEntries( - i.group(1)!.split(':').map((item) { - final parts = item.split('='); - return MapEntry(parts[0].toLowerCase(), num.parse(parts[1])); - }), - ), - )}', + if (kDebugMode || Platform.isAndroid) { + String audioNormalization = AudioNormalization.getParamFromConfig( + Pref.audioNormalization, ); - } else { - audioNormalization = audioNormalization.replaceFirst( - loudnormRegExp, - AudioNormalization.getParamFromConfig(Pref.fallbackNormalization), - ); - } - if (audioNormalization.isNotEmpty) { - extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; + if (volume != null && volume.isNotEmpty) { + audioNormalization = audioNormalization.replaceFirstMapped( + loudnormRegExp, + (i) => + 'loudnorm=${volume.format( + Map.fromEntries( + i.group(1)!.split(':').map((item) { + final parts = item.split('='); + return MapEntry(parts[0].toLowerCase(), num.parse(parts[1])); + }), + ), + )}', + ); + } else { + audioNormalization = audioNormalization.replaceFirst( + loudnormRegExp, + AudioNormalization.getParamFromConfig(Pref.fallbackNormalization), + ); + } + if (audioNormalization.isNotEmpty) { + extras['lavfi-complex'] = '"[aid1] $audioNormalization [ao]"'; + } } } - if (dataSource is FileSource) video = 'file://$video'; + await player.open( Media( video, @@ -902,9 +894,9 @@ class PlPlayerController with BlockConfigMixin { ); } - Future refreshPlayer() async { + Future refreshPlayer() async { if (dataSource is FileSource) { - return true; + return null; } if (_videoPlayerController == null) { // SmartDialog.showToast('视频播放器为空,请重新进入本页面'); @@ -1116,7 +1108,7 @@ class PlPlayerController with BlockConfigMixin { 'controllerStream.error.listen', const Duration(milliseconds: 10000), () { - Future.delayed(const Duration(milliseconds: 3000), () async { + Future.delayed(const Duration(milliseconds: 3000), () { // if (kDebugMode) { // debugPrint("isBuffering.value: ${isBuffering.value}"); // } @@ -1128,9 +1120,7 @@ class PlPlayerController with BlockConfigMixin { '视频链接打开失败,重试中', displayTime: const Duration(milliseconds: 500), ); - if (!await refreshPlayer()) { - if (kDebugMode) debugPrint("failed"); - } + refreshPlayer(); } }); }, @@ -1144,7 +1134,8 @@ class PlPlayerController with BlockConfigMixin { event.startsWith("Can not open")) { return; } - SmartDialog.showToast('视频加载错误, $event'); + Utils.reportError(event); + // SmartDialog.showToast('视频加载错误, $event'); } }), // controllerStream.volume.listen((event) { diff --git a/lib/plugin/pl_player/models/data_source.dart b/lib/plugin/pl_player/models/data_source.dart index 4a1671a1d3..98edc3449f 100644 --- a/lib/plugin/pl_player/models/data_source.dart +++ b/lib/plugin/pl_player/models/data_source.dart @@ -25,6 +25,7 @@ class FileSource extends DataSource { FileSource({ required this.dir, required this.isMp4, + required bool hasDashAudio, required String typeTag, }) : super( videoSource: path.join( @@ -32,7 +33,7 @@ class FileSource extends DataSource { typeTag, isMp4 ? PathUtils.videoNameType1 : PathUtils.videoNameType2, ), - audioSource: isMp4 + audioSource: isMp4 || !hasDashAudio ? null : path.join(dir, typeTag, PathUtils.audioNameType2), ); diff --git a/lib/plugin/pl_player/models/video_fit_type.dart b/lib/plugin/pl_player/models/video_fit_type.dart index 67938c72d0..96b922dd60 100644 --- a/lib/plugin/pl_player/models/video_fit_type.dart +++ b/lib/plugin/pl_player/models/video_fit_type.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:flutter/material.dart' show BoxFit; enum VideoFitType { @@ -10,7 +10,7 @@ enum VideoFitType { none('原始', boxFit: BoxFit.none), scaleDown('限制', boxFit: BoxFit.scaleDown), ratio_4x3('4:3', aspectRatio: 4 / 3), - ratio_16x9('16:9', aspectRatio: StyleString.aspectRatio16x9) + ratio_16x9('16:9', aspectRatio: Style.aspectRatio16x9) ; final String desc; diff --git a/lib/plugin/pl_player/view/view.dart b/lib/plugin/pl_player/view/view.dart index 4a6ada0547..9b00492c87 100644 --- a/lib/plugin/pl_player/view/view.dart +++ b/lib/plugin/pl_player/view/view.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; import 'dart:ui' as ui; import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/cropped_image.dart'; import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/disabled_icon.dart'; @@ -56,6 +57,7 @@ import 'package:PiliPlus/utils/extension/num_ext.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:PiliPlus/utils/image_utils.dart'; +import 'package:PiliPlus/utils/mobile_observer.dart'; import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/platform_utils.dart'; import 'package:PiliPlus/utils/storage.dart'; @@ -213,7 +215,7 @@ class _PLVideoPlayerState extends State @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); + addObserverMobile(this); _controlsListener = plPlayerController.showControls.listen( _onControlChanged, @@ -388,7 +390,7 @@ class _PLVideoPlayerState extends State @override void dispose() { - WidgetsBinding.instance.removeObserver(this); + removeObserverMobile(this); _danmakuListener?.cancel(); _tapGestureRecognizer.dispose(); _longPressRecognizer?.dispose(); diff --git a/lib/plugin/pl_player/view/widgets.dart b/lib/plugin/pl_player/view/widgets.dart index 38c9c04ff1..d1227d719e 100644 --- a/lib/plugin/pl_player/view/widgets.dart +++ b/lib/plugin/pl_player/view/widgets.dart @@ -98,7 +98,7 @@ Widget buildSeekPreviewWidget( final url = data.image[pageIndex]; return ClipRRect( - borderRadius: StyleString.mdRadius, + borderRadius: Style.mdRadius, child: VideoShotImage( url: url, x: x, diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index ce5a1dd3a0..85793a2a57 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -38,6 +38,8 @@ import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_profile/view.dart'; import 'package:PiliPlus/pages/member_search/view.dart'; import 'package:PiliPlus/pages/member_upower_rank/view.dart'; +import 'package:PiliPlus/pages/member_video_web/archive/view.dart'; +import 'package:PiliPlus/pages/member_video_web/season_series/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/at_me/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/like_detail/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/like_me/view.dart'; @@ -195,5 +197,7 @@ class Routes { GetPage(name: '/download', page: () => const DownloadPage()), GetPage(name: '/dlna', page: () => const DLNAPage()), GetPage(name: '/myReply', page: () => const MyReply()), + GetPage(name: '/videoWeb', page: () => const MemberVideoWeb()), + GetPage(name: '/ssWeb', page: () => const MemberSSWeb()), ]; } diff --git a/lib/services/download/download_manager.dart b/lib/services/download/download_manager.dart index 3ec8cbc048..a9aa7466ec 100644 --- a/lib/services/download/download_manager.dart +++ b/lib/services/download/download_manager.dart @@ -58,7 +58,7 @@ class DownloadManager { Response response; try { - response = await Request.dio.get( + response = await Request.http11Dio.get( url.http2https, options: Options( headers: {'range': 'bytes=$received-'}, diff --git a/lib/services/download/download_service.dart b/lib/services/download/download_service.dart index c44a859b84..1d469da0db 100644 --- a/lib/services/download/download_service.dart +++ b/lib/services/download/download_service.dart @@ -138,7 +138,7 @@ class DownloadService extends GetxService { final currentTime = DateTime.now().millisecondsSinceEpoch ~/ 1000; final entry = BiliDownloadEntryInfo( mediaType: 2, - hasDashAudio: true, + hasDashAudio: false, isCompleted: false, totalBytes: 0, downloadedBytes: 0, @@ -206,7 +206,7 @@ class DownloadService extends GetxService { ); final entry = BiliDownloadEntryInfo( mediaType: 2, - hasDashAudio: true, + hasDashAudio: false, isCompleted: false, totalBytes: 0, downloadedBytes: 0, diff --git a/lib/utils/accounts.dart b/lib/utils/accounts.dart index 9232917550..0a4d2cf483 100644 --- a/lib/utils/accounts.dart +++ b/lib/utils/accounts.dart @@ -3,7 +3,7 @@ import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/pages/mine/controller.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/login_utils.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; abstract final class Accounts { static late final Box account; @@ -70,13 +70,13 @@ abstract final class Accounts { // } // } - static Future refresh() async { + static Future refresh() { for (final a in account.values) { for (final t in a.type) { accountMode[t.index] = a; } } - await Future.wait( + return Future.wait( (accountMode.toSet()..removeWhere((i) => i.activated)).map( Request.buvidActive, ), diff --git a/lib/utils/accounts/account.dart b/lib/utils/accounts/account.dart index b1420439c0..031957e954 100644 --- a/lib/utils/accounts/account.dart +++ b/lib/utils/accounts/account.dart @@ -4,7 +4,7 @@ import 'package:PiliPlus/utils/accounts.dart'; import 'package:PiliPlus/utils/accounts/grpc_headers.dart'; import 'package:PiliPlus/utils/id_utils.dart'; import 'package:cookie_jar/cookie_jar.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; sealed class Account { Map? toJson() => null; diff --git a/lib/utils/accounts/account_adapter.dart b/lib/utils/accounts/account_adapter.dart index 2f48733746..6e742b1d20 100644 --- a/lib/utils/accounts/account_adapter.dart +++ b/lib/utils/accounts/account_adapter.dart @@ -1,7 +1,7 @@ import 'package:PiliPlus/models/common/account_type.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:cookie_jar/cookie_jar.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class LoginAccountAdapter extends TypeAdapter { @override diff --git a/lib/utils/accounts/account_type_adapter.dart b/lib/utils/accounts/account_type_adapter.dart index abe1b8aa49..2600108950 100644 --- a/lib/utils/accounts/account_type_adapter.dart +++ b/lib/utils/accounts/account_type_adapter.dart @@ -1,5 +1,5 @@ import 'package:PiliPlus/models/common/account_type.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class AccountTypeAdapter extends TypeAdapter { @override diff --git a/lib/utils/accounts/api_type.dart b/lib/utils/accounts/api_type.dart index 18076e94b8..9551f8b0e9 100644 --- a/lib/utils/accounts/api_type.dart +++ b/lib/utils/accounts/api_type.dart @@ -31,7 +31,8 @@ abstract final class ApiType { Api.onlineTotal, Api.dynamicDetail, Api.aiConclusion, - Api.getSeasonDetailApi, + Api.seasonArchives, + Api.seriesArchives, Api.liveRoomDmToken, Api.liveRoomDmPrefetch, Api.superChatMsg, diff --git a/lib/utils/accounts/cookie_jar_adapter.dart b/lib/utils/accounts/cookie_jar_adapter.dart index b7d87c127b..7762d91e28 100644 --- a/lib/utils/accounts/cookie_jar_adapter.dart +++ b/lib/utils/accounts/cookie_jar_adapter.dart @@ -1,6 +1,6 @@ import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:cookie_jar/cookie_jar.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class BiliCookieJarAdapter extends TypeAdapter { @override diff --git a/lib/utils/extension/box_ext.dart b/lib/utils/extension/box_ext.dart index 64fc6dba43..fca90a9ef3 100644 --- a/lib/utils/extension/box_ext.dart +++ b/lib/utils/extension/box_ext.dart @@ -1,5 +1,5 @@ import 'package:collection/collection.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; extension BoxExt on Box { bool equal(dynamic key, E value) { diff --git a/lib/utils/extension/theme_ext.dart b/lib/utils/extension/theme_ext.dart index d83e3f02e3..87aafcc5a0 100644 --- a/lib/utils/extension/theme_ext.dart +++ b/lib/utils/extension/theme_ext.dart @@ -1,9 +1,14 @@ import 'package:flex_seed_scheme/flex_seed_scheme.dart'; import 'package:flutter/material.dart'; +const _pinkLight = Color(0xFFFF6699); +const _pinkDark = Color(0xFFD44E7D); + extension ColorSchemeExt on ColorScheme { - Color get vipColor => - brightness.isLight ? const Color(0xFFFF6699) : const Color(0xFFD44E7D); + Color get vipColor => brightness.isLight ? _pinkLight : _pinkDark; + + Color get btnColor => + brightness.isLight ? _pinkLight : const Color(0xFF8F0030); Color get freeColor => brightness.isLight ? const Color(0xFFFF7F24) : const Color(0xFFD66011); diff --git a/lib/utils/extension/three_dot_ext.dart b/lib/utils/extension/three_dot_ext.dart index 6affdec988..dbbeead329 100644 --- a/lib/utils/extension/three_dot_ext.dart +++ b/lib/utils/extension/three_dot_ext.dart @@ -47,15 +47,15 @@ extension ThreeDotItemTypeExt on ThreeDotItemType { case ThreeDotItemType.THREE_DOT_ITEM_TYPE_READ_ALL: showConfirmDialog( context: context, - title: '一键已读', - content: '是否清除全部新消息提醒?', + title: const Text('一键已读'), + content: const Text('是否清除全部新消息提醒?'), onConfirm: controller.onClearUnread, ); case ThreeDotItemType.THREE_DOT_ITEM_TYPE_CLEAR_LIST: showConfirmDialog( context: context, - title: '清空列表', - content: '清空后所有消息将被删除,无法恢复', + title: const Text('清空列表'), + content: const Text('清空后所有消息将被删除,无法恢复'), onConfirm: controller.onDeleteList, ); case ThreeDotItemType.THREE_DOT_ITEM_TYPE_MSG_SETTING: diff --git a/lib/utils/global_data.dart b/lib/utils/global_data.dart index d536170f66..ff75d45c7e 100644 --- a/lib/utils/global_data.dart +++ b/lib/utils/global_data.dart @@ -15,6 +15,8 @@ class GlobalData { bool dynamicsWaterfallFlow = Pref.dynamicsWaterfallFlow; + bool showMedal = Pref.showMedal; + // 私有构造函数 GlobalData._(); diff --git a/lib/utils/grid.dart b/lib/utils/grid.dart index b41caa482b..30f178afd6 100644 --- a/lib/utils/grid.dart +++ b/lib/utils/grid.dart @@ -1,7 +1,7 @@ import 'dart:math'; -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -25,7 +25,7 @@ abstract final class Grid { }) => SliverGridDelegateWithExtentAndRatio( mainAxisSpacing: 2, maxCrossAxisExtent: Grid.smallCardWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.2, + childAspectRatio: Style.aspectRatio * 2.2, minHeight: MediaQuery.textScalerOf(context).scale(minHeight), ); } diff --git a/lib/utils/mobile_observer.dart b/lib/utils/mobile_observer.dart new file mode 100644 index 0000000000..a45c0dfd9e --- /dev/null +++ b/lib/utils/mobile_observer.dart @@ -0,0 +1,15 @@ +import 'package:PiliPlus/utils/platform_utils.dart'; +import 'package:flutter/widgets.dart' + show WidgetsBinding, WidgetsBindingObserver; + +void addObserverMobile(WidgetsBindingObserver observer) { + if (PlatformUtils.isMobile) { + WidgetsBinding.instance.addObserver(observer); + } +} + +void removeObserverMobile(WidgetsBindingObserver observer) { + if (PlatformUtils.isMobile) { + WidgetsBinding.instance.removeObserver(observer); + } +} diff --git a/lib/utils/page_utils.dart b/lib/utils/page_utils.dart index 5fb056d699..42cc26eaf1 100644 --- a/lib/utils/page_utils.dart +++ b/lib/utils/page_utils.dart @@ -37,9 +37,6 @@ import 'package:get/get.dart'; import 'package:url_launcher/url_launcher.dart'; abstract final class PageUtils { - static final RouteObserver routeObserver = - RouteObserver(); - static RelativeRect menuPosition(Offset offset) { return RelativeRect.fromLTRB(offset.dx, offset.dy, offset.dx, 0); } @@ -49,6 +46,7 @@ abstract final class PageUtils { required List imgList, int? quality, ValueChanged? onPageChanged, + String tag = '', }) { return Get.key.currentState!.push( HeroDialogRoute( @@ -57,6 +55,7 @@ abstract final class PageUtils { initIndex: initialPage, quality: quality ?? GlobalData().imgQuality, onPageChanged: onPageChanged, + tag: tag, ), ), ); @@ -544,7 +543,7 @@ abstract final class PageUtils { if (off) { Get.offNamed('/liveRoom', arguments: roomId); } else { - Get.toNamed('/liveRoom', arguments: roomId); + PageUtils.toDupNamed('/liveRoom', arguments: roomId); } } diff --git a/lib/utils/request_utils.dart b/lib/utils/request_utils.dart index b5ae32b3b4..214c011e6d 100644 --- a/lib/utils/request_utils.dart +++ b/lib/utils/request_utils.dart @@ -18,6 +18,7 @@ import 'package:PiliPlus/models/dynamics/result.dart'; import 'package:PiliPlus/models/login/model.dart'; import 'package:PiliPlus/models_new/fav/fav_detail/media.dart'; import 'package:PiliPlus/models_new/later/list.dart'; +import 'package:PiliPlus/models_new/relation/data.dart'; import 'package:PiliPlus/pages/common/multi_select/base.dart'; import 'package:PiliPlus/pages/dynamics_tab/controller.dart'; import 'package:PiliPlus/pages/fav_detail/controller.dart' @@ -103,7 +104,7 @@ abstract final class RequestUtils { required dynamic mid, required bool isFollow, required ValueChanged? afterMod, - Map? followStatus, + RelationData? followStatus, }) async { if (mid == null) { return; @@ -122,8 +123,8 @@ abstract final class RequestUtils { res.toast(); } } else { - if (followStatus?['tag'] == null) { - final res = await UserHttp.hasFollow(mid); + if (followStatus?.tag == null) { + final res = await UserHttp.userRelation(mid); if (res case Success(:final response)) { followStatus = response; } else { @@ -133,7 +134,7 @@ abstract final class RequestUtils { } if (context.mounted) { - bool isSpecialFollowed = followStatus!['special'] == 1; + bool isSpecialFollowed = followStatus!.special == 1; String text = isSpecialFollowed ? '移除特别关注' : '加入特别关注'; showDialog( context: context, @@ -194,15 +195,15 @@ abstract final class RequestUtils { ) { return GroupPanel( mid: mid, - tags: followStatus!['tag'], + tags: followStatus!.tag, scrollController: scrollController, ); }, ); }, ); - followStatus!['tag'] = result?.toList(); if (result != null) { + followStatus!.tag = result.toList(); afterMod?.call(result.contains(-10) ? -10 : 2); } }, diff --git a/lib/utils/set_int_adapter.dart b/lib/utils/set_int_adapter.dart index 2179ed85a4..3e4090094e 100644 --- a/lib/utils/set_int_adapter.dart +++ b/lib/utils/set_int_adapter.dart @@ -1,4 +1,4 @@ -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; class SetIntAdapter extends TypeAdapter> { @override diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 79482d14b8..b364d254ba 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -12,7 +12,7 @@ import 'package:PiliPlus/utils/path_utils.dart'; import 'package:PiliPlus/utils/set_int_adapter.dart'; import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/utils.dart'; -import 'package:hive_flutter/hive_flutter.dart'; +import 'package:hive_ce/hive.dart'; import 'package:path/path.dart' as path; abstract final class GStorage { @@ -25,7 +25,7 @@ abstract final class GStorage { static late final Box? reply; static Future init() async { - await Hive.initFlutter(path.join(appSupportDirPath, 'hive')); + Hive.init(path.join(appSupportDirPath, 'hive')); regAdapter(); await Future.wait([ @@ -57,7 +57,7 @@ abstract final class GStorage { Accounts.init(), Hive.openBox( 'watchProgress', - keyComparator: _intStrKeyComparator, + keyComparator: _intStrDescKeyComparator, compactionStrategy: (entries, deletedEntries) { return deletedEntries > 4; }, @@ -67,7 +67,7 @@ abstract final class GStorage { if (Pref.saveReply) { reply = await Hive.openBox( 'reply', - keyComparator: _intStrKeyComparator, + keyComparator: _intStrDescKeyComparator, compactionStrategy: (entries, deletedEntries) { return deletedEntries > 10; }, @@ -147,7 +147,7 @@ abstract final class GStorage { ]); } - static int _intStrKeyComparator(dynamic k1, dynamic k2) { + static int _intStrDescKeyComparator(dynamic k1, dynamic k2) { if (k1 is int) { if (k2 is int) { return k2.compareTo(k1); diff --git a/lib/utils/storage_key.dart b/lib/utils/storage_key.dart index 6eb590846a..d6b1e1559f 100644 --- a/lib/utils/storage_key.dart +++ b/lib/utils/storage_key.dart @@ -104,7 +104,8 @@ abstract final class SettingBoxKey { preInitPlayer = 'preInitPlayer', mainTabBarView = 'mainTabBarView', searchSuggestion = 'searchSuggestion', - showDynDecorate = 'showDynDecorate', + showDecorate = 'showDynDecorate', + showMedal = 'showMedal', enableLivePhoto = 'enableLivePhoto', showSeekPreview = 'showSeekPreview', showDmChart = 'showDmChart', diff --git a/lib/utils/storage_pref.dart b/lib/utils/storage_pref.dart index 37c557d41e..9369957dec 100644 --- a/lib/utils/storage_pref.dart +++ b/lib/utils/storage_pref.dart @@ -43,7 +43,7 @@ import 'package:flex_seed_scheme/flex_seed_scheme.dart' show FlexSchemeVariant; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; import 'package:os_type/os_type.dart'; abstract final class Pref { @@ -480,8 +480,11 @@ abstract final class Pref { static bool get searchSuggestion => _setting.get(SettingBoxKey.searchSuggestion, defaultValue: true); - static bool get showDynDecorate => - _setting.get(SettingBoxKey.showDynDecorate, defaultValue: true); + static bool get showDecorate => + _setting.get(SettingBoxKey.showDecorate, defaultValue: true); + + static bool get showMedal => + _setting.get(SettingBoxKey.showMedal, defaultValue: true); static bool get enableLivePhoto => _setting.get(SettingBoxKey.enableLivePhoto, defaultValue: true); diff --git a/lib/utils/theme_utils.dart b/lib/utils/theme_utils.dart index a91cf4ebc7..49b54ec05a 100644 --- a/lib/utils/theme_utils.dart +++ b/lib/utils/theme_utils.dart @@ -1,4 +1,4 @@ -import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/harmony_adapt/harmony_channel.dart'; import 'package:PiliPlus/main.dart'; import 'package:PiliPlus/utils/extension/theme_ext.dart'; @@ -143,7 +143,7 @@ abstract final class ThemeUtils { bottomSheetTheme: BottomSheetThemeData( backgroundColor: colorScheme.surface, shape: const RoundedRectangleBorder( - borderRadius: StyleString.bottomSheetRadius, + borderRadius: Style.bottomSheetRadius, ), ), // ignore: deprecated_member_use diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 033b77ddde..f9c5dda1a4 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -84,6 +84,10 @@ abstract final class Utils { 0xFF000000 | int.parse(color.substring(1), radix: 16); static Color parseColor(String color) => Color(parseColorInt(color)); + + static Color parseMedalColor(String color) => Color( + int.parse('${color.substring(7)}${color.substring(1, 7)}', radix: 16), + ); static int? _sdkInt; static Future get sdkInt async { diff --git a/lib/utils/waterfall.dart b/lib/utils/waterfall.dart index 78ad40ce21..55fde4762a 100644 --- a/lib/utils/waterfall.dart +++ b/lib/utils/waterfall.dart @@ -1,5 +1,5 @@ -import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/skeleton/dynamic_card.dart'; +import 'package:PiliPlus/common/style.dart'; import 'package:PiliPlus/common/widgets/flutter/sliver_layout_builder.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/grid.dart'; @@ -38,7 +38,7 @@ mixin DynMixin { crossAxisSpacing: 4, mainAxisSpacing: 4, maxCrossAxisExtent: Grid.smallCardWidth * 2, - childAspectRatio: StyleString.aspectRatio, + childAspectRatio: Style.aspectRatio, mainAxisExtent: 50, ); diff --git a/lib/utils/wbi_sign.dart b/lib/utils/wbi_sign.dart index 644d839526..a67719619f 100644 --- a/lib/utils/wbi_sign.dart +++ b/lib/utils/wbi_sign.dart @@ -11,7 +11,7 @@ import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage_key.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:crypto/crypto.dart'; -import 'package:hive/hive.dart'; +import 'package:hive_ce/hive.dart'; abstract final class WbiSign { static Box get _localCache => GStorage.localCache; diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 4789daa6a4..8c279e5e94 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -28,5 +28,13 @@ MainMenu NSPrincipalClass NSApplication + NSLocalNetworkUsageDescription + 需要访问本地网络以发现和连接 DLNA 投屏设备 + NSBonjourServices + + _ssdp._udp + _upnp._tcp + _http._tcp + diff --git a/pubspec.lock b/pubspec.lock index e89609b6b8..b6f948c1c7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "1b0e6a07425a3e460666e88bf1c949ccc7bb0116ad562ce94a1eca60fe820725" + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.flutter-io.cn" source: hosted - version: "103.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "61c04d0c1bfed555c681ea079519933f071a5a026578ff73c4ff0df2d3462e5e" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.flutter-io.cn" source: hosted - version: "13.3.0" + version: "6.4.1" ansicolor: dependency: transitive description: @@ -136,7 +136,7 @@ packages: description: path: "." ref: HEAD - resolved-ref: c01b9bf97ecbab58cd8bfc2af6cd7ab89af2452a + resolved-ref: d8ae3bee1d1ca5b19862e47b66bc8f6e854a7071 url: "https://gitcode.com/CPF-Flutter/fluttertpc_auto_orientation" source: git version: "2.3.1" @@ -144,10 +144,10 @@ packages: dependency: "direct main" description: name: battery_plus - sha256: f14567a9548ac57c010df641acbd35869429b3dc234fec0bb61b01f429285e61 + sha256: "3853aa1e18ea1d15493dd1d3edac05770b4b95c1e691fb85934c5fd3b1ac0fcc" url: "https://pub.flutter-io.cn" source: hosted - version: "7.1.0" + version: "7.1.1" battery_plus_ohos: dependency: "direct main" description: @@ -184,34 +184,50 @@ packages: dependency: transitive description: name: build - sha256: a156715e7cd728130c592f30552575908aae5b100005fbc1f0fb16b3c03a3d10 + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.6" + version: "2.4.1" build_config: dependency: transitive description: name: build_config - sha256: "4070d2a59f8eec34c97c86ceb44403834899075f66e8a9d59706f8e7834f6f71" + sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.0" + version: "1.1.2" build_daemon: dependency: transitive description: name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 url: "https://pub.flutter-io.cn" source: hosted - version: "4.1.1" + version: "4.1.2" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "1523ce62448ebac2c15a8ba5fbad8acac169788658a7dd2a1c2d9c2a9318b9a6" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.flutter-io.cn" source: hosted - version: "2.15.0" + version: "2.4.13" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 + url: "https://pub.flutter-io.cn" + source: hosted + version: "7.3.2" built_collection: dependency: transitive description: @@ -240,10 +256,10 @@ packages: dependency: "direct main" description: name: cached_network_image_ce - sha256: d4701fbbdf508de831e847fd1d49c644ffa6191c6018c416ab6f66bbe1ad5fff + sha256: "9c61372fecd5f6ac6aaa66fa6808b4c303b8574875e853a0916116bf2fae6840" url: "https://pub.flutter-io.cn" source: hosted - version: "4.6.4" + version: "4.9.0" cached_network_image_platform_interface: dependency: transitive description: @@ -342,6 +358,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.2.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.11.1" collection: dependency: "direct main" description: @@ -426,10 +450,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "59d53ef8eaed9d288ed9767618e2b31c4fa0383a127db59d5eb2e737a7638a60" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.flutter-io.cn" source: hosted - version: "3.1.9" + version: "2.3.6" dbus: dependency: transitive description: @@ -443,7 +467,7 @@ packages: description: path: "packages/device_info_plus/device_info_plus" ref: HEAD - resolved-ref: b9b7a5819ac8aa009e0516f98e3aad8d132bd381 + resolved-ref: "44f907d59a3d7c8c1bce7e37893564de3d81f335" url: "https://gitcode.com/CPF-Flutter/flutter_plus_plugins.git" source: git version: "9.1.0" @@ -515,10 +539,10 @@ packages: dependency: transitive description: name: equatable - sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + sha256: "3bce007a596ff8b3119c45d68aaef631272537c03d30e5d4534dd24bf4c5eaa2" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.8" + version: "2.1.0" expandable: dependency: "direct main" description: @@ -539,7 +563,7 @@ packages: dependency: "direct main" description: path: "." - ref: mod + ref: "161cd202c20bf0ba2394e3a86381d58864dc9e4e" resolved-ref: "161cd202c20bf0ba2394e3a86381d58864dc9e4e" url: "https://github.com/bggRGjQaUbCoE/extended_nested_scroll_view.git" source: git @@ -675,8 +699,8 @@ packages: dependency: "direct main" description: path: flutter_inappwebview - ref: "br_v6.1.5_ohos" - resolved-ref: aaebf895abbf824ef295c446f7210e17bebcea02 + ref: "br_v6.1.5_ohos_dev" + resolved-ref: cafd2a41316be804b133ec48b68ddc75ca718a37 url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" source: git version: "6.1.5" @@ -684,8 +708,8 @@ packages: dependency: transitive description: path: flutter_inappwebview_android - ref: aaebf895abbf824ef295c446f7210e17bebcea02 - resolved-ref: aaebf895abbf824ef295c446f7210e17bebcea02 + ref: cafd2a41316be804b133ec48b68ddc75ca718a37 + resolved-ref: cafd2a41316be804b133ec48b68ddc75ca718a37 url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" source: git version: "1.1.3" @@ -702,8 +726,8 @@ packages: dependency: transitive description: path: flutter_inappwebview_ios - ref: aaebf895abbf824ef295c446f7210e17bebcea02 - resolved-ref: aaebf895abbf824ef295c446f7210e17bebcea02 + ref: cafd2a41316be804b133ec48b68ddc75ca718a37 + resolved-ref: cafd2a41316be804b133ec48b68ddc75ca718a37 url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" source: git version: "1.1.2" @@ -711,8 +735,8 @@ packages: dependency: transitive description: path: flutter_inappwebview_macos - ref: aaebf895abbf824ef295c446f7210e17bebcea02 - resolved-ref: aaebf895abbf824ef295c446f7210e17bebcea02 + ref: cafd2a41316be804b133ec48b68ddc75ca718a37 + resolved-ref: cafd2a41316be804b133ec48b68ddc75ca718a37 url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" source: git version: "1.1.2" @@ -720,8 +744,8 @@ packages: dependency: transitive description: path: flutter_inappwebview_ohos - ref: aaebf895abbf824ef295c446f7210e17bebcea02 - resolved-ref: aaebf895abbf824ef295c446f7210e17bebcea02 + ref: cafd2a41316be804b133ec48b68ddc75ca718a37 + resolved-ref: cafd2a41316be804b133ec48b68ddc75ca718a37 url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" source: git version: "1.1.3" @@ -738,8 +762,8 @@ packages: dependency: transitive description: path: flutter_inappwebview_web - ref: aaebf895abbf824ef295c446f7210e17bebcea02 - resolved-ref: aaebf895abbf824ef295c446f7210e17bebcea02 + ref: cafd2a41316be804b133ec48b68ddc75ca718a37 + resolved-ref: cafd2a41316be804b133ec48b68ddc75ca718a37 url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" source: git version: "1.1.2" @@ -768,10 +792,10 @@ packages: dependency: transitive description: name: flutter_mailer - sha256: e1c0b4425e3cc789f8dd914fd08b5da660c3ac46c8ebf3e7db183d39775a8fa6 + sha256: b01486ed1abed30e50748b330082a9287dde50433bcdb2cfe9fd27da32b8a763 url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.2" + version: "3.0.3" flutter_native_splash: dependency: "direct dev" description: @@ -801,10 +825,10 @@ packages: description: path: "." ref: main - resolved-ref: "1dbede0ee22f161ec2c928e1e82f0ae9339a097d" + resolved-ref: "9a8cd4c5a01699be4155d4e328db6e32e84f63de" url: "https://github.com/bggRGjQaUbCoE/flutter_smart_dialog.git" source: git - version: "5.1.0" + version: "5.1.1" flutter_sortable_wrap: dependency: "direct main" description: @@ -856,6 +880,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "10.9.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.0.0" get: dependency: "direct main" description: @@ -898,7 +930,7 @@ packages: source: hosted version: "2.2.0" hive: - dependency: "direct main" + dependency: transitive description: name: hive sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" @@ -906,7 +938,7 @@ packages: source: hosted version: "2.2.3" hive_ce: - dependency: transitive + dependency: "direct main" description: name: hive_ce sha256: "8e9980e68643afb1e765d3af32b47996552a64e190d03faf622cea07c1294418" @@ -925,10 +957,10 @@ packages: dependency: transitive description: name: hooks - sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" + sha256: "03a564c3704524ee0f7fc56fc621e8796cc2eb8c24d1f1a33b34979815b285c4" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.2" + version: "2.1.0" html: dependency: "direct main" description: @@ -982,7 +1014,7 @@ packages: description: path: image_cropper ref: "br_v11.0.0_ohos" - resolved-ref: "8c9100b106d32b5ee00834902d0c3728e2487f25" + resolved-ref: fccfafe991fb4a136a3ef481a2554ff97f7dca3b url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git" source: git version: "11.0.0" @@ -990,8 +1022,8 @@ packages: dependency: transitive description: path: image_cropper_for_web - ref: "8c9100b106d32b5ee00834902d0c3728e2487f25" - resolved-ref: "8c9100b106d32b5ee00834902d0c3728e2487f25" + ref: fccfafe991fb4a136a3ef481a2554ff97f7dca3b + resolved-ref: fccfafe991fb4a136a3ef481a2554ff97f7dca3b url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git" source: git version: "7.0.0" @@ -999,8 +1031,8 @@ packages: dependency: transitive description: path: image_cropper_platform_interface - ref: "8c9100b106d32b5ee00834902d0c3728e2487f25" - resolved-ref: "8c9100b106d32b5ee00834902d0c3728e2487f25" + ref: fccfafe991fb4a136a3ef481a2554ff97f7dca3b + resolved-ref: fccfafe991fb4a136a3ef481a2554ff97f7dca3b url: "https://gitcode.com/CPF-Flutter/fluttertpc_image_cropper.git" source: git version: "8.0.0" @@ -1009,7 +1041,7 @@ packages: description: path: "packages/image_picker/image_picker" ref: "br_image_picker-v1.1.2_ohos" - resolved-ref: "378ac72ebcedcd9ebf327c4778f6c28228880dcb" + resolved-ref: a2d978536b96d347e3732f86b3c7d4ed4fd77182 url: "https://gitcode.com/CPF-Flutter/flutter_packages.git" source: git version: "1.1.2" @@ -1058,7 +1090,7 @@ packages: description: path: "packages/image_picker/image_picker_ohos" ref: "br_image_picker-v1.1.2_ohos" - resolved-ref: "378ac72ebcedcd9ebf327c4778f6c28228880dcb" + resolved-ref: a2d978536b96d347e3732f86b3c7d4ed4fd77182 url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" source: git version: "0.8.7+4" @@ -1239,12 +1271,20 @@ packages: url: "https://github.com/bggRGjQaUbCoE/material_design_icons_flutter.git" source: git version: "7.0.7447" + material_new_shapes: + dependency: "direct main" + description: + name: material_new_shapes + sha256: e4bc375205e187e8fb232573387112dd8c0dd45b03af8aa2b3c79eb4b9e3e0dc + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" media_kit: dependency: "direct main" description: path: media_kit ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.1.11" @@ -1262,7 +1302,7 @@ packages: description: path: "libs/ios/media_kit_libs_ios_video" ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.1.4" @@ -1271,7 +1311,7 @@ packages: description: path: "libs/linux/media_kit_libs_linux" ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.1.3" @@ -1280,7 +1320,7 @@ packages: description: path: "libs/macos/media_kit_libs_macos_video" ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.1.4" @@ -1289,7 +1329,7 @@ packages: description: path: "libs/ohos/media_kit_libs_ohos" ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.0.0" @@ -1298,7 +1338,7 @@ packages: description: path: "libs/universal/media_kit_libs_video" ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.0.5" @@ -1325,7 +1365,7 @@ packages: description: path: media_kit_video ref: HEAD - resolved-ref: "652cfc765a43792be8bec429ea72296d5d96fb7d" + resolved-ref: df5a969e617a1ce038d5cca46bedcfa1970a7dc6 url: "https://github.com/Predidit/media-kit.git" source: git version: "1.2.5" @@ -1341,10 +1381,10 @@ packages: dependency: "direct overridden" description: name: meta - sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" url: "https://pub.flutter-io.cn" source: hosted - version: "1.18.3" + version: "1.19.0" mime: dependency: "direct main" description: @@ -1430,7 +1470,7 @@ packages: description: path: "packages/path_provider/path_provider" ref: HEAD - resolved-ref: "0bc98e19b5c779df108e50ebb8c90554be6bbf88" + resolved-ref: "5c69f5f65ac32631846a6a3ae682f1f91c710cc3" url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" source: git version: "2.1.0" @@ -1463,7 +1503,7 @@ packages: description: path: "packages/path_provider/path_provider_ohos" ref: HEAD - resolved-ref: "0bc98e19b5c779df108e50ebb8c90554be6bbf88" + resolved-ref: "5c69f5f65ac32631846a6a3ae682f1f91c710cc3" url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" source: git version: "2.2.1" @@ -1523,6 +1563,15 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "7.0.2" + pigeon: + dependency: transitive + description: + path: "packages/pigeon" + ref: "81b4f65df86624a1530c56625d1361b451ead6fc" + resolved-ref: "81b4f65df86624a1530c56625d1361b451ead6fc" + url: "https://gitcode.com/openharmony-sig/flutter_packages.git" + source: git + version: "22.4.1" platform: dependency: transitive description: @@ -1623,10 +1672,10 @@ packages: dependency: transitive description: name: record_use - sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + sha256: af37186ff9ede46fa32f152526c48f46c5b3ad7d3d5a566140cb5df3dc4ed737 url: "https://pub.flutter-io.cn" source: hosted - version: "0.6.0" + version: "1.0.0" rxdart: dependency: "direct overridden" description: @@ -1639,10 +1688,10 @@ packages: dependency: transitive description: name: safe_local_storage - sha256: "7483b3d5e8976f0bd263647c03b96131ee8e43f48b56fa8a8ec459e8515d74b0" + sha256: "494b982d5edb71030650ea463d939670e91b232b588323dc75229d2c5f23e7b7" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.4" + version: "2.0.6" saver_gallery: dependency: "direct main" description: @@ -1711,50 +1760,50 @@ packages: dependency: "direct main" description: name: screen_retriever - sha256: "42cc3b402a0f67d2455a0d067553d0f13453f6a008d98eababf8b63958d506bd" + sha256: ace919117a7520c13a50a6259e60c4a0d4cbe98809468792a91b5c5adada2aa6 url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.1" + version: "0.2.2" screen_retriever_linux: dependency: transitive description: name: screen_retriever_linux - sha256: "2a476f1a5538065bc5badf376cfdc83d6ecf07d77eb2391b9c2bff5a76970048" + sha256: "7b52006a5ceae1f3d5af7f77188c3290d6e7d8ded16d99809bea84967c65c257" url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.1" + version: "0.2.2" screen_retriever_macos: dependency: transitive description: name: screen_retriever_macos - sha256: b5abb900fcb86614ff10b738b34e37b9e1d03b0447280668e2bc8a98bdc7bd59 + sha256: a1489b99cce597c45a54b9aae1cd94c8d4705353b7e0bb2457a6e4de44e0ad8a url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.1" + version: "0.2.2" screen_retriever_platform_interface: dependency: transitive description: name: screen_retriever_platform_interface - sha256: "3af22d926bedf20c2caa308eea376776451a3af125919ce072e56525fded8901" + sha256: "94a5535277510a63184ca178ce12a1449bc0b38618879aa1c18bf57369c5064a" url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.1" + version: "0.2.2" screen_retriever_windows: dependency: transitive description: name: screen_retriever_windows - sha256: c44b38a4c4bab34af259180a70a4eee1e29384e7b82e627c9faa68afcdab2e73 + sha256: dafc6922b0bfbf1d48cf3ccbf519b4fff47bdcb820da1728ea6db675fecc9324 url: "https://pub.flutter-io.cn" source: hosted - version: "0.2.1" + version: "0.2.2" sentry: dependency: transitive description: name: sentry - sha256: "2528b3d9550996b1f933270fabd13ad0f22aa05a19b46cb66afd05adcf313441" + sha256: "09c573f98ff6c5e98d527f351f037eb8ea9ff684a6e9e84b2e91357144016254" url: "https://pub.flutter-io.cn" source: hosted - version: "9.23.0" + version: "9.24.0" share_plus: dependency: "direct main" description: @@ -1841,10 +1890,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "2.0.1" shortid: dependency: transitive description: @@ -1971,6 +2020,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.7.10" + timing: + dependency: transitive + description: + name: timing + sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.2" tray_manager: dependency: "direct main" description: @@ -2032,7 +2089,7 @@ packages: description: path: "packages/url_launcher/url_launcher" ref: "br_url_launcher_v6.3.1_ohos" - resolved-ref: "9cf613d22398d9138e8bc7bbf270444518db585e" + resolved-ref: "3cbdcb35cf5d8e4b4dc18bd9081c98c0c1c97df0" url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" source: git version: "6.1.12" @@ -2073,7 +2130,7 @@ packages: description: path: "packages/url_launcher/url_launcher_ohos" ref: "br_url_launcher_v6.3.1_ohos" - resolved-ref: "9cf613d22398d9138e8bc7bbf270444518db585e" + resolved-ref: "3cbdcb35cf5d8e4b4dc18bd9081c98c0c1c97df0" url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" source: git version: "6.0.38" @@ -2105,10 +2162,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" url: "https://pub.flutter-io.cn" source: hosted - version: "4.5.3" + version: "4.6.0" vector_graphics: dependency: transitive description: @@ -2146,7 +2203,7 @@ packages: description: path: "packages/video_player/video_player" ref: "br_video_player-v2.9.2_ohos" - resolved-ref: "8afaaf75836d9ee21bef68b165db7a9dce607774" + resolved-ref: "81b4f65df86624a1530c56625d1361b451ead6fc" url: "https://gitcode.com/openharmony-tpc/flutter_packages.git" source: git version: "2.9.2" @@ -2162,16 +2219,16 @@ packages: dependency: transitive description: name: video_player_avfoundation - sha256: "76097729ef0c976937945afa53f1ca3afa9b50c9a95909ba347bcf93270466fd" + sha256: c238f5f0a26845cd0bcc2956049b63065a4d3c40ddfc22c3414cd170bef7fff9 url: "https://pub.flutter-io.cn" source: hosted - version: "2.10.0" + version: "2.11.0" video_player_ohos: dependency: transitive description: path: "packages/video_player/video_player_ohos" ref: "br_video_player-v2.9.2_ohos" - resolved-ref: "8afaaf75836d9ee21bef68b165db7a9dce607774" + resolved-ref: "81b4f65df86624a1530c56625d1361b451ead6fc" url: "https://gitcode.com/openharmony-sig/flutter_packages.git" source: git version: "1.0.0+1" @@ -2179,10 +2236,10 @@ packages: dependency: transitive description: name: video_player_platform_interface - sha256: e4ae5bc934b528e5b95c5e47be2812860186260cd3eed3ac62f5ed380fdd1613 + sha256: "92c0fbabe20c788e71fd10d26cea998d0d253282e65d145aed0818731cf593ce" url: "https://pub.flutter-io.cn" source: hosted - version: "6.8.0" + version: "6.9.0" video_player_web: dependency: transitive description: @@ -2301,10 +2358,10 @@ packages: description: path: "packages/window_manager" ref: main - resolved-ref: "974ab0a71d37cfca9d9bd5b295d7bd1df80b790b" + resolved-ref: ac535d09a5850279e6591a9bdd285f9a3edeebf8 url: "https://github.com/bggRGjQaUbCoE/window_manager.git" source: git - version: "0.5.1" + version: "0.5.2" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1dd2ffa68d..8d52043155 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. # update when release -version: 2.0.1+5121 +version: 2.0.2+1 environment: sdk: ">=3.11.1" @@ -31,14 +31,14 @@ environment: # versions available, run `flutter pub outdated`. dependencies: os_type: ^0.2.2 - liquid_glass_widgets: ^0.8.2 # 液态玻璃 + liquid_glass_widgets: ^0.8.4 # 液态玻璃 flutter: sdk: flutter flutter_localizations: sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 + cupertino_icons: ^1.0.9 # 动态取色 dynamic_color: ^1.8.1 @@ -50,10 +50,10 @@ dependencies: # 网络 dio: 5.9.2 - cookie_jar: ^4.0.8 + cookie_jar: ^4.0.9 connectivity_plus: ^5.0.2 connectivity_plus_ohos: ^0.0.3 - dio_http2_adapter: ^2.5.3 + dio_http2_adapter: ^2.7.1 # 图片 cached_network_image: ^3.4.1 @@ -62,18 +62,18 @@ dependencies: # QRCode # qr_flutter: ^4.1.0 - pretty_qr_code: ^3.3.0 + pretty_qr_code: ^3.6.0 # 存储 path_provider: ^2.1.5 - hive: ^2.2.3 + hive_ce: ^2.2.3 hive_flutter: ^1.1.0 # 设备信息 device_info_plus: ^10.1.0 # 权限 # permission_handler: ^12.0.0+1 - permission_handler_apple: ^9.4.7 + permission_handler_apple: ^9.4.10 permission_handler_android: ^13.0.1 permission_handler_platform_interface: ^4.3.0 permission_handler_ohos: ^0.0.8 @@ -89,7 +89,7 @@ dependencies: extended_nested_scroll_view: git: url: https://github.com/bggRGjQaUbCoE/extended_nested_scroll_view.git - ref: mod + ref: 161cd202c20bf0ba2394e3a86381d58864dc9e4e # 上拉加载 # loading_more_list: ^7.1.0 # 下拉刷新 @@ -110,7 +110,7 @@ dependencies: # dismissible_page: ^1.0.2 # custom_sliding_segmented_control: ^1.8.4 # 加密 - crypto: ^3.0.6 + crypto: ^3.0.7 encrypt: ^5.0.3 # 视频播放器 @@ -158,7 +158,7 @@ dependencies: # 高帧率 flutter_displaymode: ^0.7.0 # scheme跳转 - app_links: ^6.4.0 + app_links: ^6.4.1 app_links_ohos: ^0.2.0 # 弹幕 # ns_danmaku: @@ -181,18 +181,18 @@ dependencies: ref: 3eefccb759c4ea8114e722f1511fe1dbce70dc9f # html解析 - html: ^0.15.4 + html: ^0.15.6 # html渲染 - flutter_html: ^3.0.0-beta.2 + flutter_html: ^3.0.0 # 极验 - gt3_flutter_plugin: ^0.1.0 - uuid: ^4.5.1 + gt3_flutter_plugin: ^0.1.1 + uuid: ^4.5.3 # scrollable_positioned_list: ^0.3.8 # nil: ^1.1.1 - catcher_2: ^2.1.0 - logger: ^2.5.0 + catcher_2: ^2.1.9 + logger: ^2.7.0 #瀑布流 - waterfall_flow: ^3.1.0 + waterfall_flow: ^3.1.1 #跑马灯 # marquee: ^2.3.0 #富文本 @@ -205,8 +205,8 @@ dependencies: path: packages/chat_bottom_container image_picker: ^1.1.2 intl: ^0.20.2 - archive: ^4.0.0 - flutter_svg: ^2.0.14 + archive: ^4.0.9 + flutter_svg: ^2.3.0 image_cropper: ^11.0.0 #解压直播消息 brotli: ^0.6.0 @@ -214,7 +214,7 @@ dependencies: flex_seed_scheme: ^3.6.1 live_photo_maker: ^0.0.6 fl_chart: ^1.0.0 - synchronized: ^3.3.0 + synchronized: ^3.4.0+1 # document_file_save_plus: ^2.0.0 # webdav_client: ^1.2.2 webdav_client: @@ -234,7 +234,7 @@ dependencies: url: https://github.com/bggRGjQaUbCoE/window_manager.git path: packages/window_manager ref: main - tray_manager: ^0.5.1 + tray_manager: ^0.5.3 # file_picker: ^10.3.3 file_picker: git: @@ -244,9 +244,10 @@ dependencies: git: url: https://github.com/bggRGjQaUbCoE/super_sliver_list.git ref: mod - dlna_dart: ^0.1.0 + dlna_dart: ^0.1.1 battery_plus: ^7.0.0 battery_plus_ohos: ^0.0.3 + material_new_shapes: ^1.0.0 vector_math: any fixnum: any @@ -265,7 +266,7 @@ dependencies: cached_network_image_ce: any dependency_overrides: - meta: ^1.16.0 # 兼容液态玻璃 + meta: ^1.18.3 # 兼容液态玻璃 device_info_plus: git: url: https://gitcode.com/CPF-Flutter/flutter_plus_plugins.git @@ -274,7 +275,7 @@ dependency_overrides: git: url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" path: "flutter_inappwebview" - ref: br_v6.1.5_ohos + ref: br_v6.1.5_ohos_dev flutter_inappwebview_platform_interface: git: url: "https://gitcode.com/CPF-Flutter/flutter_inappwebview.git" @@ -374,7 +375,7 @@ dev_dependencies: flutter_lints: ^2.0.0 flutter_launcher_icons: ^0.14.4 # hive_generator: ^2.0.1 - build_runner: ^2.10.3 + build_runner: 2.4.13 flutter_native_splash: ^2.4.6 flutter_launcher_icons: