✨ Better command pattle
This commit is contained in:
@@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:image_picker_android/image_picker_android.dart';
|
||||
import 'package:island/talker.dart';
|
||||
import 'package:island/firebase_options.dart';
|
||||
@@ -53,7 +54,8 @@ void main() async {
|
||||
|
||||
if (!kIsWeb && (Platform.isLinux || Platform.isMacOS || Platform.isWindows)) {
|
||||
talker.info("[SplashScreen] Initializing desktop window manager...");
|
||||
await protocolHandler.register('myprotocol');
|
||||
await protocolHandler.register('solian');
|
||||
await hotKeyManager.unregisterAll();
|
||||
talker.info("[SplashScreen] Desktop window manager is ready!");
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
import 'package:island/pods/config.dart';
|
||||
import 'package:island/route.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
@@ -39,7 +40,6 @@ class WindowScaffold extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final isMaximized = useState(false);
|
||||
final showPalette = useState(false);
|
||||
final lastShiftTime = useState<DateTime?>(null);
|
||||
final keyboardFocusNode = useFocusNode();
|
||||
|
||||
useEffect(() {
|
||||
@@ -111,169 +111,158 @@ class WindowScaffold extends HookConsumerWidget {
|
||||
const Gap(8),
|
||||
];
|
||||
|
||||
final popHotKey = HotKey(
|
||||
identifier: 'return_previous_page',
|
||||
key: PhysicalKeyboardKey.escape,
|
||||
scope: HotKeyScope.inapp,
|
||||
);
|
||||
final cmpHotKey = HotKey(
|
||||
identifier: 'open_command_pattle',
|
||||
key: PhysicalKeyboardKey.tab,
|
||||
modifiers: [HotKeyModifier.shift],
|
||||
scope: HotKeyScope.inapp,
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
hotKeyManager.register(
|
||||
popHotKey,
|
||||
keyDownHandler: (_) {
|
||||
if (closeTopmostOverlayDialog()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no overlay to close, pop the route
|
||||
if (ref.watch(routerProvider).canPop()) {
|
||||
ref.read(routerProvider).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
hotKeyManager.register(
|
||||
cmpHotKey,
|
||||
keyDownHandler: (_) {
|
||||
showPalette.value = true;
|
||||
},
|
||||
);
|
||||
|
||||
return () {
|
||||
hotKeyManager.unregister(popHotKey);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!kIsWeb &&
|
||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS)) {
|
||||
return Shortcuts(
|
||||
shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
||||
},
|
||||
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: <Type, Action<Intent>>{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,
|
||||
return 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))
|
||||
Row(
|
||||
children: [
|
||||
if (isWideScreen(context))
|
||||
Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
...pageActionsButton,
|
||||
],
|
||||
)
|
||||
else
|
||||
SizedBox(height: 32),
|
||||
Text(
|
||||
'Solar Network',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(
|
||||
context,
|
||||
).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
...pageActionsButton,
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
else
|
||||
SizedBox(height: 32),
|
||||
Text(
|
||||
'Solar Network',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 'assets/icons/icon-dark.png'
|
||||
: 'assets/icons/icon.png',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Solar Network',
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 12, vertical: 5),
|
||||
Image.asset(
|
||||
Theme.of(context).brightness ==
|
||||
Brightness.dark
|
||||
? 'assets/icons/icon-dark.png'
|
||||
: 'assets/icons/icon.png',
|
||||
width: 20,
|
||||
height: 20,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Symbols.minimize),
|
||||
onPressed: () => windowManager.minimize(),
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isMaximized.value
|
||||
? Symbols.fullscreen_exit
|
||||
: Symbols.fullscreen,
|
||||
),
|
||||
onPressed: () async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
windowManager.restore();
|
||||
} else {
|
||||
windowManager.maximize();
|
||||
}
|
||||
},
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Symbols.close),
|
||||
onPressed: () => windowManager.hide(),
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'Solar Network',
|
||||
textAlign: TextAlign.start,
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 12, vertical: 5),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Symbols.minimize),
|
||||
onPressed: () => windowManager.minimize(),
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isMaximized.value
|
||||
? Symbols.fullscreen_exit
|
||||
: Symbols.fullscreen,
|
||||
),
|
||||
),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
_WebSocketIndicator(),
|
||||
const UploadOverlay(),
|
||||
if (showPalette.value)
|
||||
CommandPattleWidget(
|
||||
onDismiss: () => showPalette.value = false,
|
||||
),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
if (await windowManager.isMaximized()) {
|
||||
windowManager.restore();
|
||||
} else {
|
||||
windowManager.maximize();
|
||||
}
|
||||
},
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Symbols.close),
|
||||
onPressed: () => windowManager.hide(),
|
||||
iconSize: 16,
|
||||
padding: EdgeInsets.all(8),
|
||||
constraints: BoxConstraints(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(child: child),
|
||||
],
|
||||
),
|
||||
),
|
||||
_WebSocketIndicator(),
|
||||
const UploadOverlay(),
|
||||
if (showPalette.value)
|
||||
CommandPattleWidget(onDismiss: () => showPalette.value = false),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Shortcuts(
|
||||
shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
||||
},
|
||||
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: <Type, Action<Intent>>{PopIntent: PopAction(ref)},
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Positioned.fill(child: child),
|
||||
_WebSocketIndicator(),
|
||||
const UploadOverlay(),
|
||||
if (showPalette.value)
|
||||
CommandPattleWidget(onDismiss: () => showPalette.value = false),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
Positioned.fill(child: child),
|
||||
_WebSocketIndicator(),
|
||||
const UploadOverlay(),
|
||||
if (showPalette.value)
|
||||
CommandPattleWidget(onDismiss: () => showPalette.value = false),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -405,29 +394,6 @@ class AppScaffold extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class PopIntent extends Intent {
|
||||
const PopIntent();
|
||||
}
|
||||
|
||||
class PopAction extends Action<PopIntent> {
|
||||
final WidgetRef ref;
|
||||
|
||||
PopAction(this.ref);
|
||||
|
||||
@override
|
||||
void invoke(PopIntent intent) {
|
||||
// First, try to close any overlay dialogs
|
||||
if (closeTopmostOverlayDialog()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If no overlay to close, pop the route
|
||||
if (ref.watch(routerProvider).canPop()) {
|
||||
ref.read(routerProvider).pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PageBackButton extends StatelessWidget {
|
||||
final Color? color;
|
||||
final List<Shadow>? shadows;
|
||||
|
||||
@@ -195,6 +195,7 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
final focusNode = useFocusNode();
|
||||
final searchQuery = useState('');
|
||||
final focusedIndex = useState<int?>(null);
|
||||
final scrollController = useScrollController();
|
||||
|
||||
final animationController = useAnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@@ -271,6 +272,48 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
// Combine results: chats first, then routes
|
||||
final allResults = [...filteredChats, ...filteredRoutes];
|
||||
|
||||
// Update focused index when results change
|
||||
useEffect(() {
|
||||
if (allResults.isNotEmpty && focusedIndex.value == null) {
|
||||
focusedIndex.value = 0;
|
||||
} else if (allResults.isEmpty) {
|
||||
focusedIndex.value = null;
|
||||
}
|
||||
return null;
|
||||
}, [allResults]);
|
||||
|
||||
// Scroll to focused item
|
||||
useEffect(() {
|
||||
if (focusedIndex.value != null && allResults.isNotEmpty) {
|
||||
// Wait for the next frame to ensure ScrollController is attached
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (scrollController.hasClients) {
|
||||
// Estimate item height (ListTile is typically around 72-88 pixels)
|
||||
const double estimatedItemHeight = 80.0;
|
||||
final double itemTopOffset =
|
||||
focusedIndex.value! * estimatedItemHeight;
|
||||
final double viewportHeight =
|
||||
scrollController.position.viewportDimension;
|
||||
final double centeredOffset =
|
||||
itemTopOffset -
|
||||
(viewportHeight / 2) +
|
||||
(estimatedItemHeight / 2);
|
||||
|
||||
// Animate scroll to center the focused item
|
||||
scrollController.animateTo(
|
||||
centeredOffset.clamp(
|
||||
0.0,
|
||||
scrollController.position.maxScrollExtent,
|
||||
),
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOut,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}, [focusedIndex.value]);
|
||||
|
||||
return KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent: (event) {
|
||||
@@ -280,14 +323,11 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
} else if (isDesktop()) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
||||
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
||||
if (focusedIndex.value != null &&
|
||||
focusedIndex.value! < allResults.length) {
|
||||
final item = allResults[focusedIndex.value!];
|
||||
if (item is SnChatRoom) {
|
||||
_navigateToChat(context, ref, item);
|
||||
} else if (item is RouteItem) {
|
||||
_navigateToRoute(context, ref, item);
|
||||
}
|
||||
final item = allResults[focusedIndex.value ?? 0];
|
||||
if (item is SnChatRoom) {
|
||||
_navigateToChat(context, ref, item);
|
||||
} else if (item is RouteItem) {
|
||||
_navigateToRoute(context, ref, item);
|
||||
}
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||
if (allResults.isNotEmpty) {
|
||||
@@ -358,16 +398,6 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
leading: CircleAvatar(
|
||||
child: const Icon(Symbols.keyboard_command_key),
|
||||
).padding(horizontal: 8),
|
||||
onSubmitted: (_) {
|
||||
if (allResults.isNotEmpty) {
|
||||
final item = allResults.first;
|
||||
if (item is SnChatRoom) {
|
||||
_navigateToChat(context, ref, item);
|
||||
} else if (item is RouteItem) {
|
||||
_navigateToRoute(context, ref, item);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@@ -378,6 +408,7 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
maxHeight: 300,
|
||||
),
|
||||
child: ListView.builder(
|
||||
controller: scrollController,
|
||||
shrinkWrap: true,
|
||||
itemCount: allResults.length,
|
||||
itemBuilder: (context, index) {
|
||||
@@ -426,6 +457,7 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
void _navigateToChat(BuildContext context, WidgetRef ref, SnChatRoom room) {
|
||||
onDismiss();
|
||||
if (isWideScreen(context)) {
|
||||
debugPrint('${room.name}');
|
||||
ref
|
||||
.read(routerProvider)
|
||||
.replaceNamed('chatRoom', pathParameters: {'id': room.id});
|
||||
@@ -455,18 +487,23 @@ class _RouteSearchResult extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
tileColor: isFocused
|
||||
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||
: null,
|
||||
leading: CircleAvatar(
|
||||
child: Icon(route.icon),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isFocused
|
||||
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||
: null,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
foregroundColor: Theme.of(context).colorScheme.onSecondaryContainer,
|
||||
child: Icon(route.icon),
|
||||
),
|
||||
title: Text(route.name),
|
||||
subtitle: Text(route.description),
|
||||
onTap: onTap,
|
||||
),
|
||||
title: Text(route.name),
|
||||
subtitle: Text(route.description),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -578,30 +615,35 @@ class _ChatRoomSearchResult extends HookConsumerWidget {
|
||||
|
||||
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
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isFocused
|
||||
? Theme.of(context).colorScheme.surfaceContainerHighest
|
||||
: null,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(24)),
|
||||
),
|
||||
child: ListTile(
|
||||
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,
|
||||
),
|
||||
title: Text(titleText),
|
||||
subtitle: buildSubtitle(),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,10 @@ PODS:
|
||||
- GoogleUtilities/UserDefaults (8.1.0):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- HotKey (0.2.1)
|
||||
- hotkey_manager_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- HotKey
|
||||
- irondash_engine_context (0.0.1):
|
||||
- FlutterMacOS
|
||||
- KeychainAccess (4.2.2)
|
||||
@@ -274,6 +278,7 @@ DEPENDENCIES:
|
||||
- flutter_webrtc (from `Flutter/ephemeral/.symlinks/plugins/flutter_webrtc/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
||||
- hotkey_manager_macos (from `Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos`)
|
||||
- irondash_engine_context (from `Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos`)
|
||||
- livekit_client (from `Flutter/ephemeral/.symlinks/plugins/livekit_client/macos`)
|
||||
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
@@ -312,6 +317,7 @@ SPEC REPOS:
|
||||
- GoogleAppMeasurement
|
||||
- GoogleDataTransport
|
||||
- GoogleUtilities
|
||||
- HotKey
|
||||
- KeychainAccess
|
||||
- nanopb
|
||||
- OrderedSet
|
||||
@@ -359,6 +365,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
gal:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
||||
hotkey_manager_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/hotkey_manager_macos/macos
|
||||
irondash_engine_context:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/irondash_engine_context/macos
|
||||
livekit_client:
|
||||
@@ -437,6 +445,8 @@ SPEC CHECKSUMS:
|
||||
GoogleAppMeasurement: 3bf40aff49a601af5da1c3345702fcb4991d35ee
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1
|
||||
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
||||
hotkey_manager_macos: a4317849af96d2430fa89944d3c58977ca089fbe
|
||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||
KeychainAccess: c0c4f7f38f6fc7bbe58f5702e25f7bd2f65abf51
|
||||
livekit_client: 3df5a1787d64010ca56c4002959d9e47c03ba3fb
|
||||
|
||||
Reference in New Issue
Block a user