mirror of
https://github.com/immich-app/immich.git
synced 2025-02-03 01:22:44 +01:00
Merge fd94741698
into 060300de8a
This commit is contained in:
commit
57e0bc57d1
50 changed files with 787 additions and 26 deletions
mobile
assets/i18n
lib
interfaces
models/folder
pages/library
providers
repositories
routing
services
openapi/lib/api
activities_api.dartalbums_api.dartapi_keys_api.dartassets_api.dartaudit_api.dartauthentication_api.dartdeprecated_api.dartdownload_api.dartduplicates_api.dartfaces_api.dartfile_reports_api.dartjobs_api.dartlibraries_api.dartmap_api.dartmemories_api.dartnotifications_api.darto_auth_api.dartpartners_api.dartpeople_api.dartsearch_api.dartserver_api.dartsessions_api.dartshared_links_api.dartstacks_api.dartsync_api.dartsystem_config_api.dartsystem_metadata_api.darttags_api.darttimeline_api.darttrash_api.dartusers_admin_api.dartusers_api.dartview_api.dart
pubspec.lockpubspec.yamlopen-api
|
@ -274,6 +274,7 @@
|
|||
"favorites_page_title": "Favorites",
|
||||
"filename_search": "File name or extension",
|
||||
"filter": "Filter",
|
||||
"folders": "Folders",
|
||||
"get_wifiname_error": "Could not get Wi-Fi name. Make sure you have granted the necessary permissions and are connected to a Wi-Fi network",
|
||||
"grant_permission": "Grant permission",
|
||||
"haptic_feedback_switch": "Enable haptic feedback",
|
||||
|
|
6
mobile/lib/interfaces/folder_api.interface.dart
Normal file
6
mobile/lib/interfaces/folder_api.interface.dart
Normal file
|
@ -0,0 +1,6 @@
|
|||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
|
||||
abstract interface class IFolderApiRepository {
|
||||
Future<List<String>> getAllUniquePaths();
|
||||
Future<List<Asset>> getAssetsForPath(String? path);
|
||||
}
|
12
mobile/lib/models/folder/recursive_folder.model.dart
Normal file
12
mobile/lib/models/folder/recursive_folder.model.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
import 'package:immich_mobile/models/folder/root_folder.model.dart';
|
||||
|
||||
class RecursiveFolder extends RootFolder {
|
||||
final String name;
|
||||
final String path;
|
||||
|
||||
RecursiveFolder({
|
||||
required this.path,
|
||||
required this.name,
|
||||
required super.subfolders,
|
||||
});
|
||||
}
|
9
mobile/lib/models/folder/root_folder.model.dart
Normal file
9
mobile/lib/models/folder/root_folder.model.dart
Normal file
|
@ -0,0 +1,9 @@
|
|||
import 'package:immich_mobile/models/folder/recursive_folder.model.dart';
|
||||
|
||||
class RootFolder {
|
||||
final List<RecursiveFolder> subfolders;
|
||||
|
||||
RootFolder({
|
||||
required this.subfolders,
|
||||
});
|
||||
}
|
165
mobile/lib/pages/library/folder/folder.page.dart
Normal file
165
mobile/lib/pages/library/folder/folder.page.dart
Normal file
|
@ -0,0 +1,165 @@
|
|||
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/models/folder/recursive_folder.model.dart';
|
||||
import 'package:immich_mobile/models/folder/root_folder.model.dart';
|
||||
import 'package:immich_mobile/providers/folder.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
@RoutePage()
|
||||
class FolderPage extends HookConsumerWidget {
|
||||
final RecursiveFolder? folder;
|
||||
|
||||
const FolderPage({super.key, this.folder});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final folderState = ref.watch(folderStructureProvider);
|
||||
useEffect(
|
||||
() {
|
||||
if (folder == null) {
|
||||
ref.read(folderStructureProvider.notifier).fetchFolders();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
void onToggleSortOrder() {
|
||||
if (folder != null) {
|
||||
ref.read(folderRenderListProvider(folder!).notifier).toggleSortOrder();
|
||||
}
|
||||
ref.read(folderStructureProvider.notifier).toggleSortOrder();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(folder?.name ?? 'Root'),
|
||||
elevation: 0,
|
||||
centerTitle: false,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.swap_vert),
|
||||
onPressed: onToggleSortOrder,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: folderState.when(
|
||||
data: (rootFolder) {
|
||||
if (folder == null) {
|
||||
return FolderContent(folder: rootFolder);
|
||||
} else {
|
||||
return FolderContent(folder: folder!);
|
||||
}
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "Failed to load folder".tr(),
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
return Center(child: const Text("Failed to load folder").tr());
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FolderContent extends HookConsumerWidget {
|
||||
final RootFolder? folder;
|
||||
|
||||
const FolderContent({super.key, this.folder});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
if (folder == null) {
|
||||
return Center(child: const Text("Folder not found").tr());
|
||||
}
|
||||
|
||||
final folderRenderlist = ref.watch(folderRenderListProvider(folder!));
|
||||
useEffect(
|
||||
() {
|
||||
ref.read(folderRenderListProvider(folder!).notifier).fetchAssets();
|
||||
return null;
|
||||
},
|
||||
[folder],
|
||||
);
|
||||
|
||||
return folderRenderlist.when(
|
||||
data: (list) {
|
||||
return ListView(
|
||||
children: [
|
||||
if (folder!.subfolders.isNotEmpty)
|
||||
...folder!.subfolders.map(
|
||||
(subfolder) => ListTile(
|
||||
leading: Icon(Icons.folder, color: context.primaryColor),
|
||||
title: Text(subfolder.name),
|
||||
onTap: () =>
|
||||
context.pushRoute(FolderRoute(folder: subfolder)),
|
||||
),
|
||||
),
|
||||
if (!list.isEmpty &&
|
||||
list.allAssets != null &&
|
||||
list.allAssets!.isNotEmpty)
|
||||
...list.allAssets!.map(
|
||||
(asset) => ListTile(
|
||||
onTap: () => context.pushRoute(
|
||||
GalleryViewerRoute(
|
||||
renderList: list,
|
||||
initialIndex: list.allAssets!.indexOf(asset),
|
||||
),
|
||||
),
|
||||
leading: SizedBox(
|
||||
// height: 100,
|
||||
width: 80,
|
||||
child: ThumbnailImage(
|
||||
asset: asset,
|
||||
showStorageIndicator: false,
|
||||
),
|
||||
),
|
||||
title: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
// Remove the file extension from the file name
|
||||
// Sometimes the file name has multiple dots (.TS.mp4)
|
||||
asset.fileName.split('.').first,
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
// Display the file extension(s)
|
||||
".${asset.fileName.substring(asset.fileName.indexOf('.') + 1)}",
|
||||
),
|
||||
],
|
||||
),
|
||||
subtitle: Text(
|
||||
"${asset.exifInfo?.fileSize != null ? formatBytes(asset.exifInfo?.fileSize ?? 0) : ""} · ${DateFormat.yMMMd().format(asset.fileCreatedAt)}",
|
||||
),
|
||||
),
|
||||
),
|
||||
if (folder!.subfolders.isEmpty && list.isEmpty)
|
||||
Center(child: const Text("No subfolders or assets").tr()),
|
||||
],
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (error, stack) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: "Failed to load assets".tr(),
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
return Center(child: const Text("Failed to load assets").tr());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -78,6 +78,7 @@ class LibraryPage extends ConsumerWidget {
|
|||
PeopleCollectionCard(),
|
||||
PlacesCollectionCard(),
|
||||
LocalAlbumsCollectionCard(),
|
||||
FoldersCollectionCard(),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
@ -380,6 +381,54 @@ class PlacesCollectionCard extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class FoldersCollectionCard extends StatelessWidget {
|
||||
const FoldersCollectionCard({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final isTablet = constraints.maxWidth > 600;
|
||||
final widthFactor = isTablet ? 0.25 : 0.5;
|
||||
final size = context.width * widthFactor - 20.0;
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => context.pushRoute(FolderRoute()),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
height: size,
|
||||
width: size,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
color: context.colorScheme.secondaryContainer.withAlpha(100),
|
||||
),
|
||||
child: IgnorePointer(
|
||||
child: Icon(
|
||||
Icons.folder_outlined,
|
||||
color: context.primaryColor,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
'folders'.tr(),
|
||||
style: context.textTheme.titleSmall?.copyWith(
|
||||
color: context.colorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ActionButton extends StatelessWidget {
|
||||
final VoidCallback onPressed;
|
||||
final IconData icon;
|
||||
|
|
76
mobile/lib/providers/folder.provider.dart
Normal file
76
mobile/lib/providers/folder.provider.dart
Normal file
|
@ -0,0 +1,76 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/models/folder/root_folder.model.dart';
|
||||
import 'package:immich_mobile/services/folder.service.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class FolderStructureNotifier extends StateNotifier<AsyncValue<RootFolder>> {
|
||||
final FolderService _folderService;
|
||||
final Logger _log = Logger("FolderStructureNotifier");
|
||||
|
||||
var sortOrder = SortOrder.asc;
|
||||
|
||||
FolderStructureNotifier(this._folderService) : super(const AsyncLoading());
|
||||
|
||||
Future<void> fetchFolders() async {
|
||||
try {
|
||||
final folders = await _folderService.getFolderStructure(sortOrder);
|
||||
state = AsyncData(folders);
|
||||
} catch (e, stack) {
|
||||
_log.severe("Failed to build folder structure", e, stack);
|
||||
state = AsyncError(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleSortOrder() {
|
||||
sortOrder = sortOrder == SortOrder.asc ? SortOrder.desc : SortOrder.asc;
|
||||
return fetchFolders();
|
||||
}
|
||||
}
|
||||
|
||||
final folderStructureProvider =
|
||||
StateNotifierProvider<FolderStructureNotifier, AsyncValue<RootFolder>>(
|
||||
(ref) {
|
||||
return FolderStructureNotifier(
|
||||
ref.watch(folderServiceProvider),
|
||||
);
|
||||
});
|
||||
|
||||
class FolderRenderListNotifier extends StateNotifier<AsyncValue<RenderList>> {
|
||||
final FolderService _folderService;
|
||||
final RootFolder _folder;
|
||||
final Logger _log = Logger("FolderAssetsNotifier");
|
||||
|
||||
var sortOrder = SortOrder.asc;
|
||||
|
||||
FolderRenderListNotifier(this._folderService, this._folder)
|
||||
: super(const AsyncLoading());
|
||||
|
||||
Future<void> fetchAssets() async {
|
||||
try {
|
||||
final assets = await _folderService.getFolderAssets(_folder, sortOrder);
|
||||
|
||||
state =
|
||||
AsyncData(await RenderList.fromAssets(assets, GroupAssetsBy.none));
|
||||
} catch (e, stack) {
|
||||
_log.severe("Failed to fetch folder assets", e, stack);
|
||||
state = AsyncError(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> toggleSortOrder() {
|
||||
sortOrder = sortOrder == SortOrder.asc ? SortOrder.desc : SortOrder.asc;
|
||||
return fetchAssets();
|
||||
}
|
||||
}
|
||||
|
||||
final folderRenderListProvider = StateNotifierProvider.family<
|
||||
FolderRenderListNotifier,
|
||||
AsyncValue<RenderList>,
|
||||
RootFolder>((ref, folder) {
|
||||
return FolderRenderListNotifier(
|
||||
ref.watch(folderServiceProvider),
|
||||
folder,
|
||||
);
|
||||
});
|
44
mobile/lib/repositories/folder_api.repository.dart
Normal file
44
mobile/lib/repositories/folder_api.repository.dart
Normal file
|
@ -0,0 +1,44 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/interfaces/folder_api.interface.dart';
|
||||
import 'package:immich_mobile/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/repositories/api.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
final folderApiRepositoryProvider = Provider(
|
||||
(ref) => FolderApiRepository(
|
||||
ref.watch(apiServiceProvider).viewApi,
|
||||
),
|
||||
);
|
||||
|
||||
class FolderApiRepository extends ApiRepository
|
||||
implements IFolderApiRepository {
|
||||
final ViewApi _api;
|
||||
final Logger _log = Logger("FolderApiRepository");
|
||||
|
||||
FolderApiRepository(this._api);
|
||||
|
||||
@override
|
||||
Future<List<String>> getAllUniquePaths() async {
|
||||
try {
|
||||
final list = await _api.getUniqueOriginalPaths();
|
||||
return list ?? [];
|
||||
} catch (e, stack) {
|
||||
_log.severe("Failed to fetch unique original links", e, stack);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<Asset>> getAssetsForPath(String? path) async {
|
||||
try {
|
||||
final list = await _api.getAssetsByOriginalPath(path ?? '/');
|
||||
print("Assets for path: $path -> $list");
|
||||
return list != null ? list.map(Asset.remote).toList() : [];
|
||||
} catch (e, stack) {
|
||||
_log.severe("Failed to fetch Assets by original path", e, stack);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import 'package:immich_mobile/entities/album.entity.dart';
|
|||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/entities/logger_message.entity.dart';
|
||||
import 'package:immich_mobile/entities/user.entity.dart';
|
||||
import 'package:immich_mobile/models/folder/recursive_folder.model.dart';
|
||||
import 'package:immich_mobile/models/memories/memory.model.dart';
|
||||
import 'package:immich_mobile/models/search/search_filter.model.dart';
|
||||
import 'package:immich_mobile/models/shared_link/shared_link.model.dart';
|
||||
|
@ -16,6 +17,7 @@ import 'package:immich_mobile/pages/backup/backup_options.page.dart';
|
|||
import 'package:immich_mobile/pages/backup/failed_backup_status.page.dart';
|
||||
import 'package:immich_mobile/pages/albums/albums.page.dart';
|
||||
import 'package:immich_mobile/pages/common/native_video_viewer.page.dart';
|
||||
import 'package:immich_mobile/pages/library/folder/folder.page.dart';
|
||||
import 'package:immich_mobile/pages/library/local_albums.page.dart';
|
||||
import 'package:immich_mobile/pages/library/people/people_collection.page.dart';
|
||||
import 'package:immich_mobile/pages/library/places/places_collection.page.dart';
|
||||
|
@ -208,6 +210,11 @@ class AppRouter extends RootStackRouter {
|
|||
guards: [_authGuard, _duplicateGuard],
|
||||
transitionsBuilder: TransitionsBuilders.slideLeft,
|
||||
),
|
||||
CustomRoute(
|
||||
page: FolderRoute.page,
|
||||
guards: [_authGuard],
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
AutoRoute(
|
||||
page: PartnerDetailRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
|
|
|
@ -1175,6 +1175,40 @@ class PartnerRoute extends PageRouteInfo<void> {
|
|||
);
|
||||
}
|
||||
|
||||
/// manually written (with love) route for
|
||||
/// [FolderPage]
|
||||
class FolderRoute extends PageRouteInfo<FolderRouteArgs> {
|
||||
FolderRoute({
|
||||
RecursiveFolder? folder,
|
||||
List<PageRouteInfo>? children,
|
||||
}) : super(
|
||||
FolderRoute.name,
|
||||
args: FolderRouteArgs(folder: folder),
|
||||
initialChildren: children,
|
||||
);
|
||||
|
||||
static const String name = 'FolderRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
final args = data.argsAs<FolderRouteArgs>();
|
||||
return FolderPage(folder: args.folder);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class FolderRouteArgs {
|
||||
const FolderRouteArgs({this.folder});
|
||||
|
||||
final RecursiveFolder? folder;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'FolderRouteArgs{folder: $folder}';
|
||||
}
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [PeopleCollectionPage]
|
||||
class PeopleCollectionRoute extends PageRouteInfo<void> {
|
||||
|
|
|
@ -31,6 +31,7 @@ class ApiService implements Authentication {
|
|||
late DownloadApi downloadApi;
|
||||
late TrashApi trashApi;
|
||||
late StacksApi stacksApi;
|
||||
late ViewApi viewApi;
|
||||
|
||||
ApiService() {
|
||||
final endpoint = Store.tryGet(StoreKey.serverEndpoint);
|
||||
|
@ -64,6 +65,7 @@ class ApiService implements Authentication {
|
|||
downloadApi = DownloadApi(_apiClient);
|
||||
trashApi = TrashApi(_apiClient);
|
||||
stacksApi = StacksApi(_apiClient);
|
||||
viewApi = ViewApi(_apiClient);
|
||||
}
|
||||
|
||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||
|
|
129
mobile/lib/services/folder.service.dart
Normal file
129
mobile/lib/services/folder.service.dart
Normal file
|
@ -0,0 +1,129 @@
|
|||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/entities/asset.entity.dart';
|
||||
import 'package:immich_mobile/models/folder/recursive_folder.model.dart';
|
||||
import 'package:immich_mobile/models/folder/root_folder.model.dart';
|
||||
import 'package:immich_mobile/repositories/folder_api.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final folderServiceProvider = Provider(
|
||||
(ref) => FolderService(ref.watch(folderApiRepositoryProvider)),
|
||||
);
|
||||
|
||||
class FolderService {
|
||||
final FolderApiRepository _folderApiRepository;
|
||||
final Logger _log = Logger("FolderService");
|
||||
|
||||
FolderService(this._folderApiRepository);
|
||||
|
||||
Future<RootFolder> getFolderStructure(SortOrder order) async {
|
||||
final paths = await _folderApiRepository.getAllUniquePaths();
|
||||
|
||||
// Create folder structure
|
||||
Map<String, List<RecursiveFolder>> folderMap = {};
|
||||
|
||||
for (String fullPath in paths) {
|
||||
if (fullPath == '/') continue;
|
||||
|
||||
// Ensure the path starts with a slash
|
||||
if (!fullPath.startsWith('/')) {
|
||||
fullPath = '/$fullPath';
|
||||
}
|
||||
|
||||
List<String> segments = fullPath.split('/')
|
||||
..removeWhere((s) => s.isEmpty);
|
||||
|
||||
String currentPath = '';
|
||||
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
String parentPath = currentPath.isEmpty ? '_root_' : currentPath;
|
||||
currentPath =
|
||||
i == 0 ? '/${segments[i]}' : '$currentPath/${segments[i]}';
|
||||
|
||||
if (!folderMap.containsKey(parentPath)) {
|
||||
folderMap[parentPath] = [];
|
||||
}
|
||||
|
||||
if (!folderMap[parentPath]!.any((f) => f.name == segments[i])) {
|
||||
folderMap[parentPath]!.add(
|
||||
RecursiveFolder(
|
||||
path: parentPath == '_root_' ? '' : parentPath,
|
||||
name: segments[i],
|
||||
subfolders: [],
|
||||
),
|
||||
);
|
||||
// Sort folders based on order parameter
|
||||
folderMap[parentPath]!.sort(
|
||||
(a, b) => order == SortOrder.desc
|
||||
? b.name.compareTo(a.name)
|
||||
: a.name.compareTo(b.name),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void attachSubfolders(RecursiveFolder folder) {
|
||||
String fullPath = folder.path.isEmpty
|
||||
? '/${folder.name}'
|
||||
: '${folder.path}/${folder.name}';
|
||||
|
||||
if (folderMap.containsKey(fullPath)) {
|
||||
folder.subfolders.addAll(folderMap[fullPath]!);
|
||||
// Sort subfolders based on order parameter
|
||||
folder.subfolders.sort(
|
||||
(a, b) => order == SortOrder.desc
|
||||
? b.name.compareTo(a.name)
|
||||
: a.name.compareTo(b.name),
|
||||
);
|
||||
for (var subfolder in folder.subfolders) {
|
||||
attachSubfolders(subfolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<RecursiveFolder> rootSubfolders = folderMap['_root_'] ?? [];
|
||||
// Sort root subfolders based on order parameter
|
||||
rootSubfolders.sort(
|
||||
(a, b) => order == SortOrder.desc
|
||||
? b.name.compareTo(a.name)
|
||||
: a.name.compareTo(b.name),
|
||||
);
|
||||
|
||||
for (var folder in rootSubfolders) {
|
||||
attachSubfolders(folder);
|
||||
}
|
||||
|
||||
return RootFolder(
|
||||
subfolders: rootSubfolders,
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<Asset>> getFolderAssets(
|
||||
RootFolder folder, SortOrder order) async {
|
||||
try {
|
||||
if (folder is RecursiveFolder) {
|
||||
String fullPath =
|
||||
folder.path.isEmpty ? folder.name : '${folder.path}/${folder.name}';
|
||||
fullPath = fullPath[0] == '/' ? fullPath.substring(1) : fullPath;
|
||||
var result = await _folderApiRepository.getAssetsForPath(fullPath);
|
||||
|
||||
if (order == SortOrder.desc) {
|
||||
result.sort((a, b) => b.fileCreatedAt.compareTo(a.fileCreatedAt));
|
||||
} else {
|
||||
result.sort((a, b) => a.fileCreatedAt.compareTo(b.fileCreatedAt));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
final result = await _folderApiRepository.getAssetsForPath('/');
|
||||
return result;
|
||||
} catch (e, stack) {
|
||||
_log.severe(
|
||||
"Failed to fetch assets for folder ${folder is RecursiveFolder ? folder.name : "root"}",
|
||||
e,
|
||||
stack,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
BIN
mobile/openapi/lib/api/activities_api.dart
generated
BIN
mobile/openapi/lib/api/activities_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/albums_api.dart
generated
BIN
mobile/openapi/lib/api/albums_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/api_keys_api.dart
generated
BIN
mobile/openapi/lib/api/api_keys_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/assets_api.dart
generated
BIN
mobile/openapi/lib/api/assets_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/audit_api.dart
generated
BIN
mobile/openapi/lib/api/audit_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/authentication_api.dart
generated
BIN
mobile/openapi/lib/api/authentication_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/deprecated_api.dart
generated
BIN
mobile/openapi/lib/api/deprecated_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/download_api.dart
generated
BIN
mobile/openapi/lib/api/download_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/duplicates_api.dart
generated
BIN
mobile/openapi/lib/api/duplicates_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/faces_api.dart
generated
BIN
mobile/openapi/lib/api/faces_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/file_reports_api.dart
generated
BIN
mobile/openapi/lib/api/file_reports_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/jobs_api.dart
generated
BIN
mobile/openapi/lib/api/jobs_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/libraries_api.dart
generated
BIN
mobile/openapi/lib/api/libraries_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/map_api.dart
generated
BIN
mobile/openapi/lib/api/map_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/memories_api.dart
generated
BIN
mobile/openapi/lib/api/memories_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/notifications_api.dart
generated
BIN
mobile/openapi/lib/api/notifications_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/o_auth_api.dart
generated
BIN
mobile/openapi/lib/api/o_auth_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/partners_api.dart
generated
BIN
mobile/openapi/lib/api/partners_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/people_api.dart
generated
BIN
mobile/openapi/lib/api/people_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/search_api.dart
generated
BIN
mobile/openapi/lib/api/search_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/server_api.dart
generated
BIN
mobile/openapi/lib/api/server_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/sessions_api.dart
generated
BIN
mobile/openapi/lib/api/sessions_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/shared_links_api.dart
generated
BIN
mobile/openapi/lib/api/shared_links_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/stacks_api.dart
generated
BIN
mobile/openapi/lib/api/stacks_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/sync_api.dart
generated
BIN
mobile/openapi/lib/api/sync_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/system_config_api.dart
generated
BIN
mobile/openapi/lib/api/system_config_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/system_metadata_api.dart
generated
BIN
mobile/openapi/lib/api/system_metadata_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/tags_api.dart
generated
BIN
mobile/openapi/lib/api/tags_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/timeline_api.dart
generated
BIN
mobile/openapi/lib/api/timeline_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/trash_api.dart
generated
BIN
mobile/openapi/lib/api/trash_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/users_admin_api.dart
generated
BIN
mobile/openapi/lib/api/users_admin_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/users_api.dart
generated
BIN
mobile/openapi/lib/api/users_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/view_api.dart
generated
BIN
mobile/openapi/lib/api/view_api.dart
generated
Binary file not shown.
|
@ -5,23 +5,23 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: _fe_analyzer_shared
|
||||
sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834
|
||||
sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "72.0.0"
|
||||
version: "76.0.0"
|
||||
_macros:
|
||||
dependency: transitive
|
||||
description: dart
|
||||
source: sdk
|
||||
version: "0.3.2"
|
||||
version: "0.3.3"
|
||||
analyzer:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
name: analyzer
|
||||
sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139
|
||||
sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.7.0"
|
||||
version: "6.11.0"
|
||||
analyzer_plugin:
|
||||
dependency: "direct overridden"
|
||||
description:
|
||||
|
@ -250,10 +250,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: collection
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.0"
|
||||
connectivity_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -900,18 +900,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||
sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.5"
|
||||
version: "10.0.7"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||
sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.5"
|
||||
version: "3.0.8"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -940,10 +940,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: macros
|
||||
sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536"
|
||||
sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2-main.4"
|
||||
version: "0.1.3-main.0"
|
||||
maplibre_gl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1452,7 +1452,7 @@ packages:
|
|||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.99"
|
||||
version: "0.0.0"
|
||||
socket_io_client:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1513,10 +1513,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
version: "1.12.0"
|
||||
state_notifier:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1545,10 +1545,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.3.0"
|
||||
sync_http:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1577,10 +1577,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb"
|
||||
sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.2"
|
||||
version: "0.7.3"
|
||||
thumbhash:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1737,10 +1737,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.5"
|
||||
version: "14.3.0"
|
||||
wakelock_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1793,10 +1793,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: webdriver
|
||||
sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e"
|
||||
sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.4"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -83,7 +83,7 @@ dependencies:
|
|||
# Taken from https://github.com/Myzel394/locus/blob/445013d22ec1d759027d4303bd65b30c5c8588c8/pubspec.yaml#L105
|
||||
dependency_overrides:
|
||||
# TODO: remove once Isar is updated
|
||||
analyzer: ^6.3.0
|
||||
analyzer: ^6.0.0
|
||||
# TODO: remove once analyzer override is removed
|
||||
meta: ^1.11.0
|
||||
# TODO: remove once analyzer override is removed
|
||||
|
|
|
@ -9,7 +9,11 @@ function dart {
|
|||
wget -O native_class.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/serialization/native/native_class.mustache
|
||||
patch --no-backup-if-mismatch -u native_class.mustache <native_class.mustache.patch
|
||||
|
||||
cd ../../../../
|
||||
cd ../../
|
||||
wget -O api.mustache https://raw.githubusercontent.com/OpenAPITools/openapi-generator/$OPENAPI_GENERATOR_VERSION/modules/openapi-generator/src/main/resources/dart2/api.mustache
|
||||
patch --no-backup-if-mismatch -u api.mustache <api.mustache.patch
|
||||
|
||||
cd ../../
|
||||
npx --yes @openapitools/openapi-generator-cli generate -g dart -i ./immich-openapi-specs.json -o ../mobile/openapi -t ./templates/mobile
|
||||
|
||||
# Post generate patches
|
||||
|
|
194
open-api/templates/mobile/api.mustache
Normal file
194
open-api/templates/mobile/api.mustache
Normal file
|
@ -0,0 +1,194 @@
|
|||
{{>header}}
|
||||
{{>part_of}}
|
||||
{{#operations}}
|
||||
|
||||
class {{{classname}}} {
|
||||
{{{classname}}}([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||
|
||||
final ApiClient apiClient;
|
||||
{{#operation}}
|
||||
|
||||
{{#summary}}
|
||||
/// {{{.}}}
|
||||
{{/summary}}
|
||||
{{#notes}}
|
||||
{{#summary}}
|
||||
///
|
||||
{{/summary}}
|
||||
/// {{{notes}}}
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
{{/notes}}
|
||||
{{^notes}}
|
||||
{{#summary}}
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
{{/summary}}
|
||||
{{^summary}}
|
||||
/// Performs an HTTP '{{{httpMethod}}} {{{path}}}' operation and returns the [Response].
|
||||
{{/summary}}
|
||||
{{/notes}}
|
||||
{{#hasParams}}
|
||||
{{#summary}}
|
||||
///
|
||||
{{/summary}}
|
||||
{{^summary}}
|
||||
{{#notes}}
|
||||
///
|
||||
{{/notes}}
|
||||
{{/summary}}
|
||||
/// Parameters:
|
||||
///
|
||||
{{/hasParams}}
|
||||
{{#allParams}}
|
||||
/// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}:
|
||||
{{#description}}
|
||||
/// {{{.}}}
|
||||
{{/description}}
|
||||
{{^-last}}
|
||||
///
|
||||
{{/-last}}
|
||||
{{/allParams}}
|
||||
Future<Response> {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'{{{path}}}'{{#pathParams}}
|
||||
.replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}};
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}};
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
{{#hasQueryParams}}
|
||||
|
||||
{{#queryParams}}
|
||||
{{^required}}
|
||||
if ({{{paramName}}} != null) {
|
||||
{{/required}}
|
||||
queryParams.addAll(_queryParams('{{{collectionFormat}}}', '{{{baseName}}}', {{{paramName}}}));
|
||||
{{^required}}
|
||||
}
|
||||
{{/required}}
|
||||
{{/queryParams}}
|
||||
{{/hasQueryParams}}
|
||||
{{#hasHeaderParams}}
|
||||
|
||||
{{#headerParams}}
|
||||
{{#required}}
|
||||
headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||
{{/required}}
|
||||
{{^required}}
|
||||
if ({{{paramName}}} != null) {
|
||||
headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||
}
|
||||
{{/required}}
|
||||
{{/headerParams}}
|
||||
{{/hasHeaderParams}}
|
||||
|
||||
const contentTypes = <String>[{{#prioritizedContentTypes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/prioritizedContentTypes}}];
|
||||
|
||||
{{#isMultipart}}
|
||||
bool hasFields = false;
|
||||
final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(apiPath));
|
||||
{{#formParams}}
|
||||
{{^isFile}}
|
||||
if ({{{paramName}}} != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||
}
|
||||
{{/isFile}}
|
||||
{{#isFile}}
|
||||
if ({{{paramName}}} != null) {
|
||||
hasFields = true;
|
||||
mp.fields[r'{{{baseName}}}'] = {{{paramName}}}.field;
|
||||
mp.files.add({{{paramName}}});
|
||||
}
|
||||
{{/isFile}}
|
||||
{{/formParams}}
|
||||
if (hasFields) {
|
||||
postBody = mp;
|
||||
}
|
||||
{{/isMultipart}}
|
||||
{{^isMultipart}}
|
||||
{{#formParams}}
|
||||
{{^isFile}}
|
||||
if ({{{paramName}}} != null) {
|
||||
formParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||
}
|
||||
{{/isFile}}
|
||||
{{/formParams}}
|
||||
{{/isMultipart}}
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'{{{httpMethod}}}',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
{{#summary}}
|
||||
/// {{{.}}}
|
||||
{{/summary}}
|
||||
{{#notes}}
|
||||
{{#summary}}
|
||||
///
|
||||
{{/summary}}
|
||||
/// {{{notes}}}
|
||||
{{/notes}}
|
||||
{{#hasParams}}
|
||||
{{#summary}}
|
||||
///
|
||||
{{/summary}}
|
||||
{{^summary}}
|
||||
{{#notes}}
|
||||
///
|
||||
{{/notes}}
|
||||
{{/summary}}
|
||||
/// Parameters:
|
||||
///
|
||||
{{/hasParams}}
|
||||
{{#allParams}}
|
||||
/// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}:
|
||||
{{#description}}
|
||||
/// {{{.}}}
|
||||
{{/description}}
|
||||
{{^-last}}
|
||||
///
|
||||
{{/-last}}
|
||||
{{/allParams}}
|
||||
Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
|
||||
final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}});
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
{{#returnType}}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
{{#native_serialization}}
|
||||
{{#isArray}}
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List)
|
||||
.cast<{{{returnBaseType}}}>()
|
||||
.{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}};
|
||||
{{/isArray}}
|
||||
{{^isArray}}
|
||||
{{#isMap}}
|
||||
return {{{returnType}}}.from(await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}'),);
|
||||
{{/isMap}}
|
||||
{{^isMap}}
|
||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}',) as {{{returnType}}};
|
||||
{{/isMap}}{{/isArray}}{{/native_serialization}}
|
||||
}
|
||||
return null;
|
||||
{{/returnType}}
|
||||
}
|
||||
{{/operation}}
|
||||
}
|
||||
{{/operations}}
|
29
open-api/templates/mobile/api.mustache.patch
Normal file
29
open-api/templates/mobile/api.mustache.patch
Normal file
|
@ -0,0 +1,29 @@
|
|||
--- api.mustache 2025-01-22 05:50:25
|
||||
+++ api.mustache.modified 2025-01-22 05:52:23
|
||||
@@ -51,7 +51,7 @@
|
||||
{{/allParams}}
|
||||
Future<Response> {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
|
||||
// ignore: prefer_const_declarations
|
||||
- final path = r'{{{path}}}'{{#pathParams}}
|
||||
+ final apiPath = r'{{{path}}}'{{#pathParams}}
|
||||
.replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}};
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
@@ -90,7 +90,7 @@
|
||||
|
||||
{{#isMultipart}}
|
||||
bool hasFields = false;
|
||||
- final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(path));
|
||||
+ final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(apiPath));
|
||||
{{#formParams}}
|
||||
{{^isFile}}
|
||||
if ({{{paramName}}} != null) {
|
||||
@@ -121,7 +121,7 @@
|
||||
{{/isMultipart}}
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
- path,
|
||||
+ apiPath,
|
||||
'{{{httpMethod}}}',
|
||||
queryParams,
|
||||
postBody,
|
Loading…
Reference in a new issue