Compare commits

..

7 commits

Author SHA1 Message Date
shalong-tanwen
3b8656a603 style: colors 2023-12-03 23:54:47 +05:30
shalong-tanwen
85841e3349 refactor: theme provider to generator 2023-12-03 23:08:10 +05:30
shalong-tanwen
966b38b99f Merge branch 'main' into refactor/mobile-colors 2023-12-03 23:02:28 +05:30
shalong-tanwen
748821d7a7 chore: pull main 2023-11-19 12:04:25 +05:30
shalong-tanwen
ecb1669ab8 wip(mobile): unify-colors 2023-11-19 11:54:42 +05:30
shalong-tanwen
780d3b174d chore: pull main 2023-11-11 15:11:12 +05:30
shalong-tanwen
d20801c65c wip(mobile): unify-colors 2023-11-11 09:48:29 +05:30
196 changed files with 1317 additions and 7305 deletions

View file

@ -213,7 +213,7 @@ jobs:
runs-on: ubuntu-latest
services:
postgres:
image: postgres@sha256:6dfee32131933ab4ca25a00360c3f427fdc134de56f9a90c6c9a4956b48aea85
image: postgres@sha256:71da05df8c4f1e1bac9b92ebfba2a0eeb183f6ac6a972fd5e55e8146e29efe9c
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres

View file

@ -586,142 +586,6 @@ export const 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
@ -978,10 +842,10 @@ export interface AssetResponseDto {
'ownerId': string;
/**
*
* @type {Array<PersonWithFacesResponseDto>}
* @type {Array<PersonResponseDto>}
* @memberof AssetResponseDto
*/
'people'?: Array<PersonWithFacesResponseDto>;
'people'?: Array<PersonResponseDto>;
/**
*
* @type {boolean}
@ -1808,19 +1672,6 @@ export interface ExifResponseDto {
*/
'timeZone'?: string | null;
}
/**
*
* @export
* @interface FaceDto
*/
export interface FaceDto {
/**
*
* @type {string}
* @memberof FaceDto
*/
'id': string;
}
/**
*
* @export
@ -1934,8 +1785,7 @@ export const JobCommand = {
Start: 'start',
Pause: 'pause',
Resume: 'resume',
Empty: 'empty',
ClearFailed: 'clear-failed'
Empty: 'empty'
} as const;
export type JobCommand = typeof JobCommand[keyof typeof JobCommand];
@ -2713,49 +2563,6 @@ export interface PersonUpdateDto {
*/
'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
@ -11541,233 +11348,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
* @export
@ -13599,44 +13179,6 @@ export class PartnerApi extends BaseAPI {
*/
export const PersonApiAxiosParamCreator = function (configuration?: Configuration) {
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]
@ -13896,54 +13438,6 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
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
@ -14046,15 +13540,6 @@ export const PersonApiAxiosParamCreator = function (configuration?: Configuratio
export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = PersonApiAxiosParamCreator(configuration)
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]
@ -14116,17 +13601,6 @@ export const PersonApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.mergePerson(id, mergePersonDto, options);
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
@ -14158,14 +13632,6 @@ export const PersonApiFp = function(configuration?: Configuration) {
export const PersonApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = PersonApiFp(configuration)
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.
@ -14220,15 +13686,6 @@ export const PersonApiFactory = function (configuration?: Configuration, basePat
mergePerson(requestParameters: PersonApiMergePersonRequest, options?: AxiosRequestConfig): AxiosPromise<Array<BulkIdResponseDto>> {
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.
@ -14341,27 +13798,6 @@ export interface PersonApiMergePersonRequest {
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.
* @export
@ -14404,16 +13840,6 @@ export interface PersonApiUpdatePersonRequest {
* @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.
@ -14480,17 +13906,6 @@ export class PersonApi extends BaseAPI {
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.

View file

@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
COPY poetry.lock pyproject.toml ./
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/*

View file

@ -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 \
TRANSFORMERS_CACHE=/cache \

View file

@ -2,5 +2,5 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
distributionSha256Sum=6001aba9b2204d26fa25a5800bb9382cf3ee01ccb78fe77317b2872336eb2f80
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip
distributionSha256Sum=518a863631feb7452b8f1b3dc2aaee5f388355cc3421bbd0275fbeadd77e84b2

View file

@ -144,8 +144,6 @@
"control_bottom_app_bar_stack": "Stack",
"control_bottom_app_bar_unarchive": "Unarchive",
"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_shared_album_page_create": "Create",
"create_shared_album_page_share": "Share",
@ -167,7 +165,6 @@
"exif_bottom_sheet_description": "Add Description...",
"exif_bottom_sheet_details": "DETAILS",
"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_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
@ -464,18 +461,5 @@
"viewer_remove_from_stack": "Remove from Stack",
"viewer_stack_use_as_main_asset": "Use as Main Asset",
"viewer_unstack": "Un-Stack",
"scaffold_body_error_occurred": "Error occurred",
"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"
"scaffold_body_error_occured": "Error occured"
}

View file

@ -1,5 +0,0 @@
import 'package:flutter/material.dart';
const Color immichBackgroundColor = Color(0xFFf6f8fe);
const Color immichDarkBackgroundColor = Color.fromARGB(255, 0, 0, 0);
const Color immichDarkThemePrimaryColor = Color.fromARGB(255, 173, 203, 250);

View file

@ -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);
}
}

View file

@ -1,5 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
extension ContextHelper on BuildContext {
// Returns the current size from MediaQuery
@ -32,6 +34,12 @@ extension ContextHelper on BuildContext {
// Current ColorScheme used
ColorScheme get colorScheme => themeData.colorScheme;
// Red Accent harmonized with primary color
Color get redColor => redAccent.harmonizeWith(primaryColor);
// Orange Accent harmonized with primary color
Color get orangeColor => orangeAccent.harmonizeWith(primaryColor);
// Pop-out from the current context with optional result
void pop<T>([T? result]) => Navigator.of(this).pop(result);

View file

@ -0,0 +1,27 @@
import 'dart:ui';
extension LightenDarken on Color {
/// Darken a color by [percent] amount (100 = black)
Color darken([int percent = 10]) {
assert(1 <= percent && percent <= 100);
var f = 1 - percent / 100;
return Color.fromARGB(
alpha,
(red * f).round(),
(green * f).round(),
(blue * f).round(),
);
}
/// Lighten a color by [percent] amount (100 = white)
Color lighten([int percent = 10]) {
assert(1 <= percent && percent <= 100);
var p = percent / 100;
return Color.fromARGB(
alpha,
red + ((255 - red) * p).round(),
green + ((255 - green) * p).round(),
blue + ((255 - blue) * p).round(),
);
}
}

View file

@ -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')}";
}

View file

@ -9,6 +9,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/providers/theme.provider.dart';
import 'package:timezone/data/latest.dart';
import 'package:immich_mobile/constants/locales.dart';
import 'package:immich_mobile/modules/backup/background_service/background.service.dart';

View file

@ -95,11 +95,7 @@ class ActivityStatisticsNotifier extends StateNotifier<int> {
}
Future<void> fetchStatistics() async {
final count =
await _activityService.getStatistics(albumId, assetId: assetId);
if (mounted) {
state = count;
}
state = await _activityService.getStatistics(albumId, assetId: assetId);
}
Future<void> addActivity() async {

View file

@ -50,9 +50,8 @@ class ActivitiesPage extends HookConsumerWidget {
);
buildTitleWithTimestamp(Activity activity, {bool leftAlign = true}) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final textStyle = context.textTheme.bodyMedium
?.copyWith(color: textColor.withOpacity(0.6));
final textStyle = context.textTheme.bodyMedium?.copyWith(
color: context.textTheme.bodyMedium?.color?.withOpacity(0.6),);
return Row(
mainAxisAlignment: leftAlign
@ -150,14 +149,14 @@ class ActivitiesPage extends HookConsumerWidget {
},
),
),
suffixIconColor: liked ? Colors.red[700] : null,
suffixIconColor: liked ? context.redColor : null,
hintText: isReadOnly
? 'shared_album_activities_input_disable'.tr()
: 'shared_album_activities_input_hint'.tr(),
hintStyle: TextStyle(
fontWeight: FontWeight.normal,
fontSize: 14,
color: Colors.grey[600],
color: context.themeData.hintColor,
),
),
onEditingComplete: () async {
@ -200,27 +199,31 @@ class ActivitiesPage extends HookConsumerWidget {
onDismissed: (direction) async =>
await ref.read(provider.notifier).removeActivity(activity.id),
background: Container(
color: canDelete ? Colors.red[400] : Colors.grey[600],
color: canDelete
? context.colorScheme.error
: context.themeData.disabledColor,
alignment: AlignmentDirectional.centerStart,
child: canDelete
? const Padding(
padding: EdgeInsets.all(15),
? Padding(
padding: const EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
color: context.colorScheme.surface,
),
)
: null,
),
secondaryBackground: Container(
color: canDelete ? Colors.red[400] : Colors.grey[600],
color: canDelete
? context.colorScheme.error
: context.themeData.disabledColor,
alignment: AlignmentDirectional.centerEnd,
child: canDelete
? const Padding(
padding: EdgeInsets.all(15),
? Padding(
padding: const EdgeInsets.all(15),
child: Icon(
Icons.delete_sweep_rounded,
color: Colors.black,
color: context.colorScheme.surface,
),
)
: null,
@ -285,7 +288,7 @@ class ActivitiesPage extends HookConsumerWidget {
alignment: Alignment.center,
child: Icon(
Icons.favorite_rounded,
color: Colors.red[700],
color: context.redColor,
),
),
title: buildTitleWithTimestamp(activity),

View file

@ -23,32 +23,38 @@ class AlbumThumbnailCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
var isDarkTheme = context.isDarkTheme;
return LayoutBuilder(
builder: (context, constraints) {
var cardSize = constraints.maxWidth;
buildEmptyThumbnail() {
return Container(
return SizedBox(
height: cardSize,
width: cardSize,
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[800] : Colors.grey[200],
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0)),
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
),
),
),
);
}
buildAlbumThumbnail() => ImmichImage(
album.thumbnail.value,
width: cardSize,
height: cardSize,
buildAlbumThumbnail() => Card(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: ImmichImage(
album.thumbnail.value,
width: cardSize,
height: cardSize,
),
);
buildAlbumTextRow() {
@ -62,9 +68,9 @@ class AlbumThumbnailCard extends StatelessWidget {
}
}
return RichText(
return Text.rich(
overflow: TextOverflow.fade,
text: TextSpan(
TextSpan(
children: [
TextSpan(
text: album.assetCount == 1
@ -78,7 +84,9 @@ class AlbumThumbnailCard extends StatelessWidget {
if (owner != null)
TextSpan(
text: owner,
style: context.textTheme.bodyMedium,
style: context.textTheme.bodyMedium?.copyWith(
color: context.colorScheme.onSurface,
),
),
],
),
@ -87,40 +95,36 @@ class AlbumThumbnailCard extends StatelessWidget {
return GestureDetector(
onTap: onTap,
child: Flex(
direction: Axis.vertical,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: cardSize,
height: cardSize,
child: ClipRRect(
borderRadius: BorderRadius.circular(20),
child: album.thumbnail.value == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: SizedBox(
width: cardSize,
child: Text(
album.name,
style: context.textTheme.bodyMedium?.copyWith(
color: context.primaryColor,
fontWeight: FontWeight.w500,
),
),
),
),
buildAlbumTextRow(),
],
SizedBox(
width: cardSize,
height: cardSize,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(20)),
child: album.thumbnail.value == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0, left: 8.0),
child: SizedBox(
width: cardSize,
child: Text(
album.name,
style: context.textTheme.bodyMedium?.copyWith(
color: context.primaryColor,
fontWeight: FontWeight.w500,
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 2.0, left: 8.0),
child: buildAlbumTextRow(),
),
],
),
);

View file

@ -23,36 +23,46 @@ class AlbumThumbnailListTile extends StatelessWidget {
var cardSize = 68.0;
buildEmptyThumbnail() {
return Container(
decoration: BoxDecoration(
color: context.isDarkTheme ? Colors.grey[800] : Colors.grey[200],
),
child: SizedBox(
height: cardSize,
width: cardSize,
child: const Center(
child: Icon(Icons.no_photography),
return SizedBox(
height: cardSize,
width: cardSize,
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
),
),
),
);
}
buildAlbumThumbnail() {
return CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
return Card(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
child: CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
),
httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
},
cacheKey:
getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
),
httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
},
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),
);
}
@ -68,46 +78,46 @@ class AlbumThumbnailListTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
borderRadius: BorderRadius.circular(8),
child: album.thumbnail.value == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
album.name,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
album.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
album.assetCount == 1
? 'album_thumbnail_card_item'
: 'album_thumbnail_card_items',
style: const TextStyle(
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
album.assetCount == 1
? 'album_thumbnail_card_item'
: '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,
),
).tr(args: ['${album.assetCount}']),
if (album.shared)
const Text(
'album_thumbnail_card_shared',
style: TextStyle(
fontSize: 12,
),
).tr(),
],
),
],
),
).tr(),
],
),
],
),
),
],

View file

@ -20,8 +20,6 @@ class AlbumTitleTextField extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isDarkTheme = context.isDarkTheme;
return TextField(
onChanged: (v) {
if (v.isEmpty) {
@ -33,9 +31,8 @@ class AlbumTitleTextField extends ConsumerWidget {
ref.watch(albumTitleProvider.notifier).setAlbumTitle(v);
},
focusNode: albumTitleTextFieldFocusNode,
style: TextStyle(
style: const TextStyle(
fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
controller: albumTitleController,
@ -70,15 +67,10 @@ class AlbumTitleTextField extends ConsumerWidget {
borderRadius: BorderRadius.circular(10),
),
hintText: 'share_add_title'.tr(),
hintStyle: TextStyle(
hintStyle: const TextStyle(
fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
focusColor: Colors.grey[300],
fillColor: isDarkTheme
? const Color.fromARGB(255, 32, 33, 35)
: Colors.grey[200],
filled: isAlbumTitleTextFieldFocus.value,
),
);

View file

@ -108,7 +108,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
'Confirm',
style: TextStyle(
fontWeight: FontWeight.bold,
color: !context.isDarkTheme ? Colors.red : Colors.red[300],
color: context.colorScheme.error,
),
),
),

View file

@ -78,15 +78,10 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
borderSide: const BorderSide(color: Colors.transparent),
borderRadius: BorderRadius.circular(10),
),
focusColor: Colors.grey[300],
fillColor: context.isDarkTheme
? const Color.fromARGB(255, 32, 33, 35)
: Colors.grey[200],
filled: titleFocusNode.hasFocus,
hintText: 'share_add_title'.tr(),
hintStyle: TextStyle(
hintStyle: const TextStyle(
fontSize: 28,
color: context.isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
),

View file

@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/color_extensions.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@ -129,7 +130,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
),
subtitle: Text(
album.owner.value?.email ?? "",
style: TextStyle(color: Colors.grey[600]),
style: TextStyle(color: context.colorScheme.onSurface.darken(40)),
),
trailing: Text(
"shared_album_section_people_owner_label",
@ -157,7 +158,7 @@ class AlbumOptionsPage extends HookConsumerWidget {
),
subtitle: Text(
user.email,
style: TextStyle(color: Colors.grey[600]),
style: TextStyle(color: context.colorScheme.onSurface.darken(40)),
),
trailing: userId == user.id || isOwner
? const Icon(Icons.more_horiz_rounded)
@ -201,9 +202,6 @@ class AlbumOptionsPage extends HookConsumerWidget {
album.activityEnabled = value;
}
},
activeColor: activityEnabled.value
? context.primaryColor
: context.themeData.disabledColor,
dense: true,
title: Text(
"shared_album_activity_setting_title",

View file

@ -148,7 +148,7 @@ class AlbumViewerPage extends HookConsumerWidget {
titleFocusNode: titleFocusNode,
)
: Padding(
padding: const EdgeInsets.only(left: 8.0),
padding: const EdgeInsets.only(left: 8.0, bottom: 20.0),
child: Text(
album.name,
style: context.textTheme.headlineMedium,

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/color_extensions.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart';
import 'package:immich_mobile/routing/router.dart';
@ -21,7 +22,6 @@ class LibraryPage extends HookConsumerWidget {
final trashEnabled =
ref.watch(serverInfoProvider.select((v) => v.serverFeatures.trash));
final albums = ref.watch(albumProvider);
var isDarkTheme = context.isDarkTheme;
var settings = ref.watch(appSettingsServiceProvider);
useEffect(
@ -102,9 +102,8 @@ class LibraryPage extends HookConsumerWidget {
),
Text(
option,
style: TextStyle(
style: context.textTheme.displaySmall?.copyWith(
color: selected ? context.primaryColor : null,
fontSize: 12.0,
),
),
],
@ -137,36 +136,29 @@ class LibraryPage extends HookConsumerWidget {
Widget buildCreateAlbumButton() {
return LayoutBuilder(
builder: (context, constraints) {
var cardSize = constraints.maxWidth;
return GestureDetector(
onTap: () {
context.autoPush(CreateAlbumRoute(isSharedAlbum: false));
},
onTap: () =>
context.autoPush(CreateAlbumRoute(isSharedAlbum: false)),
child: Padding(
padding:
const EdgeInsets.only(bottom: 32), // Adjust padding to suit
padding: const EdgeInsets.only(bottom: 32),
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),
SizedBox.square(
dimension: constraints.maxWidth,
child: Card(
clipBehavior: Clip.hardEdge,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
),
color: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Icon(
Icons.add_rounded,
size: 28,
color: context.primaryColor,
color: context.themeData.cardColor.lighten(5),
child: Center(
child: Icon(
Icons.add_rounded,
size: 28,
color: context.primaryColor,
),
),
),
),
@ -174,6 +166,7 @@ class LibraryPage extends HookConsumerWidget {
padding: const EdgeInsets.only(
top: 8.0,
bottom: 16,
left: 8.0,
),
child: Text(
'library_page_new_album',
@ -194,30 +187,17 @@ class LibraryPage extends HookConsumerWidget {
Function() onClick,
) {
return Expanded(
child: OutlinedButton.icon(
child: ElevatedButton.icon(
onPressed: onClick,
label: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
label,
style: TextStyle(
color: context.isDarkTheme
? Colors.white
: Colors.black.withAlpha(200),
),
),
child: Text(label),
),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
backgroundColor: isDarkTheme ? Colors.grey[900] : Colors.grey[50],
side: BorderSide(
color: isDarkTheme ? Colors.grey[800]! : Colors.grey[300]!,
),
style: context.themeData.elevatedButtonTheme.style?.copyWith(
alignment: Alignment.centerLeft,
),
icon: Icon(
icon,
color: context.primaryColor,
),
),
);
@ -231,7 +211,7 @@ class LibraryPage extends HookConsumerWidget {
return trashEnabled
? InkWell(
onTap: () => context.autoPush(const TrashRoute()),
borderRadius: BorderRadius.circular(12),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: const Icon(
Icons.delete_rounded,
size: 25,
@ -257,8 +237,8 @@ class LibraryPage extends HookConsumerWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildLibraryNavButton(
"library_page_favorites".tr(), Icons.favorite_border, () {
buildLibraryNavButton("library_page_favorites".tr(),
Icons.favorite_outline_rounded, () {
context.autoNavigate(const FavoritesRoute());
}),
const SizedBox(width: 12.0),
@ -297,7 +277,6 @@ class LibraryPage extends HookConsumerWidget {
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 250,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: .7,
),

View file

@ -29,9 +29,11 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: context.primaryColor,
child: const Icon(
radius: 22,
child: Icon(
Icons.check_rounded,
size: 25,
color: context.colorScheme.surface,
size: 24,
),
);
} else {
@ -49,14 +51,10 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Chip(
backgroundColor: context.primaryColor.withOpacity(0.15),
backgroundColor: context.colorScheme.primaryContainer,
label: Text(
user.email,
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
style: context.textTheme.displaySmall,
),
),
),
@ -72,9 +70,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
padding: const EdgeInsets.all(16.0),
child: Text(
'select_additional_user_for_sharing_page_suggestions'.tr(),
style: const TextStyle(
style: TextStyle(
fontSize: 14,
color: Colors.grey,
color: context.themeData.hintColor,
fontWeight: FontWeight.bold,
),
),

View file

@ -56,9 +56,11 @@ class SelectUserForSharingPage extends HookConsumerWidget {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: context.primaryColor,
child: const Icon(
radius: 22,
child: Icon(
Icons.check_rounded,
size: 25,
color: context.colorScheme.surface,
size: 24,
),
);
} else {
@ -76,14 +78,10 @@ class SelectUserForSharingPage extends HookConsumerWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: Chip(
backgroundColor: context.primaryColor.withOpacity(0.15),
backgroundColor: context.colorScheme.primaryContainer,
label: Text(
user.email,
style: const TextStyle(
fontSize: 12,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
style: context.textTheme.displaySmall,
),
),
),
@ -97,11 +95,11 @@ class SelectUserForSharingPage extends HookConsumerWidget {
),
Padding(
padding: const EdgeInsets.all(16.0),
child: const Text(
child: Text(
'select_user_for_sharing_page_share_suggestions',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
color: context.themeData.hintColor,
fontWeight: FontWeight.bold,
),
).tr(),

View file

@ -128,6 +128,9 @@ class SharingPage extends HookConsumerWidget {
Icons.photo_album_outlined,
size: 20,
),
style: context.themeData.elevatedButtonTheme.style?.copyWith(
alignment: Alignment.centerLeft,
),
label: const Text(
"sharing_silver_appbar_create_shared_album",
maxLines: 1,
@ -146,6 +149,9 @@ class SharingPage extends HookConsumerWidget {
Icons.link,
size: 20,
),
style: context.themeData.elevatedButtonTheme.style?.copyWith(
alignment: Alignment.centerLeft,
),
label: const Text(
"sharing_silver_appbar_shared_links",
style: TextStyle(
@ -169,8 +175,8 @@ class SharingPage extends HookConsumerWidget {
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
side: const BorderSide(
color: Colors.grey,
side: BorderSide(
color: context.themeData.hintColor,
width: 0.5,
),
),
@ -191,7 +197,8 @@ class SharingPage extends HookConsumerWidget {
padding: const EdgeInsets.all(8.0),
child: Text(
'sharing_page_empty_list',
style: context.textTheme.displaySmall,
style: context.textTheme.displaySmall
?.copyWith(color: context.primaryColor),
).tr(),
),
Padding(

View file

@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
class AdvancedBottomSheet extends HookConsumerWidget {
final Asset assetDetail;
@ -24,33 +25,47 @@ class AdvancedBottomSheet extends HookConsumerWidget {
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: LayoutBuilder(
builder: (context, constraints) {
builder: (ctx, constraints) {
// One column
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 32.0),
const Align(
child: Text(
"ADVANCED INFO",
style: TextStyle(fontSize: 12.0),
const Padding(
padding: EdgeInsets.only(top: 15, bottom: 10),
child: CustomDraggingHandle(),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: TextButton.icon(
label: Text(
"ADVANCED INFO",
style: context.textTheme.displaySmall,
),
icon: Icon(
Icons.copy,
size: 16.0,
color: context.primaryColor,
),
onPressed: () {
Clipboard.setData(
ClipboardData(text: assetDetail.toString()),
).then((_) {
ScaffoldMessenger.of(ctx).showSnackBar(
const SnackBar(
content: Text("Copied to clipboard"),
behavior: SnackBarBehavior.floating,
),
);
});
},
),
),
const SizedBox(height: 32.0),
Container(
decoration: BoxDecoration(
color: context.isDarkTheme
? Colors.grey[900]
: Colors.grey[200],
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 16,
top: 8,
bottom: 16,
),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: ListView(
shrinkWrap: true,
children: [

View file

@ -20,7 +20,6 @@ class DescriptionInput extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final textColor = context.isDarkTheme ? Colors.white : Colors.black;
final controller = useTextEditingController();
final focusNode = useFocusNode();
final isFocus = useState(false);
@ -68,7 +67,7 @@ class DescriptionInput extends HookConsumerWidget {
},
icon: Icon(
Icons.cancel_rounded,
color: Colors.grey[500],
color: context.themeData.hintColor,
),
splashRadius: 10,
);
@ -98,7 +97,7 @@ class DescriptionInput extends HookConsumerWidget {
hintText: 'description_input_hint_text'.tr(),
border: InputBorder.none,
hintStyle: context.textTheme.labelLarge?.copyWith(
color: textColor.withOpacity(0.5),
color: context.colorScheme.onSurface.withOpacity(0.5),
),
suffixIcon: suffixIcon,
),

View file

@ -4,15 +4,14 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.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/duration_extensions.dart';
import 'package:timezone/timezone.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/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/ui/drag_sheet.dart';
import 'package:immich_mobile/utils/selection_handlers.dart';
import 'package:latlong2/latlong.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
import 'package:url_launcher/url_launcher.dart';
@ -22,69 +21,98 @@ class ExifBottomSheet extends HookConsumerWidget {
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
Widget build(BuildContext context, WidgetRef ref) {
final assetWithExif = ref.watch(assetDetailProvider(asset));
final exifInfo = (assetWithExif.value ?? asset).exifInfo;
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',
);
}
var textColor = context.colorScheme.onSurface;
buildMap() {
return Padding(
@ -92,14 +120,12 @@ class ExifBottomSheet extends HookConsumerWidget {
child: LayoutBuilder(
builder: (context, constraints) {
return MapThumbnail(
showAttribution: false,
coords: LatLng(
exifInfo?.latitude ?? 0,
exifInfo?.longitude ?? 0,
),
height: 150,
width: constraints.maxWidth,
zoom: 12.0,
zoom: 16.0,
markers: [
Marker(
anchorPos: AnchorPos.align(AnchorAlign.top),
@ -113,7 +139,7 @@ class ExifBottomSheet extends HookConsumerWidget {
),
],
onTap: (tapPosition, latLong) async {
Uri? uri = await createCoordinatesUri();
Uri? uri = await _createCoordinatesUri(exifInfo);
if (uri == null) {
return;
@ -155,26 +181,8 @@ class ExifBottomSheet extends HookConsumerWidget {
buildLocation() {
// Guard no lat/lng
if (!hasCoordinates()) {
return asset.isRemote
? 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();
if (!hasCoordinates(exifInfo)) {
return Container();
}
return Column(
@ -183,29 +191,13 @@ class ExifBottomSheet extends HookConsumerWidget {
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"exif_bottom_sheet_location",
style: context.textTheme.labelMedium?.copyWith(
color:
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,
),
],
),
Text(
"exif_bottom_sheet_location",
style: context.textTheme.labelMedium?.copyWith(
color: context.textTheme.labelMedium?.color?.withAlpha(200),
fontWeight: FontWeight.w600,
),
).tr(),
buildMap(),
RichText(
text: TextSpan(
@ -241,27 +233,12 @@ class ExifBottomSheet extends HookConsumerWidget {
}
buildDate() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
formattedDateTime(),
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,
),
],
return Text(
formattedDateTime,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
);
}
@ -386,7 +363,7 @@ class ExifBottomSheet extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: hasCoordinates() ? 5 : 0,
flex: hasCoordinates(exifInfo) ? 5 : 0,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: buildLocation(),
@ -425,8 +402,9 @@ class ExifBottomSheet extends HookConsumerWidget {
child: CircularProgressIndicator.adaptive(),
),
),
const SizedBox(height: 8.0),
buildLocation(),
SizedBox(height: hasCoordinates() ? 16.0 : 6.0),
SizedBox(height: hasCoordinates(exifInfo) ? 16.0 : 0.0),
buildDetail(),
const SizedBox(height: 50),
],

View file

@ -795,7 +795,6 @@ class GalleryViewerPage extends HookConsumerWidget {
tag: isFromDto
? '${a.remoteId}-$heroOffset'
: a.id + heroOffset,
transitionOnUserGestures: true,
),
filterQuality: FilterQuality.high,
tightMode: true,

View file

@ -375,8 +375,6 @@ class BackupNotifier extends StateNotifier<BackUpState> {
await _getBackupAlbumsInfo();
await updateServerInfo();
await _updateBackupAssetCount();
} else {
log.warning("cannot get backup info - background backup is in progress!");
}
}

View file

@ -61,7 +61,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
Widget buildAssetInfoTable() {
return Table(
border: TableBorder.all(
color: context.themeData.primaryColorLight,
color: context.colorScheme.onSurfaceVariant,
width: 1,
),
children: [

View file

@ -2,7 +2,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
@ -135,13 +134,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
album.name,
style: TextStyle(
fontSize: 12,
color: isDarkTheme ? Colors.black : immichBackgroundColor,
color: context.colorScheme.surface,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.red[300],
deleteIconColor:
isDarkTheme ? Colors.black : immichBackgroundColor,
deleteIconColor: context.colorScheme.surface,
deleteIcon: const Icon(
Icons.cancel_rounded,
size: 15,

View file

@ -674,7 +674,6 @@ class BackupControllerPage extends HookConsumerWidget {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: const Text(
"backup_controller_page_backup",
).tr(),

View file

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class DisableMultiSelectButton extends ConsumerWidget {
class DisableMultiSelectButton extends StatelessWidget {
const DisableMultiSelectButton({
Key? key,
required this.onPressed,
@ -12,25 +12,29 @@ class DisableMultiSelectButton extends ConsumerWidget {
final int selectedItemCount;
@override
Widget build(BuildContext context, WidgetRef ref) {
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton.icon(
onPressed: () {
onPressed();
},
icon: const Icon(Icons.close_rounded),
label: Text(
'$selectedItemCount',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
),
padding: const EdgeInsets.only(left: 16.0, top: 16.0),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: ElevatedButton.icon(
style: ElevatedButton.styleFrom(
foregroundColor: context.colorScheme.surface,
backgroundColor: context.primaryColor,
),
onPressed: () {
onPressed();
},
icon: const Icon(Icons.close_rounded),
label: Text(
'$selectedItemCount',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
),
),
);
}
}

View file

@ -6,6 +6,7 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
/// Build the Scroll Thumb and label using the current configuration
typedef ScrollThumbBuilder = Widget Function(
Color backgroundColor,
Color foregroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
@ -33,6 +34,9 @@ class DraggableScrollbar extends StatefulWidget {
/// The background color of the label and thumb
final Color backgroundColor;
/// The foreground color of the arrows in the thumb
final Color foregroundColor;
/// The amount of padding that should surround the thumb
final EdgeInsetsGeometry? padding;
@ -66,6 +70,7 @@ class DraggableScrollbar extends StatefulWidget {
required this.scrollStateListener,
this.heightScrollThumb = 48.0,
this.backgroundColor = Colors.white,
this.foregroundColor = Colors.white,
this.padding,
this.scrollbarAnimationDuration = const Duration(milliseconds: 300),
this.scrollbarTimeToFade = const Duration(milliseconds: 600),
@ -85,6 +90,7 @@ class DraggableScrollbar extends StatefulWidget {
static buildScrollThumbAndLabel({
required Widget scrollThumb,
required Color backgroundColor,
required Color foregroundColor,
required Animation<double>? thumbAnimation,
required Animation<double>? labelAnimation,
required Text? labelText,
@ -123,6 +129,7 @@ class DraggableScrollbar extends StatefulWidget {
) {
return (
Color backgroundColor,
Color foregroundColor,
Animation<double> thumbAnimation,
Animation<double> labelAnimation,
double height, {
@ -131,7 +138,7 @@ class DraggableScrollbar extends StatefulWidget {
}) {
final scrollThumb = CustomPaint(
key: scrollThumbKey,
foregroundPainter: ArrowCustomPainter(Colors.white),
foregroundPainter: ArrowCustomPainter(foregroundColor),
child: Material(
elevation: 4.0,
color: backgroundColor,
@ -150,6 +157,7 @@ class DraggableScrollbar extends StatefulWidget {
return buildScrollThumbAndLabel(
scrollThumb: scrollThumb,
backgroundColor: backgroundColor,
foregroundColor: foregroundColor,
thumbAnimation: thumbAnimation,
labelAnimation: labelAnimation,
labelText: labelText,
@ -286,6 +294,7 @@ class DraggableScrollbarState extends State<DraggableScrollbar>
padding: widget.padding,
child: widget.scrollThumbBuilder(
widget.backgroundColor,
widget.foregroundColor,
_thumbAnimation,
_labelAnimation,
widget.heightScrollThumb,

View file

@ -74,9 +74,9 @@ class GroupDividerTitle extends HookConsumerWidget {
Icons.check_circle_rounded,
color: context.primaryColor,
)
: const Icon(
: Icon(
Icons.check_circle_outline_rounded,
color: Colors.grey,
color: context.colorScheme.onBackground.withAlpha(100),
),
),
],

View file

@ -222,9 +222,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
padding: const EdgeInsets.only(left: 12.0, top: 24.0),
child: Text(
title,
style: const TextStyle(
fontSize: 26,
fontWeight: FontWeight.w500,
style: context.textTheme.displayLarge?.copyWith(
color: context.colorScheme.onSurface,
),
),
);
@ -243,7 +242,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
bottom: widget.margin,
right: i + 1 == num ? 0.0 : widget.margin,
),
color: Colors.grey,
color: context.colorScheme.surfaceVariant,
),
],
);
@ -328,8 +327,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
return Text(
DateFormat.yMMMM().format(date),
style: const TextStyle(
color: Colors.white,
style: TextStyle(
color: context.colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
);
@ -372,7 +371,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
scrollStateListener: dragScrolling,
itemPositionsListener: _itemPositionsListener,
controller: _itemScrollController,
backgroundColor: context.themeData.hintColor,
backgroundColor: context.primaryColor,
foregroundColor: context.colorScheme.onPrimary,
labelTextBuilder: _labelBuilder,
labelConstraints: const BoxConstraints(maxHeight: 28),
scrollbarAnimationDuration: const Duration(milliseconds: 300),

View file

@ -43,9 +43,6 @@ class ThumbnailImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final assetContainerColor = context.isDarkTheme
? Colors.blueGrey
: context.themeData.primaryColorLight;
// Assets from response DTOs do not have an isar id, querying which would give us the default autoIncrement id
final isFromDto = asset.id == Isar.autoIncrement;
@ -54,11 +51,13 @@ class ThumbnailImage extends StatelessWidget {
return Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: assetContainerColor,
color: context.colorScheme.surfaceVariant,
),
child: Icon(
Icons.check_circle_rounded,
color: context.primaryColor,
color: onDeselect == null
? context.primaryColor.withAlpha(120)
: context.primaryColor,
),
);
} else {
@ -132,9 +131,10 @@ class ThumbnailImage extends StatelessWidget {
}
Widget buildImage() {
final image = SizedBox(
final image = Container(
width: 300,
height: 300,
color: context.colorScheme.surfaceVariant,
child: Hero(
tag: isFromDto
? '${asset.remoteId}-$heroOffset'
@ -153,9 +153,9 @@ class ThumbnailImage extends StatelessWidget {
decoration: BoxDecoration(
border: Border.all(
width: 0,
color: onDeselect == null ? Colors.grey : assetContainerColor,
color: context.colorScheme.surfaceVariant,
),
color: onDeselect == null ? Colors.grey : assetContainerColor,
color: context.colorScheme.surfaceVariant,
),
child: ClipRRect(
borderRadius: const BorderRadius.only(
@ -203,10 +203,8 @@ class ThumbnailImage extends StatelessWidget {
decoration: BoxDecoration(
border: multiselectEnabled && isSelected
? Border.all(
color: onDeselect == null
? Colors.grey
: assetContainerColor,
width: 8,
color: context.colorScheme.surfaceVariant,
width: 12,
)
: const Border(),
),

View file

@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/color_extensions.dart';
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
import 'package:immich_mobile/modules/home/models/selection_state.dart';
import 'package:immich_mobile/modules/home/ui/delete_dialog.dart';
@ -19,8 +20,6 @@ class ControlBottomAppBar extends ConsumerWidget {
final void Function() onCreateNewAlbum;
final void Function() onUpload;
final void Function() onStack;
final void Function() onEditTime;
final void Function() onEditLocation;
final List<Album> albums;
final List<Album> sharedAlbums;
@ -39,8 +38,6 @@ class ControlBottomAppBar extends ConsumerWidget {
required this.onCreateNewAlbum,
required this.onUpload,
required this.onStack,
required this.onEditTime,
required this.onEditLocation,
this.selectionAssetState = const SelectionAssetState(),
this.enabled = true,
}) : super(key: key);
@ -78,18 +75,6 @@ class ControlBottomAppBar extends ConsumerWidget {
label: "control_bottom_app_bar_favorite".tr(),
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(
iconData: Icons.delete_outline_rounded,
label: "control_bottom_app_bar_delete".tr(),
@ -144,9 +129,8 @@ class ControlBottomAppBar extends ConsumerWidget {
ScrollController scrollController,
) {
return Card(
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
surfaceTintColor: Colors.transparent,
elevation: 18.0,
elevation: 2,
color: context.colorScheme.surface.lighten(15),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),

View file

@ -213,10 +213,10 @@ class HomePage extends HookConsumerWidget {
processing.value = true;
selectionEnabledHook.value = false;
try {
ref.read(manualUploadProvider.notifier).uploadAssets(
context,
selection.value.where((a) => a.storage == AssetState.local),
);
ref.read(manualUploadProvider.notifier).uploadAssets(
context,
selection.value.where((a) => a.storage == AssetState.local),
);
} finally {
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 {
final fullRefresh = refreshCount.value > 0;
await ref.read(assetProvider.notifier).getAllAsset(clear: fullRefresh);
@ -439,8 +411,6 @@ class HomePage extends HookConsumerWidget {
enabled: !processing.value,
selectionAssetState: selectionAssetState.value,
onStack: onStack,
onEditTime: onEditTime,
onEditLocation: onEditLocation,
),
],
),

View file

@ -5,8 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_udid/flutter_udid.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/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/shared/models/user.dart';
@ -23,7 +21,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
AuthenticationNotifier(
this._apiService,
this._db,
this._ref,
) : super(
AuthenticationState(
deviceId: "",
@ -39,8 +36,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
final ApiService _apiService;
final Isar _db;
final StateNotifierProviderRef<AuthenticationNotifier, AuthenticationState>
_ref;
final _log = Logger("AuthenticationNotifier");
Future<bool> login(
@ -116,8 +111,6 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
Store.delete(StoreKey.currentUser),
Store.delete(StoreKey.accessToken),
]);
_ref.invalidate(albumProvider);
_ref.invalidate(sharedAlbumProvider);
state = state.copyWith(
deviceId: "",
@ -229,6 +222,5 @@ final authenticationProvider =
return AuthenticationNotifier(
ref.watch(apiServiceProvider),
ref.watch(dbProvider),
ref,
);
});

View file

@ -306,7 +306,8 @@ class LoginForm extends HookConsumerWidget {
children: [
Text(
sanitizeUrl(serverEndpointController.text),
style: context.textTheme.displaySmall,
style: context.textTheme.displaySmall
?.copyWith(color: context.primaryColor),
textAlign: TextAlign.center,
),
if (isPasswordLoginEnable.value) ...[
@ -616,7 +617,7 @@ class LoadingIcon extends StatelessWidget {
height: 24,
child: FittedBox(
child: CircularProgressIndicator(
strokeWidth: 2,
strokeWidth: 5,
),
),
),

View file

@ -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(),
),
],
),
],
),
),
),
),
);
}
}

View file

@ -1,9 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_map/plugin_api.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/utils/map_controller_hook.dart';
import 'package:latlong2/latlong.dart';
import 'package:url_launcher/url_launcher.dart';
@ -14,15 +12,13 @@ class MapThumbnail extends HookConsumerWidget {
final double zoom;
final List<Marker> markers;
final double height;
final double width;
final bool showAttribution;
final bool isDarkTheme;
const MapThumbnail({
super.key,
required this.coords,
this.height = 100,
this.width = 100,
required this.height,
this.onTap,
this.zoom = 1,
this.showAttribution = true,
@ -32,33 +28,18 @@ class MapThumbnail extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final mapController = useMapController();
final isMapReady = useRef(false);
ref.watch(mapStateNotifier.select((s) => s.mapStyle));
useEffect(
() {
if (isMapReady.value && mapController.center != coords) {
mapController.move(coords, zoom);
}
return null;
},
[coords],
);
return SizedBox(
height: height,
width: width,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(15)),
child: FlutterMap(
mapController: mapController,
options: MapOptions(
interactiveFlags: InteractiveFlag.none,
center: coords,
zoom: zoom,
onTap: onTap,
onMapReady: () => isMapReady.value = true,
),
nonRotatedChildren: [
if (showAttribution)

View file

@ -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';
}

View file

@ -55,7 +55,6 @@ class MapPageState extends ConsumerState<MapPage> {
// in onMapEvent() since MapEventMove#id is not populated properly in the
// current version of flutter_map(4.0.0) used
bool forceAssetUpdate = false;
bool isMapReady = false;
late final Debounce debounce;
@override
@ -80,7 +79,7 @@ class MapPageState extends ConsumerState<MapPage> {
bool forceReload = false,
}) {
try {
final bounds = isMapReady ? mapController.bounds : null;
final bounds = mapController.bounds;
if (bounds != null) {
final oldAssetsInBounds = assetsInBounds.toSet();
assetsInBounds =
@ -126,8 +125,11 @@ class MapPageState extends ConsumerState<MapPage> {
final refetchMarkers = useState(true);
final isLoading =
ref.watch(mapStateNotifier.select((state) => state.isLoading));
final mapStyle =
ref.watch(mapStateNotifier.select((state) => state.mapStyle));
final maxZoom = ref.read(mapStateNotifier.notifier).maxZoom;
final zoomLevel = math.min(maxZoom, 14.0);
final themeData = isDarkTheme ? immichDarkTheme : immichLightTheme;
if (refetchMarkers.value) {
mapMarkerData.value = ref.watch(mapMarkersProvider).when(
@ -196,7 +198,7 @@ class MapPageState extends ConsumerState<MapPage> {
showDialog(
context: context,
builder: (context) => Theme(
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
data: themeData,
child: LocationServiceDisabledDialog(),
),
);
@ -210,7 +212,7 @@ class MapPageState extends ConsumerState<MapPage> {
shouldRequestPermission = await showDialog(
context: context,
builder: (context) => Theme(
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
data: themeData,
child: LocationPermissionDisabledDialog(),
),
);
@ -427,7 +429,7 @@ class MapPageState extends ConsumerState<MapPage> {
),
child: Theme(
// Override app theme based on map theme
data: isDarkTheme ? immichDarkTheme : immichLightTheme,
data: themeData,
child: Scaffold(
appBar: MapAppBar(
isDarkTheme: isDarkTheme,
@ -440,7 +442,7 @@ class MapPageState extends ConsumerState<MapPage> {
extendBodyBehindAppBar: true,
body: Stack(
children: [
if (!isLoading)
if (!isLoading || mapStyle != null)
FlutterMap(
mapController: mapController,
options: MapOptions(
@ -456,7 +458,6 @@ class MapPageState extends ConsumerState<MapPage> {
minZoom: 1,
maxZoom: maxZoom,
onMapReady: () {
isMapReady = true;
mapController.mapEventStream.listen(onMapEvent);
},
),
@ -466,7 +467,7 @@ class MapPageState extends ConsumerState<MapPage> {
markerLayer,
],
),
if (!isLoading)
if (!isLoading || mapStyle != null)
MapPageBottomSheet(
mapPageEventStream: mapPageEventSC.stream,
bottomSheetEventSC: bottomSheetEventSC,
@ -475,10 +476,13 @@ class MapPageState extends ConsumerState<MapPage> {
isDarkTheme: isDarkTheme,
),
if (showLoadingIndicator.value || isLoading)
Positioned(
top: context.height * 0.35,
left: context.width * 0.425,
child: const ImmichLoadingIndicator(),
IgnorePointer(
child: Container(
width: double.infinity,
height: double.infinity,
color: context.colorScheme.surface.withAlpha(70),
child: const Center(child: ImmichLoadingIndicator()),
),
),
],
),

View file

@ -31,8 +31,8 @@ class CuratedPeopleRow extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: SizedBox(
width: imageSize,
height: imageSize,
width: 120,
height: 120,
child: ThumbnailWithInfo(
textInfo: '',
onTap: () {},
@ -69,6 +69,7 @@ class CuratedPeopleRow extends StatelessWidget {
elevation: 3,
child: CircleAvatar(
maxRadius: imageSize / 2,
backgroundColor: context.colorScheme.surfaceVariant,
backgroundImage: NetworkImage(
getFaceThumbnailUrl(person.id),
headers: headers,

View file

@ -29,8 +29,9 @@ class CuratedPlacesRow extends CuratedRow {
onTap: () => context.autoPush(
const MapRoute(),
),
child: SizedBox.square(
dimension: imageSize,
child: SizedBox(
height: imageSize,
width: imageSize,
child: Stack(
children: [
Padding(
@ -42,7 +43,6 @@ class CuratedPlacesRow extends CuratedRow {
5,
),
height: imageSize,
width: imageSize,
showAttribution: false,
isDarkTheme: context.isDarkTheme,
),
@ -52,13 +52,13 @@ class CuratedPlacesRow extends CuratedRow {
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: Colors.black,
color: context.colorScheme.shadow,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.blueGrey.withOpacity(0.0),
Colors.black.withOpacity(0.4),
context.colorScheme.shadow.withOpacity(0.1),
context.colorScheme.shadow.withOpacity(0.2),
],
stops: const [0.0, 0.4],
),

View file

@ -23,8 +23,6 @@ class ThumbnailWithInfo extends StatelessWidget {
@override
Widget build(BuildContext context) {
var textAndIconColor =
context.isDarkTheme ? Colors.grey[100] : Colors.grey[700];
return GestureDetector(
onTap: () {
onTap();
@ -35,7 +33,7 @@ class ThumbnailWithInfo extends StatelessWidget {
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[100],
color: context.colorScheme.surfaceVariant,
),
child: imageUrl != null
? ClipRRect(
@ -44,6 +42,16 @@ class ThumbnailWithInfo extends StatelessWidget {
width: double.infinity,
height: double.infinity,
fit: BoxFit.cover,
placeholder: (context, url) {
return SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
),
);
},
imageUrl: imageUrl!,
httpHeaders: {
"Authorization":
@ -56,22 +64,22 @@ class ThumbnailWithInfo extends StatelessWidget {
: Center(
child: Icon(
noImageIcon ?? Icons.not_listed_location,
color: textAndIconColor,
color: context.primaryColor,
),
),
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
color: Colors.white,
color: context.colorScheme.inverseSurface,
gradient: LinearGradient(
begin: FractionalOffset.topCenter,
end: FractionalOffset.bottomCenter,
colors: [
Colors.grey.withOpacity(0.0),
context.colorScheme.shadow.withOpacity(0),
textInfo == ''
? Colors.black.withOpacity(0.1)
: Colors.black.withOpacity(0.5),
? context.colorScheme.shadow.withOpacity(0.1)
: context.colorScheme.shadow.withOpacity(0.2),
],
stops: const [0.0, 1.0],
),

View file

@ -81,7 +81,6 @@ class AdvancedSettings extends HookConsumerWidget {
min: 1.0,
divisions: 7,
label: logLevel,
activeColor: context.primaryColor,
),
),
SettingsSwitchListTile(

View file

@ -51,7 +51,6 @@ class LayoutSettings extends HookConsumerWidget {
return Column(
children: [
SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_dynamic_layout_title",
style: context.textTheme.labelLarge,
@ -73,7 +72,6 @@ class LayoutSettings extends HookConsumerWidget {
).tr(),
),
RadioListTile(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_group_by_month_day",
style: context.textTheme.labelLarge,
@ -84,7 +82,6 @@ class LayoutSettings extends HookConsumerWidget {
controlAffinity: ListTileControlAffinity.trailing,
),
RadioListTile(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_group_by_month",
style: context.textTheme.labelLarge,
@ -95,7 +92,6 @@ class LayoutSettings extends HookConsumerWidget {
controlAffinity: ListTileControlAffinity.trailing,
),
RadioListTile(
activeColor: context.primaryColor,
title: Text(
"asset_list_layout_settings_group_automatically",
style: context.textTheme.labelLarge,

View file

@ -34,7 +34,6 @@ class StorageIndicator extends HookConsumerWidget {
);
return SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
"theme_setting_asset_list_storage_indicator_title",
style: context.textTheme.labelLarge,

View file

@ -49,7 +49,6 @@ class TilesPerRow extends HookConsumerWidget {
max: 6,
divisions: 4,
label: "${itemsValue.value.toInt()}",
activeColor: context.primaryColor,
),
],
);

View file

@ -143,7 +143,6 @@ class NotificationSetting extends HookConsumerWidget {
max: 5.0,
divisions: 5,
label: formattedValue,
activeColor: context.primaryColor,
),
),
],

View file

@ -35,17 +35,21 @@ class SettingsSwitchListTile extends StatelessWidget {
onChanged!(value);
}
},
activeColor:
enabled ? context.primaryColor : context.themeData.disabledColor,
activeColor: enabled ? null : context.themeData.disabledColor,
dense: true,
title: Text(
title,
style: context.textTheme.titleSmall,
style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
color: enabled ? null : context.themeData.disabledColor,
),
),
subtitle: subtitle != null
? Text(
subtitle!,
style: context.textTheme.bodyMedium,
style: TextStyle(
color: enabled ? null : context.themeData.disabledColor,
),
)
: null,
);

View file

@ -5,7 +5,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
import 'package:immich_mobile/shared/providers/theme.provider.dart';
class ThemeSetting extends HookConsumerWidget {
const ThemeSetting({
@ -35,7 +35,6 @@ class ThemeSetting extends HookConsumerWidget {
).tr(),
children: [
SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
'theme_setting_system_theme_switch',
style: context.textTheme.labelLarge
@ -48,20 +47,26 @@ class ThemeSetting extends HookConsumerWidget {
if (isSystem) {
currentTheme.value = ThemeMode.system;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.system;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.system);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "system");
} else {
if (currentSystemBrightness == Brightness.light) {
currentTheme.value = ThemeMode.light;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.light;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.light);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "light");
} else if (currentSystemBrightness == Brightness.dark) {
currentTheme.value = ThemeMode.dark;
ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.dark);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "dark");
@ -71,7 +76,6 @@ class ThemeSetting extends HookConsumerWidget {
),
if (currentTheme.value != ThemeMode.system)
SwitchListTile.adaptive(
activeColor: context.primaryColor,
title: Text(
'theme_setting_dark_mode_switch',
style: context.textTheme.labelLarge
@ -80,12 +84,16 @@ class ThemeSetting extends HookConsumerWidget {
value: ref.watch(immichThemeProvider) == ThemeMode.dark,
onChanged: (bool isDark) {
if (isDark) {
ref.watch(immichThemeProvider.notifier).state = ThemeMode.dark;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.dark);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "dark");
} else {
ref.watch(immichThemeProvider.notifier).state = ThemeMode.light;
ref
.watch(immichThemeProvider.notifier)
.updateTheme(ThemeMode.light);
ref
.watch(appSettingsServiceProvider)
.setSetting(AppSettingsEnum.themeMode, "light");

View file

@ -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,
);
}
}

View file

@ -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/create_album_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/memories/models/memory.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/settings/views/settings_page.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/backup_permission_guard.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/tab_controller_page.dart';
import 'package:isar/isar.dart';
import 'package:photo_manager/photo_manager.dart' hide LatLng;
import 'package:latlong2/latlong.dart';
import 'package:photo_manager/photo_manager.dart';
part 'router.gr.dart';
@ -89,10 +86,9 @@ part 'router.gr.dart';
],
transitionsBuilder: TransitionsBuilders.fadeIn,
),
CustomRoute(
AutoRoute(
page: GalleryViewerPage,
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
),
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(
@ -174,10 +170,6 @@ part 'router.gr.dart';
transitionsBuilder: TransitionsBuilders.slideLeft,
durationInMilliseconds: 200,
),
CustomRoute<LatLng?>(
page: MapLocationPickerPage,
guards: [AuthGuard, DuplicateGuard],
),
],
)
class AppRouter extends _$AppRouter {

View file

@ -63,7 +63,7 @@ class _$AppRouter extends RootStackRouter {
},
GalleryViewerRoute.name: (routeData) {
final args = routeData.argsAs<GalleryViewerRouteArgs>();
return CustomPage<dynamic>(
return MaterialPageX<dynamic>(
routeData: routeData,
child: GalleryViewerPage(
key: args.key,
@ -75,9 +75,6 @@ class _$AppRouter extends RootStackRouter {
isOwner: args.isOwner,
sharedAlbumId: args.sharedAlbumId,
),
transitionsBuilder: CustomTransitionsBuilders.zoomedPage,
opaque: true,
barrierDismissible: false,
);
},
VideoViewerRoute.name: (routeData) {
@ -360,19 +357,6 @@ class _$AppRouter extends RootStackRouter {
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) {
return MaterialPageX<dynamic>(
routeData: routeData,
@ -717,14 +701,6 @@ class _$AppRouter extends RootStackRouter {
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
/// [HomePage]
class HomeRoute extends PageRouteInfo<void> {

View file

@ -21,14 +21,6 @@ class TabNavigationObserver extends AutoRouterObserver {
required this.ref,
});
@override
void didInitTabRoute(TabPageRoute route, TabPageRoute? previousRoute) {
// Perform tasks on first navigation to SearchRoute
if (route.name == 'SearchRoute') {
// ref.refresh(getCuratedLocationProvider);
}
}
@override
Future<void> didChangeTabRoute(
TabPageRoute route,

View file

@ -256,8 +256,6 @@ class Asset {
isFavorite != a.isFavorite ||
isArchived != a.isArchived ||
isTrashed != a.isTrashed ||
a.exifInfo?.latitude != exifInfo?.latitude ||
a.exifInfo?.longitude != exifInfo?.longitude ||
// no local stack count or different count from remote
((stackCount == null && a.stackCount != null) ||
(stackCount != null &&

View file

@ -0,0 +1,14 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
class ImmichLoadingOverlayNotifier extends StateNotifier<bool> {
ImmichLoadingOverlayNotifier() : super(false);
void show() => state = true;
void hide() => state = false;
}
final immichLoadingOverlayController =
StateNotifierProvider.autoDispose<ImmichLoadingOverlayNotifier, bool>(
(_) => ImmichLoadingOverlayNotifier(),
);

View file

@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'theme.provider.g.dart';
@Riverpod(keepAlive: true)
class ImmichTheme extends _$ImmichTheme {
@override
ThemeMode build() {
final themeMode = ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.themeMode);
switch (themeMode) {
case "light":
return ThemeMode.light;
case "dark":
return ThemeMode.dark;
default:
return ThemeMode.system;
}
}
void updateTheme(ThemeMode newMode) => state = newMode;
}

View file

@ -0,0 +1,24 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'theme.provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$immichThemeHash() => r'22952207d5d5e6289a8244589e703c816501d6e3';
/// See also [ImmichTheme].
@ProviderFor(ImmichTheme)
final immichThemeProvider = NotifierProvider<ImmichTheme, ThemeMode>.internal(
ImmichTheme.new,
name: r'immichThemeProvider',
debugGetCreateSourceHash:
const bool.fromEnvironment('dart.vm.product') ? null : _$immichThemeHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ImmichTheme = Notifier<ThemeMode>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member

View file

@ -1,13 +1,10 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.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/server_info/server_version.model.dart';
import 'package:immich_mobile/shared/models/store.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/services/sync.service.dart';
import 'package:immich_mobile/utils/debounce.dart';
@ -17,33 +14,13 @@ import 'package:socket_io_client/socket_io_client.dart';
enum PendingAction {
assetDelete,
assetUploaded,
assetHidden,
}
class PendingChange {
final String id;
final PendingAction action;
final dynamic value;
const PendingChange(
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;
const PendingChange(this.action, this.value);
}
class WebsocketState {
@ -154,7 +131,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
socket.on('on_asset_trash', _handleServerUpdates);
socket.on('on_asset_restore', _handleServerUpdates);
socket.on('on_asset_update', _handleServerUpdates);
socket.on('on_asset_hidden', _handleOnAssetHidden);
socket.on('on_new_release', _handleReleaseUpdates);
} catch (e) {
debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
@ -187,78 +163,35 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
}
void addPendingChange(PendingAction action, dynamic value) {
final now = DateTime.now();
state = state.copyWith(
pendingChanges: [
...state.pendingChanges,
PendingChange(now.millisecondsSinceEpoch.toString(), action, value),
],
pendingChanges: [...state.pendingChanges, PendingChange(action, value)],
);
_debounce(handlePendingChanges);
}
Future<void> _handlePendingDeletes() async {
void handlePendingChanges() {
final deleteChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetDelete)
.toList();
if (deleteChanges.isNotEmpty) {
List<String> remoteIds =
deleteChanges.map((a) => a.value.toString()).toList();
await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
_ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
state = state.copyWith(
pendingChanges: state.pendingChanges
.whereNot((c) => deleteChanges.contains(c))
.where((c) => c.action != PendingAction.assetDelete)
.toList(),
);
}
}
Future<void> _handlePendingUploaded() async {
final uploadedChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetUploaded)
.toList();
if (uploadedChanges.isNotEmpty) {
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(),
);
void _handleOnUploadSuccess(dynamic data) {
final dto = AssetResponseDto.fromJson(data);
if (dto != null) {
final newAsset = Asset.remote(dto);
_ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
}
}
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 _) {
_ref.read(serverInfoProvider.notifier).getServerFeatures();
_ref.read(serverInfoProvider.notifier).getServerConfig();
@ -269,14 +202,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
_ref.read(assetProvider.notifier).getAllAsset();
}
void _handleOnUploadSuccess(dynamic data) =>
addPendingChange(PendingAction.assetUploaded, data);
void _handleOnAssetDelete(dynamic data) =>
addPendingChange(PendingAction.assetDelete, data);
void _handleOnAssetHidden(dynamic data) =>
addPendingChange(PendingAction.assetHidden, data);
void _handleOnAssetDelete(dynamic data) {
addPendingChange(PendingAction.assetDelete, data);
_debounce(handlePendingChanges);
}
_handleReleaseUpdates(dynamic data) {
// Json guard

View file

@ -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/sync.service.dart';
import 'package:isar/isar.dart';
import 'package:latlong2/latlong.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
@ -182,27 +181,4 @@ class AssetService {
Future<List<Asset?>> changeArchiveStatus(List<Asset> assets, bool 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,
),
);
}
}

View file

@ -401,10 +401,6 @@ class SyncService {
final Album a = await Album.remote(dto);
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}");
}
}

View file

@ -135,11 +135,7 @@ class ImmichAppBarDialog extends HookConsumerWidget {
padding: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 3),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
color: context.isDarkTheme
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
),
decoration: BoxDecoration(color: context.colorScheme.surface),
child: ListTile(
minLeadingWidth: 50,
leading: Icon(
@ -163,7 +159,8 @@ class ImmichAppBarDialog extends HookConsumerWidget {
child: LinearProgressIndicator(
minHeight: 5.0,
value: backupState.serverInfo.diskUsagePercentage / 100.0,
backgroundColor: Colors.grey,
backgroundColor:
context.colorScheme.onSurface.withAlpha(50),
color: theme.primaryColor,
),
),

View file

@ -77,9 +77,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: context.isDarkTheme
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
color: context.colorScheme.surface,
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(10),
@ -97,9 +95,7 @@ class AppBarProfileInfoBox extends HookConsumerWidget {
bottom: -5,
right: -8,
child: Material(
color: context.isDarkTheme
? Colors.blueGrey[800]
: Colors.white,
color: context.colorScheme.primaryContainer,
elevation: 3,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),

View file

@ -42,9 +42,7 @@ class AppBarServerInfo extends HookConsumerWidget {
padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10.0),
child: Container(
decoration: BoxDecoration(
color: context.isDarkTheme
? context.scaffoldBackgroundColor
: const Color.fromARGB(255, 225, 229, 240),
color: context.colorScheme.surface,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(10),
bottomRight: Radius.circular(10),
@ -186,8 +184,7 @@ class AppBarServerInfo extends HookConsumerWidget {
borderRadius: BorderRadius.circular(10),
),
textStyle: TextStyle(
color:
context.isDarkTheme ? Colors.black : Colors.white,
color: context.colorScheme.onPrimary,
fontWeight: FontWeight.bold,
),
message: getServerUrl() ?? '--',

View file

@ -44,7 +44,7 @@ class ConfirmDialog extends ConsumerWidget {
child: Text(
ok,
style: TextStyle(
color: Colors.red[400],
color: context.colorScheme.error,
fontWeight: FontWeight.bold,
),
).tr(),

View file

@ -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;
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class CustomDraggingHandle extends StatelessWidget {
const CustomDraggingHandle({super.key});
@ -9,7 +10,7 @@ class CustomDraggingHandle extends StatelessWidget {
height: 5,
width: 30,
decoration: BoxDecoration(
color: Colors.grey[500],
color: context.themeData.dividerColor,
borderRadius: BorderRadius.circular(16),
),
);

View file

@ -25,7 +25,6 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
backupState.backgroundBackup || backupState.autoBackup;
final ServerInfo serverInfoState = ref.watch(serverInfoProvider);
final user = Store.tryGet(StoreKey.currentUser);
final isDarkTheme = context.isDarkTheme;
const widgetSize = 30.0;
buildProfileIndicator() {
@ -42,9 +41,9 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
color: Colors.black,
borderRadius: BorderRadius.circular(widgetSize / 2),
),
child: const Icon(
child: Icon(
Icons.info,
color: Color.fromARGB(255, 243, 188, 106),
color: context.orangeColor,
size: widgetSize / 2,
),
),
@ -68,9 +67,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
);
}
getBackupBadgeIcon() {
final iconColor = isDarkTheme ? Colors.white : Colors.black;
Widget? getBackupBadgeIcon() {
if (isEnableAutoBackup) {
if (backupState.backupProgress == BackUpProgressEnum.inProgress) {
return Container(
@ -78,7 +75,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
child: CircularProgressIndicator(
strokeWidth: 2,
strokeCap: StrokeCap.round,
valueColor: AlwaysStoppedAnimation<Color>(iconColor),
color: context.colorScheme.onSurfaceVariant,
),
);
} else if (backupState.backupProgress !=
@ -87,7 +84,7 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
return Icon(
Icons.check_outlined,
size: 9,
color: iconColor,
color: context.colorScheme.onSurfaceVariant,
);
}
}
@ -96,14 +93,14 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
return Icon(
Icons.cloud_off_rounded,
size: 9,
color: iconColor,
color: context.colorScheme.onSurfaceVariant,
);
}
return null;
}
buildBackupIndicator() {
final indicatorIcon = getBackupBadgeIcon();
final badgeBackground = isDarkTheme ? Colors.blueGrey[800] : Colors.white;
return InkWell(
onTap: () => context.autoPush(const BackupControllerRoute()),
@ -113,11 +110,11 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
width: widgetSize / 2,
height: widgetSize / 2,
decoration: BoxDecoration(
color: badgeBackground,
border: Border.all(
color: isDarkTheme ? Colors.black : Colors.grey,
color: context.colorScheme.surface,
),
borderRadius: BorderRadius.circular(widgetSize / 2),
color: context.colorScheme.surfaceVariant,
),
child: indicatorIcon,
),
@ -125,16 +122,16 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
alignment: Alignment.bottomRight,
isLabelVisible: indicatorIcon != null,
offset: const Offset(2, 2),
child: Icon(
child: const Icon(
Icons.backup_rounded,
size: widgetSize,
color: context.primaryColor,
),
),
);
}
return AppBar(
elevation: 0.0,
backgroundColor: context.themeData.appBarTheme.backgroundColor,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(
@ -157,12 +154,13 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
),
Container(
margin: const EdgeInsets.only(left: 10),
child: const Text(
child: Text(
'IMMICH',
style: TextStyle(
fontFamily: 'SnowburstOne',
fontWeight: FontWeight.bold,
fontSize: 24,
color: context.primaryColor,
),
),
),

View file

@ -33,8 +33,8 @@ class ImmichImage extends StatelessWidget {
Widget build(BuildContext context) {
if (this.asset == null) {
return Container(
decoration: const BoxDecoration(
color: Colors.grey,
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
child: SizedBox(
width: width,
@ -61,10 +61,12 @@ class ImmichImage extends StatelessWidget {
return Stack(
children: [
if (useGrayBoxPlaceholder)
const SizedBox.square(
SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
),
),
if (useProgressIndicator)
@ -110,10 +112,12 @@ class ImmichImage extends StatelessWidget {
return Stack(
children: [
if (useGrayBoxPlaceholder)
const SizedBox.square(
SizedBox.square(
dimension: 250,
child: DecoratedBox(
decoration: BoxDecoration(color: Colors.grey),
decoration: BoxDecoration(
color: context.colorScheme.surfaceVariant,
),
),
),
if (useProgressIndicator)

View file

@ -15,12 +15,12 @@ class ImmichLoadingIndicator extends StatelessWidget {
height: 60,
width: 60,
decoration: BoxDecoration(
color: context.primaryColor.withAlpha(200),
color: context.primaryColor,
borderRadius: BorderRadius.circular(borderRadius ?? 10),
),
padding: const EdgeInsets.all(15),
child: const CircularProgressIndicator(
color: Colors.white,
child: CircularProgressIndicator(
color: context.colorScheme.onPrimary,
strokeWidth: 3,
),
);

View file

@ -9,7 +9,7 @@ class ImmichToast {
required BuildContext context,
required String msg,
ToastType toastType = ToastType.info,
ToastGravity gravity = ToastGravity.TOP,
ToastGravity gravity = ToastGravity.BOTTOM,
int durationInSecond = 3,
}) {
final fToast = FToast();
@ -18,7 +18,7 @@ class ImmichToast {
Color getColor(ToastType type, BuildContext context) {
switch (type) {
case ToastType.info:
return context.primaryColor;
return const Color.fromARGB(255, 48, 111, 220);
case ToastType.success:
return const Color.fromARGB(255, 78, 140, 124);
case ToastType.error:
@ -26,41 +26,39 @@ class ImmichToast {
}
}
Icon getIcon(ToastType type) {
IconData getIcon(ToastType type) {
switch (type) {
case ToastType.info:
return Icon(
Icons.info_outline_rounded,
color: context.primaryColor,
);
return Icons.info_outline_rounded;
case ToastType.success:
return const Icon(
Icons.check_circle_rounded,
color: Color.fromARGB(255, 78, 140, 124),
);
return Icons.check_circle_outline_rounded;
case ToastType.error:
return const Icon(
Icons.error_outline_rounded,
color: Color.fromARGB(255, 240, 162, 156),
);
return Icons.report_problem_outlined;
}
}
fToast.showToast(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
padding: const EdgeInsets.symmetric(vertical: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: context.isDarkTheme ? Colors.grey[900] : Colors.grey[50],
color: context.colorScheme.inverseSurface,
border: Border.all(
color: Colors.black12,
color: context.colorScheme.inverseSurface,
width: 1,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
children: [
getIcon(toastType),
Padding(
padding: const EdgeInsets.only(left: 15, right: 5),
child: Icon(
getIcon(toastType),
color: getColor(toastType, context),
),
),
const SizedBox(
width: 12.0,
),
@ -68,8 +66,7 @@ class ImmichToast {
child: Text(
msg,
style: TextStyle(
color: getColor(toastType, context),
fontWeight: FontWeight.bold,
color: context.colorScheme.onInverseSurface,
fontSize: 15,
),
),

View file

@ -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(),
),
],
);
}
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
class PhotoViewDefaultError extends StatelessWidget {
const PhotoViewDefaultError({Key? key, required this.decoration})
@ -13,7 +14,7 @@ class PhotoViewDefaultError extends StatelessWidget {
child: Center(
child: Icon(
Icons.broken_image,
color: Colors.grey[400],
color: context.primaryColor,
size: 40.0,
),
),

View file

@ -15,7 +15,7 @@ class ScaffoldErrorBody extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"scaffold_body_error_occurred",
"scaffold_body_error_occured",
style: context.textTheme.displayMedium,
textAlign: TextAlign.center,
).tr(),

View file

@ -3,6 +3,7 @@ import 'dart:math';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
@ -22,7 +23,6 @@ class UserCircleAvatar extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
bool isDarkTheme = Theme.of(context).brightness == Brightness.dark;
final profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}';
@ -30,10 +30,8 @@ class UserCircleAvatar extends ConsumerWidget {
user.name[0].toUpperCase(),
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: isDarkTheme && user.avatarColor == AvatarColorEnum.primary
? Colors.black
: Colors.white,
fontSize: 16,
color: context.colorScheme.surface,
),
);
return CircleAvatar(

View file

@ -11,8 +11,6 @@ class AppLogDetailPage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
var isDarkTheme = context.isDarkTheme;
buildStackMessage(String stackTrace) {
return Padding(
padding: const EdgeInsets.all(8.0),
@ -60,7 +58,7 @@ class AppLogDetailPage extends HookConsumerWidget {
),
Container(
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
color: context.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
@ -126,7 +124,7 @@ class AppLogDetailPage extends HookConsumerWidget {
),
Container(
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
color: context.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(
@ -165,7 +163,7 @@ class AppLogDetailPage extends HookConsumerWidget {
),
Container(
decoration: BoxDecoration(
color: isDarkTheme ? Colors.grey[900] : Colors.grey[200],
color: context.colorScheme.surfaceVariant,
borderRadius: BorderRadius.circular(15.0),
),
child: Padding(

View file

@ -16,7 +16,6 @@ class AppLogPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final immichLogger = ImmichLogger();
final logMessages = useState(immichLogger.messages);
final isDarkTheme = context.isDarkTheme;
Widget colorStatusIndicator(Color color) {
return Column(
@ -39,12 +38,13 @@ class AppLogPage extends HookConsumerWidget {
case LogLevel.INFO:
return colorStatusIndicator(context.primaryColor);
case LogLevel.SEVERE:
return colorStatusIndicator(Colors.redAccent);
return colorStatusIndicator(context.redColor);
case LogLevel.WARNING:
return colorStatusIndicator(Colors.orangeAccent);
return colorStatusIndicator(context.orangeColor);
default:
return colorStatusIndicator(Colors.grey);
return colorStatusIndicator(
context.colorScheme.onSurfaceVariant.withAlpha(150),
);
}
}
@ -53,15 +53,11 @@ class AppLogPage extends HookConsumerWidget {
case LogLevel.INFO:
return Colors.transparent;
case LogLevel.SEVERE:
return isDarkTheme
? Colors.redAccent.withOpacity(0.25)
: Colors.redAccent.withOpacity(0.075);
return context.redColor.withAlpha(50);
case LogLevel.WARNING:
return isDarkTheme
? Colors.orangeAccent.withOpacity(0.25)
: Colors.orangeAccent.withOpacity(0.075);
return context.orangeColor.withAlpha(50);
default:
return context.primaryColor.withOpacity(0.1);
return context.primaryColor.withAlpha(50);
}
}
@ -116,7 +112,7 @@ class AppLogPage extends HookConsumerWidget {
separatorBuilder: (context, index) {
return Divider(
height: 0,
color: isDarkTheme ? Colors.white70 : Colors.grey[600],
color: context.themeData.dividerColor,
);
},
itemCount: logMessages.value.length,
@ -139,7 +135,7 @@ class AppLogPage extends HookConsumerWidget {
TextSpan(
text: "#$index ",
style: TextStyle(
color: isDarkTheme ? Colors.white70 : Colors.grey[600],
color: context.themeData.dividerColor,
fontSize: 14.0,
fontWeight: FontWeight.bold,
),
@ -158,7 +154,7 @@ class AppLogPage extends HookConsumerWidget {
"[${logMessage.context1}] Logged on ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)}",
style: TextStyle(
fontSize: 12.0,
color: Colors.grey[600],
color: context.themeData.hintColor,
),
),
leading: buildLeadingIcon(logMessage.level),

View file

@ -7,8 +7,9 @@ final _loadingEntry = OverlayEntry(
builder: (context) => SizedBox.square(
dimension: double.infinity,
child: DecoratedBox(
decoration:
BoxDecoration(color: context.colorScheme.surface.withAlpha(200)),
decoration: BoxDecoration(
color: context.colorScheme.surface.withAlpha(70),
),
child: const Center(child: ImmichLoadingIndicator()),
),
),

View file

@ -92,8 +92,11 @@ class SplashScreenPage extends HookConsumerWidget {
[],
);
return const Scaffold(
body: Center(
return Scaffold(
appBar: AppBar(
elevation: 0.0,
),
body: const Center(
child: Image(
image: AssetImage('assets/immich-logo-no-outline.png'),
width: 80,

View file

@ -25,7 +25,7 @@ class TabControllerPage extends HookConsumerWidget {
children: [
icon,
Positioned(
right: -14,
right: -16,
child: SizedBox(
height: 12,
width: 12,
@ -115,9 +115,8 @@ class TabControllerPage extends HookConsumerWidget {
Icons.photo_library_outlined,
),
selectedIcon: buildIcon(
Icon(
const Icon(
Icons.photo_library,
color: context.primaryColor,
),
),
),
@ -126,9 +125,8 @@ class TabControllerPage extends HookConsumerWidget {
icon: const Icon(
Icons.search_rounded,
),
selectedIcon: Icon(
selectedIcon: const Icon(
Icons.search,
color: context.primaryColor,
),
),
NavigationDestination(
@ -136,9 +134,8 @@ class TabControllerPage extends HookConsumerWidget {
icon: const Icon(
Icons.group_outlined,
),
selectedIcon: Icon(
selectedIcon: const Icon(
Icons.group,
color: context.primaryColor,
),
),
NavigationDestination(
@ -147,9 +144,8 @@ class TabControllerPage extends HookConsumerWidget {
Icons.photo_album_outlined,
),
selectedIcon: buildIcon(
Icon(
const Icon(
Icons.photo_album_rounded,
color: context.primaryColor,
),
),
),

View file

@ -1,278 +1,177 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
final immichThemeProvider = StateProvider<ThemeMode>((ref) {
var themeMode = ref
.watch(appSettingsServiceProvider)
.getSetting(AppSettingsEnum.themeMode);
debugPrint("Current themeMode $themeMode");
if (themeMode == "light") {
return ThemeMode.light;
} else if (themeMode == "dark") {
return ThemeMode.dark;
} else {
return ThemeMode.system;
}
});
final ThemeData base = ThemeData(
chipTheme: const ChipThemeData(
side: BorderSide.none,
),
sliderTheme: const SliderThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
),
);
final ThemeData immichLightTheme = ThemeData(
useMaterial3: true,
ColorScheme _lightColorScheme = const ColorScheme(
brightness: Brightness.light,
primarySwatch: Colors.indigo,
primaryColor: Colors.indigo,
hintColor: Colors.indigo,
focusColor: Colors.indigo,
splashColor: Colors.indigo.withOpacity(0.15),
fontFamily: 'Overpass',
scaffoldBackgroundColor: immichBackgroundColor,
snackBarTheme: const SnackBarThemeData(
contentTextStyle: TextStyle(
fontFamily: 'Overpass',
color: Colors.indigo,
fontWeight: FontWeight.bold,
),
backgroundColor: Colors.white,
),
appBarTheme: const AppBarTheme(
titleTextStyle: TextStyle(
fontFamily: 'Overpass',
color: Colors.indigo,
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor: immichBackgroundColor,
foregroundColor: Colors.indigo,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: immichBackgroundColor,
selectedItemColor: Colors.indigo,
),
cardTheme: const CardTheme(
surfaceTintColor: Colors.transparent,
),
drawerTheme: const DrawerThemeData(
backgroundColor: immichBackgroundColor,
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
displayMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.indigo,
),
titleSmall: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.indigo,
foregroundColor: Colors.white,
),
),
chipTheme: base.chipTheme,
sliderTheme: base.sliderTheme,
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
surfaceTintColor: Colors.transparent,
color: Colors.white,
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: Colors.indigo.withOpacity(0.15),
iconTheme: MaterialStatePropertyAll(
IconThemeData(color: Colors.grey[700]),
),
backgroundColor: immichBackgroundColor,
surfaceTintColor: Colors.transparent,
labelTextStyle: MaterialStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey[800],
),
),
),
dialogTheme: const DialogTheme(
surfaceTintColor: Colors.transparent,
),
inputDecorationTheme: const InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.indigo,
),
),
labelStyle: TextStyle(
color: Colors.indigo,
),
hintStyle: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: Colors.indigo,
),
primary: Color(0xff4755b5),
onPrimary: Color(0xffffffff),
primaryContainer: Color(0xffdfe0ff),
onPrimaryContainer: Color(0xff000d60),
secondary: Color(0xff5b5d72),
onSecondary: Color(0xffffffff),
secondaryContainer: Color(0xFFD6D8FF),
onSecondaryContainer: Color(0xff181a2c),
tertiary: Color(0xff77536c),
onTertiary: Color(0xffffffff),
tertiaryContainer: Color(0xffffd7f0),
onTertiaryContainer: Color(0xff2d1127),
error: Color(0xffba1a1a),
onError: Color(0xffffffff),
errorContainer: Color(0xffffdad6),
onErrorContainer: Color(0xff410002),
background: Color(0xfff9f6fc),
onBackground: Color(0xff1b1b1f),
surface: Color(0xfff9f6fc),
onSurface: Color(0xff1b1b1f),
surfaceVariant: Color(0xffdeddea),
onSurfaceVariant: Color(0xff46464f),
outline: Color(0xff777680),
outlineVariant: Color(0xffc7c5d0),
shadow: Color(0xff000000),
scrim: Color(0xff000000),
inverseSurface: Color(0xff303137),
onInverseSurface: Color(0xfff3f0f4),
inversePrimary: Color(0xffbcc3ff),
surfaceTint: Color(0xff4755b5),
);
final ThemeData immichDarkTheme = ThemeData(
useMaterial3: true,
ColorScheme _darkColorScheme = const ColorScheme(
brightness: Brightness.dark,
primarySwatch: Colors.indigo,
primaryColor: immichDarkThemePrimaryColor,
scaffoldBackgroundColor: immichDarkBackgroundColor,
hintColor: Colors.grey[600],
fontFamily: 'Overpass',
snackBarTheme: SnackBarThemeData(
contentTextStyle: const TextStyle(
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor,
fontWeight: FontWeight.bold,
),
backgroundColor: Colors.grey[900],
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: immichDarkThemePrimaryColor,
),
),
appBarTheme: const AppBarTheme(
titleTextStyle: TextStyle(
fontFamily: 'Overpass',
color: immichDarkThemePrimaryColor,
fontWeight: FontWeight.bold,
fontSize: 18,
),
backgroundColor: Color.fromARGB(255, 32, 33, 35),
foregroundColor: immichDarkThemePrimaryColor,
elevation: 0,
scrolledUnderElevation: 0,
centerTitle: true,
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: Color.fromARGB(255, 35, 36, 37),
selectedItemColor: immichDarkThemePrimaryColor,
),
drawerTheme: DrawerThemeData(
backgroundColor: immichDarkBackgroundColor,
scrimColor: Colors.white.withOpacity(0.1),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
displayMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color.fromARGB(255, 255, 255, 255),
),
displaySmall: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: immichDarkThemePrimaryColor,
),
titleSmall: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
),
),
cardColor: Colors.grey[900],
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
foregroundColor: Colors.black87,
backgroundColor: immichDarkThemePrimaryColor,
),
),
chipTheme: base.chipTheme,
sliderTheme: base.sliderTheme,
popupMenuTheme: const PopupMenuThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10)),
),
surfaceTintColor: Colors.transparent,
),
navigationBarTheme: NavigationBarThemeData(
indicatorColor: immichDarkThemePrimaryColor.withOpacity(0.4),
iconTheme: MaterialStatePropertyAll(
IconThemeData(color: Colors.grey[500]),
),
backgroundColor: Colors.grey[900],
surfaceTintColor: Colors.transparent,
labelTextStyle: MaterialStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: Colors.grey[300],
),
),
),
dialogTheme: const DialogTheme(
surfaceTintColor: Colors.transparent,
),
inputDecorationTheme: const InputDecorationTheme(
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: immichDarkThemePrimaryColor,
),
),
labelStyle: TextStyle(
color: immichDarkThemePrimaryColor,
),
hintStyle: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
textSelectionTheme: const TextSelectionThemeData(
cursorColor: immichDarkThemePrimaryColor,
),
primary: Color(0xffa4c8ff),
onPrimary: Color(0xff00315e),
primaryContainer: Color(0xFF182C40),
onPrimaryContainer: Color(0xffd4e3ff),
secondary: Color(0xffbcc7dc),
onSecondary: Color(0xff37474f),
secondaryContainer: Color(0xff3d4758),
onSecondaryContainer: Color(0xffd8e3f8),
tertiary: Color(0xffdabde2),
onTertiary: Color(0xff3d2946),
tertiaryContainer: Color(0xff543f5e),
onTertiaryContainer: Color(0xfff6d9ff),
error: Color(0xffffb4ab),
onError: Color(0xff690005),
errorContainer: Color(0xff93000a),
onErrorContainer: Color(0xffffb4ab),
background: Color(0xff101214),
onBackground: Color(0xffe2e2e5),
surface: Color(0xff101214),
onSurface: Color(0xffe2e2e5),
surfaceVariant: Color(0xff363c42),
onSurfaceVariant: Color(0xffc1c7ce),
outline: Color(0xff8b9198),
outlineVariant: Color(0xff41474d),
shadow: Color(0xff000000),
scrim: Color(0xff000000),
inverseSurface: Color(0xffeeeef1),
onInverseSurface: Color(0xff2e3133),
inversePrimary: Color(0xff1c5fa5),
surfaceTint: Color(0xff90caf9),
);
ThemeData getThemeForScheme(ColorScheme scheme) {
return ThemeData(
useMaterial3: true,
brightness: scheme.brightness,
colorScheme: scheme,
primaryColor: scheme.primary,
scaffoldBackgroundColor: scheme.background,
fontFamily: 'Overpass',
appBarTheme: AppBarTheme(
iconTheme: IconThemeData(color: scheme.primary),
titleTextStyle: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
centerTitle: true,
scrolledUnderElevation: 4.0,
elevation: 4.0,
),
snackBarTheme: const SnackBarThemeData(
contentTextStyle: TextStyle(
fontFamily: 'Overpass',
fontWeight: FontWeight.bold,
),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
),
cardTheme: const CardTheme(elevation: 2.0),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
visualDensity: VisualDensity.standard,
textStyle: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 11,
),
shadowColor: scheme.shadow,
foregroundColor: scheme.onPrimary,
backgroundColor: scheme.primary,
),
),
navigationBarTheme: NavigationBarThemeData(
iconTheme: MaterialStateProperty.resolveWith<IconThemeData?>((states) {
if (states.contains(MaterialState.selected)) {
return IconThemeData(color: scheme.primary);
}
return null;
}),
labelTextStyle: const MaterialStatePropertyAll(
TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
),
),
),
textTheme: TextTheme(
displayLarge: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
displayMedium: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
displaySmall: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
titleSmall: const TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.bold,
),
titleMedium: const TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
titleLarge: TextStyle(
fontSize: 26.0,
fontWeight: FontWeight.bold,
color: scheme.primary,
),
),
chipTheme: const ChipThemeData(
side: BorderSide.none,
),
sliderTheme: const SliderThemeData(
thumbShape: RoundSliderThumbShape(enabledThumbRadius: 7),
trackHeight: 2.0,
),
inputDecorationTheme: InputDecorationTheme(
labelStyle: TextStyle(
color: scheme.primary,
),
hintStyle: const TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.normal,
),
),
);
}
final ThemeData immichLightTheme = getThemeForScheme(_lightColorScheme);
final ThemeData immichDarkTheme = getThemeForScheme(_darkColorScheme);
const redAccent = Colors.redAccent;
const orangeAccent = Colors.orangeAccent;

View file

@ -2,17 +2,12 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.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/shared/models/asset.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/ui/date_time_picker.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:latlong2/latlong.dart';
void handleShareAssets(
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);
}

View file

@ -24,10 +24,6 @@ doc/AssetBulkUploadCheckDto.md
doc/AssetBulkUploadCheckItem.md
doc/AssetBulkUploadCheckResponseDto.md
doc/AssetBulkUploadCheckResult.md
doc/AssetFaceResponseDto.md
doc/AssetFaceUpdateDto.md
doc/AssetFaceUpdateItem.md
doc/AssetFaceWithoutPersonResponseDto.md
doc/AssetFileUploadResponseDto.md
doc/AssetIdsDto.md
doc/AssetIdsResponseDto.md
@ -64,8 +60,6 @@ doc/DownloadInfoDto.md
doc/DownloadResponseDto.md
doc/EntityType.md
doc/ExifResponseDto.md
doc/FaceApi.md
doc/FaceDto.md
doc/FileChecksumDto.md
doc/FileChecksumResponseDto.md
doc/FileReportDto.md
@ -106,7 +100,6 @@ doc/PersonApi.md
doc/PersonResponseDto.md
doc/PersonStatisticsResponseDto.md
doc/PersonUpdateDto.md
doc/PersonWithFacesResponseDto.md
doc/QueueStatusDto.md
doc/ReactionLevel.md
doc/ReactionType.md
@ -184,7 +177,6 @@ lib/api/api_key_api.dart
lib/api/asset_api.dart
lib/api/audit_api.dart
lib/api/authentication_api.dart
lib/api/face_api.dart
lib/api/job_api.dart
lib/api/library_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_response_dto.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_ids_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/entity_type.dart
lib/model/exif_response_dto.dart
lib/model/face_dto.dart
lib/model/file_checksum_dto.dart
lib/model/file_checksum_response_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_statistics_response_dto.dart
lib/model/person_update_dto.dart
lib/model/person_with_faces_response_dto.dart
lib/model/queue_status_dto.dart
lib/model/reaction_level.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_response_dto_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_ids_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/entity_type_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_response_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_statistics_response_dto_test.dart
test/person_update_dto_test.dart
test/person_with_faces_response_dto_test.dart
test/queue_status_dto_test.dart
test/reaction_level_test.dart
test/reaction_type_test.dart

View file

@ -133,8 +133,6 @@ Class | Method | HTTP request | Description
*AuthenticationApi* | [**logoutAuthDevices**](doc//AuthenticationApi.md#logoutauthdevices) | **DELETE** /auth/devices |
*AuthenticationApi* | [**signUpAdmin**](doc//AuthenticationApi.md#signupadmin) | **POST** /auth/admin-sign-up |
*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* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{id} |
*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* | [**removePartner**](doc//PartnerApi.md#removepartner) | **DELETE** /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* | [**getPerson**](doc//PersonApi.md#getperson) | **GET** /person/{id} |
*PersonApi* | [**getPersonAssets**](doc//PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
*PersonApi* | [**getPersonStatistics**](doc//PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
*PersonApi* | [**getPersonThumbnail**](doc//PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
*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* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} |
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
@ -228,10 +224,6 @@ Class | Method | HTTP request | Description
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
- [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.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)
- [AssetIdsDto](doc//AssetIdsDto.md)
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
@ -266,7 +258,6 @@ Class | Method | HTTP request | Description
- [DownloadResponseDto](doc//DownloadResponseDto.md)
- [EntityType](doc//EntityType.md)
- [ExifResponseDto](doc//ExifResponseDto.md)
- [FaceDto](doc//FaceDto.md)
- [FileChecksumDto](doc//FileChecksumDto.md)
- [FileChecksumResponseDto](doc//FileChecksumResponseDto.md)
- [FileReportDto](doc//FileReportDto.md)
@ -302,7 +293,6 @@ Class | Method | HTTP request | Description
- [PersonResponseDto](doc//PersonResponseDto.md)
- [PersonStatisticsResponseDto](doc//PersonStatisticsResponseDto.md)
- [PersonUpdateDto](doc//PersonUpdateDto.md)
- [PersonWithFacesResponseDto](doc//PersonWithFacesResponseDto.md)
- [QueueStatusDto](doc//QueueStatusDto.md)
- [ReactionLevel](doc//ReactionLevel.md)
- [ReactionType](doc//ReactionType.md)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -30,7 +30,7 @@ Name | Type | Description | Notes
**originalPath** | **String** | |
**owner** | [**UserResponseDto**](UserResponseDto.md) | | [optional]
**ownerId** | **String** | |
**people** | [**List<PersonWithFacesResponseDto>**](PersonWithFacesResponseDto.md) | | [optional] [default to const []]
**people** | [**List<PersonResponseDto>**](PersonResponseDto.md) | | [optional] [default to const []]
**resized** | **bool** | |
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
**stack** | [**List<AssetResponseDto>**](AssetResponseDto.md) | | [optional] [default to const []]

View file

@ -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)

View file

@ -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)

View file

@ -9,69 +9,16 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**createPerson**](PersonApi.md#createperson) | **POST** /person |
[**getAllPeople**](PersonApi.md#getallpeople) | **GET** /person |
[**getPerson**](PersonApi.md#getperson) | **GET** /person/{id} |
[**getPersonAssets**](PersonApi.md#getpersonassets) | **GET** /person/{id}/assets |
[**getPersonStatistics**](PersonApi.md#getpersonstatistics) | **GET** /person/{id}/statistics |
[**getPersonThumbnail**](PersonApi.md#getpersonthumbnail) | **GET** /person/{id}/thumbnail |
[**mergePerson**](PersonApi.md#mergeperson) | **POST** /person/{id}/merge |
[**reassignFaces**](PersonApi.md#reassignfaces) | **PUT** /person/{id}/reassign |
[**updatePeople**](PersonApi.md#updatepeople) | **PUT** /person |
[**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**
> 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)
# **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**
> List<BulkIdResponseDto> updatePeople(peopleUpdateDto)

View file

@ -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)

View file

@ -34,7 +34,6 @@ part 'api/album_api.dart';
part 'api/asset_api.dart';
part 'api/audit_api.dart';
part 'api/authentication_api.dart';
part 'api/face_api.dart';
part 'api/job_api.dart';
part 'api/library_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_response_dto.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_ids_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/entity_type.dart';
part 'model/exif_response_dto.dart';
part 'model/face_dto.dart';
part 'model/file_checksum_dto.dart';
part 'model/file_checksum_response_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_statistics_response_dto.dart';
part 'model/person_update_dto.dart';
part 'model/person_with_faces_response_dto.dart';
part 'model/queue_status_dto.dart';
part 'model/reaction_level.dart';
part 'model/reaction_type.dart';

Some files were not shown because too many files have changed in this diff Show more