From c7f507836be780c15fb0881ff31219936424b00e Mon Sep 17 00:00:00 2001 From: RarDog Date: Fri, 4 Sep 2026 23:33:42 +0300 Subject: [PATCH] fix: fullscreen playback state retention and responsive control buttons (v3.1.8) --- README.md | 2 +- lib/app/app_version.dart | 4 +- lib/app/changelog.dart | 10 ++ .../widgets/post_media_viewer.dart | 144 +++++++++++------- lib/shared/widgets/post_card.dart | 2 + pubspec.yaml | 2 +- 6 files changed, 103 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 1ae3770..5ee50c9 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

- Version 3.1.7 + Version 3.1.8 Flutter Platforms Wiki diff --git a/lib/app/app_version.dart b/lib/app/app_version.dart index f847632..3a5f911 100644 --- a/lib/app/app_version.dart +++ b/lib/app/app_version.dart @@ -1,3 +1,3 @@ -const appDisplayVersion = '3.1.7'; -const appBuildNumber = 68; +const appDisplayVersion = '3.1.8'; +const appBuildNumber = 69; const appReleaseChannel = 'Stable'; diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart index 9f3be11..f8415c0 100644 --- a/lib/app/changelog.dart +++ b/lib/app/changelog.dart @@ -1,4 +1,14 @@ const lunarisChangelog = [ + LunarisChange( + version: '3.1.8', + title: 'Безупречный полноэкранный плеер: устранение остановки при развертывании и отзывчивые кнопки управления', + bullets: [ + 'Непрерывное воспроизведение при открытии на весь экран: видео больше не останавливается самопроизвольно при входе в полноэкранный режим или смене ориентации устройства.', + 'Корректный статус воспроизведения: устранен сброс кнопки на «Пуск» при переходе в полный экран — видео продолжает воспроизведение без ложных остановок.', + 'Мгновенный отклик кнопок «Пуск/Пауза» и перемотки: жесты управления поверхностью (двойной тап, смахивание громкости) перемещены в фоновый слой, исключая перехват и отмену нажатий по кнопкам управления.', + 'Синхронизация состояния при сворачивании: плеер точно запоминает состояние паузы/воспроизведения при выходе из полноэкранного режима обратно в пост.', + ], + ), LunarisChange( version: '3.1.7', title: 'Устранение критического сбоя при открытии видеопоста (SIGSEGV в libmpv)', diff --git a/lib/features/post/presentation/widgets/post_media_viewer.dart b/lib/features/post/presentation/widgets/post_media_viewer.dart index a9bf3f5..b534030 100644 --- a/lib/features/post/presentation/widgets/post_media_viewer.dart +++ b/lib/features/post/presentation/widgets/post_media_viewer.dart @@ -571,6 +571,7 @@ class _PostMediaViewerState extends State final player = _player; final controller = _controller; if (player == null || controller == null) return; + final wasPlaying = player.state.playing; setState(() => _inFullscreen = true); if (!context.mounted) return; final result = await Navigator.of(context, rootNavigator: true) @@ -580,6 +581,7 @@ class _PostMediaViewerState extends State pageBuilder: (_, __, ___) => _FullscreenVideoPage( player: player, controller: controller, + wasPlaying: wasPlaying, aspectRatio: widget.post.width > 0 && widget.post.height > 0 ? widget.post.width / widget.post.height : 16 / 9, @@ -603,6 +605,11 @@ class _PostMediaViewerState extends State if (mounted) setState(() => _inFullscreen = false); if (result != null) { await _applyPlaybackSnapshot(result); + if (result.playing && !player.state.playing) { + await player.play(); + } else if (!result.playing && player.state.playing) { + await player.pause(); + } return; } } @@ -729,6 +736,7 @@ class _FullscreenVideoPage extends StatefulWidget { const _FullscreenVideoPage({ required this.player, required this.controller, + required this.wasPlaying, required this.aspectRatio, required this.loopVideo, required this.muted, @@ -742,6 +750,7 @@ class _FullscreenVideoPage extends StatefulWidget { final Player player; final VideoController controller; + final bool wasPlaying; final double aspectRatio; final bool loopVideo; final bool muted; @@ -779,6 +788,13 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky); _applyOrientation(); _scheduleControlsHide(); + if (widget.wasPlaying) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && !widget.player.state.playing) { + widget.player.play(); + } + }); + } } void _applyOrientation() { @@ -1131,6 +1147,7 @@ class _VideoSurfaceState extends State<_VideoSurface> { @override Widget build(BuildContext context) { final player = widget.player; + Offset? lastTapDown; final child = LayoutBuilder( builder: (context, constraints) { @@ -1143,6 +1160,32 @@ class _VideoSurfaceState extends State<_VideoSurface> { controller: widget.controller, fit: widget.coverVideo ? BoxFit.cover : BoxFit.contain, controls: null, + pauseUponEnteringBackgroundMode: false, + resumeUponEnteringForegroundMode: false, + ), + ), + Positioned.fill( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) => lastTapDown = details.localPosition, + onTap: widget.onInteract, + onDoubleTap: () { + if (lastTapDown != null) { + _onDoubleTapAt( + lastTapDown!, + MediaQuery.sizeOf(context).width, + ); + } + }, + onLongPressStart: (_) => _startSpeedBoost(), + onLongPressEnd: (_) => _stopSpeedBoost(), + onVerticalDragUpdate: (details) { + final x = details.localPosition.dx; + final width = MediaQuery.sizeOf(context).width; + if (x > width * 0.4) { + _adjustVolume(details.primaryDelta ?? 0.0); + } + }, ), ), StreamBuilder( @@ -1199,36 +1242,16 @@ class _VideoSurfaceState extends State<_VideoSurface> { }, ); - Offset? lastTapDown; - return MouseRegion( onHover: (_) => widget.onInteract(), - child: GestureDetector( - onTapDown: (details) => lastTapDown = details.localPosition, - onTap: widget.onInteract, - onDoubleTap: () { - if (lastTapDown != null) { - _onDoubleTapAt(lastTapDown!, MediaQuery.sizeOf(context).width); - } - }, - onLongPressStart: (_) => _startSpeedBoost(), - onLongPressEnd: (_) => _stopSpeedBoost(), - onVerticalDragUpdate: (details) { - final x = details.localPosition.dx; - final width = MediaQuery.sizeOf(context).width; - if (x > width * 0.4) { - _adjustVolume(details.primaryDelta ?? 0.0); - } - }, - child: ClipRRect( - borderRadius: BorderRadius.circular(widget.fullscreen ? 0 : 10), - child: widget.fullscreen - ? SizedBox.expand(child: child) - : AspectRatio( - aspectRatio: widget.aspectRatio.clamp(0.35, 2.4), - child: child, - ), - ), + child: ClipRRect( + borderRadius: BorderRadius.circular(widget.fullscreen ? 0 : 10), + child: widget.fullscreen + ? SizedBox.expand(child: child) + : AspectRatio( + aspectRatio: widget.aspectRatio.clamp(0.35, 2.4), + child: child, + ), ), ); } @@ -1546,32 +1569,37 @@ class _RoundControlButton extends StatelessWidget { : Colors.white; return Tooltip( message: tooltip, - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: onPressed, - child: AnimatedContainer( - duration: AppMotion.duration(context, 140), - width: size, - height: size, - decoration: BoxDecoration( - color: background, - shape: BoxShape.circle, - border: Border.all( - color: selected - ? scheme.primary.withValues(alpha: 0.4) - : Colors.white.withValues(alpha: 0.14), + child: Material( + color: Colors.transparent, + shape: const CircleBorder(), + child: InkResponse( + containedInkWell: true, + highlightShape: BoxShape.circle, + onTap: onPressed, + child: AnimatedContainer( + duration: AppMotion.duration(context, 140), + width: size, + height: size, + decoration: BoxDecoration( + color: background, + shape: BoxShape.circle, + border: Border.all( + color: selected + ? scheme.primary.withValues(alpha: 0.4) + : Colors.white.withValues(alpha: 0.14), + ), + boxShadow: [ + if (emphasized) + BoxShadow( + color: scheme.primary.withValues(alpha: 0.45), + blurRadius: 28, + spreadRadius: 2, + ), + ], + ), + child: Center( + child: Icon(icon, color: foreground, size: iconSize), ), - boxShadow: [ - if (emphasized) - BoxShadow( - color: scheme.primary.withValues(alpha: 0.45), - blurRadius: 28, - spreadRadius: 2, - ), - ], - ), - child: Center( - child: Icon(icon, color: foreground, size: iconSize), ), ), ), @@ -1982,11 +2010,13 @@ class _VideoControls extends StatelessWidget { emphasized: true, onPressed: () async { try { - if (player.state.completed) { - await player.seek(Duration.zero); - await player.play(); + if (player.state.playing) { + await player.pause(); } else { - await player.playOrPause(); + if (player.state.completed) { + await player.seek(Duration.zero); + } + await player.play(); } } catch (_) { if (player.state.playing) { diff --git a/lib/shared/widgets/post_card.dart b/lib/shared/widgets/post_card.dart index 12e1f32..2e979f1 100644 --- a/lib/shared/widgets/post_card.dart +++ b/lib/shared/widgets/post_card.dart @@ -596,6 +596,8 @@ class _FeedVideoPreviewState extends State<_FeedVideoPreview> { controller: _controller, fit: BoxFit.cover, controls: null, + pauseUponEnteringBackgroundMode: false, + resumeUponEnteringForegroundMode: false, ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index ecb8c24..990e03b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: gel_rule_app description: Local backend/core layer for a Flutter booru browser app. publish_to: "none" -version: 3.1.7+68 +version: 3.1.8+69 environment: sdk: ">=3.3.0 <4.0.0"