Refactor mobile to use OpenApi generated SDK (#336)

This commit is contained in:
Alex 2022-07-13 07:23:48 -05:00 committed by GitHub
parent d69470e207
commit ae7e582ec8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
276 changed files with 14513 additions and 3003 deletions

View file

@ -1,84 +0,0 @@
import 'dart:convert';
class CuratedLocation {
final String id;
final String city;
final String resizePath;
final String deviceAssetId;
final String deviceId;
CuratedLocation({
required this.id,
required this.city,
required this.resizePath,
required this.deviceAssetId,
required this.deviceId,
});
CuratedLocation copyWith({
String? id,
String? city,
String? resizePath,
String? deviceAssetId,
String? deviceId,
}) {
return CuratedLocation(
id: id ?? this.id,
city: city ?? this.city,
resizePath: resizePath ?? this.resizePath,
deviceAssetId: deviceAssetId ?? this.deviceAssetId,
deviceId: deviceId ?? this.deviceId,
);
}
Map<String, dynamic> toMap() {
return {
'id': id,
'city': city,
'resizePath': resizePath,
'deviceAssetId': deviceAssetId,
'deviceId': deviceId,
};
}
factory CuratedLocation.fromMap(Map<String, dynamic> map) {
return CuratedLocation(
id: map['id'] ?? '',
city: map['city'] ?? '',
resizePath: map['resizePath'] ?? '',
deviceAssetId: map['deviceAssetId'] ?? '',
deviceId: map['deviceId'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory CuratedLocation.fromJson(String source) =>
CuratedLocation.fromMap(json.decode(source));
@override
String toString() {
return 'CuratedLocation(id: $id, city: $city, resizePath: $resizePath, deviceAssetId: $deviceAssetId, deviceId: $deviceId)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is CuratedLocation &&
other.id == id &&
other.city == city &&
other.resizePath == resizePath &&
other.deviceAssetId == deviceAssetId &&
other.deviceId == deviceId;
}
@override
int get hashCode {
return id.hashCode ^
city.hashCode ^
resizePath.hashCode ^
deviceAssetId.hashCode ^
deviceId.hashCode;
}
}

View file

@ -1,85 +0,0 @@
import 'dart:convert';
class CuratedObject {
final String id;
final String object;
final String resizePath;
final String deviceAssetId;
final String deviceId;
CuratedObject({
required this.id,
required this.object,
required this.resizePath,
required this.deviceAssetId,
required this.deviceId,
});
CuratedObject copyWith({
String? id,
String? object,
String? resizePath,
String? deviceAssetId,
String? deviceId,
}) {
return CuratedObject(
id: id ?? this.id,
object: object ?? this.object,
resizePath: resizePath ?? this.resizePath,
deviceAssetId: deviceAssetId ?? this.deviceAssetId,
deviceId: deviceId ?? this.deviceId,
);
}
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'id': id});
result.addAll({'object': object});
result.addAll({'resizePath': resizePath});
result.addAll({'deviceAssetId': deviceAssetId});
result.addAll({'deviceId': deviceId});
return result;
}
factory CuratedObject.fromMap(Map<String, dynamic> map) {
return CuratedObject(
id: map['id'] ?? '',
object: map['object'] ?? '',
resizePath: map['resizePath'] ?? '',
deviceAssetId: map['deviceAssetId'] ?? '',
deviceId: map['deviceId'] ?? '',
);
}
String toJson() => json.encode(toMap());
factory CuratedObject.fromJson(String source) =>
CuratedObject.fromMap(json.decode(source));
@override
String toString() {
return 'CuratedObject(id: $id, object: $object, resizePath: $resizePath, deviceAssetId: $deviceAssetId, deviceId: $deviceId)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is CuratedObject &&
other.id == id &&
other.object == object &&
other.resizePath == resizePath &&
other.deviceAssetId == deviceAssetId &&
other.deviceId == deviceId;
}
@override
int get hashCode {
return id.hashCode ^
object.hashCode ^
resizePath.hashCode ^
deviceAssetId.hashCode ^
deviceId.hashCode;
}
}

View file

@ -1,13 +1,13 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:openapi/api.dart';
class SearchResultPageState {
final bool isLoading;
final bool isSuccess;
final bool isError;
final List<ImmichAsset> searchResult;
final List<AssetResponseDto> searchResult;
SearchResultPageState({
required this.isLoading,
@ -20,7 +20,7 @@ class SearchResultPageState {
bool? isLoading,
bool? isSuccess,
bool? isError,
List<ImmichAsset>? searchResult,
List<AssetResponseDto>? searchResult,
}) {
return SearchResultPageState(
isLoading: isLoading ?? this.isLoading,
@ -35,7 +35,7 @@ class SearchResultPageState {
'isLoading': isLoading,
'isSuccess': isSuccess,
'isError': isError,
'searchResult': searchResult.map((x) => x.toMap()).toList(),
'searchResult': searchResult.map((x) => x.toJson()).toList(),
};
}
@ -44,8 +44,9 @@ class SearchResultPageState {
isLoading: map['isLoading'] ?? false,
isSuccess: map['isSuccess'] ?? false,
isError: map['isError'] ?? false,
searchResult: List<ImmichAsset>.from(
map['searchResult']?.map((x) => ImmichAsset.fromMap(x))),
searchResult: List<AssetResponseDto>.from(
map['searchResult']?.map((x) => AssetResponseDto.mapFromJson(x)),
),
);
}

View file

@ -1,9 +1,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
import 'package:immich_mobile/modules/search/models/search_page_state.model.dart';
import 'package:immich_mobile/modules/search/services/search.service.dart';
import 'package:openapi/api.dart';
class SearchPageStateNotifier extends StateNotifier<SearchPageState> {
SearchPageStateNotifier(this._searchService)
@ -58,7 +57,7 @@ final searchPageStateProvider =
});
final getCuratedLocationProvider =
FutureProvider.autoDispose<List<CuratedLocation>>((ref) async {
FutureProvider.autoDispose<List<CuratedLocationsResponseDto>>((ref) async {
final SearchService searchService = ref.watch(searchServiceProvider);
var curatedLocation = await searchService.getCuratedLocation();
@ -66,7 +65,7 @@ final getCuratedLocationProvider =
});
final getCuratedObjectProvider =
FutureProvider.autoDispose<List<CuratedObject>>((ref) async {
FutureProvider.autoDispose<List<CuratedObjectsResponseDto>>((ref) async {
final SearchService searchService = ref.watch(searchServiceProvider);
var curatedObject = await searchService.getCuratedObjects();

View file

@ -3,8 +3,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/models/search_result_page_state.model.dart';
import 'package:immich_mobile/modules/search/services/search.service.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:intl/intl.dart';
import 'package:openapi/api.dart';
class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
SearchResultPageNotifier(this._searchService)
@ -21,19 +21,29 @@ class SearchResultPageNotifier extends StateNotifier<SearchResultPageState> {
void search(String searchTerm) async {
state = state.copyWith(
searchResult: [], isError: false, isLoading: true, isSuccess: false);
searchResult: [],
isError: false,
isLoading: true,
isSuccess: false,
);
List<ImmichAsset>? assets = await _searchService.searchAsset(searchTerm);
List<AssetResponseDto>? assets =
await _searchService.searchAsset(searchTerm);
if (assets != null) {
state = state.copyWith(
searchResult: assets,
isError: false,
isLoading: false,
isSuccess: true);
searchResult: assets,
isError: false,
isLoading: false,
isSuccess: true,
);
} else {
state = state.copyWith(
searchResult: [], isError: true, isLoading: false, isSuccess: false);
searchResult: [],
isError: true,
isLoading: false,
isSuccess: false,
);
}
}
}
@ -48,7 +58,11 @@ final searchResultGroupByDateTimeProvider = StateProvider((ref) {
var assets = ref.watch(searchResultPageProvider).searchResult;
assets.sortByCompare<DateTime>(
(e) => DateTime.parse(e.createdAt), (a, b) => b.compareTo(a));
return assets.groupListsBy((element) =>
DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)));
(e) => DateTime.parse(e.createdAt),
(a, b) => b.compareTo(a),
);
return assets.groupListsBy(
(element) =>
DateFormat('y-MM-dd').format(DateTime.parse(element.createdAt)),
);
});

View file

@ -1,79 +1,54 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
import 'package:immich_mobile/shared/services/network.service.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
final searchServiceProvider =
Provider((ref) => SearchService(ref.watch(networkServiceProvider)));
final searchServiceProvider = Provider(
(ref) => SearchService(
ref.watch(apiServiceProvider),
),
);
class SearchService {
final NetworkService _networkService;
SearchService(this._networkService);
final ApiService _apiService;
SearchService(this._apiService);
Future<List<String>?> getUserSuggestedSearchTerms() async {
try {
var res = await _networkService.getRequest(url: "asset/searchTerm");
List<dynamic> decodedData = jsonDecode(res.toString());
return List.from(decodedData);
return await _apiService.assetApi.getAssetSearchTerms();
} catch (e) {
debugPrint("[ERROR] [getUserSuggestedSearchTerms] ${e.toString()}");
return [];
}
}
Future<List<ImmichAsset>?> searchAsset(String searchTerm) async {
Future<List<AssetResponseDto>?> searchAsset(String searchTerm) async {
try {
var res = await _networkService.postRequest(
url: "asset/search",
data: {"searchTerm": searchTerm},
);
List<dynamic> decodedData = jsonDecode(res.toString());
List<ImmichAsset> result =
List.from(decodedData.map((a) => ImmichAsset.fromMap(a)));
return result;
return await _apiService.assetApi
.searchAsset(SearchAssetDto(searchTerm: searchTerm));
} catch (e) {
debugPrint("[ERROR] [searchAsset] ${e.toString()}");
return null;
}
}
Future<List<CuratedLocation>?> getCuratedLocation() async {
Future<List<CuratedLocationsResponseDto>?> getCuratedLocation() async {
try {
var res = await _networkService.getRequest(url: "asset/allLocation");
var locations = await _apiService.assetApi.getCuratedLocations();
List<dynamic> decodedData = jsonDecode(res.toString());
List<CuratedLocation> result =
List.from(decodedData.map((a) => CuratedLocation.fromMap(a)));
return result;
return locations;
} catch (e) {
debugPrint("[ERROR] [getCuratedLocation] ${e.toString()}");
throw Error();
debugPrint("Error [getCuratedLocation] ${e.toString()}");
return [];
}
}
Future<List<CuratedObject>?> getCuratedObjects() async {
Future<List<CuratedObjectsResponseDto>?> getCuratedObjects() async {
try {
var res = await _networkService.getRequest(url: "asset/allObjects");
List<dynamic> decodedData = jsonDecode(res.toString());
List<CuratedObject> result =
List.from(decodedData.map((a) => CuratedObject.fromMap(a)));
return result;
return await _apiService.assetApi.getCuratedObjects();
} catch (e) {
debugPrint("[ERROR] [CuratedObject] ${e.toString()}");
throw Error();
debugPrint("Error [getCuratedObjects] ${e.toString()}");
throw [];
}
}
}

View file

@ -5,9 +5,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
SearchBar(
{Key? key, required this.searchFocusNode, required this.onSubmitted})
: super(key: key);
SearchBar({
Key? key,
required this.searchFocusNode,
required this.onSubmitted,
}) : super(key: key);
final FocusNode searchFocusNode;
final Function(String) onSubmitted;
@ -26,7 +28,8 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
ref.watch(searchPageStateProvider.notifier).disableSearch();
searchTermController.clear();
},
icon: const Icon(Icons.arrow_back_ios_rounded))
icon: const Icon(Icons.arrow_back_ios_rounded),
)
: const Icon(Icons.search_rounded),
title: TextField(
controller: searchTermController,
@ -50,10 +53,10 @@ class SearchBar extends HookConsumerWidget with PreferredSizeWidget {
},
decoration: InputDecoration(
hintText: 'search_bar_hint'.tr(),
enabledBorder: UnderlineInputBorder(
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
focusedBorder: UnderlineInputBorder(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
),

View file

@ -2,15 +2,14 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
class ThumbnailWithInfo extends StatelessWidget {
const ThumbnailWithInfo(
{Key? key,
required this.textInfo,
required this.imageUrl,
required this.onTap})
: super(key: key);
const ThumbnailWithInfo({
Key? key,
required this.textInfo,
required this.imageUrl,
required this.onTap,
}) : super(key: key);
final String textInfo;
final String imageUrl;

View file

@ -5,8 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/search/models/curated_location.model.dart';
import 'package:immich_mobile/modules/search/models/curated_object.model.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/ui/search_bar.dart';
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
@ -14,6 +12,7 @@ import 'package:immich_mobile/modules/search/ui/thumbnail_with_info.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/utils/capitalize_first_letter.dart';
import 'package:openapi/api.dart';
// ignore: must_be_immutable
class SearchPage extends HookConsumerWidget {
@ -25,15 +24,18 @@ class SearchPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
var box = Hive.box(userInfoBox);
final isSearchEnabled = ref.watch(searchPageStateProvider).isSearchEnabled;
AsyncValue<List<CuratedLocation>> curatedLocation =
AsyncValue<List<CuratedLocationsResponseDto>> curatedLocation =
ref.watch(getCuratedLocationProvider);
AsyncValue<List<CuratedObject>> curatedObjects =
AsyncValue<List<CuratedObjectsResponseDto>> curatedObjects =
ref.watch(getCuratedObjectProvider);
useEffect(() {
searchFocusNode = FocusNode();
return () => searchFocusNode.dispose();
}, []);
useEffect(
() {
searchFocusNode = FocusNode();
return () => searchFocusNode.dispose();
},
[],
);
_onSearchSubmitted(String searchTerm) async {
searchFocusNode.unfocus();
@ -58,16 +60,16 @@ class SearchPage extends HookConsumerWidget {
scrollDirection: Axis.horizontal,
itemCount: curatedLocation.value?.length,
itemBuilder: ((context, index) {
CuratedLocation locationInfo = curatedLocations[index];
var locationInfo = curatedLocations[index];
var thumbnailRequestUrl =
'${box.get(serverEndpointKey)}/asset/file?aid=${locationInfo.deviceAssetId}&did=${locationInfo.deviceId}&isThumb=true';
'${box.get(serverEndpointKey)}/asset/thumbnail/${locationInfo.id}';
return ThumbnailWithInfo(
imageUrl: thumbnailRequestUrl,
textInfo: locationInfo.city,
onTap: () {
AutoRouter.of(context).push(
SearchResultRoute(searchTerm: locationInfo.city));
SearchResultRoute(searchTerm: locationInfo.city),
);
},
);
}),
@ -109,7 +111,7 @@ class SearchPage extends HookConsumerWidget {
scrollDirection: Axis.horizontal,
itemCount: curatedObjects.value?.length,
itemBuilder: ((context, index) {
CuratedObject curatedObjectInfo = objects[index];
var curatedObjectInfo = objects[index];
var thumbnailRequestUrl =
'${box.get(serverEndpointKey)}/asset/file?aid=${curatedObjectInfo.deviceAssetId}&did=${curatedObjectInfo.deviceId}&isThumb=true';
@ -117,9 +119,12 @@ class SearchPage extends HookConsumerWidget {
imageUrl: thumbnailRequestUrl,
textInfo: curatedObjectInfo.object,
onTap: () {
AutoRouter.of(context).push(SearchResultRoute(
AutoRouter.of(context).push(
SearchResultRoute(
searchTerm: curatedObjectInfo.object
.capitalizeFirstLetter()));
.capitalizeFirstLetter(),
),
);
},
);
}),
@ -160,7 +165,7 @@ class SearchPage extends HookConsumerWidget {
ListView(
children: [
Padding(
padding: EdgeInsets.all(16.0),
padding: const EdgeInsets.all(16.0),
child: const Text(
"search_page_places",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
@ -168,8 +173,8 @@ class SearchPage extends HookConsumerWidget {
),
_buildPlaces(),
Padding(
padding: EdgeInsets.all(16.0),
child: const Text(
padding: const EdgeInsets.all(16.0),
child: const Text(
"search_page_things",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
).tr(),

View file

@ -29,13 +29,18 @@ class SearchResultPage extends HookConsumerWidget {
late FocusNode searchFocusNode;
useEffect(() {
searchFocusNode = FocusNode();
useEffect(
() {
searchFocusNode = FocusNode();
Future.delayed(Duration.zero,
() => ref.read(searchResultPageProvider.notifier).search(searchTerm));
return () => searchFocusNode.dispose();
}, []);
Future.delayed(
Duration.zero,
() => ref.read(searchResultPageProvider.notifier).search(searchTerm),
);
return () => searchFocusNode.dispose();
},
[],
);
_onSearchSubmitted(String newSearchTerm) {
debugPrint("Re-Search with $newSearchTerm");
@ -69,10 +74,10 @@ class SearchResultPage extends HookConsumerWidget {
},
decoration: InputDecoration(
hintText: 'search_result_page_new_search_hint'.tr(),
enabledBorder: UnderlineInputBorder(
enabledBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
focusedBorder: UnderlineInputBorder(
focusedBorder: const UnderlineInputBorder(
borderSide: BorderSide(color: Colors.transparent),
),
),
@ -90,9 +95,10 @@ class SearchResultPage extends HookConsumerWidget {
Text(
currentSearchTerm.value,
style: TextStyle(
color: Theme.of(context).primaryColor,
fontSize: 13,
fontWeight: FontWeight.bold),
color: Theme.of(context).primaryColor,
fontSize: 13,
fontWeight: FontWeight.bold,
),
maxLines: 1,
),
Icon(
@ -116,9 +122,10 @@ class SearchResultPage extends HookConsumerWidget {
if (searchResultPageState.isLoading) {
return Center(
child: SpinKitDancingSquare(
color: Theme.of(context).primaryColor,
));
child: SpinKitDancingSquare(
color: Theme.of(context).primaryColor,
),
);
}
if (searchResultPageState.isSuccess) {
@ -184,11 +191,12 @@ class SearchResultPage extends HookConsumerWidget {
icon: const Icon(Icons.arrow_back_ios_rounded),
),
title: GestureDetector(
onTap: () {
isNewSearch.value = true;
searchFocusNode.requestFocus();
},
child: isNewSearch.value ? _buildTextField() : _buildChip()),
onTap: () {
isNewSearch.value = true;
searchFocusNode.requestFocus();
},
child: isNewSearch.value ? _buildTextField() : _buildChip(),
),
centerTitle: false,
),
body: GestureDetector(