✨ Command pattle search pages
This commit is contained in:
14
lib/models/route_item.dart
Normal file
14
lib/models/route_item.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'route_item.freezed.dart';
|
||||
|
||||
@freezed
|
||||
sealed class RouteItem with _$RouteItem {
|
||||
const factory RouteItem({
|
||||
required String name,
|
||||
required String path,
|
||||
required String description,
|
||||
required IconData icon,
|
||||
}) = _RouteItem;
|
||||
}
|
||||
274
lib/models/route_item.freezed.dart
Normal file
274
lib/models/route_item.freezed.dart
Normal file
@@ -0,0 +1,274 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'route_item.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$RouteItem {
|
||||
|
||||
String get name; String get path; String get description; IconData get icon;
|
||||
/// Create a copy of RouteItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$RouteItemCopyWith<RouteItem> get copyWith => _$RouteItemCopyWithImpl<RouteItem>(this as RouteItem, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,path,description,icon);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RouteItem(name: $name, path: $path, description: $description, icon: $icon)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $RouteItemCopyWith<$Res> {
|
||||
factory $RouteItemCopyWith(RouteItem value, $Res Function(RouteItem) _then) = _$RouteItemCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String name, String path, String description, IconData icon
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$RouteItemCopyWithImpl<$Res>
|
||||
implements $RouteItemCopyWith<$Res> {
|
||||
_$RouteItemCopyWithImpl(this._self, this._then);
|
||||
|
||||
final RouteItem _self;
|
||||
final $Res Function(RouteItem) _then;
|
||||
|
||||
/// Create a copy of RouteItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? path = null,Object? description = null,Object? icon = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
|
||||
as IconData,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [RouteItem].
|
||||
extension RouteItemPatterns on RouteItem {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _RouteItem value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _RouteItem() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _RouteItem value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _RouteItem():
|
||||
return $default(_that);}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _RouteItem value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _RouteItem() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String name, String path, String description, IconData icon)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _RouteItem() when $default != null:
|
||||
return $default(_that.name,_that.path,_that.description,_that.icon);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String name, String path, String description, IconData icon) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _RouteItem():
|
||||
return $default(_that.name,_that.path,_that.description,_that.icon);}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String name, String path, String description, IconData icon)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _RouteItem() when $default != null:
|
||||
return $default(_that.name,_that.path,_that.description,_that.icon);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _RouteItem implements RouteItem {
|
||||
const _RouteItem({required this.name, required this.path, required this.description, required this.icon});
|
||||
|
||||
|
||||
@override final String name;
|
||||
@override final String path;
|
||||
@override final String description;
|
||||
@override final IconData icon;
|
||||
|
||||
/// Create a copy of RouteItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$RouteItemCopyWith<_RouteItem> get copyWith => __$RouteItemCopyWithImpl<_RouteItem>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _RouteItem&&(identical(other.name, name) || other.name == name)&&(identical(other.path, path) || other.path == path)&&(identical(other.description, description) || other.description == description)&&(identical(other.icon, icon) || other.icon == icon));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,path,description,icon);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'RouteItem(name: $name, path: $path, description: $description, icon: $icon)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$RouteItemCopyWith<$Res> implements $RouteItemCopyWith<$Res> {
|
||||
factory _$RouteItemCopyWith(_RouteItem value, $Res Function(_RouteItem) _then) = __$RouteItemCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String name, String path, String description, IconData icon
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$RouteItemCopyWithImpl<$Res>
|
||||
implements _$RouteItemCopyWith<$Res> {
|
||||
__$RouteItemCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _RouteItem _self;
|
||||
final $Res Function(_RouteItem) _then;
|
||||
|
||||
/// Create a copy of RouteItem
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? path = null,Object? description = null,Object? icon = null,}) {
|
||||
return _then(_RouteItem(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,path: null == path ? _self.path : path // ignore: cast_nullable_to_non_nullable
|
||||
as String,description: null == description ? _self.description : description // ignore: cast_nullable_to_non_nullable
|
||||
as String,icon: null == icon ? _self.icon : icon // ignore: cast_nullable_to_non_nullable
|
||||
as IconData,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
@@ -59,7 +59,7 @@ final class ChatSubscribeNotifierProvider
|
||||
}
|
||||
|
||||
String _$chatSubscribeNotifierHash() =>
|
||||
r'2b9fae96eb1f96a514a074985e5efa1c13d10aa4';
|
||||
r'1aa164429aaab1628b5edbae11e33b0860abdcdc';
|
||||
|
||||
final class ChatSubscribeNotifierFamily extends $Family
|
||||
with
|
||||
|
||||
@@ -8,6 +8,7 @@ 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/models/route_item.dart';
|
||||
import 'package:island/pods/chat/chat_room.dart';
|
||||
import 'package:island/pods/chat/chat_summary.dart';
|
||||
import 'package:island/pods/userinfo.dart';
|
||||
@@ -23,6 +24,171 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
|
||||
const CommandPattleWidget({super.key, required this.onDismiss});
|
||||
|
||||
static final List<RouteItem> _availableRoutes = [
|
||||
RouteItem(
|
||||
name: 'Dashboard',
|
||||
path: '/',
|
||||
description: 'Main dashboard',
|
||||
icon: Symbols.home,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Explore',
|
||||
path: '/explore',
|
||||
description: 'Discover content',
|
||||
icon: Symbols.explore,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Post Search',
|
||||
path: '/posts/search',
|
||||
description: 'Search posts',
|
||||
icon: Symbols.search,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Post Shuffle',
|
||||
path: '/posts/shuffle',
|
||||
description: 'Random posts',
|
||||
icon: Symbols.shuffle,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Post Categories',
|
||||
path: '/posts/categories',
|
||||
description: 'Browse categories',
|
||||
icon: Symbols.category,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Discovery Realms',
|
||||
path: '/discovery/realms',
|
||||
description: 'Explore realms',
|
||||
icon: Symbols.public,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Chat',
|
||||
path: '/chat',
|
||||
description: 'Messages and conversations',
|
||||
icon: Symbols.chat,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Realms',
|
||||
path: '/realms',
|
||||
description: 'Community realms',
|
||||
icon: Symbols.group,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Account',
|
||||
path: '/account',
|
||||
description: 'Your profile and settings',
|
||||
icon: Symbols.person,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Sticker Marketplace',
|
||||
path: '/stickers',
|
||||
description: 'Browse sticker packs',
|
||||
icon: Symbols.emoji_emotions,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Web Feeds',
|
||||
path: '/feeds',
|
||||
description: 'RSS and web feeds',
|
||||
icon: Symbols.feed,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Wallet',
|
||||
path: '/account/wallet',
|
||||
description: 'Your digital wallet',
|
||||
icon: Symbols.account_balance_wallet,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Relationships',
|
||||
path: '/account/relationships',
|
||||
description: 'Friends and connections',
|
||||
icon: Symbols.people,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Update Profile',
|
||||
path: '/account/me/update',
|
||||
description: 'Edit your profile',
|
||||
icon: Symbols.edit,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Leveling',
|
||||
path: '/account/me/leveling',
|
||||
description: 'Your progress and levels',
|
||||
icon: Symbols.trending_up,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Account Settings',
|
||||
path: '/account/me/settings',
|
||||
description: 'App preferences',
|
||||
icon: Symbols.settings,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Reports',
|
||||
path: '/safety/reports/me',
|
||||
description: 'Your abuse reports',
|
||||
icon: Symbols.report,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Files',
|
||||
path: '/files',
|
||||
description: 'File manager',
|
||||
icon: Symbols.folder,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Thought',
|
||||
path: '/thought',
|
||||
description: 'AI assistant',
|
||||
icon: Symbols.psychology,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Creator Hub',
|
||||
path: '/creators',
|
||||
description: 'Content creation tools',
|
||||
icon: Symbols.create,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Developer Hub',
|
||||
path: '/developers',
|
||||
description: 'Developer tools',
|
||||
icon: Symbols.code,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Logs',
|
||||
path: '/logs',
|
||||
description: 'Application logs',
|
||||
icon: Symbols.bug_report,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Articles',
|
||||
path: '/feeds/articles',
|
||||
description: 'Web articles',
|
||||
icon: Symbols.article,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Login',
|
||||
path: '/auth/login',
|
||||
description: 'Sign in to your account',
|
||||
icon: Symbols.login,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Create Account',
|
||||
path: '/auth/create-account',
|
||||
description: 'Create a new account',
|
||||
icon: Symbols.person_add,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'Settings',
|
||||
path: '/settings',
|
||||
description: 'Application settings',
|
||||
icon: Symbols.settings,
|
||||
),
|
||||
RouteItem(
|
||||
name: 'About',
|
||||
path: '/about',
|
||||
description: 'About this app',
|
||||
icon: Symbols.info,
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final textController = useTextEditingController();
|
||||
@@ -30,8 +196,23 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
final searchQuery = useState('');
|
||||
final focusedIndex = useState<int?>(null);
|
||||
|
||||
final animationController = useAnimationController(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
);
|
||||
final scaleAnimation = useAnimation(
|
||||
Tween<double>(begin: 0.8, end: 1.0).animate(
|
||||
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
|
||||
),
|
||||
);
|
||||
final opacityAnimation = useAnimation(
|
||||
Tween<double>(begin: 0.0, end: 1.0).animate(
|
||||
CurvedAnimation(parent: animationController, curve: Curves.easeOut),
|
||||
),
|
||||
);
|
||||
|
||||
useEffect(() {
|
||||
focusNode.requestFocus();
|
||||
animationController.forward();
|
||||
return null;
|
||||
}, []);
|
||||
|
||||
@@ -47,14 +228,13 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
}, [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(
|
||||
final filteredChats = chatRooms.maybeWhen(
|
||||
data: (rooms) {
|
||||
if (searchQuery.value.isEmpty) return <SnChatRoom>[];
|
||||
return rooms
|
||||
@@ -77,6 +257,20 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
orElse: () => <SnChatRoom>[],
|
||||
);
|
||||
|
||||
final filteredRoutes = searchQuery.value.isEmpty
|
||||
? <RouteItem>[]
|
||||
: _availableRoutes
|
||||
.where((route) {
|
||||
final query = searchQuery.value.toLowerCase();
|
||||
return route.name.toLowerCase().contains(query) ||
|
||||
route.description.toLowerCase().contains(query);
|
||||
})
|
||||
.take(5) // Limit to 5 results
|
||||
.toList();
|
||||
|
||||
// Combine results: chats first, then routes
|
||||
final allResults = [...filteredChats, ...filteredRoutes];
|
||||
|
||||
return KeyboardListener(
|
||||
focusNode: FocusNode(),
|
||||
onKeyEvent: (event) {
|
||||
@@ -87,15 +281,16 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
||||
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
||||
if (focusedIndex.value != null &&
|
||||
focusedIndex.value! < filteredRooms.length) {
|
||||
_navigateToRoom(
|
||||
context,
|
||||
ref,
|
||||
filteredRooms[focusedIndex.value!],
|
||||
);
|
||||
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);
|
||||
}
|
||||
}
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||
if (filteredRooms.isNotEmpty) {
|
||||
if (allResults.isNotEmpty) {
|
||||
if (focusedIndex.value == null) {
|
||||
focusedIndex.value = 0;
|
||||
} else {
|
||||
@@ -103,12 +298,12 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||
if (filteredRooms.isNotEmpty) {
|
||||
if (allResults.isNotEmpty) {
|
||||
if (focusedIndex.value == null) {
|
||||
focusedIndex.value = 0;
|
||||
} else {
|
||||
focusedIndex.value = math.min(
|
||||
filteredRooms.length - 1,
|
||||
allResults.length - 1,
|
||||
focusedIndex.value! + 1,
|
||||
);
|
||||
}
|
||||
@@ -121,13 +316,23 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
onTap: onDismiss,
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||
child: AnimatedBuilder(
|
||||
animation: animationController,
|
||||
builder: (context, child) => Opacity(
|
||||
opacity: opacityAnimation,
|
||||
child: Transform.scale(scale: scaleAnimation, child: child),
|
||||
),
|
||||
child: Container(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
onTap: () {}, // Prevent tap from dismissing when tapping inside
|
||||
onTap:
|
||||
() {}, // Prevent tap from dismissing when tapping inside
|
||||
child: Container(
|
||||
width: math.max(MediaQuery.of(context).size.width * 0.6, 320),
|
||||
width: math.max(
|
||||
MediaQuery.of(context).size.width * 0.6,
|
||||
320,
|
||||
),
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: 600,
|
||||
maxHeight: 500,
|
||||
@@ -149,29 +354,44 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
SearchBar(
|
||||
controller: textController,
|
||||
focusNode: focusNode,
|
||||
hintText: 'Search chats...',
|
||||
hintText: 'Search chats and pages...',
|
||||
leading: const Icon(
|
||||
Symbols.keyboard_command_key,
|
||||
).padding(horizontal: 8),
|
||||
onSubmitted: (_) {
|
||||
if (filteredRooms.isNotEmpty) {
|
||||
_navigateToRoom(context, ref, filteredRooms.first);
|
||||
if (allResults.isNotEmpty) {
|
||||
final item = allResults.first;
|
||||
if (item is SnChatRoom) {
|
||||
_navigateToChat(context, ref, item);
|
||||
} else if (item is RouteItem) {
|
||||
_navigateToRoute(context, ref, item);
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
if (filteredRooms.isNotEmpty)
|
||||
if (allResults.isNotEmpty)
|
||||
Flexible(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
itemCount: filteredRooms.length,
|
||||
itemCount: allResults.length,
|
||||
itemBuilder: (context, index) {
|
||||
final room = filteredRooms[index];
|
||||
final item = allResults[index];
|
||||
if (item is SnChatRoom) {
|
||||
return _ChatRoomSearchResult(
|
||||
room: room,
|
||||
room: item,
|
||||
isFocused: index == focusedIndex.value,
|
||||
onTap: () =>
|
||||
_navigateToRoom(context, ref, room),
|
||||
_navigateToChat(context, ref, item),
|
||||
);
|
||||
} else if (item is RouteItem) {
|
||||
return _RouteSearchResult(
|
||||
route: item,
|
||||
isFocused: index == focusedIndex.value,
|
||||
onTap: () =>
|
||||
_navigateToRoute(context, ref, item),
|
||||
);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -183,10 +403,11 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _navigateToRoom(BuildContext context, WidgetRef ref, SnChatRoom room) {
|
||||
void _navigateToChat(BuildContext context, WidgetRef ref, SnChatRoom room) {
|
||||
onDismiss();
|
||||
if (isWideScreen(context)) {
|
||||
ref
|
||||
@@ -198,6 +419,40 @@ class CommandPattleWidget extends HookConsumerWidget {
|
||||
.pushNamed('chatRoom', pathParameters: {'id': room.id});
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateToRoute(BuildContext context, WidgetRef ref, RouteItem route) {
|
||||
onDismiss();
|
||||
ref.read(routerProvider).go(route.path);
|
||||
}
|
||||
}
|
||||
|
||||
class _RouteSearchResult extends StatelessWidget {
|
||||
final RouteItem route;
|
||||
final bool isFocused;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const _RouteSearchResult({
|
||||
required this.route,
|
||||
required this.isFocused,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@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,
|
||||
),
|
||||
title: Text(route.name),
|
||||
subtitle: Text(route.description),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ChatRoomSearchResult extends HookConsumerWidget {
|
||||
|
||||
Reference in New Issue
Block a user