mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feature: HTPC mode startup argument (#358)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
a8795cf0c9
commit
69a5e3db7a
24 changed files with 416 additions and 151 deletions
13
.vscode/launch.json
vendored
13
.vscode/launch.json
vendored
|
|
@ -10,7 +10,18 @@
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"args": [
|
"args": [
|
||||||
"--flavor",
|
"--flavor",
|
||||||
"development"
|
"development",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fladder Development HTPC (debug)",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "dart",
|
||||||
|
"args": [
|
||||||
|
"--flavor",
|
||||||
|
"development",
|
||||||
|
"-a",
|
||||||
|
"--htpc",
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1189,6 +1189,7 @@
|
||||||
"segmentActionAskToSkip": "Ask to skip",
|
"segmentActionAskToSkip": "Ask to skip",
|
||||||
"segmentActionSkip": "Skip",
|
"segmentActionSkip": "Skip",
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
|
"exitFladderTitle": "Exit Fladder",
|
||||||
"castAndCrew": "Cast & Crew",
|
"castAndCrew": "Cast & Crew",
|
||||||
"guestActor": "{count, plural, other{Guest Actors} one{Guest Actor}}",
|
"guestActor": "{count, plural, other{Guest Actors} one{Guest Actor}}",
|
||||||
"@guestActor": {
|
"@guestActor": {
|
||||||
|
|
@ -1220,5 +1221,4 @@
|
||||||
"hasLikedActor": "Has liked actor",
|
"hasLikedActor": "Has liked actor",
|
||||||
"latest": "Latest",
|
"latest": "Latest",
|
||||||
"recommended": "Recommended"
|
"recommended": "Recommended"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -19,7 +19,9 @@ 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/account_model.dart';
|
import 'package:fladder/models/account_model.dart';
|
||||||
|
import 'package:fladder/models/settings/arguments_model.dart';
|
||||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
import 'package:fladder/models/syncing/i_synced_item.dart';
|
||||||
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
import 'package:fladder/providers/crash_log_provider.dart';
|
import 'package:fladder/providers/crash_log_provider.dart';
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/shared_provider.dart';
|
import 'package:fladder/providers/shared_provider.dart';
|
||||||
|
|
@ -51,7 +53,7 @@ Future<Map<String, dynamic>> loadConfig() async {
|
||||||
return jsonDecode(configString);
|
return jsonDecode(configString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main(List<String> args) async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
final crashProvider = CrashLogNotifier();
|
final crashProvider = CrashLogNotifier();
|
||||||
|
|
||||||
|
|
@ -95,6 +97,7 @@ void main() async {
|
||||||
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
|
sharedPreferencesProvider.overrideWith((ref) => sharedPreferences),
|
||||||
applicationInfoProvider.overrideWith((ref) => applicationInfo),
|
applicationInfoProvider.overrideWith((ref) => applicationInfo),
|
||||||
crashLogProvider.overrideWith((ref) => crashProvider),
|
crashLogProvider.overrideWith((ref) => crashProvider),
|
||||||
|
argumentsStateProvider.overrideWith((ref) => ArgumentsModel.fromArguments(args)),
|
||||||
syncProvider.overrideWith((ref) => SyncNotifier(
|
syncProvider.overrideWith((ref) => SyncNotifier(
|
||||||
ref,
|
ref,
|
||||||
!kIsWeb
|
!kIsWeb
|
||||||
|
|
@ -234,6 +237,10 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
||||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||||
await windowManager.show();
|
await windowManager.show();
|
||||||
await windowManager.focus();
|
await windowManager.focus();
|
||||||
|
final startupArguments = ref.read(argumentsStateProvider);
|
||||||
|
if (startupArguments.htpcMode && !(await windowManager.isFullScreen())) {
|
||||||
|
await windowManager.setFullScreen(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge, overlays: []);
|
||||||
|
|
|
||||||
19
lib/models/settings/arguments_model.dart
Normal file
19
lib/models/settings/arguments_model.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'arguments_model.freezed.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class ArgumentsModel with _$ArgumentsModel {
|
||||||
|
const ArgumentsModel._();
|
||||||
|
|
||||||
|
factory ArgumentsModel({
|
||||||
|
@Default(false) bool htpcMode,
|
||||||
|
}) = _ArgumentsModel;
|
||||||
|
|
||||||
|
factory ArgumentsModel.fromArguments(List<String> arguments) {
|
||||||
|
arguments = arguments.map((e) => e.trim()).toList();
|
||||||
|
return ArgumentsModel(
|
||||||
|
htpcMode: arguments.contains('--htpc'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
147
lib/models/settings/arguments_model.freezed.dart
Normal file
147
lib/models/settings/arguments_model.freezed.dart
Normal file
|
|
@ -0,0 +1,147 @@
|
||||||
|
// coverage:ignore-file
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'arguments_model.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
final _privateConstructorUsedError = UnsupportedError(
|
||||||
|
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$ArgumentsModel {
|
||||||
|
bool get htpcMode => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of ArgumentsModel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$ArgumentsModelCopyWith<ArgumentsModel> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $ArgumentsModelCopyWith<$Res> {
|
||||||
|
factory $ArgumentsModelCopyWith(
|
||||||
|
ArgumentsModel value, $Res Function(ArgumentsModel) then) =
|
||||||
|
_$ArgumentsModelCopyWithImpl<$Res, ArgumentsModel>;
|
||||||
|
@useResult
|
||||||
|
$Res call({bool htpcMode});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$ArgumentsModelCopyWithImpl<$Res, $Val extends ArgumentsModel>
|
||||||
|
implements $ArgumentsModelCopyWith<$Res> {
|
||||||
|
_$ArgumentsModelCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of ArgumentsModel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? htpcMode = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
htpcMode: null == htpcMode
|
||||||
|
? _value.htpcMode
|
||||||
|
: htpcMode // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$ArgumentsModelImplCopyWith<$Res>
|
||||||
|
implements $ArgumentsModelCopyWith<$Res> {
|
||||||
|
factory _$$ArgumentsModelImplCopyWith(_$ArgumentsModelImpl value,
|
||||||
|
$Res Function(_$ArgumentsModelImpl) then) =
|
||||||
|
__$$ArgumentsModelImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({bool htpcMode});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$ArgumentsModelImplCopyWithImpl<$Res>
|
||||||
|
extends _$ArgumentsModelCopyWithImpl<$Res, _$ArgumentsModelImpl>
|
||||||
|
implements _$$ArgumentsModelImplCopyWith<$Res> {
|
||||||
|
__$$ArgumentsModelImplCopyWithImpl(
|
||||||
|
_$ArgumentsModelImpl _value, $Res Function(_$ArgumentsModelImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of ArgumentsModel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? htpcMode = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$ArgumentsModelImpl(
|
||||||
|
htpcMode: null == htpcMode
|
||||||
|
? _value.htpcMode
|
||||||
|
: htpcMode // ignore: cast_nullable_to_non_nullable
|
||||||
|
as bool,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
|
||||||
|
class _$ArgumentsModelImpl extends _ArgumentsModel {
|
||||||
|
_$ArgumentsModelImpl({this.htpcMode = false}) : super._();
|
||||||
|
|
||||||
|
@override
|
||||||
|
@JsonKey()
|
||||||
|
final bool htpcMode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ArgumentsModel(htpcMode: $htpcMode)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$ArgumentsModelImpl &&
|
||||||
|
(identical(other.htpcMode, htpcMode) ||
|
||||||
|
other.htpcMode == htpcMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType, htpcMode);
|
||||||
|
|
||||||
|
/// Create a copy of ArgumentsModel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$ArgumentsModelImplCopyWith<_$ArgumentsModelImpl> get copyWith =>
|
||||||
|
__$$ArgumentsModelImplCopyWithImpl<_$ArgumentsModelImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _ArgumentsModel extends ArgumentsModel {
|
||||||
|
factory _ArgumentsModel({final bool htpcMode}) = _$ArgumentsModelImpl;
|
||||||
|
_ArgumentsModel._() : super._();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get htpcMode;
|
||||||
|
|
||||||
|
/// Create a copy of ArgumentsModel
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$ArgumentsModelImplCopyWith<_$ArgumentsModelImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
5
lib/providers/arguments_provider.dart
Normal file
5
lib/providers/arguments_provider.dart
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/models/settings/arguments_model.dart';
|
||||||
|
|
||||||
|
final argumentsStateProvider = StateProvider<ArgumentsModel>((ref) => ArgumentsModel());
|
||||||
|
|
@ -20,9 +20,8 @@ import 'package:fladder/util/input_handler.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/throttler.dart';
|
import 'package:fladder/util/throttler.dart';
|
||||||
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||||
import 'package:fladder/widgets/shared/elevated_icon.dart';
|
import 'package:fladder/widgets/shared/elevated_icon.dart';
|
||||||
import 'package:fladder/widgets/shared/full_screen_button.dart'
|
|
||||||
if (dart.library.html) 'package:fladder/widgets/shared/full_screen_button_web.dart';
|
|
||||||
import 'package:fladder/widgets/shared/progress_floating_button.dart';
|
import 'package:fladder/widgets/shared/progress_floating_button.dart';
|
||||||
|
|
||||||
class PhotoViewerControls extends ConsumerStatefulWidget {
|
class PhotoViewerControls extends ConsumerStatefulWidget {
|
||||||
|
|
@ -131,7 +130,7 @@ class _PhotoViewerControllsState extends ConsumerState<PhotoViewerControls> with
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
timerController.dispose();
|
timerController.dispose();
|
||||||
closeFullScreen();
|
fullScreenHelper.closeFullScreen(ref);
|
||||||
windowManager.removeListener(this);
|
windowManager.removeListener(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,6 @@ import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/bitrate_helper.dart';
|
import 'package:fladder/util/bitrate_helper.dart';
|
||||||
import 'package:fladder/util/box_fit_extension.dart';
|
import 'package:fladder/util/box_fit_extension.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/option_dialogue.dart';
|
|
||||||
import 'package:fladder/widgets/shared/enum_selection.dart';
|
import 'package:fladder/widgets/shared/enum_selection.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
|
|
@ -74,22 +73,18 @@ class _PlayerSettingsPageState extends ConsumerState<PlayerSettingsPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
label: Text(context.localized.videoScalingFillScreenTitle),
|
label: Text(context.localized.videoScaling),
|
||||||
subLabel: Text(videoSettings.videoFit.label(context)),
|
trailing: EnumBox(
|
||||||
onTap: () => openMultiSelectOptions(
|
current: videoSettings.videoFit.label(context),
|
||||||
context,
|
itemBuilder: (context) => BoxFit.values
|
||||||
label: context.localized.videoScalingFillScreenTitle,
|
.map(
|
||||||
items: BoxFit.values,
|
(entry) => PopupMenuItem(
|
||||||
selected: [ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit))],
|
value: entry,
|
||||||
onChanged: (values) => ref.read(videoPlayerSettingsProvider.notifier).setFitType(values.first),
|
child: Text(entry.label(context)),
|
||||||
itemBuilder: (type, selected, tap) => RadioListTile(
|
onTap: () => ref.read(videoPlayerSettingsProvider.notifier).setFitType(entry),
|
||||||
groupValue: ref.read(videoPlayerSettingsProvider.select((value) => value.videoFit)),
|
|
||||||
title: Text(type.label(context)),
|
|
||||||
value: type,
|
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
onChanged: (value) => tap(),
|
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SettingsListTile(
|
SettingsListTile(
|
||||||
|
|
@ -363,7 +358,7 @@ class _StatusIndicator extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
label,
|
Flexible(child: label),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ class SettingsListTile extends StatelessWidget {
|
||||||
final Widget? trailing;
|
final Widget? trailing;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final IconData? icon;
|
final IconData? icon;
|
||||||
final Widget? suffix;
|
final Widget? leading;
|
||||||
final Color? contentColor;
|
final Color? contentColor;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
const SettingsListTile({
|
const SettingsListTile({
|
||||||
|
|
@ -16,7 +16,7 @@ class SettingsListTile extends StatelessWidget {
|
||||||
this.subLabel,
|
this.subLabel,
|
||||||
this.trailing,
|
this.trailing,
|
||||||
this.selected = false,
|
this.selected = false,
|
||||||
this.suffix,
|
this.leading,
|
||||||
this.icon,
|
this.icon,
|
||||||
this.contentColor,
|
this.contentColor,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
|
@ -27,7 +27,7 @@ class SettingsListTile extends StatelessWidget {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final iconWidget = icon != null ? Icon(icon) : null;
|
final iconWidget = icon != null ? Icon(icon) : null;
|
||||||
|
|
||||||
final leadingWidget = (suffix ?? iconWidget) != null
|
final leadingWidget = (leading ?? iconWidget) != null
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(left: 8, right: 16.0),
|
padding: const EdgeInsets.only(left: 8, right: 16.0),
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
|
|
@ -38,11 +38,11 @@ class SettingsListTile extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12),
|
padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 12),
|
||||||
child: (suffix ?? iconWidget),
|
child: (leading ?? iconWidget),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: suffix ?? const SizedBox();
|
: leading ?? const SizedBox();
|
||||||
return Card(
|
return Card(
|
||||||
elevation: selected ? 2 : 0,
|
elevation: selected ? 2 : 0,
|
||||||
color: selected ? Theme.of(context).colorScheme.surfaceContainerLow : Colors.transparent,
|
color: selected ? Theme.of(context).colorScheme.surfaceContainerLow : Colors.transparent,
|
||||||
|
|
@ -57,7 +57,7 @@ class SettingsListTile extends StatelessWidget {
|
||||||
horizontal: 16,
|
horizontal: 16,
|
||||||
vertical: 12,
|
vertical: 12,
|
||||||
).copyWith(
|
).copyWith(
|
||||||
left: (suffix ?? iconWidget) != null ? 0 : null,
|
left: (leading ?? iconWidget) != null ? 0 : null,
|
||||||
),
|
),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(
|
constraints: const BoxConstraints(
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
import 'package:fladder/providers/auth_provider.dart';
|
import 'package:fladder/providers/auth_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
|
|
@ -11,6 +13,7 @@ import 'package:fladder/screens/settings/quick_connect_window.dart';
|
||||||
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
import 'package:fladder/screens/settings/settings_list_tile.dart';
|
||||||
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
import 'package:fladder/screens/settings/settings_scaffold.dart';
|
||||||
import 'package:fladder/screens/shared/fladder_icon.dart';
|
import 'package:fladder/screens/shared/fladder_icon.dart';
|
||||||
|
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/theme_extensions.dart';
|
import 'package:fladder/util/theme_extensions.dart';
|
||||||
|
|
@ -137,7 +140,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||||
label: Text(context.localized.about),
|
label: Text(context.localized.about),
|
||||||
subLabel: const Text("Fladder"),
|
subLabel: const Text("Fladder"),
|
||||||
selected: containsRoute(const AboutSettingsRoute()),
|
selected: containsRoute(const AboutSettingsRoute()),
|
||||||
suffix: Opacity(
|
leading: Opacity(
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
child: FladderIconOutlined(
|
child: FladderIconOutlined(
|
||||||
size: 24,
|
size: 24,
|
||||||
|
|
@ -146,6 +149,20 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
|
||||||
),
|
),
|
||||||
onTap: () => navigateTo(const AboutSettingsRoute()),
|
onTap: () => navigateTo(const AboutSettingsRoute()),
|
||||||
),
|
),
|
||||||
|
if (ref.watch(argumentsStateProvider.select((value) => value.htpcMode))) ...[
|
||||||
|
SettingsListTile(
|
||||||
|
label: Text(context.localized.exitFladderTitle),
|
||||||
|
icon: IconsaxPlusLinear.close_square,
|
||||||
|
onTap: () async {
|
||||||
|
final manager = WindowManager.instance;
|
||||||
|
if (await manager.isClosable()) {
|
||||||
|
manager.close();
|
||||||
|
} else {
|
||||||
|
fladderSnackbar(context, title: context.localized.somethingWentWrong);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
],
|
],
|
||||||
floatingActionButton: Padding(
|
floatingActionButton: Padding(
|
||||||
padding: EdgeInsets.symmetric(horizontal: MediaQuery.paddingOf(context).horizontal),
|
padding: EdgeInsets.symmetric(horizontal: MediaQuery.paddingOf(context).horizontal),
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||||
|
|
||||||
class DefaultTitleBar extends ConsumerStatefulWidget {
|
class DefaultTitleBar extends ConsumerStatefulWidget {
|
||||||
final String? label;
|
final String? label;
|
||||||
|
|
@ -33,6 +35,7 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (ref.watch(argumentsStateProvider.select((value) => value.htpcMode))) return const SizedBox.shrink();
|
||||||
final brightness = widget.brightness ?? Theme.of(context).brightness;
|
final brightness = widget.brightness ?? Theme.of(context).brightness;
|
||||||
final iconColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.65);
|
final iconColor = Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.65);
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
|
|
@ -77,11 +80,9 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
children: [
|
children: [
|
||||||
FutureBuilder<List<bool>>(future: Future.microtask(() async {
|
FutureBuilder<List<bool>>(future: Future.microtask(() async {
|
||||||
final isMinimized = await windowManager.isMinimized();
|
final isMinimized = await windowManager.isMinimized();
|
||||||
final isFullScreen = await windowManager.isFullScreen();
|
return [isMinimized];
|
||||||
return [isMinimized, isFullScreen];
|
|
||||||
}), builder: (context, snapshot) {
|
}), builder: (context, snapshot) {
|
||||||
final isMinimized = snapshot.data?.firstOrNull ?? false;
|
final isMinimized = snapshot.data?.firstOrNull ?? false;
|
||||||
final fullScreen = snapshot.data?.lastOrNull ?? false;
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
hoverColor: brightness == Brightness.light
|
hoverColor: brightness == Brightness.light
|
||||||
|
|
@ -89,9 +90,7 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
: Colors.white.withValues(alpha: 0.2),
|
: Colors.white.withValues(alpha: 0.2),
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2))),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2))),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (fullScreen) {
|
fullScreenHelper.closeFullScreen(ref);
|
||||||
await windowManager.setFullScreen(false);
|
|
||||||
}
|
|
||||||
if (isMinimized) {
|
if (isMinimized) {
|
||||||
windowManager.restore();
|
windowManager.restore();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -111,12 +110,10 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
FutureBuilder<List<bool>>(
|
FutureBuilder<List<bool>>(
|
||||||
future: Future.microtask(() async {
|
future: Future.microtask(() async {
|
||||||
final isMaximized = await windowManager.isMaximized();
|
final isMaximized = await windowManager.isMaximized();
|
||||||
final isFullScreen = await windowManager.isFullScreen();
|
return [isMaximized];
|
||||||
return [isMaximized, isFullScreen];
|
|
||||||
}),
|
}),
|
||||||
builder: (BuildContext context, AsyncSnapshot<List<bool>> snapshot) {
|
builder: (BuildContext context, AsyncSnapshot<List<bool>> snapshot) {
|
||||||
final maximized = snapshot.data?.firstOrNull ?? false;
|
final maximized = snapshot.data?.firstOrNull ?? false;
|
||||||
final fullScreen = snapshot.data?.lastOrNull ?? false;
|
|
||||||
return IconButton(
|
return IconButton(
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
hoverColor: brightness == Brightness.light
|
hoverColor: brightness == Brightness.light
|
||||||
|
|
@ -125,15 +122,12 @@ class _DefaultTitleBarState extends ConsumerState<DefaultTitleBar> with WindowLi
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(2)),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
if (fullScreen && maximized) {
|
fullScreenHelper.closeFullScreen(ref);
|
||||||
await windowManager.setFullScreen(false);
|
if (maximized) {
|
||||||
await windowManager.unmaximize();
|
await windowManager.unmaximize();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!maximized) {
|
||||||
if (fullScreen) {
|
|
||||||
await windowManager.setFullScreen(false);
|
|
||||||
} else if (!maximized) {
|
|
||||||
await windowManager.maximize();
|
await windowManager.maximize();
|
||||||
} else {
|
} else {
|
||||||
await windowManager.unmaximize();
|
await windowManager.unmaximize();
|
||||||
|
|
|
||||||
|
|
@ -20,9 +20,8 @@ import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.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/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||||
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
|
import 'package:fladder/widgets/navigation_scaffold/components/floating_player_bar.dart';
|
||||||
import 'package:fladder/widgets/shared/full_screen_button.dart'
|
|
||||||
if (dart.library.html) 'package:fladder/widgets/shared/full_screen_button_web.dart';
|
|
||||||
import 'package:fladder/widgets/shared/progress_floating_button.dart';
|
import 'package:fladder/widgets/shared/progress_floating_button.dart';
|
||||||
|
|
||||||
class VideoPlayerNextWrapper extends ConsumerStatefulWidget {
|
class VideoPlayerNextWrapper extends ConsumerStatefulWidget {
|
||||||
|
|
@ -132,7 +131,7 @@ class _VideoPlayerNextWrapperState extends ConsumerState<VideoPlayerNextWrapper>
|
||||||
if (AdaptiveLayout.of(context).inputDevice != InputDevice.pointer) {
|
if (AdaptiveLayout.of(context).inputDevice != InputDevice.pointer) {
|
||||||
ScreenBrightness().resetApplicationScreenBrightness();
|
ScreenBrightness().resetApplicationScreenBrightness();
|
||||||
} else {
|
} else {
|
||||||
closeFullScreen();
|
fullScreenHelper.closeFullScreen(ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,7 @@ import 'package:fladder/util/input_handler.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'
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.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});
|
||||||
|
|
@ -86,7 +85,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (value.logicalKey == LogicalKeyboardKey.keyF) {
|
if (value.logicalKey == LogicalKeyboardKey.keyF) {
|
||||||
toggleFullScreen(ref);
|
fullScreenHelper.toggleFullScreen(ref);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
|
if (value.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
|
|
@ -137,7 +136,7 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
? () => player.playOrPause()
|
? () => player.playOrPause()
|
||||||
: () => toggleOverlay(),
|
: () => toggleOverlay(),
|
||||||
onDoubleTap: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
|
onDoubleTap: AdaptiveLayout.of(context).inputDevice == InputDevice.pointer
|
||||||
? () => toggleFullScreen(ref)
|
? () => fullScreenHelper.toggleFullScreen(ref)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -665,6 +664,6 @@ class _DesktopControlsState extends ConsumerState<DesktopControls> {
|
||||||
|
|
||||||
Future<void> disableFullScreen() async {
|
Future<void> disableFullScreen() async {
|
||||||
resetTimer();
|
resetTimer();
|
||||||
closeFullScreen();
|
fullScreenHelper.closeFullScreen(ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import 'package:fladder/util/adaptive_layout/adaptive_layout_model.dart';
|
||||||
import 'package:fladder/util/debug_banner.dart';
|
import 'package:fladder/util/debug_banner.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/poster_defaults.dart';
|
import 'package:fladder/util/poster_defaults.dart';
|
||||||
|
import 'package:fladder/util/resolution_checker.dart';
|
||||||
|
|
||||||
enum InputDevice {
|
enum InputDevice {
|
||||||
touch,
|
touch,
|
||||||
|
|
@ -209,8 +210,12 @@ class _AdaptiveLayoutBuilderState extends ConsumerState<AdaptiveLayoutBuilder> {
|
||||||
controller: controller,
|
controller: controller,
|
||||||
posterDefaults: posterDefaults,
|
posterDefaults: posterDefaults,
|
||||||
),
|
),
|
||||||
|
child: Builder(
|
||||||
|
builder: (context) => ResolutionChecker(
|
||||||
child: widget.adaptiveLayout == null ? DebugBanner(child: widget.child(context)) : widget.child(context),
|
child: widget.adaptiveLayout == null ? DebugBanner(child: widget.child(context)) : widget.child(context),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
import 'package:fladder/models/book_model.dart';
|
import 'package:fladder/models/book_model.dart';
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
|
|
@ -26,6 +25,7 @@ import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/list_extensions.dart';
|
import 'package:fladder/util/list_extensions.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/refresh_state.dart';
|
import 'package:fladder/util/refresh_state.dart';
|
||||||
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||||
|
|
||||||
Future<void> _showLoadingIndicator(BuildContext context) async {
|
Future<void> _showLoadingIndicator(BuildContext context) async {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
|
|
@ -103,10 +103,7 @@ Future<void> _playVideo(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (AdaptiveLayout.of(context).isDesktop) {
|
if (AdaptiveLayout.of(context).isDesktop) {
|
||||||
final fullScreen = await windowManager.isFullScreen();
|
fullScreenHelper.closeFullScreen(ref);
|
||||||
if (fullScreen) {
|
|
||||||
await windowManager.setFullScreen(false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.refreshData();
|
context.refreshData();
|
||||||
|
|
|
||||||
63
lib/util/resolution_checker.dart
Normal file
63
lib/util/resolution_checker.dart
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:screen_retriever/screen_retriever.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
|
|
||||||
|
class ResolutionChecker extends ConsumerStatefulWidget {
|
||||||
|
final Widget child;
|
||||||
|
const ResolutionChecker({required this.child, super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<ResolutionChecker> createState() => _ResolutionCheckerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ResolutionCheckerState extends ConsumerState<ResolutionChecker> {
|
||||||
|
Size? lastResolution;
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((value) async {
|
||||||
|
if (ref.read(argumentsStateProvider).htpcMode) {
|
||||||
|
lastResolution = (await screenRetriever.getPrimaryDisplay()).size;
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 2), (timer) => checkResolution());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> checkResolution() async {
|
||||||
|
if (!mounted) return;
|
||||||
|
final newResolution = (await screenRetriever.getPrimaryDisplay()).size;
|
||||||
|
if (lastResolution != newResolution) {
|
||||||
|
lastResolution = newResolution;
|
||||||
|
shouldSetResolution();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> shouldSetResolution() async {
|
||||||
|
if (lastResolution != null) {
|
||||||
|
final isFullScreen = await windowManager.isFullScreen();
|
||||||
|
if (isFullScreen) {
|
||||||
|
await windowManager.setFullScreen(false);
|
||||||
|
}
|
||||||
|
await windowManager.setFullScreen(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_timer?.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||||
|
|
||||||
|
class FullScreenHelper implements FullScreenWrapper {
|
||||||
|
const FullScreenHelper._();
|
||||||
|
factory FullScreenHelper.instantiate() => const FullScreenHelper._();
|
||||||
|
@override
|
||||||
|
Future<void> closeFullScreen(WidgetRef ref) async {
|
||||||
|
if (ref.watch(argumentsStateProvider.select((value) => value.htpcMode))) return;
|
||||||
|
final isFullScreen = await windowManager.isFullScreen();
|
||||||
|
if (isFullScreen) {
|
||||||
|
await windowManager.setFullScreen(false);
|
||||||
|
}
|
||||||
|
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(fullScreen: false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> toggleFullScreen(WidgetRef ref) async {
|
||||||
|
if (ref.watch(argumentsStateProvider.select((value) => value.htpcMode))) return;
|
||||||
|
final isFullScreen = await windowManager.isFullScreen();
|
||||||
|
await windowManager.setFullScreen(!isFullScreen);
|
||||||
|
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(fullScreen: !isFullScreen));
|
||||||
|
}
|
||||||
|
}
|
||||||
34
lib/widgets/full_screen_helpers/full_screen_helper_web.dart
Normal file
34
lib/widgets/full_screen_helpers/full_screen_helper_web.dart
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:universal_html/html.dart' as html;
|
||||||
|
|
||||||
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_wrapper.dart';
|
||||||
|
|
||||||
|
class FullScreenHelper implements FullScreenWrapper {
|
||||||
|
const FullScreenHelper._();
|
||||||
|
factory FullScreenHelper.instantiate() => const FullScreenHelper._();
|
||||||
|
@override
|
||||||
|
Future<void> closeFullScreen(WidgetRef ref) async {
|
||||||
|
if (html.document.fullscreenElement != null) {
|
||||||
|
html.document.exitFullscreen();
|
||||||
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(fullScreen: false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> toggleFullScreen(WidgetRef ref) async {
|
||||||
|
final isFullScreen = html.document.fullscreenElement != null;
|
||||||
|
|
||||||
|
if (isFullScreen) {
|
||||||
|
html.document.exitFullscreen();
|
||||||
|
//Wait for 1 second
|
||||||
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
|
} else {
|
||||||
|
await html.document.documentElement?.requestFullscreen();
|
||||||
|
}
|
||||||
|
ref
|
||||||
|
.read(mediaPlaybackProvider.notifier)
|
||||||
|
.update((state) => state.copyWith(fullScreen: html.document.fullscreenElement != null));
|
||||||
|
}
|
||||||
|
}
|
||||||
32
lib/widgets/full_screen_helpers/full_screen_wrapper.dart
Normal file
32
lib/widgets/full_screen_helpers/full_screen_wrapper.dart
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
import 'package:fladder/widgets/full_screen_helpers/full_screen_helper_desktop.dart'
|
||||||
|
if (dart.library.html) 'package:fladder/widgets/full_screen_helpers/full_screen_helper_web.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
|
final FullScreenHelper fullScreenHelper = FullScreenHelper.instantiate();
|
||||||
|
|
||||||
|
abstract class FullScreenWrapper {
|
||||||
|
Future<void> closeFullScreen(WidgetRef ref);
|
||||||
|
Future<void> toggleFullScreen(WidgetRef ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullScreenButton extends ConsumerWidget {
|
||||||
|
const FullScreenButton({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
if (ref.watch(argumentsStateProvider.select((value) => value.htpcMode))) return const SizedBox.shrink();
|
||||||
|
final fullScreen = ref.watch(mediaPlaybackProvider.select((value) => value.fullScreen));
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () => fullScreenHelper.toggleFullScreen(ref),
|
||||||
|
icon: Icon(
|
||||||
|
fullScreen ? IconsaxPlusLinear.screenmirroring : IconsaxPlusLinear.maximize_4,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -58,7 +58,7 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
|
||||||
|
|
||||||
void stopTimer() {
|
void stopTimer() {
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
timer = Timer(const Duration(milliseconds: 350), () {
|
timer = Timer(const Duration(milliseconds: 125), () {
|
||||||
setState(() {
|
setState(() {
|
||||||
showOnHover = false;
|
showOnHover = false;
|
||||||
});
|
});
|
||||||
|
|
@ -93,6 +93,7 @@ class _SideNavigationBarState extends ConsumerState<SideNavigationBar> {
|
||||||
child: MouseRegion(
|
child: MouseRegion(
|
||||||
onEnter: (value) => startTimer(),
|
onEnter: (value) => startTimer(),
|
||||||
onExit: (event) => stopTimer(),
|
onExit: (event) => stopTimer(),
|
||||||
|
onHover: (value) => startTimer(),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (isDesktop && AdaptiveLayout.of(context).platform != TargetPlatform.macOS) ...{
|
if (isDesktop && AdaptiveLayout.of(context).platform != TargetPlatform.macOS) ...{
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:window_manager/window_manager.dart';
|
|
||||||
|
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
|
||||||
|
|
||||||
Future<void> closeFullScreen() async {
|
|
||||||
final isFullScreen = await windowManager.isFullScreen();
|
|
||||||
if (isFullScreen) {
|
|
||||||
await windowManager.setFullScreen(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> toggleFullScreen(WidgetRef ref) async {
|
|
||||||
final isFullScreen = await windowManager.isFullScreen();
|
|
||||||
await windowManager.setFullScreen(!isFullScreen);
|
|
||||||
ref.read(mediaPlaybackProvider.notifier).update((state) => state.copyWith(fullScreen: !isFullScreen));
|
|
||||||
}
|
|
||||||
|
|
||||||
class FullScreenButton extends ConsumerWidget {
|
|
||||||
const FullScreenButton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final fullScreen = ref.watch(mediaPlaybackProvider.select((value) => value.fullScreen));
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () => toggleFullScreen(ref),
|
|
||||||
icon: Icon(
|
|
||||||
fullScreen ? IconsaxPlusLinear.screenmirroring : IconsaxPlusLinear.maximize_4,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:universal_html/html.dart' as html;
|
|
||||||
|
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
|
||||||
|
|
||||||
Future<void> closeFullScreen() async {
|
|
||||||
if (html.document.fullscreenElement != null) {
|
|
||||||
html.document.exitFullscreen();
|
|
||||||
await Future.delayed(const Duration(milliseconds: 500));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> toggleFullScreen(WidgetRef ref) async {
|
|
||||||
final isFullScreen = html.document.fullscreenElement != null;
|
|
||||||
|
|
||||||
if (isFullScreen) {
|
|
||||||
html.document.exitFullscreen();
|
|
||||||
//Wait for 1 second
|
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
|
||||||
} else {
|
|
||||||
await html.document.documentElement?.requestFullscreen();
|
|
||||||
}
|
|
||||||
ref
|
|
||||||
.read(mediaPlaybackProvider.notifier)
|
|
||||||
.update((state) => state.copyWith(fullScreen: html.document.fullscreenElement != null));
|
|
||||||
}
|
|
||||||
|
|
||||||
class FullScreenButton extends ConsumerWidget {
|
|
||||||
const FullScreenButton({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final fullScreen = ref.watch(mediaPlaybackProvider.select((value) => value.fullScreen));
|
|
||||||
return IconButton(
|
|
||||||
onPressed: () => toggleFullScreen(ref),
|
|
||||||
icon: Icon(
|
|
||||||
fullScreen ? IconsaxPlusLinear.screenmirroring : IconsaxPlusLinear.maximize_4,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
pubspec.lock
10
pubspec.lock
|
|
@ -1531,7 +1531,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
screen_retriever:
|
screen_retriever:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: screen_retriever
|
name: screen_retriever
|
||||||
sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"
|
sha256: "570dbc8e4f70bac451e0efc9c9bb19fa2d6799a11e6ef04f946d7886d2e23d0c"
|
||||||
|
|
@ -1807,14 +1807,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.1"
|
||||||
super_sliver_list:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: super_sliver_list
|
|
||||||
sha256: b1e1e64d08ce40e459b9bb5d9f8e361617c26b8c9f3bb967760b0f436b6e3f56
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.4.1"
|
|
||||||
swagger_dart_code_generator:
|
swagger_dart_code_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ dependencies:
|
||||||
font_awesome_flutter: ^10.8.0
|
font_awesome_flutter: ^10.8.0
|
||||||
reorderable_grid: ^1.0.10
|
reorderable_grid: ^1.0.10
|
||||||
overflow_view: ^0.4.0
|
overflow_view: ^0.4.0
|
||||||
|
flutter_sticky_header: ^0.7.0
|
||||||
|
|
||||||
# Navigation
|
# Navigation
|
||||||
auto_route: ^9.3.0+1
|
auto_route: ^9.3.0+1
|
||||||
|
|
@ -109,6 +110,7 @@ dependencies:
|
||||||
window_manager: ^0.4.3
|
window_manager: ^0.4.3
|
||||||
smtc_windows: ^1.0.0
|
smtc_windows: ^1.0.0
|
||||||
background_downloader: ^8.9.4
|
background_downloader: ^8.9.4
|
||||||
|
screen_retriever: ^0.2.0
|
||||||
|
|
||||||
# Data
|
# Data
|
||||||
isar: ^4.0.0-dev.14
|
isar: ^4.0.0-dev.14
|
||||||
|
|
@ -123,8 +125,6 @@ dependencies:
|
||||||
share_plus: ^10.1.4
|
share_plus: ^10.1.4
|
||||||
archive: ^4.0.2
|
archive: ^4.0.2
|
||||||
dart_mappable: ^4.3.0
|
dart_mappable: ^4.3.0
|
||||||
super_sliver_list: ^0.4.1
|
|
||||||
flutter_sticky_header: ^0.7.0
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue