From a039f09ec3c0ab2c1b9c3574a254cf3ac9e2cc50 Mon Sep 17 00:00:00 2001
From: RarDog
Date: Fri, 4 Sep 2026 23:43:46 +0300
Subject: [PATCH] feat: Fullscreen 2.0 with instant handoff, screen brightness
gestures, and real-time scrub preview (v3.1.9)
---
README.md | 2 +-
lib/app/app_version.dart | 4 +-
lib/app/changelog.dart | 10 +
.../widgets/post_media_viewer.dart | 436 ++++++++++++------
pubspec.lock | 46 +-
pubspec.yaml | 3 +-
6 files changed, 354 insertions(+), 147 deletions(-)
diff --git a/README.md b/README.md
index 5ee50c9..2ec63e0 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
-
+
diff --git a/lib/app/app_version.dart b/lib/app/app_version.dart
index 3a5f911..5819934 100644
--- a/lib/app/app_version.dart
+++ b/lib/app/app_version.dart
@@ -1,3 +1,3 @@
-const appDisplayVersion = '3.1.8';
-const appBuildNumber = 69;
+const appDisplayVersion = '3.1.9';
+const appBuildNumber = 70;
const appReleaseChannel = 'Stable';
diff --git a/lib/app/changelog.dart b/lib/app/changelog.dart
index f8415c0..7d3a0ff 100644
--- a/lib/app/changelog.dart
+++ b/lib/app/changelog.dart
@@ -1,4 +1,14 @@
const lunarisChangelog = [
+ LunarisChange(
+ version: '3.1.9',
+ title: 'Полноэкранный плеер 2.0: мгновенный переход без задержек, жесты яркости и скраббинг с превью',
+ bullets: [
+ 'Мгновенный переход в полноэкранный режим: убраны промежуточные задержки и наложения текстур, вход и выход происходят без малейших задержек.',
+ 'Двухзонное жестовое управление: левая сторона экрана регулирует яркость подсветки с индикатором солнца, правая сторона — громкость звука.',
+ 'Плавный скраббинг с плавающим таймингом: перемещение по таймлайну видео теперь отображает интерактивную плашку точного времени, а контролы не исчезают во время удержания ползунка.',
+ 'Сброс яркости: при выходе из полноэкранного режима системный уровень яркости экрана аккуратно восстанавливается.',
+ ],
+ ),
LunarisChange(
version: '3.1.8',
title: 'Безупречный полноэкранный плеер: устранение остановки при развертывании и отзывчивые кнопки управления',
diff --git a/lib/features/post/presentation/widgets/post_media_viewer.dart b/lib/features/post/presentation/widgets/post_media_viewer.dart
index b534030..48139e3 100644
--- a/lib/features/post/presentation/widgets/post_media_viewer.dart
+++ b/lib/features/post/presentation/widgets/post_media_viewer.dart
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
+import 'package:screen_brightness/screen_brightness.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../../app/motion.dart';
@@ -578,6 +579,8 @@ class _PostMediaViewerState extends State
.push(
PageRouteBuilder(
opaque: true,
+ transitionDuration: Duration.zero,
+ reverseTransitionDuration: Duration.zero,
pageBuilder: (_, __, ___) => _FullscreenVideoPage(
player: player,
controller: controller,
@@ -596,10 +599,6 @@ class _PostMediaViewerState extends State
unawaited(_applyPlaybackSnapshot(snapshot));
},
),
- transitionsBuilder: (_, animation, __, child) => FadeTransition(
- opacity: animation,
- child: child,
- ),
),
);
if (mounted) setState(() => _inFullscreen = false);
@@ -820,6 +819,9 @@ class _FullscreenVideoPageState extends State<_FullscreenVideoPage> {
@override
void dispose() {
_hideTimer?.cancel();
+ try {
+ ScreenBrightness.instance.resetApplicationScreenBrightness();
+ } catch (_) {}
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setPreferredOrientations(DeviceOrientation.values);
super.dispose();
@@ -1052,11 +1054,23 @@ class _VideoSurfaceState extends State<_VideoSurface> {
double _currentVolume = 100;
Timer? _volumeTimer;
+ bool _showBrightnessIndicator = false;
+ double _currentBrightness = 0.5;
+ Timer? _brightnessTimer;
+
@override
void initState() {
super.initState();
_currentVolume = widget.initialVolume;
widget.player.setVolume(_currentVolume);
+ _initBrightness();
+ }
+
+ Future _initBrightness() async {
+ try {
+ final b = await ScreenBrightness.instance.application;
+ if (mounted) setState(() => _currentBrightness = b);
+ } catch (_) {}
}
@override
@@ -1073,6 +1087,7 @@ class _VideoSurfaceState extends State<_VideoSurface> {
_seekLeftTimer?.cancel();
_seekRightTimer?.cancel();
_volumeTimer?.cancel();
+ _brightnessTimer?.cancel();
super.dispose();
}
@@ -1144,6 +1159,20 @@ class _VideoSurfaceState extends State<_VideoSurface> {
});
}
+ void _adjustBrightness(double deltaY) async {
+ if (_isLocked) return;
+ final newB = (_currentBrightness - deltaY * 0.004).clamp(0.02, 1.0);
+ _currentBrightness = newB;
+ try {
+ await ScreenBrightness.instance.setApplicationScreenBrightness(newB);
+ } catch (_) {}
+ setState(() => _showBrightnessIndicator = true);
+ _brightnessTimer?.cancel();
+ _brightnessTimer = Timer(const Duration(milliseconds: 1100), () {
+ if (mounted) setState(() => _showBrightnessIndicator = false);
+ });
+ }
+
@override
Widget build(BuildContext context) {
final player = widget.player;
@@ -1182,7 +1211,9 @@ class _VideoSurfaceState extends State<_VideoSurface> {
onVerticalDragUpdate: (details) {
final x = details.localPosition.dx;
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);
}
},
@@ -1202,6 +1233,8 @@ class _VideoSurfaceState extends State<_VideoSurface> {
_DoubleTapSeekRipple(isLeft: false, visible: _showRightSeek),
if (_showVolumeIndicator)
_VolumeGestureBadge(volume: _currentVolume),
+ if (_showBrightnessIndicator)
+ _BrightnessGestureBadge(brightness: _currentBrightness),
if (_isSpeedBoosted)
const _SpeedBoostOverlayBadge(),
if (widget.errorMessage != null)
@@ -1781,6 +1814,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 {
const _DoubleTapSeekRipple({
required this.isLeft,
@@ -1840,7 +1935,7 @@ class _DoubleTapSeekRipple extends StatelessWidget {
}
}
-class _VideoControls extends StatelessWidget {
+class _VideoControls extends StatefulWidget {
const _VideoControls({
required this.player,
required this.muted,
@@ -1875,11 +1970,18 @@ class _VideoControls extends StatelessWidget {
final void Function(Duration) onSeekBy;
final VoidCallback? onInteract;
+ @override
+ State<_VideoControls> createState() => _VideoControlsState();
+}
+
+class _VideoControlsState extends State<_VideoControls> {
+ double? _scrubValue;
+
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
- if (isLocked) {
+ if (widget.isLocked) {
return Align(
alignment: Alignment.topLeft,
child: SafeArea(
@@ -1891,7 +1993,7 @@ class _VideoControls extends StatelessWidget {
size: 44,
iconSize: 24,
emphasized: true,
- onPressed: onToggleLock,
+ onPressed: widget.onToggleLock,
),
),
),
@@ -1899,8 +2001,8 @@ class _VideoControls extends StatelessWidget {
}
return StreamBuilder(
- stream: player.stream.rate,
- initialData: player.state.rate,
+ stream: widget.player.stream.rate,
+ initialData: widget.player.state.rate,
builder: (context, rateSnapshot) {
final currentRate = rateSnapshot.data ?? 1.0;
@@ -1922,56 +2024,56 @@ class _VideoControls extends StatelessWidget {
),
),
child: SafeArea(
- top: fullscreen,
+ top: widget.fullscreen,
bottom: false,
- left: fullscreen,
- right: fullscreen,
+ left: widget.fullscreen,
+ right: widget.fullscreen,
child: Row(
children: [
- if (fullscreen) ...[
+ if (widget.fullscreen) ...[
_RoundControlButton(
tooltip: 'Закрыть',
icon: Icons.arrow_back_rounded,
- onPressed: onFullscreen,
+ onPressed: widget.onFullscreen,
),
const SizedBox(width: 8),
],
_RoundControlButton(
tooltip: 'Заблокировать экран',
icon: Icons.lock_outline_rounded,
- onPressed: onToggleLock,
+ onPressed: widget.onToggleLock,
),
const Spacer(),
_SpeedBadgeButton(
currentRate: currentRate,
- onSelected: (newRate) => player.setRate(newRate),
+ onSelected: (newRate) => widget.player.setRate(newRate),
),
const SizedBox(width: 8),
_RoundControlButton(
- tooltip: loopVideo ? 'Выключить повтор' : 'Повтор видео',
- selected: loopVideo,
- icon: loopVideo
+ tooltip: widget.loopVideo ? 'Выключить повтор' : 'Повтор видео',
+ selected: widget.loopVideo,
+ icon: widget.loopVideo
? Icons.repeat_one_on_rounded
: Icons.repeat_one_rounded,
- onPressed: onToggleLoop,
+ onPressed: widget.onToggleLoop,
),
const SizedBox(width: 8),
_RoundControlButton(
- tooltip: coverVideo ? 'Вписать' : 'Заполнить',
- selected: coverVideo,
- icon: coverVideo
+ tooltip: widget.coverVideo ? 'Вписать' : 'Заполнить',
+ selected: widget.coverVideo,
+ icon: widget.coverVideo
? Icons.fit_screen_rounded
: Icons.crop_free_rounded,
- onPressed: onToggleFit,
+ onPressed: widget.onToggleFit,
),
const SizedBox(width: 8),
_RoundControlButton(
- tooltip: muted ? 'Включить звук' : 'Выключить звук',
- selected: !muted,
- icon: muted
+ tooltip: widget.muted ? 'Включить звук' : 'Выключить звук',
+ selected: !widget.muted,
+ icon: widget.muted
? Icons.volume_off_rounded
: Icons.volume_up_rounded,
- onPressed: onToggleMute,
+ onPressed: widget.onToggleMute,
),
],
),
@@ -1982,8 +2084,8 @@ class _VideoControls extends StatelessWidget {
// Center play/pause with quick 10s buttons
Center(
child: StreamBuilder(
- stream: player.stream.playing,
- initialData: player.state.playing,
+ stream: widget.player.stream.playing,
+ initialData: widget.player.state.playing,
builder: (context, playingSnapshot) {
final playing = playingSnapshot.data ?? false;
return Row(
@@ -1995,8 +2097,8 @@ class _VideoControls extends StatelessWidget {
size: 46,
iconSize: 26,
onPressed: () {
- onSeekBy(const Duration(seconds: -10));
- onInteract?.call();
+ widget.onSeekBy(const Duration(seconds: -10));
+ widget.onInteract?.call();
},
),
const SizedBox(width: 32),
@@ -2010,22 +2112,22 @@ class _VideoControls extends StatelessWidget {
emphasized: true,
onPressed: () async {
try {
- if (player.state.playing) {
- await player.pause();
+ if (widget.player.state.playing) {
+ await widget.player.pause();
} else {
- if (player.state.completed) {
- await player.seek(Duration.zero);
+ if (widget.player.state.completed) {
+ await widget.player.seek(Duration.zero);
}
- await player.play();
+ await widget.player.play();
}
} catch (_) {
- if (player.state.playing) {
- await player.pause();
+ if (widget.player.state.playing) {
+ await widget.player.pause();
} else {
- await player.play();
+ await widget.player.play();
}
}
- onInteract?.call();
+ widget.onInteract?.call();
},
),
const SizedBox(width: 32),
@@ -2035,8 +2137,8 @@ class _VideoControls extends StatelessWidget {
size: 46,
iconSize: 26,
onPressed: () {
- onSeekBy(const Duration(seconds: 10));
- onInteract?.call();
+ widget.onSeekBy(const Duration(seconds: 10));
+ widget.onInteract?.call();
},
),
],
@@ -2055,7 +2157,7 @@ class _VideoControls extends StatelessWidget {
14,
8,
14,
- fullscreen ? 10 : 4,
+ widget.fullscreen ? 10 : 4,
),
decoration: const BoxDecoration(
gradient: LinearGradient(
@@ -2066,23 +2168,23 @@ class _VideoControls extends StatelessWidget {
),
child: SafeArea(
top: false,
- bottom: fullscreen,
- left: fullscreen,
- right: fullscreen,
+ bottom: widget.fullscreen,
+ left: widget.fullscreen,
+ right: widget.fullscreen,
child: StreamBuilder(
- stream: player.stream.duration,
- initialData: player.state.duration,
+ stream: widget.player.stream.duration,
+ initialData: widget.player.state.duration,
builder: (context, durationSnapshot) {
final duration = durationSnapshot.data ?? Duration.zero;
return StreamBuilder(
- stream: player.stream.position,
- initialData: player.state.position,
+ stream: widget.player.stream.position,
+ initialData: widget.player.state.position,
builder: (context, positionSnapshot) {
final position =
positionSnapshot.data ?? Duration.zero;
return StreamBuilder(
- stream: player.stream.buffer,
- initialData: player.state.buffer,
+ stream: widget.player.stream.buffer,
+ initialData: widget.player.state.buffer,
builder: (context, bufferSnapshot) {
final buffer =
bufferSnapshot.data ?? Duration.zero;
@@ -2095,99 +2197,153 @@ class _VideoControls extends StatelessWidget {
final bufferMs = buffer.inMilliseconds
.clamp(0, maxMs.toInt())
.toDouble();
+ final displayMs =
+ (_scrubValue ?? valueMs).clamp(0.0, maxMs);
- return Row(
- crossAxisAlignment: CrossAxisAlignment.center,
+ return Column(
+ mainAxisSize: MainAxisSize.min,
children: [
- Text(
- _format(position),
- style: const TextStyle(
- color: Colors.white,
- fontSize: 12,
- fontWeight: FontWeight.w800,
- letterSpacing: 0.3,
+ 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,
+ ),
+ ),
),
- ),
- const SizedBox(width: 4),
- Expanded(
- child: Stack(
- alignment: Alignment.center,
- children: [
- // Buffer progress track
- if (maxMs > 0 && bufferMs > 0)
- Positioned(
- left: 14,
- right: 14,
- child: ClipRRect(
- borderRadius:
- BorderRadius.circular(2),
- child: LinearProgressIndicator(
- value: (bufferMs / maxMs)
- .clamp(0.0, 1.0),
- minHeight: 3.5,
- backgroundColor:
- Colors.transparent,
- valueColor:
- AlwaysStoppedAnimation(
- Colors.white
- .withValues(alpha: 0.28),
+ Row(
+ crossAxisAlignment:
+ CrossAxisAlignment.center,
+ children: [
+ Text(
+ _format(Duration(
+ milliseconds: displayMs.round())),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ fontWeight: FontWeight.w800,
+ letterSpacing: 0.3,
+ ),
+ ),
+ const SizedBox(width: 4),
+ Expanded(
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ // Buffer progress track
+ if (maxMs > 0 && bufferMs > 0)
+ Positioned(
+ left: 14,
+ right: 14,
+ child: ClipRRect(
+ borderRadius:
+ BorderRadius.circular(2),
+ child:
+ LinearProgressIndicator(
+ value: (bufferMs / maxMs)
+ .clamp(0.0, 1.0),
+ minHeight: 3.5,
+ backgroundColor:
+ Colors.transparent,
+ valueColor:
+ AlwaysStoppedAnimation(
+ Colors.white.withValues(
+ alpha: 0.28),
+ ),
+ ),
),
),
- ),
- ),
- SliderTheme(
- data: SliderTheme.of(context).copyWith(
- activeTrackColor: scheme.primary,
- inactiveTrackColor: Colors.white
- .withValues(alpha: 0.2),
- trackHeight: 3.5,
- thumbColor: scheme.primary,
- overlayColor: scheme.primary
- .withValues(alpha: 0.22),
- thumbShape:
- const RoundSliderThumbShape(
- enabledThumbRadius: 6,
- ),
- overlayShape:
- const RoundSliderOverlayShape(
- overlayRadius: 14,
- ),
- ),
- child: Slider(
- value: valueMs,
- max: maxMs,
- onChanged: duration == Duration.zero
- ? null
- : (value) => player.seek(
- Duration(
- milliseconds:
- value.round(),
- ),
+ SliderTheme(
+ data: SliderTheme.of(context)
+ .copyWith(
+ activeTrackColor:
+ scheme.primary,
+ inactiveTrackColor: Colors.white
+ .withValues(alpha: 0.2),
+ trackHeight: 3.5,
+ thumbColor: scheme.primary,
+ overlayColor: scheme.primary
+ .withValues(alpha: 0.22),
+ thumbShape:
+ const RoundSliderThumbShape(
+ enabledThumbRadius: 6,
+ ),
+ overlayShape:
+ const RoundSliderOverlayShape(
+ overlayRadius: 14,
+ ),
+ ),
+ child: Slider(
+ value: displayMs,
+ max: maxMs,
+ onChangeStart: (value) {
+ setState(() =>
+ _scrubValue = value);
+ widget.onInteract?.call();
+ },
+ onChanged: duration ==
+ Duration.zero
+ ? null
+ : (value) {
+ setState(() =>
+ _scrubValue =
+ value);
+ widget.onInteract
+ ?.call();
+ },
+ onChangeEnd: (value) {
+ widget.player.seek(
+ Duration(
+ milliseconds:
+ value.round(),
),
- ),
+ );
+ setState(
+ () => _scrubValue = null);
+ widget.onInteract?.call();
+ },
+ ),
+ ),
+ ],
),
- ],
- ),
- ),
- const SizedBox(width: 4),
- Text(
- _format(duration),
- style: TextStyle(
- color: Colors.white.withValues(alpha: 0.72),
- fontSize: 12,
- fontWeight: FontWeight.w700,
- letterSpacing: 0.3,
- ),
- ),
- const SizedBox(width: 8),
- _RoundControlButton(
- tooltip: fullscreen
- ? 'Выйти из полноэкранного режима'
- : 'Полноэкранный режим',
- icon: fullscreen
- ? Icons.fullscreen_exit_rounded
- : Icons.fullscreen_rounded,
- onPressed: onFullscreen,
+ ),
+ const SizedBox(width: 4),
+ Text(
+ _format(duration),
+ style: TextStyle(
+ color: Colors.white
+ .withValues(alpha: 0.72),
+ fontSize: 12,
+ fontWeight: FontWeight.w700,
+ letterSpacing: 0.3,
+ ),
+ ),
+ const SizedBox(width: 8),
+ _RoundControlButton(
+ tooltip: widget.fullscreen
+ ? 'Выйти из полноэкранного режима'
+ : 'Полноэкранный режим',
+ icon: widget.fullscreen
+ ? Icons.fullscreen_exit_rounded
+ : Icons.fullscreen_rounded,
+ onPressed: widget.onFullscreen,
+ ),
+ ],
),
],
);
diff --git a/pubspec.lock b/pubspec.lock
index 3a4095a..4f73c64 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -895,11 +895,43 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.3"
+ screen_brightness:
+ dependency: "direct main"
+ description:
+ name: screen_brightness
+ sha256: e0edf92c08889e8f493cde291e7c687db2b4a1471f2371c074070b75d7c7d79b
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.11"
screen_brightness_android:
dependency: transitive
description:
name: screen_brightness_android
- sha256: "2df5e0488a22a73271a43d92dbcd80643df6910cf69690faacaf9929ae9ade3b"
+ sha256: "2008ad8e9527cc968f7a4de1ec58b476d495b3c612a149dbd6550c4f046da147"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.6"
+ screen_brightness_ios:
+ dependency: transitive
+ description:
+ name: screen_brightness_ios
+ sha256: "352d355e8523a186ba1e494b74218e5ca96e51975a0630b8ed89fbb7ff609762"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ screen_brightness_macos:
+ dependency: transitive
+ description:
+ name: screen_brightness_macos
+ sha256: b0957237b842d846a84363b69f229b339a8f91faced55041e245b2ebff7b0142
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ screen_brightness_ohos:
+ dependency: transitive
+ description:
+ name: screen_brightness_ohos
+ sha256: "569a2c97909a50894b81487619eb7654f5358443a5af05f840c19261f49c0940"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
@@ -907,10 +939,18 @@ packages:
dependency: transitive
description:
name: screen_brightness_platform_interface
- sha256: "59d50850d6735d677780fc7359c8e997d0ff6df91c8465161c9e617a7b0a11d8"
+ sha256: "2de60c0ba569b898950029cc1f7e9dd72bda44a22beb5054aac331cb6fce2ff2"
url: "https://pub.dev"
source: hosted
- version: "2.1.1"
+ version: "2.1.2"
+ screen_brightness_windows:
+ dependency: transitive
+ description:
+ name: screen_brightness_windows
+ sha256: "368b9c822e1671de81616e48150006e39eff2a434957e47ee638b09a32b2297c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
share_plus:
dependency: "direct main"
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 990e03b..b91fd31 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.8+69
+version: 3.1.9+70
environment:
sdk: ">=3.3.0 <4.0.0"
@@ -33,6 +33,7 @@ dependencies:
media_kit_libs_android_video: ^1.3.8
share_plus: ^10.0.0
dynamic_color: ^1.7.0
+ screen_brightness: ^2.1.11
dev_dependencies:
flutter_test: