Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a88a8b5050 | ||
|
|
a039f09ec3 |
@@ -10,7 +10,7 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/RarDog/Lunaris/releases"><img src="https://img.shields.io/badge/Version-3.1.8-8A2BE2?style=for-the-badge&logo=semver&logoColor=white" alt="Version 3.1.8" /></a>
|
<a href="https://github.com/RarDog/Lunaris/releases"><img src="https://img.shields.io/badge/Version-3.2.0-8A2BE2?style=for-the-badge&logo=semver&logoColor=white" alt="Version 3.2.0" /></a>
|
||||||
<img src="https://img.shields.io/badge/Flutter-3.47+-02569B?style=for-the-badge&logo=flutter&logoColor=white" alt="Flutter" />
|
<img src="https://img.shields.io/badge/Flutter-3.47+-02569B?style=for-the-badge&logo=flutter&logoColor=white" alt="Flutter" />
|
||||||
<img src="https://img.shields.io/badge/Platform-Android%20%7C%20Linux%20%7C%20Windows-00C853?style=for-the-badge" alt="Platforms" />
|
<img src="https://img.shields.io/badge/Platform-Android%20%7C%20Linux%20%7C%20Windows-00C853?style=for-the-badge" alt="Platforms" />
|
||||||
<a href="https://github.com/RarDog/Lunaris/wiki"><img src="https://img.shields.io/badge/Wiki-Documentation-FF6F00?style=for-the-badge&logo=gitbook&logoColor=white" alt="Wiki" /></a>
|
<a href="https://github.com/RarDog/Lunaris/wiki"><img src="https://img.shields.io/badge/Wiki-Documentation-FF6F00?style=for-the-badge&logo=gitbook&logoColor=white" alt="Wiki" /></a>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
const appDisplayVersion = '3.1.8';
|
const appDisplayVersion = '3.2.0';
|
||||||
const appBuildNumber = 69;
|
const appBuildNumber = 71;
|
||||||
const appReleaseChannel = 'Stable';
|
const appReleaseChannel = 'Stable';
|
||||||
|
|||||||
@@ -1,4 +1,24 @@
|
|||||||
const lunarisChangelog = [
|
const lunarisChangelog = [
|
||||||
|
LunarisChange(
|
||||||
|
version: '3.2.0',
|
||||||
|
title: 'Стабильный полноэкранный режим: сохранение воспроизведения, устранение вылетов плеера и чистый интерфейс',
|
||||||
|
bullets: [
|
||||||
|
'Сохранение воспроизведения видео при повороте экрана: исправлена логика адаптивности на смартфонах — альбомная ориентация больше не сбрасывает мобильный интерфейс и не уничтожает запущенный плеер.',
|
||||||
|
'Защита жизненного цикла Player в фоне: плеер не уничтожается при переходе в полный экран, а кнопки «Пуск/Пауза» и перемотки работают мгновенно и безотказно.',
|
||||||
|
'Чистый кинематографичный интерфейс: удалены старые дублирующиеся кнопки в правом верхнем углу; кнопка смены ориентации (альбомный/портретный режим) аккуратно интегрирована в верхнюю панель управления.',
|
||||||
|
'Быстрое скрытие и показ контролов: касание свободной области видео плавно переключает видимость панели управления, а при нажатии на кнопки таймер автоскрытия автоматически обновляется.',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
LunarisChange(
|
||||||
|
version: '3.1.9',
|
||||||
|
title: 'Полноэкранный плеер 2.0: мгновенный переход без задержек, жесты яркости и скраббинг с превью',
|
||||||
|
bullets: [
|
||||||
|
'Мгновенный переход в полноэкранный режим: убраны промежуточные задержки и наложения текстур, вход и выход происходят без малейших задержек.',
|
||||||
|
'Двухзонное жестовое управление: левая сторона экрана регулирует яркость подсветки с индикатором солнца, правая сторона — громкость звука.',
|
||||||
|
'Плавный скраббинг с плавающим таймингом: перемещение по таймлайну видео теперь отображает интерактивную плашку точного времени, а контролы не исчезают во время удержания ползунка.',
|
||||||
|
'Сброс яркости: при выходе из полноэкранного режима системный уровень яркости экрана аккуратно восстанавливается.',
|
||||||
|
],
|
||||||
|
),
|
||||||
LunarisChange(
|
LunarisChange(
|
||||||
version: '3.1.8',
|
version: '3.1.8',
|
||||||
title: 'Безупречный полноэкранный плеер: устранение остановки при развертывании и отзывчивые кнопки управления',
|
title: 'Безупречный полноэкранный плеер: устранение остановки при развертывании и отзывчивые кнопки управления',
|
||||||
|
|||||||
+14
-4
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
class AppBreakpoints {
|
class AppBreakpoints {
|
||||||
@@ -6,11 +8,19 @@ class AppBreakpoints {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Responsive {
|
class Responsive {
|
||||||
static bool isMobile(BuildContext context) =>
|
static bool isMobile(BuildContext context) {
|
||||||
MediaQuery.sizeOf(context).width < AppBreakpoints.mobile;
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
return MediaQuery.sizeOf(context).shortestSide < 600;
|
||||||
|
}
|
||||||
|
return MediaQuery.sizeOf(context).width < AppBreakpoints.mobile;
|
||||||
|
}
|
||||||
|
|
||||||
static bool isDesktop(BuildContext context) =>
|
static bool isDesktop(BuildContext context) {
|
||||||
MediaQuery.sizeOf(context).width >= AppBreakpoints.desktop;
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
return MediaQuery.sizeOf(context).shortestSide >= 600;
|
||||||
|
}
|
||||||
|
return MediaQuery.sizeOf(context).width >= AppBreakpoints.desktop;
|
||||||
|
}
|
||||||
|
|
||||||
static int columnsFor(
|
static int columnsFor(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -142,7 +143,9 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
if (!_inFullscreen) {
|
||||||
_disposeVideo();
|
_disposeVideo();
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,6 +207,7 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
},
|
},
|
||||||
fullscreen: widget.fullscreen,
|
fullscreen: widget.fullscreen,
|
||||||
errorMessage: _videoError,
|
errorMessage: _videoError,
|
||||||
|
onTapSurface: _toggleControls,
|
||||||
onInteract: _showControls,
|
onInteract: _showControls,
|
||||||
onRetry: _retryVideo,
|
onRetry: _retryVideo,
|
||||||
onToggleFit: () {
|
onToggleFit: () {
|
||||||
@@ -552,6 +556,16 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleControls() {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (_controlsVisible) {
|
||||||
|
_hideTimer?.cancel();
|
||||||
|
setState(() => _controlsVisible = false);
|
||||||
|
} else {
|
||||||
|
_showControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _showControls() {
|
void _showControls() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _controlsVisible = true);
|
setState(() => _controlsVisible = true);
|
||||||
@@ -578,6 +592,8 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
.push<VideoPlaybackSnapshot>(
|
.push<VideoPlaybackSnapshot>(
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
opaque: true,
|
opaque: true,
|
||||||
|
transitionDuration: Duration.zero,
|
||||||
|
reverseTransitionDuration: Duration.zero,
|
||||||
pageBuilder: (_, __, ___) => _FullscreenVideoPage(
|
pageBuilder: (_, __, ___) => _FullscreenVideoPage(
|
||||||
player: player,
|
player: player,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
@@ -596,13 +612,14 @@ class _PostMediaViewerState extends State<PostMediaViewer>
|
|||||||
unawaited(_applyPlaybackSnapshot(snapshot));
|
unawaited(_applyPlaybackSnapshot(snapshot));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
transitionsBuilder: (_, animation, __, child) => FadeTransition(
|
|
||||||
opacity: animation,
|
|
||||||
child: child,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (mounted) setState(() => _inFullscreen = false);
|
if (mounted) {
|
||||||
|
setState(() => _inFullscreen = false);
|
||||||
|
} else {
|
||||||
|
player.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
await _applyPlaybackSnapshot(result);
|
await _applyPlaybackSnapshot(result);
|
||||||
if (result.playing && !player.state.playing) {
|
if (result.playing && !player.state.playing) {
|
||||||
@@ -817,11 +834,28 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
|
|||||||
_showControls();
|
_showControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _toggleControls() {
|
||||||
|
if (!mounted) return;
|
||||||
|
if (_controlsVisible) {
|
||||||
|
_hideTimer?.cancel();
|
||||||
|
setState(() => _controlsVisible = false);
|
||||||
|
} else {
|
||||||
|
_showControls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_hideTimer?.cancel();
|
_hideTimer?.cancel();
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||||
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
SystemChrome.setPreferredOrientations([]);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
|
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
|
||||||
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -858,9 +892,6 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
|
|||||||
},
|
},
|
||||||
child: Focus(
|
child: Focus(
|
||||||
autofocus: true,
|
autofocus: true,
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: _VideoSurface(
|
child: _VideoSurface(
|
||||||
player: widget.player,
|
player: widget.player,
|
||||||
controller: widget.controller,
|
controller: widget.controller,
|
||||||
@@ -877,7 +908,10 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
|
|||||||
},
|
},
|
||||||
fullscreen: true,
|
fullscreen: true,
|
||||||
errorMessage: widget.errorMessage,
|
errorMessage: widget.errorMessage,
|
||||||
|
onTapSurface: _toggleControls,
|
||||||
onInteract: _showControls,
|
onInteract: _showControls,
|
||||||
|
isLandscape: _isLandscape,
|
||||||
|
onToggleOrientation: _toggleOrientation,
|
||||||
onRetry: widget.onRetry,
|
onRetry: widget.onRetry,
|
||||||
onToggleFit: () {
|
onToggleFit: () {
|
||||||
setState(() => _coverVideo = !_coverVideo);
|
setState(() => _coverVideo = !_coverVideo);
|
||||||
@@ -913,38 +947,6 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
|
|||||||
).pop(_snapshot()),
|
).pop(_snapshot()),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Positioned(
|
|
||||||
top: 12,
|
|
||||||
right: 12,
|
|
||||||
child: SafeArea(
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
IconButton.filledTonal(
|
|
||||||
tooltip: _isLandscape
|
|
||||||
? 'Портретная ориентация'
|
|
||||||
: 'Альбомная ориентация',
|
|
||||||
onPressed: _toggleOrientation,
|
|
||||||
icon: Icon(_isLandscape
|
|
||||||
? Icons.screen_lock_portrait_rounded
|
|
||||||
: Icons.screen_lock_landscape_rounded),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
IconButton.filledTonal(
|
|
||||||
tooltip: 'Close fullscreen',
|
|
||||||
onPressed: () => Navigator.of(
|
|
||||||
context,
|
|
||||||
rootNavigator: true,
|
|
||||||
).pop(_snapshot()),
|
|
||||||
icon: const Icon(Icons.close_rounded),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1005,6 +1007,7 @@ class _VideoSurface extends StatefulWidget {
|
|||||||
this.onVolumeChanged,
|
this.onVolumeChanged,
|
||||||
required this.fullscreen,
|
required this.fullscreen,
|
||||||
required this.errorMessage,
|
required this.errorMessage,
|
||||||
|
this.onTapSurface,
|
||||||
required this.onInteract,
|
required this.onInteract,
|
||||||
required this.onRetry,
|
required this.onRetry,
|
||||||
required this.onToggleFit,
|
required this.onToggleFit,
|
||||||
@@ -1012,6 +1015,8 @@ class _VideoSurface extends StatefulWidget {
|
|||||||
required this.onToggleHalfVolume,
|
required this.onToggleHalfVolume,
|
||||||
required this.onToggleLoop,
|
required this.onToggleLoop,
|
||||||
required this.onFullscreen,
|
required this.onFullscreen,
|
||||||
|
this.isLandscape,
|
||||||
|
this.onToggleOrientation,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Player player;
|
final Player player;
|
||||||
@@ -1026,6 +1031,7 @@ class _VideoSurface extends StatefulWidget {
|
|||||||
final ValueChanged<double>? onVolumeChanged;
|
final ValueChanged<double>? onVolumeChanged;
|
||||||
final bool fullscreen;
|
final bool fullscreen;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
final VoidCallback? onTapSurface;
|
||||||
final VoidCallback onInteract;
|
final VoidCallback onInteract;
|
||||||
final VoidCallback onRetry;
|
final VoidCallback onRetry;
|
||||||
final VoidCallback onToggleFit;
|
final VoidCallback onToggleFit;
|
||||||
@@ -1033,6 +1039,8 @@ class _VideoSurface extends StatefulWidget {
|
|||||||
final VoidCallback onToggleHalfVolume;
|
final VoidCallback onToggleHalfVolume;
|
||||||
final VoidCallback onToggleLoop;
|
final VoidCallback onToggleLoop;
|
||||||
final VoidCallback onFullscreen;
|
final VoidCallback onFullscreen;
|
||||||
|
final bool? isLandscape;
|
||||||
|
final VoidCallback? onToggleOrientation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_VideoSurface> createState() => _VideoSurfaceState();
|
State<_VideoSurface> createState() => _VideoSurfaceState();
|
||||||
@@ -1052,6 +1060,10 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
double _currentVolume = 100;
|
double _currentVolume = 100;
|
||||||
Timer? _volumeTimer;
|
Timer? _volumeTimer;
|
||||||
|
|
||||||
|
bool _showBrightnessIndicator = false;
|
||||||
|
double _currentBrightness = 1.0;
|
||||||
|
Timer? _brightnessTimer;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -1073,6 +1085,7 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
_seekLeftTimer?.cancel();
|
_seekLeftTimer?.cancel();
|
||||||
_seekRightTimer?.cancel();
|
_seekRightTimer?.cancel();
|
||||||
_volumeTimer?.cancel();
|
_volumeTimer?.cancel();
|
||||||
|
_brightnessTimer?.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1144,6 +1157,19 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _adjustBrightness(double deltaY) {
|
||||||
|
if (_isLocked) return;
|
||||||
|
final newB = (_currentBrightness - deltaY * 0.004).clamp(0.08, 1.0);
|
||||||
|
setState(() {
|
||||||
|
_currentBrightness = newB;
|
||||||
|
_showBrightnessIndicator = true;
|
||||||
|
});
|
||||||
|
_brightnessTimer?.cancel();
|
||||||
|
_brightnessTimer = Timer(const Duration(milliseconds: 1100), () {
|
||||||
|
if (mounted) setState(() => _showBrightnessIndicator = false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final player = widget.player;
|
final player = widget.player;
|
||||||
@@ -1164,11 +1190,21 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
resumeUponEnteringForegroundMode: false,
|
resumeUponEnteringForegroundMode: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (_currentBrightness < 0.99)
|
||||||
|
Positioned.fill(
|
||||||
|
child: IgnorePointer(
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Colors.black.withValues(
|
||||||
|
alpha: (1.0 - _currentBrightness).clamp(0.0, 0.92),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Positioned.fill(
|
Positioned.fill(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTapDown: (details) => lastTapDown = details.localPosition,
|
onTapDown: (details) => lastTapDown = details.localPosition,
|
||||||
onTap: widget.onInteract,
|
onTap: widget.onTapSurface ?? widget.onInteract,
|
||||||
onDoubleTap: () {
|
onDoubleTap: () {
|
||||||
if (lastTapDown != null) {
|
if (lastTapDown != null) {
|
||||||
_onDoubleTapAt(
|
_onDoubleTapAt(
|
||||||
@@ -1182,7 +1218,9 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
onVerticalDragUpdate: (details) {
|
onVerticalDragUpdate: (details) {
|
||||||
final x = details.localPosition.dx;
|
final x = details.localPosition.dx;
|
||||||
final width = MediaQuery.sizeOf(context).width;
|
final width = MediaQuery.sizeOf(context).width;
|
||||||
if (x > width * 0.4) {
|
if (x < width * 0.45) {
|
||||||
|
_adjustBrightness(details.primaryDelta ?? 0.0);
|
||||||
|
} else if (x > width * 0.55) {
|
||||||
_adjustVolume(details.primaryDelta ?? 0.0);
|
_adjustVolume(details.primaryDelta ?? 0.0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1202,6 +1240,8 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
_DoubleTapSeekRipple(isLeft: false, visible: _showRightSeek),
|
_DoubleTapSeekRipple(isLeft: false, visible: _showRightSeek),
|
||||||
if (_showVolumeIndicator)
|
if (_showVolumeIndicator)
|
||||||
_VolumeGestureBadge(volume: _currentVolume),
|
_VolumeGestureBadge(volume: _currentVolume),
|
||||||
|
if (_showBrightnessIndicator)
|
||||||
|
_BrightnessGestureBadge(brightness: _currentBrightness),
|
||||||
if (_isSpeedBoosted)
|
if (_isSpeedBoosted)
|
||||||
const _SpeedBoostOverlayBadge(),
|
const _SpeedBoostOverlayBadge(),
|
||||||
if (widget.errorMessage != null)
|
if (widget.errorMessage != null)
|
||||||
@@ -1226,6 +1266,8 @@ class _VideoSurfaceState extends State<_VideoSurface> {
|
|||||||
coverVideo: widget.coverVideo,
|
coverVideo: widget.coverVideo,
|
||||||
fullscreen: widget.fullscreen,
|
fullscreen: widget.fullscreen,
|
||||||
isLocked: _isLocked,
|
isLocked: _isLocked,
|
||||||
|
isLandscape: widget.isLandscape,
|
||||||
|
onToggleOrientation: widget.onToggleOrientation,
|
||||||
onToggleLock: () => setState(() => _isLocked = !_isLocked),
|
onToggleLock: () => setState(() => _isLocked = !_isLocked),
|
||||||
onToggleFit: widget.onToggleFit,
|
onToggleFit: widget.onToggleFit,
|
||||||
onToggleMute: widget.onToggleMute,
|
onToggleMute: widget.onToggleMute,
|
||||||
@@ -1781,6 +1823,68 @@ class _VolumeGestureBadge extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _BrightnessGestureBadge extends StatelessWidget {
|
||||||
|
const _BrightnessGestureBadge({required this.brightness});
|
||||||
|
|
||||||
|
final double brightness;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final pct = (brightness * 100).round().clamp(0, 100);
|
||||||
|
final icon = pct < 33
|
||||||
|
? Icons.brightness_low_rounded
|
||||||
|
: pct < 66
|
||||||
|
? Icons.brightness_medium_rounded
|
||||||
|
: Icons.brightness_high_rounded;
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 18, vertical: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black.withValues(alpha: 0.8),
|
||||||
|
borderRadius: BorderRadius.circular(22),
|
||||||
|
border: Border.all(color: Colors.white.withValues(alpha: 0.16)),
|
||||||
|
boxShadow: const [
|
||||||
|
BoxShadow(color: Colors.black54, blurRadius: 24),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: Colors.white,
|
||||||
|
size: 26,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
SizedBox(
|
||||||
|
width: 96,
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: brightness.clamp(0.0, 1.0),
|
||||||
|
minHeight: 6,
|
||||||
|
backgroundColor: Colors.white24,
|
||||||
|
valueColor: const AlwaysStoppedAnimation(Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Text(
|
||||||
|
'$pct%',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
fontWeight: FontWeight.w800,
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _DoubleTapSeekRipple extends StatelessWidget {
|
class _DoubleTapSeekRipple extends StatelessWidget {
|
||||||
const _DoubleTapSeekRipple({
|
const _DoubleTapSeekRipple({
|
||||||
required this.isLeft,
|
required this.isLeft,
|
||||||
@@ -1840,7 +1944,7 @@ class _DoubleTapSeekRipple extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _VideoControls extends StatelessWidget {
|
class _VideoControls extends StatefulWidget {
|
||||||
const _VideoControls({
|
const _VideoControls({
|
||||||
required this.player,
|
required this.player,
|
||||||
required this.muted,
|
required this.muted,
|
||||||
@@ -1849,6 +1953,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
required this.coverVideo,
|
required this.coverVideo,
|
||||||
required this.fullscreen,
|
required this.fullscreen,
|
||||||
required this.isLocked,
|
required this.isLocked,
|
||||||
|
this.isLandscape,
|
||||||
|
this.onToggleOrientation,
|
||||||
required this.onToggleLock,
|
required this.onToggleLock,
|
||||||
required this.onToggleFit,
|
required this.onToggleFit,
|
||||||
required this.onToggleMute,
|
required this.onToggleMute,
|
||||||
@@ -1866,6 +1972,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
final bool coverVideo;
|
final bool coverVideo;
|
||||||
final bool fullscreen;
|
final bool fullscreen;
|
||||||
final bool isLocked;
|
final bool isLocked;
|
||||||
|
final bool? isLandscape;
|
||||||
|
final VoidCallback? onToggleOrientation;
|
||||||
final VoidCallback onToggleLock;
|
final VoidCallback onToggleLock;
|
||||||
final VoidCallback onToggleFit;
|
final VoidCallback onToggleFit;
|
||||||
final VoidCallback onToggleMute;
|
final VoidCallback onToggleMute;
|
||||||
@@ -1875,11 +1983,18 @@ class _VideoControls extends StatelessWidget {
|
|||||||
final void Function(Duration) onSeekBy;
|
final void Function(Duration) onSeekBy;
|
||||||
final VoidCallback? onInteract;
|
final VoidCallback? onInteract;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_VideoControls> createState() => _VideoControlsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VideoControlsState extends State<_VideoControls> {
|
||||||
|
double? _scrubValue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final scheme = Theme.of(context).colorScheme;
|
final scheme = Theme.of(context).colorScheme;
|
||||||
|
|
||||||
if (isLocked) {
|
if (widget.isLocked) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
@@ -1891,7 +2006,7 @@ class _VideoControls extends StatelessWidget {
|
|||||||
size: 44,
|
size: 44,
|
||||||
iconSize: 24,
|
iconSize: 24,
|
||||||
emphasized: true,
|
emphasized: true,
|
||||||
onPressed: onToggleLock,
|
onPressed: widget.onToggleLock,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -1899,8 +2014,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return StreamBuilder<double>(
|
return StreamBuilder<double>(
|
||||||
stream: player.stream.rate,
|
stream: widget.player.stream.rate,
|
||||||
initialData: player.state.rate,
|
initialData: widget.player.state.rate,
|
||||||
builder: (context, rateSnapshot) {
|
builder: (context, rateSnapshot) {
|
||||||
final currentRate = rateSnapshot.data ?? 1.0;
|
final currentRate = rateSnapshot.data ?? 1.0;
|
||||||
|
|
||||||
@@ -1922,56 +2037,71 @@ class _VideoControls extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: fullscreen,
|
top: widget.fullscreen,
|
||||||
bottom: false,
|
bottom: false,
|
||||||
left: fullscreen,
|
left: widget.fullscreen,
|
||||||
right: fullscreen,
|
right: widget.fullscreen,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (fullscreen) ...[
|
if (widget.fullscreen) ...[
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
tooltip: 'Закрыть',
|
tooltip: 'Закрыть',
|
||||||
icon: Icons.arrow_back_rounded,
|
icon: Icons.arrow_back_rounded,
|
||||||
onPressed: onFullscreen,
|
onPressed: widget.onFullscreen,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
],
|
||||||
|
if (widget.fullscreen && widget.onToggleOrientation != null) ...[
|
||||||
|
_RoundControlButton(
|
||||||
|
tooltip: (widget.isLandscape ?? false)
|
||||||
|
? 'Портретная ориентация'
|
||||||
|
: 'Альбомная ориентация',
|
||||||
|
icon: (widget.isLandscape ?? false)
|
||||||
|
? Icons.screen_lock_portrait_rounded
|
||||||
|
: Icons.screen_lock_landscape_rounded,
|
||||||
|
onPressed: () {
|
||||||
|
widget.onToggleOrientation?.call();
|
||||||
|
widget.onInteract?.call();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
],
|
],
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
tooltip: 'Заблокировать экран',
|
tooltip: 'Заблокировать экран',
|
||||||
icon: Icons.lock_outline_rounded,
|
icon: Icons.lock_outline_rounded,
|
||||||
onPressed: onToggleLock,
|
onPressed: widget.onToggleLock,
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
_SpeedBadgeButton(
|
_SpeedBadgeButton(
|
||||||
currentRate: currentRate,
|
currentRate: currentRate,
|
||||||
onSelected: (newRate) => player.setRate(newRate),
|
onSelected: (newRate) => widget.player.setRate(newRate),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
tooltip: loopVideo ? 'Выключить повтор' : 'Повтор видео',
|
tooltip: widget.loopVideo ? 'Выключить повтор' : 'Повтор видео',
|
||||||
selected: loopVideo,
|
selected: widget.loopVideo,
|
||||||
icon: loopVideo
|
icon: widget.loopVideo
|
||||||
? Icons.repeat_one_on_rounded
|
? Icons.repeat_one_on_rounded
|
||||||
: Icons.repeat_one_rounded,
|
: Icons.repeat_one_rounded,
|
||||||
onPressed: onToggleLoop,
|
onPressed: widget.onToggleLoop,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
tooltip: coverVideo ? 'Вписать' : 'Заполнить',
|
tooltip: widget.coverVideo ? 'Вписать' : 'Заполнить',
|
||||||
selected: coverVideo,
|
selected: widget.coverVideo,
|
||||||
icon: coverVideo
|
icon: widget.coverVideo
|
||||||
? Icons.fit_screen_rounded
|
? Icons.fit_screen_rounded
|
||||||
: Icons.crop_free_rounded,
|
: Icons.crop_free_rounded,
|
||||||
onPressed: onToggleFit,
|
onPressed: widget.onToggleFit,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
tooltip: muted ? 'Включить звук' : 'Выключить звук',
|
tooltip: widget.muted ? 'Включить звук' : 'Выключить звук',
|
||||||
selected: !muted,
|
selected: !widget.muted,
|
||||||
icon: muted
|
icon: widget.muted
|
||||||
? Icons.volume_off_rounded
|
? Icons.volume_off_rounded
|
||||||
: Icons.volume_up_rounded,
|
: Icons.volume_up_rounded,
|
||||||
onPressed: onToggleMute,
|
onPressed: widget.onToggleMute,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -1982,8 +2112,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
// Center play/pause with quick 10s buttons
|
// Center play/pause with quick 10s buttons
|
||||||
Center(
|
Center(
|
||||||
child: StreamBuilder<bool>(
|
child: StreamBuilder<bool>(
|
||||||
stream: player.stream.playing,
|
stream: widget.player.stream.playing,
|
||||||
initialData: player.state.playing,
|
initialData: widget.player.state.playing,
|
||||||
builder: (context, playingSnapshot) {
|
builder: (context, playingSnapshot) {
|
||||||
final playing = playingSnapshot.data ?? false;
|
final playing = playingSnapshot.data ?? false;
|
||||||
return Row(
|
return Row(
|
||||||
@@ -1995,8 +2125,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
size: 46,
|
size: 46,
|
||||||
iconSize: 26,
|
iconSize: 26,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onSeekBy(const Duration(seconds: -10));
|
widget.onSeekBy(const Duration(seconds: -10));
|
||||||
onInteract?.call();
|
widget.onInteract?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 32),
|
const SizedBox(width: 32),
|
||||||
@@ -2010,22 +2140,20 @@ class _VideoControls extends StatelessWidget {
|
|||||||
emphasized: true,
|
emphasized: true,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
try {
|
try {
|
||||||
if (player.state.playing) {
|
if (widget.player.state.playing) {
|
||||||
await player.pause();
|
await widget.player.pause();
|
||||||
} else {
|
} else {
|
||||||
if (player.state.completed) {
|
if (widget.player.state.completed) {
|
||||||
await player.seek(Duration.zero);
|
await widget.player.seek(Duration.zero);
|
||||||
}
|
}
|
||||||
await player.play();
|
await widget.player.play();
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
if (player.state.playing) {
|
try {
|
||||||
await player.pause();
|
await widget.player.playOrPause();
|
||||||
} else {
|
} catch (_) {}
|
||||||
await player.play();
|
|
||||||
}
|
}
|
||||||
}
|
widget.onInteract?.call();
|
||||||
onInteract?.call();
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 32),
|
const SizedBox(width: 32),
|
||||||
@@ -2035,8 +2163,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
size: 46,
|
size: 46,
|
||||||
iconSize: 26,
|
iconSize: 26,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onSeekBy(const Duration(seconds: 10));
|
widget.onSeekBy(const Duration(seconds: 10));
|
||||||
onInteract?.call();
|
widget.onInteract?.call();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -2055,7 +2183,7 @@ class _VideoControls extends StatelessWidget {
|
|||||||
14,
|
14,
|
||||||
8,
|
8,
|
||||||
14,
|
14,
|
||||||
fullscreen ? 10 : 4,
|
widget.fullscreen ? 10 : 4,
|
||||||
),
|
),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
@@ -2066,23 +2194,23 @@ class _VideoControls extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
bottom: fullscreen,
|
bottom: widget.fullscreen,
|
||||||
left: fullscreen,
|
left: widget.fullscreen,
|
||||||
right: fullscreen,
|
right: widget.fullscreen,
|
||||||
child: StreamBuilder<Duration>(
|
child: StreamBuilder<Duration>(
|
||||||
stream: player.stream.duration,
|
stream: widget.player.stream.duration,
|
||||||
initialData: player.state.duration,
|
initialData: widget.player.state.duration,
|
||||||
builder: (context, durationSnapshot) {
|
builder: (context, durationSnapshot) {
|
||||||
final duration = durationSnapshot.data ?? Duration.zero;
|
final duration = durationSnapshot.data ?? Duration.zero;
|
||||||
return StreamBuilder<Duration>(
|
return StreamBuilder<Duration>(
|
||||||
stream: player.stream.position,
|
stream: widget.player.stream.position,
|
||||||
initialData: player.state.position,
|
initialData: widget.player.state.position,
|
||||||
builder: (context, positionSnapshot) {
|
builder: (context, positionSnapshot) {
|
||||||
final position =
|
final position =
|
||||||
positionSnapshot.data ?? Duration.zero;
|
positionSnapshot.data ?? Duration.zero;
|
||||||
return StreamBuilder<Duration>(
|
return StreamBuilder<Duration>(
|
||||||
stream: player.stream.buffer,
|
stream: widget.player.stream.buffer,
|
||||||
initialData: player.state.buffer,
|
initialData: widget.player.state.buffer,
|
||||||
builder: (context, bufferSnapshot) {
|
builder: (context, bufferSnapshot) {
|
||||||
final buffer =
|
final buffer =
|
||||||
bufferSnapshot.data ?? Duration.zero;
|
bufferSnapshot.data ?? Duration.zero;
|
||||||
@@ -2095,12 +2223,42 @@ class _VideoControls extends StatelessWidget {
|
|||||||
final bufferMs = buffer.inMilliseconds
|
final bufferMs = buffer.inMilliseconds
|
||||||
.clamp(0, maxMs.toInt())
|
.clamp(0, maxMs.toInt())
|
||||||
.toDouble();
|
.toDouble();
|
||||||
|
final displayMs =
|
||||||
|
(_scrubValue ?? valueMs).clamp(0.0, maxMs);
|
||||||
|
|
||||||
return Row(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (_scrubValue != null)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10,
|
||||||
|
vertical: 4,
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(bottom: 6),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black87,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
border: Border.all(
|
||||||
|
color: scheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${_format(Duration(milliseconds: displayMs.round()))} / ${_format(duration)}',
|
||||||
|
style: TextStyle(
|
||||||
|
color: scheme.primary,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
_format(position),
|
_format(Duration(
|
||||||
|
milliseconds: displayMs.round())),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
@@ -2121,7 +2279,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(2),
|
BorderRadius.circular(2),
|
||||||
child: LinearProgressIndicator(
|
child:
|
||||||
|
LinearProgressIndicator(
|
||||||
value: (bufferMs / maxMs)
|
value: (bufferMs / maxMs)
|
||||||
.clamp(0.0, 1.0),
|
.clamp(0.0, 1.0),
|
||||||
minHeight: 3.5,
|
minHeight: 3.5,
|
||||||
@@ -2129,15 +2288,17 @@ class _VideoControls extends StatelessWidget {
|
|||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
valueColor:
|
valueColor:
|
||||||
AlwaysStoppedAnimation(
|
AlwaysStoppedAnimation(
|
||||||
Colors.white
|
Colors.white.withValues(
|
||||||
.withValues(alpha: 0.28),
|
alpha: 0.28),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliderTheme(
|
SliderTheme(
|
||||||
data: SliderTheme.of(context).copyWith(
|
data: SliderTheme.of(context)
|
||||||
activeTrackColor: scheme.primary,
|
.copyWith(
|
||||||
|
activeTrackColor:
|
||||||
|
scheme.primary,
|
||||||
inactiveTrackColor: Colors.white
|
inactiveTrackColor: Colors.white
|
||||||
.withValues(alpha: 0.2),
|
.withValues(alpha: 0.2),
|
||||||
trackHeight: 3.5,
|
trackHeight: 3.5,
|
||||||
@@ -2154,16 +2315,34 @@ class _VideoControls extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Slider(
|
child: Slider(
|
||||||
value: valueMs,
|
value: displayMs,
|
||||||
max: maxMs,
|
max: maxMs,
|
||||||
onChanged: duration == Duration.zero
|
onChangeStart: (value) {
|
||||||
|
setState(() =>
|
||||||
|
_scrubValue = value);
|
||||||
|
widget.onInteract?.call();
|
||||||
|
},
|
||||||
|
onChanged: duration ==
|
||||||
|
Duration.zero
|
||||||
? null
|
? null
|
||||||
: (value) => player.seek(
|
: (value) {
|
||||||
|
setState(() =>
|
||||||
|
_scrubValue =
|
||||||
|
value);
|
||||||
|
widget.onInteract
|
||||||
|
?.call();
|
||||||
|
},
|
||||||
|
onChangeEnd: (value) {
|
||||||
|
widget.player.seek(
|
||||||
Duration(
|
Duration(
|
||||||
milliseconds:
|
milliseconds:
|
||||||
value.round(),
|
value.round(),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
|
setState(
|
||||||
|
() => _scrubValue = null);
|
||||||
|
widget.onInteract?.call();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -2173,7 +2352,8 @@ class _VideoControls extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
_format(duration),
|
_format(duration),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white.withValues(alpha: 0.72),
|
color: Colors.white
|
||||||
|
.withValues(alpha: 0.72),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
fontWeight: FontWeight.w700,
|
fontWeight: FontWeight.w700,
|
||||||
letterSpacing: 0.3,
|
letterSpacing: 0.3,
|
||||||
@@ -2181,13 +2361,15 @@ class _VideoControls extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
_RoundControlButton(
|
_RoundControlButton(
|
||||||
tooltip: fullscreen
|
tooltip: widget.fullscreen
|
||||||
? 'Выйти из полноэкранного режима'
|
? 'Выйти из полноэкранного режима'
|
||||||
: 'Полноэкранный режим',
|
: 'Полноэкранный режим',
|
||||||
icon: fullscreen
|
icon: widget.fullscreen
|
||||||
? Icons.fullscreen_exit_rounded
|
? Icons.fullscreen_exit_rounded
|
||||||
: Icons.fullscreen_rounded,
|
: Icons.fullscreen_rounded,
|
||||||
onPressed: onFullscreen,
|
onPressed: widget.onFullscreen,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
+4
-4
@@ -899,18 +899,18 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: screen_brightness_android
|
name: screen_brightness_android
|
||||||
sha256: "2df5e0488a22a73271a43d92dbcd80643df6910cf69690faacaf9929ae9ade3b"
|
sha256: "2008ad8e9527cc968f7a4de1ec58b476d495b3c612a149dbd6550c4f046da147"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.6"
|
||||||
screen_brightness_platform_interface:
|
screen_brightness_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: screen_brightness_platform_interface
|
name: screen_brightness_platform_interface
|
||||||
sha256: "59d50850d6735d677780fc7359c8e997d0ff6df91c8465161c9e617a7b0a11d8"
|
sha256: "2de60c0ba569b898950029cc1f7e9dd72bda44a22beb5054aac331cb6fce2ff2"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
+1
-2
@@ -1,7 +1,7 @@
|
|||||||
name: gel_rule_app
|
name: gel_rule_app
|
||||||
description: Local backend/core layer for a Flutter booru browser app.
|
description: Local backend/core layer for a Flutter booru browser app.
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 3.1.8+69
|
version: 3.2.0+71
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.3.0 <4.0.0"
|
sdk: ">=3.3.0 <4.0.0"
|
||||||
@@ -33,7 +33,6 @@ dependencies:
|
|||||||
media_kit_libs_android_video: ^1.3.8
|
media_kit_libs_android_video: ^1.3.8
|
||||||
share_plus: ^10.0.0
|
share_plus: ^10.0.0
|
||||||
dynamic_color: ^1.7.0
|
dynamic_color: ^1.7.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user