mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
feat(mobile): search enhancement (#8392)
This commit is contained in:
parent
861b72ef04
commit
27be813011
35 changed files with 4018 additions and 2753 deletions
|
@ -17,6 +17,9 @@ PODS:
|
||||||
- fluttertoast (0.0.2):
|
- fluttertoast (0.0.2):
|
||||||
- Flutter
|
- Flutter
|
||||||
- Toast
|
- Toast
|
||||||
|
- FMDB (2.7.5):
|
||||||
|
- FMDB/standard (= 2.7.5)
|
||||||
|
- FMDB/standard (2.7.5)
|
||||||
- geolocator_apple (1.2.0):
|
- geolocator_apple (1.2.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
|
@ -36,7 +39,7 @@ PODS:
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- permission_handler_apple (9.3.0):
|
- permission_handler_apple (9.1.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- photo_manager (2.0.0):
|
- photo_manager (2.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
@ -50,7 +53,7 @@ PODS:
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- sqflite (0.0.3):
|
- sqflite (0.0.3):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FMDB (>= 2.7.5)
|
||||||
- Toast (4.0.0)
|
- Toast (4.0.0)
|
||||||
- url_launcher_ios (0.0.1):
|
- url_launcher_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
@ -81,13 +84,14 @@ DEPENDENCIES:
|
||||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
trunk:
|
trunk:
|
||||||
|
- FMDB
|
||||||
- MapLibre
|
- MapLibre
|
||||||
- ReachabilitySwift
|
- ReachabilitySwift
|
||||||
- SAMKeychain
|
- SAMKeychain
|
||||||
|
@ -135,7 +139,7 @@ EXTERNAL SOURCES:
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/darwin"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
|
@ -151,23 +155,24 @@ SPEC CHECKSUMS:
|
||||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||||
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
flutter_udid: a2482c67a61b9c806ef59dd82ed8d007f1b7ac04
|
||||||
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
|
||||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
|
||||||
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
geolocator_apple: 9157311f654584b9bb72686c55fc02a97b73f461
|
||||||
image_picker_ios: 99dfe1854b4fa34d0364e74a78448a0151025425
|
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||||
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
|
MapLibre: 620fc933c1d6029b33738c905c1490d024e5d4ef
|
||||||
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9
|
maplibre_gl: a2efec727dd340e4c65e26d2b03b584f14881fd9
|
||||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
permission_handler_apple: 036b856153a2b1f61f21030ff725f3e6fece2b78
|
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||||
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
|
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
url_launcher_ios: bbd758c6e7f9fd7b5b1d4cde34d2b95fcce5e812
|
||||||
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579
|
||||||
|
@ -175,4 +180,4 @@ SPEC CHECKSUMS:
|
||||||
|
|
||||||
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
|
PODFILE CHECKSUM: 64c9b5291666c0ca3caabdfe9865c141ac40321d
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.15.2
|
||||||
|
|
|
@ -42,7 +42,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
|
||||||
this.assetsPerRow,
|
this.assetsPerRow,
|
||||||
this.showStorageIndicator,
|
this.showStorageIndicator,
|
||||||
this.listener,
|
this.listener,
|
||||||
this.margin = 5.0,
|
this.margin = 2.0,
|
||||||
this.selectionActive = false,
|
this.selectionActive = false,
|
||||||
this.preselectedAssets,
|
this.preselectedAssets,
|
||||||
this.canDeselect = true,
|
this.canDeselect = true,
|
||||||
|
|
|
@ -1,3 +1,6 @@
|
||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
/// A wrapper for [CuratedLocationsResponseDto] objects
|
/// A wrapper for [CuratedLocationsResponseDto] objects
|
||||||
/// and [CuratedObjectsResponseDto] to be displayed in
|
/// and [CuratedObjectsResponseDto] to be displayed in
|
||||||
/// a view
|
/// a view
|
||||||
|
@ -9,7 +12,49 @@ class CuratedContent {
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
CuratedContent({
|
CuratedContent({
|
||||||
required this.id,
|
|
||||||
required this.label,
|
required this.label,
|
||||||
|
required this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CuratedContent copyWith({
|
||||||
|
String? label,
|
||||||
|
String? id,
|
||||||
|
}) {
|
||||||
|
return CuratedContent(
|
||||||
|
label: label ?? this.label,
|
||||||
|
id: id ?? this.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'label': label,
|
||||||
|
'id': id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CuratedContent.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CuratedContent(
|
||||||
|
label: map['label'] as String,
|
||||||
|
id: map['id'] as String,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory CuratedContent.fromJson(String source) =>
|
||||||
|
CuratedContent.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'CuratedContent(label: $label, id: $id)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant CuratedContent other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.label == label && other.id == id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => label.hashCode ^ id.hashCode;
|
||||||
}
|
}
|
||||||
|
|
310
mobile/lib/modules/search/models/search_filter.dart
Normal file
310
mobile/lib/modules/search/models/search_filter.dart
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class SearchLocationFilter {
|
||||||
|
String? country;
|
||||||
|
String? state;
|
||||||
|
String? city;
|
||||||
|
SearchLocationFilter({
|
||||||
|
this.country,
|
||||||
|
this.state,
|
||||||
|
this.city,
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchLocationFilter copyWith({
|
||||||
|
String? country,
|
||||||
|
String? state,
|
||||||
|
String? city,
|
||||||
|
}) {
|
||||||
|
return SearchLocationFilter(
|
||||||
|
country: country ?? this.country,
|
||||||
|
state: state ?? this.state,
|
||||||
|
city: city ?? this.city,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'country': country,
|
||||||
|
'state': state,
|
||||||
|
'city': city,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SearchLocationFilter.fromMap(Map<String, dynamic> map) {
|
||||||
|
return SearchLocationFilter(
|
||||||
|
country: map['country'] != null ? map['country'] as String : null,
|
||||||
|
state: map['state'] != null ? map['state'] as String : null,
|
||||||
|
city: map['city'] != null ? map['city'] as String : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchLocationFilter.fromJson(String source) =>
|
||||||
|
SearchLocationFilter.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'SearchLocationFilter(country: $country, state: $state, city: $city)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant SearchLocationFilter other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.country == country &&
|
||||||
|
other.state == state &&
|
||||||
|
other.city == city;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => country.hashCode ^ state.hashCode ^ city.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchCameraFilter {
|
||||||
|
String? make;
|
||||||
|
String? model;
|
||||||
|
SearchCameraFilter({
|
||||||
|
this.make,
|
||||||
|
this.model,
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchCameraFilter copyWith({
|
||||||
|
String? make,
|
||||||
|
String? model,
|
||||||
|
}) {
|
||||||
|
return SearchCameraFilter(
|
||||||
|
make: make ?? this.make,
|
||||||
|
model: model ?? this.model,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'make': make,
|
||||||
|
'model': model,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SearchCameraFilter.fromMap(Map<String, dynamic> map) {
|
||||||
|
return SearchCameraFilter(
|
||||||
|
make: map['make'] != null ? map['make'] as String : null,
|
||||||
|
model: map['model'] != null ? map['model'] as String : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchCameraFilter.fromJson(String source) =>
|
||||||
|
SearchCameraFilter.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SearchCameraFilter(make: $make, model: $model)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant SearchCameraFilter other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.make == make && other.model == model;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => make.hashCode ^ model.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchDateFilter {
|
||||||
|
DateTime? takenBefore;
|
||||||
|
DateTime? takenAfter;
|
||||||
|
SearchDateFilter({
|
||||||
|
this.takenBefore,
|
||||||
|
this.takenAfter,
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchDateFilter copyWith({
|
||||||
|
DateTime? takenBefore,
|
||||||
|
DateTime? takenAfter,
|
||||||
|
}) {
|
||||||
|
return SearchDateFilter(
|
||||||
|
takenBefore: takenBefore ?? this.takenBefore,
|
||||||
|
takenAfter: takenAfter ?? this.takenAfter,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'takenBefore': takenBefore?.millisecondsSinceEpoch,
|
||||||
|
'takenAfter': takenAfter?.millisecondsSinceEpoch,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SearchDateFilter.fromMap(Map<String, dynamic> map) {
|
||||||
|
return SearchDateFilter(
|
||||||
|
takenBefore: map['takenBefore'] != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(map['takenBefore'] as int)
|
||||||
|
: null,
|
||||||
|
takenAfter: map['takenAfter'] != null
|
||||||
|
? DateTime.fromMillisecondsSinceEpoch(map['takenAfter'] as int)
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchDateFilter.fromJson(String source) =>
|
||||||
|
SearchDateFilter.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'SearchDateFilter(takenBefore: $takenBefore, takenAfter: $takenAfter)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant SearchDateFilter other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.takenBefore == takenBefore && other.takenAfter == takenAfter;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => takenBefore.hashCode ^ takenAfter.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchDisplayFilters {
|
||||||
|
bool isNotInAlbum = false;
|
||||||
|
bool isArchive = false;
|
||||||
|
bool isFavorite = false;
|
||||||
|
SearchDisplayFilters({
|
||||||
|
required this.isNotInAlbum,
|
||||||
|
required this.isArchive,
|
||||||
|
required this.isFavorite,
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchDisplayFilters copyWith({
|
||||||
|
bool? isNotInAlbum,
|
||||||
|
bool? isArchive,
|
||||||
|
bool? isFavorite,
|
||||||
|
}) {
|
||||||
|
return SearchDisplayFilters(
|
||||||
|
isNotInAlbum: isNotInAlbum ?? this.isNotInAlbum,
|
||||||
|
isArchive: isArchive ?? this.isArchive,
|
||||||
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return <String, dynamic>{
|
||||||
|
'isNotInAlbum': isNotInAlbum,
|
||||||
|
'isArchive': isArchive,
|
||||||
|
'isFavorite': isFavorite,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory SearchDisplayFilters.fromMap(Map<String, dynamic> map) {
|
||||||
|
return SearchDisplayFilters(
|
||||||
|
isNotInAlbum: map['isNotInAlbum'] as bool,
|
||||||
|
isArchive: map['isArchive'] as bool,
|
||||||
|
isFavorite: map['isFavorite'] as bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory SearchDisplayFilters.fromJson(String source) =>
|
||||||
|
SearchDisplayFilters.fromMap(json.decode(source) as Map<String, dynamic>);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'SearchDisplayFilters(isNotInAlbum: $isNotInAlbum, isArchive: $isArchive, isFavorite: $isFavorite)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant SearchDisplayFilters other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.isNotInAlbum == isNotInAlbum &&
|
||||||
|
other.isArchive == isArchive &&
|
||||||
|
other.isFavorite == isFavorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
isNotInAlbum.hashCode ^ isArchive.hashCode ^ isFavorite.hashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchFilter {
|
||||||
|
String? context;
|
||||||
|
String? filename;
|
||||||
|
Set<PersonResponseDto> people;
|
||||||
|
SearchLocationFilter location;
|
||||||
|
SearchCameraFilter camera;
|
||||||
|
SearchDateFilter date;
|
||||||
|
SearchDisplayFilters display;
|
||||||
|
|
||||||
|
// Enum
|
||||||
|
AssetType mediaType;
|
||||||
|
|
||||||
|
SearchFilter({
|
||||||
|
this.context,
|
||||||
|
this.filename,
|
||||||
|
required this.people,
|
||||||
|
required this.location,
|
||||||
|
required this.camera,
|
||||||
|
required this.date,
|
||||||
|
required this.display,
|
||||||
|
required this.mediaType,
|
||||||
|
});
|
||||||
|
|
||||||
|
SearchFilter copyWith({
|
||||||
|
String? context,
|
||||||
|
String? filename,
|
||||||
|
Set<PersonResponseDto>? people,
|
||||||
|
SearchLocationFilter? location,
|
||||||
|
SearchCameraFilter? camera,
|
||||||
|
SearchDateFilter? date,
|
||||||
|
SearchDisplayFilters? display,
|
||||||
|
AssetType? mediaType,
|
||||||
|
}) {
|
||||||
|
return SearchFilter(
|
||||||
|
context: context ?? this.context,
|
||||||
|
filename: filename ?? this.filename,
|
||||||
|
people: people ?? this.people,
|
||||||
|
location: location ?? this.location,
|
||||||
|
camera: camera ?? this.camera,
|
||||||
|
date: date ?? this.date,
|
||||||
|
display: display ?? this.display,
|
||||||
|
mediaType: mediaType ?? this.mediaType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SearchFilter(context: $context, filename: $filename, people: $people, location: $location, camera: $camera, date: $date, display: $display, mediaType: $mediaType)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(covariant SearchFilter other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other.context == context &&
|
||||||
|
other.filename == filename &&
|
||||||
|
other.people == people &&
|
||||||
|
other.location == location &&
|
||||||
|
other.camera == camera &&
|
||||||
|
other.date == date &&
|
||||||
|
other.display == display &&
|
||||||
|
other.mediaType == mediaType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return context.hashCode ^
|
||||||
|
filename.hashCode ^
|
||||||
|
people.hashCode ^
|
||||||
|
location.hashCode ^
|
||||||
|
camera.hashCode ^
|
||||||
|
date.hashCode ^
|
||||||
|
display.hashCode ^
|
||||||
|
mediaType.hashCode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/services/search.service.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'paginated_search.provider.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class PaginatedSearch extends _$PaginatedSearch {
|
||||||
|
Future<List<Asset>?> _search(SearchFilter filter, int page) async {
|
||||||
|
final service = ref.read(searchServiceProvider);
|
||||||
|
final result = await service.search(filter, page);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<Asset>> build() async {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Asset>> getNextPage(SearchFilter filter, int nextPage) async {
|
||||||
|
state = const AsyncValue.loading();
|
||||||
|
|
||||||
|
final newState = await AsyncValue.guard(() async {
|
||||||
|
final assets = await _search(filter, nextPage);
|
||||||
|
|
||||||
|
if (assets != null) {
|
||||||
|
return [...?state.value, ...assets];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
state = newState.valueOrNull == null
|
||||||
|
? const AsyncValue.data([])
|
||||||
|
: AsyncValue.data(newState.value!);
|
||||||
|
|
||||||
|
return newState.valueOrNull ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
state = const AsyncValue.data([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
AsyncValue<RenderList> paginatedSearchRenderList(
|
||||||
|
PaginatedSearchRenderListRef ref,
|
||||||
|
) {
|
||||||
|
final assets = ref.watch(paginatedSearchProvider).value;
|
||||||
|
|
||||||
|
if (assets != null) {
|
||||||
|
return ref.watch(
|
||||||
|
renderListProviderWithGrouping(
|
||||||
|
(assets, GroupAssetsBy.none),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const AsyncValue.loading();
|
||||||
|
}
|
||||||
|
}
|
BIN
mobile/lib/modules/search/providers/paginated_search.provider.g.dart
generated
Normal file
BIN
mobile/lib/modules/search/providers/paginated_search.provider.g.dart
generated
Normal file
Binary file not shown.
|
@ -1,23 +1,21 @@
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart';
|
||||||
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/services/person.service.dart';
|
import 'package:immich_mobile/modules/search/services/person.service.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
part 'people.provider.g.dart';
|
part 'people.provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
Future<List<CuratedContent>> getCuratedPeople(
|
Future<List<PersonResponseDto>> getAllPeople(
|
||||||
GetCuratedPeopleRef ref,
|
GetAllPeopleRef ref,
|
||||||
) async {
|
) async {
|
||||||
final PersonService personService = ref.read(personServiceProvider);
|
final PersonService personService = ref.read(personServiceProvider);
|
||||||
|
|
||||||
final curatedPeople = await personService.getCuratedPeople();
|
final people = await personService.getAllPeople();
|
||||||
|
|
||||||
return curatedPeople
|
return people;
|
||||||
.map((p) => CuratedContent(id: p.id, label: p.name))
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
|
@ -44,7 +42,7 @@ Future<bool> updatePersonName(
|
||||||
final person = await personService.updateName(personId, updatedName);
|
final person = await personService.updateName(personId, updatedName);
|
||||||
|
|
||||||
if (person != null && person.name == updatedName) {
|
if (person != null && person.name == updatedName) {
|
||||||
ref.invalidate(getCuratedPeopleProvider);
|
ref.invalidate(getAllPeopleProvider);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,27 @@
|
||||||
|
import 'package:immich_mobile/modules/search/services/search.service.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
part 'search_filter.provider.g.dart';
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
Future<List<String>> getSearchSuggestions(
|
||||||
|
GetSearchSuggestionsRef ref,
|
||||||
|
SearchSuggestionType type, {
|
||||||
|
String? locationCountry,
|
||||||
|
String? locationState,
|
||||||
|
String? make,
|
||||||
|
String? model,
|
||||||
|
}) async {
|
||||||
|
final SearchService service = ref.read(searchServiceProvider);
|
||||||
|
|
||||||
|
final suggestions = await service.getSearchSuggestions(
|
||||||
|
type,
|
||||||
|
country: locationCountry,
|
||||||
|
state: locationState,
|
||||||
|
make: make,
|
||||||
|
model: model,
|
||||||
|
);
|
||||||
|
|
||||||
|
return suggestions ?? [];
|
||||||
|
}
|
BIN
mobile/lib/modules/search/providers/search_filter.provider.g.dart
generated
Normal file
BIN
mobile/lib/modules/search/providers/search_filter.provider.g.dart
generated
Normal file
Binary file not shown.
|
@ -1,67 +0,0 @@
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/render_list.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.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/asset.dart';
|
|
||||||
|
|
||||||
class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
|
|
||||||
SearchResultPageNotifier(this._searchService)
|
|
||||||
: super(
|
|
||||||
SearchResultPageState(
|
|
||||||
searchResult: [],
|
|
||||||
isError: false,
|
|
||||||
isLoading: true,
|
|
||||||
isSuccess: false,
|
|
||||||
isSmart: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final SearchService _searchService;
|
|
||||||
|
|
||||||
Future<void> search(String searchTerm, {bool smartSearch = true}) async {
|
|
||||||
state = state.copyWith(
|
|
||||||
searchResult: [],
|
|
||||||
isError: false,
|
|
||||||
isLoading: true,
|
|
||||||
isSuccess: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
List<Asset>? assets =
|
|
||||||
await _searchService.searchAsset(searchTerm, smartSearch: smartSearch);
|
|
||||||
|
|
||||||
if (assets != null) {
|
|
||||||
state = state.copyWith(
|
|
||||||
searchResult: assets,
|
|
||||||
isError: false,
|
|
||||||
isLoading: false,
|
|
||||||
isSuccess: true,
|
|
||||||
isSmart: smartSearch,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
state = state.copyWith(
|
|
||||||
searchResult: [],
|
|
||||||
isError: true,
|
|
||||||
isLoading: false,
|
|
||||||
isSuccess: false,
|
|
||||||
isSmart: smartSearch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final searchResultPageProvider =
|
|
||||||
StateNotifierProvider<SearchResultPageNotifier, SearchResultPageState>(
|
|
||||||
(ref) {
|
|
||||||
return SearchResultPageNotifier(ref.watch(searchServiceProvider));
|
|
||||||
});
|
|
||||||
|
|
||||||
final searchRenderListProvider = Provider((ref) {
|
|
||||||
final result = ref.watch(searchResultPageProvider);
|
|
||||||
return ref.watch(
|
|
||||||
renderListProviderWithGrouping(
|
|
||||||
(result.searchResult, result.isSmart ? GroupAssetsBy.none : null),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
|
@ -20,7 +20,7 @@ class PersonService {
|
||||||
|
|
||||||
PersonService(this._apiService, this._db);
|
PersonService(this._apiService, this._db);
|
||||||
|
|
||||||
Future<List<PersonResponseDto>> getCuratedPeople() async {
|
Future<List<PersonResponseDto>> getAllPeople() async {
|
||||||
try {
|
try {
|
||||||
final peopleResponseDto = await _apiService.personApi.getAllPeople();
|
final peopleResponseDto = await _apiService.personApi.getAllPeople();
|
||||||
return peopleResponseDto?.people ?? [];
|
return peopleResponseDto?.people ?? [];
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
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';
|
||||||
|
@ -29,25 +30,92 @@ class SearchService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Asset>?> searchAsset(
|
Future<List<String>?> getSearchSuggestions(
|
||||||
String searchTerm, {
|
SearchSuggestionType type, {
|
||||||
bool smartSearch = true,
|
String? country,
|
||||||
|
String? state,
|
||||||
|
String? make,
|
||||||
|
String? model,
|
||||||
}) async {
|
}) async {
|
||||||
// TODO search in local DB: 1. when offline, 2. to find local assets
|
|
||||||
try {
|
try {
|
||||||
final SearchResponseDto? results = await _apiService.searchApi.search(
|
return await _apiService.searchApi.getSearchSuggestions(
|
||||||
query: searchTerm,
|
type,
|
||||||
smart: smartSearch,
|
country: country,
|
||||||
|
state: state,
|
||||||
|
make: make,
|
||||||
|
model: model,
|
||||||
);
|
);
|
||||||
if (results == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// TODO local DB might be out of date; add assets not yet in DB?
|
|
||||||
return _db.assets.getAllByRemoteId(results.assets.items.map((e) => e.id));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("[ERROR] [searchAsset] ${e.toString()}");
|
debugPrint("[ERROR] [getSearchSuggestions] ${e.toString()}");
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Asset>?> search(SearchFilter filter, int page) async {
|
||||||
|
try {
|
||||||
|
SearchResponseDto? response;
|
||||||
|
AssetTypeEnum? type;
|
||||||
|
if (filter.mediaType == AssetType.image) {
|
||||||
|
type = AssetTypeEnum.IMAGE;
|
||||||
|
} else if (filter.mediaType == AssetType.video) {
|
||||||
|
type = AssetTypeEnum.VIDEO;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.context != null && filter.context!.isNotEmpty) {
|
||||||
|
response = await _apiService.searchApi.searchSmart(
|
||||||
|
SmartSearchDto(
|
||||||
|
query: filter.context!,
|
||||||
|
country: filter.location.country,
|
||||||
|
state: filter.location.state,
|
||||||
|
city: filter.location.city,
|
||||||
|
make: filter.camera.make,
|
||||||
|
model: filter.camera.model,
|
||||||
|
takenAfter: filter.date.takenAfter,
|
||||||
|
takenBefore: filter.date.takenBefore,
|
||||||
|
isArchived: filter.display.isArchive,
|
||||||
|
isFavorite: filter.display.isFavorite,
|
||||||
|
isNotInAlbum: filter.display.isNotInAlbum,
|
||||||
|
personIds: filter.people.map((e) => e.id).toList(),
|
||||||
|
type: type,
|
||||||
|
page: page,
|
||||||
|
size: 1000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
response = await _apiService.searchApi.searchMetadata(
|
||||||
|
MetadataSearchDto(
|
||||||
|
originalFileName:
|
||||||
|
filter.filename != null && filter.filename!.isNotEmpty
|
||||||
|
? filter.filename
|
||||||
|
: null,
|
||||||
|
country: filter.location.country,
|
||||||
|
state: filter.location.state,
|
||||||
|
city: filter.location.city,
|
||||||
|
make: filter.camera.make,
|
||||||
|
model: filter.camera.model,
|
||||||
|
takenAfter: filter.date.takenAfter,
|
||||||
|
takenBefore: filter.date.takenBefore,
|
||||||
|
isArchived: filter.display.isArchive,
|
||||||
|
isFavorite: filter.display.isFavorite,
|
||||||
|
isNotInAlbum: filter.display.isNotInAlbum,
|
||||||
|
personIds: filter.people.map((e) => e.id).toList(),
|
||||||
|
type: type,
|
||||||
|
page: page,
|
||||||
|
size: 1000,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return _db.assets
|
||||||
|
.getAllByRemoteId(response.assets.items.map((e) => e.id));
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint("Error [search] $error");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<CuratedLocationsResponseDto>?> getCuratedLocation() async {
|
Future<List<CuratedLocationsResponseDto>?> getCuratedLocation() async {
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.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/models/search_filter.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
|
import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
|
||||||
|
@ -57,7 +59,22 @@ class ExploreGrid extends StatelessWidget {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: context.pushRoute(
|
: context.pushRoute(
|
||||||
SearchResultRoute(searchTerm: 'm:${content.label}'),
|
SearchInputRoute(
|
||||||
|
prefilter: SearchFilter(
|
||||||
|
people: {},
|
||||||
|
location: SearchLocationFilter(
|
||||||
|
city: content.label,
|
||||||
|
),
|
||||||
|
camera: SearchCameraFilter(),
|
||||||
|
date: SearchDateFilter(),
|
||||||
|
display: SearchDisplayFilters(
|
||||||
|
isNotInAlbum: false,
|
||||||
|
isArchive: false,
|
||||||
|
isFavorite: false,
|
||||||
|
),
|
||||||
|
mediaType: AssetType.other,
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
|
||||||
|
|
||||||
class ImmichSearchBar extends HookConsumerWidget
|
|
||||||
implements PreferredSizeWidget {
|
|
||||||
const ImmichSearchBar({
|
|
||||||
super.key,
|
|
||||||
required this.searchFocusNode,
|
|
||||||
required this.onSubmitted,
|
|
||||||
});
|
|
||||||
|
|
||||||
final FocusNode searchFocusNode;
|
|
||||||
final Function(String) onSubmitted;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final searchTermController = useTextEditingController(text: "");
|
|
||||||
final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
|
|
||||||
|
|
||||||
focusSearch() {
|
|
||||||
searchTermController.clear();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).getSuggestedSearchTerms();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).enableSearch();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
|
|
||||||
|
|
||||||
searchFocusNode.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
searchFocusNotifier.addListener(focusSearch);
|
|
||||||
return () {
|
|
||||||
searchFocusNotifier.removeListener(focusSearch);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
return AppBar(
|
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
leading: isSearchEnabled
|
|
||||||
? IconButton(
|
|
||||||
onPressed: () {
|
|
||||||
searchFocusNode.unfocus();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).disableSearch();
|
|
||||||
searchTermController.clear();
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
|
||||||
)
|
|
||||||
: const Icon(
|
|
||||||
Icons.search_rounded,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
title: TextField(
|
|
||||||
controller: searchTermController,
|
|
||||||
focusNode: searchFocusNode,
|
|
||||||
autofocus: false,
|
|
||||||
onTap: focusSearch,
|
|
||||||
onSubmitted: (searchTerm) {
|
|
||||||
onSubmitted(searchTerm);
|
|
||||||
searchTermController.clear();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
|
|
||||||
},
|
|
||||||
onChanged: (value) {
|
|
||||||
ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'search_bar_hint'.tr(),
|
|
||||||
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
|
||||||
color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
|
|
||||||
),
|
|
||||||
enabledBorder: const UnderlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
focusedBorder: const UnderlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Size get preferredSize => const Size.fromHeight(kToolbarHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used to focus search from outside this widget.
|
|
||||||
// For example when double pressing the search nav icon.
|
|
||||||
final searchFocusNotifier = SearchFocusNotifier();
|
|
||||||
|
|
||||||
class SearchFocusNotifier with ChangeNotifier {
|
|
||||||
void requestFocus() {
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
120
mobile/lib/modules/search/ui/search_filter/camera_picker.dart
Normal file
120
mobile/lib/modules/search/ui/search_filter/camera_picker.dart
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/providers/search_filter.provider.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class CameraPicker extends HookConsumerWidget {
|
||||||
|
const CameraPicker({super.key, required this.onSelect, this.filter});
|
||||||
|
|
||||||
|
final Function(Map<String, String?>) onSelect;
|
||||||
|
final SearchCameraFilter? filter;
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final makeTextController = useTextEditingController(text: filter?.make);
|
||||||
|
final modelTextController = useTextEditingController(text: filter?.model);
|
||||||
|
final selectedMake = useState<String?>(filter?.make);
|
||||||
|
final selectedModel = useState<String?>(filter?.model);
|
||||||
|
|
||||||
|
final make = ref.watch(
|
||||||
|
getSearchSuggestionsProvider(
|
||||||
|
SearchSuggestionType.cameraMake,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final models = ref.watch(
|
||||||
|
getSearchSuggestionsProvider(
|
||||||
|
SearchSuggestionType.cameraModel,
|
||||||
|
make: selectedMake.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final inputDecorationTheme = InputDecorationTheme(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 16),
|
||||||
|
);
|
||||||
|
|
||||||
|
final menuStyle = MenuStyle(
|
||||||
|
shape: MaterialStatePropertyAll<OutlinedBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
// bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
DropdownMenu(
|
||||||
|
dropdownMenuEntries: switch (make) {
|
||||||
|
AsyncError() => [],
|
||||||
|
AsyncData(:final value) => value
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuEntry(
|
||||||
|
value: e,
|
||||||
|
label: e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
_ => [],
|
||||||
|
},
|
||||||
|
width: context.width * 0.45,
|
||||||
|
menuHeight: 400,
|
||||||
|
label: const Text('Make'),
|
||||||
|
inputDecorationTheme: inputDecorationTheme,
|
||||||
|
controller: makeTextController,
|
||||||
|
menuStyle: menuStyle,
|
||||||
|
leadingIcon: const Icon(Icons.photo_camera_rounded),
|
||||||
|
trailingIcon: const Icon(Icons.arrow_drop_down_rounded),
|
||||||
|
selectedTrailingIcon: const Icon(Icons.arrow_drop_up_rounded),
|
||||||
|
onSelected: (value) {
|
||||||
|
selectedMake.value = value.toString();
|
||||||
|
onSelect({
|
||||||
|
'make': selectedMake.value,
|
||||||
|
'model': selectedModel.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
DropdownMenu(
|
||||||
|
dropdownMenuEntries: switch (models) {
|
||||||
|
AsyncError() => [],
|
||||||
|
AsyncData(:final value) => value
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuEntry(
|
||||||
|
value: e,
|
||||||
|
label: e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
_ => [],
|
||||||
|
},
|
||||||
|
width: context.width * 0.45,
|
||||||
|
menuHeight: 400,
|
||||||
|
label: const Text('Model'),
|
||||||
|
inputDecorationTheme: inputDecorationTheme,
|
||||||
|
controller: modelTextController,
|
||||||
|
menuStyle: menuStyle,
|
||||||
|
leadingIcon: const Icon(Icons.camera),
|
||||||
|
trailingIcon: const Icon(Icons.arrow_drop_down_rounded),
|
||||||
|
selectedTrailingIcon: const Icon(Icons.arrow_drop_up_rounded),
|
||||||
|
onSelected: (value) {
|
||||||
|
selectedModel.value = value.toString();
|
||||||
|
onSelect({
|
||||||
|
'make': selectedMake.value,
|
||||||
|
'model': selectedModel.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
|
|
||||||
|
enum DisplayOption {
|
||||||
|
notInAlbum,
|
||||||
|
favorite,
|
||||||
|
archive,
|
||||||
|
}
|
||||||
|
|
||||||
|
class DisplayOptionPicker extends HookWidget {
|
||||||
|
const DisplayOptionPicker({
|
||||||
|
super.key,
|
||||||
|
required this.onSelect,
|
||||||
|
this.filter,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Function(Map<DisplayOption, bool>) onSelect;
|
||||||
|
final SearchDisplayFilters? filter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final options = useState<Map<DisplayOption, bool>>({
|
||||||
|
DisplayOption.notInAlbum: filter?.isNotInAlbum ?? false,
|
||||||
|
DisplayOption.favorite: filter?.isFavorite ?? false,
|
||||||
|
DisplayOption.archive: filter?.isArchive ?? false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('Not in album'),
|
||||||
|
value: options.value[DisplayOption.notInAlbum],
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
options.value = {
|
||||||
|
...options.value,
|
||||||
|
DisplayOption.notInAlbum: value!,
|
||||||
|
};
|
||||||
|
onSelect(options.value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('Favorite'),
|
||||||
|
value: options.value[DisplayOption.favorite],
|
||||||
|
onChanged: (value) {
|
||||||
|
options.value = {
|
||||||
|
...options.value,
|
||||||
|
DisplayOption.favorite: value!,
|
||||||
|
};
|
||||||
|
onSelect(options.value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
CheckboxListTile(
|
||||||
|
title: const Text('Archive'),
|
||||||
|
value: options.value[DisplayOption.archive],
|
||||||
|
onChanged: (value) {
|
||||||
|
options.value = {
|
||||||
|
...options.value,
|
||||||
|
DisplayOption.archive: value!,
|
||||||
|
};
|
||||||
|
onSelect(options.value);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
|
||||||
|
class FilterBottomSheetScaffold extends StatelessWidget {
|
||||||
|
const FilterBottomSheetScaffold({
|
||||||
|
super.key,
|
||||||
|
required this.child,
|
||||||
|
required this.onSearch,
|
||||||
|
required this.onClear,
|
||||||
|
required this.title,
|
||||||
|
this.expanded,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool? expanded;
|
||||||
|
final String title;
|
||||||
|
final Widget child;
|
||||||
|
final Function() onSearch;
|
||||||
|
final Function() onClear;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
buildChildWidget() {
|
||||||
|
if (expanded != null && expanded == true) {
|
||||||
|
return Expanded(child: child);
|
||||||
|
}
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
title,
|
||||||
|
style: context.textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildChildWidget(),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
OutlinedButton(
|
||||||
|
onPressed: () {
|
||||||
|
onClear();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Clear'),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
onSearch();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: const Text('Apply filter'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
166
mobile/lib/modules/search/ui/search_filter/location_picker.dart
Normal file
166
mobile/lib/modules/search/ui/search_filter/location_picker.dart
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/providers/search_filter.provider.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class LocationPicker extends HookConsumerWidget {
|
||||||
|
const LocationPicker({super.key, required this.onSelected, this.filter});
|
||||||
|
|
||||||
|
final Function(Map<String, String?>) onSelected;
|
||||||
|
final SearchLocationFilter? filter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final countryTextController =
|
||||||
|
useTextEditingController(text: filter?.country);
|
||||||
|
final stateTextController = useTextEditingController(text: filter?.state);
|
||||||
|
final cityTextController = useTextEditingController(text: filter?.city);
|
||||||
|
|
||||||
|
final selectedCountry = useState<String?>(filter?.country);
|
||||||
|
final selectedState = useState<String?>(filter?.state);
|
||||||
|
final selectedCity = useState<String?>(filter?.city);
|
||||||
|
|
||||||
|
final countries = ref.watch(
|
||||||
|
getSearchSuggestionsProvider(
|
||||||
|
SearchSuggestionType.country,
|
||||||
|
locationCountry: selectedCountry.value,
|
||||||
|
locationState: selectedState.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final states = ref.watch(
|
||||||
|
getSearchSuggestionsProvider(
|
||||||
|
SearchSuggestionType.state,
|
||||||
|
locationCountry: selectedCountry.value,
|
||||||
|
locationState: selectedState.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final cities = ref.watch(
|
||||||
|
getSearchSuggestionsProvider(
|
||||||
|
SearchSuggestionType.city,
|
||||||
|
locationCountry: selectedCountry.value,
|
||||||
|
locationState: selectedState.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final inputDecorationTheme = InputDecorationTheme(
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
contentPadding: const EdgeInsets.only(left: 16),
|
||||||
|
);
|
||||||
|
|
||||||
|
final menuStyle = MenuStyle(
|
||||||
|
shape: MaterialStatePropertyAll<OutlinedBorder>(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
DropdownMenu(
|
||||||
|
dropdownMenuEntries: switch (countries) {
|
||||||
|
AsyncError() => [],
|
||||||
|
AsyncData(:final value) => value
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuEntry(
|
||||||
|
value: e,
|
||||||
|
label: e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
_ => [],
|
||||||
|
},
|
||||||
|
menuHeight: 400,
|
||||||
|
width: context.width * 0.9,
|
||||||
|
label: const Text('Country'),
|
||||||
|
inputDecorationTheme: inputDecorationTheme,
|
||||||
|
menuStyle: menuStyle,
|
||||||
|
controller: countryTextController,
|
||||||
|
trailingIcon: const Icon(Icons.arrow_drop_down_rounded),
|
||||||
|
selectedTrailingIcon: const Icon(Icons.arrow_drop_up_rounded),
|
||||||
|
onSelected: (value) {
|
||||||
|
selectedCountry.value = value.toString();
|
||||||
|
onSelected({
|
||||||
|
'country': selectedCountry.value,
|
||||||
|
'state': selectedState.value,
|
||||||
|
'city': selectedCity.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
DropdownMenu(
|
||||||
|
dropdownMenuEntries: switch (states) {
|
||||||
|
AsyncError() => [],
|
||||||
|
AsyncData(:final value) => value
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuEntry(
|
||||||
|
value: e,
|
||||||
|
label: e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
_ => [],
|
||||||
|
},
|
||||||
|
menuHeight: 400,
|
||||||
|
width: context.width * 0.9,
|
||||||
|
label: const Text('State'),
|
||||||
|
inputDecorationTheme: inputDecorationTheme,
|
||||||
|
menuStyle: menuStyle,
|
||||||
|
controller: stateTextController,
|
||||||
|
trailingIcon: const Icon(Icons.arrow_drop_down_rounded),
|
||||||
|
selectedTrailingIcon: const Icon(Icons.arrow_drop_up_rounded),
|
||||||
|
onSelected: (value) {
|
||||||
|
selectedState.value = value.toString();
|
||||||
|
onSelected({
|
||||||
|
'country': selectedCountry.value,
|
||||||
|
'state': selectedState.value,
|
||||||
|
'city': selectedCity.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
DropdownMenu(
|
||||||
|
dropdownMenuEntries: switch (cities) {
|
||||||
|
AsyncError() => [],
|
||||||
|
AsyncData(:final value) => value
|
||||||
|
.map(
|
||||||
|
(e) => DropdownMenuEntry(
|
||||||
|
value: e,
|
||||||
|
label: e,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
_ => [],
|
||||||
|
},
|
||||||
|
menuHeight: 400,
|
||||||
|
width: context.width * 0.9,
|
||||||
|
label: const Text('City'),
|
||||||
|
inputDecorationTheme: inputDecorationTheme,
|
||||||
|
menuStyle: menuStyle,
|
||||||
|
controller: cityTextController,
|
||||||
|
trailingIcon: const Icon(Icons.arrow_drop_down_rounded),
|
||||||
|
selectedTrailingIcon: const Icon(Icons.arrow_drop_up_rounded),
|
||||||
|
onSelected: (value) {
|
||||||
|
selectedCity.value = value.toString();
|
||||||
|
onSelected({
|
||||||
|
'country': selectedCountry.value,
|
||||||
|
'state': selectedState.value,
|
||||||
|
'city': selectedCity.value,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
|
||||||
|
class MediaTypePicker extends HookWidget {
|
||||||
|
const MediaTypePicker({super.key, required this.onSelect, this.filter});
|
||||||
|
|
||||||
|
final Function(AssetType) onSelect;
|
||||||
|
final AssetType? filter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final selectedMediaType = useState(filter ?? AssetType.other);
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: [
|
||||||
|
RadioListTile(
|
||||||
|
title: const Text("All"),
|
||||||
|
value: AssetType.other,
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedMediaType.value = value!;
|
||||||
|
onSelect(value);
|
||||||
|
},
|
||||||
|
groupValue: selectedMediaType.value,
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: const Text("Image"),
|
||||||
|
value: AssetType.image,
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedMediaType.value = value!;
|
||||||
|
onSelect(value);
|
||||||
|
},
|
||||||
|
groupValue: selectedMediaType.value,
|
||||||
|
),
|
||||||
|
RadioListTile(
|
||||||
|
title: const Text("Video"),
|
||||||
|
value: AssetType.video,
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedMediaType.value = value!;
|
||||||
|
onSelect(value);
|
||||||
|
},
|
||||||
|
groupValue: selectedMediaType.value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/store.dart' as local_store;
|
||||||
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
class PeoplePicker extends HookConsumerWidget {
|
||||||
|
const PeoplePicker({super.key, required this.onSelect, this.filter});
|
||||||
|
|
||||||
|
final Function(Set<PersonResponseDto>) onSelect;
|
||||||
|
final Set<PersonResponseDto>? filter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
var imageSize = 45.0;
|
||||||
|
final people = ref.watch(getAllPeopleProvider);
|
||||||
|
final headers = {
|
||||||
|
"x-immich-user-token":
|
||||||
|
local_store.Store.get(local_store.StoreKey.accessToken),
|
||||||
|
};
|
||||||
|
final selectedPeople = useState<Set<PersonResponseDto>>(filter ?? {});
|
||||||
|
|
||||||
|
return people.widgetWhen(
|
||||||
|
onData: (people) {
|
||||||
|
return ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: people.length,
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final person = people[index];
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
title: Text(
|
||||||
|
person.name,
|
||||||
|
style: context.textTheme.bodyLarge,
|
||||||
|
),
|
||||||
|
leading: SizedBox(
|
||||||
|
height: imageSize,
|
||||||
|
child: Material(
|
||||||
|
shape: const CircleBorder(side: BorderSide.none),
|
||||||
|
elevation: 3,
|
||||||
|
child: CircleAvatar(
|
||||||
|
maxRadius: imageSize / 2,
|
||||||
|
backgroundImage: NetworkImage(
|
||||||
|
getFaceThumbnailUrl(person.id),
|
||||||
|
headers: headers,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
if (selectedPeople.value.contains(person)) {
|
||||||
|
selectedPeople.value.remove(person);
|
||||||
|
} else {
|
||||||
|
selectedPeople.value.add(person);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedPeople.value = {...selectedPeople.value};
|
||||||
|
onSelect(selectedPeople.value);
|
||||||
|
},
|
||||||
|
selected: selectedPeople.value.contains(person),
|
||||||
|
selectedTileColor: context.primaryColor.withOpacity(0.2),
|
||||||
|
shape: const RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
|
||||||
|
class SearchFilterChip extends StatelessWidget {
|
||||||
|
final String label;
|
||||||
|
final Function() onTap;
|
||||||
|
final Widget? currentFilter;
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
|
const SearchFilterChip({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
required this.onTap,
|
||||||
|
required this.icon,
|
||||||
|
this.currentFilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (currentFilter != null) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: context.primaryColor.withAlpha(25),
|
||||||
|
shape: StadiumBorder(
|
||||||
|
side: BorderSide(color: context.primaryColor),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4.0),
|
||||||
|
currentFilter!,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape:
|
||||||
|
StadiumBorder(side: BorderSide(color: Colors.grey.withAlpha(100))),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 14.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 4.0),
|
||||||
|
Text(label),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
Future<T> showFilterBottomSheet<T>({
|
||||||
|
required BuildContext context,
|
||||||
|
required Widget child,
|
||||||
|
bool isScrollControlled = false,
|
||||||
|
bool isDismissible = true,
|
||||||
|
}) async {
|
||||||
|
return await showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: isScrollControlled,
|
||||||
|
useSafeArea: false,
|
||||||
|
isDismissible: isDismissible,
|
||||||
|
showDragHandle: isDismissible,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return child;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
|
||||||
|
|
||||||
class SearchSuggestionList extends ConsumerWidget {
|
|
||||||
const SearchSuggestionList({super.key, required this.onSubmitted});
|
|
||||||
|
|
||||||
final Function(String) onSubmitted;
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final searchTerm = ref.watch(searchPageStateProvider).searchTerm;
|
|
||||||
final searchSuggestion =
|
|
||||||
ref.watch(searchPageStateProvider).searchSuggestion;
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
color: searchTerm.isEmpty
|
|
||||||
? Colors.black.withOpacity(0.5)
|
|
||||||
: context.scaffoldBackgroundColor,
|
|
||||||
child: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverToBoxAdapter(
|
|
||||||
child: Container(
|
|
||||||
color: context.isDarkTheme ? Colors.grey[800] : Colors.grey[100],
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: 'search_suggestion_list_smart_search_hint_1'.tr(),
|
|
||||||
style: context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
TextSpan(
|
|
||||||
text: 'search_suggestion_list_smart_search_hint_2'.tr(),
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: context.primaryColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SliverFillRemaining(
|
|
||||||
hasScrollBody: true,
|
|
||||||
child: ListView.builder(
|
|
||||||
itemBuilder: ((context, index) {
|
|
||||||
return ListTile(
|
|
||||||
onTap: () {
|
|
||||||
onSubmitted("m:${searchSuggestion[index]}");
|
|
||||||
},
|
|
||||||
title: Text(searchSuggestion[index]),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
itemCount: searchSuggestion.length,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/curated_content.dart';
|
||||||
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
|
import 'package:immich_mobile/modules/search/ui/explore_grid.dart';
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ class AllPeoplePage extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final curatedPeople = ref.watch(getCuratedPeopleProvider);
|
final curatedPeople = ref.watch(getAllPeopleProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
|
@ -27,7 +28,9 @@ class AllPeoplePage extends HookConsumerWidget {
|
||||||
body: curatedPeople.widgetWhen(
|
body: curatedPeople.widgetWhen(
|
||||||
onData: (people) => ExploreGrid(
|
onData: (people) => ExploreGrid(
|
||||||
isPeople: true,
|
isPeople: true,
|
||||||
curatedContent: people,
|
curatedContent: people
|
||||||
|
.map((e) => CuratedContent(label: e.name, id: e.id))
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
563
mobile/lib/modules/search/views/search_input_page.dart
Normal file
563
mobile/lib/modules/search/views/search_input_page.dart
Normal file
|
@ -0,0 +1,563 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/providers/paginated_search.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/camera_picker.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/display_option_picker.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/filter_bottom_sheet_scaffold.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/location_picker.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/media_type_picker.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/people_picker.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/search_filter_chip.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/ui/search_filter/search_filter_utils.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
@RoutePage()
|
||||||
|
class SearchInputPage extends HookConsumerWidget {
|
||||||
|
const SearchInputPage({super.key, this.prefilter});
|
||||||
|
|
||||||
|
final SearchFilter? prefilter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final isContextualSearch = useState(true);
|
||||||
|
final textSearchController = useTextEditingController();
|
||||||
|
final filter = useState<SearchFilter>(
|
||||||
|
SearchFilter(
|
||||||
|
people: prefilter?.people ?? {},
|
||||||
|
location: prefilter?.location ?? SearchLocationFilter(),
|
||||||
|
camera: prefilter?.camera ?? SearchCameraFilter(),
|
||||||
|
date: prefilter?.date ?? SearchDateFilter(),
|
||||||
|
display: prefilter?.display ??
|
||||||
|
SearchDisplayFilters(
|
||||||
|
isNotInAlbum: false,
|
||||||
|
isArchive: false,
|
||||||
|
isFavorite: false,
|
||||||
|
),
|
||||||
|
mediaType: prefilter?.mediaType ?? AssetType.other,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final previousFilter = useState(filter.value);
|
||||||
|
|
||||||
|
final peopleCurrentFilterWidget = useState<Widget?>(null);
|
||||||
|
final dateRangeCurrentFilterWidget = useState<Widget?>(null);
|
||||||
|
final cameraCurrentFilterWidget = useState<Widget?>(null);
|
||||||
|
final locationCurrentFilterWidget = useState<Widget?>(null);
|
||||||
|
final mediaTypeCurrentFilterWidget = useState<Widget?>(null);
|
||||||
|
final displayOptionCurrentFilterWidget = useState<Widget?>(null);
|
||||||
|
|
||||||
|
final currentPage = useState(1);
|
||||||
|
final searchProvider = ref.watch(paginatedSearchProvider);
|
||||||
|
final searchResultCount = useState(0);
|
||||||
|
|
||||||
|
search() async {
|
||||||
|
if (prefilter == null && filter.value == previousFilter.value) return;
|
||||||
|
|
||||||
|
ref.watch(paginatedSearchProvider.notifier).clear();
|
||||||
|
|
||||||
|
currentPage.value = 1;
|
||||||
|
|
||||||
|
final searchResult = await ref
|
||||||
|
.watch(paginatedSearchProvider.notifier)
|
||||||
|
.getNextPage(filter.value, currentPage.value);
|
||||||
|
previousFilter.value = filter.value;
|
||||||
|
|
||||||
|
searchResultCount.value = searchResult.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
searchPrefilter() {
|
||||||
|
if (prefilter != null) {
|
||||||
|
Future.delayed(
|
||||||
|
Duration.zero,
|
||||||
|
() {
|
||||||
|
search();
|
||||||
|
|
||||||
|
if (prefilter!.location.city != null) {
|
||||||
|
locationCurrentFilterWidget.value = Text(
|
||||||
|
prefilter!.location.city!,
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() {
|
||||||
|
searchPrefilter();
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
loadMoreSearchResult() async {
|
||||||
|
currentPage.value += 1;
|
||||||
|
final searchResult = await ref
|
||||||
|
.watch(paginatedSearchProvider.notifier)
|
||||||
|
.getNextPage(filter.value, currentPage.value);
|
||||||
|
searchResultCount.value = searchResult.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
showPeoplePicker() {
|
||||||
|
handleOnSelect(Set<PersonResponseDto> value) {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
people: value,
|
||||||
|
);
|
||||||
|
|
||||||
|
peopleCurrentFilterWidget.value = Text(
|
||||||
|
value.map((e) => e.name != '' ? e.name : "No name").join(', '),
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClear() {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
people: {},
|
||||||
|
);
|
||||||
|
|
||||||
|
peopleCurrentFilterWidget.value = null;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
showFilterBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
heightFactor: 0.8,
|
||||||
|
child: FilterBottomSheetScaffold(
|
||||||
|
title: 'Select people',
|
||||||
|
expanded: true,
|
||||||
|
onSearch: search,
|
||||||
|
onClear: handleClear,
|
||||||
|
child: PeoplePicker(
|
||||||
|
onSelect: handleOnSelect,
|
||||||
|
filter: filter.value.people,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showLocationPicker() {
|
||||||
|
handleOnSelect(Map<String, String?> value) {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
location: SearchLocationFilter(
|
||||||
|
country: value['country'],
|
||||||
|
city: value['city'],
|
||||||
|
state: value['state'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final locationText = <String>[];
|
||||||
|
if (value['country'] != null) {
|
||||||
|
locationText.add(value['country']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value['state'] != null) {
|
||||||
|
locationText.add(value['state']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value['city'] != null) {
|
||||||
|
locationText.add(value['city']!);
|
||||||
|
}
|
||||||
|
|
||||||
|
locationCurrentFilterWidget.value = Text(
|
||||||
|
locationText.join(', '),
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClear() {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
location: SearchLocationFilter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
locationCurrentFilterWidget.value = null;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
showFilterBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: false,
|
||||||
|
child: FilterBottomSheetScaffold(
|
||||||
|
title: 'Select location',
|
||||||
|
onSearch: search,
|
||||||
|
onClear: handleClear,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
child: LocationPicker(
|
||||||
|
onSelected: handleOnSelect,
|
||||||
|
filter: filter.value.location,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showCameraPicker() {
|
||||||
|
handleOnSelect(Map<String, String?> value) {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
camera: SearchCameraFilter(
|
||||||
|
make: value['make'],
|
||||||
|
model: value['model'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
cameraCurrentFilterWidget.value = Text(
|
||||||
|
'${value['make'] ?? ''} ${value['model'] ?? ''}',
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClear() {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
camera: SearchCameraFilter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
cameraCurrentFilterWidget.value = null;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
showFilterBottomSheet(
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
isDismissible: false,
|
||||||
|
child: FilterBottomSheetScaffold(
|
||||||
|
title: 'Select camera type',
|
||||||
|
onSearch: search,
|
||||||
|
onClear: handleClear,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: CameraPicker(
|
||||||
|
onSelect: handleOnSelect,
|
||||||
|
filter: filter.value.camera,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
showDatePicker() async {
|
||||||
|
final firstDate = DateTime(1900);
|
||||||
|
final lastDate = DateTime.now();
|
||||||
|
|
||||||
|
final date = await showDateRangePicker(
|
||||||
|
context: context,
|
||||||
|
firstDate: firstDate,
|
||||||
|
lastDate: lastDate,
|
||||||
|
currentDate: DateTime.now(),
|
||||||
|
initialDateRange: DateTimeRange(
|
||||||
|
start: filter.value.date.takenAfter ?? lastDate,
|
||||||
|
end: filter.value.date.takenBefore ?? lastDate,
|
||||||
|
),
|
||||||
|
helpText: 'Select a date range',
|
||||||
|
cancelText: 'Cancel',
|
||||||
|
confirmText: 'Select',
|
||||||
|
saveText: 'Save',
|
||||||
|
errorFormatText: 'Invalid date format',
|
||||||
|
errorInvalidText: 'Invalid date',
|
||||||
|
fieldStartHintText: 'Start date',
|
||||||
|
fieldEndHintText: 'End date',
|
||||||
|
initialEntryMode: DatePickerEntryMode.input,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (date == null) {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
date: SearchDateFilter(),
|
||||||
|
);
|
||||||
|
|
||||||
|
dateRangeCurrentFilterWidget.value = null;
|
||||||
|
search();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
date: SearchDateFilter(
|
||||||
|
takenAfter: date.start,
|
||||||
|
takenBefore: date.end.add(
|
||||||
|
const Duration(
|
||||||
|
hours: 23,
|
||||||
|
minutes: 59,
|
||||||
|
seconds: 59,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// If date range is less than 24 hours, set the end date to the end of the day
|
||||||
|
if (date.end.difference(date.start).inHours < 24) {
|
||||||
|
dateRangeCurrentFilterWidget.value = Text(
|
||||||
|
date.start.toLocal().toIso8601String().split('T').first,
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
dateRangeCurrentFilterWidget.value = Text(
|
||||||
|
'${date.start.toLocal().toIso8601String().split('T').first} to ${date.end.toLocal().toIso8601String().split('T').first}',
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
// MEDIA PICKER
|
||||||
|
showMediaTypePicker() {
|
||||||
|
handleOnSelected(AssetType assetType) {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
mediaType: assetType,
|
||||||
|
);
|
||||||
|
|
||||||
|
mediaTypeCurrentFilterWidget.value = Text(
|
||||||
|
assetType == AssetType.image ? 'Image' : 'Video',
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClear() {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
mediaType: AssetType.other,
|
||||||
|
);
|
||||||
|
|
||||||
|
mediaTypeCurrentFilterWidget.value = null;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
showFilterBottomSheet(
|
||||||
|
context: context,
|
||||||
|
child: FilterBottomSheetScaffold(
|
||||||
|
title: 'Select media type',
|
||||||
|
onSearch: search,
|
||||||
|
onClear: handleClear,
|
||||||
|
child: MediaTypePicker(
|
||||||
|
onSelect: handleOnSelected,
|
||||||
|
filter: filter.value.mediaType,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DISPLAY OPTION
|
||||||
|
showDisplayOptionPicker() {
|
||||||
|
handleOnSelect(Map<DisplayOption, bool> value) {
|
||||||
|
final filterText = <String>[];
|
||||||
|
|
||||||
|
value.forEach((key, value) {
|
||||||
|
switch (key) {
|
||||||
|
case DisplayOption.notInAlbum:
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
display: filter.value.display.copyWith(
|
||||||
|
isNotInAlbum: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (value) filterText.add('Not in album');
|
||||||
|
break;
|
||||||
|
case DisplayOption.archive:
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
display: filter.value.display.copyWith(
|
||||||
|
isArchive: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (value) filterText.add('Archive');
|
||||||
|
break;
|
||||||
|
case DisplayOption.favorite:
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
display: filter.value.display.copyWith(
|
||||||
|
isFavorite: value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (value) filterText.add('Favorite');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
displayOptionCurrentFilterWidget.value = Text(
|
||||||
|
filterText.join(', '),
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClear() {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
display: SearchDisplayFilters(
|
||||||
|
isNotInAlbum: false,
|
||||||
|
isArchive: false,
|
||||||
|
isFavorite: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
displayOptionCurrentFilterWidget.value = null;
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
showFilterBottomSheet(
|
||||||
|
context: context,
|
||||||
|
child: FilterBottomSheetScaffold(
|
||||||
|
title: 'Display options',
|
||||||
|
onSearch: search,
|
||||||
|
onClear: handleClear,
|
||||||
|
child: DisplayOptionPicker(
|
||||||
|
onSelect: handleOnSelect,
|
||||||
|
filter: filter.value.display,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleTextSubmitted(String value) {
|
||||||
|
if (isContextualSearch.value) {
|
||||||
|
filter.value = filter.value.copyWith(
|
||||||
|
context: value,
|
||||||
|
filename: null,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
filter.value = filter.value.copyWith(filename: value, context: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
search();
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSearchResult() {
|
||||||
|
return switch (searchProvider) {
|
||||||
|
AsyncData() => Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
child: NotificationListener<ScrollEndNotification>(
|
||||||
|
onNotification: (notification) {
|
||||||
|
final metrics = notification.metrics;
|
||||||
|
final shouldLoadMore = searchResultCount.value > 75;
|
||||||
|
if (metrics.pixels >= metrics.maxScrollExtent &&
|
||||||
|
shouldLoadMore) {
|
||||||
|
loadMoreSearchResult();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: MultiselectGrid(
|
||||||
|
renderListProvider: paginatedSearchRenderListProvider,
|
||||||
|
archiveEnabled: true,
|
||||||
|
deleteEnabled: true,
|
||||||
|
editEnabled: true,
|
||||||
|
favoriteEnabled: true,
|
||||||
|
stackEnabled: false,
|
||||||
|
emptyIndicator: const SizedBox(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AsyncError(:final error) => Text('Error: $error'),
|
||||||
|
_ => const Expanded(child: Center(child: CircularProgressIndicator())),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
resizeToAvoidBottomInset: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
automaticallyImplyLeading: true,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
icon: isContextualSearch.value
|
||||||
|
? const Icon(Icons.abc_rounded)
|
||||||
|
: const Icon(Icons.image_search_rounded),
|
||||||
|
onPressed: () {
|
||||||
|
isContextualSearch.value = !isContextualSearch.value;
|
||||||
|
textSearchController.clear();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
|
onPressed: () {
|
||||||
|
context.router.pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: TextField(
|
||||||
|
controller: textSearchController,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: isContextualSearch.value
|
||||||
|
? 'Sunrise on the beach'
|
||||||
|
: 'File name or extension',
|
||||||
|
hintStyle: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
color: context.themeData.colorScheme.onSurface.withOpacity(0.75),
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
enabledBorder: const UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
focusedBorder: const UnderlineInputBorder(
|
||||||
|
borderSide: BorderSide(color: Colors.transparent),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onSubmitted: handleTextSubmitted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(top: 12.0),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 50,
|
||||||
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
children: [
|
||||||
|
SearchFilterChip(
|
||||||
|
icon: Icons.people_alt_rounded,
|
||||||
|
onTap: showPeoplePicker,
|
||||||
|
label: 'People',
|
||||||
|
currentFilter: peopleCurrentFilterWidget.value,
|
||||||
|
),
|
||||||
|
SearchFilterChip(
|
||||||
|
icon: Icons.location_pin,
|
||||||
|
onTap: showLocationPicker,
|
||||||
|
label: 'Location',
|
||||||
|
currentFilter: locationCurrentFilterWidget.value,
|
||||||
|
),
|
||||||
|
SearchFilterChip(
|
||||||
|
icon: Icons.camera_alt_rounded,
|
||||||
|
onTap: showCameraPicker,
|
||||||
|
label: 'Camera',
|
||||||
|
currentFilter: cameraCurrentFilterWidget.value,
|
||||||
|
),
|
||||||
|
SearchFilterChip(
|
||||||
|
icon: Icons.date_range_rounded,
|
||||||
|
onTap: showDatePicker,
|
||||||
|
label: 'Date',
|
||||||
|
currentFilter: dateRangeCurrentFilterWidget.value,
|
||||||
|
),
|
||||||
|
SearchFilterChip(
|
||||||
|
icon: Icons.video_collection_outlined,
|
||||||
|
onTap: showMediaTypePicker,
|
||||||
|
label: 'Media Type',
|
||||||
|
currentFilter: mediaTypeCurrentFilterWidget.value,
|
||||||
|
),
|
||||||
|
SearchFilterChip(
|
||||||
|
icon: Icons.display_settings_outlined,
|
||||||
|
onTap: showDisplayOptionPicker,
|
||||||
|
label: 'Display Options',
|
||||||
|
currentFilter: displayOptionCurrentFilterWidget.value,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
buildSearchResult(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,36 +1,34 @@
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_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/models/search_filter.dart';
|
||||||
import 'package:immich_mobile/modules/search/providers/people.provider.dart';
|
import 'package:immich_mobile/modules/search/providers/people.provider.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/curated_people_row.dart';
|
import 'package:immich_mobile/modules/search/ui/curated_people_row.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/curated_places_row.dart';
|
import 'package:immich_mobile/modules/search/ui/curated_places_row.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/immich_search_bar.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart';
|
import 'package:immich_mobile/modules/search/ui/person_name_edit_form.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/search_row_title.dart';
|
import 'package:immich_mobile/modules/search/ui/search_row_title.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/ui/immich_app_bar.dart';
|
||||||
import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
|
import 'package:immich_mobile/shared/ui/scaffold_error_body.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class SearchPage extends HookConsumerWidget {
|
class SearchPage extends HookConsumerWidget {
|
||||||
SearchPage({super.key});
|
const SearchPage({super.key});
|
||||||
|
|
||||||
FocusNode searchFocusNode = FocusNode();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
|
|
||||||
final curatedLocation = ref.watch(getCuratedLocationProvider);
|
final curatedLocation = ref.watch(getCuratedLocationProvider);
|
||||||
final curatedPeople = ref.watch(getCuratedPeopleProvider);
|
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));
|
||||||
double imageSize = math.min(context.width / 3, 150);
|
double imageSize = math.min(context.width / 3, 150);
|
||||||
|
@ -42,25 +40,6 @@ class SearchPage extends HookConsumerWidget {
|
||||||
|
|
||||||
Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black;
|
Color categoryIconColor = context.isDarkTheme ? Colors.white : Colors.black;
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
searchFocusNode = FocusNode();
|
|
||||||
return () => searchFocusNode.dispose();
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
onSearchSubmitted(String searchTerm) async {
|
|
||||||
searchFocusNode.unfocus();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).disableSearch();
|
|
||||||
|
|
||||||
context.pushRoute(
|
|
||||||
SearchResultRoute(
|
|
||||||
searchTerm: searchTerm,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showNameEditModel(
|
showNameEditModel(
|
||||||
String personId,
|
String personId,
|
||||||
String personName,
|
String personName,
|
||||||
|
@ -84,7 +63,10 @@ class SearchPage extends HookConsumerWidget {
|
||||||
top: 8,
|
top: 8,
|
||||||
),
|
),
|
||||||
child: CuratedPeopleRow(
|
child: CuratedPeopleRow(
|
||||||
content: people.take(12).toList(),
|
content: people
|
||||||
|
.map((e) => CuratedContent(label: e.name, id: e.id))
|
||||||
|
.take(12)
|
||||||
|
.toList(),
|
||||||
onTap: (content, index) {
|
onTap: (content, index) {
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
PersonResultRoute(
|
PersonResultRoute(
|
||||||
|
@ -120,8 +102,21 @@ class SearchPage extends HookConsumerWidget {
|
||||||
imageSize: imageSize,
|
imageSize: imageSize,
|
||||||
onTap: (content, index) {
|
onTap: (content, index) {
|
||||||
context.pushRoute(
|
context.pushRoute(
|
||||||
SearchResultRoute(
|
SearchInputRoute(
|
||||||
searchTerm: 'm:${content.label}',
|
prefilter: SearchFilter(
|
||||||
|
people: {},
|
||||||
|
location: SearchLocationFilter(
|
||||||
|
city: content.label,
|
||||||
|
),
|
||||||
|
camera: SearchCameraFilter(),
|
||||||
|
date: SearchDateFilter(),
|
||||||
|
display: SearchDisplayFilters(
|
||||||
|
isNotInAlbum: false,
|
||||||
|
isArchive: false,
|
||||||
|
isFavorite: false,
|
||||||
|
),
|
||||||
|
mediaType: AssetType.other,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -130,20 +125,53 @@ class SearchPage extends HookConsumerWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
buildSearchButton() {
|
||||||
appBar: ImmichSearchBar(
|
return GestureDetector(
|
||||||
searchFocusNode: searchFocusNode,
|
|
||||||
onSubmitted: onSearchSubmitted,
|
|
||||||
),
|
|
||||||
body: GestureDetector(
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
searchFocusNode.unfocus();
|
context.pushRoute(SearchInputRoute());
|
||||||
ref.watch(searchPageStateProvider.notifier).disableSearch();
|
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Card(
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
side: BorderSide(
|
||||||
|
color: context.isDarkTheme
|
||||||
|
? Colors.grey[800]!
|
||||||
|
: const Color.fromARGB(255, 225, 225, 225),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 12.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.search, color: context.primaryColor),
|
||||||
|
const SizedBox(width: 16.0),
|
||||||
|
Text(
|
||||||
|
"Search your photos",
|
||||||
|
style: context.textTheme.bodyLarge?.copyWith(
|
||||||
|
color:
|
||||||
|
context.isDarkTheme ? Colors.white70 : Colors.black54,
|
||||||
|
fontWeight: FontWeight.w400,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: const ImmichAppBar(),
|
||||||
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView(
|
ListView(
|
||||||
children: [
|
children: [
|
||||||
|
buildSearchButton(),
|
||||||
SearchRowTitle(
|
SearchRowTitle(
|
||||||
title: "search_page_people".tr(),
|
title: "search_page_people".tr(),
|
||||||
onViewAllPressed: () =>
|
onViewAllPressed: () =>
|
||||||
|
@ -173,8 +201,7 @@ class SearchPage extends HookConsumerWidget {
|
||||||
Icons.favorite_border_rounded,
|
Icons.favorite_border_rounded,
|
||||||
color: categoryIconColor,
|
color: categoryIconColor,
|
||||||
),
|
),
|
||||||
title:
|
title: Text('search_page_favorites', style: categoryTitleStyle)
|
||||||
Text('search_page_favorites', style: categoryTitleStyle)
|
|
||||||
.tr(),
|
.tr(),
|
||||||
onTap: () => context.pushRoute(const FavoritesRoute()),
|
onTap: () => context.pushRoute(const FavoritesRoute()),
|
||||||
),
|
),
|
||||||
|
@ -202,36 +229,7 @@ class SearchPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title:
|
title:
|
||||||
Text('search_page_screenshots', style: categoryTitleStyle)
|
Text('search_page_videos', style: categoryTitleStyle).tr(),
|
||||||
.tr(),
|
|
||||||
leading: Icon(
|
|
||||||
Icons.screenshot,
|
|
||||||
color: categoryIconColor,
|
|
||||||
),
|
|
||||||
onTap: () => context.pushRoute(
|
|
||||||
SearchResultRoute(
|
|
||||||
searchTerm: 'screenshots',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const CategoryDivider(),
|
|
||||||
ListTile(
|
|
||||||
title: Text('search_page_selfies', style: categoryTitleStyle)
|
|
||||||
.tr(),
|
|
||||||
leading: Icon(
|
|
||||||
Icons.photo_camera_front_outlined,
|
|
||||||
color: categoryIconColor,
|
|
||||||
),
|
|
||||||
onTap: () => context.pushRoute(
|
|
||||||
SearchResultRoute(
|
|
||||||
searchTerm: 'selfies',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const CategoryDivider(),
|
|
||||||
ListTile(
|
|
||||||
title: Text('search_page_videos', style: categoryTitleStyle)
|
|
||||||
.tr(),
|
|
||||||
leading: Icon(
|
leading: Icon(
|
||||||
Icons.play_circle_outline,
|
Icons.play_circle_outline,
|
||||||
color: categoryIconColor,
|
color: categoryIconColor,
|
||||||
|
@ -252,11 +250,8 @@ class SearchPage extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (isSearchEnabled)
|
|
||||||
SearchSuggestionList(onSubmitted: onSearchSubmitted),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,213 +0,0 @@
|
||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/providers/search_result_page.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/asset_grid/multiselect_grid.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
|
||||||
|
|
||||||
class SearchType {
|
|
||||||
SearchType({required this.isSmart, required this.searchTerm});
|
|
||||||
|
|
||||||
final bool isSmart;
|
|
||||||
final String searchTerm;
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchType _getSearchType(String searchTerm) {
|
|
||||||
if (searchTerm.startsWith('m:')) {
|
|
||||||
return SearchType(isSmart: false, searchTerm: searchTerm.substring(2));
|
|
||||||
} else {
|
|
||||||
return SearchType(isSmart: true, searchTerm: searchTerm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RoutePage()
|
|
||||||
class SearchResultPage extends HookConsumerWidget {
|
|
||||||
const SearchResultPage({
|
|
||||||
super.key,
|
|
||||||
required this.searchTerm,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String searchTerm;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final searchTermController = useTextEditingController(text: "");
|
|
||||||
final isNewSearch = useState(false);
|
|
||||||
final currentSearchTerm = useState(searchTerm);
|
|
||||||
|
|
||||||
FocusNode? searchFocusNode;
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
searchFocusNode = FocusNode();
|
|
||||||
|
|
||||||
var searchType = _getSearchType(searchTerm);
|
|
||||||
|
|
||||||
Future.delayed(
|
|
||||||
Duration.zero,
|
|
||||||
() => ref
|
|
||||||
.read(searchResultPageProvider.notifier)
|
|
||||||
.search(searchType.searchTerm, smartSearch: searchType.isSmart),
|
|
||||||
);
|
|
||||||
return () => searchFocusNode?.dispose();
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<void> onSearchSubmitted(String newSearchTerm) {
|
|
||||||
debugPrint("Re-Search with $newSearchTerm");
|
|
||||||
searchFocusNode?.unfocus();
|
|
||||||
isNewSearch.value = false;
|
|
||||||
currentSearchTerm.value = newSearchTerm;
|
|
||||||
var searchType = _getSearchType(newSearchTerm);
|
|
||||||
return ref
|
|
||||||
.watch(searchResultPageProvider.notifier)
|
|
||||||
.search(searchType.searchTerm, smartSearch: searchType.isSmart);
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTextField() {
|
|
||||||
return TextField(
|
|
||||||
controller: searchTermController,
|
|
||||||
focusNode: searchFocusNode,
|
|
||||||
autofocus: false,
|
|
||||||
onTap: () {
|
|
||||||
searchTermController.clear();
|
|
||||||
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
|
|
||||||
searchFocusNode?.requestFocus();
|
|
||||||
},
|
|
||||||
textInputAction: TextInputAction.search,
|
|
||||||
onSubmitted: (searchTerm) {
|
|
||||||
if (searchTerm.isNotEmpty) {
|
|
||||||
searchTermController.clear();
|
|
||||||
onSearchSubmitted(searchTerm);
|
|
||||||
} else {
|
|
||||||
isNewSearch.value = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onChanged: (value) {
|
|
||||||
ref.watch(searchPageStateProvider.notifier).setSearchTerm(value);
|
|
||||||
},
|
|
||||||
decoration: InputDecoration(
|
|
||||||
hintText: 'search_result_page_new_search_hint'.tr(),
|
|
||||||
enabledBorder: const UnderlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
focusedBorder: const UnderlineInputBorder(
|
|
||||||
borderSide: BorderSide(color: Colors.transparent),
|
|
||||||
),
|
|
||||||
hintStyle: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16.0,
|
|
||||||
color: context.isDarkTheme
|
|
||||||
? Colors.grey[500]
|
|
||||||
: Colors.black.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
buildChip() {
|
|
||||||
return Chip(
|
|
||||||
label: Wrap(
|
|
||||||
spacing: 5,
|
|
||||||
runAlignment: WrapAlignment.center,
|
|
||||||
crossAxisAlignment: WrapCrossAlignment.center,
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
currentSearchTerm.value,
|
|
||||||
style: TextStyle(
|
|
||||||
color: context.primaryColor,
|
|
||||||
fontSize: 13,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
),
|
|
||||||
Icon(
|
|
||||||
Icons.close_rounded,
|
|
||||||
color: context.primaryColor,
|
|
||||||
size: 20,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
backgroundColor: context.primaryColor.withAlpha(50),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refresh() async => onSearchSubmitted(currentSearchTerm.value);
|
|
||||||
|
|
||||||
buildSearchResult() {
|
|
||||||
final searchResultPageState = ref.watch(searchResultPageProvider);
|
|
||||||
|
|
||||||
if (searchResultPageState.isError) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(12),
|
|
||||||
child: const Text("common_server_error").tr(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchResultPageState.isLoading) {
|
|
||||||
return const Center(child: ImmichLoadingIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchResultPageState.isSuccess) {
|
|
||||||
return MultiselectGrid(
|
|
||||||
renderListProvider: searchRenderListProvider,
|
|
||||||
archiveEnabled: true,
|
|
||||||
deleteEnabled: true,
|
|
||||||
editEnabled: true,
|
|
||||||
favoriteEnabled: true,
|
|
||||||
stackEnabled: false,
|
|
||||||
onRefresh: refresh,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
leading: IconButton(
|
|
||||||
splashRadius: 20,
|
|
||||||
onPressed: () {
|
|
||||||
if (isNewSearch.value) {
|
|
||||||
isNewSearch.value = false;
|
|
||||||
} else {
|
|
||||||
context.popRoute(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
|
||||||
),
|
|
||||||
title: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
isNewSearch.value = true;
|
|
||||||
searchFocusNode?.requestFocus();
|
|
||||||
},
|
|
||||||
child: isNewSearch.value ? buildTextField() : buildChip(),
|
|
||||||
),
|
|
||||||
centerTitle: false,
|
|
||||||
),
|
|
||||||
body: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
if (searchFocusNode != null) {
|
|
||||||
searchFocusNode?.unfocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.watch(searchPageStateProvider.notifier).disableSearch();
|
|
||||||
},
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
buildSearchResult(),
|
|
||||||
if (isNewSearch.value)
|
|
||||||
SearchSuggestionList(onSubmitted: onSearchSubmitted),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,7 +31,9 @@ import 'package:immich_mobile/modules/login/views/change_password_page.dart';
|
||||||
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
||||||
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart';
|
import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/models/search_filter.dart';
|
||||||
import 'package:immich_mobile/modules/settings/views/settings_sub_page.dart';
|
import 'package:immich_mobile/modules/settings/views/settings_sub_page.dart';
|
||||||
|
import 'package:immich_mobile/modules/search/views/search_input_page.dart';
|
||||||
import 'package:immich_mobile/modules/shared_link/models/shared_link.dart';
|
import 'package:immich_mobile/modules/shared_link/models/shared_link.dart';
|
||||||
import 'package:immich_mobile/modules/shared_link/views/shared_link_edit_page.dart';
|
import 'package:immich_mobile/modules/shared_link/views/shared_link_edit_page.dart';
|
||||||
import 'package:immich_mobile/modules/shared_link/views/shared_link_page.dart';
|
import 'package:immich_mobile/modules/shared_link/views/shared_link_page.dart';
|
||||||
|
@ -43,7 +45,6 @@ import 'package:immich_mobile/modules/search/views/curated_location_page.dart';
|
||||||
import 'package:immich_mobile/modules/search/views/person_result_page.dart';
|
import 'package:immich_mobile/modules/search/views/person_result_page.dart';
|
||||||
import 'package:immich_mobile/modules/search/views/recently_added_page.dart';
|
import 'package:immich_mobile/modules/search/views/recently_added_page.dart';
|
||||||
import 'package:immich_mobile/modules/search/views/search_page.dart';
|
import 'package:immich_mobile/modules/search/views/search_page.dart';
|
||||||
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
|
|
||||||
import 'package:immich_mobile/modules/settings/views/settings_page.dart';
|
import 'package:immich_mobile/modules/settings/views/settings_page.dart';
|
||||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||||
import 'package:immich_mobile/routing/custom_transition_builders.dart';
|
import 'package:immich_mobile/routing/custom_transition_builders.dart';
|
||||||
|
@ -125,10 +126,6 @@ class AppRouter extends _$AppRouter {
|
||||||
page: BackupControllerRoute.page,
|
page: BackupControllerRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard, _backupPermissionGuard],
|
guards: [_authGuard, _duplicateGuard, _backupPermissionGuard],
|
||||||
),
|
),
|
||||||
AutoRoute(
|
|
||||||
page: SearchResultRoute.page,
|
|
||||||
guards: [_authGuard, _duplicateGuard],
|
|
||||||
),
|
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
page: CuratedLocationRoute.page,
|
page: CuratedLocationRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
@ -223,6 +220,11 @@ class AppRouter extends _$AppRouter {
|
||||||
page: BackupOptionsRoute.page,
|
page: BackupOptionsRoute.page,
|
||||||
guards: [_authGuard, _duplicateGuard],
|
guards: [_authGuard, _duplicateGuard],
|
||||||
),
|
),
|
||||||
|
CustomRoute(
|
||||||
|
page: SearchInputRoute.page,
|
||||||
|
guards: [_authGuard, _duplicateGuard],
|
||||||
|
transitionsBuilder: TransitionsBuilders.noTransition,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -255,22 +255,21 @@ abstract class _$AppRouter extends RootStackRouter {
|
||||||
child: const RecentlyAddedPage(),
|
child: const RecentlyAddedPage(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
SearchRoute.name: (routeData) {
|
SearchInputRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<SearchRouteArgs>(
|
final args = routeData.argsAs<SearchInputRouteArgs>(
|
||||||
orElse: () => const SearchRouteArgs());
|
orElse: () => const SearchInputRouteArgs());
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
child: SearchPage(key: args.key),
|
child: SearchInputPage(
|
||||||
|
key: args.key,
|
||||||
|
prefilter: args.prefilter,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
SearchResultRoute.name: (routeData) {
|
SearchRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<SearchResultRouteArgs>();
|
|
||||||
return AutoRoutePage<dynamic>(
|
return AutoRoutePage<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
child: SearchResultPage(
|
child: const SearchPage(),
|
||||||
key: args.key,
|
|
||||||
searchTerm: args.searchTerm,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
SelectAdditionalUserForSharingRoute.name: (routeData) {
|
SelectAdditionalUserForSharingRoute.name: (routeData) {
|
||||||
|
@ -1113,69 +1112,55 @@ class RecentlyAddedRoute extends PageRouteInfo<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [SearchPage]
|
/// [SearchInputPage]
|
||||||
class SearchRoute extends PageRouteInfo<SearchRouteArgs> {
|
class SearchInputRoute extends PageRouteInfo<SearchInputRouteArgs> {
|
||||||
SearchRoute({
|
SearchInputRoute({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
SearchFilter? prefilter,
|
||||||
List<PageRouteInfo>? children,
|
List<PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
|
SearchInputRoute.name,
|
||||||
|
args: SearchInputRouteArgs(
|
||||||
|
key: key,
|
||||||
|
prefilter: prefilter,
|
||||||
|
),
|
||||||
|
initialChildren: children,
|
||||||
|
);
|
||||||
|
|
||||||
|
static const String name = 'SearchInputRoute';
|
||||||
|
|
||||||
|
static const PageInfo<SearchInputRouteArgs> page =
|
||||||
|
PageInfo<SearchInputRouteArgs>(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
class SearchInputRouteArgs {
|
||||||
|
const SearchInputRouteArgs({
|
||||||
|
this.key,
|
||||||
|
this.prefilter,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Key? key;
|
||||||
|
|
||||||
|
final SearchFilter? prefilter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SearchInputRouteArgs{key: $key, prefilter: $prefilter}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// generated route for
|
||||||
|
/// [SearchPage]
|
||||||
|
class SearchRoute extends PageRouteInfo<void> {
|
||||||
|
const SearchRoute({List<PageRouteInfo>? children})
|
||||||
|
: super(
|
||||||
SearchRoute.name,
|
SearchRoute.name,
|
||||||
args: SearchRouteArgs(key: key),
|
|
||||||
initialChildren: children,
|
initialChildren: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
static const String name = 'SearchRoute';
|
static const String name = 'SearchRoute';
|
||||||
|
|
||||||
static const PageInfo<SearchRouteArgs> page = PageInfo<SearchRouteArgs>(name);
|
static const PageInfo<void> page = PageInfo<void>(name);
|
||||||
}
|
|
||||||
|
|
||||||
class SearchRouteArgs {
|
|
||||||
const SearchRouteArgs({this.key});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SearchRouteArgs{key: $key}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [SearchResultPage]
|
|
||||||
class SearchResultRoute extends PageRouteInfo<SearchResultRouteArgs> {
|
|
||||||
SearchResultRoute({
|
|
||||||
Key? key,
|
|
||||||
required String searchTerm,
|
|
||||||
List<PageRouteInfo>? children,
|
|
||||||
}) : super(
|
|
||||||
SearchResultRoute.name,
|
|
||||||
args: SearchResultRouteArgs(
|
|
||||||
key: key,
|
|
||||||
searchTerm: searchTerm,
|
|
||||||
),
|
|
||||||
initialChildren: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'SearchResultRoute';
|
|
||||||
|
|
||||||
static const PageInfo<SearchResultRouteArgs> page =
|
|
||||||
PageInfo<SearchResultRouteArgs>(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SearchResultRouteArgs {
|
|
||||||
const SearchResultRouteArgs({
|
|
||||||
this.key,
|
|
||||||
required this.searchTerm,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
final String searchTerm;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SearchResultRouteArgs{key: $key, searchTerm: $searchTerm}';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
|
|
|
@ -38,7 +38,7 @@ class TabNavigationObserver extends AutoRouterObserver {
|
||||||
if (route.name == 'SearchRoute') {
|
if (route.name == 'SearchRoute') {
|
||||||
// Refresh Location State
|
// Refresh Location State
|
||||||
ref.invalidate(getCuratedLocationProvider);
|
ref.invalidate(getCuratedLocationProvider);
|
||||||
ref.invalidate(getCuratedPeopleProvider);
|
ref.invalidate(getAllPeopleProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.name == 'SharingRoute') {
|
if (route.name == 'SharingRoute') {
|
||||||
|
|
|
@ -43,6 +43,7 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||||
this.editEnabled = false,
|
this.editEnabled = false,
|
||||||
this.unarchive = false,
|
this.unarchive = false,
|
||||||
this.unfavorite = false,
|
this.unfavorite = false,
|
||||||
|
this.emptyIndicator,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ProviderListenable<AsyncValue<RenderList>> renderListProvider;
|
final ProviderListenable<AsyncValue<RenderList>> renderListProvider;
|
||||||
|
@ -57,12 +58,12 @@ class MultiselectGrid extends HookConsumerWidget {
|
||||||
final bool favoriteEnabled;
|
final bool favoriteEnabled;
|
||||||
final bool unfavorite;
|
final bool unfavorite;
|
||||||
final bool editEnabled;
|
final bool editEnabled;
|
||||||
|
final Widget? emptyIndicator;
|
||||||
Widget buildDefaultLoadingIndicator() =>
|
Widget buildDefaultLoadingIndicator() =>
|
||||||
const Center(child: ImmichLoadingIndicator());
|
const Center(child: ImmichLoadingIndicator());
|
||||||
|
|
||||||
Widget buildEmptyIndicator() =>
|
Widget buildEmptyIndicator() =>
|
||||||
const Center(child: Text("No assets to show"));
|
emptyIndicator ?? const Center(child: Text("No assets to show"));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
|
|
@ -6,7 +6,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/scroll_notifier.provider.dart';
|
||||||
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/modules/search/ui/immich_search_bar.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/tab.provider.dart';
|
import 'package:immich_mobile/shared/providers/tab.provider.dart';
|
||||||
|
@ -53,10 +52,6 @@ class TabControllerPage extends HookConsumerWidget {
|
||||||
// Scroll to top
|
// Scroll to top
|
||||||
scrollToTopNotifierProvider.scrollToTop();
|
scrollToTopNotifierProvider.scrollToTop();
|
||||||
}
|
}
|
||||||
if (tabsRouter.activeIndex == 1 && index == 1) {
|
|
||||||
// Focus search
|
|
||||||
searchFocusNotifier.requestFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
tabsRouter.setActiveIndex(index);
|
tabsRouter.setActiveIndex(index);
|
||||||
|
@ -111,10 +106,7 @@ class TabControllerPage extends HookConsumerWidget {
|
||||||
// Scroll to top
|
// Scroll to top
|
||||||
scrollToTopNotifierProvider.scrollToTop();
|
scrollToTopNotifierProvider.scrollToTop();
|
||||||
}
|
}
|
||||||
if (tabsRouter.activeIndex == 1 && index == 1) {
|
|
||||||
// Focus search
|
|
||||||
searchFocusNotifier.requestFocus();
|
|
||||||
}
|
|
||||||
HapticFeedback.selectionClick();
|
HapticFeedback.selectionClick();
|
||||||
tabsRouter.setActiveIndex(index);
|
tabsRouter.setActiveIndex(index);
|
||||||
ref.read(tabProvider.notifier).state = TabEnum.values[index];
|
ref.read(tabProvider.notifier).state = TabEnum.values[index];
|
||||||
|
@ -170,11 +162,11 @@ class TabControllerPage extends HookConsumerWidget {
|
||||||
|
|
||||||
final multiselectEnabled = ref.watch(multiselectProvider);
|
final multiselectEnabled = ref.watch(multiselectProvider);
|
||||||
return AutoTabsRouter(
|
return AutoTabsRouter(
|
||||||
routes: [
|
routes: const [
|
||||||
const HomeRoute(),
|
HomeRoute(),
|
||||||
SearchRoute(),
|
SearchRoute(),
|
||||||
const SharingRoute(),
|
SharingRoute(),
|
||||||
const LibraryRoute(),
|
LibraryRoute(),
|
||||||
],
|
],
|
||||||
duration: const Duration(milliseconds: 600),
|
duration: const Duration(milliseconds: 600),
|
||||||
transitionBuilder: (context, child, animation) => FadeTransition(
|
transitionBuilder: (context, child, animation) => FadeTransition(
|
||||||
|
|
|
@ -33,6 +33,9 @@ final ThemeData base = ThemeData(
|
||||||
final ThemeData immichLightTheme = ThemeData(
|
final ThemeData immichLightTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: Colors.indigo,
|
||||||
|
),
|
||||||
primarySwatch: Colors.indigo,
|
primarySwatch: Colors.indigo,
|
||||||
primaryColor: Colors.indigo,
|
primaryColor: Colors.indigo,
|
||||||
hintColor: Colors.indigo,
|
hintColor: Colors.indigo,
|
||||||
|
@ -158,6 +161,10 @@ final ThemeData immichDarkTheme = ThemeData(
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primarySwatch: Colors.indigo,
|
primarySwatch: Colors.indigo,
|
||||||
primaryColor: immichDarkThemePrimaryColor,
|
primaryColor: immichDarkThemePrimaryColor,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: immichDarkThemePrimaryColor,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
scaffoldBackgroundColor: immichDarkBackgroundColor,
|
scaffoldBackgroundColor: immichDarkBackgroundColor,
|
||||||
hintColor: Colors.grey[600],
|
hintColor: Colors.grey[600],
|
||||||
fontFamily: 'Overpass',
|
fontFamily: 'Overpass',
|
||||||
|
|
|
@ -5,18 +5,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
sha256: "0f7b1783ddb1e4600580b8c00d0ddae5b06ae7f0382bd4fcce5db4df97b618e1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "67.0.0"
|
version: "66.0.0"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
sha256: "5e8bdcda061d91da6b034d64d8e4026f355bcb8c3e7a0ac2da1523205a91a737"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.4.1"
|
version: "6.4.0"
|
||||||
analyzer_plugin:
|
analyzer_plugin:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
|
@ -101,18 +101,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.0"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.2.1"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -125,10 +125,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
|
sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.3.0"
|
version: "7.2.10"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -141,10 +141,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: a3ec2e0f967bc47f69f95009bb93db936288d61d5343b9436e378b28a2f830c6
|
sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.9.0"
|
version: "8.6.1"
|
||||||
cached_network_image:
|
cached_network_image:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -205,10 +205,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: chewie
|
name: chewie
|
||||||
sha256: "8bc4ac4cf3f316e50a25958c0f5eb9bb12cf7e8308bb1d74a43b230da2cfc144"
|
sha256: "3427e469d7cc99536ac4fbaa069b3352c21760263e65ffb4f0e1c054af43a73e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.5"
|
version: "1.7.4"
|
||||||
ci:
|
ci:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -221,10 +221,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cli_util
|
name: cli_util
|
||||||
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.4.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -237,10 +237,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
|
sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.10.0"
|
version: "4.5.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -277,10 +277,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cross_file
|
name: cross_file
|
||||||
sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e
|
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.3+8"
|
version: "0.3.3+4"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -301,42 +301,42 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cupertino_icons
|
name: cupertino_icons
|
||||||
sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d
|
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.6"
|
version: "1.0.5"
|
||||||
custom_lint:
|
custom_lint:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: custom_lint
|
name: custom_lint
|
||||||
sha256: f89ff83efdba7c8996e86bb3bad0b759d58f9b19ae4d0e277a386ddd8b481217
|
sha256: "7c0aec12df22f9082146c354692056677f1e70bc43471644d1fdb36c6fdda799"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.4"
|
||||||
custom_lint_builder:
|
custom_lint_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: custom_lint_builder
|
name: custom_lint_builder
|
||||||
sha256: "3a14687fc71a5e2124a29722106f7b7e67dd5a6d58e33f2859650b46acff1d54"
|
sha256: d7dc41e709dde223806660268678be7993559e523eb3164e2a1425fd6f7615a9
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.6.4"
|
||||||
custom_lint_core:
|
custom_lint_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: custom_lint_core
|
name: custom_lint_core
|
||||||
sha256: "1e9128e095ad5e0973469bdaac1ead8bfc86c485954c23cf617299de5e6fa029"
|
sha256: a85e8f78f4c52f6c63cdaf8c872eb573db0231dcdf3c3a5906d493c1f8bc20e6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.6.3"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dart_style
|
name: dart_style
|
||||||
sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368"
|
sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.4"
|
version: "2.3.2"
|
||||||
dartx:
|
dartx:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -349,18 +349,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: dbus
|
name: dbus
|
||||||
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
|
sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.10"
|
version: "0.7.8"
|
||||||
device_info_plus:
|
device_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
sha256: "77f757b789ff68e4eaf9c56d1752309bd9f7ad557cb105b938a7f8eb89e59110"
|
sha256: "0042cb3b2a76413ea5f8a2b40cec2a33e01d0c937e91f0f7c211fde4f7739ba6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.1.2"
|
version: "9.1.1"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -373,18 +373,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: easy_image_viewer
|
name: easy_image_viewer
|
||||||
sha256: "750bb85e0a34504557d378a616110540caeec2324490fc040709589219e75834"
|
sha256: "6d765e9040a6e625796b387140b95f23318f25a448bf2647af30d17a77cea022"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.1"
|
version: "1.4.0"
|
||||||
easy_localization:
|
easy_localization:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: easy_localization
|
name: easy_localization
|
||||||
sha256: "9c86754b22aaa3e74e471635b25b33729f958dd6fb83df0ad6612948a7b231af"
|
sha256: de63e3b422adfc97f256cbb3f8cf12739b6a4993d390f3cadb3f51837afaefe5
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.4"
|
version: "3.0.3"
|
||||||
easy_logger:
|
easy_logger:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -421,34 +421,34 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_linux
|
name: file_selector_linux
|
||||||
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
|
sha256: "770eb1ab057b5ae4326d1c24cc57710758b9a46026349d021d6311bd27580046"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.2+1"
|
version: "0.9.2"
|
||||||
file_selector_macos:
|
file_selector_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_macos
|
name: file_selector_macos
|
||||||
sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6
|
sha256: "4ada532862917bf16e3adb3891fe3a5917a58bae03293e497082203a80909412"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+3"
|
version: "0.9.3+1"
|
||||||
file_selector_platform_interface:
|
file_selector_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_platform_interface
|
name: file_selector_platform_interface
|
||||||
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
sha256: "412705a646a0ae90f33f37acfae6a0f7cbc02222d6cd34e479421c3e74d3853c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.2"
|
version: "2.6.0"
|
||||||
file_selector_windows:
|
file_selector_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: file_selector_windows
|
name: file_selector_windows
|
||||||
sha256: d3547240c20cabf205c7c7f01a50ecdbc413755814d6677f3cb366f04abcead0
|
sha256: "1372760c6b389842b77156203308940558a2817360154084368608413835fc26"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3+1"
|
version: "0.9.3"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -487,10 +487,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_hooks
|
name: flutter_hooks
|
||||||
sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70
|
sha256: "09f64db63fee3b2ab8b9038a1346be7d8986977fae3fec601275bf32455ccfc0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.20.5"
|
version: "0.20.4"
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -548,18 +548,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_plugin_android_lifecycle
|
name: flutter_plugin_android_lifecycle
|
||||||
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
|
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.17"
|
version: "2.0.15"
|
||||||
flutter_riverpod:
|
flutter_riverpod:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flutter_riverpod
|
name: flutter_riverpod
|
||||||
sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098"
|
sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.10"
|
version: "2.5.1"
|
||||||
flutter_svg:
|
flutter_svg:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -635,26 +635,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_android
|
name: geolocator_android
|
||||||
sha256: "136f1c97e1903366393bda514c5d9e98843418baea52899aa45edae9af8a5cd6"
|
sha256: "93906636752ea4d4e778afa981fdfe7409f545b3147046300df194330044d349"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.5.2"
|
version: "4.3.1"
|
||||||
geolocator_apple:
|
geolocator_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_apple
|
name: geolocator_apple
|
||||||
sha256: "2f2d4ee16c4df269e93c0e382be075cc01d5db6703c3196e4af20a634fe49ef4"
|
sha256: "79babf44b692ec5e789d322dc736ef71586056e8e6828f747c9e005456b248bf"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.6"
|
version: "2.3.5"
|
||||||
geolocator_platform_interface:
|
geolocator_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: geolocator_platform_interface
|
name: geolocator_platform_interface
|
||||||
sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9"
|
sha256: b8cc1d3be0ca039a3f2174b0b026feab8af3610e220b8532e42cff8ec6658535
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.2"
|
version: "4.1.0"
|
||||||
geolocator_web:
|
geolocator_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -691,18 +691,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: hooks_riverpod
|
name: hooks_riverpod
|
||||||
sha256: "758b07eba336e3cbacbd81dba481f2228a14102083fdde07045e8514e8054c49"
|
sha256: "45b2030a18bcd6dbd680c2c91bc3b33e3fe7c323e3acb5ecec93a613e2fbaa8a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.10"
|
version: "2.5.1"
|
||||||
hotreloader:
|
hotreloader:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: hotreloader
|
name: hotreloader
|
||||||
sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e
|
sha256: "94ee21a60ea2836500799f3af035dc3212b1562027f1e0031c14e087f0231449"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.1.0"
|
||||||
html:
|
html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -739,10 +739,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
sha256: "004a2e90ce080f8627b5a04aecb4cdfac87d2c3f3b520aa291260be5a32c033d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.7"
|
version: "4.1.4"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -755,58 +755,58 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: "39f2bfe497e495450c81abcd44b62f56c2a36a37a175da7d137b4454977b51b1"
|
sha256: "8179b54039b50eee561676232304f487602e2950ffb3e8995ed9034d6505ca34"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9+3"
|
version: "0.8.7+4"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_for_web
|
name: image_picker_for_web
|
||||||
sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3
|
sha256: "869fe8a64771b7afbc99fc433a5f7be2fea4d1cb3d7c11a48b6b579eb9c797f0"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "2.2.0"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: fadafce49e8569257a0cad56d24438a6fa1f0cbd7ee0af9b631f7492818a4ca3
|
sha256: b3e2f21feb28b24dd73a35d7ad6e83f568337c70afab5eabac876e23803f264b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.9+1"
|
version: "0.8.8"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_linux
|
name: image_picker_linux
|
||||||
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
|
sha256: "02cbc21fe1706b97942b575966e5fbbeaac535e76deef70d3a242e4afb857831"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.1"
|
||||||
image_picker_macos:
|
image_picker_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_macos
|
name: image_picker_macos
|
||||||
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
|
sha256: cee2aa86c56780c13af2c77b5f2f72973464db204569e1ba2dd744459a065af4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.1"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b
|
sha256: c1134543ae2187e85299996d21c526b2f403854994026d575ae4cf30d7bb2a32
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.3"
|
version: "2.9.0"
|
||||||
image_picker_windows:
|
image_picker_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_windows
|
name: image_picker_windows
|
||||||
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
sha256: c3066601ea42113922232c7b7b3330a2d86f029f685bba99d82c30e799914952
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+1"
|
version: "0.2.1"
|
||||||
integration_test:
|
integration_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -922,7 +922,7 @@ packages:
|
||||||
description:
|
description:
|
||||||
path: maplibre_gl_platform_interface
|
path: maplibre_gl_platform_interface
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "3cf0abb051849ca3f14e6aa19d2261ad18f22ce1"
|
resolved-ref: acb428a005efd9832a0a8e7ef0945f899dfb3ca5
|
||||||
url: "https://github.com/maplibre/flutter-maplibre-gl.git"
|
url: "https://github.com/maplibre/flutter-maplibre-gl.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.18.0"
|
version: "0.18.0"
|
||||||
|
@ -931,7 +931,7 @@ packages:
|
||||||
description:
|
description:
|
||||||
path: maplibre_gl_web
|
path: maplibre_gl_web
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: "3cf0abb051849ca3f14e6aa19d2261ad18f22ce1"
|
resolved-ref: acb428a005efd9832a0a8e7ef0945f899dfb3ca5
|
||||||
url: "https://github.com/maplibre/flutter-maplibre-gl.git"
|
url: "https://github.com/maplibre/flutter-maplibre-gl.git"
|
||||||
source: git
|
source: git
|
||||||
version: "0.18.0"
|
version: "0.18.0"
|
||||||
|
@ -955,18 +955,18 @@ packages:
|
||||||
dependency: "direct overridden"
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.11.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.5"
|
version: "1.0.4"
|
||||||
mocktail:
|
mocktail:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -1058,18 +1058,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.1.0"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.0"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1082,50 +1082,50 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_linux
|
name: path_provider_linux
|
||||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.0"
|
||||||
path_provider_platform_interface:
|
path_provider_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.0"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_windows
|
name: path_provider_windows
|
||||||
sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170"
|
sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.0"
|
||||||
permission_handler:
|
permission_handler:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: permission_handler
|
name: permission_handler
|
||||||
sha256: "74e962b7fad7ff75959161bb2c0ad8fe7f2568ee82621c9c2660b751146bfe44"
|
sha256: "45ff3fbcb99040fde55c528d5e3e6ca29171298a85436274d49c6201002087d6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.3.0"
|
version: "11.2.0"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474"
|
sha256: "758284a0976772f9c744d6384fc5dc4834aa61e3f7aa40492927f244767374eb"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "12.0.5"
|
version: "12.0.3"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
sha256: bdafc6db74253abb63907f4e357302e6bb786ab41465e8635f362ee71fd8707b
|
sha256: c6bf440f80acd2a873d3d91a699e4cc770f86e7e6b576dda98759e8b92b39830
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.4.0"
|
version: "9.3.0"
|
||||||
permission_handler_html:
|
permission_handler_html:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1138,10 +1138,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
sha256: "23dfba8447c076ab5be3dee9ceb66aad345c4a648f0cac292c77b1eb0e800b78"
|
sha256: "5c43148f2bfb6d14c5a8162c0a712afe891f2d847f35fcff29c406b37da43c3c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.0"
|
version: "4.1.0"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1186,18 +1186,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: plugin_platform_interface
|
name: plugin_platform_interface
|
||||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.5"
|
||||||
pointycastle:
|
pointycastle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pointycastle
|
name: pointycastle
|
||||||
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
|
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.7.4"
|
version: "3.7.3"
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1218,10 +1218,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.1"
|
version: "6.0.5"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1242,10 +1242,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: riverpod
|
name: riverpod
|
||||||
sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589"
|
sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.1"
|
||||||
riverpod_analyzer_utils:
|
riverpod_analyzer_utils:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1258,26 +1258,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: riverpod_annotation
|
name: riverpod_annotation
|
||||||
sha256: "77e5d51afa4fa3e67903fb8746f33d368728d7051a0b6c292bcee60aeba46d95"
|
sha256: b70e95fbd5ca7ce42f5148092022971bb2e9843b6ab71e97d479e8ab52e98979
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.4"
|
version: "2.3.3"
|
||||||
riverpod_generator:
|
riverpod_generator:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: riverpod_generator
|
name: riverpod_generator
|
||||||
sha256: "359068f04879347ae4edbe66c81cc95f83fa1743806d1a0c86e55dd3c33ebb32"
|
sha256: ff8f064f1d7ef3cc6af481bba8e9a3fcdb4d34df34fac1b39bbc003167065be0
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.11"
|
version: "2.3.9"
|
||||||
riverpod_lint:
|
riverpod_lint:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: riverpod_lint
|
name: riverpod_lint
|
||||||
sha256: e9bbd02e9e89e18eecb183bbca556d7b523a0669024da9b8167c08903f442937
|
sha256: "3c67c14ccd16f0c9d53e35ef70d06cd9d072e2fb14557326886bbde903b230a5"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.9"
|
version: "2.3.10"
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1314,26 +1314,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences
|
name: shared_preferences
|
||||||
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
|
sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.0"
|
||||||
shared_preferences_android:
|
shared_preferences_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
|
sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.1"
|
version: "2.2.0"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
|
sha256: d29753996d8eb8f7619a1f13df6ce65e34bc107bef6330739ed76f18b22310ef
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.5"
|
version: "2.3.3"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1346,18 +1346,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.0"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_web
|
name: shared_preferences_web
|
||||||
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.0"
|
||||||
shared_preferences_windows:
|
shared_preferences_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1407,10 +1407,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "1.4.0"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1419,30 +1419,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
sprintf:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sprintf
|
|
||||||
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "7.0.0"
|
|
||||||
sqflite:
|
sqflite:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite
|
name: sqflite
|
||||||
sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6
|
sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.0"
|
||||||
sqflite_common:
|
sqflite_common:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqflite_common
|
name: sqflite_common
|
||||||
sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5"
|
sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.3"
|
version: "2.5.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1495,10 +1487,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: synchronized
|
name: synchronized
|
||||||
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
|
sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0+1"
|
version: "3.1.0"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1527,10 +1519,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: time
|
name: time
|
||||||
sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221
|
sha256: "83427e11d9072e038364a5e4da559e85869b227cf699a541be0da74f14140124"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.4"
|
version: "2.1.3"
|
||||||
timezone:
|
timezone:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1575,10 +1567,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745
|
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.0"
|
version: "6.2.2"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1607,10 +1599,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.0"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1631,10 +1623,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.3.3"
|
version: "3.0.7"
|
||||||
vector_graphics:
|
vector_graphics:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1679,10 +1671,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_android
|
name: video_player_android
|
||||||
sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2"
|
sha256: f338a5a396c845f4632959511cad3542cdf3167e1b2a1a948ef07f7123c03608
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.12"
|
version: "2.4.9"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1695,18 +1687,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_platform_interface
|
name: video_player_platform_interface
|
||||||
sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6"
|
sha256: "1ca9acd7a0fb15fb1a990cb554e6f004465c6f37c99d2285766f08a4b2802988"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.2"
|
version: "6.2.0"
|
||||||
video_player_web:
|
video_player_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: video_player_web
|
name: video_player_web
|
||||||
sha256: "34beb3a07d4331a24f7e7b2f75b8e2b103289038e07e65529699a671b6a6e2cb"
|
sha256: "44ce41424d104dfb7cf6982cc6b84af2b007a24d126406025bf40de5d481c74c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.0.16"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1767,26 +1759,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
|
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.2.0"
|
version: "4.1.4"
|
||||||
win32_registry:
|
win32_registry:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32_registry
|
name: win32_registry
|
||||||
sha256: "41fd8a189940d8696b1b810efb9abcf60827b6cbfab90b0c43e8439e3a39d85a"
|
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.2"
|
version: "1.1.0"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: xdg_directories
|
name: xdg_directories
|
||||||
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d
|
sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.2"
|
||||||
xml:
|
xml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
Loading…
Reference in a new issue