mirror of
https://github.com/immich-app/immich.git
synced 2025-01-28 06:32:44 +01:00
fix(mobile): show places in Search page on mobile (#9085)
* fix(mobile): show map on mobile * remove ununsed code
This commit is contained in:
parent
d52ed51aab
commit
52bcb46b42
6 changed files with 40 additions and 191 deletions
mobile/lib
modules/search
models
providers
services
views
routing
|
@ -1,81 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
|
|
||||||
class SearchPageState {
|
|
||||||
final String searchTerm;
|
|
||||||
final bool isSearchEnabled;
|
|
||||||
final List<String> searchSuggestion;
|
|
||||||
final List<String> userSuggestedSearchTerms;
|
|
||||||
|
|
||||||
SearchPageState({
|
|
||||||
required this.searchTerm,
|
|
||||||
required this.isSearchEnabled,
|
|
||||||
required this.searchSuggestion,
|
|
||||||
required this.userSuggestedSearchTerms,
|
|
||||||
});
|
|
||||||
|
|
||||||
SearchPageState copyWith({
|
|
||||||
String? searchTerm,
|
|
||||||
bool? isSearchEnabled,
|
|
||||||
List<String>? searchSuggestion,
|
|
||||||
List<String>? userSuggestedSearchTerms,
|
|
||||||
}) {
|
|
||||||
return SearchPageState(
|
|
||||||
searchTerm: searchTerm ?? this.searchTerm,
|
|
||||||
isSearchEnabled: isSearchEnabled ?? this.isSearchEnabled,
|
|
||||||
searchSuggestion: searchSuggestion ?? this.searchSuggestion,
|
|
||||||
userSuggestedSearchTerms:
|
|
||||||
userSuggestedSearchTerms ?? this.userSuggestedSearchTerms,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return {
|
|
||||||
'searchTerm': searchTerm,
|
|
||||||
'isSearchEnabled': isSearchEnabled,
|
|
||||||
'searchSuggestion': searchSuggestion,
|
|
||||||
'userSuggestedSearchTerms': userSuggestedSearchTerms,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory SearchPageState.fromMap(Map<String, dynamic> map) {
|
|
||||||
return SearchPageState(
|
|
||||||
searchTerm: map['searchTerm'] ?? '',
|
|
||||||
isSearchEnabled: map['isSearchEnabled'] ?? false,
|
|
||||||
searchSuggestion: List<String>.from(map['searchSuggestion']),
|
|
||||||
userSuggestedSearchTerms:
|
|
||||||
List<String>.from(map['userSuggestedSearchTerms']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory SearchPageState.fromJson(String source) =>
|
|
||||||
SearchPageState.fromMap(json.decode(source));
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SearchPageState(searchTerm: $searchTerm, isSearchEnabled: $isSearchEnabled, searchSuggestion: $searchSuggestion, userSuggestedSearchTerms: $userSuggestedSearchTerms)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
final listEquals = const DeepCollectionEquality().equals;
|
|
||||||
|
|
||||||
return other is SearchPageState &&
|
|
||||||
other.searchTerm == searchTerm &&
|
|
||||||
other.isSearchEnabled == isSearchEnabled &&
|
|
||||||
listEquals(other.searchSuggestion, searchSuggestion) &&
|
|
||||||
listEquals(other.userSuggestedSearchTerms, userSuggestedSearchTerms);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
return searchTerm.hashCode ^
|
|
||||||
isSearchEnabled.hashCode ^
|
|
||||||
searchSuggestion.hashCode ^
|
|
||||||
userSuggestedSearchTerms.hashCode;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,65 +1,29 @@
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/search/models/search_page_state.model.dart';
|
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
||||||
|
|
||||||
import 'package:immich_mobile/modules/search/services/search.service.dart';
|
import 'package:immich_mobile/modules/search/services/search.service.dart';
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
class SearchPageStateNotifier extends StateNotifier<SearchPageState> {
|
final getPlacesProvider =
|
||||||
SearchPageStateNotifier(this._searchService)
|
FutureProvider.autoDispose<List<CuratedContent>>((ref) async {
|
||||||
: super(
|
|
||||||
SearchPageState(
|
|
||||||
searchTerm: "",
|
|
||||||
isSearchEnabled: false,
|
|
||||||
searchSuggestion: [],
|
|
||||||
userSuggestedSearchTerms: [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final SearchService _searchService;
|
|
||||||
|
|
||||||
void enableSearch() {
|
|
||||||
state = state.copyWith(isSearchEnabled: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void disableSearch() {
|
|
||||||
state = state.copyWith(isSearchEnabled: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setSearchTerm(String value) {
|
|
||||||
state = state.copyWith(searchTerm: value);
|
|
||||||
|
|
||||||
_getSearchSuggestion(state.searchTerm);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _getSearchSuggestion(String searchTerm) {
|
|
||||||
var searchList = state.userSuggestedSearchTerms;
|
|
||||||
|
|
||||||
var newList = searchList.where((e) => e.toLowerCase().contains(searchTerm));
|
|
||||||
|
|
||||||
state = state.copyWith(searchSuggestion: [...newList]);
|
|
||||||
|
|
||||||
if (searchTerm.isEmpty) {
|
|
||||||
state = state.copyWith(searchSuggestion: []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void getSuggestedSearchTerms() async {
|
|
||||||
var userSuggestedSearchTerms =
|
|
||||||
await _searchService.getUserSuggestedSearchTerms();
|
|
||||||
|
|
||||||
state = state.copyWith(userSuggestedSearchTerms: userSuggestedSearchTerms);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final searchPageStateProvider =
|
|
||||||
StateNotifierProvider<SearchPageStateNotifier, SearchPageState>((ref) {
|
|
||||||
return SearchPageStateNotifier(ref.watch(searchServiceProvider));
|
|
||||||
});
|
|
||||||
|
|
||||||
final getCuratedLocationProvider =
|
|
||||||
FutureProvider.autoDispose<List<CuratedLocationsResponseDto>>((ref) async {
|
|
||||||
final SearchService searchService = ref.watch(searchServiceProvider);
|
final SearchService searchService = ref.watch(searchServiceProvider);
|
||||||
|
|
||||||
var curatedLocation = await searchService.getCuratedLocation();
|
final exploreData = await searchService.getExploreData();
|
||||||
return curatedLocation ?? [];
|
|
||||||
|
if (exploreData == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final locations =
|
||||||
|
exploreData.firstWhere((data) => data.fieldName == "exifInfo.city").items;
|
||||||
|
|
||||||
|
final curatedContent = locations
|
||||||
|
.map(
|
||||||
|
(l) => CuratedContent(
|
||||||
|
label: l.value,
|
||||||
|
id: l.data.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return curatedContent;
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
final searchServiceProvider = Provider(
|
final searchServiceProvider = Provider(
|
||||||
|
@ -19,17 +20,9 @@ class SearchService {
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final Isar _db;
|
final Isar _db;
|
||||||
|
|
||||||
|
final _log = Logger("SearchService");
|
||||||
SearchService(this._apiService, this._db);
|
SearchService(this._apiService, this._db);
|
||||||
|
|
||||||
Future<List<String>?> getUserSuggestedSearchTerms() async {
|
|
||||||
try {
|
|
||||||
return await _apiService.assetApi.getAssetSearchTerms();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("[ERROR] [getUserSuggestedSearchTerms] ${e.toString()}");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<String>?> getSearchSuggestions(
|
Future<List<String>?> getSearchSuggestions(
|
||||||
SearchSuggestionType type, {
|
SearchSuggestionType type, {
|
||||||
String? country,
|
String? country,
|
||||||
|
@ -112,29 +105,18 @@ class SearchService {
|
||||||
|
|
||||||
return _db.assets
|
return _db.assets
|
||||||
.getAllByRemoteId(response.assets.items.map((e) => e.id));
|
.getAllByRemoteId(response.assets.items.map((e) => e.id));
|
||||||
} catch (error) {
|
} catch (error, stackTrace) {
|
||||||
debugPrint("Error [search] $error");
|
_log.severe("Failed to search for assets", error, stackTrace);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<CuratedLocationsResponseDto>?> getCuratedLocation() async {
|
Future<List<SearchExploreResponseDto>?> getExploreData() async {
|
||||||
try {
|
try {
|
||||||
var locations = await _apiService.assetApi.getCuratedLocations();
|
return await _apiService.searchApi.getExploreData();
|
||||||
|
} catch (error, stackTrace) {
|
||||||
return locations;
|
_log.severe("Failed to getExploreData", error, stackTrace);
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Error [getCuratedLocation] ${e.toString()}");
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<CuratedObjectsResponseDto>?> getCuratedObjects() async {
|
|
||||||
try {
|
|
||||||
return await _apiService.assetApi.getCuratedObjects();
|
|
||||||
} catch (e) {
|
|
||||||
debugPrint("Error [getCuratedObjects] ${e.toString()}");
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
|
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class CuratedLocationPage extends HookConsumerWidget {
|
class CuratedLocationPage extends HookConsumerWidget {
|
||||||
|
@ -14,8 +13,7 @@ class CuratedLocationPage extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
AsyncValue<List<CuratedLocationsResponseDto>> curatedLocation =
|
AsyncValue<List<CuratedContent>> places = ref.watch(getPlacesProvider);
|
||||||
ref.watch(getCuratedLocationProvider);
|
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -27,16 +25,9 @@ class CuratedLocationPage extends HookConsumerWidget {
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: curatedLocation.widgetWhen(
|
body: places.widgetWhen(
|
||||||
onData: (curatedLocations) => ExploreGrid(
|
onData: (data) => ExploreGrid(
|
||||||
curatedContent: curatedLocations
|
curatedContent: data,
|
||||||
.map(
|
|
||||||
(l) => CuratedContent(
|
|
||||||
label: l.city,
|
|
||||||
id: l.id,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -27,7 +27,7 @@ class SearchPage extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final curatedLocation = ref.watch(getCuratedLocationProvider);
|
final places = ref.watch(getPlacesProvider);
|
||||||
final curatedPeople = ref.watch(getAllPeopleProvider);
|
final curatedPeople = ref.watch(getAllPeopleProvider);
|
||||||
final isMapEnabled =
|
final isMapEnabled =
|
||||||
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.map));
|
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.map));
|
||||||
|
@ -87,18 +87,11 @@ class SearchPage extends HookConsumerWidget {
|
||||||
buildPlaces() {
|
buildPlaces() {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: imageSize,
|
height: imageSize,
|
||||||
child: curatedLocation.widgetWhen(
|
child: places.widgetWhen(
|
||||||
onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
|
onError: (error, stack) => const ScaffoldErrorBody(withIcon: false),
|
||||||
onData: (locations) => CuratedPlacesRow(
|
onData: (data) => CuratedPlacesRow(
|
||||||
isMapEnabled: isMapEnabled,
|
isMapEnabled: isMapEnabled,
|
||||||
content: locations
|
content: data,
|
||||||
.map(
|
|
||||||
(o) => CuratedContent(
|
|
||||||
id: o.id,
|
|
||||||
label: o.city,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
imageSize: imageSize,
|
imageSize: imageSize,
|
||||||
onTap: (content, index) {
|
onTap: (content, index) {
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
|
|
|
@ -37,7 +37,7 @@ class TabNavigationObserver extends AutoRouterObserver {
|
||||||
// Perform tasks on re-visit to SearchRoute
|
// Perform tasks on re-visit to SearchRoute
|
||||||
if (route.name == 'SearchRoute') {
|
if (route.name == 'SearchRoute') {
|
||||||
// Refresh Location State
|
// Refresh Location State
|
||||||
ref.invalidate(getCuratedLocationProvider);
|
ref.invalidate(getPlacesProvider);
|
||||||
ref.invalidate(getAllPeopleProvider);
|
ref.invalidate(getAllPeopleProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue