mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3753a3ec41 | |||
| 49b30ea2f2 | |||
| 17af4324eb | |||
| af39384efb | |||
| 01712cf0a7 | |||
| 2015f95ff5 | |||
| d4f29ab6ac | |||
| 3decc864b5 | |||
| eca0e60db8 |
@@ -28,6 +28,10 @@ export const errorDto = {
|
|||||||
badRequest: (message: any = null) => ({
|
badRequest: (message: any = null) => ({
|
||||||
message: message ?? expect.anything(),
|
message: message ?? expect.anything(),
|
||||||
}),
|
}),
|
||||||
|
validationError: (errors?: ReadonlyArray<{ path: ReadonlyArray<string | number>; message: string }>) => ({
|
||||||
|
message: 'Validation failed',
|
||||||
|
errors: errors ? expect.arrayContaining(errors.map((e) => expect.objectContaining(e))) : expect.any(Array),
|
||||||
|
}),
|
||||||
noPermission: {
|
noPermission: {
|
||||||
message: expect.stringContaining('Not found or no'),
|
message: expect.stringContaining('Not found or no'),
|
||||||
},
|
},
|
||||||
@@ -37,9 +41,6 @@ export const errorDto = {
|
|||||||
alreadyHasAdmin: {
|
alreadyHasAdmin: {
|
||||||
message: 'The server already has an admin',
|
message: 'The server already has an admin',
|
||||||
},
|
},
|
||||||
invalidEmail: {
|
|
||||||
message: ['email must be an email'],
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const signupResponseDto = {
|
export const signupResponseDto = {
|
||||||
|
|||||||
@@ -110,7 +110,9 @@ describe('/libraries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['importPaths'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not create an external library with duplicate exclusion patterns', async () => {
|
it('should not create an external library with duplicate exclusion patterns', async () => {
|
||||||
@@ -125,7 +127,9 @@ describe('/libraries', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -157,7 +161,9 @@ describe('/libraries', () => {
|
|||||||
.send({ name: '' });
|
.send({ name: '' });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[name] Too small: expected string to have >=1 characters']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['name'], message: 'Too small: expected string to have >=1 characters' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change the import paths', async () => {
|
it('should change the import paths', async () => {
|
||||||
@@ -181,7 +187,9 @@ describe('/libraries', () => {
|
|||||||
.send({ importPaths: [''] });
|
.send({ importPaths: [''] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array items must not be empty']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['importPaths'], message: 'Array items must not be empty' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject duplicate import paths', async () => {
|
it('should reject duplicate import paths', async () => {
|
||||||
@@ -191,7 +199,9 @@ describe('/libraries', () => {
|
|||||||
.send({ importPaths: ['/path', '/path'] });
|
.send({ importPaths: ['/path', '/path'] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[importPaths] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['importPaths'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should change the exclusion pattern', async () => {
|
it('should change the exclusion pattern', async () => {
|
||||||
@@ -215,7 +225,9 @@ describe('/libraries', () => {
|
|||||||
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
.send({ exclusionPatterns: ['**/*.jpg', '**/*.jpg'] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array must have unique items']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array must have unique items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject an empty exclusion pattern', async () => {
|
it('should reject an empty exclusion pattern', async () => {
|
||||||
@@ -225,7 +237,9 @@ describe('/libraries', () => {
|
|||||||
.send({ exclusionPatterns: [''] });
|
.send({ exclusionPatterns: [''] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[exclusionPatterns] Array items must not be empty']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['exclusionPatterns'], message: 'Array items must not be empty' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -109,7 +109,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lon=123')
|
.get('/map/reverse-geocode?lon=123')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lat'], message: 'Invalid input: expected number, received NaN' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a lat is not a number', async () => {
|
it('should throw an error if a lat is not a number', async () => {
|
||||||
@@ -117,7 +119,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
.get('/map/reverse-geocode?lat=abc&lon=123.456')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lat] Invalid input: expected number, received NaN']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lat'], message: 'Invalid input: expected number, received NaN' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a lat is out of range', async () => {
|
it('should throw an error if a lat is out of range', async () => {
|
||||||
@@ -125,7 +129,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
.get('/map/reverse-geocode?lat=91&lon=123.456')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lat] Too big: expected number to be <=90']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lat'], message: 'Too big: expected number to be <=90' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if a lon is not provided', async () => {
|
it('should throw an error if a lon is not provided', async () => {
|
||||||
@@ -133,7 +139,9 @@ describe('/map', () => {
|
|||||||
.get('/map/reverse-geocode?lat=75')
|
.get('/map/reverse-geocode?lat=75')
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[lon] Invalid input: expected number, received NaN']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['lon'], message: 'Invalid input: expected number, received NaN' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const reverseGeocodeTestCases = [
|
const reverseGeocodeTestCases = [
|
||||||
|
|||||||
@@ -105,7 +105,11 @@ describe(`/oauth`, () => {
|
|||||||
it(`should throw an error if a redirect uri is not provided`, async () => {
|
it(`should throw an error if a redirect uri is not provided`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/authorize').send({});
|
const { status, body } = await request(app).post('/oauth/authorize').send({});
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[redirectUri] Invalid input: expected string, received undefined']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([
|
||||||
|
{ path: ['redirectUri'], message: 'Invalid input: expected string, received undefined' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a redirect uri', async () => {
|
it('should return a redirect uri', async () => {
|
||||||
@@ -164,13 +168,17 @@ describe(`/oauth`, () => {
|
|||||||
it(`should throw an error if a url is not provided`, async () => {
|
it(`should throw an error if a url is not provided`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/callback').send({});
|
const { status, body } = await request(app).post('/oauth/callback').send({});
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[url] Invalid input: expected string, received undefined']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['url'], message: 'Invalid input: expected string, received undefined' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if the url is empty`, async () => {
|
it(`should throw an error if the url is empty`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
|
const { status, body } = await request(app).post('/oauth/callback').send({ url: '' });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[url] Too small: expected string to have >=1 characters']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['url'], message: 'Too small: expected string to have >=1 characters' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if the state is not provided`, async () => {
|
it(`should throw an error if the state is not provided`, async () => {
|
||||||
@@ -375,7 +383,11 @@ describe(`/oauth`, () => {
|
|||||||
it(`should throw an error if the logout_token is not provided`, async () => {
|
it(`should throw an error if the logout_token is not provided`, async () => {
|
||||||
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({});
|
const { status, body } = await request(app).post('/oauth/backchannel-logout').send({});
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[logout_token] Invalid input: expected string, received undefined']));
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([
|
||||||
|
{ path: ['logout_token'], message: 'Invalid input: expected string, received undefined' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should throw an error if an invalid logout token is provided`, async () => {
|
it(`should throw an error if an invalid logout token is provided`, async () => {
|
||||||
|
|||||||
@@ -341,7 +341,9 @@ describe('/shared-links', () => {
|
|||||||
.set('Authorization', `Bearer ${user1.accessToken}`);
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: [], message: 'Invalid input: expected object, received undefined' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require an asset/album id', async () => {
|
it('should require an asset/album id', async () => {
|
||||||
|
|||||||
@@ -41,7 +41,9 @@ describe('/stacks', () => {
|
|||||||
.send({ assetIds: [asset.id] });
|
.send({ assetIds: [asset.id] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([{ path: ['assetIds'], message: 'Too small: expected array to have >=2 items' }]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require a valid id', async () => {
|
it('should require a valid id', async () => {
|
||||||
@@ -51,7 +53,12 @@ describe('/stacks', () => {
|
|||||||
.send({ assetIds: [uuidDto.invalid, uuidDto.invalid] });
|
.send({ assetIds: [uuidDto.invalid, uuidDto.invalid] });
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(
|
||||||
|
errorDto.validationError([
|
||||||
|
{ path: ['assetIds', 0], message: 'Invalid UUID' },
|
||||||
|
{ path: ['assetIds', 1], message: 'Invalid UUID' },
|
||||||
|
]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require access', async () => {
|
it('should require access', async () => {
|
||||||
|
|||||||
@@ -309,7 +309,7 @@ describe('/tags', () => {
|
|||||||
.get(`/tags/${uuidDto.invalid}`)
|
.get(`/tags/${uuidDto.invalid}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
|
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get tag details', async () => {
|
it('should get tag details', async () => {
|
||||||
@@ -427,7 +427,7 @@ describe('/tags', () => {
|
|||||||
.delete(`/tags/${uuidDto.invalid}`)
|
.delete(`/tags/${uuidDto.invalid}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest(['[id] Invalid UUID']));
|
expect(body).toEqual(errorDto.validationError([{ path: ['id'], message: 'Invalid UUID' }]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete a tag', async () => {
|
it('should delete a tag', async () => {
|
||||||
|
|||||||
@@ -108,14 +108,20 @@ describe('/admin/users', () => {
|
|||||||
expect(body).toEqual(errorDto.forbidden);
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const key of ['password', 'email', 'name', 'quotaSizeInBytes', 'shouldChangePassword', 'notify']) {
|
for (const [key, message] of [
|
||||||
|
['password', 'Invalid input: expected string, received null'],
|
||||||
|
['email', 'Invalid input: expected email, received object'],
|
||||||
|
['name', 'Invalid input: expected string, received null'],
|
||||||
|
['shouldChangePassword', 'Invalid input: expected boolean, received null'],
|
||||||
|
['notify', 'Invalid input: expected boolean, received null'],
|
||||||
|
] as const) {
|
||||||
it(`should not allow null ${key}`, async () => {
|
it(`should not allow null ${key}`, async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.post(`/admin/users`)
|
.post(`/admin/users`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ ...createUserDto.user1, [key]: null });
|
.send({ ...createUserDto.user1, [key]: null });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(errorDto.validationError([{ path: [key], message }]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,14 +159,19 @@ describe('/admin/users', () => {
|
|||||||
expect(body).toEqual(errorDto.forbidden);
|
expect(body).toEqual(errorDto.forbidden);
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const key of ['password', 'email', 'name', 'shouldChangePassword']) {
|
for (const [key, message] of [
|
||||||
|
['password', 'Invalid input: expected string, received null'],
|
||||||
|
['email', 'Invalid input: expected email, received object'],
|
||||||
|
['name', 'Invalid input: expected string, received null'],
|
||||||
|
['shouldChangePassword', 'Invalid input: expected boolean, received null'],
|
||||||
|
] as const) {
|
||||||
it(`should not allow null ${key}`, async () => {
|
it(`should not allow null ${key}`, async () => {
|
||||||
const { status, body } = await request(app)
|
const { status, body } = await request(app)
|
||||||
.put(`/admin/users/${uuidDto.notFound}`)
|
.put(`/admin/users/${uuidDto.notFound}`)
|
||||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||||
.send({ [key]: null });
|
.send({ [key]: null });
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest());
|
expect(body).toEqual(errorDto.validationError([{ path: [key], message }]));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -179,7 +179,9 @@ describe('/users', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(['[download.archiveSize] Invalid input: expected int, received number']),
|
errorDto.validationError([
|
||||||
|
{ path: ['download', 'archiveSize'], message: 'Invalid input: expected int, received number' },
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -207,7 +209,9 @@ describe('/users', () => {
|
|||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(
|
expect(body).toEqual(
|
||||||
errorDto.badRequest(['[download.includeEmbeddedVideos] Invalid input: expected boolean, received number']),
|
errorDto.validationError([
|
||||||
|
{ path: ['download', 'includeEmbeddedVideos'], message: 'Invalid input: expected boolean, received number' },
|
||||||
|
]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ ARG RKNN_TOOLKIT_VERSION="v2.3.0"
|
|||||||
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
||||||
MACHINE_LEARNING_MODEL_ARENA=false
|
MACHINE_LEARNING_MODEL_ARENA=false
|
||||||
|
|
||||||
ADD --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
|
ADD --chmod=644 --checksum=sha256:73993ed4b440460825f21611731564503cc1d5a0c123746477da6cd574f34885 "https://github.com/airockchip/rknn-toolkit2/raw/refs/tags/${RKNN_TOOLKIT_VERSION}/rknpu2/runtime/Linux/librknn_api/aarch64/librknnrt.so" /usr/lib/
|
||||||
|
|
||||||
FROM prod-${DEVICE} AS prod
|
FROM prod-${DEVICE} AS prod
|
||||||
|
|
||||||
|
|||||||
-3358
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray }
|
enum ImmichColorPreset { indigo, deepPurple, pink, red, orange, yellow, lime, green, cyan, slateGray }
|
||||||
|
|
||||||
|
const ImmichColorPreset defaultColorPreset = ImmichColorPreset.indigo;
|
||||||
|
const String defaultColorPresetName = "indigo";
|
||||||
|
|
||||||
const Color immichBrandColorLight = Color(0xFF4150AF);
|
const Color immichBrandColorLight = Color(0xFF4150AF);
|
||||||
const Color immichBrandColorDark = Color(0xFFACCBFA);
|
const Color immichBrandColorDark = Color(0xFFACCBFA);
|
||||||
const Color whiteOpacity75 = Color.fromRGBO(255, 255, 255, 0.75);
|
const Color whiteOpacity75 = Color.fromRGBO(255, 255, 255, 0.75);
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
class AlbumConfig {
|
|
||||||
final AlbumSortMode sortMode;
|
|
||||||
final bool isReverse;
|
|
||||||
final bool isGrid;
|
|
||||||
|
|
||||||
const AlbumConfig({this.sortMode = AlbumSortMode.mostRecent, this.isReverse = true, this.isGrid = false});
|
|
||||||
|
|
||||||
AlbumConfig copyWith({AlbumSortMode? sortMode, bool? isReverse, bool? isGrid}) =>
|
|
||||||
AlbumConfig(sortMode: sortMode ?? this.sortMode, isReverse: isReverse ?? this.isReverse, isGrid: isGrid ?? this.isGrid);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is AlbumConfig && other.sortMode == sortMode && other.isReverse == isReverse && other.isGrid == isGrid);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(sortMode, isReverse, isGrid);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'AlbumConfig(sortMode: $sortMode, isReverse: $isReverse, isGrid: $isGrid)';
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/config/album_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/backup_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/cleanup_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/image_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/map_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/theme_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/timeline_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/viewer_config.dart';
|
|
||||||
|
|
||||||
class AppConfig {
|
|
||||||
final ThemeConfig theme;
|
|
||||||
final CleanupConfig cleanup;
|
|
||||||
final MapConfig map;
|
|
||||||
final TimelineConfig timeline;
|
|
||||||
final ImageConfig image;
|
|
||||||
final ViewerConfig viewer;
|
|
||||||
final AlbumConfig album;
|
|
||||||
final BackupConfig backup;
|
|
||||||
|
|
||||||
const AppConfig({
|
|
||||||
this.theme = const .new(),
|
|
||||||
this.cleanup = const .new(),
|
|
||||||
this.map = const .new(),
|
|
||||||
this.timeline = const .new(),
|
|
||||||
this.image = const .new(),
|
|
||||||
this.viewer = const .new(),
|
|
||||||
this.album = const .new(),
|
|
||||||
this.backup = const .new(),
|
|
||||||
});
|
|
||||||
|
|
||||||
AppConfig copyWith({
|
|
||||||
ThemeConfig? theme,
|
|
||||||
CleanupConfig? cleanup,
|
|
||||||
MapConfig? map,
|
|
||||||
TimelineConfig? timeline,
|
|
||||||
ImageConfig? image,
|
|
||||||
ViewerConfig? viewer,
|
|
||||||
AlbumConfig? album,
|
|
||||||
BackupConfig? backup,
|
|
||||||
}) => .new(
|
|
||||||
theme: theme ?? this.theme,
|
|
||||||
cleanup: cleanup ?? this.cleanup,
|
|
||||||
map: map ?? this.map,
|
|
||||||
timeline: timeline ?? this.timeline,
|
|
||||||
image: image ?? this.image,
|
|
||||||
viewer: viewer ?? this.viewer,
|
|
||||||
album: album ?? this.album,
|
|
||||||
backup: backup ?? this.backup,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is AppConfig &&
|
|
||||||
other.theme == theme &&
|
|
||||||
other.cleanup == cleanup &&
|
|
||||||
other.map == map &&
|
|
||||||
other.timeline == timeline &&
|
|
||||||
other.image == image &&
|
|
||||||
other.viewer == viewer &&
|
|
||||||
other.album == album &&
|
|
||||||
other.backup == backup);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(theme, cleanup, map, timeline, image, viewer, album, backup);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'AppConfig(theme: $theme, cleanup: $cleanup, map: $map, timeline: $timeline, image: $image, viewer: $viewer, album: $album, backup: $backup)';
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
class BackupConfig {
|
|
||||||
final bool enabled;
|
|
||||||
final bool useCellularForVideos;
|
|
||||||
final bool useCellularForPhotos;
|
|
||||||
final bool requireCharging;
|
|
||||||
final int triggerDelay;
|
|
||||||
final bool syncAlbums;
|
|
||||||
|
|
||||||
const BackupConfig({
|
|
||||||
this.enabled = false,
|
|
||||||
this.useCellularForVideos = false,
|
|
||||||
this.useCellularForPhotos = false,
|
|
||||||
this.requireCharging = false,
|
|
||||||
this.triggerDelay = 30,
|
|
||||||
this.syncAlbums = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
BackupConfig copyWith({
|
|
||||||
bool? enabled,
|
|
||||||
bool? useCellularForVideos,
|
|
||||||
bool? useCellularForPhotos,
|
|
||||||
bool? requireCharging,
|
|
||||||
int? triggerDelay,
|
|
||||||
bool? syncAlbums,
|
|
||||||
}) => BackupConfig(
|
|
||||||
enabled: enabled ?? this.enabled,
|
|
||||||
useCellularForVideos: useCellularForVideos ?? this.useCellularForVideos,
|
|
||||||
useCellularForPhotos: useCellularForPhotos ?? this.useCellularForPhotos,
|
|
||||||
requireCharging: requireCharging ?? this.requireCharging,
|
|
||||||
triggerDelay: triggerDelay ?? this.triggerDelay,
|
|
||||||
syncAlbums: syncAlbums ?? this.syncAlbums,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is BackupConfig &&
|
|
||||||
other.enabled == enabled &&
|
|
||||||
other.useCellularForVideos == useCellularForVideos &&
|
|
||||||
other.useCellularForPhotos == useCellularForPhotos &&
|
|
||||||
other.requireCharging == requireCharging &&
|
|
||||||
other.triggerDelay == triggerDelay &&
|
|
||||||
other.syncAlbums == syncAlbums);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode =>
|
|
||||||
Object.hash(enabled, useCellularForVideos, useCellularForPhotos, requireCharging, triggerDelay, syncAlbums);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'BackupConfig(enabled: $enabled, useCellularForVideos: $useCellularForVideos, useCellularForPhotos: $useCellularForPhotos, requireCharging: $requireCharging, triggerDelay: $triggerDelay, syncAlbums: $syncAlbums)';
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'package:immich_mobile/constants/enums.dart';
|
|
||||||
|
|
||||||
class CleanupConfig {
|
|
||||||
final bool keepFavorites;
|
|
||||||
final AssetKeepType keepMediaType;
|
|
||||||
final List<String> keepAlbumIds;
|
|
||||||
final int cutoffDaysAgo;
|
|
||||||
final bool defaultsInitialized;
|
|
||||||
|
|
||||||
const CleanupConfig({
|
|
||||||
this.keepFavorites = true,
|
|
||||||
this.keepMediaType = AssetKeepType.none,
|
|
||||||
this.keepAlbumIds = const [],
|
|
||||||
this.cutoffDaysAgo = -1,
|
|
||||||
this.defaultsInitialized = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
CleanupConfig copyWith({
|
|
||||||
bool? keepFavorites,
|
|
||||||
AssetKeepType? keepMediaType,
|
|
||||||
List<String>? keepAlbumIds,
|
|
||||||
int? cutoffDaysAgo,
|
|
||||||
bool? defaultsInitialized,
|
|
||||||
}) => .new(
|
|
||||||
keepFavorites: keepFavorites ?? this.keepFavorites,
|
|
||||||
keepMediaType: keepMediaType ?? this.keepMediaType,
|
|
||||||
keepAlbumIds: keepAlbumIds ?? this.keepAlbumIds,
|
|
||||||
cutoffDaysAgo: cutoffDaysAgo ?? this.cutoffDaysAgo,
|
|
||||||
defaultsInitialized: defaultsInitialized ?? this.defaultsInitialized,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is CleanupConfig &&
|
|
||||||
other.keepFavorites == keepFavorites &&
|
|
||||||
other.keepMediaType == keepMediaType &&
|
|
||||||
other.keepAlbumIds == keepAlbumIds &&
|
|
||||||
other.cutoffDaysAgo == cutoffDaysAgo &&
|
|
||||||
other.defaultsInitialized == defaultsInitialized);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(keepFavorites, keepMediaType, keepAlbumIds, cutoffDaysAgo, defaultsInitialized);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'CleanupConfig(keepFavorites: $keepFavorites, keepMediaType: $keepMediaType, keepAlbumIds: $keepAlbumIds, cutoffDaysAgo: $cutoffDaysAgo, defaultsInitialized: $defaultsInitialized)';
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
class ImageConfig {
|
|
||||||
final bool preferRemote;
|
|
||||||
final bool loadOriginal;
|
|
||||||
|
|
||||||
const ImageConfig({this.preferRemote = false, this.loadOriginal = false});
|
|
||||||
|
|
||||||
ImageConfig copyWith({bool? preferRemote, bool? loadOriginal}) =>
|
|
||||||
ImageConfig(preferRemote: preferRemote ?? this.preferRemote, loadOriginal: loadOriginal ?? this.loadOriginal);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is ImageConfig && other.preferRemote == preferRemote && other.loadOriginal == loadOriginal);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(preferRemote, loadOriginal);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'ImageConfig(preferRemoteImage: $preferRemote, loadOriginal: $loadOriginal)';
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class MapConfig {
|
|
||||||
final int relativeDays;
|
|
||||||
final bool favoritesOnly;
|
|
||||||
final bool includeArchived;
|
|
||||||
final ThemeMode themeMode;
|
|
||||||
final bool withPartners;
|
|
||||||
|
|
||||||
const MapConfig({
|
|
||||||
this.relativeDays = 0,
|
|
||||||
this.favoritesOnly = false,
|
|
||||||
this.includeArchived = false,
|
|
||||||
this.themeMode = ThemeMode.system,
|
|
||||||
this.withPartners = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
MapConfig copyWith({
|
|
||||||
int? relativeDays,
|
|
||||||
bool? favoritesOnly,
|
|
||||||
bool? includeArchived,
|
|
||||||
ThemeMode? themeMode,
|
|
||||||
bool? withPartners,
|
|
||||||
}) => MapConfig(
|
|
||||||
relativeDays: relativeDays ?? this.relativeDays,
|
|
||||||
favoritesOnly: favoritesOnly ?? this.favoritesOnly,
|
|
||||||
includeArchived: includeArchived ?? this.includeArchived,
|
|
||||||
themeMode: themeMode ?? this.themeMode,
|
|
||||||
withPartners: withPartners ?? this.withPartners,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is MapConfig &&
|
|
||||||
other.relativeDays == relativeDays &&
|
|
||||||
other.favoritesOnly == favoritesOnly &&
|
|
||||||
other.includeArchived == includeArchived &&
|
|
||||||
other.themeMode == themeMode &&
|
|
||||||
other.withPartners == withPartners);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(relativeDays, favoritesOnly, includeArchived, themeMode, withPartners);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'MapConfig(relativeDays: $relativeDays, favoritesOnly: $favoritesOnly, includeArchived: $includeArchived, themeMode: $themeMode, withPartners: $withPartners)';
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
|
|
||||||
class NetworkConfig {
|
|
||||||
final bool autoEndpointSwitching;
|
|
||||||
final String? preferredWifiName;
|
|
||||||
final String? localEndpoint;
|
|
||||||
final List<String> externalEndpointList;
|
|
||||||
final Map<String, String> customHeaders;
|
|
||||||
|
|
||||||
const NetworkConfig({
|
|
||||||
this.autoEndpointSwitching = false,
|
|
||||||
this.preferredWifiName,
|
|
||||||
this.localEndpoint,
|
|
||||||
this.externalEndpointList = const [],
|
|
||||||
this.customHeaders = const {},
|
|
||||||
});
|
|
||||||
|
|
||||||
NetworkConfig copyWith({
|
|
||||||
bool? autoEndpointSwitching,
|
|
||||||
String? preferredWifiName,
|
|
||||||
String? localEndpoint,
|
|
||||||
List<String>? externalEndpointList,
|
|
||||||
Map<String, String>? customHeaders,
|
|
||||||
}) => NetworkConfig(
|
|
||||||
autoEndpointSwitching: autoEndpointSwitching ?? this.autoEndpointSwitching,
|
|
||||||
preferredWifiName: preferredWifiName ?? this.preferredWifiName,
|
|
||||||
localEndpoint: localEndpoint ?? this.localEndpoint,
|
|
||||||
externalEndpointList: externalEndpointList ?? this.externalEndpointList,
|
|
||||||
customHeaders: customHeaders ?? this.customHeaders,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is NetworkConfig &&
|
|
||||||
other.autoEndpointSwitching == autoEndpointSwitching &&
|
|
||||||
other.preferredWifiName == preferredWifiName &&
|
|
||||||
other.localEndpoint == localEndpoint &&
|
|
||||||
listEquals(other.externalEndpointList, externalEndpointList) &&
|
|
||||||
mapEquals(other.customHeaders, customHeaders));
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
autoEndpointSwitching,
|
|
||||||
preferredWifiName,
|
|
||||||
localEndpoint,
|
|
||||||
Object.hashAll(externalEndpointList),
|
|
||||||
Object.hashAllUnordered(customHeaders.entries.map((e) => Object.hash(e.key, e.value))),
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'NetworkConfig(autoEndpointSwitching: $autoEndpointSwitching, preferredWifiName: $preferredWifiName, localEndpoint: $localEndpoint, externalEndpointList: $externalEndpointList, customHeaders: $customHeaders)';
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/config/network_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
||||||
|
|
||||||
class SystemConfig {
|
|
||||||
final LogLevel logLevel;
|
|
||||||
final NetworkConfig network;
|
|
||||||
|
|
||||||
const SystemConfig({this.logLevel = .info, this.network = const .new()});
|
|
||||||
|
|
||||||
SystemConfig copyWith({LogLevel? logLevel, NetworkConfig? network}) =>
|
|
||||||
SystemConfig(logLevel: logLevel ?? this.logLevel, network: network ?? this.network);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is SystemConfig && other.logLevel == logLevel && other.network == network);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(logLevel, network);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => 'SystemConfig(logLevel: $logLevel, network: $network)';
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
|
||||||
|
|
||||||
class ThemeConfig {
|
|
||||||
final ThemeMode mode;
|
|
||||||
final ImmichColorPreset primaryColor;
|
|
||||||
final bool dynamicTheme;
|
|
||||||
final bool colorfulInterface;
|
|
||||||
|
|
||||||
const ThemeConfig({
|
|
||||||
this.mode = .system,
|
|
||||||
this.primaryColor = .indigo,
|
|
||||||
this.dynamicTheme = false,
|
|
||||||
this.colorfulInterface = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
ThemeConfig copyWith({
|
|
||||||
ThemeMode? mode,
|
|
||||||
ImmichColorPreset? primaryColor,
|
|
||||||
bool? dynamicTheme,
|
|
||||||
bool? colorfulInterface,
|
|
||||||
}) => .new(
|
|
||||||
mode: mode ?? this.mode,
|
|
||||||
primaryColor: primaryColor ?? this.primaryColor,
|
|
||||||
dynamicTheme: dynamicTheme ?? this.dynamicTheme,
|
|
||||||
colorfulInterface: colorfulInterface ?? this.colorfulInterface,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is ThemeConfig &&
|
|
||||||
other.mode == mode &&
|
|
||||||
other.primaryColor == primaryColor &&
|
|
||||||
other.dynamicTheme == dynamicTheme &&
|
|
||||||
other.colorfulInterface == colorfulInterface);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(mode, primaryColor, dynamicTheme, colorfulInterface);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'ThemeConfig(mode: $mode, primaryColor: $primaryColor, dynamicTheme: $dynamicTheme, colorfulInterface: $colorfulInterface)';
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
||||||
|
|
||||||
class TimelineConfig {
|
|
||||||
final int tilesPerRow;
|
|
||||||
final GroupAssetsBy groupAssetsBy;
|
|
||||||
final bool storageIndicator;
|
|
||||||
|
|
||||||
const TimelineConfig({this.tilesPerRow = 4, this.groupAssetsBy = GroupAssetsBy.day, this.storageIndicator = true});
|
|
||||||
|
|
||||||
TimelineConfig copyWith({int? tilesPerRow, GroupAssetsBy? groupAssetsBy, bool? storageIndicator}) => TimelineConfig(
|
|
||||||
tilesPerRow: tilesPerRow ?? this.tilesPerRow,
|
|
||||||
groupAssetsBy: groupAssetsBy ?? this.groupAssetsBy,
|
|
||||||
storageIndicator: storageIndicator ?? this.storageIndicator,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is TimelineConfig &&
|
|
||||||
other.tilesPerRow == tilesPerRow &&
|
|
||||||
other.groupAssetsBy == groupAssetsBy &&
|
|
||||||
other.storageIndicator == storageIndicator);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(tilesPerRow, groupAssetsBy, storageIndicator);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'TimelineConfig(tilesPerRow: $tilesPerRow, groupAssetsBy: $groupAssetsBy, storageIndicator: $storageIndicator)';
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
class ViewerConfig {
|
|
||||||
final bool loopVideo;
|
|
||||||
final bool loadOriginalVideo;
|
|
||||||
final bool autoPlayVideo;
|
|
||||||
final bool tapToNavigate;
|
|
||||||
|
|
||||||
const ViewerConfig({
|
|
||||||
this.loopVideo = true,
|
|
||||||
this.loadOriginalVideo = false,
|
|
||||||
this.autoPlayVideo = true,
|
|
||||||
this.tapToNavigate = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
ViewerConfig copyWith({bool? loopVideo, bool? loadOriginalVideo, bool? autoPlayVideo, bool? tapToNavigate}) =>
|
|
||||||
ViewerConfig(
|
|
||||||
loopVideo: loopVideo ?? this.loopVideo,
|
|
||||||
loadOriginalVideo: loadOriginalVideo ?? this.loadOriginalVideo,
|
|
||||||
autoPlayVideo: autoPlayVideo ?? this.autoPlayVideo,
|
|
||||||
tapToNavigate: tapToNavigate ?? this.tapToNavigate,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is ViewerConfig &&
|
|
||||||
other.loopVideo == loopVideo &&
|
|
||||||
other.loadOriginalVideo == loadOriginalVideo &&
|
|
||||||
other.autoPlayVideo == autoPlayVideo &&
|
|
||||||
other.tapToNavigate == tapToNavigate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(loopVideo, loadOriginalVideo, autoPlayVideo, tapToNavigate);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() =>
|
|
||||||
'ViewerConfig(loopVideo: $loopVideo, loadOriginalVideo: $loadOriginalVideo, autoPlayVideo: $autoPlayVideo, tapToNavigate: $tapToNavigate)';
|
|
||||||
}
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
enum MetadataDomain<T extends Object> {
|
|
||||||
appConfig<AppConfig>('config.app'),
|
|
||||||
systemConfig<SystemConfig>('config.system');
|
|
||||||
|
|
||||||
final String prefix;
|
|
||||||
const MetadataDomain(this.prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
enum MetadataKey<T extends Object> {
|
|
||||||
// Theme
|
|
||||||
themePrimaryColor<ImmichColorPreset>(.appConfig, .indigo, _EnumCodec(ImmichColorPreset.values)),
|
|
||||||
themeMode<ThemeMode>(.appConfig, .system, _EnumCodec(ThemeMode.values)),
|
|
||||||
themeDynamic<bool>(.appConfig, false),
|
|
||||||
themeColorfulInterface<bool>(.appConfig, true),
|
|
||||||
|
|
||||||
// Image
|
|
||||||
imagePreferRemote<bool>(.appConfig, false),
|
|
||||||
imageLoadOriginal<bool>(.appConfig, false),
|
|
||||||
|
|
||||||
// Viewer
|
|
||||||
viewerLoopVideo<bool>(.appConfig, true),
|
|
||||||
viewerLoadOriginalVideo<bool>(.appConfig, false),
|
|
||||||
viewerAutoPlayVideo<bool>(.appConfig, true),
|
|
||||||
viewerTapToNavigate<bool>(.appConfig, false),
|
|
||||||
|
|
||||||
// Network
|
|
||||||
networkAutoEndpointSwitching<bool>(.systemConfig, false),
|
|
||||||
networkPreferredWifiName<String>(.systemConfig, ''),
|
|
||||||
networkLocalEndpoint<String>(.systemConfig, ''),
|
|
||||||
networkExternalEndpointList<List<String>>(.systemConfig, [], _ListCodec(_PrimitiveCodec.string)),
|
|
||||||
networkCustomHeaders<Map<String, String>>(
|
|
||||||
.systemConfig,
|
|
||||||
{},
|
|
||||||
_MapCodec(_PrimitiveCodec.string, _PrimitiveCodec.string),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Album
|
|
||||||
albumSortMode<AlbumSortMode>(.appConfig, AlbumSortMode.mostRecent, _EnumCodec(AlbumSortMode.values)),
|
|
||||||
albumIsReverse<bool>(.appConfig, true),
|
|
||||||
albumIsGrid<bool>(.appConfig, false),
|
|
||||||
|
|
||||||
// Backup
|
|
||||||
backupEnabled<bool>(.appConfig, false),
|
|
||||||
backupUseCellularForVideos<bool>(.appConfig, false),
|
|
||||||
backupUseCellularForPhotos<bool>(.appConfig, false),
|
|
||||||
backupRequireCharging<bool>(.appConfig, false),
|
|
||||||
backupTriggerDelay<int>(.appConfig, 30),
|
|
||||||
backupSyncAlbums<bool>(.appConfig, false),
|
|
||||||
|
|
||||||
// Timeline
|
|
||||||
timelineTilesPerRow<int>(.appConfig, 4),
|
|
||||||
timelineGroupAssetsBy<GroupAssetsBy>(.appConfig, GroupAssetsBy.day, _EnumCodec(GroupAssetsBy.values)),
|
|
||||||
timelineStorageIndicator<bool>(.appConfig, true),
|
|
||||||
|
|
||||||
// Log
|
|
||||||
logLevel<LogLevel>(.systemConfig, .info, _EnumCodec(LogLevel.values)),
|
|
||||||
|
|
||||||
// Map
|
|
||||||
mapShowFavoriteOnly<bool>(.appConfig, false),
|
|
||||||
mapRelativeDate<int>(.appConfig, 0),
|
|
||||||
mapIncludeArchived<bool>(.appConfig, false),
|
|
||||||
mapThemeMode<ThemeMode>(.appConfig, .system, _EnumCodec(ThemeMode.values)),
|
|
||||||
mapWithPartners<bool>(.appConfig, false),
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
cleanupKeepFavorites<bool>(.appConfig, true),
|
|
||||||
cleanupKeepMediaType<AssetKeepType>(.appConfig, AssetKeepType.none, _EnumCodec(AssetKeepType.values)),
|
|
||||||
cleanupKeepAlbumIds<List<String>>(.appConfig, [], _ListCodec(_PrimitiveCodec.string)),
|
|
||||||
cleanupCutoffDaysAgo<int>(.appConfig, -1),
|
|
||||||
cleanupDefaultsInitialized<bool>(.appConfig, false);
|
|
||||||
|
|
||||||
final MetadataDomain domain;
|
|
||||||
final T defaultValue;
|
|
||||||
final _MetadataCodec<T>? _codecOverride;
|
|
||||||
|
|
||||||
const MetadataKey(this.domain, this.defaultValue, [this._codecOverride]);
|
|
||||||
|
|
||||||
String get key => '${domain.prefix}.$name';
|
|
||||||
|
|
||||||
_MetadataCodec<T> get _codec => _codecOverride ?? _MetadataCodec.forPrimitive(defaultValue);
|
|
||||||
|
|
||||||
String encode(T value) => _codec.encode(value);
|
|
||||||
|
|
||||||
T decode(String raw) => _codec.decode(raw) ?? defaultValue;
|
|
||||||
|
|
||||||
static Map<String, MetadataKey<Object>> asKeyMap() => {for (var value in MetadataKey.values) value.key: value};
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class _MetadataCodec<T extends Object> {
|
|
||||||
const _MetadataCodec();
|
|
||||||
|
|
||||||
String encode(T value);
|
|
||||||
T? decode(String raw);
|
|
||||||
|
|
||||||
static const Map<Type, _MetadataCodec<Object>> _primitives = {
|
|
||||||
int: _PrimitiveCodec.integer,
|
|
||||||
double: _PrimitiveCodec.real,
|
|
||||||
bool: _PrimitiveCodec.boolean,
|
|
||||||
String: _PrimitiveCodec.string,
|
|
||||||
DateTime: _DateTimeCodec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
static _MetadataCodec<T> forPrimitive<T extends Object>(T sample) {
|
|
||||||
final codec = _primitives[sample.runtimeType];
|
|
||||||
if (codec == null) {
|
|
||||||
throw StateError(
|
|
||||||
'No primitive codec for ${sample.runtimeType}. Provide an explicit codec when defining the MetadataKey.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return codec as _MetadataCodec<T>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _EnumCodec<T extends Enum> extends _MetadataCodec<T> {
|
|
||||||
final List<T> values;
|
|
||||||
|
|
||||||
const _EnumCodec(this.values);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(T value) => value.name;
|
|
||||||
|
|
||||||
@override
|
|
||||||
T? decode(String raw) => values.firstWhereOrNull((v) => v.name == raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _DateTimeCodec extends _MetadataCodec<DateTime> {
|
|
||||||
const _DateTimeCodec();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(DateTime value) => value.toIso8601String();
|
|
||||||
|
|
||||||
@override
|
|
||||||
DateTime? decode(String raw) => DateTime.tryParse(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _MapCodec<K extends Object, V extends Object> extends _MetadataCodec<Map<K, V>> {
|
|
||||||
final _MetadataCodec<K> _keyCodec;
|
|
||||||
final _MetadataCodec<V> _valueCodec;
|
|
||||||
|
|
||||||
const _MapCodec(this._keyCodec, this._valueCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(Map<K, V> value) {
|
|
||||||
final entries = <String, String>{};
|
|
||||||
value.forEach((k, v) => entries[_keyCodec.encode(k)] = _valueCodec.encode(v));
|
|
||||||
return jsonEncode(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<K, V>? decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! Map) return null;
|
|
||||||
final result = <K, V>{};
|
|
||||||
for (final entry in decoded.entries) {
|
|
||||||
final rawKey = entry.key;
|
|
||||||
final rawValue = entry.value;
|
|
||||||
if (rawKey is! String || rawValue is! String) return null;
|
|
||||||
final k = _keyCodec.decode(rawKey);
|
|
||||||
final v = _valueCodec.decode(rawValue);
|
|
||||||
if (k == null || v == null) return null;
|
|
||||||
result[k] = v;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _ListCodec<T extends Object> extends _MetadataCodec<List<T>> {
|
|
||||||
final _MetadataCodec<T> _elementCodec;
|
|
||||||
|
|
||||||
const _ListCodec(this._elementCodec);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(List<T> value) => jsonEncode(value.map(_elementCodec.encode).toList());
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<T>? decode(String raw) {
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is! List) return null;
|
|
||||||
final result = <T>[];
|
|
||||||
for (final item in decoded) {
|
|
||||||
if (item is! String) return null;
|
|
||||||
final element = _elementCodec.decode(item);
|
|
||||||
if (element == null) return null;
|
|
||||||
result.add(element);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} on FormatException {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class _PrimitiveCodec<T extends Object> extends _MetadataCodec<T> {
|
|
||||||
final T? Function(String) _parse;
|
|
||||||
|
|
||||||
const _PrimitiveCodec._(this._parse);
|
|
||||||
|
|
||||||
@override
|
|
||||||
String encode(T value) => value.toString();
|
|
||||||
|
|
||||||
@override
|
|
||||||
T? decode(String raw) => _parse(raw);
|
|
||||||
|
|
||||||
static const integer = _PrimitiveCodec<int>._(int.tryParse);
|
|
||||||
static const real = _PrimitiveCodec<double>._(double.tryParse);
|
|
||||||
static const boolean = _PrimitiveCodec<bool>._(bool.tryParse);
|
|
||||||
static const string = _PrimitiveCodec<String>._(_identity);
|
|
||||||
|
|
||||||
static String? _identity(String s) => s;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
|
||||||
enum Setting<T> {
|
enum Setting<T> {
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false);
|
tilesPerRow<int>(StoreKey.tilesPerRow, 4),
|
||||||
|
groupAssetsBy<int>(StoreKey.groupAssetsBy, 0),
|
||||||
|
showStorageIndicator<bool>(StoreKey.storageIndicator, true),
|
||||||
|
loadOriginal<bool>(StoreKey.loadOriginal, false),
|
||||||
|
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, false),
|
||||||
|
autoPlayVideo<bool>(StoreKey.autoPlayVideo, true),
|
||||||
|
preferRemoteImage<bool>(StoreKey.preferRemoteImage, false),
|
||||||
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false),
|
||||||
|
enableBackup<bool>(StoreKey.enableBackup, false);
|
||||||
|
|
||||||
const Setting(this.storeKey, this.defaultValue);
|
const Setting(this.storeKey, this.defaultValue);
|
||||||
|
|
||||||
|
|||||||
@@ -4,59 +4,97 @@ import 'package:immich_mobile/domain/models/user.model.dart';
|
|||||||
/// Defines the data type for each value
|
/// Defines the data type for each value
|
||||||
enum StoreKey<T> {
|
enum StoreKey<T> {
|
||||||
version<int>._(0),
|
version<int>._(0),
|
||||||
|
assetETag<String>._(1),
|
||||||
currentUser<UserDto>._(2),
|
currentUser<UserDto>._(2),
|
||||||
|
deviceIdHash<int>._(3),
|
||||||
deviceId<String>._(4),
|
deviceId<String>._(4),
|
||||||
|
backupFailedSince<DateTime>._(5),
|
||||||
|
backupRequireWifi<bool>._(6),
|
||||||
|
backupRequireCharging<bool>._(7),
|
||||||
|
backupTriggerDelay<int>._(8),
|
||||||
serverUrl<String>._(10),
|
serverUrl<String>._(10),
|
||||||
accessToken<String>._(11),
|
accessToken<String>._(11),
|
||||||
serverEndpoint<String>._(12),
|
serverEndpoint<String>._(12),
|
||||||
|
autoBackup<bool>._(13),
|
||||||
|
backgroundBackup<bool>._(14),
|
||||||
|
sslClientCertData<String>._(15),
|
||||||
|
sslClientPasswd<String>._(16),
|
||||||
|
// user settings from [AppSettingsEnum] below:
|
||||||
|
loadPreview<bool>._(100),
|
||||||
|
loadOriginal<bool>._(101),
|
||||||
|
themeMode<String>._(102),
|
||||||
|
tilesPerRow<int>._(103),
|
||||||
|
dynamicLayout<bool>._(104),
|
||||||
|
groupAssetsBy<int>._(105),
|
||||||
|
uploadErrorNotificationGracePeriod<int>._(106),
|
||||||
|
backgroundBackupTotalProgress<bool>._(107),
|
||||||
|
backgroundBackupSingleProgress<bool>._(108),
|
||||||
|
storageIndicator<bool>._(109),
|
||||||
|
thumbnailCacheSize<int>._(110),
|
||||||
|
imageCacheSize<int>._(111),
|
||||||
|
albumThumbnailCacheSize<int>._(112),
|
||||||
|
selectedAlbumSortOrder<int>._(113),
|
||||||
advancedTroubleshooting<bool>._(114),
|
advancedTroubleshooting<bool>._(114),
|
||||||
|
logLevel<int>._(115),
|
||||||
|
preferRemoteImage<bool>._(116),
|
||||||
|
loopVideo<bool>._(117),
|
||||||
|
// map related settings
|
||||||
|
mapShowFavoriteOnly<bool>._(118),
|
||||||
|
mapRelativeDate<int>._(119),
|
||||||
|
selfSignedCert<bool>._(120),
|
||||||
|
mapIncludeArchived<bool>._(121),
|
||||||
|
ignoreIcloudAssets<bool>._(122),
|
||||||
|
selectedAlbumSortReverse<bool>._(123),
|
||||||
|
mapThemeMode<int>._(124),
|
||||||
|
mapwithPartners<bool>._(125),
|
||||||
enableHapticFeedback<bool>._(126),
|
enableHapticFeedback<bool>._(126),
|
||||||
|
customHeaders<String>._(127),
|
||||||
|
|
||||||
|
// theme settings
|
||||||
|
primaryColor<String>._(128),
|
||||||
|
dynamicTheme<bool>._(129),
|
||||||
|
colorfulInterface<bool>._(130),
|
||||||
|
|
||||||
|
syncAlbums<bool>._(131),
|
||||||
|
|
||||||
|
// Auto endpoint switching
|
||||||
|
autoEndpointSwitching<bool>._(132),
|
||||||
|
preferredWifiName<String>._(133),
|
||||||
|
localEndpoint<String>._(134),
|
||||||
|
externalEndpointList<String>._(135),
|
||||||
|
|
||||||
|
// Video settings
|
||||||
|
loadOriginalVideo<bool>._(136),
|
||||||
manageLocalMediaAndroid<bool>._(137),
|
manageLocalMediaAndroid<bool>._(137),
|
||||||
|
|
||||||
// Read-only Mode settings
|
// Read-only Mode settings
|
||||||
readonlyModeEnabled<bool>._(138),
|
readonlyModeEnabled<bool>._(138),
|
||||||
|
|
||||||
syncMigrationStatus<String>._(1013),
|
autoPlayVideo<bool>._(139),
|
||||||
|
albumGridView<bool>._(140),
|
||||||
|
|
||||||
// Legacy keys that have been migrated to the new metadata store
|
// Image viewer navigation settings
|
||||||
legacyBackupRequireCharging<bool>._(7),
|
tapToNavigate<bool>._(141),
|
||||||
legacyBackupTriggerDelay<int>._(8),
|
|
||||||
legacySyncAlbums<bool>._(131),
|
// Experimental stuff
|
||||||
legacyEnableBackup<bool>._(1003),
|
photoManagerCustomFilter<bool>._(1000),
|
||||||
legacyUseWifiForUploadVideos<bool>._(1004),
|
betaPromptShown<bool>._(1001),
|
||||||
legacyUseWifiForUploadPhotos<bool>._(1005),
|
betaTimeline<bool>._(1002),
|
||||||
legacySelectedAlbumSortOrder<int>._(113),
|
enableBackup<bool>._(1003),
|
||||||
legacySelectedAlbumSortReverse<bool>._(123),
|
useWifiForUploadVideos<bool>._(1004),
|
||||||
legacyAlbumGridView<bool>._(140),
|
useWifiForUploadPhotos<bool>._(1005),
|
||||||
legacyAutoEndpointSwitching<bool>._(132),
|
needBetaMigration<bool>._(1006),
|
||||||
legacyPreferredWifiName<String>._(133),
|
// TODO: Remove this after patching open-api
|
||||||
legacyLocalEndpoint<String>._(134),
|
shouldResetSync<bool>._(1007),
|
||||||
legacyExternalEndpointList<String>._(135),
|
|
||||||
legacyCustomHeaders<String>._(127),
|
// Free up space
|
||||||
legacyLoopVideo<bool>._(117),
|
cleanupKeepFavorites<bool>._(1008),
|
||||||
legacyLoadOriginalVideo<bool>._(136),
|
cleanupKeepMediaType<int>._(1009),
|
||||||
legacyAutoPlayVideo<bool>._(139),
|
cleanupKeepAlbumIds<String>._(1010),
|
||||||
legacyTapToNavigate<bool>._(141),
|
cleanupCutoffDaysAgo<int>._(1011),
|
||||||
legacyPreferRemoteImage<bool>._(116),
|
cleanupDefaultsInitialized<bool>._(1012),
|
||||||
legacyLoadOriginal<bool>._(101),
|
|
||||||
legacyPrimaryColor<String>._(128),
|
syncMigrationStatus<String>._(1013);
|
||||||
legacyDynamicTheme<bool>._(129),
|
|
||||||
legacyColorfulInterface<bool>._(130),
|
|
||||||
legacyThemeMode<String>._(102),
|
|
||||||
legacyCleanupKeepFavorites<bool>._(1008),
|
|
||||||
legacyCleanupKeepMediaType<int>._(1009),
|
|
||||||
legacyCleanupKeepAlbumIds<String>._(1010),
|
|
||||||
legacyCleanupCutoffDaysAgo<int>._(1011),
|
|
||||||
legacyCleanupDefaultsInitialized<bool>._(1012),
|
|
||||||
legacyTilesPerRow<int>._(103),
|
|
||||||
legacyGroupAssetsBy<int>._(105),
|
|
||||||
legacyStorageIndicator<bool>._(109),
|
|
||||||
legacyMapRelativeDate<int>._(119),
|
|
||||||
legacyMapShowFavoriteOnly<bool>._(118),
|
|
||||||
legacyMapIncludeArchived<bool>._(121),
|
|
||||||
legacyMapThemeMode<int>._(124),
|
|
||||||
legacyMapwithPartners<bool>._(125),
|
|
||||||
legacyLogLevel<int>._(115);
|
|
||||||
|
|
||||||
const StoreKey._(this.id);
|
const StoreKey._(this.id);
|
||||||
final int id;
|
final int id;
|
||||||
|
|||||||
@@ -11,14 +11,15 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider;
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart' show nativeSyncApiProvider;
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/services/auth.service.dart';
|
import 'package:immich_mobile/services/auth.service.dart';
|
||||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||||
import 'package:immich_mobile/services/localization.service.dart';
|
import 'package:immich_mobile/services/localization.service.dart';
|
||||||
@@ -38,15 +39,16 @@ class BackgroundWorkerFgService {
|
|||||||
Future<void> saveNotificationMessage(String title, String body) =>
|
Future<void> saveNotificationMessage(String title, String body) =>
|
||||||
_foregroundHostApi.saveNotificationMessage(title, body);
|
_foregroundHostApi.saveNotificationMessage(title, body);
|
||||||
|
|
||||||
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
|
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) => _foregroundHostApi.configure(
|
||||||
final backup = MetadataStore.appConfig.backup;
|
|
||||||
return _foregroundHostApi.configure(
|
|
||||||
BackgroundWorkerSettings(
|
BackgroundWorkerSettings(
|
||||||
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
|
minimumDelaySeconds:
|
||||||
requiresCharging: requireCharging ?? backup.requireCharging,
|
minimumDelaySeconds ??
|
||||||
|
Store.get(AppSettingsEnum.backupTriggerDelay.storeKey, AppSettingsEnum.backupTriggerDelay.defaultValue),
|
||||||
|
requiresCharging:
|
||||||
|
requireCharging ??
|
||||||
|
Store.get(AppSettingsEnum.backupRequireCharging.storeKey, AppSettingsEnum.backupRequireCharging.defaultValue),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> disable() => _foregroundHostApi.disable();
|
Future<void> disable() => _foregroundHostApi.disable();
|
||||||
}
|
}
|
||||||
@@ -69,7 +71,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
BackgroundWorkerFlutterApi.setUp(this);
|
BackgroundWorkerFlutterApi.setUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isBackupEnabled => MetadataStore.appConfig.backup.enabled;
|
bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -93,7 +93,8 @@ class LocalSyncService {
|
|||||||
|
|
||||||
if (CurrentPlatform.isIOS) {
|
if (CurrentPlatform.isIOS) {
|
||||||
// On iOS, we need to full sync albums that are marked as cloud as the delta sync
|
// On iOS, we need to full sync albums that are marked as cloud as the delta sync
|
||||||
// does not include changes for cloud albums.
|
// does not include changes for cloud albums. If ignoreIcloudAssets is enabled,
|
||||||
|
// remove the albums from the local database from the previous sync
|
||||||
final cloudAlbums = deviceAlbums.where((a) => a.isCloud).toLocalAlbums();
|
final cloudAlbums = deviceAlbums.where((a) => a.isCloud).toLocalAlbums();
|
||||||
for (final album in cloudAlbums) {
|
for (final album in cloudAlbums) {
|
||||||
final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id);
|
final dbAlbum = dbAlbums.firstWhereOrNull((a) => a.id == album.id);
|
||||||
|
|||||||
@@ -2,20 +2,20 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
import 'package:immich_mobile/domain/models/log.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
/// Service responsible for handling application logging.
|
/// Service responsible for handling application logging.
|
||||||
///
|
///
|
||||||
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
/// It listens to Dart's [Logger.root], buffers logs in memory (optionally),
|
||||||
/// writes them to a persistent [LogRepository], and manages log levels via
|
/// writes them to a persistent [ILogRepository], and manages log levels
|
||||||
/// [MetadataRepository].
|
/// via [IStoreRepository]
|
||||||
class LogService {
|
class LogService {
|
||||||
final LogRepository _logRepository;
|
final LogRepository _logRepository;
|
||||||
final MetadataRepository _metadataRepository;
|
final DriftStoreRepository _storeRepository;
|
||||||
|
|
||||||
final List<LogMessage> _msgBuffer = [];
|
final List<LogMessage> _msgBuffer = [];
|
||||||
|
|
||||||
@@ -38,12 +38,12 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> init({
|
static Future<LogService> init({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required MetadataRepository metadataRepository,
|
required DriftStoreRepository storeRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
_instance ??= await create(
|
_instance ??= await create(
|
||||||
logRepository: logRepository,
|
logRepository: logRepository,
|
||||||
metadataRepository: metadataRepository,
|
storeRepository: storeRepository,
|
||||||
shouldBuffer: shouldBuffer,
|
shouldBuffer: shouldBuffer,
|
||||||
);
|
);
|
||||||
return _instance!;
|
return _instance!;
|
||||||
@@ -51,17 +51,17 @@ class LogService {
|
|||||||
|
|
||||||
static Future<LogService> create({
|
static Future<LogService> create({
|
||||||
required LogRepository logRepository,
|
required LogRepository logRepository,
|
||||||
required MetadataRepository metadataRepository,
|
required DriftStoreRepository storeRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
|
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
||||||
await logRepository.truncate(limit: kLogTruncateLimit);
|
await logRepository.truncate(limit: kLogTruncateLimit);
|
||||||
final level = instance._metadataRepository.systemConfig.logLevel;
|
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index;
|
||||||
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogService._(this._logRepository, this._metadataRepository, this._shouldBuffer) {
|
LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) {
|
||||||
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
_logSubscription = Logger.root.onRecord.listen(_handleLogRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ class LogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> setLogLevel(LogLevel level) async {
|
Future<void> setLogLevel(LogLevel level) async {
|
||||||
await _metadataRepository.write(MetadataKey.logLevel, level);
|
await _storeRepository.upsert(StoreKey.logLevel, level.index);
|
||||||
Logger.root.level = level.toLevel();
|
Logger.root.level = level.toLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,10 @@ import 'package:collection/collection.dart';
|
|||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/setting.service.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||||
|
|
||||||
@@ -38,16 +39,14 @@ enum TimelineOrigin {
|
|||||||
|
|
||||||
class TimelineFactory {
|
class TimelineFactory {
|
||||||
final DriftTimelineRepository _timelineRepository;
|
final DriftTimelineRepository _timelineRepository;
|
||||||
final MetadataRepository _metadataRepository;
|
final SettingsService _settingsService;
|
||||||
|
|
||||||
const TimelineFactory({
|
const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService})
|
||||||
required DriftTimelineRepository timelineRepository,
|
: _timelineRepository = timelineRepository,
|
||||||
required MetadataRepository metadataRepository,
|
_settingsService = settingsService;
|
||||||
}) : _timelineRepository = timelineRepository,
|
|
||||||
_metadataRepository = metadataRepository;
|
|
||||||
|
|
||||||
GroupAssetsBy get groupBy {
|
GroupAssetsBy get groupBy {
|
||||||
final group = _metadataRepository.appConfig.timeline.groupAssetsBy;
|
final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)];
|
||||||
// We do not support auto grouping in the new timeline yet, fallback to day grouping
|
// We do not support auto grouping in the new timeline yet, fallback to day grouping
|
||||||
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
|
return group == GroupAssetsBy.auto ? GroupAssetsBy.day : group;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ extension StringExtension on String {
|
|||||||
String capitalize() {
|
String capitalize() {
|
||||||
return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" ");
|
return split(" ").map((str) => str.isEmpty ? str : str[0].toUpperCase() + str.substring(1)).join(" ");
|
||||||
}
|
}
|
||||||
|
|
||||||
String? get nullIfEmpty => isEmpty ? null : this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DurationExtension on String {
|
extension DurationExtension on String {
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
|
||||||
|
|
||||||
class MetadataEntity extends Table with DriftDefaultsMixin {
|
|
||||||
const MetadataEntity();
|
|
||||||
|
|
||||||
TextColumn get key => text()();
|
|
||||||
|
|
||||||
TextColumn get value => text()();
|
|
||||||
|
|
||||||
DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column> get primaryKey => {key};
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get tableName => "metadata";
|
|
||||||
}
|
|
||||||
@@ -1,429 +0,0 @@
|
|||||||
// dart format width=80
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
import 'package:drift/drift.dart' as i0;
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
|
||||||
as i1;
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart'
|
|
||||||
as i2;
|
|
||||||
import 'package:drift/src/runtime/query_builder/query_builder.dart' as i3;
|
|
||||||
|
|
||||||
typedef $$MetadataEntityTableCreateCompanionBuilder =
|
|
||||||
i1.MetadataEntityCompanion Function({
|
|
||||||
required String key,
|
|
||||||
required String value,
|
|
||||||
i0.Value<DateTime> updatedAt,
|
|
||||||
});
|
|
||||||
typedef $$MetadataEntityTableUpdateCompanionBuilder =
|
|
||||||
i1.MetadataEntityCompanion Function({
|
|
||||||
i0.Value<String> key,
|
|
||||||
i0.Value<String> value,
|
|
||||||
i0.Value<DateTime> updatedAt,
|
|
||||||
});
|
|
||||||
|
|
||||||
class $$MetadataEntityTableFilterComposer
|
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
|
||||||
$$MetadataEntityTableFilterComposer({
|
|
||||||
required super.$db,
|
|
||||||
required super.$table,
|
|
||||||
super.joinBuilder,
|
|
||||||
super.$addJoinBuilderToRootComposer,
|
|
||||||
super.$removeJoinBuilderFromRootComposer,
|
|
||||||
});
|
|
||||||
i0.ColumnFilters<String> get key => $composableBuilder(
|
|
||||||
column: $table.key,
|
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
i0.ColumnFilters<String> get value => $composableBuilder(
|
|
||||||
column: $table.value,
|
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
i0.ColumnFilters<DateTime> get updatedAt => $composableBuilder(
|
|
||||||
column: $table.updatedAt,
|
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class $$MetadataEntityTableOrderingComposer
|
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
|
||||||
$$MetadataEntityTableOrderingComposer({
|
|
||||||
required super.$db,
|
|
||||||
required super.$table,
|
|
||||||
super.joinBuilder,
|
|
||||||
super.$addJoinBuilderToRootComposer,
|
|
||||||
super.$removeJoinBuilderFromRootComposer,
|
|
||||||
});
|
|
||||||
i0.ColumnOrderings<String> get key => $composableBuilder(
|
|
||||||
column: $table.key,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
i0.ColumnOrderings<String> get value => $composableBuilder(
|
|
||||||
column: $table.value,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
|
|
||||||
i0.ColumnOrderings<DateTime> get updatedAt => $composableBuilder(
|
|
||||||
column: $table.updatedAt,
|
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class $$MetadataEntityTableAnnotationComposer
|
|
||||||
extends i0.Composer<i0.GeneratedDatabase, i1.$MetadataEntityTable> {
|
|
||||||
$$MetadataEntityTableAnnotationComposer({
|
|
||||||
required super.$db,
|
|
||||||
required super.$table,
|
|
||||||
super.joinBuilder,
|
|
||||||
super.$addJoinBuilderToRootComposer,
|
|
||||||
super.$removeJoinBuilderFromRootComposer,
|
|
||||||
});
|
|
||||||
i0.GeneratedColumn<String> get key =>
|
|
||||||
$composableBuilder(column: $table.key, builder: (column) => column);
|
|
||||||
|
|
||||||
i0.GeneratedColumn<String> get value =>
|
|
||||||
$composableBuilder(column: $table.value, builder: (column) => column);
|
|
||||||
|
|
||||||
i0.GeneratedColumn<DateTime> get updatedAt =>
|
|
||||||
$composableBuilder(column: $table.updatedAt, builder: (column) => column);
|
|
||||||
}
|
|
||||||
|
|
||||||
class $$MetadataEntityTableTableManager
|
|
||||||
extends
|
|
||||||
i0.RootTableManager<
|
|
||||||
i0.GeneratedDatabase,
|
|
||||||
i1.$MetadataEntityTable,
|
|
||||||
i1.MetadataEntityData,
|
|
||||||
i1.$$MetadataEntityTableFilterComposer,
|
|
||||||
i1.$$MetadataEntityTableOrderingComposer,
|
|
||||||
i1.$$MetadataEntityTableAnnotationComposer,
|
|
||||||
$$MetadataEntityTableCreateCompanionBuilder,
|
|
||||||
$$MetadataEntityTableUpdateCompanionBuilder,
|
|
||||||
(
|
|
||||||
i1.MetadataEntityData,
|
|
||||||
i0.BaseReferences<
|
|
||||||
i0.GeneratedDatabase,
|
|
||||||
i1.$MetadataEntityTable,
|
|
||||||
i1.MetadataEntityData
|
|
||||||
>,
|
|
||||||
),
|
|
||||||
i1.MetadataEntityData,
|
|
||||||
i0.PrefetchHooks Function()
|
|
||||||
> {
|
|
||||||
$$MetadataEntityTableTableManager(
|
|
||||||
i0.GeneratedDatabase db,
|
|
||||||
i1.$MetadataEntityTable table,
|
|
||||||
) : super(
|
|
||||||
i0.TableManagerState(
|
|
||||||
db: db,
|
|
||||||
table: table,
|
|
||||||
createFilteringComposer: () =>
|
|
||||||
i1.$$MetadataEntityTableFilterComposer($db: db, $table: table),
|
|
||||||
createOrderingComposer: () =>
|
|
||||||
i1.$$MetadataEntityTableOrderingComposer($db: db, $table: table),
|
|
||||||
createComputedFieldComposer: () => i1
|
|
||||||
.$$MetadataEntityTableAnnotationComposer($db: db, $table: table),
|
|
||||||
updateCompanionCallback:
|
|
||||||
({
|
|
||||||
i0.Value<String> key = const i0.Value.absent(),
|
|
||||||
i0.Value<String> value = const i0.Value.absent(),
|
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
|
||||||
}) => i1.MetadataEntityCompanion(
|
|
||||||
key: key,
|
|
||||||
value: value,
|
|
||||||
updatedAt: updatedAt,
|
|
||||||
),
|
|
||||||
createCompanionCallback:
|
|
||||||
({
|
|
||||||
required String key,
|
|
||||||
required String value,
|
|
||||||
i0.Value<DateTime> updatedAt = const i0.Value.absent(),
|
|
||||||
}) => i1.MetadataEntityCompanion.insert(
|
|
||||||
key: key,
|
|
||||||
value: value,
|
|
||||||
updatedAt: updatedAt,
|
|
||||||
),
|
|
||||||
withReferenceMapper: (p0) => p0
|
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
|
||||||
.toList(),
|
|
||||||
prefetchHooksCallback: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef $$MetadataEntityTableProcessedTableManager =
|
|
||||||
i0.ProcessedTableManager<
|
|
||||||
i0.GeneratedDatabase,
|
|
||||||
i1.$MetadataEntityTable,
|
|
||||||
i1.MetadataEntityData,
|
|
||||||
i1.$$MetadataEntityTableFilterComposer,
|
|
||||||
i1.$$MetadataEntityTableOrderingComposer,
|
|
||||||
i1.$$MetadataEntityTableAnnotationComposer,
|
|
||||||
$$MetadataEntityTableCreateCompanionBuilder,
|
|
||||||
$$MetadataEntityTableUpdateCompanionBuilder,
|
|
||||||
(
|
|
||||||
i1.MetadataEntityData,
|
|
||||||
i0.BaseReferences<
|
|
||||||
i0.GeneratedDatabase,
|
|
||||||
i1.$MetadataEntityTable,
|
|
||||||
i1.MetadataEntityData
|
|
||||||
>,
|
|
||||||
),
|
|
||||||
i1.MetadataEntityData,
|
|
||||||
i0.PrefetchHooks Function()
|
|
||||||
>;
|
|
||||||
|
|
||||||
class $MetadataEntityTable extends i2.MetadataEntity
|
|
||||||
with i0.TableInfo<$MetadataEntityTable, i1.MetadataEntityData> {
|
|
||||||
@override
|
|
||||||
final i0.GeneratedDatabase attachedDatabase;
|
|
||||||
final String? _alias;
|
|
||||||
$MetadataEntityTable(this.attachedDatabase, [this._alias]);
|
|
||||||
static const i0.VerificationMeta _keyMeta = const i0.VerificationMeta('key');
|
|
||||||
@override
|
|
||||||
late final i0.GeneratedColumn<String> key = i0.GeneratedColumn<String>(
|
|
||||||
'key',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.string,
|
|
||||||
requiredDuringInsert: true,
|
|
||||||
);
|
|
||||||
static const i0.VerificationMeta _valueMeta = const i0.VerificationMeta(
|
|
||||||
'value',
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
late final i0.GeneratedColumn<String> value = i0.GeneratedColumn<String>(
|
|
||||||
'value',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.string,
|
|
||||||
requiredDuringInsert: true,
|
|
||||||
);
|
|
||||||
static const i0.VerificationMeta _updatedAtMeta = const i0.VerificationMeta(
|
|
||||||
'updatedAt',
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
late final i0.GeneratedColumn<DateTime> updatedAt =
|
|
||||||
i0.GeneratedColumn<DateTime>(
|
|
||||||
'updated_at',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i0.DriftSqlType.dateTime,
|
|
||||||
requiredDuringInsert: false,
|
|
||||||
defaultValue: i3.currentDateAndTime,
|
|
||||||
);
|
|
||||||
@override
|
|
||||||
List<i0.GeneratedColumn> get $columns => [key, value, updatedAt];
|
|
||||||
@override
|
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
|
||||||
@override
|
|
||||||
String get actualTableName => $name;
|
|
||||||
static const String $name = 'metadata';
|
|
||||||
@override
|
|
||||||
i0.VerificationContext validateIntegrity(
|
|
||||||
i0.Insertable<i1.MetadataEntityData> instance, {
|
|
||||||
bool isInserting = false,
|
|
||||||
}) {
|
|
||||||
final context = i0.VerificationContext();
|
|
||||||
final data = instance.toColumns(true);
|
|
||||||
if (data.containsKey('key')) {
|
|
||||||
context.handle(
|
|
||||||
_keyMeta,
|
|
||||||
key.isAcceptableOrUnknown(data['key']!, _keyMeta),
|
|
||||||
);
|
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_keyMeta);
|
|
||||||
}
|
|
||||||
if (data.containsKey('value')) {
|
|
||||||
context.handle(
|
|
||||||
_valueMeta,
|
|
||||||
value.isAcceptableOrUnknown(data['value']!, _valueMeta),
|
|
||||||
);
|
|
||||||
} else if (isInserting) {
|
|
||||||
context.missing(_valueMeta);
|
|
||||||
}
|
|
||||||
if (data.containsKey('updated_at')) {
|
|
||||||
context.handle(
|
|
||||||
_updatedAtMeta,
|
|
||||||
updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<i0.GeneratedColumn> get $primaryKey => {key};
|
|
||||||
@override
|
|
||||||
i1.MetadataEntityData map(Map<String, dynamic> data, {String? tablePrefix}) {
|
|
||||||
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
|
||||||
return i1.MetadataEntityData(
|
|
||||||
key: attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.string,
|
|
||||||
data['${effectivePrefix}key'],
|
|
||||||
)!,
|
|
||||||
value: attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.string,
|
|
||||||
data['${effectivePrefix}value'],
|
|
||||||
)!,
|
|
||||||
updatedAt: attachedDatabase.typeMapping.read(
|
|
||||||
i0.DriftSqlType.dateTime,
|
|
||||||
data['${effectivePrefix}updated_at'],
|
|
||||||
)!,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
$MetadataEntityTable createAlias(String alias) {
|
|
||||||
return $MetadataEntityTable(attachedDatabase, alias);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get withoutRowId => true;
|
|
||||||
@override
|
|
||||||
bool get isStrict => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataEntityData extends i0.DataClass
|
|
||||||
implements i0.Insertable<i1.MetadataEntityData> {
|
|
||||||
final String key;
|
|
||||||
final String value;
|
|
||||||
final DateTime updatedAt;
|
|
||||||
const MetadataEntityData({
|
|
||||||
required this.key,
|
|
||||||
required this.value,
|
|
||||||
required this.updatedAt,
|
|
||||||
});
|
|
||||||
@override
|
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
|
||||||
final map = <String, i0.Expression>{};
|
|
||||||
map['key'] = i0.Variable<String>(key);
|
|
||||||
map['value'] = i0.Variable<String>(value);
|
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt);
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
factory MetadataEntityData.fromJson(
|
|
||||||
Map<String, dynamic> json, {
|
|
||||||
i0.ValueSerializer? serializer,
|
|
||||||
}) {
|
|
||||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
|
||||||
return MetadataEntityData(
|
|
||||||
key: serializer.fromJson<String>(json['key']),
|
|
||||||
value: serializer.fromJson<String>(json['value']),
|
|
||||||
updatedAt: serializer.fromJson<DateTime>(json['updatedAt']),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
|
||||||
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
|
||||||
return <String, dynamic>{
|
|
||||||
'key': serializer.toJson<String>(key),
|
|
||||||
'value': serializer.toJson<String>(value),
|
|
||||||
'updatedAt': serializer.toJson<DateTime>(updatedAt),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
i1.MetadataEntityData copyWith({
|
|
||||||
String? key,
|
|
||||||
String? value,
|
|
||||||
DateTime? updatedAt,
|
|
||||||
}) => i1.MetadataEntityData(
|
|
||||||
key: key ?? this.key,
|
|
||||||
value: value ?? this.value,
|
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
|
||||||
);
|
|
||||||
MetadataEntityData copyWithCompanion(i1.MetadataEntityCompanion data) {
|
|
||||||
return MetadataEntityData(
|
|
||||||
key: data.key.present ? data.key.value : this.key,
|
|
||||||
value: data.value.present ? data.value.value : this.value,
|
|
||||||
updatedAt: data.updatedAt.present ? data.updatedAt.value : this.updatedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return (StringBuffer('MetadataEntityData(')
|
|
||||||
..write('key: $key, ')
|
|
||||||
..write('value: $value, ')
|
|
||||||
..write('updatedAt: $updatedAt')
|
|
||||||
..write(')'))
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(key, value, updatedAt);
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) =>
|
|
||||||
identical(this, other) ||
|
|
||||||
(other is i1.MetadataEntityData &&
|
|
||||||
other.key == this.key &&
|
|
||||||
other.value == this.value &&
|
|
||||||
other.updatedAt == this.updatedAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataEntityCompanion
|
|
||||||
extends i0.UpdateCompanion<i1.MetadataEntityData> {
|
|
||||||
final i0.Value<String> key;
|
|
||||||
final i0.Value<String> value;
|
|
||||||
final i0.Value<DateTime> updatedAt;
|
|
||||||
const MetadataEntityCompanion({
|
|
||||||
this.key = const i0.Value.absent(),
|
|
||||||
this.value = const i0.Value.absent(),
|
|
||||||
this.updatedAt = const i0.Value.absent(),
|
|
||||||
});
|
|
||||||
MetadataEntityCompanion.insert({
|
|
||||||
required String key,
|
|
||||||
required String value,
|
|
||||||
this.updatedAt = const i0.Value.absent(),
|
|
||||||
}) : key = i0.Value(key),
|
|
||||||
value = i0.Value(value);
|
|
||||||
static i0.Insertable<i1.MetadataEntityData> custom({
|
|
||||||
i0.Expression<String>? key,
|
|
||||||
i0.Expression<String>? value,
|
|
||||||
i0.Expression<DateTime>? updatedAt,
|
|
||||||
}) {
|
|
||||||
return i0.RawValuesInsertable({
|
|
||||||
if (key != null) 'key': key,
|
|
||||||
if (value != null) 'value': value,
|
|
||||||
if (updatedAt != null) 'updated_at': updatedAt,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
i1.MetadataEntityCompanion copyWith({
|
|
||||||
i0.Value<String>? key,
|
|
||||||
i0.Value<String>? value,
|
|
||||||
i0.Value<DateTime>? updatedAt,
|
|
||||||
}) {
|
|
||||||
return i1.MetadataEntityCompanion(
|
|
||||||
key: key ?? this.key,
|
|
||||||
value: value ?? this.value,
|
|
||||||
updatedAt: updatedAt ?? this.updatedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
|
||||||
final map = <String, i0.Expression>{};
|
|
||||||
if (key.present) {
|
|
||||||
map['key'] = i0.Variable<String>(key.value);
|
|
||||||
}
|
|
||||||
if (value.present) {
|
|
||||||
map['value'] = i0.Variable<String>(value.value);
|
|
||||||
}
|
|
||||||
if (updatedAt.present) {
|
|
||||||
map['updated_at'] = i0.Variable<DateTime>(updatedAt.value);
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return (StringBuffer('MetadataEntityCompanion(')
|
|
||||||
..write('key: $key, ')
|
|
||||||
..write('value: $value, ')
|
|
||||||
..write('updatedAt: $updatedAt')
|
|
||||||
..write(')'))
|
|
||||||
.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -13,7 +13,6 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/partner.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/person.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
||||||
@@ -54,7 +53,6 @@ import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.da
|
|||||||
StoreEntity,
|
StoreEntity,
|
||||||
TrashedLocalAssetEntity,
|
TrashedLocalAssetEntity,
|
||||||
AssetEditEntity,
|
AssetEditEntity,
|
||||||
MetadataEntity,
|
|
||||||
],
|
],
|
||||||
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'},
|
||||||
)
|
)
|
||||||
@@ -86,7 +84,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 25;
|
int get schemaVersion => 24;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -252,9 +250,6 @@ class Drift extends $Drift {
|
|||||||
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
|
await customStatement('DROP INDEX IF EXISTS idx_remote_album_owner_id');
|
||||||
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
|
await m.alterTable(TableMigration(v24.remoteAlbumEntity));
|
||||||
},
|
},
|
||||||
from24To25: (m, v25) async {
|
|
||||||
await m.createTable(v25.metadata);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+4
-12
@@ -43,11 +43,9 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
|
|||||||
as i20;
|
as i20;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.drift.dart'
|
||||||
as i21;
|
as i21;
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
|
||||||
as i22;
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
as i23;
|
as i22;
|
||||||
import 'package:drift/internal/modular.dart' as i24;
|
import 'package:drift/internal/modular.dart' as i23;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -91,12 +89,9 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
.$TrashedLocalAssetEntityTable(this);
|
.$TrashedLocalAssetEntityTable(this);
|
||||||
late final i21.$AssetEditEntityTable assetEditEntity = i21
|
late final i21.$AssetEditEntityTable assetEditEntity = i21
|
||||||
.$AssetEditEntityTable(this);
|
.$AssetEditEntityTable(this);
|
||||||
late final i22.$MetadataEntityTable metadataEntity = i22.$MetadataEntityTable(
|
i22.MergedAssetDrift get mergedAssetDrift => i23.ReadDatabaseContainer(
|
||||||
this,
|
this,
|
||||||
);
|
).accessor<i22.MergedAssetDrift>(i22.MergedAssetDrift.new);
|
||||||
i23.MergedAssetDrift get mergedAssetDrift => i24.ReadDatabaseContainer(
|
|
||||||
this,
|
|
||||||
).accessor<i23.MergedAssetDrift>(i23.MergedAssetDrift.new);
|
|
||||||
@override
|
@override
|
||||||
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
Iterable<i0.TableInfo<i0.Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
allSchemaEntities.whereType<i0.TableInfo<i0.Table, Object?>>();
|
||||||
@@ -134,7 +129,6 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
storeEntity,
|
storeEntity,
|
||||||
trashedLocalAssetEntity,
|
trashedLocalAssetEntity,
|
||||||
assetEditEntity,
|
assetEditEntity,
|
||||||
metadataEntity,
|
|
||||||
i10.idxPartnerSharedWithId,
|
i10.idxPartnerSharedWithId,
|
||||||
i11.idxLatLng,
|
i11.idxLatLng,
|
||||||
i12.idxRemoteAlbumAssetAlbumAsset,
|
i12.idxRemoteAlbumAssetAlbumAsset,
|
||||||
@@ -395,6 +389,4 @@ class $DriftManager {
|
|||||||
);
|
);
|
||||||
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
|
i21.$$AssetEditEntityTableTableManager get assetEditEntity =>
|
||||||
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
i21.$$AssetEditEntityTableTableManager(_db, _db.assetEditEntity);
|
||||||
i22.$$MetadataEntityTableTableManager get metadataEntity =>
|
|
||||||
i22.$$MetadataEntityTableTableManager(_db, _db.metadataEntity);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12375,574 +12375,6 @@ class Shape48 extends i0.VersionedTable {
|
|||||||
columnsByName['order']! as i1.GeneratedColumn<int>;
|
columnsByName['order']! as i1.GeneratedColumn<int>;
|
||||||
}
|
}
|
||||||
|
|
||||||
final class Schema25 extends i0.VersionedSchema {
|
|
||||||
Schema25({required super.database}) : super(version: 25);
|
|
||||||
@override
|
|
||||||
late final List<i1.DatabaseSchemaEntity> entities = [
|
|
||||||
userEntity,
|
|
||||||
remoteAssetEntity,
|
|
||||||
stackEntity,
|
|
||||||
localAssetEntity,
|
|
||||||
remoteAlbumEntity,
|
|
||||||
localAlbumEntity,
|
|
||||||
localAlbumAssetEntity,
|
|
||||||
idxLocalAlbumAssetAlbumAsset,
|
|
||||||
idxLocalAssetChecksum,
|
|
||||||
idxLocalAssetCloudId,
|
|
||||||
idxStackPrimaryAssetId,
|
|
||||||
idxRemoteAssetOwnerChecksum,
|
|
||||||
uQRemoteAssetsOwnerChecksum,
|
|
||||||
uQRemoteAssetsOwnerLibraryChecksum,
|
|
||||||
idxRemoteAssetChecksum,
|
|
||||||
idxRemoteAssetStackId,
|
|
||||||
idxRemoteAssetLocalDateTimeDay,
|
|
||||||
idxRemoteAssetLocalDateTimeMonth,
|
|
||||||
authUserEntity,
|
|
||||||
userMetadataEntity,
|
|
||||||
partnerEntity,
|
|
||||||
remoteExifEntity,
|
|
||||||
remoteAlbumAssetEntity,
|
|
||||||
remoteAlbumUserEntity,
|
|
||||||
remoteAssetCloudIdEntity,
|
|
||||||
memoryEntity,
|
|
||||||
memoryAssetEntity,
|
|
||||||
personEntity,
|
|
||||||
assetFaceEntity,
|
|
||||||
storeEntity,
|
|
||||||
trashedLocalAssetEntity,
|
|
||||||
assetEditEntity,
|
|
||||||
metadata,
|
|
||||||
idxPartnerSharedWithId,
|
|
||||||
idxLatLng,
|
|
||||||
idxRemoteAlbumAssetAlbumAsset,
|
|
||||||
idxRemoteAssetCloudId,
|
|
||||||
idxPersonOwnerId,
|
|
||||||
idxAssetFacePersonId,
|
|
||||||
idxAssetFaceAssetId,
|
|
||||||
idxTrashedLocalAssetChecksum,
|
|
||||||
idxTrashedLocalAssetAlbum,
|
|
||||||
idxAssetEditAssetId,
|
|
||||||
];
|
|
||||||
late final Shape33 userEntity = Shape33(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'user_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_108,
|
|
||||||
_column_109,
|
|
||||||
_column_110,
|
|
||||||
_column_111,
|
|
||||||
_column_112,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape34 remoteAssetEntity = Shape34(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_108,
|
|
||||||
_column_113,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_116,
|
|
||||||
_column_117,
|
|
||||||
_column_118,
|
|
||||||
_column_107,
|
|
||||||
_column_119,
|
|
||||||
_column_120,
|
|
||||||
_column_121,
|
|
||||||
_column_122,
|
|
||||||
_column_123,
|
|
||||||
_column_124,
|
|
||||||
_column_125,
|
|
||||||
_column_126,
|
|
||||||
_column_127,
|
|
||||||
_column_128,
|
|
||||||
_column_129,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape35 stackEntity = Shape35(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'stack_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_121,
|
|
||||||
_column_130,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape36 localAssetEntity = Shape36(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'local_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_108,
|
|
||||||
_column_113,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_116,
|
|
||||||
_column_117,
|
|
||||||
_column_118,
|
|
||||||
_column_107,
|
|
||||||
_column_131,
|
|
||||||
_column_120,
|
|
||||||
_column_132,
|
|
||||||
_column_133,
|
|
||||||
_column_134,
|
|
||||||
_column_135,
|
|
||||||
_column_136,
|
|
||||||
_column_137,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape48 remoteAlbumEntity = Shape48(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_album_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_108,
|
|
||||||
_column_138,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_139,
|
|
||||||
_column_140,
|
|
||||||
_column_141,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape38 localAlbumEntity = Shape38(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'local_album_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_108,
|
|
||||||
_column_115,
|
|
||||||
_column_142,
|
|
||||||
_column_143,
|
|
||||||
_column_144,
|
|
||||||
_column_145,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape39 localAlbumAssetEntity = Shape39(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'local_album_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
|
||||||
columns: [_column_146, _column_147, _column_145],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
final i1.Index idxLocalAlbumAssetAlbumAsset = i1.Index(
|
|
||||||
'idx_local_album_asset_album_asset',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_local_album_asset_album_asset ON local_album_asset_entity (album_id, asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxLocalAssetChecksum = i1.Index(
|
|
||||||
'idx_local_asset_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index idxLocalAssetCloudId = i1.Index(
|
|
||||||
'idx_local_asset_cloud_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxStackPrimaryAssetId = i1.Index(
|
|
||||||
'idx_stack_primary_asset_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_stack_primary_asset_id ON stack_entity (primary_asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetOwnerChecksum = i1.Index(
|
|
||||||
'idx_remote_asset_owner_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_owner_checksum ON remote_asset_entity (owner_id, checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index uQRemoteAssetsOwnerChecksum = i1.Index(
|
|
||||||
'UQ_remote_assets_owner_checksum',
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_checksum ON remote_asset_entity (owner_id, checksum) WHERE(library_id IS NULL)',
|
|
||||||
);
|
|
||||||
final i1.Index uQRemoteAssetsOwnerLibraryChecksum = i1.Index(
|
|
||||||
'UQ_remote_assets_owner_library_checksum',
|
|
||||||
'CREATE UNIQUE INDEX IF NOT EXISTS UQ_remote_assets_owner_library_checksum ON remote_asset_entity (owner_id, library_id, checksum) WHERE(library_id IS NOT NULL)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetChecksum = i1.Index(
|
|
||||||
'idx_remote_asset_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_checksum ON remote_asset_entity (checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetStackId = i1.Index(
|
|
||||||
'idx_remote_asset_stack_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_stack_id ON remote_asset_entity (stack_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetLocalDateTimeDay = i1.Index(
|
|
||||||
'idx_remote_asset_local_date_time_day',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_day ON remote_asset_entity (STRFTIME(\'%Y-%m-%d\', local_date_time))',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetLocalDateTimeMonth = i1.Index(
|
|
||||||
'idx_remote_asset_local_date_time_month',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_local_date_time_month ON remote_asset_entity (STRFTIME(\'%Y-%m\', local_date_time))',
|
|
||||||
);
|
|
||||||
late final Shape40 authUserEntity = Shape40(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'auth_user_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_108,
|
|
||||||
_column_109,
|
|
||||||
_column_148,
|
|
||||||
_column_110,
|
|
||||||
_column_111,
|
|
||||||
_column_149,
|
|
||||||
_column_150,
|
|
||||||
_column_151,
|
|
||||||
_column_152,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape4 userMetadataEntity = Shape4(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'user_metadata_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(user_id, "key")'],
|
|
||||||
columns: [_column_153, _column_154, _column_155],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape41 partnerEntity = Shape41(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'partner_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(shared_by_id, shared_with_id)'],
|
|
||||||
columns: [_column_156, _column_157, _column_158],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape42 remoteExifEntity = Shape42(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_exif_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_159,
|
|
||||||
_column_160,
|
|
||||||
_column_161,
|
|
||||||
_column_162,
|
|
||||||
_column_163,
|
|
||||||
_column_164,
|
|
||||||
_column_117,
|
|
||||||
_column_116,
|
|
||||||
_column_165,
|
|
||||||
_column_166,
|
|
||||||
_column_167,
|
|
||||||
_column_168,
|
|
||||||
_column_135,
|
|
||||||
_column_136,
|
|
||||||
_column_169,
|
|
||||||
_column_170,
|
|
||||||
_column_171,
|
|
||||||
_column_172,
|
|
||||||
_column_173,
|
|
||||||
_column_174,
|
|
||||||
_column_175,
|
|
||||||
_column_176,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape7 remoteAlbumAssetEntity = Shape7(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_album_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id, album_id)'],
|
|
||||||
columns: [_column_159, _column_177],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape10 remoteAlbumUserEntity = Shape10(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_album_user_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(album_id, user_id)'],
|
|
||||||
columns: [_column_177, _column_153, _column_178],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape43 remoteAssetCloudIdEntity = Shape43(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'remote_asset_cloud_id_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_159,
|
|
||||||
_column_179,
|
|
||||||
_column_180,
|
|
||||||
_column_134,
|
|
||||||
_column_135,
|
|
||||||
_column_136,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape44 memoryEntity = Shape44(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'memory_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_124,
|
|
||||||
_column_121,
|
|
||||||
_column_113,
|
|
||||||
_column_181,
|
|
||||||
_column_182,
|
|
||||||
_column_183,
|
|
||||||
_column_184,
|
|
||||||
_column_185,
|
|
||||||
_column_186,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape12 memoryAssetEntity = Shape12(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'memory_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(asset_id, memory_id)'],
|
|
||||||
columns: [_column_159, _column_187],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape45 personEntity = Shape45(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'person_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_121,
|
|
||||||
_column_108,
|
|
||||||
_column_188,
|
|
||||||
_column_189,
|
|
||||||
_column_190,
|
|
||||||
_column_191,
|
|
||||||
_column_192,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape46 assetFaceEntity = Shape46(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'asset_face_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_159,
|
|
||||||
_column_193,
|
|
||||||
_column_194,
|
|
||||||
_column_195,
|
|
||||||
_column_196,
|
|
||||||
_column_197,
|
|
||||||
_column_198,
|
|
||||||
_column_199,
|
|
||||||
_column_200,
|
|
||||||
_column_201,
|
|
||||||
_column_124,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape18 storeEntity = Shape18(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'store_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [_column_202, _column_203, _column_204],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape47 trashedLocalAssetEntity = Shape47(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'trashed_local_asset_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id, album_id)'],
|
|
||||||
columns: [
|
|
||||||
_column_108,
|
|
||||||
_column_113,
|
|
||||||
_column_114,
|
|
||||||
_column_115,
|
|
||||||
_column_116,
|
|
||||||
_column_117,
|
|
||||||
_column_118,
|
|
||||||
_column_107,
|
|
||||||
_column_205,
|
|
||||||
_column_131,
|
|
||||||
_column_120,
|
|
||||||
_column_132,
|
|
||||||
_column_206,
|
|
||||||
_column_137,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape32 assetEditEntity = Shape32(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'asset_edit_entity',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY(id)'],
|
|
||||||
columns: [
|
|
||||||
_column_107,
|
|
||||||
_column_159,
|
|
||||||
_column_207,
|
|
||||||
_column_208,
|
|
||||||
_column_209,
|
|
||||||
],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
late final Shape49 metadata = Shape49(
|
|
||||||
source: i0.VersionedTable(
|
|
||||||
entityName: 'metadata',
|
|
||||||
withoutRowId: true,
|
|
||||||
isStrict: true,
|
|
||||||
tableConstraints: ['PRIMARY KEY("key")'],
|
|
||||||
columns: [_column_210, _column_211, _column_115],
|
|
||||||
attachedDatabase: database,
|
|
||||||
),
|
|
||||||
alias: null,
|
|
||||||
);
|
|
||||||
final i1.Index idxPartnerSharedWithId = i1.Index(
|
|
||||||
'idx_partner_shared_with_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_partner_shared_with_id ON partner_entity (shared_with_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxLatLng = i1.Index(
|
|
||||||
'idx_lat_lng',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_lat_lng ON remote_exif_entity (latitude, longitude)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAlbumAssetAlbumAsset = i1.Index(
|
|
||||||
'idx_remote_album_asset_album_asset',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_album_asset_album_asset ON remote_album_asset_entity (album_id, asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxRemoteAssetCloudId = i1.Index(
|
|
||||||
'idx_remote_asset_cloud_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_remote_asset_cloud_id ON remote_asset_cloud_id_entity (cloud_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxPersonOwnerId = i1.Index(
|
|
||||||
'idx_person_owner_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_person_owner_id ON person_entity (owner_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxAssetFacePersonId = i1.Index(
|
|
||||||
'idx_asset_face_person_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_person_id ON asset_face_entity (person_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxAssetFaceAssetId = i1.Index(
|
|
||||||
'idx_asset_face_asset_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_asset_face_asset_id ON asset_face_entity (asset_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxTrashedLocalAssetChecksum = i1.Index(
|
|
||||||
'idx_trashed_local_asset_checksum',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)',
|
|
||||||
);
|
|
||||||
final i1.Index idxTrashedLocalAssetAlbum = i1.Index(
|
|
||||||
'idx_trashed_local_asset_album',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)',
|
|
||||||
);
|
|
||||||
final i1.Index idxAssetEditAssetId = i1.Index(
|
|
||||||
'idx_asset_edit_asset_id',
|
|
||||||
'CREATE INDEX IF NOT EXISTS idx_asset_edit_asset_id ON asset_edit_entity (asset_id)',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Shape49 extends i0.VersionedTable {
|
|
||||||
Shape49({required super.source, required super.alias}) : super.aliased();
|
|
||||||
i1.GeneratedColumn<String> get key =>
|
|
||||||
columnsByName['key']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get value =>
|
|
||||||
columnsByName['value']! as i1.GeneratedColumn<String>;
|
|
||||||
i1.GeneratedColumn<String> get updatedAt =>
|
|
||||||
columnsByName['updated_at']! as i1.GeneratedColumn<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
i1.GeneratedColumn<String> _column_210(String aliasedName) =>
|
|
||||||
i1.GeneratedColumn<String>(
|
|
||||||
'key',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i1.DriftSqlType.string,
|
|
||||||
$customConstraints: 'NOT NULL',
|
|
||||||
);
|
|
||||||
i1.GeneratedColumn<String> _column_211(String aliasedName) =>
|
|
||||||
i1.GeneratedColumn<String>(
|
|
||||||
'value',
|
|
||||||
aliasedName,
|
|
||||||
false,
|
|
||||||
type: i1.DriftSqlType.string,
|
|
||||||
$customConstraints: 'NOT NULL',
|
|
||||||
);
|
|
||||||
i0.MigrationStepWithVersion migrationSteps({
|
i0.MigrationStepWithVersion migrationSteps({
|
||||||
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
|
||||||
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
|
||||||
@@ -12967,7 +12399,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
||||||
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||||
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||||
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
|
||||||
}) {
|
}) {
|
||||||
return (currentVersion, database) async {
|
return (currentVersion, database) async {
|
||||||
switch (currentVersion) {
|
switch (currentVersion) {
|
||||||
@@ -13086,11 +12517,6 @@ i0.MigrationStepWithVersion migrationSteps({
|
|||||||
final migrator = i1.Migrator(database, schema);
|
final migrator = i1.Migrator(database, schema);
|
||||||
await from23To24(migrator, schema);
|
await from23To24(migrator, schema);
|
||||||
return 24;
|
return 24;
|
||||||
case 24:
|
|
||||||
final schema = Schema25(database: database);
|
|
||||||
final migrator = i1.Migrator(database, schema);
|
|
||||||
await from24To25(migrator, schema);
|
|
||||||
return 25;
|
|
||||||
default:
|
default:
|
||||||
throw ArgumentError.value('Unknown migration from $currentVersion');
|
throw ArgumentError.value('Unknown migration from $currentVersion');
|
||||||
}
|
}
|
||||||
@@ -13121,7 +12547,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
required Future<void> Function(i1.Migrator m, Schema22 schema) from21To22,
|
||||||
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
required Future<void> Function(i1.Migrator m, Schema23 schema) from22To23,
|
||||||
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
required Future<void> Function(i1.Migrator m, Schema24 schema) from23To24,
|
||||||
required Future<void> Function(i1.Migrator m, Schema25 schema) from24To25,
|
|
||||||
}) => i0.VersionedSchema.stepByStepHelper(
|
}) => i0.VersionedSchema.stepByStepHelper(
|
||||||
step: migrationSteps(
|
step: migrationSteps(
|
||||||
from1To2: from1To2,
|
from1To2: from1To2,
|
||||||
@@ -13147,6 +12572,5 @@ i1.OnUpgrade stepByStep({
|
|||||||
from21To22: from21To22,
|
from21To22: from21To22,
|
||||||
from22To23: from22To23,
|
from22To23: from22To23,
|
||||||
from23To24: from23To24,
|
from23To24: from23To24,
|
||||||
from24To25: from24To25,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,165 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
|
||||||
|
|
||||||
abstract final class MetadataStore {
|
|
||||||
static MetadataRepository? _instance;
|
|
||||||
|
|
||||||
static MetadataRepository get instance {
|
|
||||||
final instance = _instance;
|
|
||||||
if (instance == null) {
|
|
||||||
throw StateError('MetadataRepository not initialized. Call ensureInitialized() first');
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<MetadataRepository> ensureInitialized(Drift db) async {
|
|
||||||
if (_instance == null) {
|
|
||||||
final instance = MetadataRepository(db);
|
|
||||||
await instance._hydrate();
|
|
||||||
_instance = instance;
|
|
||||||
}
|
|
||||||
return _instance!;
|
|
||||||
}
|
|
||||||
|
|
||||||
static AppConfig get appConfig => instance.appConfig;
|
|
||||||
static SystemConfig get systemConfig => instance.systemConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
class MetadataRepository extends DriftDatabaseRepository {
|
|
||||||
final Drift _db;
|
|
||||||
final Map<MetadataKey, Object> _cache = {};
|
|
||||||
|
|
||||||
MetadataRepository(this._db) : super(_db);
|
|
||||||
|
|
||||||
AppConfig _appConfig = const .new();
|
|
||||||
AppConfig get appConfig => _appConfig;
|
|
||||||
|
|
||||||
SystemConfig _systemConfig = const .new();
|
|
||||||
SystemConfig get systemConfig => _systemConfig;
|
|
||||||
|
|
||||||
Future<void> refresh() async {
|
|
||||||
_cache.clear();
|
|
||||||
_appConfig = const .new();
|
|
||||||
_systemConfig = const .new();
|
|
||||||
await _hydrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _hydrate() async => _hydrateCache(await _db.select(_db.metadataEntity).get());
|
|
||||||
|
|
||||||
T _read<T extends Object>(MetadataKey<T> key) => (_cache[key] as T?) ?? key.defaultValue;
|
|
||||||
|
|
||||||
Future<void> write<T extends Object, U extends T>(MetadataKey<T> key, U value) async {
|
|
||||||
if (_read(key) == value) return;
|
|
||||||
if (value == key.defaultValue) {
|
|
||||||
return delete(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
await _db
|
|
||||||
.into(_db.metadataEntity)
|
|
||||||
.insertOnConflictUpdate(
|
|
||||||
MetadataEntityCompanion.insert(key: key.key, value: key.encode(value), updatedAt: Value(DateTime.now())),
|
|
||||||
);
|
|
||||||
_updateCache(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> delete<T extends Object>(MetadataKey<T> key) async {
|
|
||||||
await (_db.delete(_db.metadataEntity)..where((t) => t.key.equals(key.key))).go();
|
|
||||||
_updateCache(key, key.defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
Stream<AppConfig> watchAppConfig() => _watchDomain(MetadataDomain.appConfig).distinct();
|
|
||||||
|
|
||||||
Stream<SystemConfig> watchSystemConfig() => _watchDomain(MetadataDomain.systemConfig).distinct();
|
|
||||||
|
|
||||||
Stream<T> _watchDomain<T extends Object>(MetadataDomain<T> domain) {
|
|
||||||
final query = _db.select(_db.metadataEntity)..where((t) => t.key.like('${domain.prefix}.%'));
|
|
||||||
return query.watch().map((rows) {
|
|
||||||
_hydrateCache(rows);
|
|
||||||
return switch (domain) {
|
|
||||||
.appConfig => _appConfig as T,
|
|
||||||
.systemConfig => _systemConfig as T,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _hydrateCache(List<MetadataEntityData> rows) {
|
|
||||||
final keyMap = MetadataKey.asKeyMap();
|
|
||||||
for (final row in rows) {
|
|
||||||
final key = keyMap[row.key];
|
|
||||||
if (key == null) continue;
|
|
||||||
_updateCache(key, key.decode(row.value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateCache<T extends Object>(MetadataKey<T> key, T value) {
|
|
||||||
if (_cache[key] == value) return;
|
|
||||||
_cache[key] = value;
|
|
||||||
|
|
||||||
switch (key.domain) {
|
|
||||||
case .appConfig:
|
|
||||||
_appConfig = _buildAppConfig();
|
|
||||||
case .systemConfig:
|
|
||||||
_systemConfig = _buildSystemConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig _buildAppConfig() => .new(
|
|
||||||
theme: .new(
|
|
||||||
mode: _read(.themeMode),
|
|
||||||
primaryColor: _read(.themePrimaryColor),
|
|
||||||
dynamicTheme: _read(.themeDynamic),
|
|
||||||
colorfulInterface: _read(.themeColorfulInterface),
|
|
||||||
),
|
|
||||||
cleanup: .new(
|
|
||||||
keepFavorites: _read(.cleanupKeepFavorites),
|
|
||||||
keepMediaType: _read(.cleanupKeepMediaType),
|
|
||||||
keepAlbumIds: _read(.cleanupKeepAlbumIds),
|
|
||||||
cutoffDaysAgo: _read(.cleanupCutoffDaysAgo),
|
|
||||||
defaultsInitialized: _read(.cleanupDefaultsInitialized),
|
|
||||||
),
|
|
||||||
map: .new(
|
|
||||||
relativeDays: _read(.mapRelativeDate),
|
|
||||||
favoritesOnly: _read(.mapShowFavoriteOnly),
|
|
||||||
includeArchived: _read(.mapIncludeArchived),
|
|
||||||
themeMode: _read(.mapThemeMode),
|
|
||||||
withPartners: _read(.mapWithPartners),
|
|
||||||
),
|
|
||||||
timeline: .new(
|
|
||||||
tilesPerRow: _read(.timelineTilesPerRow),
|
|
||||||
groupAssetsBy: _read(.timelineGroupAssetsBy),
|
|
||||||
storageIndicator: _read(.timelineStorageIndicator),
|
|
||||||
),
|
|
||||||
image: .new(preferRemote: _read(.imagePreferRemote), loadOriginal: _read(.imageLoadOriginal)),
|
|
||||||
viewer: .new(
|
|
||||||
loopVideo: _read(.viewerLoopVideo),
|
|
||||||
loadOriginalVideo: _read(.viewerLoadOriginalVideo),
|
|
||||||
autoPlayVideo: _read(.viewerAutoPlayVideo),
|
|
||||||
tapToNavigate: _read(.viewerTapToNavigate),
|
|
||||||
),
|
|
||||||
album: .new(sortMode: _read(.albumSortMode), isReverse: _read(.albumIsReverse), isGrid: _read(.albumIsGrid)),
|
|
||||||
backup: .new(
|
|
||||||
enabled: _read(.backupEnabled),
|
|
||||||
useCellularForVideos: _read(.backupUseCellularForVideos),
|
|
||||||
useCellularForPhotos: _read(.backupUseCellularForPhotos),
|
|
||||||
requireCharging: _read(.backupRequireCharging),
|
|
||||||
triggerDelay: _read(.backupTriggerDelay),
|
|
||||||
syncAlbums: _read(.backupSyncAlbums),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
SystemConfig _buildSystemConfig() => .new(
|
|
||||||
logLevel: _read(.logLevel),
|
|
||||||
network: .new(
|
|
||||||
autoEndpointSwitching: _read(.networkAutoEndpointSwitching),
|
|
||||||
preferredWifiName: _read(.networkPreferredWifiName).nullIfEmpty,
|
|
||||||
localEndpoint: _read(.networkLocalEndpoint).nullIfEmpty,
|
|
||||||
externalEndpointList: _read(.networkExternalEndpointList),
|
|
||||||
customHeaders: _read(.networkCustomHeaders),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -3,7 +3,9 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
import 'package:immich_mobile/domain/models/sync_event.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/utils/semver.dart';
|
import 'package:immich_mobile/utils/semver.dart';
|
||||||
@@ -36,6 +38,7 @@ class SyncApiRepository {
|
|||||||
|
|
||||||
final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'};
|
final headers = {'Content-Type': 'application/json', 'Accept': 'application/jsonlines+json'};
|
||||||
|
|
||||||
|
final shouldReset = Store.get(StoreKey.shouldResetSync, false);
|
||||||
final request = http.Request('POST', Uri.parse(endpoint));
|
final request = http.Request('POST', Uri.parse(endpoint));
|
||||||
request.headers.addAll(headers);
|
request.headers.addAll(headers);
|
||||||
request.body = jsonEncode(
|
request.body = jsonEncode(
|
||||||
@@ -74,6 +77,7 @@ class SyncApiRepository {
|
|||||||
? SyncRequestType.assetFacesV2
|
? SyncRequestType.assetFacesV2
|
||||||
: SyncRequestType.assetFacesV1,
|
: SyncRequestType.assetFacesV1,
|
||||||
],
|
],
|
||||||
|
reset: shouldReset,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -97,6 +101,9 @@ class SyncApiRepository {
|
|||||||
throw ApiException(response.statusCode, 'Failed to get sync stream: $errorBody');
|
throw ApiException(response.statusCode, 'Failed to get sync stream: $errorBody');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset after successful stream start
|
||||||
|
await Store.put(StoreKey.shouldResetSync, false);
|
||||||
|
|
||||||
await for (final chunk in response.stream.transform(utf8.decoder)) {
|
await for (final chunk in response.stream.transform(utf8.decoder)) {
|
||||||
if (shouldAbort) {
|
if (shouldAbort) {
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
|||||||
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
import 'package:immich_mobile/providers/app_life_cycle.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/share_intent_upload.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/locale_provider.dart';
|
import 'package:immich_mobile/providers/locale_provider.dart';
|
||||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||||
@@ -54,7 +53,7 @@ void main() async {
|
|||||||
await initApp();
|
await initApp();
|
||||||
// Warm-up isolate pool for worker manager
|
// Warm-up isolate pool for worker manager
|
||||||
await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
|
await workerManagerPatch.init(dynamicSpawning: true, isolatesCount: max(Platform.numberOfProcessors - 1, 5));
|
||||||
await migrateDatabaseIfNeeded(drift);
|
await migrateDatabaseIfNeeded();
|
||||||
|
|
||||||
runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget()));
|
runApp(ProviderScope(overrides: [driftProvider.overrideWith(driftOverride(drift))], child: const MainWidget()));
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
@@ -242,7 +241,7 @@ class ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserve
|
|||||||
localizationsDelegates: context.localizationDelegates,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
supportedLocales: context.supportedLocales,
|
supportedLocales: context.supportedLocales,
|
||||||
locale: context.locale,
|
locale: context.locale,
|
||||||
themeMode: ref.watch(appConfigProvider.select((config) => config.theme.mode)),
|
themeMode: ref.watch(immichThemeModeProvider),
|
||||||
darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: context.locale),
|
darkTheme: getThemeData(colorScheme: immichTheme.dark, locale: context.locale),
|
||||||
theme: getThemeData(colorScheme: immichTheme.light, locale: context.locale),
|
theme: getThemeData(colorScheme: immichTheme.light, locale: context.locale),
|
||||||
builder: (context, child) => ImmichTranslationProvider(
|
builder: (context, child) => ImmichTranslationProvider(
|
||||||
|
|||||||
@@ -8,13 +8,13 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
|||||||
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
|
import 'package:immich_mobile/widgets/backup/drift_album_info_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/common/search_field.dart';
|
import 'package:immich_mobile/widgets/common/search_field.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -43,7 +43,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
_searchController = TextEditingController();
|
_searchController = TextEditingController();
|
||||||
_searchFocusNode = FocusNode();
|
_searchFocusNode = FocusNode();
|
||||||
|
|
||||||
_enableSyncUploadAlbum.value = ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
_enableSyncUploadAlbum.value = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||||
ref.read(backupAlbumProvider.notifier).getAll();
|
ref.read(backupAlbumProvider.notifier).getAll();
|
||||||
|
|
||||||
_initialTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
_initialTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||||
@@ -55,7 +55,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final enableSyncUploadAlbum = ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final enableSyncUploadAlbum = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||||
final selectedAlbums = ref
|
final selectedAlbums = ref
|
||||||
.read(backupAlbumProvider)
|
.read(backupAlbumProvider)
|
||||||
.where((a) => a.backupSelection == BackupSelection.selected)
|
.where((a) => a.backupSelection == BackupSelection.selected)
|
||||||
@@ -101,7 +101,7 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isBackupEnabled = MetadataStore.appConfig.backup.enabled;
|
final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
|
await ref.read(driftBackupProvider.notifier).getBackupStatus(user.id);
|
||||||
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
final currentTotalAssetCount = ref.read(driftBackupProvider.select((p) => p.totalCount));
|
||||||
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
|
final totalChanged = currentTotalAssetCount != _initialTotalAssetCount;
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import 'dart:async';
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
|
import 'package:immich_mobile/widgets/settings/backup_settings/drift_backup_settings.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
@@ -19,20 +21,18 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
bool hasPopped = false;
|
bool hasPopped = false;
|
||||||
final previousBackup = ref.read(metadataProvider).appConfig.backup;
|
final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
||||||
final previousCellularForVideos = previousBackup.useCellularForVideos;
|
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
||||||
final previousCellularForPhotos = previousBackup.useCellularForPhotos;
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
onPopInvokedWithResult: (didPop, result) async {
|
onPopInvokedWithResult: (didPop, result) async {
|
||||||
// There is an issue with Flutter where the pop event
|
// There is an issue with Flutter where the pop event
|
||||||
// can be triggered multiple times, so we guard it with _hasPopped
|
// can be triggered multiple times, so we guard it with _hasPopped
|
||||||
|
|
||||||
final currentBackup = ref.read(metadataProvider).appConfig.backup;
|
final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
||||||
final currentCellularForVideos = currentBackup.useCellularForVideos;
|
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
||||||
final currentCellularForPhotos = currentBackup.useCellularForPhotos;
|
|
||||||
|
|
||||||
if (currentCellularForVideos == previousCellularForVideos &&
|
if (currentWifiReqForVideos == previousWifiReqForVideos &&
|
||||||
currentCellularForPhotos == previousCellularForPhotos) {
|
currentWifiReqForPhotos == previousWifiReqForPhotos) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
await ref.read(driftBackupProvider.notifier).getBackupStatus(currentUser.id);
|
||||||
final isBackupEnabled = MetadataStore.appConfig.backup.enabled;
|
final isBackupEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
if (!isBackupEnabled) {
|
if (!isBackupEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
|
|
||||||
class SettingsHeader {
|
class SettingsHeader {
|
||||||
String key = "";
|
String key = "";
|
||||||
@@ -22,14 +24,17 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
final headers = useState<List<SettingsHeader>>([]);
|
final headers = useState<List<SettingsHeader>>([]);
|
||||||
final setInitialHeaders = useState(false);
|
final setInitialHeaders = useState(false);
|
||||||
|
|
||||||
final storedHeaders = ref.read(metadataProvider).systemConfig.network.customHeaders;
|
var headersStr = Store.get(StoreKey.customHeaders, "");
|
||||||
if (!setInitialHeaders.value) {
|
if (!setInitialHeaders.value) {
|
||||||
storedHeaders.forEach((k, v) {
|
if (headersStr.isNotEmpty) {
|
||||||
|
var customHeaders = jsonDecode(headersStr) as Map;
|
||||||
|
customHeaders.forEach((k, v) {
|
||||||
final header = SettingsHeader();
|
final header = SettingsHeader();
|
||||||
header.key = k;
|
header.key = k;
|
||||||
header.value = v;
|
header.value = v;
|
||||||
headers.value.add(header);
|
headers.value.add(header);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// add first one to help the user
|
// add first one to help the user
|
||||||
if (headers.value.isEmpty) {
|
if (headers.value.isEmpty) {
|
||||||
@@ -83,17 +88,18 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveHeaders(WidgetRef ref, List<SettingsHeader> headers) async {
|
saveHeaders(WidgetRef ref, List<SettingsHeader> headers) async {
|
||||||
final headersMap = <String, String>{};
|
final headersMap = {};
|
||||||
for (final header in headers) {
|
for (var header in headers) {
|
||||||
final key = header.key.trim();
|
final key = header.key.trim();
|
||||||
final value = header.value.trim();
|
final value = header.value.trim();
|
||||||
|
|
||||||
if (key.isEmpty || value.isEmpty) continue;
|
if (key.isEmpty || value.isEmpty) continue;
|
||||||
headersMap[key] = value;
|
headersMap[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
final apiService = ref.read(apiServiceProvider);
|
var encoded = jsonEncode(headersMap);
|
||||||
await ref.read(metadataProvider).write(MetadataKey.networkCustomHeaders, headersMap);
|
await Store.put(StoreKey.customHeaders, encoded);
|
||||||
await apiService.updateHeaders();
|
await ref.read(apiServiceProvider).updateHeaders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,13 +6,12 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.dart';
|
import 'package:immich_mobile/generated/translations.g.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
@@ -36,7 +35,7 @@ class BootstrapErrorWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext _) {
|
Widget build(BuildContext _) {
|
||||||
final immichTheme = MetadataKey.themePrimaryColor.defaultValue.themeOfPreset;
|
final immichTheme = defaultColorPreset.themeOfPreset;
|
||||||
|
|
||||||
return EasyLocalization(
|
return EasyLocalization(
|
||||||
supportedLocales: locales.values.toList(),
|
supportedLocales: locales.values.toList(),
|
||||||
@@ -341,7 +340,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
await backgroundManager.hashAssets();
|
await backgroundManager.hashAssets();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MetadataStore.appConfig.backup.syncAlbums) {
|
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||||
await backgroundManager.syncLinkedAlbum();
|
await backgroundManager.syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -370,7 +369,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
||||||
final isEnableBackup = MetadataStore.appConfig.backup.enabled;
|
final isEnableBackup = Store.get(StoreKey.enableBackup, false);
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|||||||
@@ -15,15 +15,15 @@ import 'package:immich_mobile/models/albums/album_search.model.dart';
|
|||||||
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
import 'package:immich_mobile/presentation/widgets/album/album_tile.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/album/new_album_name_modal.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/utils/album_filter.utils.dart';
|
import 'package:immich_mobile/utils/album_filter.utils.dart';
|
||||||
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
import 'package:immich_mobile/widgets/common/confirm_dialog.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
@@ -58,11 +58,19 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final albumConfig = ref.read(metadataProvider).appConfig.album;
|
final appSettings = ref.read(appSettingsServiceProvider);
|
||||||
|
final savedSortMode = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortOrder);
|
||||||
|
final savedIsReverse = appSettings.getSetting(AppSettingsEnum.selectedAlbumSortReverse);
|
||||||
|
final savedIsGrid = appSettings.getSetting(AppSettingsEnum.albumGridView);
|
||||||
|
|
||||||
|
final albumSortMode = AlbumSortMode.values.firstWhere(
|
||||||
|
(e) => e.storeIndex == savedSortMode,
|
||||||
|
orElse: () => AlbumSortMode.lastModified,
|
||||||
|
);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse);
|
sort = AlbumSort(mode: albumSortMode, isReverse: savedIsReverse);
|
||||||
isGrid = albumConfig.isGrid;
|
isGrid = savedIsGrid;
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.read(remoteAlbumProvider.notifier).refresh();
|
ref.read(remoteAlbumProvider.notifier).refresh();
|
||||||
@@ -94,7 +102,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
isGrid = !isGrid;
|
isGrid = !isGrid;
|
||||||
});
|
});
|
||||||
ref.read(metadataProvider).write(MetadataKey.albumIsGrid, isGrid);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.albumGridView, isGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFilter(QuickFilterMode mode) {
|
void changeFilter(QuickFilterMode mode) {
|
||||||
@@ -110,9 +118,9 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
this.sort = sort;
|
this.sort = sort;
|
||||||
});
|
});
|
||||||
|
|
||||||
final metadata = ref.read(metadataProvider);
|
final appSettings = ref.read(appSettingsServiceProvider);
|
||||||
await metadata.write(MetadataKey.albumSortMode, sort.mode);
|
await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, sort.mode.storeIndex);
|
||||||
await metadata.write(MetadataKey.albumIsReverse, sort.isReverse);
|
await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortReverse, sort.isReverse);
|
||||||
|
|
||||||
await sortAlbums();
|
await sortAlbums();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart'
|
|||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/video_viewer.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
import 'package:immich_mobile/widgets/common/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
import 'package:immich_mobile/widgets/photo_view/photo_view.dart';
|
||||||
@@ -216,7 +217,7 @@ class _AssetPageState extends ConsumerState<AssetPage> {
|
|||||||
void _onTapUp(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue) {
|
void _onTapUp(BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue) {
|
||||||
if (_showingDetails || _dragStart != null) return;
|
if (_showingDetails || _dragStart != null) return;
|
||||||
|
|
||||||
final tapToNavigate = ref.read(metadataProvider).appConfig.viewer.tapToNavigate;
|
final tapToNavigate = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.tapToNavigate);
|
||||||
if (!tapToNavigate) {
|
if (!tapToNavigate) {
|
||||||
_viewer.toggleControls();
|
_viewer.toggleControls();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,17 +3,21 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/setting.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
import 'package:immich_mobile/providers/asset_viewer/video_player_provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:native_video_player/native_video_player.dart';
|
import 'package:native_video_player/native_video_player.dart';
|
||||||
|
|
||||||
@@ -116,7 +120,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
final remoteId = (videoAsset as RemoteAsset).id;
|
final remoteId = (videoAsset as RemoteAsset).id;
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
final isOriginalVideo = ref.read(metadataProvider).appConfig.viewer.loadOriginalVideo;
|
final isOriginalVideo = ref.read(settingsProvider).get<bool>(Setting.loadOriginalVideo);
|
||||||
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
final String postfixUrl = isOriginalVideo ? 'original' : 'video/playback';
|
||||||
final String videoUrl = videoAsset.livePhotoVideoId != null
|
final String videoUrl = videoAsset.livePhotoVideoId != null
|
||||||
? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl'
|
? '$serverEndpoint/assets/${videoAsset.livePhotoVideoId}/$postfixUrl'
|
||||||
@@ -143,7 +147,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
|
|
||||||
if (ref.read(assetViewerProvider).showingDetails) return;
|
if (ref.read(assetViewerProvider).showingDetails) return;
|
||||||
|
|
||||||
final autoPlayVideo = ref.read(metadataProvider).appConfig.viewer.autoPlayVideo;
|
final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo);
|
||||||
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play();
|
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +186,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
if (source == null || !mounted) return;
|
if (source == null || !mounted) return;
|
||||||
|
|
||||||
await _notifier.load(source);
|
await _notifier.load(source);
|
||||||
final loopVideo = ref.read(metadataProvider).appConfig.viewer.loopVideo;
|
final loopVideo = ref.read(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo);
|
||||||
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
|
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
|
||||||
await _notifier.setVolume(1);
|
await _notifier.setVolume(1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
|
||||||
class BackupToggleButton extends ConsumerStatefulWidget {
|
class BackupToggleButton extends ConsumerStatefulWidget {
|
||||||
final VoidCallback onStart;
|
final VoidCallback onStart;
|
||||||
@@ -31,7 +31,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
end: 1,
|
end: 1,
|
||||||
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
|
).animate(CurvedAnimation(parent: _animationController, curve: Curves.easeInOut));
|
||||||
|
|
||||||
_isEnabled = ref.read(metadataProvider).appConfig.backup.enabled;
|
_isEnabled = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,7 +41,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onToggle(bool value) async {
|
Future<void> _onToggle(bool value) async {
|
||||||
await ref.read(metadataProvider).write(MetadataKey.backupEnabled, value);
|
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isEnabled = value;
|
_isEnabled = value;
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ import 'dart:ui' as ui;
|
|||||||
import 'package:async/async.dart';
|
import 'package:async/async.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/setting.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/local_image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/remote_image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||||
@@ -188,4 +189,4 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
||||||
asset.hasLocal && (!asset.hasRemote || !MetadataStore.appConfig.image.preferRemote) && !asset.isEdited;
|
asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage)) && !asset.isEdited;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
||||||
@@ -102,7 +103,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loadOriginal = MetadataStore.appConfig.image.loadOriginal;
|
final loadOriginal = Store.get(StoreKey.loadOriginal, false);
|
||||||
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
final devicePixelRatio = PlatformDispatcher.instance.views.first.devicePixelRatio;
|
||||||
var request = this.request = LocalImageRequest(
|
var request = this.request = LocalImageRequest(
|
||||||
localId: key.id,
|
localId: key.id,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/painting.dart';
|
import 'package:flutter/painting.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/services/setting.service.dart';
|
||||||
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
import 'package:immich_mobile/infrastructure/loaders/image_request.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/animated_image_stream_completer.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
import 'package:immich_mobile/presentation/widgets/images/image_provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
import 'package:immich_mobile/presentation/widgets/images/one_frame_multi_image_stream_completer.dart';
|
||||||
@@ -120,7 +121,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
edited: key.edited,
|
edited: key.edited,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final loadOriginal = assetType == AssetType.image && MetadataStore.appConfig.image.loadOriginal;
|
final loadOriginal = assetType == AssetType.image && AppSetting.get(Setting.loadOriginal);
|
||||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (!loadOriginal) {
|
||||||
|
|||||||
@@ -2,14 +2,15 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/duration_extensions.dart';
|
import 'package:immich_mobile/extensions/duration_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
|
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
|
|
||||||
class ThumbnailTile extends ConsumerStatefulWidget {
|
class ThumbnailTile extends ConsumerStatefulWidget {
|
||||||
@@ -60,7 +61,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final bool storageIndicator =
|
final bool storageIndicator =
|
||||||
ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)) && widget.showStorageIndicator;
|
ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))) && widget.showStorageIndicator;
|
||||||
|
|
||||||
if (!isCurrentAsset) {
|
if (!isCurrentAsset) {
|
||||||
_hideIndicators = false;
|
_hideIndicators = false;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/map.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/map.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
import 'package:immich_mobile/providers/map/map_state.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:maplibre_gl/maplibre_gl.dart';
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
|
||||||
class MapState {
|
class MapState {
|
||||||
@@ -81,38 +81,38 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void switchFavoriteOnly(bool isFavoriteOnly) {
|
void switchFavoriteOnly(bool isFavoriteOnly) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly);
|
||||||
state = state.copyWith(onlyFavorites: isFavoriteOnly);
|
state = state.copyWith(onlyFavorites: isFavoriteOnly);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchIncludeArchived(bool isIncludeArchived) {
|
void switchIncludeArchived(bool isIncludeArchived) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived);
|
||||||
state = state.copyWith(includeArchived: isIncludeArchived);
|
state = state.copyWith(includeArchived: isIncludeArchived);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchWithPartners(bool isWithPartners) {
|
void switchWithPartners(bool isWithPartners) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners);
|
||||||
state = state.copyWith(withPartners: isWithPartners);
|
state = state.copyWith(withPartners: isWithPartners);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRelativeTime(int relativeDays) {
|
void setRelativeTime(int relativeDays) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeDays);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeDays);
|
||||||
state = state.copyWith(relativeDays: relativeDays);
|
state = state.copyWith(relativeDays: relativeDays);
|
||||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MapState build() {
|
MapState build() {
|
||||||
final mapConfig = ref.read(appConfigProvider.select((config) => config.map));
|
final appSettingsService = ref.read(appSettingsServiceProvider);
|
||||||
return MapState(
|
return MapState(
|
||||||
themeMode: mapConfig.themeMode,
|
themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)],
|
||||||
onlyFavorites: mapConfig.favoritesOnly,
|
onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly),
|
||||||
includeArchived: mapConfig.includeArchived,
|
includeArchived: appSettingsService.getSetting(AppSettingsEnum.mapIncludeArchived),
|
||||||
withPartners: mapConfig.withPartners,
|
withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners),
|
||||||
relativeDays: mapConfig.relativeDays,
|
relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate),
|
||||||
bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)),
|
bounds: LatLngBounds(northeast: const LatLng(0, 0), southwest: const LatLng(0, 0)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/constants.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/fixed/segment_builder.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
|
|
||||||
class TimelineArgs {
|
class TimelineArgs {
|
||||||
@@ -92,7 +93,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref)
|
|||||||
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
|
final availableTileWidth = args.maxWidth - (spacing * (columnCount - 1));
|
||||||
final tileExtent = math.max(0, availableTileWidth) / columnCount;
|
final tileExtent = math.max(0, availableTileWidth) / columnCount;
|
||||||
|
|
||||||
final groupBy = args.groupBy ?? ref.watch(appConfigProvider.select((config) => config.timeline.groupAssetsBy));
|
final groupBy = args.groupBy ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
|
||||||
|
|
||||||
final timelineService = ref.watch(timelineServiceProvider);
|
final timelineService = ref.watch(timelineServiceProvider);
|
||||||
yield* timelineService.watchBuckets().map((buckets) {
|
yield* timelineService.watchBuckets().map((buckets) {
|
||||||
@@ -101,7 +102,7 @@ final timelineSegmentProvider = StreamProvider.autoDispose<List<Segment>>((ref)
|
|||||||
tileHeight: tileExtent,
|
tileHeight: tileExtent,
|
||||||
columnCount: columnCount,
|
columnCount: columnCount,
|
||||||
spacing: spacing,
|
spacing: spacing,
|
||||||
groupBy: groupBy!,
|
groupBy: groupBy,
|
||||||
).generate();
|
).generate();
|
||||||
});
|
});
|
||||||
}, dependencies: [timelineServiceProvider, timelineArgsProvider]);
|
}, dependencies: [timelineServiceProvider, timelineArgsProvider]);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||||
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
||||||
@@ -22,8 +22,8 @@ import 'package:immich_mobile/presentation/widgets/timeline/scrubber.widget.dart
|
|||||||
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/segment.model.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline_drag_region.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
import 'package:immich_mobile/widgets/common/immich_sliver_app_bar.dart';
|
||||||
@@ -74,7 +74,7 @@ class Timeline extends StatelessWidget {
|
|||||||
(ref) => TimelineArgs(
|
(ref) => TimelineArgs(
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
maxHeight: constraints.maxHeight,
|
maxHeight: constraints.maxHeight,
|
||||||
columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.tilesPerRow)),
|
columnCount: ref.watch(settingsProvider.select((s) => s.get(Setting.tilesPerRow))),
|
||||||
showStorageIndicator: showStorageIndicator,
|
showStorageIndicator: showStorageIndicator,
|
||||||
withStack: withStack,
|
withStack: withStack,
|
||||||
groupBy: groupBy,
|
groupBy: groupBy,
|
||||||
@@ -161,7 +161,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
_scrollController = ScrollController(onAttach: _restoreAssetPosition);
|
_scrollController = ScrollController(onAttach: _restoreAssetPosition);
|
||||||
_eventSubscription = EventStream.shared.listen(_onEvent);
|
_eventSubscription = EventStream.shared.listen(_onEvent);
|
||||||
|
|
||||||
final currentTilesPerRow = ref.read(appConfigProvider.select((config) => config.timeline.tilesPerRow));
|
final currentTilesPerRow = ref.read(settingsProvider).get(Setting.tilesPerRow);
|
||||||
_perRow = currentTilesPerRow;
|
_perRow = currentTilesPerRow;
|
||||||
_scaleFactor = 7.0 - _perRow;
|
_scaleFactor = 7.0 - _perRow;
|
||||||
_baseScaleFactor = _scaleFactor;
|
_baseScaleFactor = _scaleFactor;
|
||||||
@@ -453,7 +453,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
_restoreAssetIndex = targetAssetIndex;
|
_restoreAssetIndex = targetAssetIndex;
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow);
|
ref.read(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,15 +5,16 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
|||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
enum AppLifeCycleEnum { active, inactive, paused, resumed, detached, hidden }
|
enum AppLifeCycleEnum { active, inactive, paused, resumed, detached, hidden }
|
||||||
@@ -105,7 +106,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
await Future.delayed(const Duration(milliseconds: 500));
|
await Future.delayed(const Duration(milliseconds: 500));
|
||||||
|
|
||||||
final backgroundManager = _ref.read(backgroundSyncProvider);
|
final backgroundManager = _ref.read(backgroundSyncProvider);
|
||||||
final isAlbumLinkedSyncEnable = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final isAlbumLinkedSyncEnable = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bool syncSuccess = false;
|
bool syncSuccess = false;
|
||||||
@@ -135,7 +136,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup() async {
|
Future<void> _resumeBackup() async {
|
||||||
final isEnableBackup = _ref.read(metadataProvider).appConfig.backup.enabled;
|
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|||||||
@@ -1,26 +1,22 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter_udid/flutter_udid.dart';
|
import 'package:flutter_udid/flutter_udid.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/user.model.dart';
|
import 'package:immich_mobile/domain/models/user.model.dart';
|
||||||
import 'package:immich_mobile/domain/services/user.service.dart';
|
import 'package:immich_mobile/domain/services/user.service.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
import 'package:immich_mobile/models/auth/auth_state.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/auth.service.dart';
|
import 'package:immich_mobile/services/auth.service.dart';
|
||||||
import 'package:immich_mobile/services/background_upload.service.dart';
|
|
||||||
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||||
import 'package:immich_mobile/services/secure_storage.service.dart';
|
import 'package:immich_mobile/services/secure_storage.service.dart';
|
||||||
|
import 'package:immich_mobile/services/background_upload.service.dart';
|
||||||
import 'package:immich_mobile/services/widget.service.dart';
|
import 'package:immich_mobile/services/widget.service.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
import 'package:immich_mobile/utils/hash.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@@ -131,8 +127,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
await _apiService.updateHeaders();
|
await _apiService.updateHeaders();
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
final headerMap = MetadataStore.systemConfig.network.customHeaders;
|
final customHeaders = Store.tryGet(StoreKey.customHeaders);
|
||||||
final customHeaders = headerMap.isEmpty ? null : jsonEncode(headerMap);
|
|
||||||
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
await _widgetService.writeCredentials(serverEndpoint, accessToken, customHeaders);
|
||||||
|
|
||||||
// Get the deviceid from the store if it exists, otherwise generate a new one
|
// Get the deviceid from the store if it exists, otherwise generate a new one
|
||||||
@@ -149,6 +144,7 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
// Due to the flow of the code, this will always happen on first login
|
// Due to the flow of the code, this will always happen on first login
|
||||||
user = serverUser;
|
user = serverUser;
|
||||||
await Store.put(StoreKey.deviceId, deviceId);
|
await Store.put(StoreKey.deviceId, deviceId);
|
||||||
|
await Store.put(StoreKey.deviceIdHash, fastHash(deviceId));
|
||||||
}
|
}
|
||||||
} on ApiException catch (error, stackTrace) {
|
} on ApiException catch (error, stackTrace) {
|
||||||
if (error.code == 401) {
|
if (error.code == 401) {
|
||||||
@@ -180,19 +176,19 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveWifiName(String wifiName) async {
|
Future<void> saveWifiName(String wifiName) async {
|
||||||
await _ref.read(metadataProvider).write(MetadataKey.networkPreferredWifiName, wifiName);
|
await Store.put(StoreKey.preferredWifiName, wifiName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveLocalEndpoint(String url) async {
|
Future<void> saveLocalEndpoint(String url) async {
|
||||||
await _ref.read(metadataProvider).write(MetadataKey.networkLocalEndpoint, url);
|
await Store.put(StoreKey.localEndpoint, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getSavedWifiName() {
|
String? getSavedWifiName() {
|
||||||
return _ref.read(metadataProvider).systemConfig.network.preferredWifiName;
|
return Store.tryGet(StoreKey.preferredWifiName);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getSavedLocalEndpoint() {
|
String? getSavedLocalEndpoint() {
|
||||||
return _ref.read(metadataProvider).systemConfig.network.localEndpoint;
|
return Store.tryGet(StoreKey.localEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current server endpoint (with /api) URL from the store
|
/// Returns the current server endpoint (with /api) URL from the store
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
import 'package:immich_mobile/constants/enums.dart';
|
||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/services/cleanup.service.dart';
|
import 'package:immich_mobile/services/cleanup.service.dart';
|
||||||
|
|
||||||
class CleanupState {
|
class CleanupState {
|
||||||
@@ -54,25 +54,27 @@ final cleanupProvider = StateNotifierProvider<CleanupNotifier, CleanupState>((re
|
|||||||
return CleanupNotifier(
|
return CleanupNotifier(
|
||||||
ref.watch(cleanupServiceProvider),
|
ref.watch(cleanupServiceProvider),
|
||||||
ref.watch(currentUserProvider)?.id,
|
ref.watch(currentUserProvider)?.id,
|
||||||
ref.watch(metadataProvider),
|
ref.watch(appSettingsServiceProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class CleanupNotifier extends StateNotifier<CleanupState> {
|
class CleanupNotifier extends StateNotifier<CleanupState> {
|
||||||
final CleanupService _cleanupService;
|
final CleanupService _cleanupService;
|
||||||
final String? _userId;
|
final String? _userId;
|
||||||
final MetadataRepository _metadataRepository;
|
final AppSettingsService _appSettingsService;
|
||||||
|
|
||||||
CleanupNotifier(this._cleanupService, this._userId, this._metadataRepository) : super(const CleanupState()) {
|
CleanupNotifier(this._cleanupService, this._userId, this._appSettingsService) : super(const CleanupState()) {
|
||||||
_loadPersistedSettings();
|
_loadPersistedSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadPersistedSettings() {
|
void _loadPersistedSettings() {
|
||||||
final cleanup = _metadataRepository.appConfig.cleanup;
|
final keepFavorites = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepFavorites);
|
||||||
final keepFavorites = cleanup.keepFavorites;
|
final keepMediaTypeIndex = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepMediaType);
|
||||||
final keepMediaType = cleanup.keepMediaType;
|
final keepAlbumIdsString = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepAlbumIds);
|
||||||
final keepAlbumIds = cleanup.keepAlbumIds.toSet();
|
final cutoffDaysAgo = _appSettingsService.getSetting(AppSettingsEnum.cleanupCutoffDaysAgo);
|
||||||
final cutoffDaysAgo = cleanup.cutoffDaysAgo;
|
|
||||||
|
final keepMediaType = AssetKeepType.values[keepMediaTypeIndex.clamp(0, AssetKeepType.values.length - 1)];
|
||||||
|
final keepAlbumIds = keepAlbumIdsString.isEmpty ? <String>{} : keepAlbumIdsString.split(',').toSet();
|
||||||
final selectedDate = cutoffDaysAgo >= 0 ? DateTime.now().subtract(Duration(days: cutoffDaysAgo)) : null;
|
final selectedDate = cutoffDaysAgo >= 0 ? DateTime.now().subtract(Duration(days: cutoffDaysAgo)) : null;
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
@@ -87,18 +89,18 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
state = state.copyWith(selectedDate: date, assetsToDelete: []);
|
state = state.copyWith(selectedDate: date, assetsToDelete: []);
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
final daysAgo = DateTime.now().difference(date).inDays;
|
final daysAgo = DateTime.now().difference(date).inDays;
|
||||||
_metadataRepository.write(.cleanupCutoffDaysAgo, daysAgo);
|
_appSettingsService.setSetting(AppSettingsEnum.cleanupCutoffDaysAgo, daysAgo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeepMediaType(AssetKeepType keepMediaType) {
|
void setKeepMediaType(AssetKeepType keepMediaType) {
|
||||||
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
|
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
|
||||||
_metadataRepository.write(.cleanupKeepMediaType, keepMediaType);
|
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepMediaType, keepMediaType.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeepFavorites(bool keepFavorites) {
|
void setKeepFavorites(bool keepFavorites) {
|
||||||
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
|
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
|
||||||
_metadataRepository.write(.cleanupKeepFavorites, keepFavorites);
|
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepFavorites, keepFavorites);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleKeepAlbum(String albumId) {
|
void toggleKeepAlbum(String albumId) {
|
||||||
@@ -118,7 +120,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _persistExcludedAlbumIds(Set<String> albumIds) {
|
void _persistExcludedAlbumIds(Set<String> albumIds) {
|
||||||
_metadataRepository.write(.cleanupKeepAlbumIds, albumIds.toList());
|
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepAlbumIds, albumIds.join(','));
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
|
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
|
||||||
@@ -131,7 +133,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
|
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
|
||||||
final isInitialized = _metadataRepository.appConfig.cleanup.defaultsInitialized;
|
final isInitialized = _appSettingsService.getSetting(AppSettingsEnum.cleanupDefaultsInitialized);
|
||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
|
|
||||||
final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums);
|
final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums);
|
||||||
@@ -142,7 +144,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
_persistExcludedAlbumIds(keepAlbumIds);
|
_persistExcludedAlbumIds(keepAlbumIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_metadataRepository.write(.cleanupDefaultsInitialized, true);
|
_appSettingsService.setSetting(AppSettingsEnum.cleanupDefaultsInitialized, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> scanAssets() async {
|
Future<void> scanAssets() async {
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/config/system_config.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
|
|
||||||
final metadataProvider = Provider.autoDispose<MetadataRepository>((_) => MetadataStore.instance);
|
|
||||||
|
|
||||||
final appConfigProvider = Provider.autoDispose<AppConfig>((ref) {
|
|
||||||
final repo = ref.watch(metadataProvider);
|
|
||||||
final subscription = repo.watchAppConfig().listen((event) => ref.state = event);
|
|
||||||
ref.onDispose(subscription.cancel);
|
|
||||||
return repo.appConfig;
|
|
||||||
});
|
|
||||||
|
|
||||||
final systemConfigProvider = Provider.autoDispose<SystemConfig>((ref) {
|
|
||||||
final repo = ref.watch(metadataProvider);
|
|
||||||
final subscription = repo.watchSystemConfig().listen((event) => ref.state = event);
|
|
||||||
ref.onDispose(subscription.cancel);
|
|
||||||
return repo.systemConfig;
|
|
||||||
});
|
|
||||||
@@ -3,7 +3,7 @@ import 'package:immich_mobile/domain/services/timeline.service.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/timeline.repository.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.state.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
|
||||||
final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
|
final timelineRepositoryProvider = Provider<DriftTimelineRepository>(
|
||||||
@@ -29,7 +29,7 @@ final timelineServiceProvider = Provider<TimelineService>(
|
|||||||
final timelineFactoryProvider = Provider<TimelineFactory>(
|
final timelineFactoryProvider = Provider<TimelineFactory>(
|
||||||
(ref) => TimelineFactory(
|
(ref) => TimelineFactory(
|
||||||
timelineRepository: ref.watch(timelineRepositoryProvider),
|
timelineRepository: ref.watch(timelineRepositoryProvider),
|
||||||
metadataRepository: ref.watch(metadataProvider),
|
settingsService: ref.watch(settingsProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,38 +1,38 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/models/map/map_state.model.dart';
|
import 'package:immich_mobile/models/map/map_state.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
|
||||||
final mapStateNotifierProvider = NotifierProvider<MapStateNotifier, MapState>(MapStateNotifier.new);
|
final mapStateNotifierProvider = NotifierProvider<MapStateNotifier, MapState>(MapStateNotifier.new);
|
||||||
|
|
||||||
class MapStateNotifier extends Notifier<MapState> {
|
class MapStateNotifier extends Notifier<MapState> {
|
||||||
@override
|
@override
|
||||||
MapState build() {
|
MapState build() {
|
||||||
final mapConfig = ref.read(appConfigProvider.select((config) => config.map));
|
final appSettingsProvider = ref.read(appSettingsServiceProvider);
|
||||||
|
|
||||||
final lightStyleUrl = ref.read(serverInfoProvider).serverConfig.mapLightStyleUrl;
|
final lightStyleUrl = ref.read(serverInfoProvider).serverConfig.mapLightStyleUrl;
|
||||||
final darkStyleUrl = ref.read(serverInfoProvider).serverConfig.mapDarkStyleUrl;
|
final darkStyleUrl = ref.read(serverInfoProvider).serverConfig.mapDarkStyleUrl;
|
||||||
|
|
||||||
return MapState(
|
return MapState(
|
||||||
themeMode: mapConfig.themeMode,
|
themeMode: ThemeMode.values[appSettingsProvider.getSetting<int>(AppSettingsEnum.mapThemeMode)],
|
||||||
showFavoriteOnly: mapConfig.favoritesOnly,
|
showFavoriteOnly: appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapShowFavoriteOnly),
|
||||||
includeArchived: mapConfig.includeArchived,
|
includeArchived: appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapIncludeArchived),
|
||||||
withPartners: mapConfig.withPartners,
|
withPartners: appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapwithPartners),
|
||||||
relativeTime: mapConfig.relativeDays,
|
relativeTime: appSettingsProvider.getSetting<int>(AppSettingsEnum.mapRelativeDate),
|
||||||
lightStyleFetched: AsyncData(lightStyleUrl),
|
lightStyleFetched: AsyncData(lightStyleUrl),
|
||||||
darkStyleFetched: AsyncData(darkStyleUrl),
|
darkStyleFetched: AsyncData(darkStyleUrl),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchTheme(ThemeMode mode) {
|
void switchTheme(ThemeMode mode) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapThemeMode, mode);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapThemeMode, mode.index);
|
||||||
state = state.copyWith(themeMode: mode);
|
state = state.copyWith(themeMode: mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchFavoriteOnly(bool isFavoriteOnly) {
|
void switchFavoriteOnly(bool isFavoriteOnly) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapShowFavoriteOnly, isFavoriteOnly);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly);
|
||||||
state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true);
|
state = state.copyWith(showFavoriteOnly: isFavoriteOnly, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +41,17 @@ class MapStateNotifier extends Notifier<MapState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void switchIncludeArchived(bool isIncludeArchived) {
|
void switchIncludeArchived(bool isIncludeArchived) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapIncludeArchived, isIncludeArchived);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived);
|
||||||
state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true);
|
state = state.copyWith(includeArchived: isIncludeArchived, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchWithPartners(bool isWithPartners) {
|
void switchWithPartners(bool isWithPartners) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapWithPartners, isWithPartners);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners);
|
||||||
state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true);
|
state = state.copyWith(withPartners: isWithPartners, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setRelativeTime(int relativeTime) {
|
void setRelativeTime(int relativeTime) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeTime);
|
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeTime);
|
||||||
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
|
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/theme/color_scheme.dart';
|
import 'package:immich_mobile/theme/color_scheme.dart';
|
||||||
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
|
||||||
import 'package:immich_mobile/theme/theme_data.dart';
|
import 'package:immich_mobile/theme/theme_data.dart';
|
||||||
|
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
|
||||||
|
final immichThemeModeProvider = StateProvider<ThemeMode>((ref) {
|
||||||
|
final themeMode = ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.themeMode);
|
||||||
|
|
||||||
|
dPrint(() => "Current themeMode $themeMode");
|
||||||
|
|
||||||
|
if (themeMode == ThemeMode.light.name) {
|
||||||
|
return ThemeMode.light;
|
||||||
|
} else if (themeMode == ThemeMode.dark.name) {
|
||||||
|
return ThemeMode.dark;
|
||||||
|
} else {
|
||||||
|
return ThemeMode.system;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final immichThemePresetProvider = StateProvider<ImmichColorPreset>((ref) {
|
||||||
|
final appSettingsProvider = ref.watch(appSettingsServiceProvider);
|
||||||
|
final primaryColorPreset = appSettingsProvider.getSetting(AppSettingsEnum.primaryColor);
|
||||||
|
|
||||||
|
dPrint(() => "Current theme preset $primaryColorPreset");
|
||||||
|
|
||||||
|
try {
|
||||||
|
return ImmichColorPreset.values.firstWhere((e) => e.name == primaryColorPreset);
|
||||||
|
} catch (e) {
|
||||||
|
dPrint(() => "Theme preset $primaryColorPreset not found. Applying default preset.");
|
||||||
|
appSettingsProvider.setSetting(AppSettingsEnum.primaryColor, defaultColorPresetName);
|
||||||
|
return defaultColorPreset;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
final dynamicThemeSettingProvider = StateProvider<bool>((ref) {
|
||||||
|
return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.dynamicTheme);
|
||||||
|
});
|
||||||
|
|
||||||
|
final colorfulInterfaceSettingProvider = StateProvider<bool>((ref) {
|
||||||
|
return ref.watch(appSettingsServiceProvider).getSetting(AppSettingsEnum.colorfulInterface);
|
||||||
|
});
|
||||||
|
|
||||||
// Provider for current selected theme
|
// Provider for current selected theme
|
||||||
final immichThemeProvider = StateProvider<ImmichTheme>((ref) {
|
final immichThemeProvider = StateProvider<ImmichTheme>((ref) {
|
||||||
final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme));
|
final primaryColorPreset = ref.read(immichThemePresetProvider);
|
||||||
|
final useSystemColor = ref.watch(dynamicThemeSettingProvider);
|
||||||
|
final useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider);
|
||||||
final ImmichTheme? dynamicTheme = DynamicTheme.theme;
|
final ImmichTheme? dynamicTheme = DynamicTheme.theme;
|
||||||
final currentTheme = (themeConfig.dynamicTheme && dynamicTheme != null)
|
final currentTheme = (useSystemColor && dynamicTheme != null) ? dynamicTheme : primaryColorPreset.themeOfPreset;
|
||||||
? dynamicTheme
|
|
||||||
: themeConfig.primaryColor.themeOfPreset;
|
|
||||||
|
|
||||||
return themeConfig.colorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme);
|
return useColorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:immich_mobile/infrastructure/repositories/network.repository.dar
|
|||||||
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
import 'package:immich_mobile/models/server_info/server_version.model.dart';
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
@@ -189,7 +188,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
@@ -210,7 +209,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
|
|||||||
@@ -1,40 +1,46 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/sync_stream.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
|
|
||||||
final authRepositoryProvider = Provider<AuthRepository>(
|
final authRepositoryProvider = Provider<AuthRepository>((ref) => AuthRepository(ref.watch(driftProvider)));
|
||||||
(ref) => AuthRepository(ref.watch(driftProvider), ref.watch(metadataProvider)),
|
|
||||||
);
|
|
||||||
|
|
||||||
class AuthRepository {
|
class AuthRepository {
|
||||||
final Drift _drift;
|
final Drift _drift;
|
||||||
final MetadataRepository _metadata;
|
|
||||||
|
|
||||||
const AuthRepository(this._drift, this._metadata);
|
const AuthRepository(this._drift);
|
||||||
|
|
||||||
Future<void> clearLocalData() async {
|
Future<void> clearLocalData() async {
|
||||||
await SyncStreamRepository(_drift).reset();
|
await SyncStreamRepository(_drift).reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getEndpointSwitchingFeature() {
|
bool getEndpointSwitchingFeature() {
|
||||||
return _metadata.systemConfig.network.autoEndpointSwitching;
|
return Store.tryGet(StoreKey.autoEndpointSwitching) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getPreferredWifiName() {
|
String? getPreferredWifiName() {
|
||||||
return _metadata.systemConfig.network.preferredWifiName;
|
return Store.tryGet(StoreKey.preferredWifiName);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getLocalEndpoint() {
|
String? getLocalEndpoint() {
|
||||||
return _metadata.systemConfig.network.localEndpoint;
|
return Store.tryGet(StoreKey.localEndpoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AuxilaryEndpoint> getExternalEndpointList() {
|
List<AuxilaryEndpoint> getExternalEndpointList() {
|
||||||
return _metadata.systemConfig.network.externalEndpointList
|
final jsonString = Store.tryGet(StoreKey.externalEndpointList);
|
||||||
.map((url) => AuxilaryEndpoint(url: url, status: .valid))
|
|
||||||
.toList();
|
if (jsonString == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<dynamic> jsonList = jsonDecode(jsonString);
|
||||||
|
final endpointList = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList();
|
||||||
|
|
||||||
|
return endpointList;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,11 +73,13 @@ class DriftAlbumApiRepository extends ApiRepository {
|
|||||||
_api.updateAlbumInfo(
|
_api.updateAlbumInfo(
|
||||||
albumId,
|
albumId,
|
||||||
UpdateAlbumDto(
|
UpdateAlbumDto(
|
||||||
albumName: name,
|
albumName: name == null ? const Optional.absent() : Optional.present(name),
|
||||||
description: description,
|
description: description == null ? const Optional.absent() : Optional.present(description),
|
||||||
albumThumbnailAssetId: thumbnailAssetId,
|
albumThumbnailAssetId: thumbnailAssetId == null
|
||||||
isActivityEnabled: isActivityEnabled,
|
? const Optional.absent()
|
||||||
order: apiOrder,
|
: Optional.present(thumbnailAssetId),
|
||||||
|
isActivityEnabled: isActivityEnabled == null ? const Optional.absent() : Optional.present(isActivityEnabled),
|
||||||
|
order: apiOrder == null ? const Optional.absent() : Optional.present(apiOrder),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -99,7 +101,9 @@ class DriftAlbumApiRepository extends ApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> setActivityStatus(String albumId, bool isEnabled) async {
|
Future<bool> setActivityStatus(String albumId, bool isEnabled) async {
|
||||||
final response = await checkNull(_api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: isEnabled)));
|
final response = await checkNull(
|
||||||
|
_api.updateAlbumInfo(albumId, UpdateAlbumDto(isActivityEnabled: Optional.present(isEnabled))),
|
||||||
|
);
|
||||||
return response.isActivityEnabled;
|
return response.isActivityEnabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class PartnerApiRepository extends ApiRepository {
|
|||||||
|
|
||||||
Future<List<UserDto>> getAll(Direction direction) async {
|
Future<List<UserDto>> getAll(Direction direction) async {
|
||||||
final response = await checkNull(
|
final response = await checkNull(
|
||||||
_api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.by : PartnerDirection.with_),
|
_api.getPartners(direction == Direction.sharedByMe ? PartnerDirection.sharedBy : PartnerDirection.sharedWith),
|
||||||
);
|
);
|
||||||
return response.map(UserConverter.fromPartnerDto).toList();
|
return response.map(UserConverter.fromPartnerDto).toList();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import 'dart:io';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -177,19 +177,28 @@ class ApiService {
|
|||||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||||
urls.add(serverEndpoint);
|
urls.add(serverEndpoint);
|
||||||
}
|
}
|
||||||
final network = MetadataStore.systemConfig.network;
|
final localEndpoint = Store.tryGet(StoreKey.localEndpoint);
|
||||||
final localEndpoint = network.localEndpoint;
|
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
||||||
if (localEndpoint != null) {
|
|
||||||
urls.add(localEndpoint);
|
urls.add(localEndpoint);
|
||||||
}
|
}
|
||||||
for (final url in network.externalEndpointList) {
|
final externalJson = Store.tryGet(StoreKey.externalEndpointList);
|
||||||
|
if (externalJson != null) {
|
||||||
|
final List<dynamic> list = jsonDecode(externalJson);
|
||||||
|
for (final entry in list) {
|
||||||
|
final url = AuxilaryEndpoint.fromJson(entry).url;
|
||||||
if (url.isNotEmpty) urls.add(url);
|
if (url.isNotEmpty) urls.add(url);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Map<String, String> getRequestHeaders() {
|
static Map<String, String> getRequestHeaders() {
|
||||||
return MetadataStore.systemConfig.network.customHeaders;
|
var customHeadersStr = Store.get(StoreKey.customHeaders, "");
|
||||||
|
if (customHeadersStr.isEmpty) {
|
||||||
|
return const {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (jsonDecode(customHeadersStr) as Map).cast<String, String>();
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiClient get apiClient => _apiClient;
|
ApiClient get apiClient => _apiClient;
|
||||||
|
|||||||
@@ -1,11 +1,66 @@
|
|||||||
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
|
||||||
enum AppSettingsEnum<T> {
|
enum AppSettingsEnum<T> {
|
||||||
|
loadPreview<bool>(StoreKey.loadPreview, "loadPreview", true),
|
||||||
|
loadOriginal<bool>(StoreKey.loadOriginal, "loadOriginal", false),
|
||||||
|
themeMode<String>(StoreKey.themeMode, "themeMode", "system"), // "light","dark","system"
|
||||||
|
primaryColor<String>(StoreKey.primaryColor, "primaryColor", defaultColorPresetName),
|
||||||
|
dynamicTheme<bool>(StoreKey.dynamicTheme, "dynamicTheme", false),
|
||||||
|
colorfulInterface<bool>(StoreKey.colorfulInterface, "colorfulInterface", true),
|
||||||
|
tilesPerRow<int>(StoreKey.tilesPerRow, "tilesPerRow", 4),
|
||||||
|
dynamicLayout<bool>(StoreKey.dynamicLayout, "dynamicLayout", false),
|
||||||
|
groupAssetsBy<int>(StoreKey.groupAssetsBy, "groupBy", 0),
|
||||||
|
uploadErrorNotificationGracePeriod<int>(
|
||||||
|
StoreKey.uploadErrorNotificationGracePeriod,
|
||||||
|
"uploadErrorNotificationGracePeriod",
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
backgroundBackupTotalProgress<bool>(StoreKey.backgroundBackupTotalProgress, "backgroundBackupTotalProgress", true),
|
||||||
|
backgroundBackupSingleProgress<bool>(
|
||||||
|
StoreKey.backgroundBackupSingleProgress,
|
||||||
|
"backgroundBackupSingleProgress",
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
storageIndicator<bool>(StoreKey.storageIndicator, "storageIndicator", true),
|
||||||
|
thumbnailCacheSize<int>(StoreKey.thumbnailCacheSize, "thumbnailCacheSize", 10000),
|
||||||
|
imageCacheSize<int>(StoreKey.imageCacheSize, "imageCacheSize", 350),
|
||||||
|
albumThumbnailCacheSize<int>(StoreKey.albumThumbnailCacheSize, "albumThumbnailCacheSize", 200),
|
||||||
|
selectedAlbumSortOrder<int>(StoreKey.selectedAlbumSortOrder, "selectedAlbumSortOrder", 2),
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
||||||
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
|
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
|
||||||
|
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
|
||||||
|
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
||||||
|
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
|
||||||
|
loadOriginalVideo<bool>(StoreKey.loadOriginalVideo, "loadOriginalVideo", false),
|
||||||
|
autoPlayVideo<bool>(StoreKey.autoPlayVideo, "autoPlayVideo", true),
|
||||||
|
tapToNavigate<bool>(StoreKey.tapToNavigate, "tapToNavigate", false),
|
||||||
|
mapThemeMode<int>(StoreKey.mapThemeMode, null, 0),
|
||||||
|
mapShowFavoriteOnly<bool>(StoreKey.mapShowFavoriteOnly, null, false),
|
||||||
|
mapIncludeArchived<bool>(StoreKey.mapIncludeArchived, null, false),
|
||||||
|
mapwithPartners<bool>(StoreKey.mapwithPartners, null, false),
|
||||||
|
mapRelativeDate<int>(StoreKey.mapRelativeDate, null, 0),
|
||||||
|
allowSelfSignedSSLCert<bool>(StoreKey.selfSignedCert, null, false),
|
||||||
|
ignoreIcloudAssets<bool>(StoreKey.ignoreIcloudAssets, null, false),
|
||||||
|
selectedAlbumSortReverse<bool>(StoreKey.selectedAlbumSortReverse, null, true),
|
||||||
enableHapticFeedback<bool>(StoreKey.enableHapticFeedback, null, true),
|
enableHapticFeedback<bool>(StoreKey.enableHapticFeedback, null, true),
|
||||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false);
|
syncAlbums<bool>(StoreKey.syncAlbums, null, false),
|
||||||
|
autoEndpointSwitching<bool>(StoreKey.autoEndpointSwitching, null, false),
|
||||||
|
photoManagerCustomFilter<bool>(StoreKey.photoManagerCustomFilter, null, true),
|
||||||
|
betaTimeline<bool>(StoreKey.betaTimeline, null, true),
|
||||||
|
enableBackup<bool>(StoreKey.enableBackup, null, false),
|
||||||
|
useCellularForUploadVideos<bool>(StoreKey.useWifiForUploadVideos, null, false),
|
||||||
|
useCellularForUploadPhotos<bool>(StoreKey.useWifiForUploadPhotos, null, false),
|
||||||
|
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false),
|
||||||
|
albumGridView<bool>(StoreKey.albumGridView, "albumGridView", false),
|
||||||
|
backupRequireCharging<bool>(StoreKey.backupRequireCharging, null, false),
|
||||||
|
backupTriggerDelay<int>(StoreKey.backupTriggerDelay, null, 30),
|
||||||
|
cleanupKeepFavorites<bool>(StoreKey.cleanupKeepFavorites, null, true),
|
||||||
|
cleanupKeepMediaType<int>(StoreKey.cleanupKeepMediaType, null, 0),
|
||||||
|
cleanupKeepAlbumIds<String>(StoreKey.cleanupKeepAlbumIds, null, ""),
|
||||||
|
cleanupCutoffDaysAgo<int>(StoreKey.cleanupCutoffDaysAgo, null, -1),
|
||||||
|
cleanupDefaultsInitialized<bool>(StoreKey.cleanupDefaultsInitialized, null, false);
|
||||||
|
|
||||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
import 'package:immich_mobile/domain/utils/background_sync.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
import 'package:immich_mobile/models/auth/login_response.model.dart';
|
||||||
import 'package:immich_mobile/providers/api.provider.dart';
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/auth.repository.dart';
|
import 'package:immich_mobile/repositories/auth.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
import 'package:immich_mobile/repositories/auth_api.repository.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/services/network.service.dart';
|
import 'package:immich_mobile/services/network.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -25,6 +25,7 @@ final authServiceProvider = Provider(
|
|||||||
ref.watch(apiServiceProvider),
|
ref.watch(apiServiceProvider),
|
||||||
ref.watch(networkServiceProvider),
|
ref.watch(networkServiceProvider),
|
||||||
ref.watch(backgroundSyncProvider),
|
ref.watch(backgroundSyncProvider),
|
||||||
|
ref.watch(appSettingsServiceProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -34,6 +35,7 @@ class AuthService {
|
|||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final NetworkService _networkService;
|
final NetworkService _networkService;
|
||||||
final BackgroundSyncManager _backgroundSyncManager;
|
final BackgroundSyncManager _backgroundSyncManager;
|
||||||
|
final AppSettingsService _appSettingsService;
|
||||||
final _log = Logger("AuthService");
|
final _log = Logger("AuthService");
|
||||||
|
|
||||||
AuthService(
|
AuthService(
|
||||||
@@ -42,6 +44,7 @@ class AuthService {
|
|||||||
this._apiService,
|
this._apiService,
|
||||||
this._networkService,
|
this._networkService,
|
||||||
this._backgroundSyncManager,
|
this._backgroundSyncManager,
|
||||||
|
this._appSettingsService,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Validates the provided server URL by resolving and setting the endpoint.
|
/// Validates the provided server URL by resolving and setting the endpoint.
|
||||||
@@ -100,7 +103,7 @@ class AuthService {
|
|||||||
_log.severe("Error clearing local data", error, stackTrace);
|
_log.severe("Error clearing local data", error, stackTrace);
|
||||||
});
|
});
|
||||||
|
|
||||||
await MetadataStore.instance.write(MetadataKey.backupEnabled, false);
|
await _appSettingsService.setSetting(AppSettingsEnum.enableBackup, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,6 +123,11 @@ class AuthService {
|
|||||||
_authRepository.clearLocalData(),
|
_authRepository.clearLocalData(),
|
||||||
Store.delete(StoreKey.currentUser),
|
Store.delete(StoreKey.currentUser),
|
||||||
Store.delete(StoreKey.accessToken),
|
Store.delete(StoreKey.accessToken),
|
||||||
|
Store.delete(StoreKey.assetETag),
|
||||||
|
Store.delete(StoreKey.autoEndpointSwitching),
|
||||||
|
Store.delete(StoreKey.preferredWifiName),
|
||||||
|
Store.delete(StoreKey.localEndpoint),
|
||||||
|
Store.delete(StoreKey.externalEndpointList),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,14 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
@@ -30,6 +31,7 @@ final backgroundUploadServiceProvider = Provider((ref) {
|
|||||||
ref.watch(storageRepositoryProvider),
|
ref.watch(storageRepositoryProvider),
|
||||||
ref.watch(localAssetRepository),
|
ref.watch(localAssetRepository),
|
||||||
ref.watch(backupRepositoryProvider),
|
ref.watch(backupRepositoryProvider),
|
||||||
|
ref.watch(appSettingsServiceProvider),
|
||||||
ref.watch(assetMediaRepositoryProvider),
|
ref.watch(assetMediaRepositoryProvider),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -101,6 +103,7 @@ class BackgroundUploadService {
|
|||||||
this._storageRepository,
|
this._storageRepository,
|
||||||
this._localAssetRepository,
|
this._localAssetRepository,
|
||||||
this._backupRepository,
|
this._backupRepository,
|
||||||
|
this._appSettingsService,
|
||||||
this._assetMediaRepository,
|
this._assetMediaRepository,
|
||||||
) {
|
) {
|
||||||
_uploadRepository.onUploadStatus = _onUploadCallback;
|
_uploadRepository.onUploadStatus = _onUploadCallback;
|
||||||
@@ -111,6 +114,7 @@ class BackgroundUploadService {
|
|||||||
final StorageRepository _storageRepository;
|
final StorageRepository _storageRepository;
|
||||||
final DriftLocalAssetRepository _localAssetRepository;
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
final DriftBackupRepository _backupRepository;
|
final DriftBackupRepository _backupRepository;
|
||||||
|
final AppSettingsService _appSettingsService;
|
||||||
final AssetMediaRepository _assetMediaRepository;
|
final AssetMediaRepository _assetMediaRepository;
|
||||||
final Logger _logger = Logger('BackgroundUploadService');
|
final Logger _logger = Logger('BackgroundUploadService');
|
||||||
|
|
||||||
@@ -357,10 +361,15 @@ class BackgroundUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||||
final backup = MetadataStore.appConfig.backup;
|
bool requiresWiFi = true;
|
||||||
if (asset.isVideo && backup.useCellularForVideos) return false;
|
|
||||||
if (!asset.isVideo && backup.useCellularForPhotos) return false;
|
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
||||||
return true;
|
requiresWiFi = false;
|
||||||
|
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
||||||
|
requiresWiFi = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requiresWiFi;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadTask> buildUploadTask(
|
Future<UploadTask> buildUploadTask(
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ import 'package:immich_mobile/domain/models/asset/asset_metadata.model.dart';
|
|||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
|
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
import 'package:immich_mobile/repositories/asset_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
import 'package:photo_manager/photo_manager.dart' show PMProgressHandler;
|
||||||
@@ -38,6 +39,7 @@ final foregroundUploadServiceProvider = Provider((ref) {
|
|||||||
ref.watch(storageRepositoryProvider),
|
ref.watch(storageRepositoryProvider),
|
||||||
ref.watch(backupRepositoryProvider),
|
ref.watch(backupRepositoryProvider),
|
||||||
ref.watch(connectivityApiProvider),
|
ref.watch(connectivityApiProvider),
|
||||||
|
ref.watch(appSettingsServiceProvider),
|
||||||
ref.watch(assetMediaRepositoryProvider),
|
ref.watch(assetMediaRepositoryProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -53,6 +55,7 @@ class ForegroundUploadService {
|
|||||||
this._storageRepository,
|
this._storageRepository,
|
||||||
this._backupRepository,
|
this._backupRepository,
|
||||||
this._connectivityApi,
|
this._connectivityApi,
|
||||||
|
this._appSettingsService,
|
||||||
this._assetMediaRepository,
|
this._assetMediaRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -60,6 +63,7 @@ class ForegroundUploadService {
|
|||||||
final StorageRepository _storageRepository;
|
final StorageRepository _storageRepository;
|
||||||
final DriftBackupRepository _backupRepository;
|
final DriftBackupRepository _backupRepository;
|
||||||
final ConnectivityApi _connectivityApi;
|
final ConnectivityApi _connectivityApi;
|
||||||
|
final AppSettingsService _appSettingsService;
|
||||||
final AssetMediaRepository _assetMediaRepository;
|
final AssetMediaRepository _assetMediaRepository;
|
||||||
final Logger _logger = Logger('ForegroundUploadService');
|
final Logger _logger = Logger('ForegroundUploadService');
|
||||||
|
|
||||||
@@ -449,9 +453,14 @@ class ForegroundUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||||
final backup = MetadataStore.appConfig.backup;
|
bool requiresWiFi = true;
|
||||||
if (asset.isVideo && backup.useCellularForVideos) return false;
|
|
||||||
if (!asset.isVideo && backup.useCellularForPhotos) return false;
|
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
||||||
return true;
|
requiresWiFi = false;
|
||||||
|
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
||||||
|
requiresWiFi = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requiresWiFi;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,14 +98,14 @@ class SharedLinkService {
|
|||||||
final responseDto = await _apiService.sharedLinksApi.updateSharedLink(
|
final responseDto = await _apiService.sharedLinksApi.updateSharedLink(
|
||||||
id,
|
id,
|
||||||
SharedLinkEditDto(
|
SharedLinkEditDto(
|
||||||
showMetadata: showMeta,
|
showMetadata: showMeta == null ? const Optional.absent() : Optional.present(showMeta),
|
||||||
allowDownload: allowDownload,
|
allowDownload: allowDownload == null ? const Optional.absent() : Optional.present(allowDownload),
|
||||||
allowUpload: allowUpload,
|
allowUpload: allowUpload == null ? const Optional.absent() : Optional.present(allowUpload),
|
||||||
expiresAt: expiresAt,
|
expiresAt: expiresAt == null ? const Optional.absent() : Optional.present(expiresAt),
|
||||||
description: description,
|
description: description == null ? const Optional.absent() : Optional.present(description),
|
||||||
password: password,
|
password: password == null ? const Optional.absent() : Optional.present(password),
|
||||||
slug: slug,
|
slug: slug == null ? const Optional.absent() : Optional.present(slug),
|
||||||
changeExpiryTime: changeExpiry,
|
changeExpiryTime: changeExpiry == null ? const Optional.absent() : Optional.present(changeExpiry),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (responseDto != null) {
|
if (responseDto != null) {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:immich_mobile/extensions/translate_extensions.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/log.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/store.repository.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
@@ -49,11 +48,9 @@ abstract final class Bootstrap {
|
|||||||
|
|
||||||
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates);
|
||||||
|
|
||||||
final metadataRepo = await MetadataStore.ensureInitialized(drift);
|
|
||||||
|
|
||||||
await LogService.init(
|
await LogService.init(
|
||||||
logRepository: LogRepository(logDb),
|
logRepository: LogRepository(logDb),
|
||||||
metadataRepository: metadataRepo,
|
storeRepository: storeRepo,
|
||||||
shouldBuffer: shouldBufferLogs,
|
shouldBuffer: shouldBufferLogs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
|
||||||
ValueNotifier<T> useAppSettingsState<T>(AppSettingsEnum<T> key) {
|
ValueNotifier<T> useAppSettingsState<T>(AppSettingsEnum<T> key) {
|
||||||
final notifier = useState<T>(Store.get(key.storeKey, key.defaultValue));
|
final notifier = useState<T>(Store.get(key.storeKey, key.defaultValue));
|
||||||
|
|||||||
@@ -1,271 +1,25 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
|
||||||
import 'package:immich_mobile/constants/enums.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/log.model.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
|
|
||||||
const int targetVersion = 26;
|
const int targetVersion = 25;
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
Future<void> migrateDatabaseIfNeeded() async {
|
||||||
final int version = Store.get(StoreKey.version, targetVersion);
|
final int version = Store.get(StoreKey.version, targetVersion);
|
||||||
|
|
||||||
if (version < 25) {
|
if (version < 25) {
|
||||||
await _migrateTo25();
|
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||||
|
if (accessToken != null && accessToken.isNotEmpty) {
|
||||||
|
final serverUrls = ApiService.getServerUrls();
|
||||||
|
if (serverUrls.isNotEmpty) {
|
||||||
|
await NetworkRepository.setHeaders(ApiService.getRequestHeaders(), serverUrls, token: accessToken);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version < 26) {
|
|
||||||
await _migrateTo26(drift);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await Store.put(StoreKey.version, targetVersion);
|
await Store.put(StoreKey.version, targetVersion);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _migrateTo25() async {
|
|
||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
|
||||||
if (accessToken == null || accessToken.isEmpty) return;
|
|
||||||
|
|
||||||
final urls = <String>[];
|
|
||||||
final serverEndpoint = Store.tryGet(StoreKey.serverEndpoint);
|
|
||||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
|
||||||
urls.add(serverEndpoint);
|
|
||||||
}
|
|
||||||
final localEndpoint = Store.tryGet(StoreKey.legacyLocalEndpoint);
|
|
||||||
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
|
||||||
urls.add(localEndpoint);
|
|
||||||
}
|
|
||||||
final externalJson = Store.tryGet(StoreKey.legacyExternalEndpointList);
|
|
||||||
if (externalJson != null) {
|
|
||||||
final List<dynamic> list = jsonDecode(externalJson);
|
|
||||||
for (final entry in list) {
|
|
||||||
final url = AuxilaryEndpoint.fromJson(entry).url;
|
|
||||||
if (url.isNotEmpty) urls.add(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (urls.isEmpty) return;
|
|
||||||
|
|
||||||
final customHeadersStr = Store.get(StoreKey.legacyCustomHeaders, "");
|
|
||||||
final headers = customHeadersStr.isEmpty
|
|
||||||
? const <String, String>{}
|
|
||||||
: (jsonDecode(customHeadersStr) as Map).cast<String, String>();
|
|
||||||
|
|
||||||
await NetworkRepository.setHeaders(headers, urls, token: accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _migrateTo26(Drift drift) async {
|
|
||||||
final migrator = _StoreMigrator(drift);
|
|
||||||
await migrator.migrateEnumIndex(StoreKey.legacyLogLevel, MetadataKey.logLevel, LogLevel.values);
|
|
||||||
// Theme
|
|
||||||
await migrator.migrateEnumName(StoreKey.legacyThemeMode, MetadataKey.themeMode, ThemeMode.values);
|
|
||||||
await migrator.migrateEnumName(StoreKey.legacyPrimaryColor, MetadataKey.themePrimaryColor, ImmichColorPreset.values);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyDynamicTheme, MetadataKey.themeDynamic);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyColorfulInterface, MetadataKey.themeColorfulInterface);
|
|
||||||
// Cleanup
|
|
||||||
final cleanupKeepAlbumIds = await migrator.readLegacyStoreString(StoreKey.legacyCleanupKeepAlbumIds.id);
|
|
||||||
if (cleanupKeepAlbumIds != null) {
|
|
||||||
final ids = cleanupKeepAlbumIds.split(',').where((id) => id.isNotEmpty).toList();
|
|
||||||
migrator.stage(StoreKey.legacyCleanupKeepAlbumIds, MetadataKey.cleanupKeepAlbumIds, ids);
|
|
||||||
}
|
|
||||||
await migrator.migrateBool(StoreKey.legacyCleanupKeepFavorites, MetadataKey.cleanupKeepFavorites);
|
|
||||||
await migrator.migrateEnumIndex(
|
|
||||||
StoreKey.legacyCleanupKeepMediaType,
|
|
||||||
MetadataKey.cleanupKeepMediaType,
|
|
||||||
AssetKeepType.values,
|
|
||||||
);
|
|
||||||
await migrator.migrateInt(StoreKey.legacyCleanupCutoffDaysAgo, MetadataKey.cleanupCutoffDaysAgo);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyCleanupDefaultsInitialized, MetadataKey.cleanupDefaultsInitialized);
|
|
||||||
// Map
|
|
||||||
await migrator.migrateBool(StoreKey.legacyMapShowFavoriteOnly, MetadataKey.mapShowFavoriteOnly);
|
|
||||||
await migrator.migrateInt(StoreKey.legacyMapRelativeDate, MetadataKey.mapRelativeDate);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyMapIncludeArchived, MetadataKey.mapIncludeArchived);
|
|
||||||
await migrator.migrateEnumIndex(StoreKey.legacyMapThemeMode, MetadataKey.mapThemeMode, ThemeMode.values);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyMapwithPartners, MetadataKey.mapWithPartners);
|
|
||||||
// Timeline
|
|
||||||
await migrator.migrateInt(StoreKey.legacyTilesPerRow, MetadataKey.timelineTilesPerRow);
|
|
||||||
await migrator.migrateEnumIndex(
|
|
||||||
StoreKey.legacyGroupAssetsBy,
|
|
||||||
MetadataKey.timelineGroupAssetsBy,
|
|
||||||
GroupAssetsBy.values,
|
|
||||||
);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyStorageIndicator, MetadataKey.timelineStorageIndicator);
|
|
||||||
// Image
|
|
||||||
await migrator.migrateBool(StoreKey.legacyPreferRemoteImage, MetadataKey.imagePreferRemote);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyLoadOriginal, MetadataKey.imageLoadOriginal);
|
|
||||||
// Viewer
|
|
||||||
await migrator.migrateBool(StoreKey.legacyLoopVideo, MetadataKey.viewerLoopVideo);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyLoadOriginalVideo, MetadataKey.viewerLoadOriginalVideo);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyAutoPlayVideo, MetadataKey.viewerAutoPlayVideo);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyTapToNavigate, MetadataKey.viewerTapToNavigate);
|
|
||||||
// Network
|
|
||||||
await migrator.migrateBool(StoreKey.legacyAutoEndpointSwitching, MetadataKey.networkAutoEndpointSwitching);
|
|
||||||
await migrator.migrateString(StoreKey.legacyPreferredWifiName, MetadataKey.networkPreferredWifiName);
|
|
||||||
await migrator.migrateString(StoreKey.legacyLocalEndpoint, MetadataKey.networkLocalEndpoint);
|
|
||||||
await _migrateExternalEndpointList(migrator);
|
|
||||||
await _migrateCustomHeaders(migrator);
|
|
||||||
// Album
|
|
||||||
await _migrateAlbumSortMode(migrator);
|
|
||||||
await migrator.migrateBool(StoreKey.legacySelectedAlbumSortReverse, MetadataKey.albumIsReverse);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyAlbumGridView, MetadataKey.albumIsGrid);
|
|
||||||
// Backup
|
|
||||||
await migrator.migrateBool(StoreKey.legacyEnableBackup, MetadataKey.backupEnabled);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadVideos, MetadataKey.backupUseCellularForVideos);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyUseWifiForUploadPhotos, MetadataKey.backupUseCellularForPhotos);
|
|
||||||
await migrator.migrateBool(StoreKey.legacyBackupRequireCharging, MetadataKey.backupRequireCharging);
|
|
||||||
await migrator.migrateInt(StoreKey.legacyBackupTriggerDelay, MetadataKey.backupTriggerDelay);
|
|
||||||
await migrator.migrateBool(StoreKey.legacySyncAlbums, MetadataKey.backupSyncAlbums);
|
|
||||||
await migrator.complete();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _migrateAlbumSortMode(_StoreMigrator migrator) async {
|
|
||||||
final raw = await migrator.readLegacyStoreInt(StoreKey.legacySelectedAlbumSortOrder.id);
|
|
||||||
if (raw == null) return;
|
|
||||||
|
|
||||||
final mode = AlbumSortMode.values.firstWhere(
|
|
||||||
(e) => e.storeIndex == raw,
|
|
||||||
orElse: () => MetadataKey.albumSortMode.defaultValue,
|
|
||||||
);
|
|
||||||
|
|
||||||
migrator.stage(StoreKey.legacySelectedAlbumSortOrder, MetadataKey.albumSortMode, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _migrateExternalEndpointList(_StoreMigrator migrator) async {
|
|
||||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyExternalEndpointList.id);
|
|
||||||
if (raw == null) return;
|
|
||||||
|
|
||||||
final urls = <String>[];
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is List) {
|
|
||||||
for (final entry in decoded) {
|
|
||||||
final url = AuxilaryEndpoint.fromJson(entry).url;
|
|
||||||
if (url.isNotEmpty) urls.add(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} on FormatException {
|
|
||||||
// ignore invalid entries
|
|
||||||
}
|
|
||||||
|
|
||||||
migrator.stage(StoreKey.legacyExternalEndpointList, MetadataKey.networkExternalEndpointList, urls);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _migrateCustomHeaders(_StoreMigrator migrator) async {
|
|
||||||
final raw = await migrator.readLegacyStoreString(StoreKey.legacyCustomHeaders.id);
|
|
||||||
if (raw == null) return;
|
|
||||||
|
|
||||||
final headers = <String, String>{};
|
|
||||||
try {
|
|
||||||
final decoded = jsonDecode(raw);
|
|
||||||
if (decoded is Map) {
|
|
||||||
decoded.forEach((key, value) {
|
|
||||||
if (key is String && value is String) headers[key] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} on FormatException {
|
|
||||||
// ignore invalid entries
|
|
||||||
}
|
|
||||||
|
|
||||||
migrator.stage(StoreKey.legacyCustomHeaders, MetadataKey.networkCustomHeaders, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
class _StoreMigrator {
|
|
||||||
final Drift _db;
|
|
||||||
final Map<MetadataKey<Object>, Object> _cache = {};
|
|
||||||
final List<int> _migratedStoreIds = [];
|
|
||||||
|
|
||||||
_StoreMigrator(this._db);
|
|
||||||
|
|
||||||
Future<void> migrateEnumIndex<T extends Enum>(StoreKey<int> legacyKey, MetadataKey<T> newKey, List<T> values) async {
|
|
||||||
final index = await readLegacyStoreInt(legacyKey.id);
|
|
||||||
if (index == null) return;
|
|
||||||
|
|
||||||
final enumValue = values.elementAtOrNull(index) ?? newKey.defaultValue;
|
|
||||||
_cache[newKey] = enumValue;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> migrateEnumName<T extends Enum>(
|
|
||||||
StoreKey<String> legacyKey,
|
|
||||||
MetadataKey<T> newKey,
|
|
||||||
List<T> values,
|
|
||||||
) async {
|
|
||||||
final name = await readLegacyStoreString(legacyKey.id);
|
|
||||||
if (name == null) return;
|
|
||||||
|
|
||||||
final enumValue = values.firstWhere((e) => e.name == name, orElse: () => newKey.defaultValue);
|
|
||||||
_cache[newKey] = enumValue;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> migrateBool(StoreKey<bool> legacyKey, MetadataKey<bool> newKey) async {
|
|
||||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
|
||||||
if (intValue == null) return;
|
|
||||||
|
|
||||||
final boolValue = intValue != 0;
|
|
||||||
_cache[newKey] = boolValue;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> migrateInt(StoreKey<int> legacyKey, MetadataKey<int> newKey) async {
|
|
||||||
final intValue = await readLegacyStoreInt(legacyKey.id);
|
|
||||||
if (intValue == null) return;
|
|
||||||
|
|
||||||
_cache[newKey] = intValue;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> migrateString(StoreKey<String> legacyKey, MetadataKey<String> newKey) async {
|
|
||||||
final value = await readLegacyStoreString(legacyKey.id);
|
|
||||||
if (value == null) return;
|
|
||||||
|
|
||||||
_cache[newKey] = value;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void stage<T extends Object>(StoreKey legacyKey, MetadataKey<T> newKey, T value) {
|
|
||||||
_cache[newKey] = value;
|
|
||||||
_migratedStoreIds.add(legacyKey.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> complete() async {
|
|
||||||
await _db.batch((batch) {
|
|
||||||
for (final entry in _cache.entries) {
|
|
||||||
if (entry.value == entry.key.defaultValue) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
batch.insert(
|
|
||||||
_db.metadataEntity,
|
|
||||||
MetadataEntityCompanion(key: Value(entry.key.key), value: Value(entry.key.encode(entry.value))),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
await deleteLegacyStoreRows(_migratedStoreIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> readLegacyStoreString(int id) async {
|
|
||||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull();
|
|
||||||
return row?.stringValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int?> readLegacyStoreInt(int id) async {
|
|
||||||
final row = await (_db.storeEntity.select()..where((t) => t.id.equals(id))).getSingleOrNull();
|
|
||||||
return row?.intValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> deleteLegacyStoreRows(List<int> ids) async {
|
|
||||||
if (ids.isEmpty) return;
|
|
||||||
await (_db.storeEntity.delete()..where((t) => t.id.isIn(ids))).go();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/svg.dart';
|
import 'package:flutter_svg/svg.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
import 'package:immich_mobile/models/server_info/server_info.model.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
import 'package:immich_mobile/providers/server_info.provider.dart';
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||||
@@ -192,14 +193,18 @@ class _BackupIndicator extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
|
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
|
||||||
final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled));
|
final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup);
|
||||||
final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none));
|
final hasError = ref.watch(driftBackupProvider.select((state) => state.error != BackupError.none));
|
||||||
final isDarkTheme = context.isDarkTheme;
|
final isDarkTheme = context.isDarkTheme;
|
||||||
final iconColor = isDarkTheme ? Colors.white : Colors.black;
|
final iconColor = isDarkTheme ? Colors.white : Colors.black;
|
||||||
final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty));
|
final isUploading = ref.watch(driftBackupProvider.select((state) => state.uploadItems.isNotEmpty));
|
||||||
|
|
||||||
return Builder(
|
return StreamBuilder(
|
||||||
builder: (ctx) {
|
stream: backupStateStream,
|
||||||
|
initialData: false,
|
||||||
|
builder: (ctx, snapshot) {
|
||||||
|
final backupEnabled = snapshot.data ?? false;
|
||||||
|
|
||||||
if (!backupEnabled) {
|
if (!backupEnabled) {
|
||||||
return _BadgeLabel(
|
return _BadgeLabel(
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import 'package:immich_mobile/domain/models/store.model.dart';
|
|||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.dart';
|
|
||||||
import 'package:immich_mobile/providers/auth.provider.dart';
|
import 'package:immich_mobile/providers/auth.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
import 'package:immich_mobile/providers/gallery_permission.provider.dart';
|
||||||
@@ -179,7 +178,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
await backgroundManager.syncRemote();
|
await backgroundManager.syncRemote();
|
||||||
await backgroundManager.hashAssets();
|
await backgroundManager.hashAssets();
|
||||||
|
|
||||||
if (MetadataStore.appConfig.backup.syncAlbums) {
|
if (Store.get(StoreKey.syncAlbums, false)) {
|
||||||
await backgroundManager.syncLinkedAlbum();
|
await backgroundManager.syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
|
||||||
@@ -31,12 +30,8 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
final manageLocalMediaAndroid = useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
||||||
final isManageMediaSupported = useState(false);
|
final isManageMediaSupported = useState(false);
|
||||||
final manageMediaAndroidPermission = useState(false);
|
final manageMediaAndroidPermission = useState(false);
|
||||||
final levelId = useState<int>(ref.read(systemConfigProvider).logLevel.index);
|
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
|
||||||
final preferRemote = useState(ref.read(appConfigProvider).image.preferRemote);
|
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
|
||||||
useValueChanged(
|
|
||||||
preferRemote.value,
|
|
||||||
(_, __) => ref.read(metadataProvider).write(.imagePreferRemote, preferRemote.value),
|
|
||||||
);
|
|
||||||
final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled);
|
final readonlyModeEnabled = useAppSettingsState(AppSettingsEnum.readonlyModeEnabled);
|
||||||
|
|
||||||
final logLevel = Level.LEVELS[levelId.value].name;
|
final logLevel = Level.LEVELS[levelId.value].name;
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_radio_list_tile.dart';
|
||||||
|
|
||||||
@@ -16,17 +15,18 @@ class GroupSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final groupBy = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.groupAssetsBy)));
|
final groupByIndex = useAppSettingsState(AppSettingsEnum.groupAssetsBy);
|
||||||
|
final groupBy = GroupAssetsBy.values[groupByIndex.value];
|
||||||
|
|
||||||
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
||||||
await ref.read(metadataProvider).write(MetadataKey.timelineGroupAssetsBy, groupBy);
|
await ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.groupAssetsBy, groupBy.index);
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
ref.invalidate(appSettingsServiceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeGroupValue(GroupAssetsBy? value) {
|
void changeGroupValue(GroupAssetsBy? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
groupBy.value = value;
|
groupByIndex.value = value.index;
|
||||||
unawaited(updateAppSettings(value));
|
unawaited(updateAppSettings(groupBy));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class GroupSettings extends HookConsumerWidget {
|
|||||||
value: GroupAssetsBy.auto,
|
value: GroupAssetsBy.auto,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
groupBy: groupBy.value,
|
groupBy: groupBy,
|
||||||
onRadioChanged: changeGroupValue,
|
onRadioChanged: changeGroupValue,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||||
|
|
||||||
@@ -14,10 +13,7 @@ class LayoutSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tilesPerRow = useState(ref.read(appConfigProvider.select((s) => s.timeline.tilesPerRow)));
|
final tilesPerRow = useAppSettingsState(AppSettingsEnum.tilesPerRow);
|
||||||
useValueChanged<int, void>(tilesPerRow.value, (_, __) {
|
|
||||||
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, tilesPerRow.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -33,9 +29,7 @@ class LayoutSettings extends HookConsumerWidget {
|
|||||||
maxValue: 6,
|
maxValue: 6,
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
noDivisons: 4,
|
noDivisons: 4,
|
||||||
onChangeEnd: (value) {
|
onChangeEnd: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
|
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_layout_settings.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
||||||
@@ -16,14 +15,13 @@ class AssetListSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final storageIndicator = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)));
|
final showStorageIndicator = useAppSettingsState(AppSettingsEnum.storageIndicator);
|
||||||
|
|
||||||
final assetListSetting = [
|
final assetListSetting = [
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: storageIndicator,
|
valueNotifier: showStorageIndicator,
|
||||||
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
||||||
onChanged: (value) {
|
onChanged: (_) {
|
||||||
ref.read(metadataProvider).write(MetadataKey.timelineStorageIndicator, value);
|
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
ref.invalidate(appSettingsServiceProvider);
|
||||||
ref.invalidate(settingsProvider);
|
ref.invalidate(settingsProvider);
|
||||||
},
|
},
|
||||||
|
|||||||
+10
-6
@@ -1,21 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
class ImageViewerQualitySetting extends HookConsumerWidget {
|
class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||||
const ImageViewerQualitySetting({super.key});
|
const ImageViewerQualitySetting({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal);
|
final isPreview = useAppSettingsState(AppSettingsEnum.loadPreview);
|
||||||
useValueChanged<bool, void>(isOriginal.value, (_, __) {
|
final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal);
|
||||||
ref.read(metadataProvider).write(.imageLoadOriginal, isOriginal.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -25,6 +23,12 @@ class ImageViewerQualitySetting extends HookConsumerWidget {
|
|||||||
icon: Icons.image_outlined,
|
icon: Icons.image_outlined,
|
||||||
subtitle: "setting_image_viewer_help".t(context: context),
|
subtitle: "setting_image_viewer_help".t(context: context),
|
||||||
),
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
valueNotifier: isPreview,
|
||||||
|
title: "setting_image_viewer_preview_title".t(context: context),
|
||||||
|
subtitle: "setting_image_viewer_preview_subtitle".t(context: context),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
|
),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: isOriginal,
|
valueNotifier: isOriginal,
|
||||||
title: "setting_image_viewer_original_title".t(context: context),
|
title: "setting_image_viewer_original_title".t(context: context),
|
||||||
|
|||||||
+5
-6
@@ -1,20 +1,18 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
|
import 'package:immich_mobile/widgets/settings/settings_sub_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
class ImageViewerTapToNavigateSetting extends HookConsumerWidget {
|
class ImageViewerTapToNavigateSetting extends HookConsumerWidget {
|
||||||
const ImageViewerTapToNavigateSetting({super.key});
|
const ImageViewerTapToNavigateSetting({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tapToNavigate = useState(ref.read(appConfigProvider).viewer.tapToNavigate);
|
final tapToNavigate = useAppSettingsState(AppSettingsEnum.tapToNavigate);
|
||||||
useValueChanged<bool, void>(tapToNavigate.value, (_, __) {
|
|
||||||
ref.read(metadataProvider).write(.viewerTapToNavigate, tapToNavigate.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -24,6 +22,7 @@ class ImageViewerTapToNavigateSetting extends HookConsumerWidget {
|
|||||||
valueNotifier: tapToNavigate,
|
valueNotifier: tapToNavigate,
|
||||||
title: "setting_image_navigation_enable_title".tr(),
|
title: "setting_image_navigation_enable_title".tr(),
|
||||||
subtitle: "setting_image_navigation_enable_subtitle".tr(),
|
subtitle: "setting_image_navigation_enable_subtitle".tr(),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,30 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
class VideoViewerSettings extends HookConsumerWidget {
|
class VideoViewerSettings extends HookConsumerWidget {
|
||||||
const VideoViewerSettings({super.key});
|
const VideoViewerSettings({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final viewer = ref.read(appConfigProvider).viewer;
|
final useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo);
|
||||||
final useAutoPlayVideo = useState(viewer.autoPlayVideo);
|
final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo);
|
||||||
final useLoopVideo = useState(viewer.loopVideo);
|
final useAutoPlayVideo = useAppSettingsState(AppSettingsEnum.autoPlayVideo);
|
||||||
final useOriginalVideo = useState(viewer.loadOriginalVideo);
|
|
||||||
|
|
||||||
useValueChanged<bool, void>(useAutoPlayVideo.value, (_, __) {
|
|
||||||
ref.read(metadataProvider).write(.viewerAutoPlayVideo, useAutoPlayVideo.value);
|
|
||||||
});
|
|
||||||
useValueChanged<bool, void>(useLoopVideo.value, (_, __) {
|
|
||||||
ref.read(metadataProvider).write(.viewerLoopVideo, useLoopVideo.value);
|
|
||||||
});
|
|
||||||
useValueChanged<bool, void>(useOriginalVideo.value, (_, __) {
|
|
||||||
ref.read(metadataProvider).write(.viewerLoadOriginalVideo, useOriginalVideo.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -37,16 +27,19 @@ class VideoViewerSettings extends HookConsumerWidget {
|
|||||||
valueNotifier: useAutoPlayVideo,
|
valueNotifier: useAutoPlayVideo,
|
||||||
title: "setting_video_viewer_auto_play_title".t(context: context),
|
title: "setting_video_viewer_auto_play_title".t(context: context),
|
||||||
subtitle: "setting_video_viewer_auto_play_subtitle".t(context: context),
|
subtitle: "setting_video_viewer_auto_play_subtitle".t(context: context),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
),
|
),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: useLoopVideo,
|
valueNotifier: useLoopVideo,
|
||||||
title: "setting_video_viewer_looping_title".t(context: context),
|
title: "setting_video_viewer_looping_title".t(context: context),
|
||||||
subtitle: "loop_videos_description".t(context: context),
|
subtitle: "loop_videos_description".t(context: context),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
),
|
),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: useOriginalVideo,
|
valueNotifier: useOriginalVideo,
|
||||||
title: "setting_video_viewer_original_video_title".t(context: context),
|
title: "setting_video_viewer_original_video_title".t(context: context),
|
||||||
subtitle: "setting_video_viewer_original_video_subtitle".t(context: context),
|
subtitle: "setting_video_viewer_original_video_subtitle".t(context: context),
|
||||||
|
onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,17 +4,18 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/config/app_config.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
|
||||||
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
import 'package:immich_mobile/domain/services/sync_linked_album.service.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup_album.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/setting_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
||||||
@@ -30,8 +31,8 @@ class DriftBackupSettings extends ConsumerWidget {
|
|||||||
title: "network_requirements".t(context: context),
|
title: "network_requirements".t(context: context),
|
||||||
icon: Icons.cell_tower,
|
icon: Icons.cell_tower,
|
||||||
),
|
),
|
||||||
const _UseCellularForVideosButton(),
|
const _UseWifiForUploadVideosButton(),
|
||||||
const _UseCellularForPhotosButton(),
|
const _UseWifiForUploadPhotosButton(),
|
||||||
if (CurrentPlatform.isAndroid) ...[
|
if (CurrentPlatform.isAndroid) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
SettingGroupTitle(
|
SettingGroupTitle(
|
||||||
@@ -95,13 +96,17 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final albumSyncEnable = ref.watch(appConfigProvider.select((c) => c.backup.syncAlbums));
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
children: [
|
children: [
|
||||||
Column(
|
StreamBuilder(
|
||||||
|
stream: Store.watch(StoreKey.syncAlbums),
|
||||||
|
initialData: Store.tryGet(StoreKey.syncAlbums) ?? false,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final albumSyncEnable = snapshot.data ?? false;
|
||||||
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
SettingListTile(
|
SettingListTile(
|
||||||
title: "sync_albums".t(context: context),
|
title: "sync_albums".t(context: context),
|
||||||
@@ -109,7 +114,7 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
|||||||
trailing: Switch(
|
trailing: Switch(
|
||||||
value: albumSyncEnable,
|
value: albumSyncEnable,
|
||||||
onChanged: (bool newValue) async {
|
onChanged: (bool newValue) async {
|
||||||
await ref.read(metadataProvider).write(MetadataKey.backupSyncAlbums, newValue);
|
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.syncAlbums, newValue);
|
||||||
|
|
||||||
if (newValue == true) {
|
if (newValue == true) {
|
||||||
await _manageLinkedAlbums();
|
await _manageLinkedAlbums();
|
||||||
@@ -147,6 +152,8 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -154,34 +161,60 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BackupSwitchTile extends ConsumerWidget {
|
class _SettingsSwitchTile extends ConsumerStatefulWidget {
|
||||||
final MetadataKey<bool> metadataKey;
|
final AppSettingsEnum<bool> appSettingsEnum;
|
||||||
final bool Function(AppConfig) selector;
|
|
||||||
final String titleKey;
|
final String titleKey;
|
||||||
final String subtitleKey;
|
final String subtitleKey;
|
||||||
final void Function(bool)? onChanged;
|
final void Function(bool?)? onChanged;
|
||||||
|
|
||||||
const _BackupSwitchTile({
|
const _SettingsSwitchTile({
|
||||||
required this.metadataKey,
|
required this.appSettingsEnum,
|
||||||
required this.selector,
|
|
||||||
required this.titleKey,
|
required this.titleKey,
|
||||||
required this.subtitleKey,
|
required this.subtitleKey,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState createState() => _SettingsSwitchTileState();
|
||||||
final value = ref.watch(appConfigProvider.select(selector));
|
}
|
||||||
|
|
||||||
|
class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> {
|
||||||
|
late final Stream<bool?> valueStream;
|
||||||
|
late final StreamSubscription<bool?> subscription;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
valueStream = Store.watch(widget.appSettingsEnum.storeKey).asBroadcastStream();
|
||||||
|
subscription = valueStream.listen((value) {
|
||||||
|
widget.onChanged?.call(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
subscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(left: 8.0),
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
child: SettingListTile(
|
child: SettingListTile(
|
||||||
title: titleKey.t(context: context),
|
title: widget.titleKey.t(context: context),
|
||||||
subtitle: subtitleKey.t(context: context),
|
subtitle: widget.subtitleKey.t(context: context),
|
||||||
trailing: Switch(
|
trailing: StreamBuilder(
|
||||||
|
stream: valueStream,
|
||||||
|
initialData: Store.tryGet(widget.appSettingsEnum.storeKey) ?? widget.appSettingsEnum.defaultValue,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final value = snapshot.data ?? false;
|
||||||
|
return Switch(
|
||||||
value: value,
|
value: value,
|
||||||
onChanged: (bool newValue) async {
|
onChanged: (bool newValue) async {
|
||||||
await ref.read(metadataProvider).write(metadataKey, newValue);
|
await ref.read(appSettingsServiceProvider).setSetting(widget.appSettingsEnum, newValue);
|
||||||
onChanged?.call(newValue);
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -189,28 +222,26 @@ class _BackupSwitchTile extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UseCellularForVideosButton extends StatelessWidget {
|
class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
||||||
const _UseCellularForVideosButton();
|
const _UseWifiForUploadVideosButton();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return _BackupSwitchTile(
|
return const _SettingsSwitchTile(
|
||||||
metadataKey: MetadataKey.backupUseCellularForVideos,
|
appSettingsEnum: AppSettingsEnum.useCellularForUploadVideos,
|
||||||
selector: (c) => c.backup.useCellularForVideos,
|
|
||||||
titleKey: "videos",
|
titleKey: "videos",
|
||||||
subtitleKey: "network_requirement_videos_upload",
|
subtitleKey: "network_requirement_videos_upload",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UseCellularForPhotosButton extends StatelessWidget {
|
class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
||||||
const _UseCellularForPhotosButton();
|
const _UseWifiForUploadPhotosButton();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return _BackupSwitchTile(
|
return const _SettingsSwitchTile(
|
||||||
metadataKey: MetadataKey.backupUseCellularForPhotos,
|
appSettingsEnum: AppSettingsEnum.useCellularForUploadPhotos,
|
||||||
selector: (c) => c.backup.useCellularForPhotos,
|
|
||||||
titleKey: "photos",
|
titleKey: "photos",
|
||||||
subtitleKey: "network_requirement_photos_upload",
|
subtitleKey: "network_requirement_photos_upload",
|
||||||
);
|
);
|
||||||
@@ -222,22 +253,29 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final fgService = ref.read(backgroundWorkerFgServiceProvider);
|
return _SettingsSwitchTile(
|
||||||
return _BackupSwitchTile(
|
appSettingsEnum: AppSettingsEnum.backupRequireCharging,
|
||||||
metadataKey: MetadataKey.backupRequireCharging,
|
|
||||||
selector: (c) => c.backup.requireCharging,
|
|
||||||
titleKey: "charging",
|
titleKey: "charging",
|
||||||
subtitleKey: "charging_requirement_mobile_backup",
|
subtitleKey: "charging_requirement_mobile_backup",
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
fgService.configure(requireCharging: value);
|
ref.read(backgroundWorkerFgServiceProvider).configure(requireCharging: value ?? false);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BackupDelaySlider extends ConsumerWidget {
|
class _BackupDelaySlider extends ConsumerStatefulWidget {
|
||||||
const _BackupDelaySlider();
|
const _BackupDelaySlider();
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<_BackupDelaySlider> createState() => _BackupDelaySliderState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> {
|
||||||
|
late final Stream<int?> valueStream;
|
||||||
|
late final StreamSubscription<int?> subscription;
|
||||||
|
late int currentValue;
|
||||||
|
|
||||||
static int backupDelayToSliderValue(int ms) => switch (ms) {
|
static int backupDelayToSliderValue(int ms) => switch (ms) {
|
||||||
5 => 0,
|
5 => 0,
|
||||||
30 => 1,
|
30 => 1,
|
||||||
@@ -260,9 +298,30 @@ class _BackupDelaySlider extends ConsumerWidget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
void initState() {
|
||||||
final triggerDelay = ref.watch(appConfigProvider.select((c) => c.backup.triggerDelay));
|
super.initState();
|
||||||
final currentValue = backupDelayToSliderValue(triggerDelay);
|
final initialValue =
|
||||||
|
Store.tryGet(AppSettingsEnum.backupTriggerDelay.storeKey) ?? AppSettingsEnum.backupTriggerDelay.defaultValue;
|
||||||
|
currentValue = backupDelayToSliderValue(initialValue);
|
||||||
|
|
||||||
|
valueStream = Store.watch(AppSettingsEnum.backupTriggerDelay.storeKey).asBroadcastStream();
|
||||||
|
subscription = valueStream.listen((value) {
|
||||||
|
if (mounted && value != null) {
|
||||||
|
setState(() {
|
||||||
|
currentValue = backupDelayToSliderValue(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
subscription.cancel();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -277,13 +336,14 @@ class _BackupDelaySlider extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
Slider(
|
Slider(
|
||||||
value: currentValue.toDouble(),
|
value: currentValue.toDouble(),
|
||||||
onChanged: (double v) async {
|
onChanged: (double v) {
|
||||||
final seconds = backupDelayToSeconds(v.toInt());
|
setState(() {
|
||||||
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
|
currentValue = v.toInt();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onChangeEnd: (double v) async {
|
onChangeEnd: (double v) async {
|
||||||
final seconds = backupDelayToSeconds(v.toInt());
|
final milliseconds = backupDelayToSeconds(v.toInt());
|
||||||
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
|
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.backupTriggerDelay, milliseconds);
|
||||||
},
|
},
|
||||||
max: 3.0,
|
max: 3.0,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/metadata_key.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart';
|
import 'package:immich_mobile/widgets/settings/networking_settings/endpoint_input.dart';
|
||||||
|
|
||||||
class ExternalNetworkPreference extends HookConsumerWidget {
|
class ExternalNetworkPreference extends HookConsumerWidget {
|
||||||
@@ -21,12 +23,11 @@ class ExternalNetworkPreference extends HookConsumerWidget {
|
|||||||
saveEndpointList() {
|
saveEndpointList() {
|
||||||
canSave.value = entries.value.every((e) => e.status == AuxCheckStatus.valid);
|
canSave.value = entries.value.every((e) => e.status == AuxCheckStatus.valid);
|
||||||
|
|
||||||
final urls = entries.value
|
final endpointList = entries.value.where((url) => url.status == AuxCheckStatus.valid).toList();
|
||||||
.where((e) => e.status == AuxCheckStatus.valid && e.url.isNotEmpty)
|
|
||||||
.map((e) => e.url)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
ref.read(metadataProvider).write(MetadataKey.networkExternalEndpointList, urls);
|
final jsonString = jsonEncode(endpointList);
|
||||||
|
|
||||||
|
Store.put(StoreKey.externalEndpointList, jsonString);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateValidationStatus(String url, int index, AuxCheckStatus status) {
|
updateValidationStatus(String url, int index, AuxCheckStatus status) {
|
||||||
@@ -68,13 +69,14 @@ class ExternalNetworkPreference extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final urls = ref.read(metadataProvider).systemConfig.network.externalEndpointList;
|
final jsonString = Store.tryGet(StoreKey.externalEndpointList);
|
||||||
|
|
||||||
if (urls.isEmpty) {
|
if (jsonString == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
entries.value = urls.map((url) => AuxilaryEndpoint(url: url, status: .valid)).toList();
|
final List<dynamic> jsonList = jsonDecode(jsonString);
|
||||||
|
entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList();
|
||||||
return null;
|
return null;
|
||||||
}, const []);
|
}, const []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/network.provider.dart';
|
import 'package:immich_mobile/providers/network.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart';
|
import 'package:immich_mobile/widgets/settings/networking_settings/external_network_preference.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart';
|
import 'package:immich_mobile/widgets/settings/networking_settings/local_network_preference.dart';
|
||||||
@@ -19,10 +20,7 @@ class NetworkingSettings extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final currentEndpoint = getServerUrl();
|
final currentEndpoint = getServerUrl();
|
||||||
final featureEnabled = useState(ref.read(systemConfigProvider).network.autoEndpointSwitching);
|
final featureEnabled = useAppSettingsState(AppSettingsEnum.autoEndpointSwitching);
|
||||||
useValueChanged<bool, void>(featureEnabled.value, (_, __) {
|
|
||||||
ref.read(metadataProvider).write(.networkAutoEndpointSwitching, featureEnabled.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<void> checkWifiReadPermission() async {
|
Future<void> checkWifiReadPermission() async {
|
||||||
final [hasLocationInUse, hasLocationAlways] = await Future.wait([
|
final [hasLocationInUse, hasLocationAlways] = await Future.wait([
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
import 'package:immich_mobile/providers/notification_permission.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_slider_list_tile.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
|
||||||
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class NotificationSetting extends HookConsumerWidget {
|
class NotificationSetting extends HookConsumerWidget {
|
||||||
@@ -13,6 +17,11 @@ class NotificationSetting extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final permissionService = ref.watch(notificationPermissionProvider);
|
final permissionService = ref.watch(notificationPermissionProvider);
|
||||||
|
|
||||||
|
final sliderValue = useAppSettingsState(AppSettingsEnum.uploadErrorNotificationGracePeriod);
|
||||||
|
final totalProgressValue = useAppSettingsState(AppSettingsEnum.backgroundBackupTotalProgress);
|
||||||
|
final singleProgressValue = useAppSettingsState(AppSettingsEnum.backgroundBackupSingleProgress);
|
||||||
|
|
||||||
final hasPermission = permissionService == PermissionStatus.granted;
|
final hasPermission = permissionService == PermissionStatus.granted;
|
||||||
|
|
||||||
openAppNotificationSettings(BuildContext ctx) {
|
openAppNotificationSettings(BuildContext ctx) {
|
||||||
@@ -35,6 +44,8 @@ class NotificationSetting extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final String formattedValue = _formatSliderValue(sliderValue.value.toDouble());
|
||||||
|
|
||||||
final notificationSettings = [
|
final notificationSettings = [
|
||||||
if (!hasPermission)
|
if (!hasPermission)
|
||||||
SettingsButtonListTile(
|
SettingsButtonListTile(
|
||||||
@@ -49,8 +60,44 @@ class NotificationSetting extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
enabled: hasPermission,
|
||||||
|
valueNotifier: totalProgressValue,
|
||||||
|
title: 'setting_notifications_total_progress_title'.tr(),
|
||||||
|
subtitle: 'setting_notifications_total_progress_subtitle'.tr(),
|
||||||
|
),
|
||||||
|
SettingsSwitchListTile(
|
||||||
|
enabled: hasPermission,
|
||||||
|
valueNotifier: singleProgressValue,
|
||||||
|
title: 'setting_notifications_single_progress_title'.tr(),
|
||||||
|
subtitle: 'setting_notifications_single_progress_subtitle'.tr(),
|
||||||
|
),
|
||||||
|
SettingsSliderListTile(
|
||||||
|
enabled: hasPermission,
|
||||||
|
valueNotifier: sliderValue,
|
||||||
|
text: 'setting_notifications_notify_failures_grace_period'.tr(namedArgs: {'duration': formattedValue}),
|
||||||
|
maxValue: 5.0,
|
||||||
|
noDivisons: 5,
|
||||||
|
label: formattedValue,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
return SettingsSubPageScaffold(settings: notificationSettings);
|
return SettingsSubPageScaffold(settings: notificationSettings);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _formatSliderValue(double v) {
|
||||||
|
if (v == 0.0) {
|
||||||
|
return 'setting_notifications_notify_immediately'.tr();
|
||||||
|
} else if (v == 1.0) {
|
||||||
|
return 'setting_notifications_notify_minutes'.tr(namedArgs: {'count': '30'});
|
||||||
|
} else if (v == 2.0) {
|
||||||
|
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '2'});
|
||||||
|
} else if (v == 3.0) {
|
||||||
|
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '8'});
|
||||||
|
} else if (v == 4.0) {
|
||||||
|
return 'setting_notifications_notify_hours'.tr(namedArgs: {'count': '24'});
|
||||||
|
} else {
|
||||||
|
return 'setting_notifications_notify_never'.tr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/colors.dart';
|
import 'package:immich_mobile/constants/colors.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/theme.provider.dart';
|
import 'package:immich_mobile/providers/theme.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/theme/color_scheme.dart';
|
import 'package:immich_mobile/theme/color_scheme.dart';
|
||||||
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
class PrimaryColorSetting extends HookConsumerWidget {
|
class PrimaryColorSetting extends HookConsumerWidget {
|
||||||
const PrimaryColorSetting({super.key});
|
const PrimaryColorSetting({super.key});
|
||||||
@@ -15,10 +17,18 @@ class PrimaryColorSetting extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final themeProvider = ref.read(immichThemeProvider);
|
final themeProvider = ref.read(immichThemeProvider);
|
||||||
final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme));
|
|
||||||
|
|
||||||
|
final primaryColorSetting = useAppSettingsState(AppSettingsEnum.primaryColor);
|
||||||
|
final systemPrimaryColorSetting = useAppSettingsState(AppSettingsEnum.dynamicTheme);
|
||||||
|
|
||||||
|
final currentPreset = useValueNotifier(ref.read(immichThemePresetProvider));
|
||||||
const tileSize = 55.0;
|
const tileSize = 55.0;
|
||||||
|
|
||||||
|
useValueChanged(
|
||||||
|
primaryColorSetting.value,
|
||||||
|
(_, __) => currentPreset.value = ImmichColorPreset.values.firstWhere((e) => e.name == primaryColorSetting.value),
|
||||||
|
);
|
||||||
|
|
||||||
void popBottomSheet() {
|
void popBottomSheet() {
|
||||||
Future.delayed(const Duration(milliseconds: 200), () {
|
Future.delayed(const Duration(milliseconds: 200), () {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -26,19 +36,24 @@ class PrimaryColorSetting extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUseSystemColorChange(bool newValue) {
|
onUseSystemColorChange(bool newValue) {
|
||||||
ref.read(metadataProvider).write(.themeDynamic, newValue);
|
systemPrimaryColorSetting.value = newValue;
|
||||||
|
ref.watch(dynamicThemeSettingProvider.notifier).state = newValue;
|
||||||
|
ref.invalidate(immichThemeProvider);
|
||||||
popBottomSheet();
|
popBottomSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPrimaryColorChange(ImmichColorPreset colorPreset) {
|
onPrimaryColorChange(ImmichColorPreset colorPreset) {
|
||||||
ref.read(metadataProvider).write(.themePrimaryColor, colorPreset);
|
primaryColorSetting.value = colorPreset.name;
|
||||||
|
ref.watch(immichThemePresetProvider.notifier).state = colorPreset;
|
||||||
|
ref.invalidate(immichThemeProvider);
|
||||||
|
|
||||||
//turn off system color setting
|
//turn off system color setting
|
||||||
if (themeConfig.dynamicTheme) {
|
if (systemPrimaryColorSetting.value) {
|
||||||
ref.read(metadataProvider).write(.themeDynamic, false);
|
onUseSystemColorChange(false);
|
||||||
}
|
} else {
|
||||||
popBottomSheet();
|
popBottomSheet();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
buildPrimaryColorTile({
|
buildPrimaryColorTile({
|
||||||
required Color topColor,
|
required Color topColor,
|
||||||
@@ -107,7 +122,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
|
|||||||
'theme_setting_system_primary_color_title'.tr(),
|
'theme_setting_system_primary_color_title'.tr(),
|
||||||
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5),
|
style: context.textTheme.bodyLarge?.copyWith(fontWeight: FontWeight.w500, height: 1.5),
|
||||||
),
|
),
|
||||||
value: themeConfig.dynamicTheme,
|
value: systemPrimaryColorSetting.value,
|
||||||
onChanged: onUseSystemColorChange,
|
onChanged: onUseSystemColorChange,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -125,7 +140,7 @@ class PrimaryColorSetting extends HookConsumerWidget {
|
|||||||
topColor: theme.light.primary,
|
topColor: theme.light.primary,
|
||||||
bottomColor: theme.dark.primary,
|
bottomColor: theme.dark.primary,
|
||||||
tileSize: tileSize,
|
tileSize: tileSize,
|
||||||
showSelector: themeConfig.primaryColor == preset && !themeConfig.dynamicTheme,
|
showSelector: currentPreset.value == preset && !systemPrimaryColorSetting.value,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
|||||||
@@ -3,48 +3,72 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
import 'package:immich_mobile/providers/theme.provider.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart';
|
import 'package:immich_mobile/widgets/settings/preference_settings/primary_color_setting.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
import 'package:immich_mobile/widgets/settings/setting_group_title.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
|
||||||
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
|
|
||||||
class ThemeSetting extends HookConsumerWidget {
|
class ThemeSetting extends HookConsumerWidget {
|
||||||
const ThemeSetting({super.key});
|
const ThemeSetting({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final currentTheme = useState(ref.read(appConfigProvider.select((config) => config.theme.mode)));
|
final currentThemeString = useAppSettingsState(AppSettingsEnum.themeMode);
|
||||||
|
final currentTheme = useValueNotifier(ref.read(immichThemeModeProvider));
|
||||||
final isDarkTheme = useValueNotifier(currentTheme.value == ThemeMode.dark);
|
final isDarkTheme = useValueNotifier(currentTheme.value == ThemeMode.dark);
|
||||||
final isSystemTheme = useValueNotifier(currentTheme.value == ThemeMode.system);
|
final isSystemTheme = useValueNotifier(currentTheme.value == ThemeMode.system);
|
||||||
final colorfulInterface = useValueNotifier(
|
|
||||||
ref.watch(appConfigProvider.select((config) => config.theme.colorfulInterface)),
|
final applyThemeToBackgroundSetting = useAppSettingsState(AppSettingsEnum.colorfulInterface);
|
||||||
|
final applyThemeToBackgroundProvider = useValueNotifier(ref.read(colorfulInterfaceSettingProvider));
|
||||||
|
|
||||||
|
useValueChanged(
|
||||||
|
currentThemeString.value,
|
||||||
|
(_, __) => currentTheme.value = switch (currentThemeString.value) {
|
||||||
|
"light" => ThemeMode.light,
|
||||||
|
"dark" => ThemeMode.dark,
|
||||||
|
_ => ThemeMode.system,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
useValueChanged(
|
||||||
|
applyThemeToBackgroundSetting.value,
|
||||||
|
(_, __) => applyThemeToBackgroundProvider.value = applyThemeToBackgroundSetting.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
void onThemeChange(bool isDark) {
|
void onThemeChange(bool isDark) {
|
||||||
currentTheme.value = isDark ? ThemeMode.dark : ThemeMode.light;
|
if (isDark) {
|
||||||
ref.read(metadataProvider).write(.themeMode, currentTheme.value);
|
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
|
||||||
|
currentThemeString.value = "dark";
|
||||||
|
} else {
|
||||||
|
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
|
||||||
|
currentThemeString.value = "light";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSystemThemeChange(bool isSystem) {
|
void onSystemThemeChange(bool isSystem) {
|
||||||
if (isSystem) {
|
if (isSystem) {
|
||||||
currentTheme.value = ThemeMode.system;
|
currentThemeString.value = "system";
|
||||||
isSystemTheme.value = true;
|
isSystemTheme.value = true;
|
||||||
|
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.system;
|
||||||
} else {
|
} else {
|
||||||
final currentSystemBrightness = context.platformBrightness;
|
final currentSystemBrightness = context.platformBrightness;
|
||||||
isSystemTheme.value = false;
|
isSystemTheme.value = false;
|
||||||
isDarkTheme.value = currentSystemBrightness == Brightness.dark;
|
isDarkTheme.value = currentSystemBrightness == Brightness.dark;
|
||||||
if (currentSystemBrightness == Brightness.light) {
|
if (currentSystemBrightness == Brightness.light) {
|
||||||
currentTheme.value = ThemeMode.light;
|
currentThemeString.value = "light";
|
||||||
|
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
|
||||||
} else if (currentSystemBrightness == Brightness.dark) {
|
} else if (currentSystemBrightness == Brightness.dark) {
|
||||||
currentTheme.value = ThemeMode.dark;
|
currentThemeString.value = "dark";
|
||||||
|
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ref.read(metadataProvider).write(.themeMode, currentTheme.value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSurfaceColorSettingChange(bool useColorfulInterface) {
|
void onSurfaceColorSettingChange(bool useColorfulInterface) {
|
||||||
ref.read(metadataProvider).write(.themeColorfulInterface, useColorfulInterface);
|
applyThemeToBackgroundSetting.value = useColorfulInterface;
|
||||||
colorfulInterface.value = useColorfulInterface;
|
ref.watch(colorfulInterfaceSettingProvider.notifier).state = useColorfulInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -67,7 +91,7 @@ class ThemeSetting extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const PrimaryColorSetting(),
|
const PrimaryColorSetting(),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: colorfulInterface,
|
valueNotifier: applyThemeToBackgroundProvider,
|
||||||
title: "theme_setting_colorful_interface_title".t(context: context),
|
title: "theme_setting_colorful_interface_title".t(context: context),
|
||||||
subtitle: 'theme_setting_colorful_interface_subtitle'.t(context: context),
|
subtitle: 'theme_setting_colorful_interface_subtitle'.t(context: context),
|
||||||
onChanged: onSurfaceColorSettingChange,
|
onChanged: onSurfaceColorSettingChange,
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
7.8.0
|
7.22.0
|
||||||
|
|||||||
Generated
+2
-2
@@ -4,7 +4,7 @@ Immich API
|
|||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 2.7.5
|
- API version: 2.7.5
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.22.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -146,7 +146,7 @@ Class | Method | HTTP request | Description
|
|||||||
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
|
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
|
||||||
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
|
*DownloadApi* | [**downloadArchive**](doc//DownloadApi.md#downloadarchive) | **POST** /download/archive | Download asset archive
|
||||||
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
|
*DownloadApi* | [**getDownloadInfo**](doc//DownloadApi.md#getdownloadinfo) | **POST** /download/info | Retrieve download information
|
||||||
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Delete a duplicate
|
*DuplicatesApi* | [**deleteDuplicate**](doc//DuplicatesApi.md#deleteduplicate) | **DELETE** /duplicates/{id} | Dismiss a duplicate group
|
||||||
*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | Delete duplicates
|
*DuplicatesApi* | [**deleteDuplicates**](doc//DuplicatesApi.md#deleteduplicates) | **DELETE** /duplicates | Delete duplicates
|
||||||
*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | Retrieve duplicates
|
*DuplicatesApi* | [**getAssetDuplicates**](doc//DuplicatesApi.md#getassetduplicates) | **GET** /duplicates | Retrieve duplicates
|
||||||
*DuplicatesApi* | [**resolveDuplicates**](doc//DuplicatesApi.md#resolveduplicates) | **POST** /duplicates/resolve | Resolve duplicate groups
|
*DuplicatesApi* | [**resolveDuplicates**](doc//DuplicatesApi.md#resolveduplicates) | **POST** /duplicates/resolve | Resolve duplicate groups
|
||||||
|
|||||||
Generated
+1
-2
@@ -15,8 +15,6 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:immich_mobile/utils/openapi_patching.dart';
|
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
@@ -29,6 +27,7 @@ part 'auth/api_key_auth.dart';
|
|||||||
part 'auth/oauth.dart';
|
part 'auth/oauth.dart';
|
||||||
part 'auth/http_basic_auth.dart';
|
part 'auth/http_basic_auth.dart';
|
||||||
part 'auth/http_bearer_auth.dart';
|
part 'auth/http_bearer_auth.dart';
|
||||||
|
part 'optional.dart';
|
||||||
|
|
||||||
part 'api/api_keys_api.dart';
|
part 'api/api_keys_api.dart';
|
||||||
part 'api/activities_api.dart';
|
part 'api/activities_api.dart';
|
||||||
|
|||||||
+4
-4
@@ -16,9 +16,9 @@ class DuplicatesApi {
|
|||||||
|
|
||||||
final ApiClient apiClient;
|
final ApiClient apiClient;
|
||||||
|
|
||||||
/// Delete a duplicate
|
/// Dismiss a duplicate group
|
||||||
///
|
///
|
||||||
/// Delete a single duplicate asset specified by its ID.
|
/// Dismiss a duplicate group by its ID, unlinking all assets in the group without deleting them.
|
||||||
///
|
///
|
||||||
/// Note: This method returns the HTTP [Response].
|
/// Note: This method returns the HTTP [Response].
|
||||||
///
|
///
|
||||||
@@ -51,9 +51,9 @@ class DuplicatesApi {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a duplicate
|
/// Dismiss a duplicate group
|
||||||
///
|
///
|
||||||
/// Delete a single duplicate asset specified by its ID.
|
/// Dismiss a duplicate group by its ID, unlinking all assets in the group without deleting them.
|
||||||
///
|
///
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
|
|||||||
Generated
+3
-3
@@ -143,19 +143,19 @@ class ApiClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> deserializeAsync(String value, String targetType, {bool growable = false,}) =>
|
Future<dynamic> deserializeAsync(String value, String targetType, {bool growable = false,}) async =>
|
||||||
// ignore: deprecated_member_use_from_same_package
|
// ignore: deprecated_member_use_from_same_package
|
||||||
deserialize(value, targetType, growable: growable);
|
deserialize(value, targetType, growable: growable);
|
||||||
|
|
||||||
@Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
|
@Deprecated('Scheduled for removal in OpenAPI Generator 6.x. Use deserializeAsync() instead.')
|
||||||
Future<dynamic> deserialize(String value, String targetType, {bool growable = false,}) async {
|
dynamic deserialize(String value, String targetType, {bool growable = false,}) {
|
||||||
// Remove all spaces. Necessary for regular expressions as well.
|
// Remove all spaces. Necessary for regular expressions as well.
|
||||||
targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments
|
targetType = targetType.replaceAll(' ', ''); // ignore: parameter_assignments
|
||||||
|
|
||||||
// If the expected target type is String, nothing to do...
|
// If the expected target type is String, nothing to do...
|
||||||
return targetType == 'String'
|
return targetType == 'String'
|
||||||
? value
|
? value
|
||||||
: fromJson(await compute((String j) => json.decode(j), value), targetType, growable: growable);
|
: fromJson(json.decode(value), targetType, growable: growable);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ignore: deprecated_member_use_from_same_package
|
// ignore: deprecated_member_use_from_same_package
|
||||||
|
|||||||
Generated
+3
@@ -220,6 +220,9 @@ Future<String> _decodeBodyBytes(Response response) async {
|
|||||||
/// Returns a valid [T] value found at the specified Map [key], null otherwise.
|
/// Returns a valid [T] value found at the specified Map [key], null otherwise.
|
||||||
T? mapValueOfType<T>(dynamic map, String key) {
|
T? mapValueOfType<T>(dynamic map, String key) {
|
||||||
final dynamic value = map is Map ? map[key] : null;
|
final dynamic value = map is Map ? map[key] : null;
|
||||||
|
if (T == double && value is int) {
|
||||||
|
return value.toDouble() as T;
|
||||||
|
}
|
||||||
return value is T ? value : null;
|
return value is T ? value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-3
@@ -66,12 +66,12 @@ class ActivityCreateDto {
|
|||||||
if (this.assetId != null) {
|
if (this.assetId != null) {
|
||||||
json[r'assetId'] = this.assetId;
|
json[r'assetId'] = this.assetId;
|
||||||
} else {
|
} else {
|
||||||
// json[r'assetId'] = null;
|
json[r'assetId'] = null;
|
||||||
}
|
}
|
||||||
if (this.comment != null) {
|
if (this.comment != null) {
|
||||||
json[r'comment'] = this.comment;
|
json[r'comment'] = this.comment;
|
||||||
} else {
|
} else {
|
||||||
// json[r'comment'] = null;
|
json[r'comment'] = null;
|
||||||
}
|
}
|
||||||
json[r'type'] = this.type;
|
json[r'type'] = this.type;
|
||||||
return json;
|
return json;
|
||||||
@@ -81,10 +81,20 @@ class ActivityCreateDto {
|
|||||||
/// [value] if it's a [Map], null otherwise.
|
/// [value] if it's a [Map], null otherwise.
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static ActivityCreateDto? fromJson(dynamic value) {
|
static ActivityCreateDto? fromJson(dynamic value) {
|
||||||
upgradeDto(value, "ActivityCreateDto");
|
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
assert(json.containsKey(r'albumId'), 'Required key "ActivityCreateDto[albumId]" is missing from JSON.');
|
||||||
|
assert(json[r'albumId'] != null, 'Required key "ActivityCreateDto[albumId]" has a null value in JSON.');
|
||||||
|
assert(json.containsKey(r'type'), 'Required key "ActivityCreateDto[type]" is missing from JSON.');
|
||||||
|
assert(json[r'type'] != null, 'Required key "ActivityCreateDto[type]" has a null value in JSON.');
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
return ActivityCreateDto(
|
return ActivityCreateDto(
|
||||||
albumId: mapValueOfType<String>(json, r'albumId')!,
|
albumId: mapValueOfType<String>(json, r'albumId')!,
|
||||||
assetId: mapValueOfType<String>(json, r'assetId'),
|
assetId: mapValueOfType<String>(json, r'assetId'),
|
||||||
|
|||||||
+18
-3
@@ -64,12 +64,12 @@ class ActivityResponseDto {
|
|||||||
if (this.assetId != null) {
|
if (this.assetId != null) {
|
||||||
json[r'assetId'] = this.assetId;
|
json[r'assetId'] = this.assetId;
|
||||||
} else {
|
} else {
|
||||||
// json[r'assetId'] = null;
|
json[r'assetId'] = null;
|
||||||
}
|
}
|
||||||
if (this.comment != null) {
|
if (this.comment != null) {
|
||||||
json[r'comment'] = this.comment;
|
json[r'comment'] = this.comment;
|
||||||
} else {
|
} else {
|
||||||
// json[r'comment'] = null;
|
json[r'comment'] = null;
|
||||||
}
|
}
|
||||||
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
json[r'createdAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
? this.createdAt.millisecondsSinceEpoch
|
? this.createdAt.millisecondsSinceEpoch
|
||||||
@@ -84,10 +84,25 @@ class ActivityResponseDto {
|
|||||||
/// [value] if it's a [Map], null otherwise.
|
/// [value] if it's a [Map], null otherwise.
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static ActivityResponseDto? fromJson(dynamic value) {
|
static ActivityResponseDto? fromJson(dynamic value) {
|
||||||
upgradeDto(value, "ActivityResponseDto");
|
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
assert(json.containsKey(r'assetId'), 'Required key "ActivityResponseDto[assetId]" is missing from JSON.');
|
||||||
|
assert(json.containsKey(r'createdAt'), 'Required key "ActivityResponseDto[createdAt]" is missing from JSON.');
|
||||||
|
assert(json[r'createdAt'] != null, 'Required key "ActivityResponseDto[createdAt]" has a null value in JSON.');
|
||||||
|
assert(json.containsKey(r'id'), 'Required key "ActivityResponseDto[id]" is missing from JSON.');
|
||||||
|
assert(json[r'id'] != null, 'Required key "ActivityResponseDto[id]" has a null value in JSON.');
|
||||||
|
assert(json.containsKey(r'type'), 'Required key "ActivityResponseDto[type]" is missing from JSON.');
|
||||||
|
assert(json[r'type'] != null, 'Required key "ActivityResponseDto[type]" has a null value in JSON.');
|
||||||
|
assert(json.containsKey(r'user'), 'Required key "ActivityResponseDto[user]" is missing from JSON.');
|
||||||
|
assert(json[r'user'] != null, 'Required key "ActivityResponseDto[user]" has a null value in JSON.');
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
return ActivityResponseDto(
|
return ActivityResponseDto(
|
||||||
assetId: mapValueOfType<String>(json, r'assetId'),
|
assetId: mapValueOfType<String>(json, r'assetId'),
|
||||||
comment: mapValueOfType<String>(json, r'comment'),
|
comment: mapValueOfType<String>(json, r'comment'),
|
||||||
|
|||||||
+11
-1
@@ -54,10 +54,20 @@ class ActivityStatisticsResponseDto {
|
|||||||
/// [value] if it's a [Map], null otherwise.
|
/// [value] if it's a [Map], null otherwise.
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static ActivityStatisticsResponseDto? fromJson(dynamic value) {
|
static ActivityStatisticsResponseDto? fromJson(dynamic value) {
|
||||||
upgradeDto(value, "ActivityStatisticsResponseDto");
|
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
assert(json.containsKey(r'comments'), 'Required key "ActivityStatisticsResponseDto[comments]" is missing from JSON.');
|
||||||
|
assert(json[r'comments'] != null, 'Required key "ActivityStatisticsResponseDto[comments]" has a null value in JSON.');
|
||||||
|
assert(json.containsKey(r'likes'), 'Required key "ActivityStatisticsResponseDto[likes]" is missing from JSON.');
|
||||||
|
assert(json[r'likes'] != null, 'Required key "ActivityStatisticsResponseDto[likes]" has a null value in JSON.');
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
return ActivityStatisticsResponseDto(
|
return ActivityStatisticsResponseDto(
|
||||||
comments: mapValueOfType<int>(json, r'comments')!,
|
comments: mapValueOfType<int>(json, r'comments')!,
|
||||||
likes: mapValueOfType<int>(json, r'likes')!,
|
likes: mapValueOfType<int>(json, r'likes')!,
|
||||||
|
|||||||
+9
-1
@@ -41,10 +41,18 @@ class AddUsersDto {
|
|||||||
/// [value] if it's a [Map], null otherwise.
|
/// [value] if it's a [Map], null otherwise.
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static AddUsersDto? fromJson(dynamic value) {
|
static AddUsersDto? fromJson(dynamic value) {
|
||||||
upgradeDto(value, "AddUsersDto");
|
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
assert(json.containsKey(r'albumUsers'), 'Required key "AddUsersDto[albumUsers]" is missing from JSON.');
|
||||||
|
assert(json[r'albumUsers'] != null, 'Required key "AddUsersDto[albumUsers]" has a null value in JSON.');
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
return AddUsersDto(
|
return AddUsersDto(
|
||||||
albumUsers: AlbumUserAddDto.listFromJson(json[r'albumUsers']),
|
albumUsers: AlbumUserAddDto.listFromJson(json[r'albumUsers']),
|
||||||
);
|
);
|
||||||
|
|||||||
+9
-1
@@ -41,10 +41,18 @@ class AdminOnboardingUpdateDto {
|
|||||||
/// [value] if it's a [Map], null otherwise.
|
/// [value] if it's a [Map], null otherwise.
|
||||||
// ignore: prefer_constructors_over_static_methods
|
// ignore: prefer_constructors_over_static_methods
|
||||||
static AdminOnboardingUpdateDto? fromJson(dynamic value) {
|
static AdminOnboardingUpdateDto? fromJson(dynamic value) {
|
||||||
upgradeDto(value, "AdminOnboardingUpdateDto");
|
|
||||||
if (value is Map) {
|
if (value is Map) {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
assert(json.containsKey(r'isOnboarded'), 'Required key "AdminOnboardingUpdateDto[isOnboarded]" is missing from JSON.');
|
||||||
|
assert(json[r'isOnboarded'] != null, 'Required key "AdminOnboardingUpdateDto[isOnboarded]" has a null value in JSON.');
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
return AdminOnboardingUpdateDto(
|
return AdminOnboardingUpdateDto(
|
||||||
isOnboarded: mapValueOfType<bool>(json, r'isOnboarded')!,
|
isOnboarded: mapValueOfType<bool>(json, r'isOnboarded')!,
|
||||||
);
|
);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user