✨ Command pattle basis
This commit is contained in:
@@ -93,7 +93,6 @@ class ChatSubscribeNotifier extends _$ChatSubscribeNotifier {
|
|||||||
|
|
||||||
// Set up periodic subscribe timer (every 5 minutes)
|
// Set up periodic subscribe timer (every 5 minutes)
|
||||||
_periodicSubscribeTimer = Timer.periodic(const Duration(minutes: 5), (_) {
|
_periodicSubscribeTimer = Timer.periodic(const Duration(minutes: 5), (_) {
|
||||||
final wsState = ref.read(websocketStateProvider.notifier);
|
|
||||||
wsState.sendMessage(
|
wsState.sendMessage(
|
||||||
jsonEncode(
|
jsonEncode(
|
||||||
WebSocketPacket(
|
WebSocketPacket(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:island/pods/chat/chat_room.dart';
|
import 'package:island/pods/chat/chat_room.dart';
|
||||||
import 'package:island/pods/event_calendar.dart';
|
import 'package:island/pods/event_calendar.dart';
|
||||||
import 'package:island/screens/chat/chat.dart';
|
import 'package:island/screens/chat/chat.dart';
|
||||||
|
import 'package:island/services/event_bus.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/account/fortune_graph.dart';
|
import 'package:island/widgets/account/fortune_graph.dart';
|
||||||
import 'package:island/widgets/account/friends_overview.dart';
|
import 'package:island/widgets/account/friends_overview.dart';
|
||||||
@@ -59,16 +60,27 @@ class DashboardGrid extends HookConsumerWidget {
|
|||||||
constraints: const BoxConstraints(minHeight: 56),
|
constraints: const BoxConstraints(minHeight: 56),
|
||||||
leading: const Icon(Symbols.search).padding(horizontal: 24),
|
leading: const Icon(Symbols.search).padding(horizontal: 24),
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
|
onTap: () {
|
||||||
|
eventBus.fire(CommandPaletteTriggerEvent());
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child:
|
||||||
padding: isWide
|
SingleChildScrollView(
|
||||||
? const EdgeInsets.symmetric(horizontal: 24)
|
padding: isWide
|
||||||
: const EdgeInsets.only(bottom: 64),
|
? const EdgeInsets.symmetric(horizontal: 24)
|
||||||
scrollDirection: isWide ? Axis.horizontal : Axis.vertical,
|
: const EdgeInsets.only(bottom: 64),
|
||||||
child: isWide ? _DashboardGridWide() : _DashboardGridNarrow(),
|
scrollDirection: isWide ? Axis.horizontal : Axis.vertical,
|
||||||
).clipRRect(topLeft: 12, topRight: 12).padding(horizontal: 24),
|
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,
|
margin: EdgeInsets.zero,
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
color: Theme.of(context).colorScheme.surfaceContainer,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Expanded(child: Text(fortune['text']!)),
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
fortune['text']!,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
),
|
||||||
|
),
|
||||||
Text(fortune['author']!).bold(),
|
Text(fortune['author']!).bold(),
|
||||||
],
|
],
|
||||||
).padding(horizontal: 24),
|
).padding(horizontal: 16),
|
||||||
).height(48);
|
).height(48);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,3 +23,8 @@ class OidcAuthCallbackEvent {
|
|||||||
|
|
||||||
const OidcAuthCallbackEvent(this.challengeId);
|
const OidcAuthCallbackEvent(this.challengeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Event fired to trigger the command palette
|
||||||
|
class CommandPaletteTriggerEvent {
|
||||||
|
const CommandPaletteTriggerEvent();
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ import 'package:island/pods/config.dart';
|
|||||||
import 'package:island/route.dart';
|
import 'package:island/route.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
import 'package:island/pods/websocket.dart';
|
import 'package:island/pods/websocket.dart';
|
||||||
|
import 'package:island/services/event_bus.dart';
|
||||||
import 'package:island/services/responsive.dart';
|
import 'package:island/services/responsive.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/cmp/pattle.dart';
|
||||||
import 'package:island/widgets/upload_overlay.dart';
|
import 'package:island/widgets/upload_overlay.dart';
|
||||||
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
import 'package:material_symbols_icons/material_symbols_icons.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -36,6 +38,14 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isMaximized = useState(false);
|
final isMaximized = useState(false);
|
||||||
|
final showPalette = useState(false);
|
||||||
|
final lastShiftTime = useState<DateTime?>(null);
|
||||||
|
final keyboardFocusNode = useFocusNode();
|
||||||
|
|
||||||
|
useEffect(() {
|
||||||
|
keyboardFocusNode.requestFocus();
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Add window resize listener for desktop platforms
|
// Add window resize listener for desktop platforms
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@@ -68,6 +78,15 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Event bus listener for command palette
|
||||||
|
final subscription = useMemoized(
|
||||||
|
() => eventBus.on<CommandPaletteTriggerEvent>().listen(
|
||||||
|
(_) => showPalette.value = true,
|
||||||
|
),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
useEffect(() => subscription.cancel, [subscription]);
|
||||||
|
|
||||||
final router = ref.watch(routerProvider);
|
final router = ref.watch(routerProvider);
|
||||||
|
|
||||||
final pageActionsButton = [
|
final pageActionsButton = [
|
||||||
@@ -98,19 +117,32 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
shortcuts: <LogicalKeySet, Intent>{
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
||||||
},
|
},
|
||||||
child: Actions(
|
child: KeyboardListener(
|
||||||
actions: <Type, Action<Intent>>{PopIntent: PopAction(ref)},
|
focusNode: keyboardFocusNode,
|
||||||
child: Material(
|
onKeyEvent: (event) {
|
||||||
color: Theme.of(context).colorScheme.surfaceContainer,
|
if (event is KeyDownEvent &&
|
||||||
child: Stack(
|
(event.logicalKey == LogicalKeyboardKey.shiftLeft ||
|
||||||
fit: StackFit.expand,
|
event.logicalKey == LogicalKeyboardKey.shiftRight)) {
|
||||||
children: [
|
final now = DateTime.now();
|
||||||
Column(
|
if (lastShiftTime.value != null &&
|
||||||
children: [
|
now.difference(lastShiftTime.value!).inMilliseconds < 300) {
|
||||||
DragToMoveArea(
|
showPalette.value = true;
|
||||||
child:
|
}
|
||||||
Platform.isMacOS
|
lastShiftTime.value = now;
|
||||||
? Stack(
|
}
|
||||||
|
},
|
||||||
|
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,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (isWideScreen(context))
|
if (isWideScreen(context))
|
||||||
@@ -126,15 +158,14 @@ class WindowScaffold extends HookConsumerWidget {
|
|||||||
'Solar Network',
|
'Solar Network',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color:
|
color: Theme.of(
|
||||||
Theme.of(
|
context,
|
||||||
context,
|
).colorScheme.onSurface,
|
||||||
).colorScheme.onSurface,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: Row(
|
: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
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, Intent>{
|
shortcuts: <LogicalKeySet, Intent>{
|
||||||
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
LogicalKeySet(LogicalKeyboardKey.escape): const PopIntent(),
|
||||||
},
|
},
|
||||||
child: Actions(
|
child: KeyboardListener(
|
||||||
actions: <Type, Action<Intent>>{PopIntent: PopAction(ref)},
|
focusNode: keyboardFocusNode,
|
||||||
child: Stack(
|
onKeyEvent: (event) {
|
||||||
fit: StackFit.expand,
|
if (event is KeyDownEvent &&
|
||||||
children: [
|
(event.logicalKey == LogicalKeyboardKey.shiftLeft ||
|
||||||
Positioned.fill(child: child),
|
event.logicalKey == LogicalKeyboardKey.shiftRight)) {
|
||||||
_WebSocketIndicator(),
|
final now = DateTime.now();
|
||||||
const UploadOverlay(),
|
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),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -407,8 +460,8 @@ class PageBackButton extends StatelessWidget {
|
|||||||
color: color,
|
color: color,
|
||||||
context.canPop()
|
context.canPop()
|
||||||
? (!kIsWeb && (Platform.isMacOS || Platform.isIOS))
|
? (!kIsWeb && (Platform.isMacOS || Platform.isIOS))
|
||||||
? Symbols.arrow_back_ios_new
|
? Symbols.arrow_back_ios_new
|
||||||
: Symbols.arrow_back
|
: Symbols.arrow_back
|
||||||
: Symbols.home,
|
: Symbols.home,
|
||||||
shadows: shadows,
|
shadows: shadows,
|
||||||
),
|
),
|
||||||
@@ -463,11 +516,10 @@ class AppBackground extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
loading: () => const SizedBox(),
|
loading: () => const SizedBox(),
|
||||||
error:
|
error: (_, _) => Material(
|
||||||
(_, _) => Material(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
color: Theme.of(context).colorScheme.surface,
|
child: child,
|
||||||
child: child,
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,10 +571,10 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
duration: Duration(milliseconds: 1850),
|
duration: Duration(milliseconds: 1850),
|
||||||
top:
|
top:
|
||||||
user.value == null ||
|
user.value == null ||
|
||||||
user.value == null ||
|
user.value == null ||
|
||||||
websocketState == WebSocketState.connected()
|
websocketState == WebSocketState.connected()
|
||||||
? -indicatorHeight
|
? -indicatorHeight
|
||||||
: 0,
|
: 0,
|
||||||
curve: Curves.fastLinearToSlowEaseIn,
|
curve: Curves.fastLinearToSlowEaseIn,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@@ -531,17 +583,16 @@ class _WebSocketIndicator extends HookConsumerWidget {
|
|||||||
child: Material(
|
child: Material(
|
||||||
elevation:
|
elevation:
|
||||||
user.value == null || websocketState == WebSocketState.connected()
|
user.value == null || websocketState == WebSocketState.connected()
|
||||||
? 0
|
? 0
|
||||||
: 4,
|
: 4,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
duration: Duration(milliseconds: 300),
|
duration: Duration(milliseconds: 300),
|
||||||
color: indicatorColor,
|
color: indicatorColor,
|
||||||
child: Center(
|
child: Center(
|
||||||
child:
|
child: Text(
|
||||||
Text(
|
indicatorText,
|
||||||
indicatorText,
|
style: TextStyle(color: Colors.white, fontSize: 16),
|
||||||
style: TextStyle(color: Colors.white, fontSize: 16),
|
).tr(),
|
||||||
).tr(),
|
|
||||||
).padding(top: MediaQuery.of(context).padding.top),
|
).padding(top: MediaQuery.of(context).padding.top),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
336
lib/widgets/cmp/pattle.dart
Normal file
336
lib/widgets/cmp/pattle.dart
Normal file
@@ -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<int?>(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 <SnChatRoom>[];
|
||||||
|
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: () => <SnChatRoom>[],
|
||||||
|
);
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <flutter_timezone/flutter_timezone_plugin.h>
|
#include <flutter_timezone/flutter_timezone_plugin.h>
|
||||||
#include <flutter_udid/flutter_udid_plugin.h>
|
#include <flutter_udid/flutter_udid_plugin.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
|
#include <hotkey_manager_linux/hotkey_manager_linux_plugin.h>
|
||||||
#include <irondash_engine_context/irondash_engine_context_plugin.h>
|
#include <irondash_engine_context/irondash_engine_context_plugin.h>
|
||||||
#include <livekit_client/live_kit_plugin.h>
|
#include <livekit_client/live_kit_plugin.h>
|
||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
@@ -49,6 +50,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
|||||||
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
g_autoptr(FlPluginRegistrar) flutter_webrtc_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterWebRTCPlugin");
|
||||||
flutter_web_r_t_c_plugin_register_with_registrar(flutter_webrtc_registrar);
|
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 =
|
g_autoptr(FlPluginRegistrar) irondash_engine_context_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "IrondashEngineContextPlugin");
|
||||||
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
|
irondash_engine_context_plugin_register_with_registrar(irondash_engine_context_registrar);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
flutter_timezone
|
flutter_timezone
|
||||||
flutter_udid
|
flutter_udid
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
|
hotkey_manager_linux
|
||||||
irondash_engine_context
|
irondash_engine_context
|
||||||
livekit_client
|
livekit_client
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import flutter_timezone
|
|||||||
import flutter_udid
|
import flutter_udid
|
||||||
import flutter_webrtc
|
import flutter_webrtc
|
||||||
import gal
|
import gal
|
||||||
|
import hotkey_manager_macos
|
||||||
import irondash_engine_context
|
import irondash_engine_context
|
||||||
import livekit_client
|
import livekit_client
|
||||||
import local_auth_darwin
|
import local_auth_darwin
|
||||||
@@ -63,6 +64,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
FlutterUdidPlugin.register(with: registry.registrar(forPlugin: "FlutterUdidPlugin"))
|
||||||
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin"))
|
||||||
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))
|
||||||
|
HotkeyManagerMacosPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerMacosPlugin"))
|
||||||
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
IrondashEngineContextPlugin.register(with: registry.registrar(forPlugin: "IrondashEngineContextPlugin"))
|
||||||
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin"))
|
||||||
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin"))
|
||||||
|
|||||||
48
pubspec.lock
48
pubspec.lock
@@ -1309,6 +1309,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
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:
|
hotreloader:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -2948,6 +2988,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
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:
|
universal_io:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -172,6 +172,7 @@ dependencies:
|
|||||||
flutter_code_editor: ^0.3.5
|
flutter_code_editor: ^0.3.5
|
||||||
skeletonizer: ^2.1.2
|
skeletonizer: ^2.1.2
|
||||||
permission_handler: ^12.0.1
|
permission_handler: ^12.0.1
|
||||||
|
hotkey_manager: ^0.2.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <flutter_udid/flutter_udid_plugin_c_api.h>
|
#include <flutter_udid/flutter_udid_plugin_c_api.h>
|
||||||
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
#include <flutter_webrtc/flutter_web_r_t_c_plugin.h>
|
||||||
#include <gal/gal_plugin_c_api.h>
|
#include <gal/gal_plugin_c_api.h>
|
||||||
|
#include <hotkey_manager_windows/hotkey_manager_windows_plugin_c_api.h>
|
||||||
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
|
#include <irondash_engine_context/irondash_engine_context_plugin_c_api.h>
|
||||||
#include <livekit_client/live_kit_plugin.h>
|
#include <livekit_client/live_kit_plugin.h>
|
||||||
#include <local_auth_windows/local_auth_plugin.h>
|
#include <local_auth_windows/local_auth_plugin.h>
|
||||||
@@ -62,6 +63,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
|||||||
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
registry->GetRegistrarForPlugin("FlutterWebRTCPlugin"));
|
||||||
GalPluginCApiRegisterWithRegistrar(
|
GalPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("GalPluginCApi"));
|
registry->GetRegistrarForPlugin("GalPluginCApi"));
|
||||||
|
HotkeyManagerWindowsPluginCApiRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("HotkeyManagerWindowsPluginCApi"));
|
||||||
IrondashEngineContextPluginCApiRegisterWithRegistrar(
|
IrondashEngineContextPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
|
registry->GetRegistrarForPlugin("IrondashEngineContextPluginCApi"));
|
||||||
LiveKitPluginRegisterWithRegistrar(
|
LiveKitPluginRegisterWithRegistrar(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
|||||||
flutter_udid
|
flutter_udid
|
||||||
flutter_webrtc
|
flutter_webrtc
|
||||||
gal
|
gal
|
||||||
|
hotkey_manager_windows
|
||||||
irondash_engine_context
|
irondash_engine_context
|
||||||
livekit_client
|
livekit_client
|
||||||
local_auth_windows
|
local_auth_windows
|
||||||
|
|||||||
Reference in New Issue
Block a user