Skip to content

Commit

Permalink
feat: custom shortcut manager and registry
Browse files Browse the repository at this point in the history
  • Loading branch information
KRTirtho committed Apr 2, 2023
1 parent 9093d21 commit 9f99216
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 59 deletions.
121 changes: 121 additions & 0 deletions lib/collections/shortcuts.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import 'package:flemozi/components/ui/vertical_tabs.dart';
import 'package:flemozi/pages/settings/settings.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:super_hot_key/super_hot_key.dart';

enum ShortcutType {
application,
system,
}

typedef ProviderReader = T Function<T>(ProviderListenable<T> provider);

abstract class ShortcutActions {
static void openFlemozi(ProviderReader read, BuildContext context) {
print("Open Flemozi");
}

static void switchTabs(ProviderReader read, BuildContext context) {
const length = 3;
final tabs = read(tabsIndex);
read(tabsIndex.notifier).state = (tabs + 1) % length;
}

static void openSettings(ProviderReader read, BuildContext context) {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const Settings(),
),
);
}
}

enum FlemoziShortcuts {
openFlemozi(
"Open Flemozi",
ShortcutActions.openFlemozi,
ShortcutType.system,
),

switchTabs(
"Switch Tabs",
ShortcutActions.switchTabs,
ShortcutType.application,
),

openSettings(
"Open Settings",
ShortcutActions.openSettings,
ShortcutType.application,
);

final String title;
final void Function(ProviderReader read, BuildContext context) action;
final ShortcutType type;
const FlemoziShortcuts(
this.title,
this.action,
this.type,
);
}

class FlemoziShortcutKey {
final KeyboardKey trigger;
final bool alt;
final bool shift;
final bool control;
final bool meta;
const FlemoziShortcutKey(
this.trigger, {
this.alt = false,
this.shift = false,
this.control = false,
this.meta = false,
});

HotKeyDefinition toHotKeyDefinition() {
return HotKeyDefinition(
key: trigger as PhysicalKeyboardKey,
alt: alt,
shift: shift,
control: control,
meta: meta,
);
}

SingleActivator toSingleActivator() {
return SingleActivator(
trigger as LogicalKeyboardKey,
alt: alt,
shift: shift,
control: control,
meta: meta,
);
}

Set<String> get keyLabels => {
if (shift == true) 'Shift',
if (control == true) 'Ctrl',
if (alt == true) 'Alt',
if (meta == true) 'Meta',
trigger.toString(),
};
}

final Map<FlemoziShortcuts, FlemoziShortcutKey> defaultFlemoziShortcuts = {
FlemoziShortcuts.openFlemozi: const FlemoziShortcutKey(
PhysicalKeyboardKey.period,
alt: true,
control: true,
),
FlemoziShortcuts.switchTabs: const FlemoziShortcutKey(
LogicalKeyboardKey.tab,
control: true,
),
FlemoziShortcuts.openSettings: const FlemoziShortcutKey(
LogicalKeyboardKey.comma,
control: true,
),
};
68 changes: 53 additions & 15 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import 'dart:io';
import 'package:fl_query/fl_query.dart';
import 'package:flemozi/api/api.dart';
import 'package:flemozi/collections/env.dart';
import 'package:flemozi/collections/shortcuts.dart';
import 'package:flemozi/intents/close_window.dart';
import 'package:flemozi/intents/switch_tabs.dart';
import 'package:flemozi/pages/root.dart';
import 'package:flemozi/providers/shortcut.dart';
import 'package:flemozi/utils/platform.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
Expand Down Expand Up @@ -127,6 +129,8 @@ void main(List<String> args) async {
runApp(const ProviderScope(child: Flemozi()));
}

final navigatorKey = GlobalKey<NavigatorState>();

class Flemozi extends StatefulHookConsumerWidget {
const Flemozi({super.key});

Expand Down Expand Up @@ -170,10 +174,19 @@ class _FlemoziState extends ConsumerState<Flemozi> with WidgetsBindingObserver {

@override
Widget build(BuildContext context) {
final shortcutsNotifier = ref.watch(ShortcutNotifier.provider.notifier);
final shortcuts = ref.watch(ShortcutNotifier.provider);

useEffect(() {
shortcutsNotifier.initialize(context);
return null;
}, const []);

return QueryClientProvider(
child: MaterialApp(
debugShowCheckedModeBanner: false,
themeMode: ThemeMode.dark,
navigatorKey: navigatorKey,
theme: ThemeData.light(useMaterial3: true),
darkTheme: ThemeData(
useMaterial3: true,
Expand All @@ -199,29 +212,54 @@ class _FlemoziState extends ConsumerState<Flemozi> with WidgetsBindingObserver {
),
builder: (context, child) {
final isDark = Theme.of(context).brightness == Brightness.dark;
return Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color:
isDark ? Colors.grey[900]!.withOpacity(.5) : Colors.white60,
borderRadius: BorderRadius.circular(10),
),
child: DragToResizeArea(child: child!),
);
return HookBuilder(builder: (context) {
final appShortcuts = useMemoized(
() => Map<SingleActivator, VoidCallback>.fromEntries(
shortcuts.entries
.where(
(entry) => entry.key.type == ShortcutType.application)
.map(
(entry) {
return MapEntry(
entry.value.toSingleActivator(),
() => entry.key.action(
ref.read,
navigatorKey.currentContext ?? context,
),
);
},
),
),
[shortcuts],
);

return CallbackShortcuts(
bindings: {
...appShortcuts,
LogicalKeySet(LogicalKeyboardKey.escape): () =>
CloseWindowAction().invoke(const CloseWindowIntent())
},
child: Container(
margin: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: isDark
? Colors.grey[900]!.withOpacity(.5)
: Colors.white60,
borderRadius: BorderRadius.circular(10),
),
child: DragToResizeArea(child: child!),
),
);
});
},
home: const RootPage(),
shortcuts: {
...WidgetsApp.defaultShortcuts,
LogicalKeySet(LogicalKeyboardKey.escape): const CloseWindowIntent(),
LogicalKeySet(
LogicalKeyboardKey.control,
LogicalKeyboardKey.tab,
): SwitchTabsIntent(ref),
},
actions: {
...WidgetsApp.defaultActions,
CloseWindowIntent: CloseWindowAction(),
SwitchTabsIntent: SwitchTabsAction(),
},
),
);
Expand Down
72 changes: 28 additions & 44 deletions lib/pages/root.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@ import 'package:flemozi/components/root/gif.dart';
import 'package:flemozi/components/ui/top_bar.dart';
import 'package:flemozi/components/ui/vertical_tabs.dart';
import 'package:flemozi/hooks/use_window_listeners.dart';
import 'package:flemozi/intents/close_window.dart';
import 'package:flemozi/pages/settings/settings.dart';
import 'package:flemozi/utils/platform.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:url_launcher/url_launcher_string.dart';

class RootPage extends HookWidget {
class RootPage extends HookConsumerWidget {
const RootPage({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
Widget build(BuildContext context, ref) {
final theme = Theme.of(context);
final windowIsShowing = useState(true);

Expand Down Expand Up @@ -94,45 +92,31 @@ class RootPage extends HookWidget {
return null;
}, []);

return CallbackShortcuts(
bindings: {
LogicalKeySet(
LogicalKeyboardKey.control,
LogicalKeyboardKey.comma,
): () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const Settings(),
),
),
LogicalKeySet(LogicalKeyboardKey.escape): () =>
CloseWindowAction().invoke(const CloseWindowIntent()),
},
child: Scaffold(
appBar: const TopBar(),
body: windowIsShowing.value
? VerticalTabs(
tabs: const [
Tooltip(
message: 'Emoji',
child: Icon(Icons.emoji_emotions),
),
Tooltip(
message: 'GIFs',
child: Icon(Icons.gif_rounded),
),
Tooltip(
message: 'Emoticons',
child: Text(':)'),
),
],
children: const [
Emoji(),
Gif(),
Emoticon(),
],
)
: const SizedBox.shrink(),
),
return Scaffold(
appBar: const TopBar(),
body: windowIsShowing.value
? VerticalTabs(
tabs: const [
Tooltip(
message: 'Emoji',
child: Icon(Icons.emoji_emotions),
),
Tooltip(
message: 'GIFs',
child: Icon(Icons.gif_rounded),
),
Tooltip(
message: 'Emoticons',
child: Text(':)'),
),
],
children: const [
Emoji(),
Gif(),
Emoticon(),
],
)
: const SizedBox.shrink(),
);
}
}
33 changes: 33 additions & 0 deletions lib/providers/shortcut.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import 'package:flemozi/collections/shortcuts.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:super_hot_key/super_hot_key.dart';

class ShortcutNotifier
extends StateNotifier<Map<FlemoziShortcuts, FlemoziShortcutKey>> {
final Ref ref;
ShortcutNotifier(super.state, this.ref);

void initialize(BuildContext context) async {
final globalShortcuts = FlemoziShortcuts.values
.where((shortcut) => shortcut.type == ShortcutType.system)
.toList();

for (var shortcut in globalShortcuts) {
await HotKey.create(
definition: state[shortcut]!.toHotKeyDefinition(),
callback: () {
return shortcut.action(ref.read, context);
},
);
}
}

static final provider = StateNotifierProvider<ShortcutNotifier,
Map<FlemoziShortcuts, FlemoziShortcutKey>>(
(ref) => ShortcutNotifier(
defaultFlemoziShortcuts,
ref,
),
);
}

0 comments on commit 9f99216

Please sign in to comment.