mirror of https://github.com/immich-app/immich.git
Show curated asset's location in search page (#55)
* Added Tab Navigation Observer to trigger event handling for tab page navigation * Added query to get access with distinct location * Showed places in search page as a horizontal list * Showed location search result on tappedpull/62/head
parent
348d395b21
commit
8c7080eaef
@ -0,0 +1,79 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class CuratedLocation {
|
||||||
|
final String id;
|
||||||
|
final String city;
|
||||||
|
final String resizePath;
|
||||||
|
final String deviceAssetId;
|
||||||
|
final String deviceId;
|
||||||
|
|
||||||
|
CuratedLocation({
|
||||||
|
required this.id,
|
||||||
|
required this.city,
|
||||||
|
required this.resizePath,
|
||||||
|
required this.deviceAssetId,
|
||||||
|
required this.deviceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
CuratedLocation copyWith({
|
||||||
|
String? id,
|
||||||
|
String? city,
|
||||||
|
String? resizePath,
|
||||||
|
String? deviceAssetId,
|
||||||
|
String? deviceId,
|
||||||
|
}) {
|
||||||
|
return CuratedLocation(
|
||||||
|
id: id ?? this.id,
|
||||||
|
city: city ?? this.city,
|
||||||
|
resizePath: resizePath ?? this.resizePath,
|
||||||
|
deviceAssetId: deviceAssetId ?? this.deviceAssetId,
|
||||||
|
deviceId: deviceId ?? this.deviceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'id': id,
|
||||||
|
'city': city,
|
||||||
|
'resizePath': resizePath,
|
||||||
|
'deviceAssetId': deviceAssetId,
|
||||||
|
'deviceId': deviceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CuratedLocation.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CuratedLocation(
|
||||||
|
id: map['id'] ?? '',
|
||||||
|
city: map['city'] ?? '',
|
||||||
|
resizePath: map['resizePath'] ?? '',
|
||||||
|
deviceAssetId: map['deviceAssetId'] ?? '',
|
||||||
|
deviceId: map['deviceId'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory CuratedLocation.fromJson(String source) => CuratedLocation.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CuratedLocation(id: $id, city: $city, resizePath: $resizePath, deviceAssetId: $deviceAssetId, deviceId: $deviceId)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other is CuratedLocation &&
|
||||||
|
other.id == id &&
|
||||||
|
other.city == city &&
|
||||||
|
other.resizePath == resizePath &&
|
||||||
|
other.deviceAssetId == deviceAssetId &&
|
||||||
|
other.deviceId == deviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return id.hashCode ^ city.hashCode ^ resizePath.hashCode ^ deviceAssetId.hashCode ^ deviceId.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,78 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_result_page_state.model.dart';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/modules/search/services/search.service.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
|
||||||
|
SearchResultPageNotifier()
|
||||||
|
: super(SearchResultPageState(searchResult: [], isError: false, isLoading: true, isSuccess: false));
|
||||||
|
|
||||||
|
final SearchService _searchService = SearchService();
|
||||||
|
|
||||||
|
void search(String searchTerm) async {
|
||||||
|
state = state.copyWith(searchResult: [], isError: false, isLoading: true, isSuccess: false);
|
||||||
|
|
||||||
|
List<ImmichAsset>? assets = await _searchService.searchAsset(searchTerm);
|
||||||
|
|
||||||
|
if (assets != null) {
|
||||||
|
state = state.copyWith(searchResult: assets, isError: false, isLoading: false, isSuccess: true);
|
||||||
|
} else {
|
||||||
|
state = state.copyWith(searchResult: [], isError: true, isLoading: false, isSuccess: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final searchResultPageProvider = StateNotifierProvider<SearchResultPageNotifier, SearchResultPageState>((ref) {
|
||||||
|
return SearchResultPageNotifier();
|
||||||
|
});
|
||||||
|
|
||||||
|
final searchResultGroupByDateTimeProvider = StateProvider((ref) {
|
||||||
|
var assets = ref.watch(searchResultPageProvider).searchResult;
|
||||||
|
|
||||||
|
assets.sortByCompare<DateTime>((e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
|
||||||
|
return assets.groupListsBy((element) => DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
|
||||||
|
});
|
||||||
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
||||||
|
|
||||||
|
class TabNavigationObserver extends AutoRouterObserver {
|
||||||
|
/// Riverpod Instance
|
||||||
|
final WidgetRef ref;
|
||||||
|
|
||||||
|
TabNavigationObserver({
|
||||||
|
required this.ref,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) {
|
||||||
|
// Perform tasks on first navigation to SearchRoute
|
||||||
|
if (route.name == 'SearchRoute') {
|
||||||
|
// ref.refresh(getCuratedLocationProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> didChangeTabRoute(TabPageRoute route, TabPageRoute previousRoute) async {
|
||||||
|
// Perform tasks on re-visit to SearchRoute
|
||||||
|
if (route.name == 'SearchRoute') {
|
||||||
|
// Refresh Location State
|
||||||
|
ref.refresh(getCuratedLocationProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,30 +0,0 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:immich_mobile/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(const ImmichApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue