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