feat: Bunch of small UI improvements for native player

This commit is contained in:
PartyDonut 2025-10-13 20:03:13 +02:00
parent 66ffc8c112
commit edbd8d467c
23 changed files with 329 additions and 327 deletions

View file

@ -98,7 +98,7 @@ void main(List<String> args) async {
applicationInfoProvider.overrideWith((ref) => applicationInfo),
crashLogProvider.overrideWith((ref) => crashProvider),
argumentsStateProvider.overrideWith((ref) => ArgumentsModel.fromArguments(args, leanBackEnabled)),
syncProvider.overrideWith((ref) => SyncNotifier(ref, applicationDirectory))
syncProvider.overrideWith((ref) => SyncNotifier(ref, applicationDirectory)),
],
child: AdaptiveLayoutBuilder(
child: (context) => const Main(),

View file

@ -0,0 +1,57 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/models/items/media_segments_model.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/src/player_settings_helper.g.dart' as pigeon;
final pigeonPlayerSettingsSyncProvider = Provider<void>((ref) {
void sendSettings() {
final userData = ref.read(userProvider);
final color = ref.read(
clientSettingsProvider.select(
(value) => value.themeColor?.color.toARGB32(),
),
);
final value = ref.read(videoPlayerSettingsProvider);
if (!kIsWeb && Platform.isAndroid) {
pigeon.PlayerSettingsPigeon().sendPlayerSettings(
pigeon.PlayerSettings(
enableTunneling: value.enableTunneling,
skipTypes: value.segmentSkipSettings.map(
(key, value) => MapEntry(
switch (key) {
MediaSegmentType.unknown => pigeon.SegmentType.intro,
MediaSegmentType.commercial => pigeon.SegmentType.commercial,
MediaSegmentType.preview => pigeon.SegmentType.preview,
MediaSegmentType.recap => pigeon.SegmentType.recap,
MediaSegmentType.outro => pigeon.SegmentType.outro,
MediaSegmentType.intro => pigeon.SegmentType.intro,
},
switch (value) {
SegmentSkip.none => pigeon.SegmentSkip.none,
SegmentSkip.askToSkip => pigeon.SegmentSkip.ask,
SegmentSkip.skip => pigeon.SegmentSkip.skip,
},
),
),
themeColor: color,
skipBackward: (userData?.userSettings?.skipBackDuration ?? const Duration(seconds: 15)).inMilliseconds,
skipForward: (userData?.userSettings?.skipForwardDuration ?? const Duration(seconds: 30)).inMilliseconds,
),
);
}
}
ref.listen(userProvider, (_, __) => sendSettings());
ref.listen(clientSettingsProvider, (_, __) => sendSettings());
ref.listen(videoPlayerSettingsProvider, (_, __) => sendSettings());
sendSettings();
});

View file

@ -1,6 +1,3 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -8,13 +5,10 @@ import 'package:collection/collection.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:screen_brightness/screen_brightness.dart';
import 'package:fladder/models/items/media_segments_model.dart';
import 'package:fladder/models/settings/key_combinations.dart';
import 'package:fladder/models/settings/video_player_settings.dart';
import 'package:fladder/providers/shared_provider.dart';
import 'package:fladder/providers/user_provider.dart';
import 'package:fladder/providers/video_player_provider.dart';
import 'package:fladder/src/player_settings_helper.g.dart' as pigeon;
final videoPlayerSettingsProvider =
StateNotifierProvider<VideoPlayerSettingsProviderNotifier, VideoPlayerSettingsModel>((ref) {
@ -33,34 +27,6 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
final oldState = super.state;
super.state = value;
ref.read(sharedUtilityProvider).videoPlayerSettings = value;
if (!kIsWeb && Platform.isAndroid) {
final userData = ref.read(userProvider);
pigeon.PlayerSettingsPigeon().sendPlayerSettings(
pigeon.PlayerSettings(
enableTunneling: value.enableTunneling,
skipTypes: value.segmentSkipSettings.map(
(key, value) => MapEntry(
switch (key) {
MediaSegmentType.unknown => pigeon.SegmentType.intro,
MediaSegmentType.commercial => pigeon.SegmentType.commercial,
MediaSegmentType.preview => pigeon.SegmentType.preview,
MediaSegmentType.recap => pigeon.SegmentType.recap,
MediaSegmentType.outro => pigeon.SegmentType.outro,
MediaSegmentType.intro => pigeon.SegmentType.intro,
},
switch (value) {
SegmentSkip.none => pigeon.SegmentSkip.none,
SegmentSkip.askToSkip => pigeon.SegmentSkip.ask,
SegmentSkip.skip => pigeon.SegmentSkip.skip,
},
),
),
skipBackward: (userData?.userSettings?.skipBackDuration ?? const Duration(seconds: 15)).inMilliseconds,
skipForward: (userData?.userSettings?.skipForwardDuration ?? const Duration(seconds: 30)).inMilliseconds,
),
);
}
if (!oldState.playerSame(value)) {
ref.read(videoPlayerProvider.notifier).init();
}

View file

@ -15,6 +15,7 @@ import 'package:fladder/providers/settings/book_viewer_settings_provider.dart';
import 'package:fladder/providers/settings/client_settings_provider.dart';
import 'package:fladder/providers/settings/home_settings_provider.dart';
import 'package:fladder/providers/settings/photo_view_settings_provider.dart';
import 'package:fladder/providers/settings/pigeon_player_settings_provider.dart';
import 'package:fladder/providers/settings/subtitle_settings_provider.dart';
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
import 'package:fladder/providers/user_provider.dart';
@ -25,6 +26,9 @@ final sharedPreferencesProvider = Provider<SharedPreferences>((ref) {
final sharedUtilityProvider = Provider<SharedUtility>((ref) {
final sharedPrefs = ref.watch(sharedPreferencesProvider);
//Init pigeon settings sync for native
ref.read(pigeonPlayerSettingsSyncProvider);
return SharedUtility(ref: ref, sharedPreferences: sharedPrefs);
});

View file

@ -24,15 +24,12 @@ List<Widget> buildClientSettingsTheme(BuildContext context, WidgetRef ref) {
items: ThemeMode.values,
selected: [ref.read(clientSettingsProvider.select((value) => value.themeMode))],
onChanged: (values) => ref.read(clientSettingsProvider.notifier).setThemeMode(values.first),
itemBuilder: (type, selected, tap) => RadioGroup(
groupValue: ref.read(clientSettingsProvider.select((value) => value.themeMode)),
itemBuilder: (type, selected, tap) => CheckboxListTile(
value: selected,
onChanged: (value) => tap(),
child: RadioListTile(
value: type,
title: Text(type.label(context)),
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
title: Text(type.label(context)),
contentPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
),
),
),
@ -41,43 +38,40 @@ List<Widget> buildClientSettingsTheme(BuildContext context, WidgetRef ref) {
subLabel: Text(clientSettings.themeColor?.name ?? context.localized.dynamicText),
onTap: () => openMultiSelectOptions<ColorThemes?>(
context,
label: context.localized.settingsLayoutModesTitle,
label: context.localized.color,
items: [null, ...ColorThemes.values],
selected: [(ref.read(clientSettingsProvider.select((value) => value.themeColor)))],
onChanged: (values) => ref.read(clientSettingsProvider.notifier).setThemeColor(values.first),
itemBuilder: (type, selected, tap) => RadioGroup(
itemBuilder: (type, selected, tap) => CheckboxListTile(
contentPadding: EdgeInsets.zero,
value: selected,
onChanged: (value) => tap(),
groupValue: ref.read(clientSettingsProvider.select((value) => value.themeColor)),
child: RadioListTile<ColorThemes?>(
contentPadding: EdgeInsets.zero,
value: type,
title: Row(
children: [
Container(
height: 24,
width: 24,
decoration: BoxDecoration(
gradient: type == null
? const SweepGradient(
center: FractionalOffset.center,
colors: <Color>[
Color(0xFF4285F4), // blue
Color(0xFF34A853), // green
Color(0xFFFBBC05), // yellow
Color(0xFFEA4335), // red
Color(0xFF4285F4), // blue again to seamlessly transition to the start
],
stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
)
: null,
color: type?.color,
borderRadius: BorderRadius.circular(4),
),
title: Row(
children: [
Container(
height: 24,
width: 24,
decoration: BoxDecoration(
gradient: type == null
? const SweepGradient(
center: FractionalOffset.center,
colors: <Color>[
Color(0xFF4285F4), // blue
Color(0xFF34A853), // green
Color(0xFFFBBC05), // yellow
Color(0xFFEA4335), // red
Color(0xFF4285F4), // blue again to seamlessly transition to the start
],
stops: <double>[0.0, 0.25, 0.5, 0.75, 1.0],
)
: null,
color: type?.color,
borderRadius: BorderRadius.circular(4),
),
const SizedBox(width: 8),
Text(type?.name ?? context.localized.dynamicText),
],
),
),
const SizedBox(width: 8),
Text(type?.name ?? context.localized.dynamicText),
],
),
),
),
@ -88,18 +82,15 @@ List<Widget> buildClientSettingsTheme(BuildContext context, WidgetRef ref) {
onTap: () async {
await openMultiSelectOptions<DynamicSchemeVariant>(
context,
label: context.localized.settingsLayoutModesTitle,
label: context.localized.clientSettingsSchemeVariantTitle,
items: DynamicSchemeVariant.values,
selected: [(ref.read(clientSettingsProvider.select((value) => value.schemeVariant)))],
onChanged: (values) => ref.read(clientSettingsProvider.notifier).setSchemeVariant(values.first),
itemBuilder: (type, selected, tap) => RadioGroup(
itemBuilder: (type, selected, tap) => CheckboxListTile(
contentPadding: EdgeInsets.zero,
value: selected,
onChanged: (value) => tap(),
groupValue: selected ? type : null,
child: RadioListTile<DynamicSchemeVariant>(
contentPadding: EdgeInsets.zero,
value: type,
title: Text(type.label(context)),
),
title: Text(type.label(context)),
),
);
},

View file

@ -47,6 +47,7 @@ class PlayerSettings {
PlayerSettings({
required this.enableTunneling,
required this.skipTypes,
this.themeColor,
required this.skipForward,
required this.skipBackward,
});
@ -55,6 +56,8 @@ class PlayerSettings {
Map<SegmentType, SegmentSkip> skipTypes;
int? themeColor;
int skipForward;
int skipBackward;
@ -63,6 +66,7 @@ class PlayerSettings {
return <Object?>[
enableTunneling,
skipTypes,
themeColor,
skipForward,
skipBackward,
];
@ -76,8 +80,9 @@ class PlayerSettings {
return PlayerSettings(
enableTunneling: result[0]! as bool,
skipTypes: (result[1] as Map<Object?, Object?>?)!.cast<SegmentType, SegmentSkip>(),
skipForward: result[2]! as int,
skipBackward: result[3]! as int,
themeColor: result[2] as int?,
skipForward: result[3]! as int,
skipBackward: result[4]! as int,
);
}

View file

@ -19,7 +19,7 @@ Future<List<T>> openMultiSelectOptions<T>(
builder: (context, setState) => AlertDialog(
title: Text(label),
content: SizedBox(
width: MediaQuery.of(context).size.width * 0.65,
width: MediaQuery.of(context).size.width * 0.85,
child: ListView(
physics: const AlwaysScrollableScrollPhysics(),
shrinkWrap: true,