diff --git a/lib/common/widgets/interactiveviewer_gallery/interactive_viewer.dart b/lib/common/widgets/interactiveviewer_gallery/interactive_viewer.dart index 876223034..98f5ad76d 100644 --- a/lib/common/widgets/interactiveviewer_gallery/interactive_viewer.dart +++ b/lib/common/widgets/interactiveviewer_gallery/interactive_viewer.dart @@ -79,6 +79,8 @@ class InteractiveViewer extends StatefulWidget { this.onPanStart, this.onPanUpdate, this.onPanEnd, + this.onReset, + this.isAnimating, required Widget this.child, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), @@ -127,6 +129,8 @@ class InteractiveViewer extends StatefulWidget { this.onPanStart, this.onPanUpdate, this.onPanEnd, + this.onReset, + this.isAnimating, required InteractiveViewerWidgetBuilder this.builder, }) : assert(minScale > 0), assert(interactionEndFrictionCoefficient > 0), @@ -147,6 +151,8 @@ class InteractiveViewer extends StatefulWidget { constrained = false, child = null; + final Function? isAnimating; + final VoidCallback? onReset; final ValueChanged? onPanStart; final ValueChanged? onPanUpdate; final ValueChanged? onPanEnd; @@ -755,8 +761,9 @@ class _InteractiveViewerState extends State // Handle the start of a gesture. All of pan, scale, and rotate are handled // with GestureDetector's scale gesture. void _onScaleStart(ScaleStartDetails details) { - if (details.pointerCount < 2 && - _transformationController?.value.row0.x == 1.0) { + if (widget.isAnimating?.call() == true || + (details.pointerCount < 2 && + _transformationController?.value.row0.x == 1.0)) { widget.onPanStart?.call(details); return; } @@ -788,8 +795,9 @@ class _InteractiveViewerState extends State // Handle an update to an ongoing gesture. All of pan, scale, and rotate are // handled with GestureDetector's scale gesture. void _onScaleUpdate(ScaleUpdateDetails details) { - if (details.pointerCount < 2 && - _transformationController?.value.row0.x == 1.0) { + if (widget.isAnimating?.call() == true || + (details.pointerCount < 2 && + _transformationController?.value.row0.x == 1.0)) { widget.onPanUpdate?.call(details); return; } @@ -892,8 +900,12 @@ class _InteractiveViewerState extends State // Handle the end of a gesture of _GestureType. All of pan, scale, and rotate // are handled with GestureDetector's scale gesture. void _onScaleEnd(ScaleEndDetails details) { - if (details.pointerCount < 2 && - _transformationController?.value.row0.x == 1.0) { + if (_transformationController?.value.row0.x == 1.0) { + widget.onReset?.call(); + } + if (widget.isAnimating?.call() == true || + (details.pointerCount < 2 && + _transformationController?.value.row0.x == 1.0)) { widget.onPanEnd?.call(details); return; } diff --git a/lib/common/widgets/interactiveviewer_gallery/interactive_viewer_boundary.dart b/lib/common/widgets/interactiveviewer_gallery/interactive_viewer_boundary.dart index 4dd591f2e..512af5d79 100644 --- a/lib/common/widgets/interactiveviewer_gallery/interactive_viewer_boundary.dart +++ b/lib/common/widgets/interactiveviewer_gallery/interactive_viewer_boundary.dart @@ -25,9 +25,11 @@ class InteractiveViewerBoundary extends StatefulWidget { required this.maxScale, required this.minScale, this.onDismissed, + this.onReset, this.dismissThreshold = 0.2, }); + final VoidCallback? onReset; final double dismissThreshold; final VoidCallback? onDismissed; @@ -230,6 +232,8 @@ class InteractiveViewerBoundaryState extends State onPanStart: _handleDragStart, onPanUpdate: _handleDragUpdate, onPanEnd: _handleDragEnd, + onReset: widget.onReset, + isAnimating: () => _animateController.value != 0, child: content, ); } diff --git a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart index ac5948164..05a2baf7d 100644 --- a/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart +++ b/lib/common/widgets/interactiveviewer_gallery/interactiveviewer_gallery.dart @@ -6,7 +6,6 @@ import 'package:PiliPalaX/utils/utils.dart'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:dio/dio.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'; import 'package:path_provider/path_provider.dart'; @@ -224,6 +223,13 @@ class _InteractiveviewerGalleryState extends State Get.back(); widget.onDismissed?.call(_pageController!.page!.floor()); }, + onReset: () { + if (!_enablePageView) { + setState(() { + _enablePageView = true; + }); + } + }, child: PageView.builder( onPageChanged: _onPageChanged, controller: _pageController, diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index bbf2be585..0851cb067 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -67,7 +67,7 @@ class NetworkImgLayer extends StatelessWidget { child: Builder( builder: (context) => CachedNetworkImage( imageUrl: - '${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}', + '${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}', width: width, height: ignoreHeight == null || ignoreHeight == false ? height diff --git a/lib/http/user.dart b/lib/http/user.dart index 1d0207589..6474a716b 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -384,7 +384,7 @@ class UserHttp { 'pn': pn, 'platform': 'web', }); - if (res.data['code'] == 0) { + if (res.data['code'] == 0 && res.data['data'] is Map) { return { 'status': true, 'data': SubFolderModelData.fromJson(res.data['data']) diff --git a/lib/pages/setting/widgets/switch_item.dart b/lib/pages/setting/widgets/switch_item.dart index d0e144400..00bdfe7a8 100644 --- a/lib/pages/setting/widgets/switch_item.dart +++ b/lib/pages/setting/widgets/switch_item.dart @@ -11,6 +11,8 @@ class SetSwitchItem extends StatefulWidget { final bool? needReboot; final Widget? leading; final GestureTapCallback? onTap; + final EdgeInsetsGeometry? contentPadding; + final TextStyle? titleStyle; const SetSwitchItem({ this.title, @@ -21,6 +23,8 @@ class SetSwitchItem extends StatefulWidget { this.needReboot, this.leading, this.onTap, + this.contentPadding, + this.titleStyle, super.key, }); @@ -45,7 +49,7 @@ class _SetSwitchItemState extends State { // Utils.checkUpdate(); // } widget.onChanged?.call(val); - if (widget.needReboot != null && widget.needReboot!) { + if (widget.needReboot == true) { SmartDialog.showToast('重启生效'); } setState(() {}); @@ -53,15 +57,18 @@ class _SetSwitchItemState extends State { @override Widget build(BuildContext context) { - TextStyle titleStyle = Theme.of(context).textTheme.titleMedium!.copyWith( - color: widget.onTap != null && !val - ? Theme.of(context).colorScheme.outline - : null); + TextStyle titleStyle = widget.titleStyle ?? + Theme.of(context).textTheme.titleMedium!.copyWith( + color: widget.onTap != null && !val + ? Theme.of(context).colorScheme.outline + : null, + ); TextStyle subTitleStyle = Theme.of(context) .textTheme .labelMedium! .copyWith(color: Theme.of(context).colorScheme.outline); return ListTile( + contentPadding: widget.contentPadding, enabled: widget.onTap != null ? val : true, enableFeedback: true, onTap: () => diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 1fce5f649..f0c8dbcc7 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -48,6 +48,7 @@ class _SubPageState extends State { future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { + // TODO: refactor Map? data = snapshot.data; if (data != null && data['status']) { return Obx(() => CustomScrollView( diff --git a/lib/pages/subscription_detail/view.dart b/lib/pages/subscription_detail/view.dart index a21b7ddf6..52285d068 100644 --- a/lib/pages/subscription_detail/view.dart +++ b/lib/pages/subscription_detail/view.dart @@ -202,6 +202,15 @@ class _SubDetailPageState extends State { future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { + // TODO: refactor + if (snapshot.data is! Map) { + return HttpError( + callback: () => setState(() { + _futureBuilderFuture = + _subDetailController.queryUserSubFolderDetail(); + }), + ); + } Map data = snapshot.data; if (data['status']) { if (_subDetailController.item.mediaCount == 0) { diff --git a/lib/pages/video/detail/widgets/header_control.dart b/lib/pages/video/detail/widgets/header_control.dart index 2233fa96d..a42fa22a6 100644 --- a/lib/pages/video/detail/widgets/header_control.dart +++ b/lib/pages/video/detail/widgets/header_control.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; +import 'package:PiliPalaX/pages/setting/widgets/switch_item.dart'; import 'package:PiliPalaX/utils/id_utils.dart'; import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:floating/floating.dart'; @@ -916,6 +917,7 @@ class _HeaderControlState extends State { double strokeWidth = widget.controller!.strokeWidth; // 字体粗细 int fontWeight = widget.controller!.fontWeight; + bool massiveMode = widget.controller!.massiveMode; final DanmakuController danmakuController = widget.controller!.danmakuController!; @@ -1041,7 +1043,7 @@ class _HeaderControlState extends State { ), const Text('显示区域'), Padding( - padding: const EdgeInsets.only(top: 12, bottom: 18), + padding: const EdgeInsets.only(top: 12), child: Row( children: [ for (final Map i in showAreas) ...[ @@ -1066,6 +1068,23 @@ class _HeaderControlState extends State { ], ), ), + SetSwitchItem( + title: '海量弹幕', + contentPadding: EdgeInsets.all(0), + titleStyle: TextStyle(fontSize: 14), + defaultVal: massiveMode, + setKey: SettingBoxKey.danmakuMassiveMode, + onChanged: (value) { + massiveMode = value; + widget.controller!.massiveMode = value; + setState(() {}); + try { + danmakuController.updateOption( + danmakuController.option.copyWith(massiveMode: value), + ); + } catch (_) {} + }, + ), Text('不透明度 ${opacityVal * 100}%'), Padding( padding: const EdgeInsets.only( @@ -1261,6 +1280,47 @@ class _HeaderControlState extends State { ), ), ), + Text('弹幕时长 $danmakuDurationVal 秒'), + Padding( + padding: const EdgeInsets.only( + top: 0, + bottom: 6, + left: 10, + right: 10, + ), + child: SliderTheme( + data: SliderThemeData( + trackShape: MSliderTrackShape(), + thumbColor: Theme.of(context).colorScheme.primary, + activeTrackColor: Theme.of(context).colorScheme.primary, + trackHeight: 10, + thumbShape: const RoundSliderThumbShape( + enabledThumbRadius: 6.0), + ), + child: Slider( + min: 1, + max: 4, + value: pow(danmakuDurationVal, 1 / 4) as double, + divisions: 60, + label: danmakuDurationVal.toString(), + onChanged: (double val) { + danmakuDurationVal = + (pow(val, 4) as double).toPrecision(2); + widget.controller!.danmakuDurationVal = + danmakuDurationVal; + widget.controller?.putDanmakuSettings(); + setState(() {}); + try { + danmakuController.updateOption( + danmakuController.option.copyWith( + duration: danmakuDurationVal ~/ + widget.controller!.playbackSpeed), + ); + } catch (_) {} + }, + ), + ), + ), Text( '字幕字体大小 ${(subtitleFontScale * 100).toStringAsFixed(1)}%'), Padding( @@ -1331,47 +1391,6 @@ class _HeaderControlState extends State { ), ), ), - Text('弹幕时长 $danmakuDurationVal 秒'), - Padding( - padding: const EdgeInsets.only( - top: 0, - bottom: 6, - left: 10, - right: 10, - ), - child: SliderTheme( - data: SliderThemeData( - trackShape: MSliderTrackShape(), - thumbColor: Theme.of(context).colorScheme.primary, - activeTrackColor: Theme.of(context).colorScheme.primary, - trackHeight: 10, - thumbShape: const RoundSliderThumbShape( - enabledThumbRadius: 6.0), - ), - child: Slider( - min: 1, - max: 4, - value: pow(danmakuDurationVal, 1 / 4) as double, - divisions: 60, - label: danmakuDurationVal.toString(), - onChanged: (double val) { - danmakuDurationVal = - (pow(val, 4) as double).toPrecision(2); - widget.controller!.danmakuDurationVal = - danmakuDurationVal; - widget.controller?.putDanmakuSettings(); - setState(() {}); - try { - danmakuController.updateOption( - danmakuController.option.copyWith( - duration: danmakuDurationVal ~/ - widget.controller!.playbackSpeed), - ); - } catch (_) {} - }, - ), - ), - ), ], ), ), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 37bfc8d24..916579a3a 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -250,6 +250,7 @@ class PlPlayerController { late double fontSizeFSVal; late double strokeWidth; late int fontWeight; + late bool massiveMode; late double danmakuDurationVal; late List speedList; double? defaultDuration; @@ -352,6 +353,7 @@ class PlPlayerController { fontSizeFSVal = GStorage.danmakuFontScaleFS; subtitleFontScale.value = GStorage.subtitleFontScale; subtitleFontScaleFS.value = GStorage.subtitleFontScaleFS; + massiveMode = GStorage.danmakuMassiveMode; // 弹幕时间 danmakuDurationVal = setting.get(SettingBoxKey.danmakuDuration, defaultValue: 7.29); diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 51734eb1c..8c86e0bbb 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -1,6 +1,5 @@ import 'dart:convert'; import 'dart:io'; -import 'dart:ui'; import 'package:PiliPalaX/common/widgets/pair.dart'; import 'package:PiliPalaX/http/constants.dart'; import 'package:PiliPalaX/models/common/theme_type.dart'; @@ -99,6 +98,9 @@ class GStorage { static double get danmakuFontScaleFS => setting.get(SettingBoxKey.danmakuFontScaleFS, defaultValue: 1.2); + static bool get danmakuMassiveMode => + setting.get(SettingBoxKey.danmakuMassiveMode, defaultValue: false); + static double get subtitleFontScale => setting.get(SettingBoxKey.subtitleFontScale, defaultValue: 1.0); @@ -323,6 +325,7 @@ class SettingBoxKey { danmakuFontScale = 'danmakuFontScale', danmakuFontScaleFS = 'danmakuFontScaleFS', danmakuDuration = 'danmakuDuration', + danmakuMassiveMode = 'danmakuMassiveMode', strokeWidth = 'strokeWidth', fontWeight = 'fontWeight', memberTab = 'memberTab',