fix(mobile): use context search once server features are loaded (#31800)
Co-authored-by: Dawid Krynski <188586034+DawidKrynski@users.noreply.github.com> Co-authored-by: Santo Shakil <santoshakil@users.noreply.github.com> (cherry picked from commit be8e42526700d9ec5e72061e33f5039d50e40bae)
This commit is contained in:
@@ -42,12 +42,16 @@ class SearchPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final serverFeatures = ref.watch(serverInfoProvider.select((v) => v.serverFeatures));
|
||||
final textSearchType = useState<TextSearchType>(
|
||||
serverFeatures.smartSearch ? TextSearchType.context : TextSearchType.filename,
|
||||
);
|
||||
final searchHintText = useState<String>(
|
||||
serverFeatures.smartSearch ? context.t.sunrise_on_the_beach : context.t.file_name_or_extension,
|
||||
);
|
||||
// Stays null until the user picks a type, so the default follows the server features once they are loaded
|
||||
final selectedTextSearchType = useState<TextSearchType?>(null);
|
||||
final textSearchType =
|
||||
selectedTextSearchType.value ?? (serverFeatures.smartSearch ? TextSearchType.context : TextSearchType.filename);
|
||||
final searchHintText = switch (textSearchType) {
|
||||
TextSearchType.context => context.t.sunrise_on_the_beach,
|
||||
TextSearchType.filename => context.t.file_name_or_extension,
|
||||
TextSearchType.description => context.t.search_by_description_example,
|
||||
TextSearchType.ocr => context.t.search_by_ocr_example,
|
||||
};
|
||||
final textSearchController = useTextEditingController();
|
||||
final filter = useState<SearchFilter>(
|
||||
SearchFilter(
|
||||
@@ -491,14 +495,14 @@ class SearchPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void handleTextSubmitted(String value) => search(switch (textSearchType.value) {
|
||||
void handleTextSubmitted(String value) => search(switch (textSearchType) {
|
||||
TextSearchType.context => filter.value.copyWith(filename: '', context: value, description: '', ocr: ''),
|
||||
TextSearchType.filename => filter.value.copyWith(filename: value, context: '', description: '', ocr: ''),
|
||||
TextSearchType.description => filter.value.copyWith(filename: '', context: '', description: value, ocr: ''),
|
||||
TextSearchType.ocr => filter.value.copyWith(filename: '', context: '', description: '', ocr: value),
|
||||
});
|
||||
|
||||
IconData getSearchPrefixIcon() => switch (textSearchType.value) {
|
||||
IconData getSearchPrefixIcon() => switch (textSearchType) {
|
||||
TextSearchType.context => Icons.image_search_rounded,
|
||||
TextSearchType.filename => Icons.abc_rounded,
|
||||
TextSearchType.description => Icons.text_snippet_outlined,
|
||||
@@ -543,15 +547,14 @@ class SearchPage extends HookConsumerWidget {
|
||||
context.t.search_by_context,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSearchType.value == TextSearchType.context ? context.colorScheme.primary : null,
|
||||
color: textSearchType == TextSearchType.context ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
selected: textSearchType.value == TextSearchType.context,
|
||||
selected: textSearchType == TextSearchType.context,
|
||||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.context;
|
||||
searchHintText.value = context.t.sunrise_on_the_beach;
|
||||
selectedTextSearchType.value = TextSearchType.context;
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -562,15 +565,14 @@ class SearchPage extends HookConsumerWidget {
|
||||
context.t.search_filter_filename,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSearchType.value == TextSearchType.filename ? context.colorScheme.primary : null,
|
||||
color: textSearchType == TextSearchType.filename ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
selected: textSearchType.value == TextSearchType.filename,
|
||||
selected: textSearchType == TextSearchType.filename,
|
||||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.filename;
|
||||
searchHintText.value = context.t.file_name_or_extension;
|
||||
selectedTextSearchType.value = TextSearchType.filename;
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
@@ -580,15 +582,14 @@ class SearchPage extends HookConsumerWidget {
|
||||
context.t.search_by_description,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSearchType.value == TextSearchType.description ? context.colorScheme.primary : null,
|
||||
color: textSearchType == TextSearchType.description ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
selected: textSearchType.value == TextSearchType.description,
|
||||
selected: textSearchType == TextSearchType.description,
|
||||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.description;
|
||||
searchHintText.value = context.t.search_by_description_example;
|
||||
selectedTextSearchType.value = TextSearchType.description;
|
||||
},
|
||||
),
|
||||
FeatureCheck(
|
||||
@@ -600,15 +601,14 @@ class SearchPage extends HookConsumerWidget {
|
||||
context.t.search_by_ocr,
|
||||
style: context.textTheme.bodyLarge?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: textSearchType.value == TextSearchType.ocr ? context.colorScheme.primary : null,
|
||||
color: textSearchType == TextSearchType.ocr ? context.colorScheme.primary : null,
|
||||
),
|
||||
),
|
||||
selectedColor: context.colorScheme.primary,
|
||||
selected: textSearchType.value == TextSearchType.ocr,
|
||||
selected: textSearchType == TextSearchType.ocr,
|
||||
),
|
||||
onPressed: () {
|
||||
textSearchType.value = TextSearchType.ocr;
|
||||
searchHintText.value = context.t.search_by_ocr_example;
|
||||
selectedTextSearchType.value = TextSearchType.ocr;
|
||||
},
|
||||
),
|
||||
),
|
||||
@@ -631,7 +631,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
child: SearchField(
|
||||
hintText: searchHintText.value,
|
||||
hintText: searchHintText,
|
||||
key: const Key('search_text_field'),
|
||||
controller: textSearchController,
|
||||
contentPadding: filter.value.assetId != null ? const EdgeInsets.only(left: 24) : const EdgeInsets.all(8),
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/presentation/pages/search/search.page.dart';
|
||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||
|
||||
import '../../unit/presentation/presentation_context.dart';
|
||||
|
||||
/// Server info whose features arrive after the page is built, like on a slow or late connection
|
||||
class DelayedServerInfoNotifier extends ServerInfoNotifier {
|
||||
DelayedServerInfoNotifier(super.serverInfoService);
|
||||
|
||||
void enableSmartSearch() => state = state.copyWith(serverFeatures: state.serverFeatures.copyWith(smartSearch: true));
|
||||
}
|
||||
|
||||
void main() {
|
||||
late PresentationContext context;
|
||||
|
||||
setUp(() async => context = await PresentationContext.create());
|
||||
|
||||
tearDown(() async => context.dispose());
|
||||
|
||||
final contextSearchIcon = find.byIcon(Icons.image_search_rounded);
|
||||
final filenameSearchIcon = find.byIcon(Icons.abc_rounded);
|
||||
|
||||
Future<DelayedServerInfoNotifier> pumpSearchPage(WidgetTester tester) async {
|
||||
final serverInfo = DelayedServerInfoNotifier(context.service.serverInfo);
|
||||
await tester.pumpTestWidget(
|
||||
context,
|
||||
const SearchPage(),
|
||||
overrides: [serverInfoProvider.overrideWith((ref) => serverInfo)],
|
||||
);
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
testWidgets('uses context search once smart search is reported by the server', (tester) async {
|
||||
final serverInfo = await pumpSearchPage(tester);
|
||||
expect(filenameSearchIcon, findsOneWidget);
|
||||
|
||||
serverInfo.enableSmartSearch();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(contextSearchIcon, findsOneWidget);
|
||||
expect(filenameSearchIcon, findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('keeps the search type selected by the user', (tester) async {
|
||||
final serverInfo = await pumpSearchPage(tester);
|
||||
|
||||
await tester.tap(find.byIcon(Icons.more_vert_rounded));
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byIcon(Icons.text_snippet_outlined));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
serverInfo.enableSmartSearch();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byIcon(Icons.text_snippet_outlined), findsOneWidget);
|
||||
expect(contextSearchIcon, findsNothing);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user