feat: check server feature to render OCR search option (#23325)

This commit is contained in:
Alex 2025-10-28 13:54:41 -05:00 committed by GitHub
parent 9676da27c9
commit 106effca2e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 71 additions and 19 deletions

View File

@ -5,33 +5,37 @@ class ServerFeatures {
final bool map; final bool map;
final bool oauthEnabled; final bool oauthEnabled;
final bool passwordLogin; final bool passwordLogin;
final bool ocr;
const ServerFeatures({ const ServerFeatures({
required this.trash, required this.trash,
required this.map, required this.map,
required this.oauthEnabled, required this.oauthEnabled,
required this.passwordLogin, required this.passwordLogin,
this.ocr = false,
}); });
ServerFeatures copyWith({bool? trash, bool? map, bool? oauthEnabled, bool? passwordLogin}) { ServerFeatures copyWith({bool? trash, bool? map, bool? oauthEnabled, bool? passwordLogin, bool? ocr}) {
return ServerFeatures( return ServerFeatures(
trash: trash ?? this.trash, trash: trash ?? this.trash,
map: map ?? this.map, map: map ?? this.map,
oauthEnabled: oauthEnabled ?? this.oauthEnabled, oauthEnabled: oauthEnabled ?? this.oauthEnabled,
passwordLogin: passwordLogin ?? this.passwordLogin, passwordLogin: passwordLogin ?? this.passwordLogin,
ocr: ocr ?? this.ocr,
); );
} }
@override @override
String toString() { String toString() {
return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin)'; return 'ServerFeatures(trash: $trash, map: $map, oauthEnabled: $oauthEnabled, passwordLogin: $passwordLogin, ocr: $ocr)';
} }
ServerFeatures.fromDto(ServerFeaturesDto dto) ServerFeatures.fromDto(ServerFeaturesDto dto)
: trash = dto.trash, : trash = dto.trash,
map = dto.map, map = dto.map,
oauthEnabled = dto.oauth, oauthEnabled = dto.oauth,
passwordLogin = dto.passwordLogin; passwordLogin = dto.passwordLogin,
ocr = dto.ocr;
@override @override
bool operator ==(covariant ServerFeatures other) { bool operator ==(covariant ServerFeatures other) {
@ -40,11 +44,12 @@ class ServerFeatures {
return other.trash == trash && return other.trash == trash &&
other.map == map && other.map == map &&
other.oauthEnabled == oauthEnabled && other.oauthEnabled == oauthEnabled &&
other.passwordLogin == passwordLogin; other.passwordLogin == passwordLogin &&
other.ocr == ocr;
} }
@override @override
int get hashCode { int get hashCode {
return trash.hashCode ^ map.hashCode ^ oauthEnabled.hashCode ^ passwordLogin.hashCode; return trash.hashCode ^ map.hashCode ^ oauthEnabled.hashCode ^ passwordLogin.hashCode ^ ocr.hashCode;
} }
} }

View File

@ -19,6 +19,7 @@ import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/common/feature_check.dart';
import 'package:immich_mobile/widgets/common/search_field.dart'; import 'package:immich_mobile/widgets/common/search_field.dart';
import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart'; import 'package:immich_mobile/widgets/search/search_filter/camera_picker.dart';
import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart'; import 'package:immich_mobile/widgets/search/search_filter/display_option_picker.dart';
@ -503,7 +504,9 @@ class DriftSearchPage extends HookConsumerWidget {
searchHintText.value = 'search_by_description_example'.t(context: context); searchHintText.value = 'search_by_description_example'.t(context: context);
}, },
), ),
MenuItemButton( FeatureCheck(
feature: (features) => features.ocr,
child: MenuItemButton(
child: ListTile( child: ListTile(
leading: const Icon(Icons.document_scanner_outlined), leading: const Icon(Icons.document_scanner_outlined),
title: Text( title: Text(
@ -521,6 +524,7 @@ class DriftSearchPage extends HookConsumerWidget {
searchHintText.value = 'search_by_ocr_example'.t(context: context); searchHintText.value = 'search_by_ocr_example'.t(context: context);
}, },
), ),
),
], ],
), ),
), ),

View File

@ -46,6 +46,11 @@ dynamic upgradeDto(dynamic value, String targetType) {
addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String()); addDefault(value, 'profileChangedAt', DateTime.now().toIso8601String());
addDefault(value, 'hasProfileImage', false); addDefault(value, 'hasProfileImage', false);
} }
case 'ServerFeaturesDto':
if (value is Map) {
addDefault(value, 'ocr', false);
}
break;
} }
} }

View File

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/models/server_info/server_features.model.dart';
import 'package:immich_mobile/providers/server_info.provider.dart';
/// A utility widget that conditionally renders its child based on a server feature flag.
///
/// Example usage:
/// ```dart
/// FeatureCheck(
/// feature: (features) => features.ocr,
/// child: Text('OCR is enabled'),
/// fallback: Text('OCR is not available'),
/// )
/// ```
class FeatureCheck extends ConsumerWidget {
/// A function that extracts the specific feature flag from ServerFeatures
final bool Function(ServerFeatures) feature;
/// The widget to display when the feature is enabled
final Widget child;
/// Optional widget to display when the feature is disabled
final Widget? fallback;
const FeatureCheck({super.key, required this.feature, required this.child, this.fallback});
@override
Widget build(BuildContext context, WidgetRef ref) {
final serverFeatures = ref.watch(serverInfoProvider.select((s) => s.serverFeatures));
final isFeatureEnabled = feature(serverFeatures);
if (isFeatureEnabled) {
return child;
}
return fallback ?? const SizedBox.shrink();
}
}