mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-09 07:28:14 -07:00
feat: Customizable shortcuts/hotkeys (#439)
This implements the logic for allowing hotkeys with modifiers. Implemented globalhotkeys and videocontrol hotkeys Also implements saving the forward backwards seconds to the user. Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
This commit is contained in:
parent
23385d8e62
commit
fa30e634b4
29 changed files with 1360 additions and 162 deletions
|
|
@ -7,7 +7,7 @@ part of 'connectivity_provider.dart';
|
|||
// **************************************************************************
|
||||
|
||||
String _$connectivityStatusHash() =>
|
||||
r'7a4ac96d163a479bd34fc6a3efcd556755f8d5e9';
|
||||
r'8c58479db511f2431942655adf3f5021e8f0290c';
|
||||
|
||||
/// See also [ConnectivityStatus].
|
||||
@ProviderFor(ConnectivityStatus)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ import 'package:fladder/providers/sync_provider.dart';
|
|||
import 'package:fladder/providers/user_provider.dart';
|
||||
import 'package:fladder/util/jellyfin_extension.dart';
|
||||
|
||||
const _userSettings = "usersettings";
|
||||
const _client = "fladder";
|
||||
|
||||
class ServerQueryResult {
|
||||
final List<BaseItemDto> original;
|
||||
final List<ItemBaseModel> items;
|
||||
|
|
@ -745,6 +748,34 @@ class JellyService {
|
|||
Future<Response<ServerConfiguration>> systemConfigurationGet() => api.systemConfigurationGet();
|
||||
Future<Response<PublicSystemInfo>> systemInfoPublicGet() => api.systemInfoPublicGet();
|
||||
|
||||
Future<Response<UserSettings>> getCustomConfig() async {
|
||||
final response = await api.displayPreferencesDisplayPreferencesIdGet(
|
||||
displayPreferencesId: _userSettings,
|
||||
userId: account?.id ?? "",
|
||||
$client: _client,
|
||||
);
|
||||
final customPrefs = response.body?.customPrefs?.parseValues();
|
||||
final userPrefs = customPrefs != null ? UserSettings.fromJson(customPrefs) : UserSettings();
|
||||
return response.copyWith(
|
||||
body: userPrefs,
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response<dynamic>> setCustomConfig(UserSettings currentSettings) async {
|
||||
final currentDisplayPreferences = await api.displayPreferencesDisplayPreferencesIdGet(
|
||||
displayPreferencesId: _userSettings,
|
||||
$client: _client,
|
||||
);
|
||||
return api.displayPreferencesDisplayPreferencesIdPost(
|
||||
displayPreferencesId: 'usersettings',
|
||||
userId: account?.id ?? "",
|
||||
$client: _client,
|
||||
body: currentDisplayPreferences.body?.copyWith(
|
||||
customPrefs: currentSettings.toJson(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<Response> sessionsLogoutPost() => api.sessionsLogoutPost();
|
||||
|
||||
Future<Response<String>> itemsItemIdDownloadGet({
|
||||
|
|
@ -1116,3 +1147,31 @@ class JellyService {
|
|||
return _updateUserConfiguration(updated);
|
||||
}
|
||||
}
|
||||
|
||||
extension ParsedMap on Map<String, dynamic> {
|
||||
Map<String, dynamic> parseValues() {
|
||||
Map<String, dynamic> parsedMap = {};
|
||||
|
||||
for (var entry in entries) {
|
||||
String key = entry.key;
|
||||
dynamic value = entry.value;
|
||||
|
||||
if (value is String) {
|
||||
// Try to parse the string to a number or boolean
|
||||
if (int.tryParse(value) != null) {
|
||||
parsedMap[key] = int.tryParse(value);
|
||||
} else if (double.tryParse(value) != null) {
|
||||
parsedMap[key] = double.tryParse(value);
|
||||
} else if (value.toLowerCase() == 'true' || value.toLowerCase() == 'false') {
|
||||
parsedMap[key] = value.toLowerCase() == 'true';
|
||||
} else {
|
||||
parsedMap[key] = value;
|
||||
}
|
||||
} else {
|
||||
parsedMap[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return parsedMap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:fladder/models/settings/client_settings_model.dart';
|
||||
import 'package:fladder/models/settings/key_combinations.dart';
|
||||
import 'package:fladder/providers/shared_provider.dart';
|
||||
import 'package:fladder/util/custom_color_themes.dart';
|
||||
import 'package:fladder/util/debouncer.dart';
|
||||
|
|
@ -58,4 +59,15 @@ class ClientSettingsNotifier extends StateNotifier<ClientSettingsModel> {
|
|||
state = state.copyWith(schemeVariant: type ?? state.schemeVariant);
|
||||
|
||||
void setRequireWifi(bool value) => state = state.copyWith(requireWifi: value);
|
||||
|
||||
void setShortcuts(MapEntry<GlobalHotKeys, KeyCombination?> mapEntry) {
|
||||
final newShortCuts = Map.fromEntries(state.shortcuts.entries);
|
||||
newShortCuts.update(
|
||||
mapEntry.key,
|
||||
(value) => mapEntry.value,
|
||||
ifAbsent: () => mapEntry.value,
|
||||
);
|
||||
newShortCuts.removeWhere((key, value) => value == null);
|
||||
state = state.copyWith(shortcuts: newShortCuts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:screen_brightness/screen_brightness.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/video_player_provider.dart';
|
||||
|
|
@ -67,4 +69,51 @@ class VideoPlayerSettingsProviderNotifier extends StateNotifier<VideoPlayerSetti
|
|||
|
||||
void toggleOrientation(Set<DeviceOrientation>? orientation) =>
|
||||
state = state.copyWith(allowedOrientations: orientation);
|
||||
|
||||
void setShortcuts(MapEntry<VideoHotKeys, KeyCombination?> newEntry) {
|
||||
final currentShortcuts = Map.fromEntries(state.hotKeys.entries);
|
||||
currentShortcuts.update(
|
||||
newEntry.key,
|
||||
(value) => newEntry.value,
|
||||
ifAbsent: () => newEntry.value,
|
||||
);
|
||||
currentShortcuts.removeWhere((key, value) => value == null);
|
||||
state = state.copyWith(hotKeys: currentShortcuts);
|
||||
}
|
||||
|
||||
void nextChapter() {
|
||||
final chapters = ref.read(playBackModel)?.chapters ?? [];
|
||||
final currentPosition = ref.read(videoPlayerProvider.select((value) => value.lastState?.position));
|
||||
|
||||
if (chapters.isNotEmpty && currentPosition != null) {
|
||||
final currentChapter = chapters.lastWhereOrNull((element) => element.startPosition <= currentPosition);
|
||||
|
||||
if (currentChapter != null) {
|
||||
final nextChapterIndex = chapters.indexOf(currentChapter) + 1;
|
||||
if (nextChapterIndex < chapters.length) {
|
||||
ref.read(videoPlayerProvider).seek(chapters[nextChapterIndex].startPosition);
|
||||
} else {
|
||||
ref.read(videoPlayerProvider).seek(currentChapter.startPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void prevChapter() {
|
||||
final chapters = ref.read(playBackModel)?.chapters ?? [];
|
||||
final currentPosition = ref.read(videoPlayerProvider.select((value) => value.lastState?.position));
|
||||
|
||||
if (chapters.isNotEmpty && currentPosition != null) {
|
||||
final currentChapter = chapters.lastWhereOrNull((element) => element.startPosition <= currentPosition);
|
||||
|
||||
if (currentChapter != null) {
|
||||
final prevChapterIndex = chapters.indexOf(currentChapter) - 1;
|
||||
if (prevChapterIndex >= 0) {
|
||||
ref.read(videoPlayerProvider).seek(chapters[prevChapterIndex].startPosition);
|
||||
} else {
|
||||
ref.read(videoPlayerProvider).seek(currentChapter.startPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ part of 'sync_provider_helpers.dart';
|
|||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$syncedItemHash() => r'7b1178ba78529ebf65425aa4cb8b239a28c7914b';
|
||||
String _$syncedItemHash() => r'8342c557accf52fd0a8561274ecf9b77b5cf7acd';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
|
|
@ -157,7 +157,7 @@ class _SyncedItemProviderElement
|
|||
ItemBaseModel? get item => (origin as SyncedItemProvider).item;
|
||||
}
|
||||
|
||||
String _$syncedChildrenHash() => r'2b6ce1611750785060df6317ce0ea25e2dc0aeb4';
|
||||
String _$syncedChildrenHash() => r'75e25432f33e0fe31708618b7ba744430523a4d3';
|
||||
|
||||
abstract class _$SyncedChildren
|
||||
extends BuildlessAutoDisposeAsyncNotifier<List<SyncedItem>> {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ class User extends _$User {
|
|||
var response = await api.usersMeGet();
|
||||
var quickConnectStatus = await api.quickConnectEnabled();
|
||||
var systemConfiguration = await api.systemConfigurationGet();
|
||||
|
||||
final customConfig = await api.getCustomConfig();
|
||||
|
||||
if (response.isSuccessful && response.body != null) {
|
||||
userState = state?.copyWith(
|
||||
name: response.body?.name ?? state?.name ?? "",
|
||||
|
|
@ -48,6 +51,7 @@ class User extends _$User {
|
|||
userConfiguration: response.body?.configuration,
|
||||
quickConnectState: quickConnectStatus.body ?? false,
|
||||
latestItemsExcludes: response.body?.configuration?.latestItemsExcludes ?? [],
|
||||
userSettings: customConfig.body,
|
||||
);
|
||||
return response.copyWith(body: state);
|
||||
}
|
||||
|
|
@ -68,6 +72,25 @@ class User extends _$User {
|
|||
}
|
||||
}
|
||||
|
||||
void setBackwardSpeed(int value) {
|
||||
final userSettings = state?.userSettings?.copyWith(skipBackDuration: Duration(seconds: value));
|
||||
if (userSettings != null) {
|
||||
updateCustomConfig(userSettings);
|
||||
}
|
||||
}
|
||||
|
||||
void setForwardSpeed(int value) {
|
||||
final userSettings = state?.userSettings?.copyWith(skipForwardDuration: Duration(seconds: value));
|
||||
if (userSettings != null) {
|
||||
updateCustomConfig(userSettings);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Response<dynamic>> updateCustomConfig(UserSettings settings) async {
|
||||
state = state?.copyWith(userSettings: settings);
|
||||
return api.setCustomConfig(settings);
|
||||
}
|
||||
|
||||
Future<Response> refreshMetaData(
|
||||
String itemId, {
|
||||
MetadataRefresh? metadataRefreshMode,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ final showSyncButtonProviderProvider = AutoDisposeProvider<bool>.internal(
|
|||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
typedef ShowSyncButtonProviderRef = AutoDisposeProviderRef<bool>;
|
||||
String _$userHash() => r'56fca6515c42347fa99dcdcf4f2d8a977335243a';
|
||||
String _$userHash() => r'24b34a88eae11aec1e377a82d1e507f293b7816a';
|
||||
|
||||
/// See also [User].
|
||||
@ProviderFor(User)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue