diff --git a/lib/pods/chat/chat_subscribe.dart b/lib/pods/chat/chat_subscribe.dart index 4af7cbf4..7c6a9b88 100644 --- a/lib/pods/chat/chat_subscribe.dart +++ b/lib/pods/chat/chat_subscribe.dart @@ -93,7 +93,6 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier { // Set up periodic subscribe timer (every 5 minutes) _periodicSubscribeTimer = Timer.periodic(const Duration(minutes: 5), (_) { - final wsState = ref.read(websocketStateProvider.notifier); wsState.sendMessage( jsonEncode( WebSocketPacket( diff --git a/lib/screens/dashboard/dash.dart b/lib/screens/dashboard/dash.dart index 9440810e..44b2fab8 100644 --- a/lib/screens/dashboard/dash.dart +++ b/lib/screens/dashboard/dash.dart @@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/pods/chat/chat_room.dart'; import 'package:island/pods/event_calendar.dart'; import 'package:island/screens/chat/chat.dart'; +import 'package:island/services/event_bus.dart'; import 'package:island/services/responsive.dart'; import 'package:island/widgets/account/fortune_graph.dart'; import 'package:island/widgets/account/friends_overview.dart'; @@ -59,16 +60,27 @@ class DashboardGrid extends HookConsumerWidget { constraints: const BoxConstraints(minHeight: 56), leading: const Icon(Symbols.search).padding(horizontal: 24), readOnly: true, + onTap: () { + eventBus.fire(CommandPaletteTriggerEvent()); + }, ), ), Expanded( - child: SingleChildScrollView( - padding: isWide - ? const EdgeInsets.symmetric(horizontal: 24) - : const EdgeInsets.only(bottom: 64), - scrollDirection: isWide ? Axis.horizontal : Axis.vertical, - child: isWide ? _DashboardGridWide() : _DashboardGridNarrow(), - ).clipRRect(topLeft: 12, topRight: 12).padding(horizontal: 24), + child: + SingleChildScrollView( + padding: isWide + ? const EdgeInsets.symmetric(horizontal: 24) + : const EdgeInsets.only(bottom: 64), + scrollDirection: isWide ? Axis.horizontal : Axis.vertical, + child: isWide + ? _DashboardGridWide() + : _DashboardGridNarrow(), + ) + .clipRRect( + topLeft: isWide ? 0 : 12, + topRight: isWide ? 0 : 12, + ) + .padding(horizontal: isWide ? 0 : 24), ), ], ), @@ -545,16 +557,22 @@ class FortuneCard extends HookWidget { margin: EdgeInsets.zero, color: Theme.of(context).colorScheme.surfaceContainer, shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(8)), + borderRadius: BorderRadius.all(Radius.circular(12)), ), child: Row( spacing: 8, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Expanded(child: Text(fortune['text']!)), + Expanded( + child: Text( + fortune['text']!, + maxLines: 2, + overflow: TextOverflow.fade, + ), + ), Text(fortune['author']!).bold(), ], - ).padding(horizontal: 24), + ).padding(horizontal: 16), ).height(48); } } diff --git a/lib/services/event_bus.dart b/lib/services/event_bus.dart index 5e5eeb41..c7fdc214 100644 --- a/lib/services/event_bus.dart +++ b/lib/services/event_bus.dart @@ -23,3 +23,8 @@ class OidcAuthCallbackEvent { const OidcAuthCallbackEvent(this.challengeId); } + +/// Event fired to trigger the command palette +class CommandPaletteTriggerEvent { + const CommandPaletteTriggerEvent(); +} diff --git a/lib/widgets/app_scaffold.dart b/lib/widgets/app_scaffold.dart index 9edc8572..d85b0b1d 100644 --- a/lib/widgets/app_scaffold.dart +++ b/lib/widgets/app_scaffold.dart @@ -12,8 +12,10 @@ import 'package:island/pods/config.dart'; import 'package:island/route.dart'; import 'package:island/pods/userinfo.dart'; import 'package:island/pods/websocket.dart'; +import 'package:island/services/event_bus.dart'; import 'package:island/services/responsive.dart'; import 'package:island/widgets/alert.dart'; +import 'package:island/widgets/cmp/pattle.dart'; import 'package:island/widgets/upload_overlay.dart'; import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:path_provider/path_provider.dart'; @@ -36,6 +38,14 @@ class WindowScaffold extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final isMaximized = useState(false); + final showPalette = useState(false); + final lastShiftTime = useState(null); + final keyboardFocusNode = useFocusNode(); + + useEffect(() { + keyboardFocusNode.requestFocus(); + return null; + }, []); // Add window resize listener for desktop platforms useEffect(() { @@ -68,6 +78,15 @@ class WindowScaffold extends HookConsumerWidget { return null; }, []); + // Event bus listener for command palette + final subscription = useMemoized( + () => eventBus.on().listen( + (_) => showPalette.value = true, + ), + [], + ); + useEffect(() => subscription.cancel, [subscription]); + final router = ref.watch(routerProvider); final pageActionsButton = [ @@ -98,19 +117,32 @@ class WindowScaffold extends HookConsumerWidget { shortcuts: { LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(), }, - child: Actions( - actions: >{PopIntent: PopAction(ref)}, - child: Material( - color: Theme.of(context).colorScheme.surfaceContainer, - child: Stack( - fit: StackFit.expand, - children: [ - Column( - children: [ - DragToMoveArea( - child: - Platform.isMacOS - ? Stack( + child: KeyboardListener( + focusNode: keyboardFocusNode, + onKeyEvent: (event) { + if (event is KeyDownEvent && + (event.logicalKey == LogicalKeyboardKey.shiftLeft || + event.logicalKey == LogicalKeyboardKey.shiftRight)) { + final now = DateTime.now(); + if (lastShiftTime.value != null && + now.difference(lastShiftTime.value!).inMilliseconds < 300) { + showPalette.value = true; + } + lastShiftTime.value = now; + } + }, + child: Actions( + actions: >{PopIntent: PopAction(ref)}, + child: Material( + color: Theme.of(context).colorScheme.surfaceContainer, + child: Stack( + fit: StackFit.expand, + children: [ + Column( + children: [ + DragToMoveArea( + child: Platform.isMacOS + ? Stack( alignment: Alignment.center, children: [ if (isWideScreen(context)) @@ -126,15 +158,14 @@ class WindowScaffold extends HookConsumerWidget { 'Solar Network', textAlign: TextAlign.center, style: TextStyle( - color: - Theme.of( - context, - ).colorScheme.onSurface, + color: Theme.of( + context, + ).colorScheme.onSurface, ), ), ], ) - : Row( + : Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -193,13 +224,18 @@ class WindowScaffold extends HookConsumerWidget { ), ], ), + ), + Expanded(child: child), + ], + ), + _WebSocketIndicator(), + const UploadOverlay(), + if (showPalette.value) + CommandPattleWidget( + onDismiss: () => showPalette.value = false, ), - Expanded(child: child), - ], - ), - _WebSocketIndicator(), - const UploadOverlay(), - ], + ], + ), ), ), ), @@ -210,15 +246,32 @@ class WindowScaffold extends HookConsumerWidget { shortcuts: { LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(), }, - child: Actions( - actions: >{PopIntent: PopAction(ref)}, - child: Stack( - fit: StackFit.expand, - children: [ - Positioned.fill(child: child), - _WebSocketIndicator(), - const UploadOverlay(), - ], + child: KeyboardListener( + focusNode: keyboardFocusNode, + onKeyEvent: (event) { + if (event is KeyDownEvent && + (event.logicalKey == LogicalKeyboardKey.shiftLeft || + event.logicalKey == LogicalKeyboardKey.shiftRight)) { + final now = DateTime.now(); + if (lastShiftTime.value != null && + now.difference(lastShiftTime.value!).inMilliseconds < 300) { + showPalette.value = true; + } + lastShiftTime.value = now; + } + }, + child: Actions( + actions: >{PopIntent: PopAction(ref)}, + child: Stack( + fit: StackFit.expand, + children: [ + Positioned.fill(child: child), + _WebSocketIndicator(), + const UploadOverlay(), + if (showPalette.value) + CommandPattleWidget(onDismiss: () => showPalette.value = false), + ], + ), ), ), ); @@ -407,8 +460,8 @@ class PageBackButton extends StatelessWidget { color: color, context.canPop() ? (!kIsWeb && (Platform.isMacOS || Platform.isIOS)) - ? Symbols.arrow_back_ios_new - : Symbols.arrow_back + ? Symbols.arrow_back_ios_new + : Symbols.arrow_back : Symbols.home, shadows: shadows, ), @@ -463,11 +516,10 @@ class AppBackground extends ConsumerWidget { ); }, loading: () => const SizedBox(), - error: - (_, _) => Material( - color: Theme.of(context).colorScheme.surface, - child: child, - ), + error: (_, _) => Material( + color: Theme.of(context).colorScheme.surface, + child: child, + ), ); } @@ -519,10 +571,10 @@ class _WebSocketIndicator extends HookConsumerWidget { duration: Duration(milliseconds: 1850), top: user.value == null || - user.value == null || - websocketState == WebSocketState.connected() - ? -indicatorHeight - : 0, + user.value == null || + websocketState == WebSocketState.connected() + ? -indicatorHeight + : 0, curve: Curves.fastLinearToSlowEaseIn, left: 0, right: 0, @@ -531,17 +583,16 @@ class _WebSocketIndicator extends HookConsumerWidget { child: Material( elevation: user.value == null || websocketState == WebSocketState.connected() - ? 0 - : 4, + ? 0 + : 4, child: AnimatedContainer( duration: Duration(milliseconds: 300), color: indicatorColor, child: Center( - child: - Text( - indicatorText, - style: TextStyle(color: Colors.white, fontSize: 16), - ).tr(), + child: Text( + indicatorText, + style: TextStyle(color: Colors.white, fontSize: 16), + ).tr(), ).padding(top: MediaQuery.of(context).padding.top), ), ), diff --git a/lib/widgets/cmp/pattle.dart b/lib/widgets/cmp/pattle.dart new file mode 100644 index 00000000..3afbb519 --- /dev/null +++ b/lib/widgets/cmp/pattle.dart @@ -0,0 +1,336 @@ +import 'dart:io'; +import 'dart:math' as math; +import 'dart:ui'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:island/models/chat.dart'; +import 'package:island/pods/chat/chat_room.dart'; +import 'package:island/pods/chat/chat_summary.dart'; +import 'package:island/pods/userinfo.dart'; +import 'package:island/route.dart'; +import 'package:island/services/responsive.dart'; +import 'package:island/widgets/content/cloud_files.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:relative_time/relative_time.dart'; +import 'package:styled_widget/styled_widget.dart'; + +class CommandPattleWidget extends HookConsumerWidget { + final VoidCallback onDismiss; + + const CommandPattleWidget({super.key, required this.onDismiss}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final textController = useTextEditingController(); + final focusNode = useFocusNode(); + final searchQuery = useState(''); + final focusedIndex = useState(null); + + useEffect(() { + focusNode.requestFocus(); + return null; + }, []); + + useEffect(() { + void listener() { + searchQuery.value = textController.text; + // Reset focused index when search changes + focusedIndex.value = null; + } + + textController.addListener(listener); + return () => textController.removeListener(listener); + }, [textController]); + + final chatRooms = ref.watch(chatRoomJoinedProvider); + final userInfo = ref.watch(userInfoProvider); + + bool isDesktop() => + kIsWeb || + (!kIsWeb && + (Platform.isWindows || Platform.isLinux || Platform.isMacOS)); + + final filteredRooms = chatRooms.maybeWhen( + data: (rooms) { + if (searchQuery.value.isEmpty) return []; + return rooms + .where((room) { + final title = room.name ?? ''; + final desc = room.description ?? ''; + final query = searchQuery.value.toLowerCase(); + return title.toLowerCase().contains(query) || + desc.toLowerCase().contains(query) || + (room.members?.any( + (member) => + member.account.name.contains(query) || + member.account.nick.contains(query), + ) ?? + false); + }) + .take(5) // Limit to 5 results + .toList(); + }, + orElse: () => [], + ); + + return KeyboardListener( + focusNode: FocusNode(), + onKeyEvent: (event) { + if (event is KeyDownEvent) { + if (event.logicalKey == LogicalKeyboardKey.escape) { + onDismiss(); + } else if (isDesktop()) { + if (event.logicalKey == LogicalKeyboardKey.enter || + event.logicalKey == LogicalKeyboardKey.numpadEnter) { + if (focusedIndex.value != null && + focusedIndex.value! < filteredRooms.length) { + _navigateToRoom( + context, + ref, + filteredRooms[focusedIndex.value!], + ); + } + } else if (event.logicalKey == LogicalKeyboardKey.arrowUp) { + if (filteredRooms.isNotEmpty) { + if (focusedIndex.value == null) { + focusedIndex.value = 0; + } else { + focusedIndex.value = math.max(0, focusedIndex.value! - 1); + } + } + } else if (event.logicalKey == LogicalKeyboardKey.arrowDown) { + if (filteredRooms.isNotEmpty) { + if (focusedIndex.value == null) { + focusedIndex.value = 0; + } else { + focusedIndex.value = math.min( + filteredRooms.length - 1, + focusedIndex.value! + 1, + ); + } + } + } + } + } + }, + child: GestureDetector( + onTap: onDismiss, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5), + child: Container( + color: Colors.black.withOpacity(0.5), + child: Center( + child: GestureDetector( + onTap: () {}, // Prevent tap from dismissing when tapping inside + child: Container( + width: math.max(MediaQuery.of(context).size.width * 0.6, 320), + constraints: const BoxConstraints( + maxWidth: 600, + maxHeight: 500, + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + borderRadius: BorderRadius.circular(24), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 10, + spreadRadius: 2, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SearchBar( + controller: textController, + focusNode: focusNode, + hintText: 'Search chats...', + leading: const Icon( + Symbols.keyboard_command_key, + ).padding(horizontal: 8), + onSubmitted: (_) { + if (filteredRooms.isNotEmpty) { + _navigateToRoom(context, ref, filteredRooms.first); + } + }, + ), + if (filteredRooms.isNotEmpty) + Flexible( + child: ListView.builder( + shrinkWrap: true, + itemCount: filteredRooms.length, + itemBuilder: (context, index) { + final room = filteredRooms[index]; + return _ChatRoomSearchResult( + room: room, + isFocused: index == focusedIndex.value, + onTap: () => + _navigateToRoom(context, ref, room), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ), + ); + } + + void _navigateToRoom(BuildContext context, WidgetRef ref, SnChatRoom room) { + onDismiss(); + if (isWideScreen(context)) { + ref + .read(routerProvider) + .replaceNamed('chatRoom', pathParameters: {'id': room.id}); + } else { + ref + .read(routerProvider) + .pushNamed('chatRoom', pathParameters: {'id': room.id}); + } + } +} + +class _ChatRoomSearchResult extends HookConsumerWidget { + final SnChatRoom room; + final bool isFocused; + final VoidCallback onTap; + + const _ChatRoomSearchResult({ + required this.room, + required this.isFocused, + required this.onTap, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final userInfo = ref.watch(userInfoProvider); + final summary = ref + .watch(chatSummaryProvider) + .whenData((summaries) => summaries[room.id]); + + var validMembers = room.members ?? []; + if (validMembers.isNotEmpty && userInfo.value != null) { + validMembers = validMembers + .where((e) => e.accountId != userInfo.value!.id) + .toList(); + } + + String titleText; + if (room.type == 1 && room.name == null) { + if (room.members?.isNotEmpty ?? false) { + titleText = validMembers.map((e) => e.account.nick).join(', '); + } else { + titleText = 'Direct Message'; + } + } else { + titleText = room.name ?? ''; + } + + Widget buildSubtitle() { + return summary.when( + data: (data) => data == null + ? (room.type == 1 && room.description == null + ? Text( + validMembers.map((e) => '@${e.account.name}').join(', '), + ) + : Text(room.description ?? '')) + : Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (data.unreadCount > 0) + Text( + 'unreadMessages'.plural(data.unreadCount), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.primary, + ), + ), + if (data.lastMessage == null) + room.type == 1 && room.description == null + ? Text( + validMembers + .map((e) => '@${e.account.name}') + .join(', '), + ) + : Text(room.description ?? '') + else + Row( + spacing: 4, + children: [ + Badge( + label: Text(data.lastMessage!.sender.account.nick), + textColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: Theme.of( + context, + ).colorScheme.primary, + ), + Expanded( + child: Text( + (data.lastMessage!.content?.isNotEmpty ?? false) + ? data.lastMessage!.content! + : 'messageNone'.tr(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodySmall, + ), + ), + Align( + alignment: Alignment.centerRight, + child: Text( + RelativeTime( + context, + ).format(data.lastMessage!.createdAt), + style: Theme.of(context).textTheme.bodySmall, + ), + ), + ], + ), + ], + ), + loading: () => room.type == 1 && room.description == null + ? Text(validMembers.map((e) => '@${e.account.name}').join(', ')) + : Text(room.description ?? ''), + error: (_, _) => room.type == 1 && room.description == null + ? Text(validMembers.map((e) => '@${e.account.name}').join(', ')) + : Text(room.description ?? ''), + ); + } + + final isDirect = room.type == 1; + + return ListTile( + tileColor: isFocused + ? Theme.of(context).colorScheme.surfaceContainerHighest + : null, + leading: Badge( + isLabelVisible: summary.maybeWhen( + data: (data) => (data?.unreadCount ?? 0) > 0, + orElse: () => false, + ), + child: (isDirect && room.picture?.id == null) + ? SplitAvatarWidget( + filesId: validMembers + .map((e) => e.account.profile.picture?.id) + .toList(), + ) + : room.picture?.id == null + ? CircleAvatar(child: Text((room.name ?? 'DM')[0].toUpperCase())) + : ProfilePictureWidget( + fileId: room.picture?.id, + ), // Placeholder for now + ), + title: Text(titleText), + subtitle: buildSubtitle(), + onTap: onTap, + ); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c3ed320d..cf03ccaf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin"); flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar); + g_autoptr(FlPluginRegistrar) hotkey_manager_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerLinuxPlugin"); + hotkey_manager_linux_plugin_register_with_registrar(hotkey_manager_linux_registrar); g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin"); irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 02828327..e641c99b 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_timezone flutter_udid flutter_webrtc + hotkey_manager_linux irondash_engine_context livekit_client media_kit_libs_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 614b1371..ea25994e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -22,6 +22,7 @@ import flutter_timezone import flutter_udid import flutter_webrtc import gal +import hotkey_manager_macos import irondash_engine_context import livekit_client import local_auth_darwin @@ -63,6 +64,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin")) FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) + HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin")) IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin")) LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) diff --git a/pubspec.lock b/pubspec.lock index f05521fb..0011064f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1309,6 +1309,46 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + hotkey_manager: + dependency: "direct main" + description: + name: hotkey_manager + sha256: "06f0655b76c8dd322fb7101dc615afbdbf39c3d3414df9e059c33892104479cd" + url: "https://pub.dev" + source: hosted + version: "0.2.3" + hotkey_manager_linux: + dependency: transitive + description: + name: hotkey_manager_linux + sha256: "83676bda8210a3377bc6f1977f193bc1dbdd4c46f1bdd02875f44b6eff9a8473" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hotkey_manager_macos: + dependency: transitive + description: + name: hotkey_manager_macos + sha256: "03b5967e64357b9ac05188ea4a5df6fe4ed4205762cb80aaccf8916ee1713c96" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hotkey_manager_platform_interface: + dependency: transitive + description: + name: hotkey_manager_platform_interface + sha256: "98ffca25b8cc9081552902747b2942e3bc37855389a4218c9d50ca316b653b13" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hotkey_manager_windows: + dependency: transitive + description: + name: hotkey_manager_windows + sha256: "0d03ced9fe563ed0b68f0a0e1b22c9ffe26eb8053cb960e401f68a4f070e0117" + url: "https://pub.dev" + source: hosted + version: "0.2.0" hotreloader: dependency: transitive description: @@ -2948,6 +2988,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + uni_platform: + dependency: transitive + description: + name: uni_platform + sha256: e02213a7ee5352212412ca026afd41d269eb00d982faa552f419ffc2debfad84 + url: "https://pub.dev" + source: hosted + version: "0.1.3" universal_io: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 4e92c2a9..241db8a4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -172,6 +172,7 @@ dependencies: flutter_code_editor: ^0.3.5 skeletonizer: ^2.1.2 permission_handler: ^12.0.1 + hotkey_manager: ^0.2.3 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 814ca910..4600bb7b 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterWebRTCPlugin")); GalPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("GalPluginCApi")); + HotkeyManagerWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi")); IrondashEngineContextPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi")); LiveKitPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 1d38bdf5..10e867a4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -15,6 +15,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_udid flutter_webrtc gal + hotkey_manager_windows irondash_engine_context livekit_client local_auth_windows