✨ 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() =>
|
String _$chatSubscribeNotifierHash() =>
|
||||||
r'2b9fae96eb1f96a514a074985e5efa1c13d10aa4';
|
r'1aa164429aaab1628b5edbae11e33b0860abdcdc';
|
||||||
|
|
||||||
final class ChatSubscribeNotifierFamily extends $Family
|
final class ChatSubscribeNotifierFamily extends $Family
|
||||||
with
|
with
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:island/models/chat.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_room.dart';
|
||||||
import 'package:island/pods/chat/chat_summary.dart';
|
import 'package:island/pods/chat/chat_summary.dart';
|
||||||
import 'package:island/pods/userinfo.dart';
|
import 'package:island/pods/userinfo.dart';
|
||||||
@@ -23,6 +24,171 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
|
|
||||||
const CommandPattleWidget({super.key, required this.onDismiss});
|
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
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final textController = useTextEditingController();
|
final textController = useTextEditingController();
|
||||||
@@ -30,8 +196,23 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
final searchQuery = useState('');
|
final searchQuery = useState('');
|
||||||
final focusedIndex = useState<int?>(null);
|
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(() {
|
useEffect(() {
|
||||||
focusNode.requestFocus();
|
focusNode.requestFocus();
|
||||||
|
animationController.forward();
|
||||||
return null;
|
return null;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -47,14 +228,13 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
}, [textController]);
|
}, [textController]);
|
||||||
|
|
||||||
final chatRooms = ref.watch(chatRoomJoinedProvider);
|
final chatRooms = ref.watch(chatRoomJoinedProvider);
|
||||||
final userInfo = ref.watch(userInfoProvider);
|
|
||||||
|
|
||||||
bool isDesktop() =>
|
bool isDesktop() =>
|
||||||
kIsWeb ||
|
kIsWeb ||
|
||||||
(!kIsWeb &&
|
(!kIsWeb &&
|
||||||
(Platform.isWindows || Platform.isLinux || Platform.isMacOS));
|
(Platform.isWindows || Platform.isLinux || Platform.isMacOS));
|
||||||
|
|
||||||
final filteredRooms = chatRooms.maybeWhen(
|
final filteredChats = chatRooms.maybeWhen(
|
||||||
data: (rooms) {
|
data: (rooms) {
|
||||||
if (searchQuery.value.isEmpty) return <SnChatRoom>[];
|
if (searchQuery.value.isEmpty) return <SnChatRoom>[];
|
||||||
return rooms
|
return rooms
|
||||||
@@ -77,6 +257,20 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
orElse: () => <SnChatRoom>[],
|
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(
|
return KeyboardListener(
|
||||||
focusNode: FocusNode(),
|
focusNode: FocusNode(),
|
||||||
onKeyEvent: (event) {
|
onKeyEvent: (event) {
|
||||||
@@ -87,15 +281,16 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
if (event.logicalKey == LogicalKeyboardKey.enter ||
|
||||||
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
event.logicalKey == LogicalKeyboardKey.numpadEnter) {
|
||||||
if (focusedIndex.value != null &&
|
if (focusedIndex.value != null &&
|
||||||
focusedIndex.value! < filteredRooms.length) {
|
focusedIndex.value! < allResults.length) {
|
||||||
_navigateToRoom(
|
final item = allResults[focusedIndex.value!];
|
||||||
context,
|
if (item is SnChatRoom) {
|
||||||
ref,
|
_navigateToChat(context, ref, item);
|
||||||
filteredRooms[focusedIndex.value!],
|
} else if (item is RouteItem) {
|
||||||
);
|
_navigateToRoute(context, ref, item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
|
||||||
if (filteredRooms.isNotEmpty) {
|
if (allResults.isNotEmpty) {
|
||||||
if (focusedIndex.value == null) {
|
if (focusedIndex.value == null) {
|
||||||
focusedIndex.value = 0;
|
focusedIndex.value = 0;
|
||||||
} else {
|
} else {
|
||||||
@@ -103,12 +298,12 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
} else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
|
||||||
if (filteredRooms.isNotEmpty) {
|
if (allResults.isNotEmpty) {
|
||||||
if (focusedIndex.value == null) {
|
if (focusedIndex.value == null) {
|
||||||
focusedIndex.value = 0;
|
focusedIndex.value = 0;
|
||||||
} else {
|
} else {
|
||||||
focusedIndex.value = math.min(
|
focusedIndex.value = math.min(
|
||||||
filteredRooms.length - 1,
|
allResults.length - 1,
|
||||||
focusedIndex.value! + 1,
|
focusedIndex.value! + 1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -121,61 +316,87 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
onTap: onDismiss,
|
onTap: onDismiss,
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
filter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
|
||||||
child: Container(
|
child: AnimatedBuilder(
|
||||||
color: Colors.black.withOpacity(0.5),
|
animation: animationController,
|
||||||
child: Center(
|
builder: (context, child) => Opacity(
|
||||||
child: GestureDetector(
|
opacity: opacityAnimation,
|
||||||
onTap: () {}, // Prevent tap from dismissing when tapping inside
|
child: Transform.scale(scale: scaleAnimation, child: child),
|
||||||
child: Container(
|
),
|
||||||
width: math.max(MediaQuery.of(context).size.width * 0.6, 320),
|
child: Container(
|
||||||
constraints: const BoxConstraints(
|
color: Colors.black.withOpacity(0.5),
|
||||||
maxWidth: 600,
|
child: Center(
|
||||||
maxHeight: 500,
|
child: GestureDetector(
|
||||||
),
|
onTap:
|
||||||
decoration: BoxDecoration(
|
() {}, // Prevent tap from dismissing when tapping inside
|
||||||
color: Theme.of(context).colorScheme.surface,
|
child: Container(
|
||||||
borderRadius: BorderRadius.circular(24),
|
width: math.max(
|
||||||
boxShadow: [
|
MediaQuery.of(context).size.width * 0.6,
|
||||||
BoxShadow(
|
320,
|
||||||
color: Colors.black.withOpacity(0.3),
|
),
|
||||||
blurRadius: 10,
|
constraints: const BoxConstraints(
|
||||||
spreadRadius: 2,
|
maxWidth: 600,
|
||||||
),
|
maxHeight: 500,
|
||||||
],
|
),
|
||||||
),
|
decoration: BoxDecoration(
|
||||||
child: Column(
|
color: Theme.of(context).colorScheme.surface,
|
||||||
mainAxisSize: MainAxisSize.min,
|
borderRadius: BorderRadius.circular(24),
|
||||||
children: [
|
boxShadow: [
|
||||||
SearchBar(
|
BoxShadow(
|
||||||
controller: textController,
|
color: Colors.black.withOpacity(0.3),
|
||||||
focusNode: focusNode,
|
blurRadius: 10,
|
||||||
hintText: 'Search chats...',
|
spreadRadius: 2,
|
||||||
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),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
SearchBar(
|
||||||
|
controller: textController,
|
||||||
|
focusNode: focusNode,
|
||||||
|
hintText: 'Search chats and pages...',
|
||||||
|
leading: 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (allResults.isNotEmpty)
|
||||||
|
Flexible(
|
||||||
|
child: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: allResults.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = allResults[index];
|
||||||
|
if (item is SnChatRoom) {
|
||||||
|
return _ChatRoomSearchResult(
|
||||||
|
room: item,
|
||||||
|
isFocused: index == focusedIndex.value,
|
||||||
|
onTap: () =>
|
||||||
|
_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();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -186,7 +407,7 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _navigateToRoom(BuildContext context, WidgetRef ref, SnChatRoom room) {
|
void _navigateToChat(BuildContext context, WidgetRef ref, SnChatRoom room) {
|
||||||
onDismiss();
|
onDismiss();
|
||||||
if (isWideScreen(context)) {
|
if (isWideScreen(context)) {
|
||||||
ref
|
ref
|
||||||
@@ -198,6 +419,40 @@ class CommandPattleWidget extends HookConsumerWidget {
|
|||||||
.pushNamed('chatRoom', pathParameters: {'id': room.id});
|
.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 {
|
class _ChatRoomSearchResult extends HookConsumerWidget {
|
||||||
|
|||||||
Reference in New Issue
Block a user