Skip to content

Commit e734577

Browse files
committed
feat(windows): proper shortcuts for windows ("settings -> about" for whole list)
1 parent a67d7f1 commit e734577

File tree

9 files changed

+441
-49
lines changed

9 files changed

+441
-49
lines changed
Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
import 'dart:async';
22

3-
import 'package:flutter/foundation.dart';
3+
import 'package:flutter/material.dart';
44
import 'package:flutter/services.dart';
5+
import 'package:flutter/widgets.dart';
56

6-
import 'package:hotkey_manager/hotkey_manager.dart';
77
import 'package:window_manager/window_manager.dart';
88

9+
import 'package:namida/controller/indexer_controller.dart';
10+
import 'package:namida/controller/lyrics_controller.dart';
11+
import 'package:namida/controller/miniplayer_controller.dart';
912
import 'package:namida/controller/navigator_controller.dart';
1013
import 'package:namida/controller/platform/base.dart';
14+
import 'package:namida/controller/player_controller.dart';
15+
import 'package:namida/controller/scroll_search_controller.dart';
16+
import 'package:namida/controller/settings_controller.dart';
17+
import 'package:namida/core/enums.dart';
18+
import 'package:namida/core/extensions.dart';
19+
import 'package:namida/core/translations/language.dart';
20+
import 'package:namida/packages/mp.dart';
21+
import 'package:namida/youtube/widgets/yt_queue_chip.dart';
1122

1223
part 'shortcuts_manager_base.dart';
1324
part 'shortcuts_manager_desktop.dart';

lib/controller/platform/shortcuts_manager/shortcuts_manager_base.dart

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,30 @@ abstract class ShortcutsManager {
1111
);
1212
}
1313

14-
Future<void> init();
14+
late final Map<ShortcutKeyData, VoidCallback> bindings = Map.fromEntries(_keysToRegister.map(
15+
(e) => MapEntry(
16+
e,
17+
e.callback,
18+
),
19+
));
20+
21+
@protected
22+
List<ShortcutKeyData> get _keysToRegister;
23+
void init();
24+
void dispose();
25+
}
26+
27+
class ShortcutKeyData extends SingleActivator {
28+
final String title;
29+
final LogicalKeyboardKey key;
30+
final void Function() callback;
31+
32+
const ShortcutKeyData({
33+
required this.title,
34+
required this.key,
35+
super.control = false,
36+
super.shift = false,
37+
super.includeRepeats = false,
38+
required this.callback,
39+
}) : super(key);
1540
}

lib/controller/platform/shortcuts_manager/shortcuts_manager_desktop.dart

Lines changed: 270 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,58 +4,285 @@ part of 'shortcuts_manager.dart';
44

55
class _ShortcutsManagerDesktop extends ShortcutsManager {
66
@override
7-
Future<void> init() async {
8-
if (kDebugMode) await hotKeyManager.unregisterAll();
7+
List<ShortcutKeyData> get _keysToRegister => __keysToRegister;
98

10-
final keysToRegister = <_ShortcutKeyData>[
11-
_ShortcutKeyData(
12-
key: PhysicalKeyboardKey.escape,
9+
late final __keysToRegister = <ShortcutKeyData>[
10+
// ------------------- playback -------------------
11+
ShortcutKeyData(
12+
key: LogicalKeyboardKey.space,
13+
callback: Player.inst.togglePlayPause,
14+
title: "${lang.PLAY}/${lang.PAUSE}",
15+
),
16+
ShortcutKeyData(
17+
key: LogicalKeyboardKey.arrowLeft,
18+
callback: Player.inst.seekSecondsBackward,
19+
title: "<- ${lang.SEEK_DURATION}",
20+
),
21+
ShortcutKeyData(
22+
key: LogicalKeyboardKey.arrowRight,
23+
callback: Player.inst.seekSecondsForward,
24+
title: "${lang.SEEK_DURATION} ->",
25+
),
26+
ShortcutKeyData(
27+
key: LogicalKeyboardKey.arrowUp,
28+
control: true,
29+
includeRepeats: true,
30+
callback: Player.inst.volumeUp,
31+
title: "${lang.VOLUME} ↑",
32+
),
33+
ShortcutKeyData(
34+
key: LogicalKeyboardKey.arrowDown,
35+
control: true,
36+
includeRepeats: true,
37+
callback: Player.inst.volumeDown,
38+
title: "${lang.VOLUME} ↓",
39+
),
40+
ShortcutKeyData(
41+
key: LogicalKeyboardKey.arrowLeft,
42+
control: true,
43+
callback: Player.inst.previous,
44+
title: lang.PREVIOUS,
45+
),
46+
ShortcutKeyData(
47+
key: LogicalKeyboardKey.arrowRight,
48+
control: true,
49+
callback: Player.inst.next,
50+
title: lang.NEXT,
51+
),
52+
53+
// -------------------
54+
ShortcutKeyData(
55+
key: LogicalKeyboardKey.keyF,
56+
control: true,
57+
callback: () => ScrollSearchController.inst.searchBarKey.currentState?.openCloseSearchBar(
58+
forceOpen: ScrollSearchController.inst.searchBarKey.currentState?.focusNode.hasPrimaryFocus != true,
59+
),
60+
title: lang.SEARCH,
61+
),
62+
ShortcutKeyData(
63+
key: LogicalKeyboardKey.keyR,
64+
control: true,
65+
callback: Indexer.inst.refreshLibraryAndCheckForDiff,
66+
title: lang.REFRESH_LIBRARY,
67+
),
68+
ShortcutKeyData(
69+
key: LogicalKeyboardKey.keyE,
70+
control: true,
71+
callback: () {
72+
_executeMiniPlayers(
73+
(localPlayer, ytPlayer, ytQueueChip) {
74+
if (ytPlayer != null) {
75+
if (ytPlayer.isExpanded) {
76+
ytPlayer.animateToState(false);
77+
} else {
78+
ytPlayer.animateToState(true);
79+
}
80+
} else {
81+
if (localPlayer.isMinimized) {
82+
localPlayer.snapToExpanded();
83+
} else {
84+
localPlayer.snapToMini();
85+
}
86+
}
87+
},
88+
);
89+
},
90+
title: lang.OPEN_MINIPLAYER,
91+
),
92+
ShortcutKeyData(
93+
key: LogicalKeyboardKey.keyQ,
94+
control: true,
95+
callback: () {
96+
_executeMiniPlayers(
97+
(localPlayer, ytPlayer, ytQueueChip) {
98+
if (ytPlayer != null) {
99+
if (!ytPlayer.isExpanded) ytPlayer.animateToState(true);
100+
_executeYtQueueSheet(ytQueueChip, (chip) => chip.toggleSheet());
101+
} else {
102+
if (localPlayer.isInQueue) {
103+
localPlayer.snapToExpanded();
104+
} else {
105+
localPlayer.snapToQueue();
106+
}
107+
}
108+
},
109+
);
110+
},
111+
title: lang.OPEN_QUEUE,
112+
),
113+
ShortcutKeyData(
114+
key: LogicalKeyboardKey.keyL,
115+
control: true,
116+
callback: () {
117+
settings.save(enableLyrics: !settings.enableLyrics.value);
118+
final currentItem = Player.inst.currentItem.value;
119+
if (currentItem != null) {
120+
Lyrics.inst.updateLyrics(currentItem);
121+
}
122+
},
123+
title: lang.LYRICS,
124+
),
125+
ShortcutKeyData(
126+
key: LogicalKeyboardKey.keyL,
127+
control: true,
128+
shift: true,
129+
callback: () {
130+
final fullscreenState = Lyrics.inst.lrcViewKeyFullscreen.currentState;
131+
if (fullscreenState != null) {
132+
fullscreenState.exitFullScreen();
133+
} else {
134+
Lyrics.inst.lrcViewKey.currentState?.enterFullScreen();
135+
}
136+
},
137+
title: "${lang.LYRICS} (${lang.FULLSCREEN})",
138+
),
139+
ShortcutKeyData(
140+
key: LogicalKeyboardKey.tab,
141+
control: true,
142+
callback: () {
143+
final e = settings.player.repeatMode.value.nextElement(RepeatMode.values);
144+
settings.player.save(repeatMode: e);
145+
},
146+
title: lang.REPEAT_MODE,
147+
),
148+
// -----------------
149+
for (int i = 1; i <= 9; i++)
150+
ShortcutKeyData(
151+
key: LogicalKeyboardKey(0x00000000030 + i),
152+
control: true,
13153
callback: () {
14-
if (NamidaNavigator.inst.isInFullScreen) {
15-
NamidaNavigator.inst.exitFullScreen();
16-
} else {
17-
NamidaNavigator.inst.popPage();
154+
try {
155+
final tab = settings.libraryTabs.value[i - 1];
156+
ScrollSearchController.inst.animatePageController(tab);
157+
} catch (_) {
158+
// -- index larger than tabs length
18159
}
19160
},
161+
title: lang.LIBRARY_TABS,
20162
),
21-
_ShortcutKeyData(
22-
key: PhysicalKeyboardKey.f11,
23-
callback: () async {
24-
final isFullscreen = await windowManager.isFullScreen();
25-
windowManager.setFullScreen(!isFullscreen);
26-
},
27-
),
28-
];
29163

30-
for (final k in keysToRegister) {
31-
_register(data: k);
32-
}
33-
}
164+
// ------------------- media buttons -------------------
165+
ShortcutKeyData(
166+
key: LogicalKeyboardKey.mediaPlay,
167+
callback: Player.inst.play,
168+
title: lang.PLAY,
169+
),
170+
ShortcutKeyData(
171+
key: LogicalKeyboardKey.mediaPause,
172+
callback: Player.inst.pause,
173+
title: lang.PAUSE,
174+
),
175+
ShortcutKeyData(
176+
key: LogicalKeyboardKey.mediaPlayPause,
177+
callback: Player.inst.togglePlayPause,
178+
title: "${lang.PLAY}/${lang.PAUSE}",
179+
),
180+
ShortcutKeyData(
181+
key: LogicalKeyboardKey.mediaStop,
182+
callback: Player.inst.pause,
183+
title: lang.PAUSE,
184+
),
185+
ShortcutKeyData(
186+
key: LogicalKeyboardKey.mediaFastForward,
187+
callback: Player.inst.seekSecondsForward,
188+
title: "${lang.SEEK_DURATION} ->",
189+
),
190+
ShortcutKeyData(
191+
key: LogicalKeyboardKey.mediaRewind,
192+
callback: Player.inst.seekSecondsBackward,
193+
title: "<- ${lang.SEEK_DURATION}",
194+
),
195+
ShortcutKeyData(
196+
key: LogicalKeyboardKey.mediaTrackNext,
197+
callback: Player.inst.next,
198+
title: lang.NEXT,
199+
),
200+
ShortcutKeyData(
201+
key: LogicalKeyboardKey.mediaTrackPrevious,
202+
callback: Player.inst.previous,
203+
title: lang.PREVIOUS,
204+
),
205+
ShortcutKeyData(
206+
key: LogicalKeyboardKey(0x000000b0),
207+
callback: Player.inst.next,
208+
title: lang.NEXT,
209+
),
210+
ShortcutKeyData(
211+
key: LogicalKeyboardKey(0x000000b1),
212+
callback: Player.inst.previous,
213+
title: lang.PREVIOUS,
214+
),
215+
// ================
216+
ShortcutKeyData(
217+
key: LogicalKeyboardKey.f11,
218+
callback: () async {
219+
final isFullscreen = await windowManager.isFullScreen();
220+
windowManager.setFullScreen(!isFullscreen);
221+
},
222+
title: lang.FULLSCREEN,
223+
),
224+
ShortcutKeyData(
225+
key: LogicalKeyboardKey.escape,
226+
callback: () {
227+
if (NamidaNavigator.inst.isInFullScreen) {
228+
NamidaNavigator.inst.exitFullScreen();
229+
} else {
230+
NamidaNavigator.inst.popPage();
231+
}
232+
},
233+
title: lang.EXIT,
234+
),
235+
];
34236

35-
void _register({required _ShortcutKeyData data}) {
36-
hotKeyManager.register(
37-
HotKey(
38-
key: data.key,
39-
scope: data.scope,
40-
modifiers: data.modifiers,
41-
),
42-
keyDownHandler: (_) {
43-
data.callback();
237+
FocusAttachment? _attachment;
238+
239+
@override
240+
void init() {
241+
_attachment = FocusManager.instance.rootScope.attach(
242+
null,
243+
onKeyEvent: (node, event) {
244+
for (final ShortcutActivator activator in bindings.keys) {
245+
if (activator.accepts(event, HardwareKeyboard.instance)) {
246+
bindings[activator]!.call();
247+
return KeyEventResult.handled;
248+
}
249+
}
250+
return KeyEventResult.ignored;
44251
},
45252
);
46253
}
47-
}
48254

49-
class _ShortcutKeyData {
50-
final KeyboardKey key;
51-
final List<HotKeyModifier>? modifiers;
52-
final HotKeyScope scope;
53-
final VoidCallback callback;
54-
55-
_ShortcutKeyData({
56-
required this.key,
57-
this.modifiers,
58-
this.scope = HotKeyScope.inapp,
59-
required this.callback,
60-
});
255+
@override
256+
void dispose() {
257+
_attachment?.detach();
258+
}
259+
260+
void _executeMiniPlayers(
261+
void Function(
262+
MiniPlayerController localPlayer,
263+
NamidaYTMiniplayerState? ytPlayer,
264+
YTMiniplayerQueueChipState? ytQueueChip,
265+
) callback) {
266+
callback(
267+
MiniPlayerController.inst,
268+
MiniPlayerController.inst.ytMiniplayerKey.currentState,
269+
NamidaNavigator.inst.ytQueueSheetKey.currentState,
270+
);
271+
}
272+
273+
void _executeYtQueueSheet(YTMiniplayerQueueChipState? ytQueueChip, void Function(YTMiniplayerQueueChipState ytQueueChip) callback) {
274+
final ytQueue = NamidaNavigator.inst.ytQueueSheetKey.currentState;
275+
if (ytQueue != null) {
276+
callback(ytQueue);
277+
return;
278+
}
279+
280+
Timer(
281+
const Duration(milliseconds: 100),
282+
() {
283+
final ytQueue = NamidaNavigator.inst.ytQueueSheetKey.currentState;
284+
if (ytQueue != null) callback(ytQueue);
285+
},
286+
);
287+
}
61288
}

0 commit comments

Comments
 (0)