Compare commits
14 commits
main
...
feat/rotat
Author | SHA1 | Date | |
---|---|---|---|
|
f9320069b7 | ||
|
9fdc982ca6 | ||
|
14120e0c65 | ||
|
618a6cd524 | ||
|
abaaa4def6 | ||
|
7818aeebf2 | ||
|
728233bf86 | ||
|
29aa3208c6 | ||
|
2d58d486e6 | ||
|
0309bba7dd | ||
|
089fa7a550 | ||
|
2e7eb9d800 | ||
|
2b2c7979a1 | ||
|
5fca8499ec |
162 changed files with 922 additions and 6717 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -213,7 +213,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres@sha256:6dfee32131933ab4ca25a00360c3f427fdc134de56f9a90c6c9a4956b48aea85
|
image: postgres@sha256:71da05df8c4f1e1bac9b92ebfba2a0eeb183f6ac6a972fd5e55e8146e29efe9c
|
||||||
env:
|
env:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
POSTGRES_USER: postgres
|
||||||
|
|
603
cli/src/api/open-api/api.ts
generated
603
cli/src/api/open-api/api.ts
generated
|
@ -483,6 +483,12 @@ export interface AssetBulkUpdateDto {
|
||||||
* @memberof AssetBulkUpdateDto
|
* @memberof AssetBulkUpdateDto
|
||||||
*/
|
*/
|
||||||
'longitude'?: number;
|
'longitude'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'orientation'?: number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -586,142 +592,6 @@ export const AssetBulkUploadCheckResultReasonEnum = {
|
||||||
|
|
||||||
export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum];
|
export type AssetBulkUploadCheckResultReasonEnum = typeof AssetBulkUploadCheckResultReasonEnum[keyof typeof AssetBulkUploadCheckResultReasonEnum];
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
export interface AssetFaceResponseDto {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxX1': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxX2': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxY1': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxY2': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'id': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'imageHeight': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'imageWidth': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {PersonResponseDto}
|
|
||||||
* @memberof AssetFaceResponseDto
|
|
||||||
*/
|
|
||||||
'person': PersonResponseDto | null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface AssetFaceUpdateDto
|
|
||||||
*/
|
|
||||||
export interface AssetFaceUpdateDto {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<AssetFaceUpdateItem>}
|
|
||||||
* @memberof AssetFaceUpdateDto
|
|
||||||
*/
|
|
||||||
'data': Array<AssetFaceUpdateItem>;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface AssetFaceUpdateItem
|
|
||||||
*/
|
|
||||||
export interface AssetFaceUpdateItem {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof AssetFaceUpdateItem
|
|
||||||
*/
|
|
||||||
'assetId': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof AssetFaceUpdateItem
|
|
||||||
*/
|
|
||||||
'personId': string;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
export interface AssetFaceWithoutPersonResponseDto {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxX1': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxX2': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxY1': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'boundingBoxY2': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'id': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'imageHeight': number;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {number}
|
|
||||||
* @memberof AssetFaceWithoutPersonResponseDto
|
|
||||||
*/
|
|
||||||
'imageWidth': number;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -978,10 +848,10 @@ export interface AssetResponseDto {
|
||||||
'ownerId': string;
|
'ownerId': string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {Array<PersonWithFacesResponseDto>}
|
* @type {Array<PersonResponseDto>}
|
||||||
* @memberof AssetResponseDto
|
* @memberof AssetResponseDto
|
||||||
*/
|
*/
|
||||||
'people'?: Array<PersonWithFacesResponseDto>;
|
'people'?: Array<PersonResponseDto>;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -1808,19 +1678,6 @@ export interface ExifResponseDto {
|
||||||
*/
|
*/
|
||||||
'timeZone'?: string | null;
|
'timeZone'?: string | null;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface FaceDto
|
|
||||||
*/
|
|
||||||
export interface FaceDto {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof FaceDto
|
|
||||||
*/
|
|
||||||
'id': string;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -1934,8 +1791,7 @@ export const JobCommand = {
|
||||||
Start: 'start',
|
Start: 'start',
|
||||||
Pause: 'pause',
|
Pause: 'pause',
|
||||||
Resume: 'resume',
|
Resume: 'resume',
|
||||||
Empty: 'empty',
|
Empty: 'empty'
|
||||||
ClearFailed: 'clear-failed'
|
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
|
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
|
||||||
|
@ -2713,49 +2569,6 @@ export interface PersonUpdateDto {
|
||||||
*/
|
*/
|
||||||
'name'?: string;
|
'name'?: string;
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @export
|
|
||||||
* @interface PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
export interface PersonWithFacesResponseDto {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
'birthDate': string | null;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {Array<AssetFaceWithoutPersonResponseDto>}
|
|
||||||
* @memberof PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
'faces': Array<AssetFaceWithoutPersonResponseDto>;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
'id': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {boolean}
|
|
||||||
* @memberof PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
'isHidden': boolean;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
'name': string;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PersonWithFacesResponseDto
|
|
||||||
*/
|
|
||||||
'thumbnailPath': string;
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -4384,6 +4197,12 @@ export interface UpdateAssetDto {
|
||||||
* @memberof UpdateAssetDto
|
* @memberof UpdateAssetDto
|
||||||
*/
|
*/
|
||||||
'longitude'?: number;
|
'longitude'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'orientation'?: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -11541,233 +11360,6 @@ export class AuthenticationApi extends BaseAPI {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FaceApi - axios parameter creator
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
export const FaceApiAxiosParamCreator = function (configuration?: Configuration) {
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
getFaces: async (id: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
// verify required parameter 'id' is not null or undefined
|
|
||||||
assertParamExists('getFaces', 'id', id)
|
|
||||||
const localVarPath = `/face`;
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
// authentication cookie required
|
|
||||||
|
|
||||||
// authentication api_key required
|
|
||||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
|
||||||
|
|
||||||
// authentication bearer required
|
|
||||||
// http bearer authentication required
|
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
|
||||||
|
|
||||||
if (id !== undefined) {
|
|
||||||
localVarQueryParameter['id'] = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @param {FaceDto} faceDto
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
reassignFacesById: async (id: string, faceDto: FaceDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
// verify required parameter 'id' is not null or undefined
|
|
||||||
assertParamExists('reassignFacesById', 'id', id)
|
|
||||||
// verify required parameter 'faceDto' is not null or undefined
|
|
||||||
assertParamExists('reassignFacesById', 'faceDto', faceDto)
|
|
||||||
const localVarPath = `/face/{id}`
|
|
||||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
// authentication cookie required
|
|
||||||
|
|
||||||
// authentication api_key required
|
|
||||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
|
||||||
|
|
||||||
// authentication bearer required
|
|
||||||
// http bearer authentication required
|
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
localVarRequestOptions.data = serializeDataIfNeeded(faceDto, localVarRequestOptions, configuration)
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FaceApi - functional programming interface
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
export const FaceApiFp = function(configuration?: Configuration) {
|
|
||||||
const localVarAxiosParamCreator = FaceApiAxiosParamCreator(configuration)
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async getFaces(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<AssetFaceResponseDto>>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getFaces(id, options);
|
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @param {FaceDto} faceDto
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async reassignFacesById(id: string, faceDto: FaceDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFacesById(id, faceDto, options);
|
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FaceApi - factory interface
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
export const FaceApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
|
||||||
const localVarFp = FaceApiFp(configuration)
|
|
||||||
return {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {FaceApiGetFacesRequest} requestParameters Request parameters.
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
getFaces(requestParameters: FaceApiGetFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<AssetFaceResponseDto>> {
|
|
||||||
return localVarFp.getFaces(requestParameters.id, options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
|
|
||||||
return localVarFp.reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request parameters for getFaces operation in FaceApi.
|
|
||||||
* @export
|
|
||||||
* @interface FaceApiGetFacesRequest
|
|
||||||
*/
|
|
||||||
export interface FaceApiGetFacesRequest {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof FaceApiGetFaces
|
|
||||||
*/
|
|
||||||
readonly id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request parameters for reassignFacesById operation in FaceApi.
|
|
||||||
* @export
|
|
||||||
* @interface FaceApiReassignFacesByIdRequest
|
|
||||||
*/
|
|
||||||
export interface FaceApiReassignFacesByIdRequest {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof FaceApiReassignFacesById
|
|
||||||
*/
|
|
||||||
readonly id: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {FaceDto}
|
|
||||||
* @memberof FaceApiReassignFacesById
|
|
||||||
*/
|
|
||||||
readonly faceDto: FaceDto
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FaceApi - object-oriented interface
|
|
||||||
* @export
|
|
||||||
* @class FaceApi
|
|
||||||
* @extends {BaseAPI}
|
|
||||||
*/
|
|
||||||
export class FaceApi extends BaseAPI {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {FaceApiGetFacesRequest} requestParameters Request parameters.
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof FaceApi
|
|
||||||
*/
|
|
||||||
public getFaces(requestParameters: FaceApiGetFacesRequest, options?: AxiosRequestConfig) {
|
|
||||||
return FaceApiFp(this.configuration).getFaces(requestParameters.id, options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {FaceApiReassignFacesByIdRequest} requestParameters Request parameters.
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof FaceApi
|
|
||||||
*/
|
|
||||||
public reassignFacesById(requestParameters: FaceApiReassignFacesByIdRequest, options?: AxiosRequestConfig) {
|
|
||||||
return FaceApiFp(this.configuration).reassignFacesById(requestParameters.id, requestParameters.faceDto, options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JobApi - axios parameter creator
|
* JobApi - axios parameter creator
|
||||||
* @export
|
* @export
|
||||||
|
@ -13599,44 +13191,6 @@ export class PartnerApi extends BaseAPI {
|
||||||
*/
|
*/
|
||||||
export const PersonApiAxiosParamCreator = function (configuration?: Configuration) {
|
export const PersonApiAxiosParamCreator = function (configuration?: Configuration) {
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
createPerson: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
const localVarPath = `/person`;
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
// authentication cookie required
|
|
||||||
|
|
||||||
// authentication api_key required
|
|
||||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
|
||||||
|
|
||||||
// authentication bearer required
|
|
||||||
// http bearer authentication required
|
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} [withHidden]
|
* @param {boolean} [withHidden]
|
||||||
|
@ -13896,54 +13450,6 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
reassignFaces: async (id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
|
||||||
// verify required parameter 'id' is not null or undefined
|
|
||||||
assertParamExists('reassignFaces', 'id', id)
|
|
||||||
// verify required parameter 'assetFaceUpdateDto' is not null or undefined
|
|
||||||
assertParamExists('reassignFaces', 'assetFaceUpdateDto', assetFaceUpdateDto)
|
|
||||||
const localVarPath = `/person/{id}/reassign`
|
|
||||||
.replace(`{${"id"}}`, encodeURIComponent(String(id)));
|
|
||||||
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
|
||||||
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
|
||||||
let baseOptions;
|
|
||||||
if (configuration) {
|
|
||||||
baseOptions = configuration.baseOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
|
|
||||||
const localVarHeaderParameter = {} as any;
|
|
||||||
const localVarQueryParameter = {} as any;
|
|
||||||
|
|
||||||
// authentication cookie required
|
|
||||||
|
|
||||||
// authentication api_key required
|
|
||||||
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
|
|
||||||
|
|
||||||
// authentication bearer required
|
|
||||||
// http bearer authentication required
|
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
localVarHeaderParameter['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
|
||||||
localVarRequestOptions.data = serializeDataIfNeeded(assetFaceUpdateDto, localVarRequestOptions, configuration)
|
|
||||||
|
|
||||||
return {
|
|
||||||
url: toPathString(localVarUrlObj),
|
|
||||||
options: localVarRequestOptions,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PeopleUpdateDto} peopleUpdateDto
|
* @param {PeopleUpdateDto} peopleUpdateDto
|
||||||
|
@ -14046,15 +13552,6 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
|
||||||
export const PersonApiFp = function(configuration?: Configuration) {
|
export const PersonApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration)
|
const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration)
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async createPerson(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<PersonResponseDto>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.createPerson(options);
|
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {boolean} [withHidden]
|
* @param {boolean} [withHidden]
|
||||||
|
@ -14116,17 +13613,6 @@ export const PersonApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} id
|
|
||||||
* @param {AssetFaceUpdateDto} assetFaceUpdateDto
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
async reassignFaces(id: string, assetFaceUpdateDto: AssetFaceUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<PersonResponseDto>>> {
|
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.reassignFaces(id, assetFaceUpdateDto, options);
|
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PeopleUpdateDto} peopleUpdateDto
|
* @param {PeopleUpdateDto} peopleUpdateDto
|
||||||
|
@ -14158,14 +13644,6 @@ export const PersonApiFp = function(configuration?: Configuration) {
|
||||||
export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
|
||||||
const localVarFp = PersonApiFp(configuration)
|
const localVarFp = PersonApiFp(configuration)
|
||||||
return {
|
return {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
createPerson(options?: AxiosRequestConfig): AxiosPromise<PersonResponseDto> {
|
|
||||||
return localVarFp.createPerson(options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
|
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
|
||||||
|
@ -14220,15 +13698,6 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
|
||||||
mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
|
mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
|
||||||
return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath));
|
return localVarFp.mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
*/
|
|
||||||
reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<PersonResponseDto>> {
|
|
||||||
return localVarFp.reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(axios, basePath));
|
|
||||||
},
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
|
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
|
||||||
|
@ -14341,27 +13810,6 @@ export interface PersonApiMergePersonRequest {
|
||||||
readonly mergePersonDto: MergePersonDto
|
readonly mergePersonDto: MergePersonDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Request parameters for reassignFaces operation in PersonApi.
|
|
||||||
* @export
|
|
||||||
* @interface PersonApiReassignFacesRequest
|
|
||||||
*/
|
|
||||||
export interface PersonApiReassignFacesRequest {
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {string}
|
|
||||||
* @memberof PersonApiReassignFaces
|
|
||||||
*/
|
|
||||||
readonly id: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @type {AssetFaceUpdateDto}
|
|
||||||
* @memberof PersonApiReassignFaces
|
|
||||||
*/
|
|
||||||
readonly assetFaceUpdateDto: AssetFaceUpdateDto
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for updatePeople operation in PersonApi.
|
* Request parameters for updatePeople operation in PersonApi.
|
||||||
* @export
|
* @export
|
||||||
|
@ -14404,16 +13852,6 @@ export interface PersonApiUpdatePersonRequest {
|
||||||
* @extends {BaseAPI}
|
* @extends {BaseAPI}
|
||||||
*/
|
*/
|
||||||
export class PersonApi extends BaseAPI {
|
export class PersonApi extends BaseAPI {
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof PersonApi
|
|
||||||
*/
|
|
||||||
public createPerson(options?: AxiosRequestConfig) {
|
|
||||||
return PersonApiFp(this.configuration).createPerson(options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
|
* @param {PersonApiGetAllPeopleRequest} requestParameters Request parameters.
|
||||||
|
@ -14480,17 +13918,6 @@ export class PersonApi extends BaseAPI {
|
||||||
return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath));
|
return PersonApiFp(this.configuration).mergePerson(requestParameters.id, requestParameters.mergePersonDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {PersonApiReassignFacesRequest} requestParameters Request parameters.
|
|
||||||
* @param {*} [options] Override http request option.
|
|
||||||
* @throws {RequiredError}
|
|
||||||
* @memberof PersonApi
|
|
||||||
*/
|
|
||||||
public reassignFaces(requestParameters: PersonApiReassignFacesRequest, options?: AxiosRequestConfig) {
|
|
||||||
return PersonApiFp(this.configuration).reassignFaces(requestParameters.id, requestParameters.assetFaceUpdateDto, options).then((request) => request(this.axios, this.basePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
|
* @param {PersonApiUpdatePeopleRequest} requestParameters Request parameters.
|
||||||
|
|
|
@ -112,7 +112,7 @@ services:
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -70,7 +70,7 @@ services:
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -23,7 +23,7 @@ services:
|
||||||
- database
|
- database
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
command: -c fsync=off
|
command: -c fsync=off
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|
|
@ -74,7 +74,7 @@ services:
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: postgres:14-alpine@sha256:6a0e35296341e676fe6bd8d236c72afffe2dfe3d7eb9c2405c0f3fc04500cd07
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.11-bookworm@sha256:ba7a7ac30c38e119c4304f98ef0e188f90f4f67a958bb6899da9defb99bfb471 as builder
|
FROM python:3.11-bookworm@sha256:47c1829f72432c33609b3095259843a88c7ffc42cc9dbb55c43f2e7bbe46ca58 as builder
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
|
@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
|
||||||
COPY poetry.lock pyproject.toml ./
|
COPY poetry.lock pyproject.toml ./
|
||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:cc758519481092eb5a4a5ab0c1b303e288880d59afc601958d19e95b300bc86b
|
FROM python:3.11-slim-bookworm@sha256:8f82989e563d0dbad057a874a96438a360978c148e34f36c1db8d2d61b5fd6f0
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM mambaorg/micromamba:bookworm-slim@sha256:e296d47be09fc5d260eba9b191f60496f028a4f3ec41e8a14d48c0bae2c60244 as builder
|
FROM mambaorg/micromamba:bookworm-slim@sha256:d20c621f3ae42f50f380166b15b6c88b14fa62ab6ea188f2cef33451d64057c7 as builder
|
||||||
|
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
TRANSFORMERS_CACHE=/cache \
|
TRANSFORMERS_CACHE=/cache \
|
||||||
|
|
|
@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
|
||||||
distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80
|
distributionSha256Sum=518a863631feb7452b8f1b3dc2aaee5f388355cc3421bbd0275fbeadd77e84b2
|
|
@ -144,8 +144,6 @@
|
||||||
"control_bottom_app_bar_stack": "Stack",
|
"control_bottom_app_bar_stack": "Stack",
|
||||||
"control_bottom_app_bar_unarchive": "Unarchive",
|
"control_bottom_app_bar_unarchive": "Unarchive",
|
||||||
"control_bottom_app_bar_upload": "Upload",
|
"control_bottom_app_bar_upload": "Upload",
|
||||||
"control_bottom_app_bar_edit_time": "Edit Date & Time",
|
|
||||||
"control_bottom_app_bar_edit_location": "Edit Location",
|
|
||||||
"create_album_page_untitled": "Untitled",
|
"create_album_page_untitled": "Untitled",
|
||||||
"create_shared_album_page_create": "Create",
|
"create_shared_album_page_create": "Create",
|
||||||
"create_shared_album_page_share": "Share",
|
"create_shared_album_page_share": "Share",
|
||||||
|
@ -167,7 +165,6 @@
|
||||||
"exif_bottom_sheet_description": "Add Description...",
|
"exif_bottom_sheet_description": "Add Description...",
|
||||||
"exif_bottom_sheet_details": "DETAILS",
|
"exif_bottom_sheet_details": "DETAILS",
|
||||||
"exif_bottom_sheet_location": "LOCATION",
|
"exif_bottom_sheet_location": "LOCATION",
|
||||||
"exif_bottom_sheet_location_add": "Add a location",
|
|
||||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||||
"experimental_settings_subtitle": "Use at your own risk!",
|
"experimental_settings_subtitle": "Use at your own risk!",
|
||||||
|
@ -464,18 +461,5 @@
|
||||||
"viewer_remove_from_stack": "Remove from Stack",
|
"viewer_remove_from_stack": "Remove from Stack",
|
||||||
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
"viewer_stack_use_as_main_asset": "Use as Main Asset",
|
||||||
"viewer_unstack": "Un-Stack",
|
"viewer_unstack": "Un-Stack",
|
||||||
"scaffold_body_error_occurred": "Error occurred",
|
"scaffold_body_error_occured": "Error occured"
|
||||||
"edit_date_time_dialog_date_time": "Date and Time",
|
|
||||||
"edit_date_time_dialog_timezone": "Timezone",
|
|
||||||
"action_common_cancel": "Cancel",
|
|
||||||
"action_common_update": "Update",
|
|
||||||
"edit_location_dialog_title": "Location",
|
|
||||||
"map_location_picker_page_use_location": "Use this location",
|
|
||||||
"location_picker_choose_on_map": "Choose on map",
|
|
||||||
"location_picker_latitude": "Latitude",
|
|
||||||
"location_picker_latitude_hint": "Enter your latitude here",
|
|
||||||
"location_picker_latitude_error": "Enter a valid latitude",
|
|
||||||
"location_picker_longitude": "Longitude",
|
|
||||||
"location_picker_longitude_hint": "Enter your longitude here",
|
|
||||||
"location_picker_longitude_error": "Enter a valid longitude"
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
|
||||||
import 'package:timezone/timezone.dart';
|
|
||||||
|
|
||||||
extension TZExtension on Asset {
|
|
||||||
/// Returns the created time of the asset from the exif info (if available) or from
|
|
||||||
/// the fileCreatedAt field, adjusted to the timezone value from the exif info along with
|
|
||||||
/// the timezone offset in [Duration]
|
|
||||||
(DateTime, Duration) getTZAdjustedTimeAndOffset() {
|
|
||||||
DateTime dt = fileCreatedAt.toLocal();
|
|
||||||
if (exifInfo?.dateTimeOriginal != null) {
|
|
||||||
dt = exifInfo!.dateTimeOriginal!;
|
|
||||||
if (exifInfo?.timeZone != null) {
|
|
||||||
dt = dt.toUtc();
|
|
||||||
try {
|
|
||||||
final location = getLocation(exifInfo!.timeZone!);
|
|
||||||
dt = TZDateTime.from(dt, location);
|
|
||||||
} on LocationNotFoundException {
|
|
||||||
RegExp re = RegExp(
|
|
||||||
r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$',
|
|
||||||
caseSensitive: false,
|
|
||||||
);
|
|
||||||
final m = re.firstMatch(exifInfo!.timeZone!);
|
|
||||||
if (m != null) {
|
|
||||||
final duration = Duration(
|
|
||||||
hours: int.parse(m.group(1) ?? '0'),
|
|
||||||
minutes: int.parse(m.group(2) ?? '0'),
|
|
||||||
);
|
|
||||||
dt = dt.add(duration);
|
|
||||||
return (dt, duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (dt, dt.timeZoneOffset);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
extension TZOffsetExtension on Duration {
|
|
||||||
String formatAsOffset() =>
|
|
||||||
"${isNegative ? '-' : '+'}${inHours.abs().toString().padLeft(2, '0')}:${inMinutes.abs().remainder(60).toString().padLeft(2, '0')}";
|
|
||||||
}
|
|
|
@ -95,11 +95,7 @@ class ActivityStatisticsNotifier extends StateNotifier<int> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fetchStatistics() async {
|
Future<void> fetchStatistics() async {
|
||||||
final count =
|
state = await _activityService.getStatistics(albumId, assetId: assetId);
|
||||||
await _activityService.getStatistics(albumId, assetId: assetId);
|
|
||||||
if (mounted) {
|
|
||||||
state = count;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addActivity() async {
|
Future<void> addActivity() async {
|
||||||
|
|
|
@ -68,46 +68,46 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: album.thumbnail.value == null
|
child: album.thumbnail.value == null
|
||||||
? buildEmptyThumbnail()
|
? buildEmptyThumbnail()
|
||||||
: buildAlbumThumbnail(),
|
: buildAlbumThumbnail(),
|
||||||
),
|
),
|
||||||
Expanded(
|
Padding(
|
||||||
child: Padding(
|
padding: const EdgeInsets.only(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
left: 8.0,
|
||||||
child: Column(
|
right: 8.0,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
),
|
||||||
children: [
|
child: Column(
|
||||||
Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
album.name,
|
children: [
|
||||||
overflow: TextOverflow.ellipsis,
|
Text(
|
||||||
style: const TextStyle(
|
album.name,
|
||||||
fontWeight: FontWeight.bold,
|
style: const TextStyle(
|
||||||
),
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
Row(
|
),
|
||||||
mainAxisSize: MainAxisSize.min,
|
Row(
|
||||||
children: [
|
mainAxisSize: MainAxisSize.min,
|
||||||
Text(
|
children: [
|
||||||
album.assetCount == 1
|
Text(
|
||||||
? 'album_thumbnail_card_item'
|
album.assetCount == 1
|
||||||
: 'album_thumbnail_card_items',
|
? 'album_thumbnail_card_item'
|
||||||
style: const TextStyle(
|
: 'album_thumbnail_card_items',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
).tr(args: ['${album.assetCount}']),
|
||||||
|
if (album.shared)
|
||||||
|
const Text(
|
||||||
|
'album_thumbnail_card_shared',
|
||||||
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
),
|
),
|
||||||
).tr(args: ['${album.assetCount}']),
|
).tr(),
|
||||||
if (album.shared)
|
],
|
||||||
const Text(
|
),
|
||||||
'album_thumbnail_card_shared',
|
],
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -135,56 +135,49 @@ class LibraryPage extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildCreateAlbumButton() {
|
Widget buildCreateAlbumButton() {
|
||||||
return LayoutBuilder(
|
return GestureDetector(
|
||||||
builder: (context, constraints) {
|
onTap: () {
|
||||||
var cardSize = constraints.maxWidth;
|
context.autoPush(CreateAlbumRoute(isSharedAlbum: false));
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
context.autoPush(CreateAlbumRoute(isSharedAlbum: false));
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.only(bottom: 32), // Adjust padding to suit
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Container(
|
|
||||||
width: cardSize,
|
|
||||||
height: cardSize,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border.all(
|
|
||||||
color: isDarkTheme
|
|
||||||
? const Color.fromARGB(255, 53, 53, 53)
|
|
||||||
: const Color.fromARGB(255, 203, 203, 203),
|
|
||||||
),
|
|
||||||
color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
child: Center(
|
|
||||||
child: Icon(
|
|
||||||
Icons.add_rounded,
|
|
||||||
size: 28,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 8.0,
|
|
||||||
bottom: 16,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'library_page_new_album',
|
|
||||||
style: context.textTheme.labelLarge,
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 32),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: isDarkTheme
|
||||||
|
? const Color.fromARGB(255, 53, 53, 53)
|
||||||
|
: const Color.fromARGB(255, 203, 203, 203),
|
||||||
|
),
|
||||||
|
color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
|
||||||
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Icon(
|
||||||
|
Icons.add_rounded,
|
||||||
|
size: 28,
|
||||||
|
color: context.primaryColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 8.0,
|
||||||
|
bottom: 16,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'library_page_new_album',
|
||||||
|
style: context.textTheme.labelLarge,
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,14 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
import 'package:flutter_map/flutter_map.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/asset_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/duration_extensions.dart';
|
import 'package:timezone/timezone.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/description_input.dart';
|
||||||
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
|
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
import 'package:immich_mobile/shared/models/exif_info.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||||
import 'package:immich_mobile/utils/selection_handlers.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
@ -22,84 +21,111 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
|
|
||||||
const ExifBottomSheet({Key? key, required this.asset}) : super(key: key);
|
const ExifBottomSheet({Key? key, required this.asset}) : super(key: key);
|
||||||
|
|
||||||
|
bool hasCoordinates(ExifInfo? exifInfo) =>
|
||||||
|
exifInfo != null &&
|
||||||
|
exifInfo.latitude != null &&
|
||||||
|
exifInfo.longitude != null &&
|
||||||
|
exifInfo.latitude != 0 &&
|
||||||
|
exifInfo.longitude != 0;
|
||||||
|
|
||||||
|
String formatTimeZone(Duration d) =>
|
||||||
|
"GMT${d.isNegative ? '-' : '+'}${d.inHours.abs().toString().padLeft(2, '0')}:${d.inMinutes.abs().remainder(60).toString().padLeft(2, '0')}";
|
||||||
|
|
||||||
|
String get formattedDateTime {
|
||||||
|
DateTime dt = asset.fileCreatedAt.toLocal();
|
||||||
|
String? timeZone;
|
||||||
|
if (asset.exifInfo?.dateTimeOriginal != null) {
|
||||||
|
dt = asset.exifInfo!.dateTimeOriginal!;
|
||||||
|
if (asset.exifInfo?.timeZone != null) {
|
||||||
|
dt = dt.toUtc();
|
||||||
|
try {
|
||||||
|
final location = getLocation(asset.exifInfo!.timeZone!);
|
||||||
|
dt = TZDateTime.from(dt, location);
|
||||||
|
} on LocationNotFoundException {
|
||||||
|
RegExp re = RegExp(
|
||||||
|
r'^utc(?:([+-]\d{1,2})(?::(\d{2}))?)?$',
|
||||||
|
caseSensitive: false,
|
||||||
|
);
|
||||||
|
final m = re.firstMatch(asset.exifInfo!.timeZone!);
|
||||||
|
if (m != null) {
|
||||||
|
final duration = Duration(
|
||||||
|
hours: int.parse(m.group(1) ?? '0'),
|
||||||
|
minutes: int.parse(m.group(2) ?? '0'),
|
||||||
|
);
|
||||||
|
dt = dt.add(duration);
|
||||||
|
timeZone = formatTimeZone(duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final date = DateFormat.yMMMEd().format(dt);
|
||||||
|
final time = DateFormat.jm().format(dt);
|
||||||
|
timeZone ??= formatTimeZone(dt.timeZoneOffset);
|
||||||
|
|
||||||
|
return '$date • $time $timeZone';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uri?> _createCoordinatesUri(ExifInfo? exifInfo) async {
|
||||||
|
if (!hasCoordinates(exifInfo)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final double latitude = exifInfo!.latitude!;
|
||||||
|
final double longitude = exifInfo.longitude!;
|
||||||
|
|
||||||
|
const zoomLevel = 16;
|
||||||
|
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
Uri uri = Uri(
|
||||||
|
scheme: 'geo',
|
||||||
|
host: '$latitude,$longitude',
|
||||||
|
queryParameters: {
|
||||||
|
'z': '$zoomLevel',
|
||||||
|
'q': '$latitude,$longitude($formattedDateTime)',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
var params = {
|
||||||
|
'll': '$latitude,$longitude',
|
||||||
|
'q': formattedDateTime,
|
||||||
|
'z': '$zoomLevel',
|
||||||
|
};
|
||||||
|
Uri uri = Uri.https('maps.apple.com', '/', params);
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uri(
|
||||||
|
scheme: 'https',
|
||||||
|
host: 'openstreetmap.org',
|
||||||
|
queryParameters: {'mlat': '$latitude', 'mlon': '$longitude'},
|
||||||
|
fragment: 'map=$zoomLevel/$latitude/$longitude',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final assetWithExif = ref.watch(assetDetailProvider(asset));
|
final assetWithExif = ref.watch(assetDetailProvider(asset));
|
||||||
final exifInfo = (assetWithExif.value ?? asset).exifInfo;
|
final exifInfo = (assetWithExif.value ?? asset).exifInfo;
|
||||||
var textColor = context.isDarkTheme ? Colors.white : Colors.black;
|
var textColor = context.isDarkTheme ? Colors.white : Colors.black;
|
||||||
|
|
||||||
bool hasCoordinates() =>
|
|
||||||
exifInfo != null &&
|
|
||||||
exifInfo.latitude != null &&
|
|
||||||
exifInfo.longitude != null &&
|
|
||||||
exifInfo.latitude != 0 &&
|
|
||||||
exifInfo.longitude != 0;
|
|
||||||
|
|
||||||
String formattedDateTime() {
|
|
||||||
final (dt, timeZone) =
|
|
||||||
(assetWithExif.value ?? asset).getTZAdjustedTimeAndOffset();
|
|
||||||
final date = DateFormat.yMMMEd().format(dt);
|
|
||||||
final time = DateFormat.jm().format(dt);
|
|
||||||
|
|
||||||
return '$date • $time GMT${timeZone.formatAsOffset()}';
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Uri?> createCoordinatesUri() async {
|
|
||||||
if (!hasCoordinates()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final double latitude = exifInfo!.latitude!;
|
|
||||||
final double longitude = exifInfo.longitude!;
|
|
||||||
|
|
||||||
const zoomLevel = 16;
|
|
||||||
|
|
||||||
if (Platform.isAndroid) {
|
|
||||||
Uri uri = Uri(
|
|
||||||
scheme: 'geo',
|
|
||||||
host: '$latitude,$longitude',
|
|
||||||
queryParameters: {
|
|
||||||
'z': '$zoomLevel',
|
|
||||||
'q': '$latitude,$longitude($formattedDateTime)',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (await canLaunchUrl(uri)) {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
} else if (Platform.isIOS) {
|
|
||||||
var params = {
|
|
||||||
'll': '$latitude,$longitude',
|
|
||||||
'q': formattedDateTime,
|
|
||||||
'z': '$zoomLevel',
|
|
||||||
};
|
|
||||||
Uri uri = Uri.https('maps.apple.com', '/', params);
|
|
||||||
if (await canLaunchUrl(uri)) {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Uri(
|
|
||||||
scheme: 'https',
|
|
||||||
host: 'openstreetmap.org',
|
|
||||||
queryParameters: {'mlat': '$latitude', 'mlon': '$longitude'},
|
|
||||||
fragment: 'map=$zoomLevel/$latitude/$longitude',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
buildMap() {
|
buildMap() {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return MapThumbnail(
|
return MapThumbnail(
|
||||||
showAttribution: false,
|
|
||||||
coords: LatLng(
|
coords: LatLng(
|
||||||
exifInfo?.latitude ?? 0,
|
exifInfo?.latitude ?? 0,
|
||||||
exifInfo?.longitude ?? 0,
|
exifInfo?.longitude ?? 0,
|
||||||
),
|
),
|
||||||
height: 150,
|
height: 150,
|
||||||
width: constraints.maxWidth,
|
zoom: 16.0,
|
||||||
zoom: 12.0,
|
|
||||||
markers: [
|
markers: [
|
||||||
Marker(
|
Marker(
|
||||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||||
|
@ -113,7 +139,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
onTap: (tapPosition, latLong) async {
|
onTap: (tapPosition, latLong) async {
|
||||||
Uri? uri = await createCoordinatesUri();
|
Uri? uri = await _createCoordinatesUri(exifInfo);
|
||||||
|
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -155,26 +181,8 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
|
|
||||||
buildLocation() {
|
buildLocation() {
|
||||||
// Guard no lat/lng
|
// Guard no lat/lng
|
||||||
if (!hasCoordinates()) {
|
if (!hasCoordinates(exifInfo)) {
|
||||||
return asset.isRemote
|
return Container();
|
||||||
? ListTile(
|
|
||||||
minLeadingWidth: 0,
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
leading: const Icon(Icons.location_on),
|
|
||||||
title: Text(
|
|
||||||
"exif_bottom_sheet_location_add",
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
onTap: () => handleEditLocation(
|
|
||||||
ref,
|
|
||||||
context,
|
|
||||||
[assetWithExif.value ?? asset],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: const SizedBox.shrink();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
@ -183,29 +191,13 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
Column(
|
Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
"exif_bottom_sheet_location",
|
||||||
children: [
|
style: context.textTheme.labelMedium?.copyWith(
|
||||||
Text(
|
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
||||||
"exif_bottom_sheet_location",
|
fontWeight: FontWeight.w600,
|
||||||
style: context.textTheme.labelMedium?.copyWith(
|
),
|
||||||
color:
|
).tr(),
|
||||||
context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
if (asset.isRemote)
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => handleEditLocation(
|
|
||||||
ref,
|
|
||||||
context,
|
|
||||||
[assetWithExif.value ?? asset],
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.edit_outlined),
|
|
||||||
iconSize: 20,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
buildMap(),
|
buildMap(),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
|
@ -241,27 +233,12 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDate() {
|
buildDate() {
|
||||||
return Row(
|
return Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
formattedDateTime,
|
||||||
children: [
|
style: const TextStyle(
|
||||||
Text(
|
fontWeight: FontWeight.bold,
|
||||||
formattedDateTime(),
|
fontSize: 14,
|
||||||
style: const TextStyle(
|
),
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (asset.isRemote)
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => handleEditDateTime(
|
|
||||||
ref,
|
|
||||||
context,
|
|
||||||
[assetWithExif.value ?? asset],
|
|
||||||
),
|
|
||||||
icon: const Icon(Icons.edit_outlined),
|
|
||||||
iconSize: 20,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -386,7 +363,7 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: hasCoordinates() ? 5 : 0,
|
flex: hasCoordinates(exifInfo) ? 5 : 0,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
child: buildLocation(),
|
child: buildLocation(),
|
||||||
|
@ -425,8 +402,9 @@ class ExifBottomSheet extends HookConsumerWidget {
|
||||||
child: CircularProgressIndicator.adaptive(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 8.0),
|
||||||
buildLocation(),
|
buildLocation(),
|
||||||
SizedBox(height: hasCoordinates() ? 16.0 : 6.0),
|
SizedBox(height: hasCoordinates(exifInfo) ? 16.0 : 0.0),
|
||||||
buildDetail(),
|
buildDetail(),
|
||||||
const SizedBox(height: 50),
|
const SizedBox(height: 50),
|
||||||
],
|
],
|
||||||
|
|
|
@ -795,7 +795,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||||
tag: isFromDto
|
tag: isFromDto
|
||||||
? '${a.remoteId}-$heroOffset'
|
? '${a.remoteId}-$heroOffset'
|
||||||
: a.id + heroOffset,
|
: a.id + heroOffset,
|
||||||
transitionOnUserGestures: true,
|
|
||||||
),
|
),
|
||||||
filterQuality: FilterQuality.high,
|
filterQuality: FilterQuality.high,
|
||||||
tightMode: true,
|
tightMode: true,
|
||||||
|
|
|
@ -375,8 +375,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||||
await _getBackupAlbumsInfo();
|
await _getBackupAlbumsInfo();
|
||||||
await updateServerInfo();
|
await updateServerInfo();
|
||||||
await _updateBackupAssetCount();
|
await _updateBackupAssetCount();
|
||||||
} else {
|
|
||||||
log.warning("cannot get backup info - background backup is in progress!");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,6 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
final void Function() onCreateNewAlbum;
|
final void Function() onCreateNewAlbum;
|
||||||
final void Function() onUpload;
|
final void Function() onUpload;
|
||||||
final void Function() onStack;
|
final void Function() onStack;
|
||||||
final void Function() onEditTime;
|
|
||||||
final void Function() onEditLocation;
|
|
||||||
|
|
||||||
final List<Album> albums;
|
final List<Album> albums;
|
||||||
final List<Album> sharedAlbums;
|
final List<Album> sharedAlbums;
|
||||||
|
@ -39,8 +37,6 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
required this.onCreateNewAlbum,
|
required this.onCreateNewAlbum,
|
||||||
required this.onUpload,
|
required this.onUpload,
|
||||||
required this.onStack,
|
required this.onStack,
|
||||||
required this.onEditTime,
|
|
||||||
required this.onEditLocation,
|
|
||||||
this.selectionAssetState = const SelectionAssetState(),
|
this.selectionAssetState = const SelectionAssetState(),
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
@ -78,18 +74,6 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||||
label: "control_bottom_app_bar_favorite".tr(),
|
label: "control_bottom_app_bar_favorite".tr(),
|
||||||
onPressed: enabled ? onFavorite : null,
|
onPressed: enabled ? onFavorite : null,
|
||||||
),
|
),
|
||||||
if (hasRemote)
|
|
||||||
ControlBoxButton(
|
|
||||||
iconData: Icons.edit_calendar_outlined,
|
|
||||||
label: "control_bottom_app_bar_edit_time".tr(),
|
|
||||||
onPressed: enabled ? onEditTime : null,
|
|
||||||
),
|
|
||||||
if (hasRemote)
|
|
||||||
ControlBoxButton(
|
|
||||||
iconData: Icons.edit_location_alt_outlined,
|
|
||||||
label: "control_bottom_app_bar_edit_location".tr(),
|
|
||||||
onPressed: enabled ? onEditLocation : null,
|
|
||||||
),
|
|
||||||
ControlBoxButton(
|
ControlBoxButton(
|
||||||
iconData: Icons.delete_outline_rounded,
|
iconData: Icons.delete_outline_rounded,
|
||||||
label: "control_bottom_app_bar_delete".tr(),
|
label: "control_bottom_app_bar_delete".tr(),
|
||||||
|
|
|
@ -213,10 +213,10 @@ class HomePage extends HookConsumerWidget {
|
||||||
processing.value = true;
|
processing.value = true;
|
||||||
selectionEnabledHook.value = false;
|
selectionEnabledHook.value = false;
|
||||||
try {
|
try {
|
||||||
ref.read(manualUploadProvider.notifier).uploadAssets(
|
ref.read(manualUploadProvider.notifier).uploadAssets(
|
||||||
context,
|
context,
|
||||||
selection.value.where((a) => a.storage == AssetState.local),
|
selection.value.where((a) => a.storage == AssetState.local),
|
||||||
);
|
);
|
||||||
} finally {
|
} finally {
|
||||||
processing.value = false;
|
processing.value = false;
|
||||||
}
|
}
|
||||||
|
@ -312,34 +312,6 @@ class HomePage extends HookConsumerWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onEditTime() async {
|
|
||||||
try {
|
|
||||||
final remoteAssets = ownedRemoteSelection(
|
|
||||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
|
||||||
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
|
||||||
);
|
|
||||||
if (remoteAssets.isNotEmpty) {
|
|
||||||
handleEditDateTime(ref, context, remoteAssets.toList());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
selectionEnabledHook.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onEditLocation() async {
|
|
||||||
try {
|
|
||||||
final remoteAssets = ownedRemoteSelection(
|
|
||||||
localErrorMessage: 'home_page_favorite_err_local'.tr(),
|
|
||||||
ownerErrorMessage: 'home_page_favorite_err_partner'.tr(),
|
|
||||||
);
|
|
||||||
if (remoteAssets.isNotEmpty) {
|
|
||||||
handleEditLocation(ref, context, remoteAssets.toList());
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
selectionEnabledHook.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> refreshAssets() async {
|
Future<void> refreshAssets() async {
|
||||||
final fullRefresh = refreshCount.value > 0;
|
final fullRefresh = refreshCount.value > 0;
|
||||||
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
|
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
|
||||||
|
@ -439,8 +411,6 @@ class HomePage extends HookConsumerWidget {
|
||||||
enabled: !processing.value,
|
enabled: !processing.value,
|
||||||
selectionAssetState: selectionAssetState.value,
|
selectionAssetState: selectionAssetState.value,
|
||||||
onStack: onStack,
|
onStack: onStack,
|
||||||
onEditTime: onEditTime,
|
|
||||||
onEditLocation: onEditLocation,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -5,8 +5,6 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/user.dart';
|
import 'package:immich_mobile/shared/models/user.dart';
|
||||||
|
@ -23,7 +21,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||||
AuthenticationNotifier(
|
AuthenticationNotifier(
|
||||||
this._apiService,
|
this._apiService,
|
||||||
this._db,
|
this._db,
|
||||||
this._ref,
|
|
||||||
) : super(
|
) : super(
|
||||||
AuthenticationState(
|
AuthenticationState(
|
||||||
deviceId: "",
|
deviceId: "",
|
||||||
|
@ -39,8 +36,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||||
|
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final Isar _db;
|
final Isar _db;
|
||||||
final StateNotifierProviderRef<AuthenticationNotifier, AuthenticationState>
|
|
||||||
_ref;
|
|
||||||
final _log = Logger("AuthenticationNotifier");
|
final _log = Logger("AuthenticationNotifier");
|
||||||
|
|
||||||
Future<bool> login(
|
Future<bool> login(
|
||||||
|
@ -116,8 +111,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||||
Store.delete(StoreKey.currentUser),
|
Store.delete(StoreKey.currentUser),
|
||||||
Store.delete(StoreKey.accessToken),
|
Store.delete(StoreKey.accessToken),
|
||||||
]);
|
]);
|
||||||
_ref.invalidate(albumProvider);
|
|
||||||
_ref.invalidate(sharedAlbumProvider);
|
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
deviceId: "",
|
deviceId: "",
|
||||||
|
@ -229,6 +222,5 @@ final authenticationProvider =
|
||||||
return AuthenticationNotifier(
|
return AuthenticationNotifier(
|
||||||
ref.watch(apiServiceProvider),
|
ref.watch(apiServiceProvider),
|
||||||
ref.watch(dbProvider),
|
ref.watch(dbProvider),
|
||||||
ref,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/modules/map/providers/map_state.provider.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
|
||||||
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
class MapLocationPickerPage extends HookConsumerWidget {
|
|
||||||
final LatLng? initialLatLng;
|
|
||||||
|
|
||||||
const MapLocationPickerPage({super.key, this.initialLatLng});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final selectedLatLng = useState<LatLng>(initialLatLng ?? LatLng(0, 0));
|
|
||||||
final isDarkTheme =
|
|
||||||
ref.watch(mapStateNotifier.select((state) => state.isDarkTheme));
|
|
||||||
final isLoading =
|
|
||||||
ref.watch(mapStateNotifier.select((state) => state.isLoading));
|
|
||||||
final maxZoom = ref.read(mapStateNotifier.notifier).maxZoom;
|
|
||||||
|
|
||||||
return Theme(
|
|
||||||
// Override app theme based on map theme
|
|
||||||
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
|
|
||||||
child: Scaffold(
|
|
||||||
extendBodyBehindAppBar: true,
|
|
||||||
body: Stack(
|
|
||||||
children: [
|
|
||||||
if (!isLoading)
|
|
||||||
FlutterMap(
|
|
||||||
options: MapOptions(
|
|
||||||
maxBounds:
|
|
||||||
LatLngBounds(LatLng(-90, -180.0), LatLng(90.0, 180.0)),
|
|
||||||
interactiveFlags: InteractiveFlag.doubleTapZoom |
|
|
||||||
InteractiveFlag.drag |
|
|
||||||
InteractiveFlag.flingAnimation |
|
|
||||||
InteractiveFlag.pinchMove |
|
|
||||||
InteractiveFlag.pinchZoom,
|
|
||||||
center: LatLng(20, 20),
|
|
||||||
zoom: 2,
|
|
||||||
minZoom: 1,
|
|
||||||
maxZoom: maxZoom,
|
|
||||||
onTap: (tapPosition, point) => selectedLatLng.value = point,
|
|
||||||
),
|
|
||||||
children: [
|
|
||||||
ref.read(mapStateNotifier.notifier).getTileLayer(),
|
|
||||||
MarkerLayer(
|
|
||||||
markers: [
|
|
||||||
Marker(
|
|
||||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
|
||||||
point: selectedLatLng.value,
|
|
||||||
builder: (ctx) => const Image(
|
|
||||||
image: AssetImage('assets/location-pin.png'),
|
|
||||||
),
|
|
||||||
height: 40,
|
|
||||||
width: 40,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (isLoading)
|
|
||||||
Positioned(
|
|
||||||
top: context.height * 0.35,
|
|
||||||
left: context.width * 0.425,
|
|
||||||
child: const ImmichLoadingIndicator(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
bottomSheet: BottomSheet(
|
|
||||||
onClosing: () {},
|
|
||||||
builder: (context) => SizedBox(
|
|
||||||
height: 150,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
"${selectedLatLng.value.latitude.toStringAsFixed(4)}, ${selectedLatLng.value.longitude.toStringAsFixed(4)}",
|
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
|
||||||
color: context.primaryColor,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => context.autoPop(selectedLatLng.value),
|
|
||||||
child: const Text("map_location_picker_page_use_location")
|
|
||||||
.tr(),
|
|
||||||
),
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () => context.autoPop(),
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: context.colorScheme.error,
|
|
||||||
),
|
|
||||||
child: const Text("action_common_cancel").tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:flutter_map/plugin_api.dart';
|
import 'package:flutter_map/plugin_api.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/map/providers/map_state.provider.dart';
|
import 'package:immich_mobile/modules/map/providers/map_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/map/utils/map_controller_hook.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
|
@ -14,15 +12,13 @@ class MapThumbnail extends HookConsumerWidget {
|
||||||
final double zoom;
|
final double zoom;
|
||||||
final List<Marker> markers;
|
final List<Marker> markers;
|
||||||
final double height;
|
final double height;
|
||||||
final double width;
|
|
||||||
final bool showAttribution;
|
final bool showAttribution;
|
||||||
final bool isDarkTheme;
|
final bool isDarkTheme;
|
||||||
|
|
||||||
const MapThumbnail({
|
const MapThumbnail({
|
||||||
super.key,
|
super.key,
|
||||||
required this.coords,
|
required this.coords,
|
||||||
this.height = 100,
|
required this.height,
|
||||||
this.width = 100,
|
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.zoom = 1,
|
this.zoom = 1,
|
||||||
this.showAttribution = true,
|
this.showAttribution = true,
|
||||||
|
@ -32,33 +28,18 @@ class MapThumbnail extends HookConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final mapController = useMapController();
|
|
||||||
final isMapReady = useRef(false);
|
|
||||||
ref.watch(mapStateNotifier.select((s) => s.mapStyle));
|
ref.watch(mapStateNotifier.select((s) => s.mapStyle));
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
if (isMapReady.value && mapController.center != coords) {
|
|
||||||
mapController.move(coords, zoom);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
[coords],
|
|
||||||
);
|
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: height,
|
height: height,
|
||||||
width: width,
|
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
child: FlutterMap(
|
child: FlutterMap(
|
||||||
mapController: mapController,
|
|
||||||
options: MapOptions(
|
options: MapOptions(
|
||||||
interactiveFlags: InteractiveFlag.none,
|
interactiveFlags: InteractiveFlag.none,
|
||||||
center: coords,
|
center: coords,
|
||||||
zoom: zoom,
|
zoom: zoom,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
onMapReady: () => isMapReady.value = true,
|
|
||||||
),
|
),
|
||||||
nonRotatedChildren: [
|
nonRotatedChildren: [
|
||||||
if (showAttribution)
|
if (showAttribution)
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:flutter_map/flutter_map.dart';
|
|
||||||
|
|
||||||
MapController useMapController({
|
|
||||||
String? debugLabel,
|
|
||||||
List<Object?>? keys,
|
|
||||||
}) {
|
|
||||||
return use(_MapControllerHook(keys: keys));
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MapControllerHook extends Hook<MapController> {
|
|
||||||
const _MapControllerHook({List<Object?>? keys}) : super(keys: keys);
|
|
||||||
|
|
||||||
@override
|
|
||||||
HookState<MapController, Hook<MapController>> createState() =>
|
|
||||||
_MapControllerHookState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MapControllerHookState
|
|
||||||
extends HookState<MapController, _MapControllerHook> {
|
|
||||||
late final controller = MapController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
MapController build(BuildContext context) => controller;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() => controller.dispose();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get debugLabel => 'useMapController';
|
|
||||||
}
|
|
|
@ -55,7 +55,6 @@ class MapPageState extends ConsumerState<MapPage> {
|
||||||
// in onMapEvent() since MapEventMove#id is not populated properly in the
|
// in onMapEvent() since MapEventMove#id is not populated properly in the
|
||||||
// current version of flutter_map(4.0.0) used
|
// current version of flutter_map(4.0.0) used
|
||||||
bool forceAssetUpdate = false;
|
bool forceAssetUpdate = false;
|
||||||
bool isMapReady = false;
|
|
||||||
late final Debounce debounce;
|
late final Debounce debounce;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -80,7 +79,7 @@ class MapPageState extends ConsumerState<MapPage> {
|
||||||
bool forceReload = false,
|
bool forceReload = false,
|
||||||
}) {
|
}) {
|
||||||
try {
|
try {
|
||||||
final bounds = isMapReady ? mapController.bounds : null;
|
final bounds = mapController.bounds;
|
||||||
if (bounds != null) {
|
if (bounds != null) {
|
||||||
final oldAssetsInBounds = assetsInBounds.toSet();
|
final oldAssetsInBounds = assetsInBounds.toSet();
|
||||||
assetsInBounds =
|
assetsInBounds =
|
||||||
|
@ -456,7 +455,6 @@ class MapPageState extends ConsumerState<MapPage> {
|
||||||
minZoom: 1,
|
minZoom: 1,
|
||||||
maxZoom: maxZoom,
|
maxZoom: maxZoom,
|
||||||
onMapReady: () {
|
onMapReady: () {
|
||||||
isMapReady = true;
|
|
||||||
mapController.mapEventStream.listen(onMapEvent);
|
mapController.mapEventStream.listen(onMapEvent);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
|
@ -6,7 +6,7 @@ part of 'person.service.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$personServiceHash() => r'cde0a9c029d16ddde2adcd58ae8c863bf8cc1fed';
|
String _$personServiceHash() => r'3fc3dcf4603c7b55c0deae65f39f6c212eea492b';
|
||||||
|
|
||||||
/// See also [personService].
|
/// See also [personService].
|
||||||
@ProviderFor(personService)
|
@ProviderFor(personService)
|
||||||
|
|
|
@ -29,8 +29,9 @@ class CuratedPlacesRow extends CuratedRow {
|
||||||
onTap: () => context.autoPush(
|
onTap: () => context.autoPush(
|
||||||
const MapRoute(),
|
const MapRoute(),
|
||||||
),
|
),
|
||||||
child: SizedBox.square(
|
child: SizedBox(
|
||||||
dimension: imageSize,
|
height: imageSize,
|
||||||
|
width: imageSize,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -42,7 +43,6 @@ class CuratedPlacesRow extends CuratedRow {
|
||||||
5,
|
5,
|
||||||
),
|
),
|
||||||
height: imageSize,
|
height: imageSize,
|
||||||
width: imageSize,
|
|
||||||
showAttribution: false,
|
showAttribution: false,
|
||||||
isDarkTheme: context.isDarkTheme,
|
isDarkTheme: context.isDarkTheme,
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,7 +7,7 @@ part of 'app_settings.provider.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$appSettingsServiceHash() =>
|
String _$appSettingsServiceHash() =>
|
||||||
r'45ea609a91d250290431a7a08a14d16b37c7515d';
|
r'957a65af6967701112f3076b507f9738fec4b7be';
|
||||||
|
|
||||||
/// See also [appSettingsService].
|
/// See also [appSettingsService].
|
||||||
@ProviderFor(appSettingsService)
|
@ProviderFor(appSettingsService)
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class CustomTransitionsBuilders {
|
|
||||||
const CustomTransitionsBuilders._();
|
|
||||||
|
|
||||||
static const ZoomPageTransitionsBuilder zoomPageTransitionsBuilder =
|
|
||||||
ZoomPageTransitionsBuilder();
|
|
||||||
|
|
||||||
static const RouteTransitionsBuilder zoomedPage = _zoomedPage;
|
|
||||||
|
|
||||||
static Widget _zoomedPage(
|
|
||||||
BuildContext context,
|
|
||||||
Animation<double> animation,
|
|
||||||
Animation<double> secondaryAnimation,
|
|
||||||
Widget child,
|
|
||||||
) {
|
|
||||||
return zoomPageTransitionsBuilder.buildTransitions(
|
|
||||||
// Empty PageRoute<> object, only used to pass allowSnapshotting to ZoomPageTransitionsBuilder
|
|
||||||
PageRouteBuilder(
|
|
||||||
allowSnapshotting: true,
|
|
||||||
fullscreenDialog: false,
|
|
||||||
pageBuilder: (context, animation, secondaryAnimation) =>
|
|
||||||
const SizedBox.shrink(),
|
|
||||||
),
|
|
||||||
context,
|
|
||||||
animation,
|
|
||||||
secondaryAnimation,
|
|
||||||
child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,7 +8,6 @@ import 'package:immich_mobile/modules/album/views/album_viewer_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
|
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
|
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
||||||
import 'package:immich_mobile/modules/map/ui/map_location_picker.dart';
|
|
||||||
import 'package:immich_mobile/modules/map/views/map_page.dart';
|
import 'package:immich_mobile/modules/map/views/map_page.dart';
|
||||||
import 'package:immich_mobile/modules/memories/models/memory.dart';
|
import 'package:immich_mobile/modules/memories/models/memory.dart';
|
||||||
import 'package:immich_mobile/modules/memories/views/memory_page.dart';
|
import 'package:immich_mobile/modules/memories/views/memory_page.dart';
|
||||||
|
@ -44,7 +43,6 @@ 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/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/duplicate_guard.dart';
|
import 'package:immich_mobile/routing/duplicate_guard.dart';
|
||||||
import 'package:immich_mobile/routing/backup_permission_guard.dart';
|
import 'package:immich_mobile/routing/backup_permission_guard.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
|
@ -58,8 +56,7 @@ import 'package:immich_mobile/shared/views/app_log_page.dart';
|
||||||
import 'package:immich_mobile/shared/views/splash_screen.dart';
|
import 'package:immich_mobile/shared/views/splash_screen.dart';
|
||||||
import 'package:immich_mobile/shared/views/tab_controller_page.dart';
|
import 'package:immich_mobile/shared/views/tab_controller_page.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart' hide LatLng;
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
part 'router.gr.dart';
|
part 'router.gr.dart';
|
||||||
|
|
||||||
|
@ -89,10 +86,9 @@ part 'router.gr.dart';
|
||||||
],
|
],
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
),
|
),
|
||||||
CustomRoute(
|
AutoRoute(
|
||||||
page: GalleryViewerPage,
|
page: GalleryViewerPage,
|
||||||
guards: [AuthGuard, DuplicateGuard],
|
guards: [AuthGuard, DuplicateGuard],
|
||||||
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
|
|
||||||
),
|
),
|
||||||
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
|
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
|
||||||
AutoRoute(
|
AutoRoute(
|
||||||
|
@ -174,10 +170,6 @@ part 'router.gr.dart';
|
||||||
transitionsBuilder: TransitionsBuilders.slideLeft,
|
transitionsBuilder: TransitionsBuilders.slideLeft,
|
||||||
durationInMilliseconds: 200,
|
durationInMilliseconds: 200,
|
||||||
),
|
),
|
||||||
CustomRoute<LatLng?>(
|
|
||||||
page: MapLocationPickerPage,
|
|
||||||
guards: [AuthGuard, DuplicateGuard],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class AppRouter extends _$AppRouter {
|
class AppRouter extends _$AppRouter {
|
||||||
|
|
|
@ -63,7 +63,7 @@ class _$AppRouter extends RootStackRouter {
|
||||||
},
|
},
|
||||||
GalleryViewerRoute.name: (routeData) {
|
GalleryViewerRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<GalleryViewerRouteArgs>();
|
final args = routeData.argsAs<GalleryViewerRouteArgs>();
|
||||||
return CustomPage<dynamic>(
|
return MaterialPageX<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
child: GalleryViewerPage(
|
child: GalleryViewerPage(
|
||||||
key: args.key,
|
key: args.key,
|
||||||
|
@ -75,9 +75,6 @@ class _$AppRouter extends RootStackRouter {
|
||||||
isOwner: args.isOwner,
|
isOwner: args.isOwner,
|
||||||
sharedAlbumId: args.sharedAlbumId,
|
sharedAlbumId: args.sharedAlbumId,
|
||||||
),
|
),
|
||||||
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
|
|
||||||
opaque: true,
|
|
||||||
barrierDismissible: false,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
VideoViewerRoute.name: (routeData) {
|
VideoViewerRoute.name: (routeData) {
|
||||||
|
@ -360,19 +357,6 @@ class _$AppRouter extends RootStackRouter {
|
||||||
barrierDismissible: false,
|
barrierDismissible: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
MapLocationPickerRoute.name: (routeData) {
|
|
||||||
final args = routeData.argsAs<MapLocationPickerRouteArgs>(
|
|
||||||
orElse: () => const MapLocationPickerRouteArgs());
|
|
||||||
return CustomPage<LatLng?>(
|
|
||||||
routeData: routeData,
|
|
||||||
child: MapLocationPickerPage(
|
|
||||||
key: args.key,
|
|
||||||
initialLatLng: args.initialLatLng,
|
|
||||||
),
|
|
||||||
opaque: true,
|
|
||||||
barrierDismissible: false,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
HomeRoute.name: (routeData) {
|
HomeRoute.name: (routeData) {
|
||||||
return MaterialPageX<dynamic>(
|
return MaterialPageX<dynamic>(
|
||||||
routeData: routeData,
|
routeData: routeData,
|
||||||
|
@ -717,14 +701,6 @@ class _$AppRouter extends RootStackRouter {
|
||||||
duplicateGuard,
|
duplicateGuard,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
RouteConfig(
|
|
||||||
MapLocationPickerRoute.name,
|
|
||||||
path: '/map-location-picker-page',
|
|
||||||
guards: [
|
|
||||||
authGuard,
|
|
||||||
duplicateGuard,
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1642,40 +1618,6 @@ class ActivitiesRouteArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// generated route for
|
|
||||||
/// [MapLocationPickerPage]
|
|
||||||
class MapLocationPickerRoute extends PageRouteInfo<MapLocationPickerRouteArgs> {
|
|
||||||
MapLocationPickerRoute({
|
|
||||||
Key? key,
|
|
||||||
LatLng? initialLatLng,
|
|
||||||
}) : super(
|
|
||||||
MapLocationPickerRoute.name,
|
|
||||||
path: '/map-location-picker-page',
|
|
||||||
args: MapLocationPickerRouteArgs(
|
|
||||||
key: key,
|
|
||||||
initialLatLng: initialLatLng,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
static const String name = 'MapLocationPickerRoute';
|
|
||||||
}
|
|
||||||
|
|
||||||
class MapLocationPickerRouteArgs {
|
|
||||||
const MapLocationPickerRouteArgs({
|
|
||||||
this.key,
|
|
||||||
this.initialLatLng,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Key? key;
|
|
||||||
|
|
||||||
final LatLng? initialLatLng;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'MapLocationPickerRouteArgs{key: $key, initialLatLng: $initialLatLng}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// generated route for
|
/// generated route for
|
||||||
/// [HomePage]
|
/// [HomePage]
|
||||||
class HomeRoute extends PageRouteInfo<void> {
|
class HomeRoute extends PageRouteInfo<void> {
|
||||||
|
|
|
@ -256,8 +256,6 @@ class Asset {
|
||||||
isFavorite != a.isFavorite ||
|
isFavorite != a.isFavorite ||
|
||||||
isArchived != a.isArchived ||
|
isArchived != a.isArchived ||
|
||||||
isTrashed != a.isTrashed ||
|
isTrashed != a.isTrashed ||
|
||||||
a.exifInfo?.latitude != exifInfo?.latitude ||
|
|
||||||
a.exifInfo?.longitude != exifInfo?.longitude ||
|
|
||||||
// no local stack count or different count from remote
|
// no local stack count or different count from remote
|
||||||
((stackCount == null && a.stackCount != null) ||
|
((stackCount == null && a.stackCount != null) ||
|
||||||
(stackCount != null &&
|
(stackCount != null &&
|
||||||
|
|
2
mobile/lib/shared/providers/api.provider.g.dart
generated
2
mobile/lib/shared/providers/api.provider.g.dart
generated
|
@ -6,7 +6,7 @@ part of 'api.provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$apiServiceHash() => r'5b8beddb448316bdae5e3963ff77601653715729';
|
String _$apiServiceHash() => r'03cbd33147a7058d56175e532ac47e1aa4858c6d';
|
||||||
|
|
||||||
/// See also [apiService].
|
/// See also [apiService].
|
||||||
@ProviderFor(apiService)
|
@ProviderFor(apiService)
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.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/db.provider.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/services/sync.service.dart';
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
|
@ -17,33 +14,13 @@ import 'package:socket_io_client/socket_io_client.dart';
|
||||||
|
|
||||||
enum PendingAction {
|
enum PendingAction {
|
||||||
assetDelete,
|
assetDelete,
|
||||||
assetUploaded,
|
|
||||||
assetHidden,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PendingChange {
|
class PendingChange {
|
||||||
final String id;
|
|
||||||
final PendingAction action;
|
final PendingAction action;
|
||||||
final dynamic value;
|
final dynamic value;
|
||||||
|
|
||||||
const PendingChange(
|
const PendingChange(this.action, this.value);
|
||||||
this.id,
|
|
||||||
this.action,
|
|
||||||
this.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'PendingChange(id: $id, action: $action, value: $value)';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is PendingChange && other.id == id && other.action == action;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => id.hashCode ^ action.hashCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebsocketState {
|
class WebsocketState {
|
||||||
|
@ -154,7 +131,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||||
socket.on('on_asset_trash', _handleServerUpdates);
|
socket.on('on_asset_trash', _handleServerUpdates);
|
||||||
socket.on('on_asset_restore', _handleServerUpdates);
|
socket.on('on_asset_restore', _handleServerUpdates);
|
||||||
socket.on('on_asset_update', _handleServerUpdates);
|
socket.on('on_asset_update', _handleServerUpdates);
|
||||||
socket.on('on_asset_hidden', _handleOnAssetHidden);
|
|
||||||
socket.on('on_new_release', _handleReleaseUpdates);
|
socket.on('on_new_release', _handleReleaseUpdates);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
|
debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
|
||||||
|
@ -187,78 +163,35 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void addPendingChange(PendingAction action, dynamic value) {
|
void addPendingChange(PendingAction action, dynamic value) {
|
||||||
final now = DateTime.now();
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
pendingChanges: [
|
pendingChanges: [...state.pendingChanges, PendingChange(action, value)],
|
||||||
...state.pendingChanges,
|
|
||||||
PendingChange(now.millisecondsSinceEpoch.toString(), action, value),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
_debounce(handlePendingChanges);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handlePendingDeletes() async {
|
void handlePendingChanges() {
|
||||||
final deleteChanges = state.pendingChanges
|
final deleteChanges = state.pendingChanges
|
||||||
.where((c) => c.action == PendingAction.assetDelete)
|
.where((c) => c.action == PendingAction.assetDelete)
|
||||||
.toList();
|
.toList();
|
||||||
if (deleteChanges.isNotEmpty) {
|
if (deleteChanges.isNotEmpty) {
|
||||||
List<String> remoteIds =
|
List<String> remoteIds =
|
||||||
deleteChanges.map((a) => a.value.toString()).toList();
|
deleteChanges.map((a) => a.value.toString()).toList();
|
||||||
await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
|
_ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
pendingChanges: state.pendingChanges
|
pendingChanges: state.pendingChanges
|
||||||
.whereNot((c) => deleteChanges.contains(c))
|
.where((c) => c.action != PendingAction.assetDelete)
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handlePendingUploaded() async {
|
void _handleOnUploadSuccess(dynamic data) {
|
||||||
final uploadedChanges = state.pendingChanges
|
final dto = AssetResponseDto.fromJson(data);
|
||||||
.where((c) => c.action == PendingAction.assetUploaded)
|
if (dto != null) {
|
||||||
.toList();
|
final newAsset = Asset.remote(dto);
|
||||||
if (uploadedChanges.isNotEmpty) {
|
_ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
|
||||||
List<AssetResponseDto?> remoteAssets = uploadedChanges
|
|
||||||
.map((a) => AssetResponseDto.fromJson(a.value))
|
|
||||||
.toList();
|
|
||||||
for (final dto in remoteAssets) {
|
|
||||||
if (dto != null) {
|
|
||||||
final newAsset = Asset.remote(dto);
|
|
||||||
await _ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
state = state.copyWith(
|
|
||||||
pendingChanges: state.pendingChanges
|
|
||||||
.whereNot((c) => uploadedChanges.contains(c))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handlingPendingHidden() async {
|
|
||||||
final hiddenChanges = state.pendingChanges
|
|
||||||
.where((c) => c.action == PendingAction.assetHidden)
|
|
||||||
.toList();
|
|
||||||
if (hiddenChanges.isNotEmpty) {
|
|
||||||
List<String> remoteIds =
|
|
||||||
hiddenChanges.map((a) => a.value.toString()).toList();
|
|
||||||
final db = _ref.watch(dbProvider);
|
|
||||||
await db.writeTxn(() => db.assets.deleteAllByRemoteId(remoteIds));
|
|
||||||
|
|
||||||
state = state.copyWith(
|
|
||||||
pendingChanges: state.pendingChanges
|
|
||||||
.whereNot((c) => hiddenChanges.contains(c))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void handlePendingChanges() async {
|
|
||||||
await _handlePendingUploaded();
|
|
||||||
await _handlePendingDeletes();
|
|
||||||
await _handlingPendingHidden();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleOnConfigUpdate(dynamic _) {
|
void _handleOnConfigUpdate(dynamic _) {
|
||||||
_ref.read(serverInfoProvider.notifier).getServerFeatures();
|
_ref.read(serverInfoProvider.notifier).getServerFeatures();
|
||||||
_ref.read(serverInfoProvider.notifier).getServerConfig();
|
_ref.read(serverInfoProvider.notifier).getServerConfig();
|
||||||
|
@ -269,14 +202,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||||
_ref.read(assetProvider.notifier).getAllAsset();
|
_ref.read(assetProvider.notifier).getAllAsset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOnUploadSuccess(dynamic data) =>
|
void _handleOnAssetDelete(dynamic data) {
|
||||||
addPendingChange(PendingAction.assetUploaded, data);
|
addPendingChange(PendingAction.assetDelete, data);
|
||||||
|
_debounce(handlePendingChanges);
|
||||||
void _handleOnAssetDelete(dynamic data) =>
|
}
|
||||||
addPendingChange(PendingAction.assetDelete, data);
|
|
||||||
|
|
||||||
void _handleOnAssetHidden(dynamic data) =>
|
|
||||||
addPendingChange(PendingAction.assetHidden, data);
|
|
||||||
|
|
||||||
_handleReleaseUpdates(dynamic data) {
|
_handleReleaseUpdates(dynamic data) {
|
||||||
// Json guard
|
// Json guard
|
||||||
|
|
|
@ -11,7 +11,6 @@ import 'package:immich_mobile/shared/providers/db.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||||
import 'package:immich_mobile/shared/services/sync.service.dart';
|
import 'package:immich_mobile/shared/services/sync.service.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
@ -182,27 +181,4 @@ class AssetService {
|
||||||
Future<List<Asset?>> changeArchiveStatus(List<Asset> assets, bool isArchive) {
|
Future<List<Asset?>> changeArchiveStatus(List<Asset> assets, bool isArchive) {
|
||||||
return updateAssets(assets, UpdateAssetDto(isArchived: isArchive));
|
return updateAssets(assets, UpdateAssetDto(isArchived: isArchive));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Asset?>> changeDateTime(
|
|
||||||
List<Asset> assets,
|
|
||||||
String updatedDt,
|
|
||||||
) {
|
|
||||||
return updateAssets(
|
|
||||||
assets,
|
|
||||||
UpdateAssetDto(dateTimeOriginal: updatedDt),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Asset?>> changeLocation(
|
|
||||||
List<Asset> assets,
|
|
||||||
LatLng location,
|
|
||||||
) {
|
|
||||||
return updateAssets(
|
|
||||||
assets,
|
|
||||||
UpdateAssetDto(
|
|
||||||
latitude: location.latitude,
|
|
||||||
longitude: location.longitude,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,10 +401,6 @@ class SyncService {
|
||||||
|
|
||||||
final Album a = await Album.remote(dto);
|
final Album a = await Album.remote(dto);
|
||||||
await _db.writeTxn(() => _db.albums.store(a));
|
await _db.writeTxn(() => _db.albums.store(a));
|
||||||
} else {
|
|
||||||
_log.warning(
|
|
||||||
"Failed to add album from server: assetCount ${dto.assetCount} != "
|
|
||||||
"asset array length ${dto.assets.length} for album ${dto.albumName}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,260 +0,0 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/duration_extensions.dart';
|
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
|
||||||
import 'package:timezone/timezone.dart';
|
|
||||||
|
|
||||||
Future<String?> showDateTimePicker({
|
|
||||||
required BuildContext context,
|
|
||||||
DateTime? initialDateTime,
|
|
||||||
String? initialTZ,
|
|
||||||
Duration? initialTZOffset,
|
|
||||||
}) {
|
|
||||||
return showDialog<String?>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => _DateTimePicker(
|
|
||||||
initialDateTime: initialDateTime,
|
|
||||||
initialTZ: initialTZ,
|
|
||||||
initialTZOffset: initialTZOffset,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String _getFormattedOffset(int offsetInMilli, tz.Location location) {
|
|
||||||
return "${location.name} (UTC${Duration(milliseconds: offsetInMilli).formatAsOffset()})";
|
|
||||||
}
|
|
||||||
|
|
||||||
class _DateTimePicker extends HookWidget {
|
|
||||||
final DateTime? initialDateTime;
|
|
||||||
final String? initialTZ;
|
|
||||||
final Duration? initialTZOffset;
|
|
||||||
|
|
||||||
const _DateTimePicker({
|
|
||||||
this.initialDateTime,
|
|
||||||
this.initialTZ,
|
|
||||||
this.initialTZOffset,
|
|
||||||
});
|
|
||||||
|
|
||||||
_TimeZoneOffset _getInitiationLocation() {
|
|
||||||
if (initialTZ != null) {
|
|
||||||
try {
|
|
||||||
return _TimeZoneOffset.fromLocation(
|
|
||||||
tz.timeZoneDatabase.get(initialTZ!),
|
|
||||||
);
|
|
||||||
} on LocationNotFoundException {
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Duration? tzOffset = initialTZOffset ?? initialDateTime?.timeZoneOffset;
|
|
||||||
|
|
||||||
if (tzOffset != null) {
|
|
||||||
final offsetInMilli = tzOffset.inMilliseconds;
|
|
||||||
// get all locations with matching offset
|
|
||||||
final locations = tz.timeZoneDatabase.locations.values.where(
|
|
||||||
(location) => location.currentTimeZone.offset == offsetInMilli,
|
|
||||||
);
|
|
||||||
// Prefer locations with abbreviation first
|
|
||||||
final location = locations.firstWhereOrNull(
|
|
||||||
(e) => !e.currentTimeZone.abbreviation.contains("0"),
|
|
||||||
) ??
|
|
||||||
locations.firstOrNull;
|
|
||||||
if (location != null) {
|
|
||||||
return _TimeZoneOffset.fromLocation(location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return _TimeZoneOffset.fromLocation(tz.getLocation("UTC"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a list of location<name> along with it's offset in duration
|
|
||||||
List<_TimeZoneOffset> getAllTimeZones() {
|
|
||||||
return tz.timeZoneDatabase.locations.values
|
|
||||||
.where((l) => !l.currentTimeZone.abbreviation.contains("0"))
|
|
||||||
.map(_TimeZoneOffset.fromLocation)
|
|
||||||
.sorted()
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final date = useState<DateTime>(initialDateTime ?? DateTime.now());
|
|
||||||
final tzOffset = useState<_TimeZoneOffset>(_getInitiationLocation());
|
|
||||||
final timeZones = useMemoized(() => getAllTimeZones(), const []);
|
|
||||||
|
|
||||||
void pickDate() async {
|
|
||||||
final now = DateTime.now();
|
|
||||||
// Handles cases where the date from the asset is far off in the future
|
|
||||||
final initialDate = date.value.isAfter(now) ? now : date.value;
|
|
||||||
final newDate = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: initialDate,
|
|
||||||
firstDate: DateTime(1800),
|
|
||||||
lastDate: now,
|
|
||||||
);
|
|
||||||
if (newDate == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final newTime = await showTimePicker(
|
|
||||||
context: context,
|
|
||||||
initialTime: TimeOfDay.fromDateTime(date.value),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (newTime == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
date.value = newDate.copyWith(hour: newTime.hour, minute: newTime.minute);
|
|
||||||
}
|
|
||||||
|
|
||||||
void popWithDateTime() {
|
|
||||||
final formattedDateTime =
|
|
||||||
DateFormat("yyyy-MM-dd'T'HH:mm:ss").format(date.value);
|
|
||||||
final dtWithOffset = formattedDateTime +
|
|
||||||
Duration(milliseconds: tzOffset.value.offsetInMilliseconds)
|
|
||||||
.formatAsOffset();
|
|
||||||
context.pop(dtWithOffset);
|
|
||||||
}
|
|
||||||
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: const EdgeInsets.all(30),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"edit_date_time_dialog_date_time",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).tr(),
|
|
||||||
TextButton.icon(
|
|
||||||
onPressed: pickDate,
|
|
||||||
icon: Text(
|
|
||||||
DateFormat("dd-MM-yyyy hh:mm a").format(date.value),
|
|
||||||
style: context.textTheme.bodyLarge
|
|
||||||
?.copyWith(color: context.primaryColor),
|
|
||||||
),
|
|
||||||
label: const Icon(
|
|
||||||
Icons.edit_outlined,
|
|
||||||
size: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Text(
|
|
||||||
"edit_date_time_dialog_timezone",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).tr(),
|
|
||||||
DropdownMenu(
|
|
||||||
menuHeight: 300,
|
|
||||||
width: 280,
|
|
||||||
inputDecorationTheme: const InputDecorationTheme(
|
|
||||||
border: InputBorder.none,
|
|
||||||
contentPadding: EdgeInsets.zero,
|
|
||||||
),
|
|
||||||
trailingIcon: Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 10),
|
|
||||||
child: Icon(
|
|
||||||
Icons.arrow_drop_down,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
textStyle: context.textTheme.bodyLarge?.copyWith(
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
menuStyle: const MenuStyle(
|
|
||||||
fixedSize: MaterialStatePropertyAll(Size.fromWidth(350)),
|
|
||||||
alignment: Alignment(-1.25, 0.5),
|
|
||||||
),
|
|
||||||
onSelected: (value) => tzOffset.value = value!,
|
|
||||||
initialSelection: tzOffset.value,
|
|
||||||
dropdownMenuEntries: timeZones
|
|
||||||
.map(
|
|
||||||
(t) => DropdownMenuEntry<_TimeZoneOffset>(
|
|
||||||
value: t,
|
|
||||||
label: t.display,
|
|
||||||
style: ButtonStyle(
|
|
||||||
textStyle: MaterialStatePropertyAll(
|
|
||||||
context.textTheme.bodyMedium,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
child: Text(
|
|
||||||
"action_common_cancel",
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.colorScheme.error,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: popWithDateTime,
|
|
||||||
child: Text(
|
|
||||||
"action_common_update",
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TimeZoneOffset implements Comparable<_TimeZoneOffset> {
|
|
||||||
final String display;
|
|
||||||
final Location location;
|
|
||||||
|
|
||||||
const _TimeZoneOffset({
|
|
||||||
required this.display,
|
|
||||||
required this.location,
|
|
||||||
});
|
|
||||||
|
|
||||||
_TimeZoneOffset copyWith({
|
|
||||||
String? display,
|
|
||||||
Location? location,
|
|
||||||
}) {
|
|
||||||
return _TimeZoneOffset(
|
|
||||||
display: display ?? this.display,
|
|
||||||
location: location ?? this.location,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int get offsetInMilliseconds => location.currentTimeZone.offset;
|
|
||||||
|
|
||||||
_TimeZoneOffset.fromLocation(tz.Location l)
|
|
||||||
: display = _getFormattedOffset(l.currentTimeZone.offset, l),
|
|
||||||
location = l;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int compareTo(_TimeZoneOffset other) {
|
|
||||||
return offsetInMilliseconds.compareTo(other.offsetInMilliseconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'_TimeZoneOffset(display: $display, location: $location)';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
if (identical(this, other)) return true;
|
|
||||||
|
|
||||||
return other is _TimeZoneOffset &&
|
|
||||||
other.display == display &&
|
|
||||||
other.offsetInMilliseconds == offsetInMilliseconds;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
display.hashCode ^ offsetInMilliseconds.hashCode ^ location.hashCode;
|
|
||||||
}
|
|
|
@ -1,256 +0,0 @@
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:flutter_map/plugin_api.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
||||||
import 'package:immich_mobile/modules/map/ui/map_thumbnail.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
Future<LatLng?> showLocationPicker({
|
|
||||||
required BuildContext context,
|
|
||||||
LatLng? initialLatLng,
|
|
||||||
}) {
|
|
||||||
return showDialog<LatLng?>(
|
|
||||||
context: context,
|
|
||||||
useRootNavigator: false,
|
|
||||||
builder: (ctx) => _LocationPicker(
|
|
||||||
initialLatLng: initialLatLng,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum _LocationPickerMode { map, manual }
|
|
||||||
|
|
||||||
bool _validateLat(String value) {
|
|
||||||
final l = double.tryParse(value);
|
|
||||||
return l != null && l > -90 && l < 90;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _validateLong(String value) {
|
|
||||||
final l = double.tryParse(value);
|
|
||||||
return l != null && l > -180 && l < 180;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LocationPicker extends HookWidget {
|
|
||||||
final LatLng? initialLatLng;
|
|
||||||
|
|
||||||
const _LocationPicker({
|
|
||||||
this.initialLatLng,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final latitude = useState(initialLatLng?.latitude ?? 0.0);
|
|
||||||
final longitude = useState(initialLatLng?.longitude ?? 0.0);
|
|
||||||
final latlng = LatLng(latitude.value, longitude.value);
|
|
||||||
final pickerMode = useState(_LocationPickerMode.map);
|
|
||||||
final latitudeController = useTextEditingController();
|
|
||||||
final isValidLatitude = useState(true);
|
|
||||||
final latitiudeFocusNode = useFocusNode();
|
|
||||||
final longitudeController = useTextEditingController();
|
|
||||||
final longitudeFocusNode = useFocusNode();
|
|
||||||
final isValidLongitude = useState(true);
|
|
||||||
|
|
||||||
void validateInputs() {
|
|
||||||
isValidLatitude.value = _validateLat(latitudeController.text);
|
|
||||||
if (isValidLatitude.value) {
|
|
||||||
latitude.value = latitudeController.text.toDouble();
|
|
||||||
}
|
|
||||||
isValidLongitude.value = _validateLong(longitudeController.text);
|
|
||||||
if (isValidLongitude.value) {
|
|
||||||
longitude.value = longitudeController.text.toDouble();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void validateAndPop() {
|
|
||||||
if (pickerMode.value == _LocationPickerMode.manual) {
|
|
||||||
validateInputs();
|
|
||||||
}
|
|
||||||
if (isValidLatitude.value && isValidLongitude.value) {
|
|
||||||
return context.pop(latlng);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> buildMapPickerMode() {
|
|
||||||
return [
|
|
||||||
TextButton.icon(
|
|
||||||
icon: Text(
|
|
||||||
"${latitude.value.toStringAsFixed(4)}, ${longitude.value.toStringAsFixed(4)}",
|
|
||||||
),
|
|
||||||
label: const Icon(Icons.edit_outlined, size: 16),
|
|
||||||
onPressed: () {
|
|
||||||
latitudeController.text = latitude.value.toStringAsFixed(4);
|
|
||||||
longitudeController.text = longitude.value.toStringAsFixed(4);
|
|
||||||
pickerMode.value = _LocationPickerMode.manual;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
MapThumbnail(
|
|
||||||
coords: latlng,
|
|
||||||
height: 200,
|
|
||||||
width: 200,
|
|
||||||
zoom: 6,
|
|
||||||
showAttribution: false,
|
|
||||||
onTap: (p0, p1) async {
|
|
||||||
final newLatLng = await context.autoPush<LatLng?>(
|
|
||||||
MapLocationPickerRoute(initialLatLng: latlng),
|
|
||||||
);
|
|
||||||
if (newLatLng != null) {
|
|
||||||
latitude.value = newLatLng.latitude;
|
|
||||||
longitude.value = newLatLng.longitude;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
markers: [
|
|
||||||
Marker(
|
|
||||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
|
||||||
point: LatLng(
|
|
||||||
latitude.value,
|
|
||||||
longitude.value,
|
|
||||||
),
|
|
||||||
builder: (ctx) => const Image(
|
|
||||||
image: AssetImage('assets/location-pin.png'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> buildManualPickerMode() {
|
|
||||||
return [
|
|
||||||
TextButton.icon(
|
|
||||||
icon: const Text("location_picker_choose_on_map").tr(),
|
|
||||||
label: const Icon(Icons.map_outlined, size: 16),
|
|
||||||
onPressed: () {
|
|
||||||
validateInputs();
|
|
||||||
if (isValidLatitude.value && isValidLongitude.value) {
|
|
||||||
pickerMode.value = _LocationPickerMode.map;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: latitudeController,
|
|
||||||
focusNode: latitiudeFocusNode,
|
|
||||||
textInputAction: TextInputAction.done,
|
|
||||||
autofocus: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'location_picker_latitude'.tr(),
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.auto,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
hintText: 'location_picker_latitude_hint'.tr(),
|
|
||||||
hintStyle: const TextStyle(
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
errorText: isValidLatitude.value
|
|
||||||
? null
|
|
||||||
: "location_picker_latitude_error".tr(),
|
|
||||||
),
|
|
||||||
onEditingComplete: () {
|
|
||||||
isValidLatitude.value = _validateLat(latitudeController.text);
|
|
||||||
if (isValidLatitude.value) {
|
|
||||||
latitude.value = latitudeController.text.toDouble();
|
|
||||||
longitudeFocusNode.requestFocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
inputFormatters: [LengthLimitingTextInputFormatter(8)],
|
|
||||||
onTapOutside: (_) => latitiudeFocusNode.unfocus(),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
TextField(
|
|
||||||
controller: longitudeController,
|
|
||||||
focusNode: longitudeFocusNode,
|
|
||||||
textInputAction: TextInputAction.done,
|
|
||||||
autofocus: false,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: 'location_picker_longitude'.tr(),
|
|
||||||
labelStyle: TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
floatingLabelBehavior: FloatingLabelBehavior.auto,
|
|
||||||
border: const OutlineInputBorder(),
|
|
||||||
hintText: 'location_picker_longitude_hint'.tr(),
|
|
||||||
hintStyle: const TextStyle(
|
|
||||||
fontWeight: FontWeight.normal,
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
errorText: isValidLongitude.value
|
|
||||||
? null
|
|
||||||
: "location_picker_longitude_error".tr(),
|
|
||||||
),
|
|
||||||
onEditingComplete: () {
|
|
||||||
isValidLongitude.value = _validateLong(longitudeController.text);
|
|
||||||
if (isValidLongitude.value) {
|
|
||||||
longitude.value = longitudeController.text.toDouble();
|
|
||||||
longitudeFocusNode.unfocus();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
|
||||||
inputFormatters: [LengthLimitingTextInputFormatter(8)],
|
|
||||||
onTapOutside: (_) => longitudeFocusNode.unfocus(),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return AlertDialog(
|
|
||||||
contentPadding: const EdgeInsets.all(30),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
content: SingleChildScrollView(
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"edit_location_dialog_title",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
).tr(),
|
|
||||||
const SizedBox(
|
|
||||||
height: 12,
|
|
||||||
),
|
|
||||||
if (pickerMode.value == _LocationPickerMode.manual)
|
|
||||||
...buildManualPickerMode(),
|
|
||||||
if (pickerMode.value == _LocationPickerMode.map)
|
|
||||||
...buildMapPickerMode(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () => context.pop(),
|
|
||||||
child: Text(
|
|
||||||
"action_common_cancel",
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.colorScheme.error,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: validateAndPop,
|
|
||||||
child: Text(
|
|
||||||
"action_common_update",
|
|
||||||
style: context.textTheme.bodyMedium?.copyWith(
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: context.primaryColor,
|
|
||||||
),
|
|
||||||
).tr(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ class ScaffoldErrorBody extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"scaffold_body_error_occurred",
|
"scaffold_body_error_occured",
|
||||||
style: context.textTheme.displayMedium,
|
style: context.textTheme.displayMedium,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
).tr(),
|
).tr(),
|
||||||
|
|
|
@ -20,7 +20,7 @@ final immichThemeProvider = StateProvider<ThemeMode>((ref) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
final ThemeData base = ThemeData(
|
ThemeData base = ThemeData(
|
||||||
chipTheme: const ChipThemeData(
|
chipTheme: const ChipThemeData(
|
||||||
side: BorderSide.none,
|
side: BorderSide.none,
|
||||||
),
|
),
|
||||||
|
@ -30,7 +30,7 @@ final ThemeData base = ThemeData(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final ThemeData immichLightTheme = ThemeData(
|
ThemeData immichLightTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primarySwatch: Colors.indigo,
|
primarySwatch: Colors.indigo,
|
||||||
|
@ -153,7 +153,7 @@ final ThemeData immichLightTheme = ThemeData(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final ThemeData immichDarkTheme = ThemeData(
|
ThemeData immichDarkTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
brightness: Brightness.dark,
|
brightness: Brightness.dark,
|
||||||
primarySwatch: Colors.indigo,
|
primarySwatch: Colors.indigo,
|
||||||
|
|
|
@ -2,17 +2,12 @@ import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/asset_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/asset.service.dart';
|
|
||||||
import 'package:immich_mobile/shared/services/share.service.dart';
|
import 'package:immich_mobile/shared/services/share.service.dart';
|
||||||
import 'package:immich_mobile/shared/ui/date_time_picker.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||||
import 'package:immich_mobile/shared/ui/location_picker.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/share_dialog.dart';
|
import 'package:immich_mobile/shared/ui/share_dialog.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
|
||||||
|
|
||||||
void handleShareAssets(
|
void handleShareAssets(
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
|
@ -90,60 +85,3 @@ Future<void> handleFavoriteAssets(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> handleEditDateTime(
|
|
||||||
WidgetRef ref,
|
|
||||||
BuildContext context,
|
|
||||||
List<Asset> selection,
|
|
||||||
) async {
|
|
||||||
DateTime? initialDate;
|
|
||||||
String? timeZone;
|
|
||||||
Duration? offset;
|
|
||||||
if (selection.length == 1) {
|
|
||||||
final asset = selection.first;
|
|
||||||
final assetWithExif = await ref.watch(assetServiceProvider).loadExif(asset);
|
|
||||||
final (dt, oft) = assetWithExif.getTZAdjustedTimeAndOffset();
|
|
||||||
initialDate = dt;
|
|
||||||
offset = oft;
|
|
||||||
timeZone = assetWithExif.exifInfo?.timeZone;
|
|
||||||
}
|
|
||||||
final dateTime = await showDateTimePicker(
|
|
||||||
context: context,
|
|
||||||
initialDateTime: initialDate,
|
|
||||||
initialTZ: timeZone,
|
|
||||||
initialTZOffset: offset,
|
|
||||||
);
|
|
||||||
if (dateTime == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.read(assetServiceProvider).changeDateTime(selection.toList(), dateTime);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> handleEditLocation(
|
|
||||||
WidgetRef ref,
|
|
||||||
BuildContext context,
|
|
||||||
List<Asset> selection,
|
|
||||||
) async {
|
|
||||||
LatLng? initialLatLng;
|
|
||||||
if (selection.length == 1) {
|
|
||||||
final asset = selection.first;
|
|
||||||
final assetWithExif = await ref.watch(assetServiceProvider).loadExif(asset);
|
|
||||||
if (assetWithExif.exifInfo?.latitude != null &&
|
|
||||||
assetWithExif.exifInfo?.longitude != null) {
|
|
||||||
initialLatLng = LatLng(
|
|
||||||
assetWithExif.exifInfo!.latitude!,
|
|
||||||
assetWithExif.exifInfo!.longitude!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final location = await showLocationPicker(
|
|
||||||
context: context,
|
|
||||||
initialLatLng: initialLatLng,
|
|
||||||
);
|
|
||||||
if (location == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.read(assetServiceProvider).changeLocation(selection.toList(), location);
|
|
||||||
}
|
|
||||||
|
|
21
mobile/openapi/.openapi-generator/FILES
generated
21
mobile/openapi/.openapi-generator/FILES
generated
|
@ -24,10 +24,6 @@ doc/AssetBulkUploadCheckDto.md
|
||||||
doc/AssetBulkUploadCheckItem.md
|
doc/AssetBulkUploadCheckItem.md
|
||||||
doc/AssetBulkUploadCheckResponseDto.md
|
doc/AssetBulkUploadCheckResponseDto.md
|
||||||
doc/AssetBulkUploadCheckResult.md
|
doc/AssetBulkUploadCheckResult.md
|
||||||
doc/AssetFaceResponseDto.md
|
|
||||||
doc/AssetFaceUpdateDto.md
|
|
||||||
doc/AssetFaceUpdateItem.md
|
|
||||||
doc/AssetFaceWithoutPersonResponseDto.md
|
|
||||||
doc/AssetFileUploadResponseDto.md
|
doc/AssetFileUploadResponseDto.md
|
||||||
doc/AssetIdsDto.md
|
doc/AssetIdsDto.md
|
||||||
doc/AssetIdsResponseDto.md
|
doc/AssetIdsResponseDto.md
|
||||||
|
@ -64,8 +60,6 @@ doc/DownloadInfoDto.md
|
||||||
doc/DownloadResponseDto.md
|
doc/DownloadResponseDto.md
|
||||||
doc/EntityType.md
|
doc/EntityType.md
|
||||||
doc/ExifResponseDto.md
|
doc/ExifResponseDto.md
|
||||||
doc/FaceApi.md
|
|
||||||
doc/FaceDto.md
|
|
||||||
doc/FileChecksumDto.md
|
doc/FileChecksumDto.md
|
||||||
doc/FileChecksumResponseDto.md
|
doc/FileChecksumResponseDto.md
|
||||||
doc/FileReportDto.md
|
doc/FileReportDto.md
|
||||||
|
@ -106,7 +100,6 @@ doc/PersonApi.md
|
||||||
doc/PersonResponseDto.md
|
doc/PersonResponseDto.md
|
||||||
doc/PersonStatisticsResponseDto.md
|
doc/PersonStatisticsResponseDto.md
|
||||||
doc/PersonUpdateDto.md
|
doc/PersonUpdateDto.md
|
||||||
doc/PersonWithFacesResponseDto.md
|
|
||||||
doc/QueueStatusDto.md
|
doc/QueueStatusDto.md
|
||||||
doc/ReactionLevel.md
|
doc/ReactionLevel.md
|
||||||
doc/ReactionType.md
|
doc/ReactionType.md
|
||||||
|
@ -184,7 +177,6 @@ lib/api/api_key_api.dart
|
||||||
lib/api/asset_api.dart
|
lib/api/asset_api.dart
|
||||||
lib/api/audit_api.dart
|
lib/api/audit_api.dart
|
||||||
lib/api/authentication_api.dart
|
lib/api/authentication_api.dart
|
||||||
lib/api/face_api.dart
|
|
||||||
lib/api/job_api.dart
|
lib/api/job_api.dart
|
||||||
lib/api/library_api.dart
|
lib/api/library_api.dart
|
||||||
lib/api/o_auth_api.dart
|
lib/api/o_auth_api.dart
|
||||||
|
@ -221,10 +213,6 @@ lib/model/asset_bulk_upload_check_dto.dart
|
||||||
lib/model/asset_bulk_upload_check_item.dart
|
lib/model/asset_bulk_upload_check_item.dart
|
||||||
lib/model/asset_bulk_upload_check_response_dto.dart
|
lib/model/asset_bulk_upload_check_response_dto.dart
|
||||||
lib/model/asset_bulk_upload_check_result.dart
|
lib/model/asset_bulk_upload_check_result.dart
|
||||||
lib/model/asset_face_response_dto.dart
|
|
||||||
lib/model/asset_face_update_dto.dart
|
|
||||||
lib/model/asset_face_update_item.dart
|
|
||||||
lib/model/asset_face_without_person_response_dto.dart
|
|
||||||
lib/model/asset_file_upload_response_dto.dart
|
lib/model/asset_file_upload_response_dto.dart
|
||||||
lib/model/asset_ids_dto.dart
|
lib/model/asset_ids_dto.dart
|
||||||
lib/model/asset_ids_response_dto.dart
|
lib/model/asset_ids_response_dto.dart
|
||||||
|
@ -259,7 +247,6 @@ lib/model/download_info_dto.dart
|
||||||
lib/model/download_response_dto.dart
|
lib/model/download_response_dto.dart
|
||||||
lib/model/entity_type.dart
|
lib/model/entity_type.dart
|
||||||
lib/model/exif_response_dto.dart
|
lib/model/exif_response_dto.dart
|
||||||
lib/model/face_dto.dart
|
|
||||||
lib/model/file_checksum_dto.dart
|
lib/model/file_checksum_dto.dart
|
||||||
lib/model/file_checksum_response_dto.dart
|
lib/model/file_checksum_response_dto.dart
|
||||||
lib/model/file_report_dto.dart
|
lib/model/file_report_dto.dart
|
||||||
|
@ -295,7 +282,6 @@ lib/model/people_update_item.dart
|
||||||
lib/model/person_response_dto.dart
|
lib/model/person_response_dto.dart
|
||||||
lib/model/person_statistics_response_dto.dart
|
lib/model/person_statistics_response_dto.dart
|
||||||
lib/model/person_update_dto.dart
|
lib/model/person_update_dto.dart
|
||||||
lib/model/person_with_faces_response_dto.dart
|
|
||||||
lib/model/queue_status_dto.dart
|
lib/model/queue_status_dto.dart
|
||||||
lib/model/reaction_level.dart
|
lib/model/reaction_level.dart
|
||||||
lib/model/reaction_type.dart
|
lib/model/reaction_type.dart
|
||||||
|
@ -381,10 +367,6 @@ test/asset_bulk_upload_check_dto_test.dart
|
||||||
test/asset_bulk_upload_check_item_test.dart
|
test/asset_bulk_upload_check_item_test.dart
|
||||||
test/asset_bulk_upload_check_response_dto_test.dart
|
test/asset_bulk_upload_check_response_dto_test.dart
|
||||||
test/asset_bulk_upload_check_result_test.dart
|
test/asset_bulk_upload_check_result_test.dart
|
||||||
test/asset_face_response_dto_test.dart
|
|
||||||
test/asset_face_update_dto_test.dart
|
|
||||||
test/asset_face_update_item_test.dart
|
|
||||||
test/asset_face_without_person_response_dto_test.dart
|
|
||||||
test/asset_file_upload_response_dto_test.dart
|
test/asset_file_upload_response_dto_test.dart
|
||||||
test/asset_ids_dto_test.dart
|
test/asset_ids_dto_test.dart
|
||||||
test/asset_ids_response_dto_test.dart
|
test/asset_ids_response_dto_test.dart
|
||||||
|
@ -421,8 +403,6 @@ test/download_info_dto_test.dart
|
||||||
test/download_response_dto_test.dart
|
test/download_response_dto_test.dart
|
||||||
test/entity_type_test.dart
|
test/entity_type_test.dart
|
||||||
test/exif_response_dto_test.dart
|
test/exif_response_dto_test.dart
|
||||||
test/face_api_test.dart
|
|
||||||
test/face_dto_test.dart
|
|
||||||
test/file_checksum_dto_test.dart
|
test/file_checksum_dto_test.dart
|
||||||
test/file_checksum_response_dto_test.dart
|
test/file_checksum_response_dto_test.dart
|
||||||
test/file_report_dto_test.dart
|
test/file_report_dto_test.dart
|
||||||
|
@ -463,7 +443,6 @@ test/person_api_test.dart
|
||||||
test/person_response_dto_test.dart
|
test/person_response_dto_test.dart
|
||||||
test/person_statistics_response_dto_test.dart
|
test/person_statistics_response_dto_test.dart
|
||||||
test/person_update_dto_test.dart
|
test/person_update_dto_test.dart
|
||||||
test/person_with_faces_response_dto_test.dart
|
|
||||||
test/queue_status_dto_test.dart
|
test/queue_status_dto_test.dart
|
||||||
test/reaction_level_test.dart
|
test/reaction_level_test.dart
|
||||||
test/reaction_type_test.dart
|
test/reaction_type_test.dart
|
||||||
|
|
10
mobile/openapi/README.md
generated
10
mobile/openapi/README.md
generated
|
@ -133,8 +133,6 @@ Class | Method | HTTP request | Description
|
||||||
*AuthenticationApi* | [**logoutAuthDevices**](doc//AuthenticationApi.md#logoutauthdevices) | **DELETE** /auth/devices |
|
*AuthenticationApi* | [**logoutAuthDevices**](doc//AuthenticationApi.md#logoutauthdevices) | **DELETE** /auth/devices |
|
||||||
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
|
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
|
||||||
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||||
*FaceApi* | [**getFaces**](doc//FaceApi.md#getfaces) | **GET** /face |
|
|
||||||
*FaceApi* | [**reassignFacesById**](doc//FaceApi.md#reassignfacesbyid) | **PUT** /face/{id} |
|
|
||||||
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
|
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
|
||||||
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
|
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
|
||||||
*LibraryApi* | [**createLibrary**](doc//LibraryApi.md#createlibrary) | **POST** /library |
|
*LibraryApi* | [**createLibrary**](doc//LibraryApi.md#createlibrary) | **POST** /library |
|
||||||
|
@ -155,14 +153,12 @@ Class | Method | HTTP request | Description
|
||||||
*PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner |
|
*PartnerApi* | [**getPartners**](doc//PartnerApi.md#getpartners) | **GET** /partner |
|
||||||
*PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} |
|
*PartnerApi* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /partner/{id} |
|
||||||
*PartnerApi* | [**updatePartner**](doc//PartnerApi.md#updatepartner) | **PUT** /partner/{id} |
|
*PartnerApi* | [**updatePartner**](doc//PartnerApi.md#updatepartner) | **PUT** /partner/{id} |
|
||||||
*PersonApi* | [**createPerson**](doc//PersonApi.md#createperson) | **POST** /person |
|
|
||||||
*PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person |
|
*PersonApi* | [**getAllPeople**](doc//PersonApi.md#getallpeople) | **GET** /person |
|
||||||
*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} |
|
*PersonApi* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} |
|
||||||
*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
|
*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
|
||||||
*PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
|
*PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
|
||||||
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
|
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
|
||||||
*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
|
*PersonApi* | [**mergePerson**](doc//PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
|
||||||
*PersonApi* | [**reassignFaces**](doc//PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
|
|
||||||
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person |
|
*PersonApi* | [**updatePeople**](doc//PersonApi.md#updatepeople) | **PUT** /person |
|
||||||
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
|
*PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
|
||||||
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
|
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
|
||||||
|
@ -228,10 +224,6 @@ Class | Method | HTTP request | Description
|
||||||
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
|
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
|
||||||
- [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md)
|
- [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md)
|
||||||
- [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md)
|
- [AssetBulkUploadCheckResult](doc//AssetBulkUploadCheckResult.md)
|
||||||
- [AssetFaceResponseDto](doc//AssetFaceResponseDto.md)
|
|
||||||
- [AssetFaceUpdateDto](doc//AssetFaceUpdateDto.md)
|
|
||||||
- [AssetFaceUpdateItem](doc//AssetFaceUpdateItem.md)
|
|
||||||
- [AssetFaceWithoutPersonResponseDto](doc//AssetFaceWithoutPersonResponseDto.md)
|
|
||||||
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
|
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
|
||||||
- [AssetIdsDto](doc//AssetIdsDto.md)
|
- [AssetIdsDto](doc//AssetIdsDto.md)
|
||||||
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
|
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
|
||||||
|
@ -266,7 +258,6 @@ Class | Method | HTTP request | Description
|
||||||
- [DownloadResponseDto](doc//DownloadResponseDto.md)
|
- [DownloadResponseDto](doc//DownloadResponseDto.md)
|
||||||
- [EntityType](doc//EntityType.md)
|
- [EntityType](doc//EntityType.md)
|
||||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||||
- [FaceDto](doc//FaceDto.md)
|
|
||||||
- [FileChecksumDto](doc//FileChecksumDto.md)
|
- [FileChecksumDto](doc//FileChecksumDto.md)
|
||||||
- [FileChecksumResponseDto](doc//FileChecksumResponseDto.md)
|
- [FileChecksumResponseDto](doc//FileChecksumResponseDto.md)
|
||||||
- [FileReportDto](doc//FileReportDto.md)
|
- [FileReportDto](doc//FileReportDto.md)
|
||||||
|
@ -302,7 +293,6 @@ Class | Method | HTTP request | Description
|
||||||
- [PersonResponseDto](doc//PersonResponseDto.md)
|
- [PersonResponseDto](doc//PersonResponseDto.md)
|
||||||
- [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md)
|
- [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md)
|
||||||
- [PersonUpdateDto](doc//PersonUpdateDto.md)
|
- [PersonUpdateDto](doc//PersonUpdateDto.md)
|
||||||
- [PersonWithFacesResponseDto](doc//PersonWithFacesResponseDto.md)
|
|
||||||
- [QueueStatusDto](doc//QueueStatusDto.md)
|
- [QueueStatusDto](doc//QueueStatusDto.md)
|
||||||
- [ReactionLevel](doc//ReactionLevel.md)
|
- [ReactionLevel](doc//ReactionLevel.md)
|
||||||
- [ReactionType](doc//ReactionType.md)
|
- [ReactionType](doc//ReactionType.md)
|
||||||
|
|
1
mobile/openapi/doc/AssetBulkUpdateDto.md
generated
1
mobile/openapi/doc/AssetBulkUpdateDto.md
generated
|
@ -14,6 +14,7 @@ Name | Type | Description | Notes
|
||||||
**isFavorite** | **bool** | | [optional]
|
**isFavorite** | **bool** | | [optional]
|
||||||
**latitude** | **num** | | [optional]
|
**latitude** | **num** | | [optional]
|
||||||
**longitude** | **num** | | [optional]
|
**longitude** | **num** | | [optional]
|
||||||
|
**orientation** | **num** | | [optional]
|
||||||
**removeParent** | **bool** | | [optional]
|
**removeParent** | **bool** | | [optional]
|
||||||
**stackParentId** | **String** | | [optional]
|
**stackParentId** | **String** | | [optional]
|
||||||
|
|
||||||
|
|
22
mobile/openapi/doc/AssetFaceResponseDto.md
generated
22
mobile/openapi/doc/AssetFaceResponseDto.md
generated
|
@ -1,22 +0,0 @@
|
||||||
# openapi.model.AssetFaceResponseDto
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**boundingBoxX1** | **int** | |
|
|
||||||
**boundingBoxX2** | **int** | |
|
|
||||||
**boundingBoxY1** | **int** | |
|
|
||||||
**boundingBoxY2** | **int** | |
|
|
||||||
**id** | **String** | |
|
|
||||||
**imageHeight** | **int** | |
|
|
||||||
**imageWidth** | **int** | |
|
|
||||||
**person** | [**PersonResponseDto**](PersonResponseDto.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
15
mobile/openapi/doc/AssetFaceUpdateDto.md
generated
15
mobile/openapi/doc/AssetFaceUpdateDto.md
generated
|
@ -1,15 +0,0 @@
|
||||||
# openapi.model.AssetFaceUpdateDto
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**List<AssetFaceUpdateItem>**](AssetFaceUpdateItem.md) | | [default to const []]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
16
mobile/openapi/doc/AssetFaceUpdateItem.md
generated
16
mobile/openapi/doc/AssetFaceUpdateItem.md
generated
|
@ -1,16 +0,0 @@
|
||||||
# openapi.model.AssetFaceUpdateItem
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**assetId** | **String** | |
|
|
||||||
**personId** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
|
@ -1,21 +0,0 @@
|
||||||
# openapi.model.AssetFaceWithoutPersonResponseDto
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**boundingBoxX1** | **int** | |
|
|
||||||
**boundingBoxX2** | **int** | |
|
|
||||||
**boundingBoxY1** | **int** | |
|
|
||||||
**boundingBoxY2** | **int** | |
|
|
||||||
**id** | **String** | |
|
|
||||||
**imageHeight** | **int** | |
|
|
||||||
**imageWidth** | **int** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
2
mobile/openapi/doc/AssetResponseDto.md
generated
2
mobile/openapi/doc/AssetResponseDto.md
generated
|
@ -30,7 +30,7 @@ Name | Type | Description | Notes
|
||||||
**originalPath** | **String** | |
|
**originalPath** | **String** | |
|
||||||
**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional]
|
**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional]
|
||||||
**ownerId** | **String** | |
|
**ownerId** | **String** | |
|
||||||
**people** | [**List<PersonWithFacesResponseDto>**](PersonWithFacesResponseDto.md) | | [optional] [default to const []]
|
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []]
|
||||||
**resized** | **bool** | |
|
**resized** | **bool** | |
|
||||||
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
|
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
|
||||||
**stack** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [optional] [default to const []]
|
**stack** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [optional] [default to const []]
|
||||||
|
|
127
mobile/openapi/doc/FaceApi.md
generated
127
mobile/openapi/doc/FaceApi.md
generated
|
@ -1,127 +0,0 @@
|
||||||
# openapi.api.FaceApi
|
|
||||||
|
|
||||||
## Load the API package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
All URIs are relative to */api*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**getFaces**](FaceApi.md#getfaces) | **GET** /face |
|
|
||||||
[**reassignFacesById**](FaceApi.md#reassignfacesbyid) | **PUT** /face/{id} |
|
|
||||||
|
|
||||||
|
|
||||||
# **getFaces**
|
|
||||||
> List<AssetFaceResponseDto> getFaces(id)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
// TODO Configure API key authorization: cookie
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure API key authorization: api_key
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure HTTP Bearer authorization: bearer
|
|
||||||
// Case 1. Use String Token
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
|
||||||
// Case 2. Use Function which generate token.
|
|
||||||
// String yourTokenGeneratorFunction() { ... }
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
|
||||||
|
|
||||||
final api_instance = FaceApi();
|
|
||||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = api_instance.getFaces(id);
|
|
||||||
print(result);
|
|
||||||
} catch (e) {
|
|
||||||
print('Exception when calling FaceApi->getFaces: $e\n');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String**| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**List<AssetFaceResponseDto>**](AssetFaceResponseDto.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **reassignFacesById**
|
|
||||||
> PersonResponseDto reassignFacesById(id, faceDto)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
// TODO Configure API key authorization: cookie
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure API key authorization: api_key
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure HTTP Bearer authorization: bearer
|
|
||||||
// Case 1. Use String Token
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
|
||||||
// Case 2. Use Function which generate token.
|
|
||||||
// String yourTokenGeneratorFunction() { ... }
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
|
||||||
|
|
||||||
final api_instance = FaceApi();
|
|
||||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
|
||||||
final faceDto = FaceDto(); // FaceDto |
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = api_instance.reassignFacesById(id, faceDto);
|
|
||||||
print(result);
|
|
||||||
} catch (e) {
|
|
||||||
print('Exception when calling FaceApi->reassignFacesById: $e\n');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String**| |
|
|
||||||
**faceDto** | [**FaceDto**](FaceDto.md)| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**PersonResponseDto**](PersonResponseDto.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
15
mobile/openapi/doc/FaceDto.md
generated
15
mobile/openapi/doc/FaceDto.md
generated
|
@ -1,15 +0,0 @@
|
||||||
# openapi.model.FaceDto
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
110
mobile/openapi/doc/PersonApi.md
generated
110
mobile/openapi/doc/PersonApi.md
generated
|
@ -9,69 +9,16 @@ All URIs are relative to */api*
|
||||||
|
|
||||||
Method | HTTP request | Description
|
Method | HTTP request | Description
|
||||||
------------- | ------------- | -------------
|
------------- | ------------- | -------------
|
||||||
[**createPerson**](PersonApi.md#createperson) | **POST** /person |
|
|
||||||
[**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person |
|
[**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person |
|
||||||
[**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} |
|
[**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} |
|
||||||
[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
|
[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
|
||||||
[**getPersonStatistics**](PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
|
[**getPersonStatistics**](PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
|
||||||
[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
|
[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
|
||||||
[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
|
[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
|
||||||
[**reassignFaces**](PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
|
|
||||||
[**updatePeople**](PersonApi.md#updatepeople) | **PUT** /person |
|
[**updatePeople**](PersonApi.md#updatepeople) | **PUT** /person |
|
||||||
[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} |
|
[**updatePerson**](PersonApi.md#updateperson) | **PUT** /person/{id} |
|
||||||
|
|
||||||
|
|
||||||
# **createPerson**
|
|
||||||
> PersonResponseDto createPerson()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
// TODO Configure API key authorization: cookie
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure API key authorization: api_key
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure HTTP Bearer authorization: bearer
|
|
||||||
// Case 1. Use String Token
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
|
||||||
// Case 2. Use Function which generate token.
|
|
||||||
// String yourTokenGeneratorFunction() { ... }
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
|
||||||
|
|
||||||
final api_instance = PersonApi();
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = api_instance.createPerson();
|
|
||||||
print(result);
|
|
||||||
} catch (e) {
|
|
||||||
print('Exception when calling PersonApi->createPerson: $e\n');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
This endpoint does not need any parameter.
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**PersonResponseDto**](PersonResponseDto.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **getAllPeople**
|
# **getAllPeople**
|
||||||
> PeopleResponseDto getAllPeople(withHidden)
|
> PeopleResponseDto getAllPeople(withHidden)
|
||||||
|
|
||||||
|
@ -404,63 +351,6 @@ Name | Type | Description | Notes
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
# **reassignFaces**
|
|
||||||
> List<PersonResponseDto> reassignFaces(id, assetFaceUpdateDto)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Example
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
// TODO Configure API key authorization: cookie
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure API key authorization: api_key
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
|
|
||||||
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
|
|
||||||
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
|
|
||||||
// TODO Configure HTTP Bearer authorization: bearer
|
|
||||||
// Case 1. Use String Token
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
|
||||||
// Case 2. Use Function which generate token.
|
|
||||||
// String yourTokenGeneratorFunction() { ... }
|
|
||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
|
||||||
|
|
||||||
final api_instance = PersonApi();
|
|
||||||
final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
|
||||||
final assetFaceUpdateDto = AssetFaceUpdateDto(); // AssetFaceUpdateDto |
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = api_instance.reassignFaces(id, assetFaceUpdateDto);
|
|
||||||
print(result);
|
|
||||||
} catch (e) {
|
|
||||||
print('Exception when calling PersonApi->reassignFaces: $e\n');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------- | ------------- | ------------- | -------------
|
|
||||||
**id** | **String**| |
|
|
||||||
**assetFaceUpdateDto** | [**AssetFaceUpdateDto**](AssetFaceUpdateDto.md)| |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**List<PersonResponseDto>**](PersonResponseDto.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
# **updatePeople**
|
# **updatePeople**
|
||||||
> List<BulkIdResponseDto> updatePeople(peopleUpdateDto)
|
> List<BulkIdResponseDto> updatePeople(peopleUpdateDto)
|
||||||
|
|
||||||
|
|
20
mobile/openapi/doc/PersonWithFacesResponseDto.md
generated
20
mobile/openapi/doc/PersonWithFacesResponseDto.md
generated
|
@ -1,20 +0,0 @@
|
||||||
# openapi.model.PersonWithFacesResponseDto
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**birthDate** | [**DateTime**](DateTime.md) | |
|
|
||||||
**faces** | [**List<AssetFaceWithoutPersonResponseDto>**](AssetFaceWithoutPersonResponseDto.md) | | [default to const []]
|
|
||||||
**id** | **String** | |
|
|
||||||
**isHidden** | **bool** | |
|
|
||||||
**name** | **String** | |
|
|
||||||
**thumbnailPath** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
1
mobile/openapi/doc/UpdateAssetDto.md
generated
1
mobile/openapi/doc/UpdateAssetDto.md
generated
|
@ -14,6 +14,7 @@ Name | Type | Description | Notes
|
||||||
**isFavorite** | **bool** | | [optional]
|
**isFavorite** | **bool** | | [optional]
|
||||||
**latitude** | **num** | | [optional]
|
**latitude** | **num** | | [optional]
|
||||||
**longitude** | **num** | | [optional]
|
**longitude** | **num** | | [optional]
|
||||||
|
**orientation** | **num** | | [optional]
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
7
mobile/openapi/lib/api.dart
generated
7
mobile/openapi/lib/api.dart
generated
|
@ -34,7 +34,6 @@ part 'api/album_api.dart';
|
||||||
part 'api/asset_api.dart';
|
part 'api/asset_api.dart';
|
||||||
part 'api/audit_api.dart';
|
part 'api/audit_api.dart';
|
||||||
part 'api/authentication_api.dart';
|
part 'api/authentication_api.dart';
|
||||||
part 'api/face_api.dart';
|
|
||||||
part 'api/job_api.dart';
|
part 'api/job_api.dart';
|
||||||
part 'api/library_api.dart';
|
part 'api/library_api.dart';
|
||||||
part 'api/o_auth_api.dart';
|
part 'api/o_auth_api.dart';
|
||||||
|
@ -64,10 +63,6 @@ part 'model/asset_bulk_upload_check_dto.dart';
|
||||||
part 'model/asset_bulk_upload_check_item.dart';
|
part 'model/asset_bulk_upload_check_item.dart';
|
||||||
part 'model/asset_bulk_upload_check_response_dto.dart';
|
part 'model/asset_bulk_upload_check_response_dto.dart';
|
||||||
part 'model/asset_bulk_upload_check_result.dart';
|
part 'model/asset_bulk_upload_check_result.dart';
|
||||||
part 'model/asset_face_response_dto.dart';
|
|
||||||
part 'model/asset_face_update_dto.dart';
|
|
||||||
part 'model/asset_face_update_item.dart';
|
|
||||||
part 'model/asset_face_without_person_response_dto.dart';
|
|
||||||
part 'model/asset_file_upload_response_dto.dart';
|
part 'model/asset_file_upload_response_dto.dart';
|
||||||
part 'model/asset_ids_dto.dart';
|
part 'model/asset_ids_dto.dart';
|
||||||
part 'model/asset_ids_response_dto.dart';
|
part 'model/asset_ids_response_dto.dart';
|
||||||
|
@ -102,7 +97,6 @@ part 'model/download_info_dto.dart';
|
||||||
part 'model/download_response_dto.dart';
|
part 'model/download_response_dto.dart';
|
||||||
part 'model/entity_type.dart';
|
part 'model/entity_type.dart';
|
||||||
part 'model/exif_response_dto.dart';
|
part 'model/exif_response_dto.dart';
|
||||||
part 'model/face_dto.dart';
|
|
||||||
part 'model/file_checksum_dto.dart';
|
part 'model/file_checksum_dto.dart';
|
||||||
part 'model/file_checksum_response_dto.dart';
|
part 'model/file_checksum_response_dto.dart';
|
||||||
part 'model/file_report_dto.dart';
|
part 'model/file_report_dto.dart';
|
||||||
|
@ -138,7 +132,6 @@ part 'model/people_update_item.dart';
|
||||||
part 'model/person_response_dto.dart';
|
part 'model/person_response_dto.dart';
|
||||||
part 'model/person_statistics_response_dto.dart';
|
part 'model/person_statistics_response_dto.dart';
|
||||||
part 'model/person_update_dto.dart';
|
part 'model/person_update_dto.dart';
|
||||||
part 'model/person_with_faces_response_dto.dart';
|
|
||||||
part 'model/queue_status_dto.dart';
|
part 'model/queue_status_dto.dart';
|
||||||
part 'model/reaction_level.dart';
|
part 'model/reaction_level.dart';
|
||||||
part 'model/reaction_type.dart';
|
part 'model/reaction_type.dart';
|
||||||
|
|
122
mobile/openapi/lib/api/face_api.dart
generated
122
mobile/openapi/lib/api/face_api.dart
generated
|
@ -1,122 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
|
|
||||||
class FaceApi {
|
|
||||||
FaceApi([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
|
||||||
|
|
||||||
final ApiClient apiClient;
|
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /face' operation and returns the [Response].
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
Future<Response> getFacesWithHttpInfo(String id,) async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final path = r'/face';
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
queryParams.addAll(_queryParams('', 'id', id));
|
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
|
||||||
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
path,
|
|
||||||
'GET',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
Future<List<AssetFaceResponseDto>?> getFaces(String id,) async {
|
|
||||||
final response = await getFacesWithHttpInfo(id,);
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// 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) {
|
|
||||||
final responseBody = await _decodeBodyBytes(response);
|
|
||||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetFaceResponseDto>') as List)
|
|
||||||
.cast<AssetFaceResponseDto>()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs an HTTP 'PUT /face/{id}' operation and returns the [Response].
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [FaceDto] faceDto (required):
|
|
||||||
Future<Response> reassignFacesByIdWithHttpInfo(String id, FaceDto faceDto,) async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final path = r'/face/{id}'
|
|
||||||
.replaceAll('{id}', id);
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody = faceDto;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
const contentTypes = <String>['application/json'];
|
|
||||||
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
path,
|
|
||||||
'PUT',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [FaceDto] faceDto (required):
|
|
||||||
Future<PersonResponseDto?> reassignFacesById(String id, FaceDto faceDto,) async {
|
|
||||||
final response = await reassignFacesByIdWithHttpInfo(id, faceDto,);
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// 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) {
|
|
||||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto;
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
96
mobile/openapi/lib/api/person_api.dart
generated
96
mobile/openapi/lib/api/person_api.dart
generated
|
@ -16,47 +16,6 @@ class PersonApi {
|
||||||
|
|
||||||
final ApiClient apiClient;
|
final ApiClient apiClient;
|
||||||
|
|
||||||
/// Performs an HTTP 'POST /person' operation and returns the [Response].
|
|
||||||
Future<Response> createPersonWithHttpInfo() async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final path = r'/person';
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
|
||||||
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
path,
|
|
||||||
'POST',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<PersonResponseDto?> createPerson() async {
|
|
||||||
final response = await createPersonWithHttpInfo();
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// 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) {
|
|
||||||
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'PersonResponseDto',) as PersonResponseDto;
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /person' operation and returns the [Response].
|
/// Performs an HTTP 'GET /person' operation and returns the [Response].
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
|
@ -358,61 +317,6 @@ class PersonApi {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'PUT /person/{id}/reassign' operation and returns the [Response].
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
|
|
||||||
Future<Response> reassignFacesWithHttpInfo(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final path = r'/person/{id}/reassign'
|
|
||||||
.replaceAll('{id}', id);
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody = assetFaceUpdateDto;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
const contentTypes = <String>['application/json'];
|
|
||||||
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
path,
|
|
||||||
'PUT',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Parameters:
|
|
||||||
///
|
|
||||||
/// * [String] id (required):
|
|
||||||
///
|
|
||||||
/// * [AssetFaceUpdateDto] assetFaceUpdateDto (required):
|
|
||||||
Future<List<PersonResponseDto>?> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto,) async {
|
|
||||||
final response = await reassignFacesWithHttpInfo(id, assetFaceUpdateDto,);
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// 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) {
|
|
||||||
final responseBody = await _decodeBodyBytes(response);
|
|
||||||
return (await apiClient.deserializeAsync(responseBody, 'List<PersonResponseDto>') as List)
|
|
||||||
.cast<PersonResponseDto>()
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs an HTTP 'PUT /person' operation and returns the [Response].
|
/// Performs an HTTP 'PUT /person' operation and returns the [Response].
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
|
|
12
mobile/openapi/lib/api_client.dart
generated
12
mobile/openapi/lib/api_client.dart
generated
|
@ -215,14 +215,6 @@ class ApiClient {
|
||||||
return AssetBulkUploadCheckResponseDto.fromJson(value);
|
return AssetBulkUploadCheckResponseDto.fromJson(value);
|
||||||
case 'AssetBulkUploadCheckResult':
|
case 'AssetBulkUploadCheckResult':
|
||||||
return AssetBulkUploadCheckResult.fromJson(value);
|
return AssetBulkUploadCheckResult.fromJson(value);
|
||||||
case 'AssetFaceResponseDto':
|
|
||||||
return AssetFaceResponseDto.fromJson(value);
|
|
||||||
case 'AssetFaceUpdateDto':
|
|
||||||
return AssetFaceUpdateDto.fromJson(value);
|
|
||||||
case 'AssetFaceUpdateItem':
|
|
||||||
return AssetFaceUpdateItem.fromJson(value);
|
|
||||||
case 'AssetFaceWithoutPersonResponseDto':
|
|
||||||
return AssetFaceWithoutPersonResponseDto.fromJson(value);
|
|
||||||
case 'AssetFileUploadResponseDto':
|
case 'AssetFileUploadResponseDto':
|
||||||
return AssetFileUploadResponseDto.fromJson(value);
|
return AssetFileUploadResponseDto.fromJson(value);
|
||||||
case 'AssetIdsDto':
|
case 'AssetIdsDto':
|
||||||
|
@ -291,8 +283,6 @@ class ApiClient {
|
||||||
return EntityTypeTypeTransformer().decode(value);
|
return EntityTypeTypeTransformer().decode(value);
|
||||||
case 'ExifResponseDto':
|
case 'ExifResponseDto':
|
||||||
return ExifResponseDto.fromJson(value);
|
return ExifResponseDto.fromJson(value);
|
||||||
case 'FaceDto':
|
|
||||||
return FaceDto.fromJson(value);
|
|
||||||
case 'FileChecksumDto':
|
case 'FileChecksumDto':
|
||||||
return FileChecksumDto.fromJson(value);
|
return FileChecksumDto.fromJson(value);
|
||||||
case 'FileChecksumResponseDto':
|
case 'FileChecksumResponseDto':
|
||||||
|
@ -363,8 +353,6 @@ class ApiClient {
|
||||||
return PersonStatisticsResponseDto.fromJson(value);
|
return PersonStatisticsResponseDto.fromJson(value);
|
||||||
case 'PersonUpdateDto':
|
case 'PersonUpdateDto':
|
||||||
return PersonUpdateDto.fromJson(value);
|
return PersonUpdateDto.fromJson(value);
|
||||||
case 'PersonWithFacesResponseDto':
|
|
||||||
return PersonWithFacesResponseDto.fromJson(value);
|
|
||||||
case 'QueueStatusDto':
|
case 'QueueStatusDto':
|
||||||
return QueueStatusDto.fromJson(value);
|
return QueueStatusDto.fromJson(value);
|
||||||
case 'ReactionLevel':
|
case 'ReactionLevel':
|
||||||
|
|
21
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
21
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
|
@ -19,6 +19,7 @@ class AssetBulkUpdateDto {
|
||||||
this.isFavorite,
|
this.isFavorite,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
|
this.orientation,
|
||||||
this.removeParent,
|
this.removeParent,
|
||||||
this.stackParentId,
|
this.stackParentId,
|
||||||
});
|
});
|
||||||
|
@ -65,6 +66,14 @@ class AssetBulkUpdateDto {
|
||||||
///
|
///
|
||||||
num? longitude;
|
num? longitude;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
num? orientation;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
@ -89,6 +98,7 @@ class AssetBulkUpdateDto {
|
||||||
other.isFavorite == isFavorite &&
|
other.isFavorite == isFavorite &&
|
||||||
other.latitude == latitude &&
|
other.latitude == latitude &&
|
||||||
other.longitude == longitude &&
|
other.longitude == longitude &&
|
||||||
|
other.orientation == orientation &&
|
||||||
other.removeParent == removeParent &&
|
other.removeParent == removeParent &&
|
||||||
other.stackParentId == stackParentId;
|
other.stackParentId == stackParentId;
|
||||||
|
|
||||||
|
@ -101,11 +111,12 @@ class AssetBulkUpdateDto {
|
||||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||||
(latitude == null ? 0 : latitude!.hashCode) +
|
(latitude == null ? 0 : latitude!.hashCode) +
|
||||||
(longitude == null ? 0 : longitude!.hashCode) +
|
(longitude == null ? 0 : longitude!.hashCode) +
|
||||||
|
(orientation == null ? 0 : orientation!.hashCode) +
|
||||||
(removeParent == null ? 0 : removeParent!.hashCode) +
|
(removeParent == null ? 0 : removeParent!.hashCode) +
|
||||||
(stackParentId == null ? 0 : stackParentId!.hashCode);
|
(stackParentId == null ? 0 : stackParentId!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, removeParent=$removeParent, stackParentId=$stackParentId]';
|
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, orientation=$orientation, removeParent=$removeParent, stackParentId=$stackParentId]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
@ -135,6 +146,11 @@ class AssetBulkUpdateDto {
|
||||||
} else {
|
} else {
|
||||||
// json[r'longitude'] = null;
|
// json[r'longitude'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.orientation != null) {
|
||||||
|
json[r'orientation'] = this.orientation;
|
||||||
|
} else {
|
||||||
|
// json[r'orientation'] = null;
|
||||||
|
}
|
||||||
if (this.removeParent != null) {
|
if (this.removeParent != null) {
|
||||||
json[r'removeParent'] = this.removeParent;
|
json[r'removeParent'] = this.removeParent;
|
||||||
} else {
|
} else {
|
||||||
|
@ -168,6 +184,9 @@ class AssetBulkUpdateDto {
|
||||||
longitude: json[r'longitude'] == null
|
longitude: json[r'longitude'] == null
|
||||||
? null
|
? null
|
||||||
: num.parse(json[r'longitude'].toString()),
|
: num.parse(json[r'longitude'].toString()),
|
||||||
|
orientation: json[r'orientation'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'orientation'].toString()),
|
||||||
removeParent: mapValueOfType<bool>(json, r'removeParent'),
|
removeParent: mapValueOfType<bool>(json, r'removeParent'),
|
||||||
stackParentId: mapValueOfType<String>(json, r'stackParentId'),
|
stackParentId: mapValueOfType<String>(json, r'stackParentId'),
|
||||||
);
|
);
|
||||||
|
|
158
mobile/openapi/lib/model/asset_face_response_dto.dart
generated
158
mobile/openapi/lib/model/asset_face_response_dto.dart
generated
|
@ -1,158 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
class AssetFaceResponseDto {
|
|
||||||
/// Returns a new [AssetFaceResponseDto] instance.
|
|
||||||
AssetFaceResponseDto({
|
|
||||||
required this.boundingBoxX1,
|
|
||||||
required this.boundingBoxX2,
|
|
||||||
required this.boundingBoxY1,
|
|
||||||
required this.boundingBoxY2,
|
|
||||||
required this.id,
|
|
||||||
required this.imageHeight,
|
|
||||||
required this.imageWidth,
|
|
||||||
required this.person,
|
|
||||||
});
|
|
||||||
|
|
||||||
int boundingBoxX1;
|
|
||||||
|
|
||||||
int boundingBoxX2;
|
|
||||||
|
|
||||||
int boundingBoxY1;
|
|
||||||
|
|
||||||
int boundingBoxY2;
|
|
||||||
|
|
||||||
String id;
|
|
||||||
|
|
||||||
int imageHeight;
|
|
||||||
|
|
||||||
int imageWidth;
|
|
||||||
|
|
||||||
PersonResponseDto? person;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetFaceResponseDto &&
|
|
||||||
other.boundingBoxX1 == boundingBoxX1 &&
|
|
||||||
other.boundingBoxX2 == boundingBoxX2 &&
|
|
||||||
other.boundingBoxY1 == boundingBoxY1 &&
|
|
||||||
other.boundingBoxY2 == boundingBoxY2 &&
|
|
||||||
other.id == id &&
|
|
||||||
other.imageHeight == imageHeight &&
|
|
||||||
other.imageWidth == imageWidth &&
|
|
||||||
other.person == person;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(boundingBoxX1.hashCode) +
|
|
||||||
(boundingBoxX2.hashCode) +
|
|
||||||
(boundingBoxY1.hashCode) +
|
|
||||||
(boundingBoxY2.hashCode) +
|
|
||||||
(id.hashCode) +
|
|
||||||
(imageHeight.hashCode) +
|
|
||||||
(imageWidth.hashCode) +
|
|
||||||
(person == null ? 0 : person!.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AssetFaceResponseDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth, person=$person]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'boundingBoxX1'] = this.boundingBoxX1;
|
|
||||||
json[r'boundingBoxX2'] = this.boundingBoxX2;
|
|
||||||
json[r'boundingBoxY1'] = this.boundingBoxY1;
|
|
||||||
json[r'boundingBoxY2'] = this.boundingBoxY2;
|
|
||||||
json[r'id'] = this.id;
|
|
||||||
json[r'imageHeight'] = this.imageHeight;
|
|
||||||
json[r'imageWidth'] = this.imageWidth;
|
|
||||||
if (this.person != null) {
|
|
||||||
json[r'person'] = this.person;
|
|
||||||
} else {
|
|
||||||
// json[r'person'] = null;
|
|
||||||
}
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [AssetFaceResponseDto] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static AssetFaceResponseDto? fromJson(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return AssetFaceResponseDto(
|
|
||||||
boundingBoxX1: mapValueOfType<int>(json, r'boundingBoxX1')!,
|
|
||||||
boundingBoxX2: mapValueOfType<int>(json, r'boundingBoxX2')!,
|
|
||||||
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
|
|
||||||
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
|
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
|
||||||
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
|
|
||||||
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
|
|
||||||
person: PersonResponseDto.fromJson(json[r'person']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<AssetFaceResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <AssetFaceResponseDto>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = AssetFaceResponseDto.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, AssetFaceResponseDto> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, AssetFaceResponseDto>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = AssetFaceResponseDto.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of AssetFaceResponseDto-objects as value to a dart map
|
|
||||||
static Map<String, List<AssetFaceResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<AssetFaceResponseDto>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = AssetFaceResponseDto.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'boundingBoxX1',
|
|
||||||
'boundingBoxX2',
|
|
||||||
'boundingBoxY1',
|
|
||||||
'boundingBoxY2',
|
|
||||||
'id',
|
|
||||||
'imageHeight',
|
|
||||||
'imageWidth',
|
|
||||||
'person',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
98
mobile/openapi/lib/model/asset_face_update_dto.dart
generated
98
mobile/openapi/lib/model/asset_face_update_dto.dart
generated
|
@ -1,98 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
class AssetFaceUpdateDto {
|
|
||||||
/// Returns a new [AssetFaceUpdateDto] instance.
|
|
||||||
AssetFaceUpdateDto({
|
|
||||||
this.data = const [],
|
|
||||||
});
|
|
||||||
|
|
||||||
List<AssetFaceUpdateItem> data;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetFaceUpdateDto &&
|
|
||||||
other.data == data;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(data.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AssetFaceUpdateDto[data=$data]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'data'] = this.data;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [AssetFaceUpdateDto] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static AssetFaceUpdateDto? fromJson(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return AssetFaceUpdateDto(
|
|
||||||
data: AssetFaceUpdateItem.listFromJson(json[r'data']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<AssetFaceUpdateDto> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <AssetFaceUpdateDto>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = AssetFaceUpdateDto.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, AssetFaceUpdateDto> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, AssetFaceUpdateDto>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = AssetFaceUpdateDto.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of AssetFaceUpdateDto-objects as value to a dart map
|
|
||||||
static Map<String, List<AssetFaceUpdateDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<AssetFaceUpdateDto>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = AssetFaceUpdateDto.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'data',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
106
mobile/openapi/lib/model/asset_face_update_item.dart
generated
106
mobile/openapi/lib/model/asset_face_update_item.dart
generated
|
@ -1,106 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
class AssetFaceUpdateItem {
|
|
||||||
/// Returns a new [AssetFaceUpdateItem] instance.
|
|
||||||
AssetFaceUpdateItem({
|
|
||||||
required this.assetId,
|
|
||||||
required this.personId,
|
|
||||||
});
|
|
||||||
|
|
||||||
String assetId;
|
|
||||||
|
|
||||||
String personId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetFaceUpdateItem &&
|
|
||||||
other.assetId == assetId &&
|
|
||||||
other.personId == personId;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(assetId.hashCode) +
|
|
||||||
(personId.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AssetFaceUpdateItem[assetId=$assetId, personId=$personId]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'assetId'] = this.assetId;
|
|
||||||
json[r'personId'] = this.personId;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [AssetFaceUpdateItem] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static AssetFaceUpdateItem? fromJson(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return AssetFaceUpdateItem(
|
|
||||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
|
||||||
personId: mapValueOfType<String>(json, r'personId')!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<AssetFaceUpdateItem> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <AssetFaceUpdateItem>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = AssetFaceUpdateItem.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, AssetFaceUpdateItem> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, AssetFaceUpdateItem>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = AssetFaceUpdateItem.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of AssetFaceUpdateItem-objects as value to a dart map
|
|
||||||
static Map<String, List<AssetFaceUpdateItem>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<AssetFaceUpdateItem>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = AssetFaceUpdateItem.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'assetId',
|
|
||||||
'personId',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,146 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
class AssetFaceWithoutPersonResponseDto {
|
|
||||||
/// Returns a new [AssetFaceWithoutPersonResponseDto] instance.
|
|
||||||
AssetFaceWithoutPersonResponseDto({
|
|
||||||
required this.boundingBoxX1,
|
|
||||||
required this.boundingBoxX2,
|
|
||||||
required this.boundingBoxY1,
|
|
||||||
required this.boundingBoxY2,
|
|
||||||
required this.id,
|
|
||||||
required this.imageHeight,
|
|
||||||
required this.imageWidth,
|
|
||||||
});
|
|
||||||
|
|
||||||
int boundingBoxX1;
|
|
||||||
|
|
||||||
int boundingBoxX2;
|
|
||||||
|
|
||||||
int boundingBoxY1;
|
|
||||||
|
|
||||||
int boundingBoxY2;
|
|
||||||
|
|
||||||
String id;
|
|
||||||
|
|
||||||
int imageHeight;
|
|
||||||
|
|
||||||
int imageWidth;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetFaceWithoutPersonResponseDto &&
|
|
||||||
other.boundingBoxX1 == boundingBoxX1 &&
|
|
||||||
other.boundingBoxX2 == boundingBoxX2 &&
|
|
||||||
other.boundingBoxY1 == boundingBoxY1 &&
|
|
||||||
other.boundingBoxY2 == boundingBoxY2 &&
|
|
||||||
other.id == id &&
|
|
||||||
other.imageHeight == imageHeight &&
|
|
||||||
other.imageWidth == imageWidth;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(boundingBoxX1.hashCode) +
|
|
||||||
(boundingBoxX2.hashCode) +
|
|
||||||
(boundingBoxY1.hashCode) +
|
|
||||||
(boundingBoxY2.hashCode) +
|
|
||||||
(id.hashCode) +
|
|
||||||
(imageHeight.hashCode) +
|
|
||||||
(imageWidth.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AssetFaceWithoutPersonResponseDto[boundingBoxX1=$boundingBoxX1, boundingBoxX2=$boundingBoxX2, boundingBoxY1=$boundingBoxY1, boundingBoxY2=$boundingBoxY2, id=$id, imageHeight=$imageHeight, imageWidth=$imageWidth]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'boundingBoxX1'] = this.boundingBoxX1;
|
|
||||||
json[r'boundingBoxX2'] = this.boundingBoxX2;
|
|
||||||
json[r'boundingBoxY1'] = this.boundingBoxY1;
|
|
||||||
json[r'boundingBoxY2'] = this.boundingBoxY2;
|
|
||||||
json[r'id'] = this.id;
|
|
||||||
json[r'imageHeight'] = this.imageHeight;
|
|
||||||
json[r'imageWidth'] = this.imageWidth;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [AssetFaceWithoutPersonResponseDto] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static AssetFaceWithoutPersonResponseDto? fromJson(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return AssetFaceWithoutPersonResponseDto(
|
|
||||||
boundingBoxX1: mapValueOfType<int>(json, r'boundingBoxX1')!,
|
|
||||||
boundingBoxX2: mapValueOfType<int>(json, r'boundingBoxX2')!,
|
|
||||||
boundingBoxY1: mapValueOfType<int>(json, r'boundingBoxY1')!,
|
|
||||||
boundingBoxY2: mapValueOfType<int>(json, r'boundingBoxY2')!,
|
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
|
||||||
imageHeight: mapValueOfType<int>(json, r'imageHeight')!,
|
|
||||||
imageWidth: mapValueOfType<int>(json, r'imageWidth')!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<AssetFaceWithoutPersonResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <AssetFaceWithoutPersonResponseDto>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = AssetFaceWithoutPersonResponseDto.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, AssetFaceWithoutPersonResponseDto> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, AssetFaceWithoutPersonResponseDto>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = AssetFaceWithoutPersonResponseDto.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of AssetFaceWithoutPersonResponseDto-objects as value to a dart map
|
|
||||||
static Map<String, List<AssetFaceWithoutPersonResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<AssetFaceWithoutPersonResponseDto>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = AssetFaceWithoutPersonResponseDto.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'boundingBoxX1',
|
|
||||||
'boundingBoxX2',
|
|
||||||
'boundingBoxY1',
|
|
||||||
'boundingBoxY2',
|
|
||||||
'id',
|
|
||||||
'imageHeight',
|
|
||||||
'imageWidth',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
4
mobile/openapi/lib/model/asset_response_dto.dart
generated
4
mobile/openapi/lib/model/asset_response_dto.dart
generated
|
@ -104,7 +104,7 @@ class AssetResponseDto {
|
||||||
|
|
||||||
String ownerId;
|
String ownerId;
|
||||||
|
|
||||||
List<PersonWithFacesResponseDto> people;
|
List<PersonResponseDto> people;
|
||||||
|
|
||||||
bool resized;
|
bool resized;
|
||||||
|
|
||||||
|
@ -299,7 +299,7 @@ class AssetResponseDto {
|
||||||
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
||||||
owner: UserResponseDto.fromJson(json[r'owner']),
|
owner: UserResponseDto.fromJson(json[r'owner']),
|
||||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||||
people: PersonWithFacesResponseDto.listFromJson(json[r'people']),
|
people: PersonResponseDto.listFromJson(json[r'people']),
|
||||||
resized: mapValueOfType<bool>(json, r'resized')!,
|
resized: mapValueOfType<bool>(json, r'resized')!,
|
||||||
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
|
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
|
||||||
stack: AssetResponseDto.listFromJson(json[r'stack']),
|
stack: AssetResponseDto.listFromJson(json[r'stack']),
|
||||||
|
|
98
mobile/openapi/lib/model/face_dto.dart
generated
98
mobile/openapi/lib/model/face_dto.dart
generated
|
@ -1,98 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
class FaceDto {
|
|
||||||
/// Returns a new [FaceDto] instance.
|
|
||||||
FaceDto({
|
|
||||||
required this.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
String id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is FaceDto &&
|
|
||||||
other.id == id;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(id.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'FaceDto[id=$id]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
json[r'id'] = this.id;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [FaceDto] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static FaceDto? fromJson(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return FaceDto(
|
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<FaceDto> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <FaceDto>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = FaceDto.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, FaceDto> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, FaceDto>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = FaceDto.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of FaceDto-objects as value to a dart map
|
|
||||||
static Map<String, List<FaceDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<FaceDto>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = FaceDto.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'id',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
3
mobile/openapi/lib/model/job_command.dart
generated
3
mobile/openapi/lib/model/job_command.dart
generated
|
@ -27,7 +27,6 @@ class JobCommand {
|
||||||
static const pause = JobCommand._(r'pause');
|
static const pause = JobCommand._(r'pause');
|
||||||
static const resume = JobCommand._(r'resume');
|
static const resume = JobCommand._(r'resume');
|
||||||
static const empty = JobCommand._(r'empty');
|
static const empty = JobCommand._(r'empty');
|
||||||
static const clearFailed = JobCommand._(r'clear-failed');
|
|
||||||
|
|
||||||
/// List of all possible values in this [enum][JobCommand].
|
/// List of all possible values in this [enum][JobCommand].
|
||||||
static const values = <JobCommand>[
|
static const values = <JobCommand>[
|
||||||
|
@ -35,7 +34,6 @@ class JobCommand {
|
||||||
pause,
|
pause,
|
||||||
resume,
|
resume,
|
||||||
empty,
|
empty,
|
||||||
clearFailed,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
static JobCommand? fromJson(dynamic value) => JobCommandTypeTransformer().decode(value);
|
static JobCommand? fromJson(dynamic value) => JobCommandTypeTransformer().decode(value);
|
||||||
|
@ -78,7 +76,6 @@ class JobCommandTypeTransformer {
|
||||||
case r'pause': return JobCommand.pause;
|
case r'pause': return JobCommand.pause;
|
||||||
case r'resume': return JobCommand.resume;
|
case r'resume': return JobCommand.resume;
|
||||||
case r'empty': return JobCommand.empty;
|
case r'empty': return JobCommand.empty;
|
||||||
case r'clear-failed': return JobCommand.clearFailed;
|
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
part of openapi.api;
|
|
||||||
|
|
||||||
class PersonWithFacesResponseDto {
|
|
||||||
/// Returns a new [PersonWithFacesResponseDto] instance.
|
|
||||||
PersonWithFacesResponseDto({
|
|
||||||
required this.birthDate,
|
|
||||||
this.faces = const [],
|
|
||||||
required this.id,
|
|
||||||
required this.isHidden,
|
|
||||||
required this.name,
|
|
||||||
required this.thumbnailPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
DateTime? birthDate;
|
|
||||||
|
|
||||||
List<AssetFaceWithoutPersonResponseDto> faces;
|
|
||||||
|
|
||||||
String id;
|
|
||||||
|
|
||||||
bool isHidden;
|
|
||||||
|
|
||||||
String name;
|
|
||||||
|
|
||||||
String thumbnailPath;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) => identical(this, other) || other is PersonWithFacesResponseDto &&
|
|
||||||
other.birthDate == birthDate &&
|
|
||||||
other.faces == faces &&
|
|
||||||
other.id == id &&
|
|
||||||
other.isHidden == isHidden &&
|
|
||||||
other.name == name &&
|
|
||||||
other.thumbnailPath == thumbnailPath;
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
// ignore: unnecessary_parenthesis
|
|
||||||
(birthDate == null ? 0 : birthDate!.hashCode) +
|
|
||||||
(faces.hashCode) +
|
|
||||||
(id.hashCode) +
|
|
||||||
(isHidden.hashCode) +
|
|
||||||
(name.hashCode) +
|
|
||||||
(thumbnailPath.hashCode);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'PersonWithFacesResponseDto[birthDate=$birthDate, faces=$faces, id=$id, isHidden=$isHidden, name=$name, thumbnailPath=$thumbnailPath]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
|
||||||
final json = <String, dynamic>{};
|
|
||||||
if (this.birthDate != null) {
|
|
||||||
json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc());
|
|
||||||
} else {
|
|
||||||
// json[r'birthDate'] = null;
|
|
||||||
}
|
|
||||||
json[r'faces'] = this.faces;
|
|
||||||
json[r'id'] = this.id;
|
|
||||||
json[r'isHidden'] = this.isHidden;
|
|
||||||
json[r'name'] = this.name;
|
|
||||||
json[r'thumbnailPath'] = this.thumbnailPath;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new [PersonWithFacesResponseDto] instance and imports its values from
|
|
||||||
/// [value] if it's a [Map], null otherwise.
|
|
||||||
// ignore: prefer_constructors_over_static_methods
|
|
||||||
static PersonWithFacesResponseDto? fromJson(dynamic value) {
|
|
||||||
if (value is Map) {
|
|
||||||
final json = value.cast<String, dynamic>();
|
|
||||||
|
|
||||||
return PersonWithFacesResponseDto(
|
|
||||||
birthDate: mapDateTime(json, r'birthDate', ''),
|
|
||||||
faces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'faces']),
|
|
||||||
id: mapValueOfType<String>(json, r'id')!,
|
|
||||||
isHidden: mapValueOfType<bool>(json, r'isHidden')!,
|
|
||||||
name: mapValueOfType<String>(json, r'name')!,
|
|
||||||
thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath')!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static List<PersonWithFacesResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <PersonWithFacesResponseDto>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = PersonWithFacesResponseDto.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Map<String, PersonWithFacesResponseDto> mapFromJson(dynamic json) {
|
|
||||||
final map = <String, PersonWithFacesResponseDto>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
final value = PersonWithFacesResponseDto.fromJson(entry.value);
|
|
||||||
if (value != null) {
|
|
||||||
map[entry.key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
// maps a json object with a list of PersonWithFacesResponseDto-objects as value to a dart map
|
|
||||||
static Map<String, List<PersonWithFacesResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final map = <String, List<PersonWithFacesResponseDto>>{};
|
|
||||||
if (json is Map && json.isNotEmpty) {
|
|
||||||
// ignore: parameter_assignments
|
|
||||||
json = json.cast<String, dynamic>();
|
|
||||||
for (final entry in json.entries) {
|
|
||||||
map[entry.key] = PersonWithFacesResponseDto.listFromJson(entry.value, growable: growable,);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
|
||||||
static const requiredKeys = <String>{
|
|
||||||
'birthDate',
|
|
||||||
'faces',
|
|
||||||
'id',
|
|
||||||
'isHidden',
|
|
||||||
'name',
|
|
||||||
'thumbnailPath',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
25
mobile/openapi/lib/model/update_asset_dto.dart
generated
25
mobile/openapi/lib/model/update_asset_dto.dart
generated
|
@ -19,6 +19,7 @@ class UpdateAssetDto {
|
||||||
this.isFavorite,
|
this.isFavorite,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
|
this.orientation,
|
||||||
});
|
});
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -69,6 +70,14 @@ class UpdateAssetDto {
|
||||||
///
|
///
|
||||||
num? longitude;
|
num? longitude;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
num? orientation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
||||||
other.dateTimeOriginal == dateTimeOriginal &&
|
other.dateTimeOriginal == dateTimeOriginal &&
|
||||||
|
@ -76,7 +85,8 @@ class UpdateAssetDto {
|
||||||
other.isArchived == isArchived &&
|
other.isArchived == isArchived &&
|
||||||
other.isFavorite == isFavorite &&
|
other.isFavorite == isFavorite &&
|
||||||
other.latitude == latitude &&
|
other.latitude == latitude &&
|
||||||
other.longitude == longitude;
|
other.longitude == longitude &&
|
||||||
|
other.orientation == orientation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
|
@ -86,10 +96,11 @@ class UpdateAssetDto {
|
||||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||||
(latitude == null ? 0 : latitude!.hashCode) +
|
(latitude == null ? 0 : latitude!.hashCode) +
|
||||||
(longitude == null ? 0 : longitude!.hashCode);
|
(longitude == null ? 0 : longitude!.hashCode) +
|
||||||
|
(orientation == null ? 0 : orientation!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude]';
|
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, orientation=$orientation]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
@ -123,6 +134,11 @@ class UpdateAssetDto {
|
||||||
} else {
|
} else {
|
||||||
// json[r'longitude'] = null;
|
// json[r'longitude'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.orientation != null) {
|
||||||
|
json[r'orientation'] = this.orientation;
|
||||||
|
} else {
|
||||||
|
// json[r'orientation'] = null;
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +160,9 @@ class UpdateAssetDto {
|
||||||
longitude: json[r'longitude'] == null
|
longitude: json[r'longitude'] == null
|
||||||
? null
|
? null
|
||||||
: num.parse(json[r'longitude'].toString()),
|
: num.parse(json[r'longitude'].toString()),
|
||||||
|
orientation: json[r'orientation'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'orientation'].toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -46,6 +46,11 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// num orientation
|
||||||
|
test('to test the property `orientation`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// bool removeParent
|
// bool removeParent
|
||||||
test('to test the property `removeParent`', () async {
|
test('to test the property `removeParent`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for AssetFaceResponseDto
|
|
||||||
void main() {
|
|
||||||
// final instance = AssetFaceResponseDto();
|
|
||||||
|
|
||||||
group('test AssetFaceResponseDto', () {
|
|
||||||
// int boundingBoxX1
|
|
||||||
test('to test the property `boundingBoxX1`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int boundingBoxX2
|
|
||||||
test('to test the property `boundingBoxX2`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int boundingBoxY1
|
|
||||||
test('to test the property `boundingBoxY1`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int boundingBoxY2
|
|
||||||
test('to test the property `boundingBoxY2`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String id
|
|
||||||
test('to test the property `id`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int imageHeight
|
|
||||||
test('to test the property `imageHeight`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int imageWidth
|
|
||||||
test('to test the property `imageWidth`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// PersonResponseDto person
|
|
||||||
test('to test the property `person`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
27
mobile/openapi/test/asset_face_update_dto_test.dart
generated
27
mobile/openapi/test/asset_face_update_dto_test.dart
generated
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for AssetFaceUpdateDto
|
|
||||||
void main() {
|
|
||||||
// final instance = AssetFaceUpdateDto();
|
|
||||||
|
|
||||||
group('test AssetFaceUpdateDto', () {
|
|
||||||
// List<AssetFaceUpdateItem> data (default value: const [])
|
|
||||||
test('to test the property `data`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
32
mobile/openapi/test/asset_face_update_item_test.dart
generated
32
mobile/openapi/test/asset_face_update_item_test.dart
generated
|
@ -1,32 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for AssetFaceUpdateItem
|
|
||||||
void main() {
|
|
||||||
// final instance = AssetFaceUpdateItem();
|
|
||||||
|
|
||||||
group('test AssetFaceUpdateItem', () {
|
|
||||||
// String assetId
|
|
||||||
test('to test the property `assetId`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String personId
|
|
||||||
test('to test the property `personId`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for AssetFaceWithoutPersonResponseDto
|
|
||||||
void main() {
|
|
||||||
// final instance = AssetFaceWithoutPersonResponseDto();
|
|
||||||
|
|
||||||
group('test AssetFaceWithoutPersonResponseDto', () {
|
|
||||||
// int boundingBoxX1
|
|
||||||
test('to test the property `boundingBoxX1`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int boundingBoxX2
|
|
||||||
test('to test the property `boundingBoxX2`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int boundingBoxY1
|
|
||||||
test('to test the property `boundingBoxY1`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int boundingBoxY2
|
|
||||||
test('to test the property `boundingBoxY2`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String id
|
|
||||||
test('to test the property `id`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int imageHeight
|
|
||||||
test('to test the property `imageHeight`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// int imageWidth
|
|
||||||
test('to test the property `imageWidth`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
2
mobile/openapi/test/asset_response_dto_test.dart
generated
2
mobile/openapi/test/asset_response_dto_test.dart
generated
|
@ -127,7 +127,7 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
// List<PersonWithFacesResponseDto> people (default value: const [])
|
// List<PersonResponseDto> people (default value: const [])
|
||||||
test('to test the property `people`', () async {
|
test('to test the property `people`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
31
mobile/openapi/test/face_api_test.dart
generated
31
mobile/openapi/test/face_api_test.dart
generated
|
@ -1,31 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
|
|
||||||
/// tests for FaceApi
|
|
||||||
void main() {
|
|
||||||
// final instance = FaceApi();
|
|
||||||
|
|
||||||
group('tests for FaceApi', () {
|
|
||||||
//Future<List<AssetFaceResponseDto>> getFaces(String id) async
|
|
||||||
test('test getFaces', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
//Future<PersonResponseDto> reassignFacesById(String id, FaceDto faceDto) async
|
|
||||||
test('test reassignFacesById', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
}
|
|
27
mobile/openapi/test/face_dto_test.dart
generated
27
mobile/openapi/test/face_dto_test.dart
generated
|
@ -1,27 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for FaceDto
|
|
||||||
void main() {
|
|
||||||
// final instance = FaceDto();
|
|
||||||
|
|
||||||
group('test FaceDto', () {
|
|
||||||
// String id
|
|
||||||
test('to test the property `id`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
10
mobile/openapi/test/person_api_test.dart
generated
10
mobile/openapi/test/person_api_test.dart
generated
|
@ -17,11 +17,6 @@ void main() {
|
||||||
// final instance = PersonApi();
|
// final instance = PersonApi();
|
||||||
|
|
||||||
group('tests for PersonApi', () {
|
group('tests for PersonApi', () {
|
||||||
//Future<PersonResponseDto> createPerson() async
|
|
||||||
test('test createPerson', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
//Future<PeopleResponseDto> getAllPeople({ bool withHidden }) async
|
//Future<PeopleResponseDto> getAllPeople({ bool withHidden }) async
|
||||||
test('test getAllPeople', () async {
|
test('test getAllPeople', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -52,11 +47,6 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
//Future<List<PersonResponseDto>> reassignFaces(String id, AssetFaceUpdateDto assetFaceUpdateDto) async
|
|
||||||
test('test reassignFaces', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
//Future<List<BulkIdResponseDto>> updatePeople(PeopleUpdateDto peopleUpdateDto) async
|
//Future<List<BulkIdResponseDto>> updatePeople(PeopleUpdateDto peopleUpdateDto) async
|
||||||
test('test updatePeople', () async {
|
test('test updatePeople', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
//
|
|
||||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
|
||||||
//
|
|
||||||
// @dart=2.12
|
|
||||||
|
|
||||||
// ignore_for_file: unused_element, unused_import
|
|
||||||
// ignore_for_file: always_put_required_named_parameters_first
|
|
||||||
// ignore_for_file: constant_identifier_names
|
|
||||||
// ignore_for_file: lines_longer_than_80_chars
|
|
||||||
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
import 'package:test/test.dart';
|
|
||||||
|
|
||||||
// tests for PersonWithFacesResponseDto
|
|
||||||
void main() {
|
|
||||||
// final instance = PersonWithFacesResponseDto();
|
|
||||||
|
|
||||||
group('test PersonWithFacesResponseDto', () {
|
|
||||||
// DateTime birthDate
|
|
||||||
test('to test the property `birthDate`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// List<AssetFaceWithoutPersonResponseDto> faces (default value: const [])
|
|
||||||
test('to test the property `faces`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String id
|
|
||||||
test('to test the property `id`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// bool isHidden
|
|
||||||
test('to test the property `isHidden`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String name
|
|
||||||
test('to test the property `name`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
// String thumbnailPath
|
|
||||||
test('to test the property `thumbnailPath`', () async {
|
|
||||||
// TODO
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
5
mobile/openapi/test/update_asset_dto_test.dart
generated
5
mobile/openapi/test/update_asset_dto_test.dart
generated
|
@ -46,6 +46,11 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// num orientation
|
||||||
|
test('to test the property `orientation`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,131 +0,0 @@
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:immich_mobile/extensions/asset_extensions.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/exif_info.dart';
|
|
||||||
import 'package:timezone/data/latest.dart';
|
|
||||||
import 'package:timezone/timezone.dart';
|
|
||||||
|
|
||||||
ExifInfo makeExif({
|
|
||||||
DateTime? dateTimeOriginal,
|
|
||||||
String? timeZone,
|
|
||||||
}) {
|
|
||||||
return ExifInfo(
|
|
||||||
dateTimeOriginal: dateTimeOriginal,
|
|
||||||
timeZone: timeZone,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Asset makeAsset({
|
|
||||||
required String id,
|
|
||||||
required DateTime createdAt,
|
|
||||||
ExifInfo? exifInfo,
|
|
||||||
}) {
|
|
||||||
return Asset(
|
|
||||||
checksum: '',
|
|
||||||
localId: id,
|
|
||||||
remoteId: id,
|
|
||||||
ownerId: 1,
|
|
||||||
fileCreatedAt: createdAt,
|
|
||||||
fileModifiedAt: DateTime.now(),
|
|
||||||
updatedAt: DateTime.now(),
|
|
||||||
durationInSeconds: 0,
|
|
||||||
type: AssetType.image,
|
|
||||||
fileName: id,
|
|
||||||
isFavorite: false,
|
|
||||||
isArchived: false,
|
|
||||||
isTrashed: false,
|
|
||||||
stackCount: 0,
|
|
||||||
exifInfo: exifInfo,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Init Timezone DB
|
|
||||||
initializeTimeZones();
|
|
||||||
|
|
||||||
group("Returns local time and offset if no exifInfo", () {
|
|
||||||
test('returns createdAt directly if in local', () {
|
|
||||||
final createdAt = DateTime(2023, 12, 12, 12, 12, 12);
|
|
||||||
final a = makeAsset(id: '1', createdAt: createdAt);
|
|
||||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
|
||||||
|
|
||||||
expect(dt, createdAt);
|
|
||||||
expect(tz, createdAt.timeZoneOffset);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns createdAt in local if in utc', () {
|
|
||||||
final createdAt = DateTime.utc(2023, 12, 12, 12, 12, 12);
|
|
||||||
final a = makeAsset(id: '1', createdAt: createdAt);
|
|
||||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
|
||||||
|
|
||||||
final localCreatedAt = createdAt.toLocal();
|
|
||||||
expect(dt, localCreatedAt);
|
|
||||||
expect(tz, localCreatedAt.timeZoneOffset);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group("Returns dateTimeOriginal", () {
|
|
||||||
test('Returns dateTimeOriginal in UTC from exifInfo without timezone', () {
|
|
||||||
final createdAt = DateTime.parse("2023-01-27T14:00:00-0500");
|
|
||||||
final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530");
|
|
||||||
final e = makeExif(dateTimeOriginal: dateTimeOriginal);
|
|
||||||
final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e);
|
|
||||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
|
||||||
|
|
||||||
final dateTimeInUTC = dateTimeOriginal.toUtc();
|
|
||||||
expect(dt, dateTimeInUTC);
|
|
||||||
expect(tz, dateTimeInUTC.timeZoneOffset);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Returns dateTimeOriginal in UTC from exifInfo with invalid timezone',
|
|
||||||
() {
|
|
||||||
final createdAt = DateTime.parse("2023-01-27T14:00:00-0500");
|
|
||||||
final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530");
|
|
||||||
final e = makeExif(
|
|
||||||
dateTimeOriginal: dateTimeOriginal,
|
|
||||||
timeZone: "#_#",
|
|
||||||
); // Invalid timezone
|
|
||||||
final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e);
|
|
||||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
|
||||||
|
|
||||||
final dateTimeInUTC = dateTimeOriginal.toUtc();
|
|
||||||
expect(dt, dateTimeInUTC);
|
|
||||||
expect(tz, dateTimeInUTC.timeZoneOffset);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
group("Returns adjusted time if timezone available", () {
|
|
||||||
test('With timezone as location', () {
|
|
||||||
final createdAt = DateTime.parse("2023-01-27T14:00:00-0500");
|
|
||||||
final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530");
|
|
||||||
const location = "Asia/Hong_Kong";
|
|
||||||
final e =
|
|
||||||
makeExif(dateTimeOriginal: dateTimeOriginal, timeZone: location);
|
|
||||||
final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e);
|
|
||||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
|
||||||
|
|
||||||
final adjustedTime =
|
|
||||||
TZDateTime.from(dateTimeOriginal.toUtc(), getLocation(location));
|
|
||||||
expect(dt, adjustedTime);
|
|
||||||
expect(tz, adjustedTime.timeZoneOffset);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('With timezone as offset', () {
|
|
||||||
final createdAt = DateTime.parse("2023-01-27T14:00:00-0500");
|
|
||||||
final dateTimeOriginal = DateTime.parse("2022-01-27T14:00:00+0530");
|
|
||||||
const offset = "utc+08:00";
|
|
||||||
final e = makeExif(dateTimeOriginal: dateTimeOriginal, timeZone: offset);
|
|
||||||
final a = makeAsset(id: '1', createdAt: createdAt, exifInfo: e);
|
|
||||||
final (dt, tz) = a.getTZAdjustedTimeAndOffset();
|
|
||||||
|
|
||||||
final location = getLocation("Asia/Hong_Kong");
|
|
||||||
final offsetFromLocation =
|
|
||||||
Duration(milliseconds: location.currentTimeZone.offset);
|
|
||||||
final adjustedTime = dateTimeOriginal.toUtc().add(offsetFromLocation);
|
|
||||||
|
|
||||||
// Adds the offset to the actual time and returns the offset separately
|
|
||||||
expect(dt, adjustedTime);
|
|
||||||
expect(tz, offsetFromLocation);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -62,9 +62,6 @@
|
||||||
"versioning": "node"
|
"versioning": "node"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"ignorePaths": [
|
|
||||||
"mobile/openapi/pubspec.yaml"
|
|
||||||
],
|
|
||||||
"ignoreDeps": [
|
"ignoreDeps": [
|
||||||
"http",
|
"http",
|
||||||
"latlong2",
|
"latlong2",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# dev build
|
# dev build
|
||||||
FROM ghcr.io/immich-app/base-server-dev:20231201@sha256:4701c0c5920c78e73040dd2b74d22042ffce393f1a9d3453d90a0ecf81ff8649 as dev
|
FROM ghcr.io/immich-app/base-server-dev:20231130@sha256:2f3b4bc0b50a0710e4a0867b4842ebde3a709d18fd19b095b8bfb884082cfa18 as dev
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY server/package.json server/package-lock.json ./
|
COPY server/package.json server/package-lock.json ./
|
||||||
|
@ -23,7 +23,7 @@ RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
# prod build
|
# prod build
|
||||||
FROM ghcr.io/immich-app/base-server-prod:20231201@sha256:b8e86cf4c3cad872f54bab25a83f7503480049eea5c0ae36a8b8460b13cad3b5
|
FROM ghcr.io/immich-app/base-server-prod:20231130@sha256:dd91bfac4090357605a862823a99b50cf01cbc519723198f7aebb6b0517fab1d
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
|
@ -3220,103 +3220,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/face": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "getFaces",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/AssetFaceResponseDto"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"bearer": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cookie": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"api_key": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Face"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/face/{id}": {
|
|
||||||
"put": {
|
|
||||||
"operationId": "reassignFacesById",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/FaceDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/PersonResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"bearer": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cookie": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"api_key": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Face"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/jobs": {
|
"/jobs": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getAllJobsStatus",
|
"operationId": "getAllJobsStatus",
|
||||||
|
@ -4119,36 +4022,6 @@
|
||||||
"Person"
|
"Person"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"post": {
|
|
||||||
"operationId": "createPerson",
|
|
||||||
"parameters": [],
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/PersonResponseDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"bearer": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cookie": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"api_key": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Person"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"put": {
|
"put": {
|
||||||
"operationId": "updatePeople",
|
"operationId": "updatePeople",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
|
@ -4385,61 +4258,6 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/person/{id}/reassign": {
|
|
||||||
"put": {
|
|
||||||
"operationId": "reassignFaces",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"name": "id",
|
|
||||||
"required": true,
|
|
||||||
"in": "path",
|
|
||||||
"schema": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"requestBody": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/AssetFaceUpdateDto"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/PersonResponseDto"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"bearer": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cookie": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"api_key": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"tags": [
|
|
||||||
"Person"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/person/{id}/statistics": {
|
"/person/{id}/statistics": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getPersonStatistics",
|
"operationId": "getPersonStatistics",
|
||||||
|
@ -6653,6 +6471,9 @@
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"orientation": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"removeParent": {
|
"removeParent": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
@ -6739,118 +6560,6 @@
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"AssetFaceResponseDto": {
|
|
||||||
"properties": {
|
|
||||||
"boundingBoxX1": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"boundingBoxX2": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"boundingBoxY1": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"boundingBoxY2": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"imageHeight": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"imageWidth": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"person": {
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/components/schemas/PersonResponseDto"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"imageHeight",
|
|
||||||
"imageWidth",
|
|
||||||
"boundingBoxX1",
|
|
||||||
"boundingBoxX2",
|
|
||||||
"boundingBoxY1",
|
|
||||||
"boundingBoxY2",
|
|
||||||
"person"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"AssetFaceUpdateDto": {
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/AssetFaceUpdateItem"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"data"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"AssetFaceUpdateItem": {
|
|
||||||
"properties": {
|
|
||||||
"assetId": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"personId": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"personId",
|
|
||||||
"assetId"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"AssetFaceWithoutPersonResponseDto": {
|
|
||||||
"properties": {
|
|
||||||
"boundingBoxX1": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"boundingBoxX2": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"boundingBoxY1": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"boundingBoxY2": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"imageHeight": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"imageWidth": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"imageHeight",
|
|
||||||
"imageWidth",
|
|
||||||
"boundingBoxX1",
|
|
||||||
"boundingBoxX2",
|
|
||||||
"boundingBoxY1",
|
|
||||||
"boundingBoxY2"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"AssetFileUploadResponseDto": {
|
"AssetFileUploadResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"duplicate": {
|
"duplicate": {
|
||||||
|
@ -7013,7 +6722,7 @@
|
||||||
},
|
},
|
||||||
"people": {
|
"people": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/PersonWithFacesResponseDto"
|
"$ref": "#/components/schemas/PersonResponseDto"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
@ -7746,18 +7455,6 @@
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"FaceDto": {
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"format": "uuid",
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"FileChecksumDto": {
|
"FileChecksumDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"filenames": {
|
"filenames": {
|
||||||
|
@ -7854,8 +7551,7 @@
|
||||||
"start",
|
"start",
|
||||||
"pause",
|
"pause",
|
||||||
"resume",
|
"resume",
|
||||||
"empty",
|
"empty"
|
||||||
"clear-failed"
|
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -8453,42 +8149,6 @@
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"PersonWithFacesResponseDto": {
|
|
||||||
"properties": {
|
|
||||||
"birthDate": {
|
|
||||||
"format": "date",
|
|
||||||
"nullable": true,
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"faces": {
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/components/schemas/AssetFaceWithoutPersonResponseDto"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"isHidden": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"thumbnailPath": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"birthDate",
|
|
||||||
"faces",
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"thumbnailPath",
|
|
||||||
"isHidden"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
},
|
|
||||||
"QueueStatusDto": {
|
"QueueStatusDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"isActive": {
|
"isActive": {
|
||||||
|
@ -9712,6 +9372,9 @@
|
||||||
},
|
},
|
||||||
"longitude": {
|
"longitude": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"orientation": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
|
@ -41,8 +41,6 @@ export enum Permission {
|
||||||
PERSON_READ = 'person.read',
|
PERSON_READ = 'person.read',
|
||||||
PERSON_WRITE = 'person.write',
|
PERSON_WRITE = 'person.write',
|
||||||
PERSON_MERGE = 'person.merge',
|
PERSON_MERGE = 'person.merge',
|
||||||
PERSON_CREATE = 'person.create',
|
|
||||||
PERSON_REASSIGN = 'person.reassign',
|
|
||||||
|
|
||||||
PARTNER_UPDATE = 'partner.update',
|
PARTNER_UPDATE = 'partner.update',
|
||||||
}
|
}
|
||||||
|
@ -249,12 +247,6 @@ export class AccessCore {
|
||||||
case Permission.PERSON_MERGE:
|
case Permission.PERSON_MERGE:
|
||||||
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.PERSON_CREATE:
|
|
||||||
return this.repository.person.hasFaceOwnerAccess(authUser.id, ids);
|
|
||||||
|
|
||||||
case Permission.PERSON_REASSIGN:
|
|
||||||
return this.repository.person.hasFaceOwnerAccess(authUser.id, ids);
|
|
||||||
|
|
||||||
case Permission.PARTNER_UPDATE:
|
case Permission.PARTNER_UPDATE:
|
||||||
return await this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
return await this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsString } from 'class-validator';
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
import { Optional, ValidateUUID } from '../../domain.util';
|
import { Optional, ValidateUUID } from '../../domain.util';
|
||||||
|
|
||||||
export class CreateAlbumDto {
|
export class CreateAlbumDto {
|
||||||
|
@IsNotEmpty()
|
||||||
@IsString()
|
@IsString()
|
||||||
@ApiProperty()
|
@ApiProperty()
|
||||||
albumName!: string;
|
albumName!: string;
|
||||||
|
|
|
@ -393,8 +393,8 @@ export class AssetService {
|
||||||
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
||||||
|
|
||||||
const { description, dateTimeOriginal, latitude, longitude, ...rest } = dto;
|
const { description, dateTimeOriginal, latitude, longitude, orientation, ...rest } = dto;
|
||||||
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude });
|
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude, orientation });
|
||||||
|
|
||||||
const asset = await this.assetRepository.save({ id, ...rest });
|
const asset = await this.assetRepository.save({ id, ...rest });
|
||||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [id] } });
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [id] } });
|
||||||
|
@ -402,7 +402,7 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto): Promise<void> {
|
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto): Promise<void> {
|
||||||
const { ids, removeParent, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
const { ids, removeParent, dateTimeOriginal, latitude, longitude, orientation, ...options } = dto;
|
||||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||||
|
|
||||||
if (removeParent) {
|
if (removeParent) {
|
||||||
|
@ -423,7 +423,7 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude });
|
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude, orientation });
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
||||||
|
@ -591,8 +591,8 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async updateMetadata(dto: ISidecarWriteJob) {
|
private async updateMetadata(dto: ISidecarWriteJob) {
|
||||||
const { id, description, dateTimeOriginal, latitude, longitude } = dto;
|
const { id, description, dateTimeOriginal, latitude, longitude, orientation } = dto;
|
||||||
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude }, _.isUndefined);
|
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude, orientation }, _.isUndefined);
|
||||||
if (Object.keys(writes).length > 0) {
|
if (Object.keys(writes).length > 0) {
|
||||||
await this.assetRepository.upsertExif({ assetId: id, ...writes });
|
await this.assetRepository.upsertExif({ assetId: id, ...writes });
|
||||||
await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id, ...writes } });
|
await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id, ...writes } });
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
IsNotEmpty,
|
IsNotEmpty,
|
||||||
IsPositive,
|
IsPositive,
|
||||||
IsString,
|
IsString,
|
||||||
|
Max,
|
||||||
Min,
|
Min,
|
||||||
ValidateIf,
|
ValidateIf,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
@ -202,6 +203,13 @@ export class AssetBulkUpdateDto extends BulkIdsDto {
|
||||||
@IsLongitude()
|
@IsLongitude()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@Max(8)
|
||||||
|
@Type(() => Number)
|
||||||
|
orientation?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateAssetDto {
|
export class UpdateAssetDto {
|
||||||
|
@ -230,6 +238,13 @@ export class UpdateAssetDto {
|
||||||
@IsLongitude()
|
@IsLongitude()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@Max(8)
|
||||||
|
@Type(() => Number)
|
||||||
|
orientation?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RandomAssetsDto {
|
export class RandomAssetsDto {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AssetEntity, AssetFaceEntity, AssetType } from '@app/infra/entities';
|
import { AssetEntity, AssetType } from '@app/infra/entities';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { PersonWithFacesResponseDto } from '../../person/person.dto';
|
import { PersonResponseDto, mapFace } from '../../person/person.dto';
|
||||||
import { TagResponseDto, mapTag } from '../../tag';
|
import { TagResponseDto, mapTag } from '../../tag';
|
||||||
import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
|
import { UserResponseDto, mapUser } from '../../user/response-dto/user-response.dto';
|
||||||
import { ExifResponseDto, mapExif } from './exif-response.dto';
|
import { ExifResponseDto, mapExif } from './exif-response.dto';
|
||||||
|
@ -39,7 +39,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
smartInfo?: SmartInfoResponseDto;
|
smartInfo?: SmartInfoResponseDto;
|
||||||
tags?: TagResponseDto[];
|
tags?: TagResponseDto[];
|
||||||
people?: PersonWithFacesResponseDto[];
|
people?: PersonResponseDto[];
|
||||||
/**base64 encoded sha1 hash */
|
/**base64 encoded sha1 hash */
|
||||||
checksum!: string;
|
checksum!: string;
|
||||||
stackParentId?: string | null;
|
stackParentId?: string | null;
|
||||||
|
@ -53,24 +53,6 @@ export type AssetMapOptions = {
|
||||||
withStack?: boolean;
|
withStack?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const peopleWithFaces = (faces: AssetFaceEntity[]): PersonWithFacesResponseDto[] => {
|
|
||||||
const result: PersonWithFacesResponseDto[] = [];
|
|
||||||
if (faces) {
|
|
||||||
faces.forEach((face) => {
|
|
||||||
if (face.person) {
|
|
||||||
const existingPersonEntry = result.find((item) => item.id === face.person!.id);
|
|
||||||
if (existingPersonEntry) {
|
|
||||||
existingPersonEntry.faces.push(face);
|
|
||||||
} else {
|
|
||||||
result.push({ ...face.person!, faces: [face] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {
|
export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): AssetResponseDto {
|
||||||
const { stripMetadata = false, withStack = false } = options;
|
const { stripMetadata = false, withStack = false } = options;
|
||||||
|
|
||||||
|
@ -114,7 +96,16 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
||||||
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
smartInfo: entity.smartInfo ? mapSmartInfo(entity.smartInfo) : undefined,
|
||||||
livePhotoVideoId: entity.livePhotoVideoId,
|
livePhotoVideoId: entity.livePhotoVideoId,
|
||||||
tags: entity.tags?.map(mapTag),
|
tags: entity.tags?.map(mapTag),
|
||||||
people: peopleWithFaces(entity.faces),
|
people: entity.faces
|
||||||
|
?.map(mapFace)
|
||||||
|
.filter((person): person is PersonResponseDto => person !== null)
|
||||||
|
.reduce((people, person) => {
|
||||||
|
const existingPerson = people.find((p) => p.id === person.id);
|
||||||
|
if (!existingPerson) {
|
||||||
|
people.push(person);
|
||||||
|
}
|
||||||
|
return people;
|
||||||
|
}, [] as PersonResponseDto[]),
|
||||||
checksum: entity.checksum.toString('base64'),
|
checksum: entity.checksum.toString('base64'),
|
||||||
stackParentId: entity.stackParentId,
|
stackParentId: entity.stackParentId,
|
||||||
stack: withStack ? entity.stack?.map((a) => mapAsset(a, { stripMetadata })) ?? undefined : undefined,
|
stack: withStack ? entity.stack?.map((a) => mapAsset(a, { stripMetadata })) ?? undefined : undefined,
|
||||||
|
|
|
@ -18,7 +18,6 @@ export enum JobCommand {
|
||||||
PAUSE = 'pause',
|
PAUSE = 'pause',
|
||||||
RESUME = 'resume',
|
RESUME = 'resume',
|
||||||
EMPTY = 'empty',
|
EMPTY = 'empty',
|
||||||
CLEAR_FAILED = 'clear-failed',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum JobName {
|
export enum JobName {
|
||||||
|
|
|
@ -39,4 +39,5 @@ export interface ISidecarWriteJob extends IEntityJob {
|
||||||
dateTimeOriginal?: string;
|
dateTimeOriginal?: string;
|
||||||
latitude?: number;
|
latitude?: number;
|
||||||
longitude?: number;
|
longitude?: number;
|
||||||
|
orientation?: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
ISystemConfigRepository,
|
ISystemConfigRepository,
|
||||||
JobHandler,
|
JobHandler,
|
||||||
JobItem,
|
JobItem,
|
||||||
QueueCleanType,
|
|
||||||
} from '../repositories';
|
} from '../repositories';
|
||||||
import { FeatureFlag, SystemConfigCore } from '../system-config/system-config.core';
|
import { FeatureFlag, SystemConfigCore } from '../system-config/system-config.core';
|
||||||
import { JobCommand, JobName, QueueName } from './job.constants';
|
import { JobCommand, JobName, QueueName } from './job.constants';
|
||||||
|
@ -50,11 +49,6 @@ export class JobService {
|
||||||
case JobCommand.EMPTY:
|
case JobCommand.EMPTY:
|
||||||
await this.jobRepository.empty(queueName);
|
await this.jobRepository.empty(queueName);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case JobCommand.CLEAR_FAILED:
|
|
||||||
const failedJobs = await this.jobRepository.clear(queueName, QueueCleanType.FAILED);
|
|
||||||
this.logger.debug(`Cleared failed jobs: ${failedJobs}`);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getJobStatus(queueName);
|
return this.getJobStatus(queueName);
|
||||||
|
@ -201,7 +195,7 @@ export class JobService {
|
||||||
const { id } = item.data;
|
const { id } = item.data;
|
||||||
const person = await this.personRepository.getById(id);
|
const person = await this.personRepository.getById(id);
|
||||||
if (person) {
|
if (person) {
|
||||||
this.communicationRepository.send(CommunicationEvent.PERSON_THUMBNAIL, person.ownerId, person.id);
|
this.communicationRepository.send(CommunicationEvent.PERSON_THUMBNAIL, person.ownerId, id);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -229,9 +223,7 @@ export class JobService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const [asset] = await this.assetRepository.getByIds([item.data.id]);
|
const [asset] = await this.assetRepository.getByIds([item.data.id]);
|
||||||
|
if (asset) {
|
||||||
// Only live-photo motion part will be marked as not visible immediately on upload. Skip notifying clients
|
|
||||||
if (asset && asset.isVisible) {
|
|
||||||
this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
|
this.communicationRepository.send(CommunicationEvent.UPLOAD_SUCCESS, asset.ownerId, mapAsset(asset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,16 +3,13 @@ import {
|
||||||
assetStub,
|
assetStub,
|
||||||
newAlbumRepositoryMock,
|
newAlbumRepositoryMock,
|
||||||
newAssetRepositoryMock,
|
newAssetRepositoryMock,
|
||||||
newCommunicationRepositoryMock,
|
|
||||||
newCryptoRepositoryMock,
|
newCryptoRepositoryMock,
|
||||||
newJobRepositoryMock,
|
newJobRepositoryMock,
|
||||||
newMediaRepositoryMock,
|
|
||||||
newMetadataRepositoryMock,
|
newMetadataRepositoryMock,
|
||||||
newMoveRepositoryMock,
|
newMoveRepositoryMock,
|
||||||
newPersonRepositoryMock,
|
newPersonRepositoryMock,
|
||||||
newStorageRepositoryMock,
|
newStorageRepositoryMock,
|
||||||
newSystemConfigRepositoryMock,
|
newSystemConfigRepositoryMock,
|
||||||
probeStub,
|
|
||||||
} from '@test';
|
} from '@test';
|
||||||
import { randomBytes } from 'crypto';
|
import { randomBytes } from 'crypto';
|
||||||
import { Stats } from 'fs';
|
import { Stats } from 'fs';
|
||||||
|
@ -20,13 +17,10 @@ import { constants } from 'fs/promises';
|
||||||
import { when } from 'jest-when';
|
import { when } from 'jest-when';
|
||||||
import { JobName } from '../job';
|
import { JobName } from '../job';
|
||||||
import {
|
import {
|
||||||
CommunicationEvent,
|
|
||||||
IAlbumRepository,
|
IAlbumRepository,
|
||||||
IAssetRepository,
|
IAssetRepository,
|
||||||
ICommunicationRepository,
|
|
||||||
ICryptoRepository,
|
ICryptoRepository,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
IMediaRepository,
|
|
||||||
IMetadataRepository,
|
IMetadataRepository,
|
||||||
IMoveRepository,
|
IMoveRepository,
|
||||||
IPersonRepository,
|
IPersonRepository,
|
||||||
|
@ -36,7 +30,7 @@ import {
|
||||||
WithProperty,
|
WithProperty,
|
||||||
WithoutProperty,
|
WithoutProperty,
|
||||||
} from '../repositories';
|
} from '../repositories';
|
||||||
import { MetadataService, Orientation } from './metadata.service';
|
import { MetadataService } from './metadata.service';
|
||||||
|
|
||||||
describe(MetadataService.name, () => {
|
describe(MetadataService.name, () => {
|
||||||
let albumMock: jest.Mocked<IAlbumRepository>;
|
let albumMock: jest.Mocked<IAlbumRepository>;
|
||||||
|
@ -46,10 +40,8 @@ describe(MetadataService.name, () => {
|
||||||
let jobMock: jest.Mocked<IJobRepository>;
|
let jobMock: jest.Mocked<IJobRepository>;
|
||||||
let metadataMock: jest.Mocked<IMetadataRepository>;
|
let metadataMock: jest.Mocked<IMetadataRepository>;
|
||||||
let moveMock: jest.Mocked<IMoveRepository>;
|
let moveMock: jest.Mocked<IMoveRepository>;
|
||||||
let mediaMock: jest.Mocked<IMediaRepository>;
|
|
||||||
let personMock: jest.Mocked<IPersonRepository>;
|
let personMock: jest.Mocked<IPersonRepository>;
|
||||||
let storageMock: jest.Mocked<IStorageRepository>;
|
let storageMock: jest.Mocked<IStorageRepository>;
|
||||||
let communicationMock: jest.Mocked<ICommunicationRepository>;
|
|
||||||
let sut: MetadataService;
|
let sut: MetadataService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
|
@ -61,9 +53,7 @@ describe(MetadataService.name, () => {
|
||||||
metadataMock = newMetadataRepositoryMock();
|
metadataMock = newMetadataRepositoryMock();
|
||||||
moveMock = newMoveRepositoryMock();
|
moveMock = newMoveRepositoryMock();
|
||||||
personMock = newPersonRepositoryMock();
|
personMock = newPersonRepositoryMock();
|
||||||
communicationMock = newCommunicationRepositoryMock();
|
|
||||||
storageMock = newStorageRepositoryMock();
|
storageMock = newStorageRepositoryMock();
|
||||||
mediaMock = newMediaRepositoryMock();
|
|
||||||
|
|
||||||
sut = new MetadataService(
|
sut = new MetadataService(
|
||||||
albumMock,
|
albumMock,
|
||||||
|
@ -73,9 +63,7 @@ describe(MetadataService.name, () => {
|
||||||
metadataMock,
|
metadataMock,
|
||||||
storageMock,
|
storageMock,
|
||||||
configMock,
|
configMock,
|
||||||
mediaMock,
|
|
||||||
moveMock,
|
moveMock,
|
||||||
communicationMock,
|
|
||||||
personMock,
|
personMock,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -178,23 +166,6 @@ describe(MetadataService.name, () => {
|
||||||
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
|
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
|
||||||
expect(albumMock.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
|
expect(albumMock.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should notify clients on live photo link', async () => {
|
|
||||||
assetMock.getByIds.mockResolvedValue([
|
|
||||||
{
|
|
||||||
...assetStub.livePhotoStillAsset,
|
|
||||||
exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as ExifEntity,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
assetMock.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset);
|
|
||||||
|
|
||||||
await expect(sut.handleLivePhotoLinking({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe(true);
|
|
||||||
expect(communicationMock.send).toHaveBeenCalledWith(
|
|
||||||
CommunicationEvent.ASSET_HIDDEN,
|
|
||||||
assetStub.livePhotoMotionAsset.ownerId,
|
|
||||||
assetStub.livePhotoMotionAsset.id,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('handleQueueMetadataExtraction', () => {
|
describe('handleQueueMetadataExtraction', () => {
|
||||||
|
@ -306,7 +277,6 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should not apply motion photos if asset is video', async () => {
|
it('should not apply motion photos if asset is video', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
|
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoMotionAsset, isVisible: true }]);
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.matroskaContainer);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id });
|
await sut.handleMetadataExtraction({ id: assetStub.livePhotoMotionAsset.id });
|
||||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id]);
|
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.livePhotoMotionAsset.id]);
|
||||||
|
@ -317,19 +287,6 @@ describe(MetadataService.name, () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should extract the correct video orientation', async () => {
|
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.video]);
|
|
||||||
mediaMock.probe.mockResolvedValue(probeStub.videoStreamVertical2160p);
|
|
||||||
metadataMock.readTags.mockResolvedValue(null);
|
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.video.id });
|
|
||||||
|
|
||||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.video.id]);
|
|
||||||
expect(assetMock.upsertExif).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({ orientation: Orientation.Rotate270CW }),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should apply motion photos', async () => {
|
it('should apply motion photos', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
||||||
metadataMock.readTags.mockResolvedValue({
|
metadataMock.readTags.mockResolvedValue({
|
||||||
|
|
|
@ -9,14 +9,11 @@ import { Subscription } from 'rxjs';
|
||||||
import { usePagination } from '../domain.util';
|
import { usePagination } from '../domain.util';
|
||||||
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
||||||
import {
|
import {
|
||||||
CommunicationEvent,
|
|
||||||
ExifDuration,
|
ExifDuration,
|
||||||
IAlbumRepository,
|
IAlbumRepository,
|
||||||
IAssetRepository,
|
IAssetRepository,
|
||||||
ICommunicationRepository,
|
|
||||||
ICryptoRepository,
|
ICryptoRepository,
|
||||||
IJobRepository,
|
IJobRepository,
|
||||||
IMediaRepository,
|
|
||||||
IMetadataRepository,
|
IMetadataRepository,
|
||||||
IMoveRepository,
|
IMoveRepository,
|
||||||
IPersonRepository,
|
IPersonRepository,
|
||||||
|
@ -52,17 +49,6 @@ interface DirectoryEntry {
|
||||||
Item: DirectoryItem;
|
Item: DirectoryItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum Orientation {
|
|
||||||
Horizontal = '1',
|
|
||||||
MirrorHorizontal = '2',
|
|
||||||
Rotate180 = '3',
|
|
||||||
MirrorVertical = '4',
|
|
||||||
MirrorHorizontalRotate270CW = '5',
|
|
||||||
Rotate90CW = '6',
|
|
||||||
MirrorHorizontalRotate90CW = '7',
|
|
||||||
Rotate270CW = '8',
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExifEntityWithoutGeocodeAndTypeOrm = Omit<
|
type ExifEntityWithoutGeocodeAndTypeOrm = Omit<
|
||||||
ExifEntity,
|
ExifEntity,
|
||||||
'city' | 'state' | 'country' | 'description' | 'exifTextSearchableColumn'
|
'city' | 'state' | 'country' | 'description' | 'exifTextSearchableColumn'
|
||||||
|
@ -104,9 +90,7 @@ export class MetadataService {
|
||||||
@Inject(IMetadataRepository) private repository: IMetadataRepository,
|
@Inject(IMetadataRepository) private repository: IMetadataRepository,
|
||||||
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
|
||||||
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
|
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
|
||||||
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
|
|
||||||
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
@Inject(IMoveRepository) moveRepository: IMoveRepository,
|
||||||
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
|
|
||||||
@Inject(IPersonRepository) personRepository: IPersonRepository,
|
@Inject(IPersonRepository) personRepository: IPersonRepository,
|
||||||
) {
|
) {
|
||||||
this.configCore = SystemConfigCore.create(configRepository);
|
this.configCore = SystemConfigCore.create(configRepository);
|
||||||
|
@ -170,9 +154,6 @@ export class MetadataService {
|
||||||
await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
|
await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
|
||||||
await this.albumRepository.removeAsset(motionAsset.id);
|
await this.albumRepository.removeAsset(motionAsset.id);
|
||||||
|
|
||||||
// Notify clients to hide the linked live photo asset
|
|
||||||
this.communicationRepository.send(CommunicationEvent.ASSET_HIDDEN, motionAsset.ownerId, motionAsset.id);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,27 +182,6 @@ export class MetadataService {
|
||||||
|
|
||||||
const { exifData, tags } = await this.exifData(asset);
|
const { exifData, tags } = await this.exifData(asset);
|
||||||
|
|
||||||
if (asset.type === AssetType.VIDEO) {
|
|
||||||
const { videoStreams } = await this.mediaRepository.probe(asset.originalPath);
|
|
||||||
|
|
||||||
if (videoStreams[0]) {
|
|
||||||
switch (videoStreams[0].rotation) {
|
|
||||||
case -90:
|
|
||||||
exifData.orientation = Orientation.Rotate90CW;
|
|
||||||
break;
|
|
||||||
case 0:
|
|
||||||
exifData.orientation = Orientation.Horizontal;
|
|
||||||
break;
|
|
||||||
case 90:
|
|
||||||
exifData.orientation = Orientation.Rotate270CW;
|
|
||||||
break;
|
|
||||||
case 180:
|
|
||||||
exifData.orientation = Orientation.Rotate180;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.applyMotionPhotos(asset, tags);
|
await this.applyMotionPhotos(asset, tags);
|
||||||
await this.applyReverseGeocoding(asset, exifData);
|
await this.applyReverseGeocoding(asset, exifData);
|
||||||
await this.assetRepository.upsertExif(exifData);
|
await this.assetRepository.upsertExif(exifData);
|
||||||
|
@ -285,7 +245,7 @@ export class MetadataService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleSidecarWrite(job: ISidecarWriteJob) {
|
async handleSidecarWrite(job: ISidecarWriteJob) {
|
||||||
const { id, description, dateTimeOriginal, latitude, longitude } = job;
|
const { id, description, dateTimeOriginal, latitude, longitude, orientation } = job;
|
||||||
const [asset] = await this.assetRepository.getByIds([id]);
|
const [asset] = await this.assetRepository.getByIds([id]);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -298,6 +258,7 @@ export class MetadataService {
|
||||||
CreationDate: dateTimeOriginal,
|
CreationDate: dateTimeOriginal,
|
||||||
GPSLatitude: latitude,
|
GPSLatitude: latitude,
|
||||||
GPSLongitude: longitude,
|
GPSLongitude: longitude,
|
||||||
|
Orientation: orientation,
|
||||||
},
|
},
|
||||||
_.isUndefined,
|
_.isUndefined,
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { AssetFaceEntity, PersonEntity } from '@app/infra/entities';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Transform, Type } from 'class-transformer';
|
import { Transform, Type } from 'class-transformer';
|
||||||
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
|
||||||
import { AuthUserDto } from '../auth';
|
|
||||||
import { Optional, ValidateUUID, toBoolean } from '../domain.util';
|
import { Optional, ValidateUUID, toBoolean } from '../domain.util';
|
||||||
|
|
||||||
export class PersonUpdateDto {
|
export class PersonUpdateDto {
|
||||||
|
@ -74,51 +73,6 @@ export class PersonResponseDto {
|
||||||
isHidden!: boolean;
|
isHidden!: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PersonWithFacesResponseDto extends PersonResponseDto {
|
|
||||||
faces!: AssetFaceWithoutPersonResponseDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AssetFaceWithoutPersonResponseDto {
|
|
||||||
@ValidateUUID()
|
|
||||||
id!: string;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
imageHeight!: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
imageWidth!: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
boundingBoxX1!: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
boundingBoxX2!: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
boundingBoxY1!: number;
|
|
||||||
@ApiProperty({ type: 'integer' })
|
|
||||||
boundingBoxY2!: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AssetFaceResponseDto extends AssetFaceWithoutPersonResponseDto {
|
|
||||||
person!: PersonResponseDto | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AssetFaceUpdateDto {
|
|
||||||
@IsArray()
|
|
||||||
@ValidateNested({ each: true })
|
|
||||||
@Type(() => AssetFaceUpdateItem)
|
|
||||||
data!: AssetFaceUpdateItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FaceDto {
|
|
||||||
@ValidateUUID()
|
|
||||||
id!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AssetFaceUpdateItem {
|
|
||||||
@ValidateUUID()
|
|
||||||
personId!: string;
|
|
||||||
|
|
||||||
@ValidateUUID()
|
|
||||||
assetId!: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PersonStatisticsResponseDto {
|
export class PersonStatisticsResponseDto {
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer' })
|
||||||
assets!: number;
|
assets!: number;
|
||||||
|
@ -144,15 +98,10 @@ export function mapPerson(person: PersonEntity): PersonResponseDto {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapFaces(face: AssetFaceEntity, authUser: AuthUserDto): AssetFaceResponseDto {
|
export function mapFace(face: AssetFaceEntity): PersonResponseDto | null {
|
||||||
return {
|
if (face.person) {
|
||||||
id: face.id,
|
return mapPerson(face.person);
|
||||||
imageHeight: face.imageHeight,
|
}
|
||||||
imageWidth: face.imageWidth,
|
|
||||||
boundingBoxX1: face.boundingBoxX1,
|
return null;
|
||||||
boundingBoxX2: face.boundingBoxX2,
|
|
||||||
boundingBoxY1: face.boundingBoxY1,
|
|
||||||
boundingBoxY2: face.boundingBoxY2,
|
|
||||||
person: face.person?.ownerId === authUser.id ? mapPerson(face.person) : null,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ import {
|
||||||
ISystemConfigRepository,
|
ISystemConfigRepository,
|
||||||
WithoutProperty,
|
WithoutProperty,
|
||||||
} from '../repositories';
|
} from '../repositories';
|
||||||
import { PersonResponseDto, mapFaces } from './person.dto';
|
import { PersonResponseDto } from './person.dto';
|
||||||
import { PersonService } from './person.service';
|
import { PersonService } from './person.service';
|
||||||
|
|
||||||
const responseDto: PersonResponseDto = {
|
const responseDto: PersonResponseDto = {
|
||||||
|
@ -339,7 +339,7 @@ describe(PersonService.name, () => {
|
||||||
).resolves.toEqual(responseDto);
|
).resolves.toEqual(responseDto);
|
||||||
|
|
||||||
expect(personMock.getById).toHaveBeenCalledWith('person-1');
|
expect(personMock.getById).toHaveBeenCalledWith('person-1');
|
||||||
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', faceAssetId: faceStub.face1.id });
|
expect(personMock.update).toHaveBeenCalledWith({ id: 'person-1', faceAssetId: faceStub.face1.assetId });
|
||||||
expect(personMock.getFacesByIds).toHaveBeenCalledWith([
|
expect(personMock.getFacesByIds).toHaveBeenCalledWith([
|
||||||
{
|
{
|
||||||
assetId: faceStub.face1.assetId,
|
assetId: faceStub.face1.assetId,
|
||||||
|
@ -375,139 +375,6 @@ describe(PersonService.name, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('reassignFaces', () => {
|
|
||||||
it('should throw an error if user has no access to the person', async () => {
|
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set());
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
sut.reassignFaces(authStub.admin, personStub.noName.id, {
|
|
||||||
data: [{ personId: 'asset-face-1', assetId: '' }],
|
|
||||||
}),
|
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
|
||||||
expect(jobMock.queue).not.toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
it('should reassign a face', async () => {
|
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.withName.id]));
|
|
||||||
personMock.getById.mockResolvedValue(personStub.noName);
|
|
||||||
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
|
|
||||||
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
|
|
||||||
personMock.reassignFace.mockResolvedValue(1);
|
|
||||||
personMock.getRandomFace.mockResolvedValue(faceStub.primaryFace1);
|
|
||||||
await expect(
|
|
||||||
sut.reassignFaces(authStub.admin, personStub.noName.id, {
|
|
||||||
data: [{ personId: personStub.withName.id, assetId: assetStub.image.id }],
|
|
||||||
}),
|
|
||||||
).resolves.toEqual([personStub.noName]);
|
|
||||||
|
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
|
||||||
name: JobName.GENERATE_PERSON_THUMBNAIL,
|
|
||||||
data: { id: personStub.newThumbnail.id },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handlePersonMigration', () => {
|
|
||||||
it('should not move person files', async () => {
|
|
||||||
personMock.getById.mockResolvedValue(null);
|
|
||||||
await expect(sut.handlePersonMigration(personStub.noName)).resolves.toStrictEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getFacesById', () => {
|
|
||||||
it('should get the bounding boxes for an asset', async () => {
|
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([faceStub.face1.assetId]));
|
|
||||||
personMock.getFaces.mockResolvedValue([faceStub.primaryFace1]);
|
|
||||||
await expect(sut.getFacesById(authStub.admin, { id: faceStub.face1.assetId })).resolves.toStrictEqual([
|
|
||||||
mapFaces(faceStub.primaryFace1, authStub.admin),
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
it('should reject if the user has not access to the asset', async () => {
|
|
||||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set());
|
|
||||||
personMock.getFaces.mockResolvedValue([faceStub.primaryFace1]);
|
|
||||||
await expect(sut.getFacesById(authStub.admin, { id: faceStub.primaryFace1.assetId })).rejects.toBeInstanceOf(
|
|
||||||
BadRequestException,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createNewFeaturePhoto', () => {
|
|
||||||
it('should change person feature photo', async () => {
|
|
||||||
personMock.getRandomFace.mockResolvedValue(faceStub.primaryFace1);
|
|
||||||
await sut.createNewFeaturePhoto([personStub.newThumbnail.id]);
|
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({
|
|
||||||
name: JobName.GENERATE_PERSON_THUMBNAIL,
|
|
||||||
data: { id: personStub.newThumbnail.id },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('reassignFacesById', () => {
|
|
||||||
it('should create a new person', async () => {
|
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
|
|
||||||
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
|
|
||||||
personMock.getFaceById.mockResolvedValue(faceStub.face1);
|
|
||||||
personMock.reassignFace.mockResolvedValue(1);
|
|
||||||
personMock.getById.mockResolvedValue(personStub.noName);
|
|
||||||
personMock.getRandomFace.mockResolvedValue(null);
|
|
||||||
await expect(
|
|
||||||
sut.reassignFacesById(authStub.admin, personStub.noName.id, {
|
|
||||||
id: faceStub.face1.id,
|
|
||||||
}),
|
|
||||||
).resolves.toEqual({
|
|
||||||
birthDate: personStub.noName.birthDate,
|
|
||||||
isHidden: personStub.noName.isHidden,
|
|
||||||
id: personStub.noName.id,
|
|
||||||
name: personStub.noName.name,
|
|
||||||
thumbnailPath: personStub.noName.thumbnailPath,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(jobMock.queue).not.toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should fail if user has not the correct permissions on the asset', async () => {
|
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set([personStub.noName.id]));
|
|
||||||
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set());
|
|
||||||
personMock.getFaceById.mockResolvedValue(faceStub.face1);
|
|
||||||
personMock.reassignFace.mockResolvedValue(1);
|
|
||||||
personMock.getById.mockResolvedValue(personStub.noName);
|
|
||||||
personMock.getRandomFace.mockResolvedValue(null);
|
|
||||||
await expect(
|
|
||||||
sut.reassignFacesById(authStub.admin, personStub.noName.id, {
|
|
||||||
id: faceStub.face1.id,
|
|
||||||
}),
|
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
|
||||||
|
|
||||||
expect(jobMock.queue).not.toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createPerson', () => {
|
|
||||||
it('should create a new person', async () => {
|
|
||||||
personMock.create.mockResolvedValue(personStub.primaryPerson);
|
|
||||||
personMock.getFaceById.mockResolvedValue(faceStub.face1);
|
|
||||||
accessMock.person.hasFaceOwnerAccess.mockResolvedValue(new Set([faceStub.face1.id]));
|
|
||||||
|
|
||||||
await expect(sut.createPerson(authStub.admin)).resolves.toBe(personStub.primaryPerson);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handlePersonDelete', () => {
|
|
||||||
it('should stop if a person has not be found', async () => {
|
|
||||||
personMock.getById.mockResolvedValue(null);
|
|
||||||
|
|
||||||
await expect(sut.handlePersonDelete({ id: 'person-1' })).resolves.toBe(false);
|
|
||||||
expect(personMock.update).not.toHaveBeenCalled();
|
|
||||||
expect(storageMock.unlink).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
it('should delete a person', async () => {
|
|
||||||
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
|
||||||
|
|
||||||
await expect(sut.handlePersonDelete({ id: 'person-1' })).resolves.toBe(true);
|
|
||||||
expect(personMock.delete).toHaveBeenCalledWith(personStub.primaryPerson);
|
|
||||||
expect(storageMock.unlink).toHaveBeenCalledWith(personStub.primaryPerson.thumbnailPath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('handlePersonCleanup', () => {
|
describe('handlePersonCleanup', () => {
|
||||||
it('should delete people without faces', async () => {
|
it('should delete people without faces', async () => {
|
||||||
personMock.getAllWithoutFaces.mockResolvedValue([personStub.noName]);
|
personMock.getAllWithoutFaces.mockResolvedValue([personStub.noName]);
|
||||||
|
@ -648,7 +515,6 @@ describe(PersonService.name, () => {
|
||||||
searchMock.searchFaces.mockResolvedValue(faceSearch.oneRemoteMatch);
|
searchMock.searchFaces.mockResolvedValue(faceSearch.oneRemoteMatch);
|
||||||
personMock.create.mockResolvedValue(personStub.noName);
|
personMock.create.mockResolvedValue(personStub.noName);
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
personMock.createFace.mockResolvedValue(faceStub.primaryFace1);
|
|
||||||
|
|
||||||
await sut.handleRecognizeFaces({ id: assetStub.image.id });
|
await sut.handleRecognizeFaces({ id: assetStub.image.id });
|
||||||
|
|
||||||
|
@ -691,16 +557,16 @@ describe(PersonService.name, () => {
|
||||||
expect(mediaMock.crop).not.toHaveBeenCalled();
|
expect(mediaMock.crop).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip a person with a face asset id not found', async () => {
|
it('should skip an person with a face asset id not found', async () => {
|
||||||
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.id });
|
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
|
||||||
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.face1);
|
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
|
||||||
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
||||||
expect(mediaMock.crop).not.toHaveBeenCalled();
|
expect(mediaMock.crop).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip a person with a face asset id without a thumbnail', async () => {
|
it('should skip a person with a face asset id without a thumbnail', async () => {
|
||||||
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
|
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
|
||||||
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.face1);
|
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
|
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath]);
|
||||||
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
||||||
expect(mediaMock.crop).not.toHaveBeenCalled();
|
expect(mediaMock.crop).not.toHaveBeenCalled();
|
||||||
|
@ -708,7 +574,7 @@ describe(PersonService.name, () => {
|
||||||
|
|
||||||
it('should generate a thumbnail', async () => {
|
it('should generate a thumbnail', async () => {
|
||||||
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
|
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.middle.assetId });
|
||||||
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.middle);
|
personMock.getFacesByIds.mockResolvedValue([faceStub.middle]);
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
assetMock.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||||
|
|
||||||
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
||||||
|
@ -735,7 +601,7 @@ describe(PersonService.name, () => {
|
||||||
|
|
||||||
it('should generate a thumbnail without going negative', async () => {
|
it('should generate a thumbnail without going negative', async () => {
|
||||||
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.start.assetId });
|
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.start.assetId });
|
||||||
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.start);
|
personMock.getFacesByIds.mockResolvedValue([faceStub.start]);
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
|
|
||||||
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
||||||
|
@ -756,7 +622,7 @@ describe(PersonService.name, () => {
|
||||||
|
|
||||||
it('should generate a thumbnail without overflowing', async () => {
|
it('should generate a thumbnail without overflowing', async () => {
|
||||||
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.end.assetId });
|
personMock.getById.mockResolvedValue({ ...personStub.primaryPerson, faceAssetId: faceStub.end.assetId });
|
||||||
personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.end);
|
personMock.getFacesByIds.mockResolvedValue([faceStub.end]);
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
assetMock.getByIds.mockResolvedValue([assetStub.primaryImage]);
|
||||||
|
|
||||||
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
await sut.handleGeneratePersonThumbnail({ id: 'person-1' });
|
||||||
|
@ -780,12 +646,15 @@ describe(PersonService.name, () => {
|
||||||
it('should require person.write and person.merge permission', async () => {
|
it('should require person.write and person.merge permission', async () => {
|
||||||
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
|
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
|
||||||
personMock.getById.mockResolvedValueOnce(personStub.mergePerson);
|
personMock.getById.mockResolvedValueOnce(personStub.mergePerson);
|
||||||
|
personMock.prepareReassignFaces.mockResolvedValue([]);
|
||||||
personMock.delete.mockResolvedValue(personStub.mergePerson);
|
personMock.delete.mockResolvedValue(personStub.mergePerson);
|
||||||
|
|
||||||
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf(
|
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).rejects.toBeInstanceOf(
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
expect(personMock.prepareReassignFaces).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(personMock.reassignFaces).not.toHaveBeenCalled();
|
expect(personMock.reassignFaces).not.toHaveBeenCalled();
|
||||||
|
|
||||||
expect(personMock.delete).not.toHaveBeenCalled();
|
expect(personMock.delete).not.toHaveBeenCalled();
|
||||||
|
@ -795,6 +664,7 @@ describe(PersonService.name, () => {
|
||||||
it('should merge two people', async () => {
|
it('should merge two people', async () => {
|
||||||
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
|
personMock.getById.mockResolvedValueOnce(personStub.primaryPerson);
|
||||||
personMock.getById.mockResolvedValueOnce(personStub.mergePerson);
|
personMock.getById.mockResolvedValueOnce(personStub.mergePerson);
|
||||||
|
personMock.prepareReassignFaces.mockResolvedValue([]);
|
||||||
personMock.delete.mockResolvedValue(personStub.mergePerson);
|
personMock.delete.mockResolvedValue(personStub.mergePerson);
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
|
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
|
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
|
||||||
|
@ -803,6 +673,11 @@ describe(PersonService.name, () => {
|
||||||
{ id: 'person-2', success: true },
|
{ id: 'person-2', success: true },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(personMock.prepareReassignFaces).toHaveBeenCalledWith({
|
||||||
|
newPersonId: personStub.primaryPerson.id,
|
||||||
|
oldPersonId: personStub.mergePerson.id,
|
||||||
|
});
|
||||||
|
|
||||||
expect(personMock.reassignFaces).toHaveBeenCalledWith({
|
expect(personMock.reassignFaces).toHaveBeenCalledWith({
|
||||||
newPersonId: personStub.primaryPerson.id,
|
newPersonId: personStub.primaryPerson.id,
|
||||||
oldPersonId: personStub.mergePerson.id,
|
oldPersonId: personStub.mergePerson.id,
|
||||||
|
@ -815,6 +690,29 @@ describe(PersonService.name, () => {
|
||||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should delete conflicting faces before merging', async () => {
|
||||||
|
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
||||||
|
personMock.getById.mockResolvedValue(personStub.mergePerson);
|
||||||
|
personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]);
|
||||||
|
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
|
||||||
|
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
|
||||||
|
|
||||||
|
await expect(sut.mergePerson(authStub.admin, 'person-1', { ids: ['person-2'] })).resolves.toEqual([
|
||||||
|
{ id: 'person-2', success: true },
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(personMock.prepareReassignFaces).toHaveBeenCalledWith({
|
||||||
|
newPersonId: personStub.primaryPerson.id,
|
||||||
|
oldPersonId: personStub.mergePerson.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(jobMock.queue).toHaveBeenCalledWith({
|
||||||
|
name: JobName.SEARCH_REMOVE_FACE,
|
||||||
|
data: { assetId: assetStub.image.id, personId: personStub.mergePerson.id },
|
||||||
|
});
|
||||||
|
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw an error when the primary person is not found', async () => {
|
it('should throw an error when the primary person is not found', async () => {
|
||||||
personMock.getById.mockResolvedValue(null);
|
personMock.getById.mockResolvedValue(null);
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||||
|
@ -837,6 +735,7 @@ describe(PersonService.name, () => {
|
||||||
{ id: 'person-2', success: false, error: BulkIdErrorReason.NOT_FOUND },
|
{ id: 'person-2', success: false, error: BulkIdErrorReason.NOT_FOUND },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
expect(personMock.prepareReassignFaces).not.toHaveBeenCalled();
|
||||||
expect(personMock.reassignFaces).not.toHaveBeenCalled();
|
expect(personMock.reassignFaces).not.toHaveBeenCalled();
|
||||||
expect(personMock.delete).not.toHaveBeenCalled();
|
expect(personMock.delete).not.toHaveBeenCalled();
|
||||||
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
expect(accessMock.person.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['person-1']));
|
||||||
|
@ -845,6 +744,7 @@ describe(PersonService.name, () => {
|
||||||
it('should handle an error reassigning faces', async () => {
|
it('should handle an error reassigning faces', async () => {
|
||||||
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
personMock.getById.mockResolvedValue(personStub.primaryPerson);
|
||||||
personMock.getById.mockResolvedValue(personStub.mergePerson);
|
personMock.getById.mockResolvedValue(personStub.mergePerson);
|
||||||
|
personMock.prepareReassignFaces.mockResolvedValue([assetStub.image.id]);
|
||||||
personMock.reassignFaces.mockRejectedValue(new Error('update failed'));
|
personMock.reassignFaces.mockRejectedValue(new Error('update failed'));
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
|
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-1']));
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
|
accessMock.person.checkOwnerAccess.mockResolvedValueOnce(new Set(['person-2']));
|
||||||
|
|
|
@ -28,9 +28,6 @@ import {
|
||||||
import { StorageCore } from '../storage';
|
import { StorageCore } from '../storage';
|
||||||
import { SystemConfigCore } from '../system-config';
|
import { SystemConfigCore } from '../system-config';
|
||||||
import {
|
import {
|
||||||
AssetFaceResponseDto,
|
|
||||||
AssetFaceUpdateDto,
|
|
||||||
FaceDto,
|
|
||||||
MergePersonDto,
|
MergePersonDto,
|
||||||
PeopleResponseDto,
|
PeopleResponseDto,
|
||||||
PeopleUpdateDto,
|
PeopleUpdateDto,
|
||||||
|
@ -38,7 +35,6 @@ import {
|
||||||
PersonSearchDto,
|
PersonSearchDto,
|
||||||
PersonStatisticsResponseDto,
|
PersonStatisticsResponseDto,
|
||||||
PersonUpdateDto,
|
PersonUpdateDto,
|
||||||
mapFaces,
|
|
||||||
mapPerson,
|
mapPerson,
|
||||||
} from './person.dto';
|
} from './person.dto';
|
||||||
|
|
||||||
|
@ -84,86 +80,6 @@ export class PersonService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
createPerson(authUser: AuthUserDto): Promise<PersonResponseDto> {
|
|
||||||
return this.repository.create({ ownerId: authUser.id });
|
|
||||||
}
|
|
||||||
|
|
||||||
async reassignFaces(authUser: AuthUserDto, personId: string, dto: AssetFaceUpdateDto): Promise<PersonResponseDto[]> {
|
|
||||||
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
|
|
||||||
const person = await this.findOrFail(personId);
|
|
||||||
const result: PersonResponseDto[] = [];
|
|
||||||
const changeFeaturePhoto: string[] = [];
|
|
||||||
for (const data of dto.data) {
|
|
||||||
const faces = await this.repository.getFacesByIds([{ personId: data.personId, assetId: data.assetId }]);
|
|
||||||
|
|
||||||
for (const face of faces) {
|
|
||||||
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, face.id);
|
|
||||||
if (person.faceAssetId === null) {
|
|
||||||
changeFeaturePhoto.push(person.id);
|
|
||||||
}
|
|
||||||
if (face.person && face.person.faceAssetId === face.id) {
|
|
||||||
changeFeaturePhoto.push(face.person.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.repository.reassignFace(face.id, personId);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push(person);
|
|
||||||
}
|
|
||||||
if (changeFeaturePhoto.length > 0) {
|
|
||||||
// Remove duplicates
|
|
||||||
await this.createNewFeaturePhoto(Array.from(new Set(changeFeaturePhoto)));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async reassignFacesById(authUser: AuthUserDto, personId: string, dto: FaceDto): Promise<PersonResponseDto> {
|
|
||||||
await this.access.requirePermission(authUser, Permission.PERSON_WRITE, personId);
|
|
||||||
|
|
||||||
await this.access.requirePermission(authUser, Permission.PERSON_CREATE, dto.id);
|
|
||||||
const face = await this.repository.getFaceById(dto.id);
|
|
||||||
const person = await this.findOrFail(personId);
|
|
||||||
|
|
||||||
await this.repository.reassignFace(face.id, personId);
|
|
||||||
if (person.faceAssetId === null) {
|
|
||||||
await this.createNewFeaturePhoto([person.id]);
|
|
||||||
}
|
|
||||||
if (face.person && face.person.faceAssetId === face.id) {
|
|
||||||
await this.createNewFeaturePhoto([face.person.id]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.findOrFail(personId).then(mapPerson);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFacesById(authUser: AuthUserDto, dto: FaceDto): Promise<AssetFaceResponseDto[]> {
|
|
||||||
await this.access.requirePermission(authUser, Permission.ASSET_READ, dto.id);
|
|
||||||
const faces = await this.repository.getFaces(dto.id);
|
|
||||||
return faces.map((asset) => mapFaces(asset, authUser));
|
|
||||||
}
|
|
||||||
|
|
||||||
async createNewFeaturePhoto(changeFeaturePhoto: string[]) {
|
|
||||||
this.logger.debug(
|
|
||||||
`Changing feature photos for ${changeFeaturePhoto.length} ${changeFeaturePhoto.length > 1 ? 'people' : 'person'}`,
|
|
||||||
);
|
|
||||||
for (const personId of changeFeaturePhoto) {
|
|
||||||
const assetFace = await this.repository.getRandomFace(personId);
|
|
||||||
|
|
||||||
if (assetFace !== null) {
|
|
||||||
await this.repository.update({
|
|
||||||
id: personId,
|
|
||||||
faceAssetId: assetFace.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.jobRepository.queue({
|
|
||||||
name: JobName.GENERATE_PERSON_THUMBNAIL,
|
|
||||||
data: {
|
|
||||||
id: personId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getById(authUser: AuthUserDto, id: string): Promise<PersonResponseDto> {
|
async getById(authUser: AuthUserDto, id: string): Promise<PersonResponseDto> {
|
||||||
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
await this.access.requirePermission(authUser, Permission.PERSON_READ, id);
|
||||||
return this.findOrFail(id).then(mapPerson);
|
return this.findOrFail(id).then(mapPerson);
|
||||||
|
@ -212,7 +128,7 @@ export class PersonService {
|
||||||
throw new BadRequestException('Invalid assetId for feature face');
|
throw new BadRequestException('Invalid assetId for feature face');
|
||||||
}
|
}
|
||||||
|
|
||||||
person = await this.repository.update({ id, faceAssetId: face.id });
|
person = await this.repository.update({ id, faceAssetId: assetId });
|
||||||
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } });
|
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,9 +255,9 @@ export class PersonService {
|
||||||
personId = newPerson.id;
|
personId = newPerson.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const face = await this.repository.createFace({
|
const faceId: AssetFaceId = { assetId: asset.id, personId };
|
||||||
assetId: asset.id,
|
await this.repository.createFace({
|
||||||
personId,
|
...faceId,
|
||||||
embedding,
|
embedding,
|
||||||
imageHeight: rest.imageHeight,
|
imageHeight: rest.imageHeight,
|
||||||
imageWidth: rest.imageWidth,
|
imageWidth: rest.imageWidth,
|
||||||
|
@ -350,11 +266,10 @@ export class PersonService {
|
||||||
boundingBoxY1: rest.boundingBox.y1,
|
boundingBoxY1: rest.boundingBox.y1,
|
||||||
boundingBoxY2: rest.boundingBox.y2,
|
boundingBoxY2: rest.boundingBox.y2,
|
||||||
});
|
});
|
||||||
const faceId: AssetFaceId = { assetId: asset.id, personId };
|
|
||||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_FACE, data: faceId });
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_FACE, data: faceId });
|
||||||
|
|
||||||
if (newPerson) {
|
if (newPerson) {
|
||||||
await this.repository.update({ id: personId, faceAssetId: face.id });
|
await this.repository.update({ id: personId, faceAssetId: asset.id });
|
||||||
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: newPerson.id } });
|
await this.jobRepository.queue({ name: JobName.GENERATE_PERSON_THUMBNAIL, data: { id: newPerson.id } });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,13 +304,14 @@ export class PersonService {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const face = await this.repository.getFaceByIdWithAssets(person.faceAssetId);
|
const [face] = await this.repository.getFacesByIds([{ personId: person.id, assetId: person.faceAssetId }]);
|
||||||
if (face === null) {
|
if (!face) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
assetId,
|
assetId,
|
||||||
|
personId,
|
||||||
boundingBoxX1: x1,
|
boundingBoxX1: x1,
|
||||||
boundingBoxX2: x2,
|
boundingBoxX2: x2,
|
||||||
boundingBoxY1: y1,
|
boundingBoxY1: y1,
|
||||||
|
@ -408,7 +324,8 @@ export class PersonService {
|
||||||
if (!asset?.resizePath) {
|
if (!asset?.resizePath) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.logger.verbose(`Cropping face for person: ${person.id}`);
|
|
||||||
|
this.logger.verbose(`Cropping face for person: ${personId}`);
|
||||||
const thumbnailPath = StorageCore.getPersonThumbnailPath(person);
|
const thumbnailPath = StorageCore.getPersonThumbnailPath(person);
|
||||||
this.storageCore.ensureFolders(thumbnailPath);
|
this.storageCore.ensureFolders(thumbnailPath);
|
||||||
|
|
||||||
|
@ -478,6 +395,10 @@ export class PersonService {
|
||||||
const mergeData: UpdateFacesData = { oldPersonId: mergeId, newPersonId: id };
|
const mergeData: UpdateFacesData = { oldPersonId: mergeId, newPersonId: id };
|
||||||
this.logger.log(`Merging ${mergeName} into ${primaryName}`);
|
this.logger.log(`Merging ${mergeName} into ${primaryName}`);
|
||||||
|
|
||||||
|
const assetIds = await this.repository.prepareReassignFaces(mergeData);
|
||||||
|
for (const assetId of assetIds) {
|
||||||
|
await this.jobRepository.queue({ name: JobName.SEARCH_REMOVE_FACE, data: { assetId, personId: mergeId } });
|
||||||
|
}
|
||||||
await this.repository.reassignFaces(mergeData);
|
await this.repository.reassignFaces(mergeData);
|
||||||
await this.jobRepository.queue({ name: JobName.PERSON_DELETE, data: { id: mergePerson.id } });
|
await this.jobRepository.queue({ name: JobName.PERSON_DELETE, data: { id: mergePerson.id } });
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@ export interface IAccessRepository {
|
||||||
};
|
};
|
||||||
|
|
||||||
person: {
|
person: {
|
||||||
hasFaceOwnerAccess(userId: string, assetFaceId: Set<string>): Promise<Set<string>>;
|
|
||||||
checkOwnerAccess(userId: string, personIds: Set<string>): Promise<Set<string>>;
|
checkOwnerAccess(userId: string, personIds: Set<string>): Promise<Set<string>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ export enum CommunicationEvent {
|
||||||
ASSET_DELETE = 'on_asset_delete',
|
ASSET_DELETE = 'on_asset_delete',
|
||||||
ASSET_TRASH = 'on_asset_trash',
|
ASSET_TRASH = 'on_asset_trash',
|
||||||
ASSET_UPDATE = 'on_asset_update',
|
ASSET_UPDATE = 'on_asset_update',
|
||||||
ASSET_HIDDEN = 'on_asset_hidden',
|
|
||||||
ASSET_RESTORE = 'on_asset_restore',
|
ASSET_RESTORE = 'on_asset_restore',
|
||||||
PERSON_THUMBNAIL = 'on_person_thumbnail',
|
PERSON_THUMBNAIL = 'on_person_thumbnail',
|
||||||
SERVER_VERSION = 'on_server_version',
|
SERVER_VERSION = 'on_server_version',
|
||||||
|
|
|
@ -26,10 +26,6 @@ export interface QueueStatus {
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum QueueCleanType {
|
|
||||||
FAILED = 'failed',
|
|
||||||
}
|
|
||||||
|
|
||||||
export type JobItem =
|
export type JobItem =
|
||||||
// Transcoding
|
// Transcoding
|
||||||
| { name: JobName.QUEUE_VIDEO_CONVERSION; data: IBaseJob }
|
| { name: JobName.QUEUE_VIDEO_CONVERSION; data: IBaseJob }
|
||||||
|
@ -124,7 +120,6 @@ export interface IJobRepository {
|
||||||
pause(name: QueueName): Promise<void>;
|
pause(name: QueueName): Promise<void>;
|
||||||
resume(name: QueueName): Promise<void>;
|
resume(name: QueueName): Promise<void>;
|
||||||
empty(name: QueueName): Promise<void>;
|
empty(name: QueueName): Promise<void>;
|
||||||
clear(name: QueueName, type: QueueCleanType): Promise<string[]>;
|
|
||||||
getQueueStatus(name: QueueName): Promise<QueueStatus>;
|
getQueueStatus(name: QueueName): Promise<QueueStatus>;
|
||||||
getJobCounts(name: QueueName): Promise<JobCounts>;
|
getJobCounts(name: QueueName): Promise<JobCounts>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ export interface ImmichTags extends Omit<Tags, 'FocalLength' | 'Duration'> {
|
||||||
ImagePixelDepth?: string;
|
ImagePixelDepth?: string;
|
||||||
FocalLength?: number;
|
FocalLength?: number;
|
||||||
Duration?: number | ExifDuration;
|
Duration?: number | ExifDuration;
|
||||||
|
Orientation?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMetadataRepository {
|
export interface IMetadataRepository {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue