feature(web): Added full-screen button and volume slider (#50)

## Pull Request Description

Adds the full screen toggle to web and the volume slider.
fix: small fixes for desktop padding
fix: only reload widgets when the content has changed

## Issue Being Fixed

Issue Number: #28

---------

Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
PartyDonut 2024-10-19 17:07:23 +02:00 committed by GitHub
parent da9e0423c8
commit 8e2ce7861b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 359 additions and 250 deletions

View file

@ -1,9 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:fladder/models/settings/video_player_settings.dart'; import 'package:fladder/models/settings/video_player_settings.dart';
import 'package:fladder/providers/shared_provider.dart'; import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:screen_brightness/screen_brightness.dart';
final videoPlayerSettingsProvider = final videoPlayerSettingsProvider =
StateNotifierProvider<VideoPlayerSettingsProviderNotifier, VideoPlayerSettingsModel>((ref) { StateNotifierProvider<VideoPlayerSettingsProviderNotifier, VideoPlayerSettingsModel>((ref) {
@ -51,7 +53,14 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
void setFitType(BoxFit? value) => state = state.copyWith(videoFit: value); void setFitType(BoxFit? value) => state = state.copyWith(videoFit: value);
void setVolume(double value) => state = state.copyWith(internalVolume: value); void setVolume(double value) {
state = state.copyWith(internalVolume: value);
void steppedVolume(int i) => state = state.copyWith(internalVolume: (state.volume + i).clamp(0, 100)); ref.read(videoPlayerProvider).setVolume(value);
}
void steppedVolume(int i) {
final value = (state.volume + i).clamp(0, 100).toDouble();
state = state.copyWith(internalVolume: value);
ref.read(videoPlayerProvider).setVolume(value);
}
} }

View file

@ -1,14 +1,15 @@
import 'dart:async'; import 'dart:async';
import 'package:fladder/models/media_playback_model.dart'; import 'package:flutter/foundation.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/screens/video_player/video_player_controls.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:media_kit_video/media_kit_video.dart'; import 'package:media_kit_video/media_kit_video.dart';
import 'package:fladder/models/media_playback_model.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/video_player_provider.dart'; import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/screens/video_player/video_player_controls.dart';
import 'package:fladder/util/adaptive_layout.dart'; import 'package:fladder/util/adaptive_layout.dart';
import 'package:fladder/util/themes_data.dart'; import 'package:fladder/util/themes_data.dart';
@ -28,7 +29,7 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
//Don't pause on desktop focus loss //Don't pause on desktop focus loss
if (!AdaptiveLayout.of(context).isDesktop) { if (!(AdaptiveLayout.of(context).isDesktop || kIsWeb)) {
switch (state) { switch (state) {
case AppLifecycleState.resumed: case AppLifecycleState.resumed:
if (playing) ref.read(videoPlayerProvider).play(); if (playing) ref.read(videoPlayerProvider).play();
@ -62,14 +63,11 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final playerProvider = ref.watch(videoPlayerProvider); final fillScreen = ref.watch(videoPlayerSettingsProvider.select((value) => value.fillScreen));
ref.listen(videoPlayerSettingsProvider.select((value) => value.volume), (previous, next) { final videoFit = ref.watch(videoPlayerSettingsProvider.select((value) => value.videoFit));
playerProvider.setVolume(next);
});
final videoPlayerSettings = ref.watch(videoPlayerSettingsProvider);
final padding = MediaQuery.of(context).padding; final padding = MediaQuery.of(context).padding;
final playerController = playerProvider.controller; final playerController = ref.watch(videoPlayerProvider.select((value) => value.controller));
return Material( return Material(
color: Colors.black, color: Colors.black,
@ -95,20 +93,16 @@ class _VideoPlayerState extends ConsumerState<VideoPlayer> with WidgetsBindingOb
children: [ children: [
if (playerController != null) if (playerController != null)
Padding( Padding(
padding: videoPlayerSettings.fillScreen padding: fillScreen ? EdgeInsets.zero : EdgeInsets.only(left: padding.left, right: padding.right),
? EdgeInsets.zero
: EdgeInsets.only(left: padding.left, right: padding.right),
child: OrientationBuilder(builder: (context, orientation) { child: OrientationBuilder(builder: (context, orientation) {
return Video( return Video(
key: Key("$videoPlayerSettings|$orientation"), key: Key(orientation.toString()),
controller: playerController, controller: playerController,
fill: Colors.transparent, fill: Colors.transparent,
wakelock: true, wakelock: true,
fit: videoPlayerSettings.fillScreen fit: fillScreen
? (MediaQuery.of(context).orientation == Orientation.portrait ? (MediaQuery.of(context).orientation == Orientation.portrait ? videoFit : BoxFit.cover)
? videoPlayerSettings.videoFit : videoFit,
: BoxFit.cover)
: videoPlayerSettings.videoFit,
subtitleViewConfiguration: const SubtitleViewConfiguration(visible: false), subtitleViewConfiguration: const SubtitleViewConfiguration(visible: false),
controls: NoVideoControls, controls: NoVideoControls,
); );

View file

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@ -11,6 +10,7 @@ import 'package:collection/collection.dart';
import 'package:ficonsax/ficonsax.dart'; import 'package:ficonsax/ficonsax.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:screen_brightness/screen_brightness.dart'; import 'package:screen_brightness/screen_brightness.dart';
import 'package:universal_html/html.dart' as html;
import 'package:window_manager/window_manager.dart'; import 'package:window_manager/window_manager.dart';
import 'package:fladder/models/items/intro_skip_model.dart'; import 'package:fladder/models/items/intro_skip_model.dart';
@ -31,6 +31,8 @@ import 'package:fladder/util/duration_extensions.dart';
import 'package:fladder/util/list_padding.dart'; import 'package:fladder/util/list_padding.dart';
import 'package:fladder/util/localization_helper.dart'; import 'package:fladder/util/localization_helper.dart';
import 'package:fladder/util/string_extensions.dart'; import 'package:fladder/util/string_extensions.dart';
import 'package:fladder/widgets/shared/full_screen_button.dart'
if (dart.library.html) 'package:fladder/widgets/shared/full_screen_button_web.dart';
class DesktopControls extends ConsumerStatefulWidget { class DesktopControls extends ConsumerStatefulWidget {
const DesktopControls({super.key}); const DesktopControls({super.key});
@ -50,20 +52,13 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mediaPlayback = ref.watch(mediaPlaybackProvider);
final introSkipModel = ref.watch(playBackModel.select((value) => value?.introSkipModel)); final introSkipModel = ref.watch(playBackModel.select((value) => value?.introSkipModel));
final player = ref.watch(videoPlayerProvider); final player = ref.watch(videoPlayerProvider.select((value) => value.controller));
bool showIntroSkipButton = introSkipModel?.introInRange(mediaPlayback.position) ?? false;
bool showCreditSkipButton = introSkipModel?.creditsInRange(mediaPlayback.position) ?? false;
if (AdaptiveLayout.of(context).isDesktop) { if (AdaptiveLayout.of(context).isDesktop) {
focusNode.requestFocus(); focusNode.requestFocus();
} }
return Listener( return Listener(
onPointerSignal: (event) { onPointerSignal: (event) => resetTimer(),
log('Timer reset');
resetTimer();
},
child: PopScope( child: PopScope(
canPop: false, canPop: false,
onPopInvokedWithResult: (didPop, result) { onPopInvokedWithResult: (didPop, result) {
@ -75,6 +70,9 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
focusNode: focusNode, focusNode: focusNode,
autofocus: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer, autofocus: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer,
onKeyEvent: (value) { onKeyEvent: (value) {
final position = ref.read(mediaPlaybackProvider).position;
bool showIntroSkipButton = introSkipModel?.introInRange(position) ?? false;
bool showCreditSkipButton = introSkipModel?.creditsInRange(position) ?? false;
if (value is KeyRepeatEvent) {} if (value is KeyRepeatEvent) {}
if (value is KeyDownEvent) { if (value is KeyDownEvent) {
if (value.logicalKey == LogicalKeyboardKey.keyS) { if (value.logicalKey == LogicalKeyboardKey.keyS) {
@ -92,10 +90,10 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
ref.read(videoPlayerProvider).playOrPause(); ref.read(videoPlayerProvider).playOrPause();
} }
if (value.logicalKey == LogicalKeyboardKey.arrowLeft) { if (value.logicalKey == LogicalKeyboardKey.arrowLeft) {
seekBack(mediaPlayback); seekBack(ref);
} }
if (value.logicalKey == LogicalKeyboardKey.arrowRight) { if (value.logicalKey == LogicalKeyboardKey.arrowRight) {
seekForward(mediaPlayback); seekForward(ref);
} }
if (value.logicalKey == LogicalKeyboardKey.keyF) { if (value.logicalKey == LogicalKeyboardKey.keyF) {
toggleFullScreen(); toggleFullScreen();
@ -122,13 +120,18 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
onHover: AdaptiveLayout.of(context).isDesktop || kIsWeb ? (event) => toggleOverlay(value: true) : null, onHover: AdaptiveLayout.of(context).isDesktop || kIsWeb ? (event) => toggleOverlay(value: true) : null,
child: Stack( child: Stack(
children: [ children: [
if (player.controller != null) if (player != null)
VideoSubtitles( VideoSubtitles(
key: const Key('subtitles'), key: const Key('subtitles'),
controller: player.controller!, controller: player,
overlayed: showOverlay, overlayed: showOverlay,
), ),
if (AdaptiveLayout.of(context).isDesktop) playButton(mediaPlayback), if (AdaptiveLayout.of(context).isDesktop)
Consumer(builder: (context, ref, child) {
final playing = ref.watch(mediaPlaybackProvider.select((value) => value.playing));
final buffering = ref.watch(mediaPlaybackProvider.select((value) => value.buffering));
return playButton(playing, buffering);
}),
IgnorePointer( IgnorePointer(
ignoring: !showOverlay, ignoring: !showOverlay,
child: AnimatedOpacity( child: AnimatedOpacity(
@ -138,11 +141,18 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
children: [ children: [
topButtons(context), topButtons(context),
const Spacer(), const Spacer(),
bottomButtons(context, mediaPlayback), bottomButtons(context),
], ],
), ),
), ),
), ),
Consumer(
builder: (context, ref, child) {
final position = ref.watch(mediaPlaybackProvider.select((value) => value.position));
bool showIntroSkipButton = introSkipModel?.introInRange(position) ?? false;
bool showCreditSkipButton = introSkipModel?.creditsInRange(position) ?? false;
return Stack(
children: [
if (showIntroSkipButton) if (showIntroSkipButton)
Align( Align(
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
@ -166,6 +176,10 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
), ),
) )
], ],
);
},
),
],
), ),
), ),
), ),
@ -174,14 +188,14 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
); );
} }
Widget playButton(MediaPlaybackModel mediaPlayback) { Widget playButton(bool playing, bool buffering) {
return Align( return Align(
alignment: Alignment.center, alignment: Alignment.center,
child: AnimatedScale( child: AnimatedScale(
curve: Curves.easeInOutCubicEmphasized, curve: Curves.easeInOutCubicEmphasized,
scale: mediaPlayback.playing scale: playing
? 0 ? 0
: mediaPlayback.buffering : buffering
? 0 ? 0
: 1, : 1,
duration: const Duration(milliseconds: 250), duration: const Duration(milliseconds: 250),
@ -211,14 +225,11 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
child: Stack( child: Stack(
children: [ children: [
if (AdaptiveLayout.of(context).isDesktop) if (AdaptiveLayout.of(context).isDesktop)
const Flexible( const Align(
child: Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: DefaultTitleBar(), child: DefaultTitleBar(),
), ),
), Padding(
Flexible(
child: Padding(
padding: MediaQuery.paddingOf(context).copyWith(bottom: 0), padding: MediaQuery.paddingOf(context).copyWith(bottom: 0),
child: Container( child: Container(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
@ -251,13 +262,14 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
), ),
), ),
), ),
),
], ],
), ),
); );
} }
Widget bottomButtons(BuildContext context, MediaPlaybackModel mediaPlayback) { Widget bottomButtons(BuildContext context) {
return Consumer(builder: (context, ref, child) {
final mediaPlayback = ref.watch(mediaPlaybackProvider);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: LinearGradient( gradient: LinearGradient(
@ -335,7 +347,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
), ),
), ),
previousButton, previousButton,
seekBackwardButton(mediaPlayback), seekBackwardButton(ref),
IconButton.filledTonal( IconButton.filledTonal(
iconSize: 38, iconSize: 38,
onPressed: () { onPressed: () {
@ -345,7 +357,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
mediaPlayback.playing ? IconsaxBold.pause : IconsaxBold.play, mediaPlayback.playing ? IconsaxBold.pause : IconsaxBold.play,
), ),
), ),
seekForwardButton(mediaPlayback), seekForwardButton(ref),
nextVideoButton, nextVideoButton,
Flexible( Flexible(
flex: 2, flex: 2,
@ -356,7 +368,8 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
message: "Stop", message: "Stop",
child: IconButton(onPressed: () => closePlayer(), icon: const Icon(IconsaxOutline.stop))), child: IconButton(onPressed: () => closePlayer(), icon: const Icon(IconsaxOutline.stop))),
const Spacer(), const Spacer(),
if (AdaptiveLayout.of(context).isDesktop && ref.read(videoPlayerProvider).player != null) ...{ if ((AdaptiveLayout.of(context).isDesktop || kIsWeb) &&
ref.read(videoPlayerProvider).player != null) ...{
// OpenQueueButton(x), // OpenQueueButton(x),
// ChapterButton( // ChapterButton(
// position: position, // position: position,
@ -376,18 +389,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
onChanged: () => resetTimer(), onChanged: () => resetTimer(),
), ),
), ),
FutureBuilder( const FullScreenButton(),
future: windowManager.isFullScreen(),
builder: (context, snapshot) {
final isFullScreen = snapshot.data ?? true;
return IconButton(
onPressed: () => windowManager.setFullScreen(!isFullScreen),
icon: Icon(
isFullScreen ? IconsaxOutline.close_square : IconsaxOutline.maximize_4,
),
);
},
),
} }
].addInBetween(const SizedBox(width: 8)), ].addInBetween(const SizedBox(width: 8)),
), ),
@ -398,6 +400,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
), ),
), ),
); );
});
} }
Widget progressBar(MediaPlaybackModel mediaPlayback) { Widget progressBar(MediaPlaybackModel mediaPlayback) {
@ -534,9 +537,9 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
); );
} }
Widget seekBackwardButton(MediaPlaybackModel mediaPlaybackModel) { Widget seekBackwardButton(WidgetRef ref) {
return IconButton( return IconButton(
onPressed: () => seekBack(mediaPlaybackModel), onPressed: () => seekBack(ref),
tooltip: "-10", tooltip: "-10",
iconSize: 40, iconSize: 40,
icon: const Icon( icon: const Icon(
@ -545,9 +548,9 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
); );
} }
Widget seekForwardButton(MediaPlaybackModel mediaPlaybackModel) { Widget seekForwardButton(WidgetRef ref) {
return IconButton( return IconButton(
onPressed: () => seekForward(mediaPlaybackModel), onPressed: () => seekForward(ref),
tooltip: "15", tooltip: "15",
iconSize: 40, iconSize: 40,
icon: const Stack( icon: const Stack(
@ -574,13 +577,15 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
} }
} }
void seekBack(MediaPlaybackModel mediaPlayback, {int seconds = 15}) { void seekBack(WidgetRef ref, {int seconds = 15}) {
final mediaPlayback = ref.read(mediaPlaybackProvider);
resetTimer(); resetTimer();
final newPosition = (mediaPlayback.position.inSeconds - seconds).clamp(0, mediaPlayback.duration.inSeconds); final newPosition = (mediaPlayback.position.inSeconds - seconds).clamp(0, mediaPlayback.duration.inSeconds);
ref.read(videoPlayerProvider).seek(Duration(seconds: newPosition)); ref.read(videoPlayerProvider).seek(Duration(seconds: newPosition));
} }
void seekForward(MediaPlaybackModel mediaPlayback, {int seconds = 15}) { void seekForward(WidgetRef ref, {int seconds = 15}) {
final mediaPlayback = ref.read(mediaPlaybackProvider);
resetTimer(); resetTimer();
final newPosition = (mediaPlayback.position.inSeconds + seconds).clamp(0, mediaPlayback.duration.inSeconds); final newPosition = (mediaPlayback.position.inSeconds + seconds).clamp(0, mediaPlayback.duration.inSeconds);
ref.read(videoPlayerProvider).seek(Duration(seconds: newPosition)); ref.read(videoPlayerProvider).seek(Duration(seconds: newPosition));
@ -592,6 +597,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
); );
void toggleOverlay({bool? value}) { void toggleOverlay({bool? value}) {
if (showOverlay == (value ?? !showOverlay)) return;
setState(() => showOverlay = (value ?? !showOverlay)); setState(() => showOverlay = (value ?? !showOverlay));
resetTimer(); resetTimer();
SystemChrome.setEnabledSystemUIMode(showOverlay ? SystemUiMode.edgeToEdge : SystemUiMode.leanBack, overlays: []); SystemChrome.setEnabledSystemUIMode(showOverlay ? SystemUiMode.edgeToEdge : SystemUiMode.leanBack, overlays: []);
@ -609,9 +615,17 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
void resetTimer() => timer.reset();
Future<void> closePlayer() async {
clearOverlaySettings();
ref.read(videoPlayerProvider).stop();
Navigator.of(context).pop();
}
Future<void> clearOverlaySettings() async { Future<void> clearOverlaySettings() async {
toggleOverlay(value: true); toggleOverlay(value: true);
if (!AdaptiveLayout.of(context).isDesktop) { if (!(AdaptiveLayout.of(context).isDesktop || kIsWeb)) {
ScreenBrightness().resetScreenBrightness(); ScreenBrightness().resetScreenBrightness();
} else { } else {
disableFullscreen(); disableFullscreen();
@ -624,21 +638,20 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
timer.cancel(); timer.cancel();
} }
void resetTimer() => timer.reset();
Future<void> closePlayer() async {
clearOverlaySettings();
ref.read(videoPlayerProvider).stop();
Navigator.of(context).pop();
}
Future<void> disableFullscreen() async { Future<void> disableFullscreen() async {
resetTimer(); resetTimer();
if (kIsWeb) {
if (html.document.fullscreenElement != null) {
html.document.exitFullscreen();
await Future.delayed(const Duration(milliseconds: 500));
}
} else {
final isFullScreen = await windowManager.isFullScreen(); final isFullScreen = await windowManager.isFullScreen();
if (isFullScreen) { if (isFullScreen) {
await windowManager.setFullScreen(false); await windowManager.setFullScreen(false);
} }
} }
}
Future<void> toggleFullScreen() async { Future<void> toggleFullScreen() async {
final isFullScreen = await windowManager.isFullScreen(); final isFullScreen = await windowManager.isFullScreen();

View file

@ -123,7 +123,8 @@ class _NavigationBodyState extends ConsumerState<NavigationBody> {
Flexible( Flexible(
child: Padding( child: Padding(
key: const Key('navigation_rail'), key: const Key('navigation_rail'),
padding: MediaQuery.paddingOf(context).copyWith(right: 0), padding:
MediaQuery.paddingOf(context).copyWith(right: 0, top: AdaptiveLayout.of(context).isDesktop ? 8 : null),
child: Column( child: Column(
children: [ children: [
IconButton( IconButton(

View file

@ -41,9 +41,8 @@ class NestedNavigationDrawer extends ConsumerWidget {
backgroundColor: isExpanded ? Colors.transparent : null, backgroundColor: isExpanded ? Colors.transparent : null,
surfaceTintColor: isExpanded ? Colors.transparent : null, surfaceTintColor: isExpanded ? Colors.transparent : null,
children: [ children: [
if (AdaptiveLayout.of(context).isDesktop || kIsWeb) const SizedBox(height: 16),
Padding( Padding(
padding: const EdgeInsets.fromLTRB(28, 16, 16, 0), padding: EdgeInsets.fromLTRB(28, AdaptiveLayout.of(context).isDesktop ? 0 : 16, 16, 0),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(

View file

@ -0,0 +1,44 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:window_manager/window_manager.dart';
class FullScreenButton extends StatefulWidget {
const FullScreenButton({super.key});
@override
State<FullScreenButton> createState() => _FullScreenButtonState();
}
class _FullScreenButtonState extends State<FullScreenButton> {
bool isFullScreen = false;
@override
void initState() {
super.initState();
Future.microtask(checkFullScreen);
}
void checkFullScreen() async {
final fullScreen = await windowManager.isFullScreen();
setState(() {
isFullScreen = fullScreen;
});
log(isFullScreen.toString());
}
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: () async {
await windowManager.setFullScreen(!isFullScreen);
checkFullScreen();
},
icon: Icon(
isFullScreen ? IconsaxOutline.close_square : IconsaxOutline.maximize_4,
),
);
}
}

View file

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:ficonsax/ficonsax.dart';
import 'package:universal_html/html.dart' as html;
class FullScreenButton extends StatefulWidget {
const FullScreenButton({super.key});
@override
State<FullScreenButton> createState() => _FullScreenButtonState();
}
class _FullScreenButtonState extends State<FullScreenButton> {
bool fullScreen = false;
@override
void initState() {
super.initState();
_updateFullScreenStatus();
}
void _updateFullScreenStatus() {
setState(() {
fullScreen = html.document.fullscreenElement != null;
});
}
void toggleFullScreen() async {
if (fullScreen) {
html.document.exitFullscreen();
//Wait for 1 second
await Future.delayed(const Duration(seconds: 1));
} else {
await html.document.documentElement?.requestFullscreen();
}
_updateFullScreenStatus();
}
@override
Widget build(BuildContext context) {
return IconButton(
onPressed: toggleFullScreen,
icon: Icon(
fullScreen ? IconsaxOutline.close_square : IconsaxOutline.maximize_4,
),
);
}
}