mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fa425fbf7 | |||
| d0f0e59334 | |||
| 8c91a5a6c4 | |||
| 3f6b3e1b99 | |||
| 2365c4b79c | |||
| 33713cf1d5 | |||
| 33107fce08 | |||
| 87be5e846f | |||
| 1459dc7c4e | |||
| 9fb2a1a02a | |||
| 80378a42a7 | |||
| 03395d9249 | |||
| 64e2a66136 | |||
| e7d225dcc5 | |||
| 296b696be9 | |||
| ca95fbb47d | |||
| 68a718fab4 | |||
| 6fdb841732 | |||
| 3a10b438ce | |||
| 7386226235 | |||
| 8eb14555dc | |||
| 4c36716d57 | |||
| 5933664862 | |||
| cc56f86333 | |||
| 8cff5883b5 | |||
| 3d320d9751 | |||
| b9e0e65bdb | |||
| 88e5e8d6ea | |||
| ee107c98d5 | |||
| affe0ac5ee | |||
| f1d8ab8aae | |||
| c0898b96ca | |||
| 5e9bda7fab | |||
| b60e9c6771 | |||
| b554664791 | |||
| 97c62136b7 |
@@ -392,6 +392,8 @@ jobs:
|
|||||||
node-version-file: './server/.nvmrc'
|
node-version-file: './server/.nvmrc'
|
||||||
cache: 'pnpm'
|
cache: 'pnpm'
|
||||||
cache-dependency-path: '**/pnpm-lock.yaml'
|
cache-dependency-path: '**/pnpm-lock.yaml'
|
||||||
|
- name: Setup Mise
|
||||||
|
uses: immich-app/devtools/actions/use-mise@035e80a7d4355d5f087ffb95db9e4a0944c04e56 # use-mise-action-v1.1.3
|
||||||
- name: Run pnpm install
|
- name: Run pnpm install
|
||||||
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
run: SHARP_IGNORE_GLOBAL_LIBVIPS=true pnpm install --frozen-lockfile
|
||||||
- name: Run medium tests
|
- name: Run medium tests
|
||||||
|
|||||||
Vendored
+2
-1
@@ -26,7 +26,8 @@
|
|||||||
},
|
},
|
||||||
"[svelte]": {
|
"[svelte]": {
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
"editor.defaultFormatter": "svelte.svelte-vscode",
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"tailwindCSS.lint.suggestCanonicalClasses": "ignore"
|
||||||
},
|
},
|
||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ VectorChord is the successor extension to pgvecto.rs, allowing for higher perfor
|
|||||||
|
|
||||||
### Migrating from pgvecto.rs
|
### Migrating from pgvecto.rs
|
||||||
|
|
||||||
Support for pgvecto.rs will be dropped in a later release, hence we recommend all users currently using pgvecto.rs to migrate to VectorChord at their convenience. There are two primary approaches to do so.
|
Support for pgvecto.rs has been dropped as of 3.0, hence all users currently using pgvecto.rs should migrate to VectorChord. There are two primary approaches to do so.
|
||||||
|
|
||||||
The easiest option is to have both extensions installed during the migration:
|
The easiest option is to have both extensions installed during the migration:
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ Information on the current workers can be found [here](/administration/jobs-work
|
|||||||
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
|
| `DB_PASSWORD` | Database password | `postgres` | server, database<sup>\*1</sup> |
|
||||||
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
|
| `DB_DATABASE_NAME` | Database name | `immich` | server, database<sup>\*1</sup> |
|
||||||
| `DB_SSL_MODE` | Database SSL mode | | server |
|
| `DB_SSL_MODE` | Database SSL mode | | server |
|
||||||
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`, `pgvecto.rs`]) | | server |
|
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database vector extension (one of [`vectorchord`, `pgvector`]) | | server |
|
||||||
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
|
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server |
|
||||||
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
|
| `DB_STORAGE_TYPE` | Optimize concurrent IO on SSDs or sequential IO on HDDs ([`SSD`, `HDD`])<sup>\*3</sup> | `SSD` | database |
|
||||||
|
|
||||||
|
|||||||
@@ -130,7 +130,3 @@ These storage mediums have different performance characteristics. As a result, t
|
|||||||
#### Can I use the new database image as a general PostgreSQL image outside of Immich?
|
#### Can I use the new database image as a general PostgreSQL image outside of Immich?
|
||||||
|
|
||||||
It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image.
|
It’s a standard PostgreSQL container image that additionally contains the VectorChord, pgvector, and (optionally) pgvecto.rs extensions. If you were using the previous pgvecto.rs image for other purposes, you can similarly do so with this image.
|
||||||
|
|
||||||
#### If pgvecto.rs and pgvector still work, why should I switch to VectorChord?
|
|
||||||
|
|
||||||
VectorChord is faster, more stable, uses less RAM, and (with the settings Immich uses) offers higher-quality results than pgvector and pgvecto.rs. This translates to better search and facial recognition experiences. In addition, pgvecto.rs support will be dropped in the future, so changing it sooner will avoid disruption.
|
|
||||||
|
|||||||
@@ -351,7 +351,7 @@ describe(`/oauth`, () => {
|
|||||||
const callbackParams = await loginWithOAuth('oauth-no-auto-register');
|
const callbackParams = await loginWithOAuth('oauth-no-auto-register');
|
||||||
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
const { status, body } = await request(app).post('/oauth/callback').send(callbackParams);
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toEqual(errorDto.badRequest('User does not exist and auto registering is disabled.'));
|
expect(body).toEqual(errorDto.badRequest('OAuth authentication failed'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should link to an existing user by email', async () => {
|
it('should link to an existing user by email', async () => {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ describe('/users', () => {
|
|||||||
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
|
||||||
|
|
||||||
expect(status).toBe(400);
|
expect(status).toBe(400);
|
||||||
expect(body).toMatchObject(errorDto.badRequest('Email already in use by another account'));
|
expect(body).toMatchObject(errorDto.badRequest('Email is not available'));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update my email', async () => {
|
it('should update my email', async () => {
|
||||||
|
|||||||
@@ -32,8 +32,12 @@ export function generateThumbhash(rng: SeededRandom): string {
|
|||||||
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDuration(rng: SeededRandom): string {
|
export function generateDuration(rng: SeededRandom): number {
|
||||||
return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`;
|
return (
|
||||||
|
rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS) *
|
||||||
|
1000 +
|
||||||
|
rng.nextInt(0, 1000)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUUID(): string {
|
export function generateUUID(): string {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export type MockTimelineAsset = {
|
|||||||
isTrashed: boolean;
|
isTrashed: boolean;
|
||||||
isVideo: boolean;
|
isVideo: boolean;
|
||||||
isImage: boolean;
|
isImage: boolean;
|
||||||
duration: string | null;
|
duration: number | null;
|
||||||
projectionType: string | null;
|
projectionType: string | null;
|
||||||
livePhotoVideoId: string | null;
|
livePhotoVideoId: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ test.describe('Timeline', () => {
|
|||||||
await page.keyboard.down('Shift');
|
await page.keyboard.down('Shift');
|
||||||
await thumbnailUtils.withAssetId(page, assets[2].id).hover();
|
await thumbnailUtils.withAssetId(page, assets[2].id).hover();
|
||||||
await expect(
|
await expect(
|
||||||
thumbnailUtils.locator(page).locator('.absolute.top-0.h-full.w-full.bg-immich-primary.opacity-40'),
|
thumbnailUtils.locator(page).locator('.absolute.top-0.size-full.bg-immich-primary.opacity-40'),
|
||||||
).toHaveCount(3);
|
).toHaveCount(3);
|
||||||
await thumbnailUtils.selectButton(page, assets[2].id).click();
|
await thumbnailUtils.selectButton(page, assets[2].id).click();
|
||||||
await page.keyboard.up('Shift');
|
await page.keyboard.up('Shift');
|
||||||
|
|||||||
+1
-1
Submodule e2e/test-assets updated: 0eac5a3738...6742055402
@@ -1761,6 +1761,7 @@
|
|||||||
"play_original_video": "Play original video",
|
"play_original_video": "Play original video",
|
||||||
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
|
"play_original_video_setting_description": "Prefer playback of original videos rather than transcoded videos. If original asset is not compatible it may not playback correctly.",
|
||||||
"play_transcoded_video": "Play transcoded video",
|
"play_transcoded_video": "Play transcoded video",
|
||||||
|
"playback_speed": "Playback speed",
|
||||||
"please_auth_to_access": "Please authenticate to access",
|
"please_auth_to_access": "Please authenticate to access",
|
||||||
"port": "Port",
|
"port": "Port",
|
||||||
"preferences_settings_subtitle": "Manage the app's preferences",
|
"preferences_settings_subtitle": "Manage the app's preferences",
|
||||||
@@ -2436,6 +2437,7 @@
|
|||||||
"workflows": "Workflows",
|
"workflows": "Workflows",
|
||||||
"workflows_help_text": "Workflows automate actions on your assets based on triggers and filters",
|
"workflows_help_text": "Workflows automate actions on your assets based on triggers and filters",
|
||||||
"wrong_pin_code": "Wrong PIN code",
|
"wrong_pin_code": "Wrong PIN code",
|
||||||
|
"x_of_total": "{x}/{total}",
|
||||||
"year": "Year",
|
"year": "Year",
|
||||||
"years_ago": "{years, plural, one {# year} other {# years}} ago",
|
"years_ago": "{years, plural, one {# year} other {# years}} ago",
|
||||||
"yes": "Yes",
|
"yes": "Yes",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ ENV LD_PRELOAD=/usr/lib/libmimalloc.so.2 \
|
|||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
# Pascal support was dropped in 9.11
|
# Pascal support was dropped in 9.11
|
||||||
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 && \
|
apt-get install --no-install-recommends -yqq libcudnn9-cuda-12=9.10.2.21-1 tzdata && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/*
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|||||||
@@ -22,10 +22,19 @@ opentofu = "1.11.6"
|
|||||||
java = "21.0.2"
|
java = "21.0.2"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"]
|
[tools."github:CQLabs/homebrew-dcm"]
|
||||||
version = "1.35.1"
|
version = "1.37.0"
|
||||||
bin = "dcm"
|
bin = "dcm"
|
||||||
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
postinstall = "chmod +x $MISE_TOOL_INSTALL_PATH/dcm"
|
||||||
|
|
||||||
|
[tools."github:jellyfin/jellyfin-ffmpeg"]
|
||||||
|
version = "7.1.3-6"
|
||||||
|
|
||||||
|
[tools."github:jellyfin/jellyfin-ffmpeg".platforms]
|
||||||
|
linux-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linux64-gpl.tar.xz" }
|
||||||
|
linux-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_linuxarm64-gpl.tar.xz" }
|
||||||
|
macos-x64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_mac64-gpl.tar.xz" }
|
||||||
|
macos-arm64 = { asset_pattern = "jellyfin-ffmpeg_*_portable_macarm64-gpl.tar.xz" }
|
||||||
|
|
||||||
[settings]
|
[settings]
|
||||||
experimental = true
|
experimental = true
|
||||||
pin = true
|
pin = true
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.41.7",
|
"dart.flutterSdkPath": ".fvm/versions/3.41.9",
|
||||||
"dart.lineLength": 120,
|
"dart.lineLength": 120,
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.rulers": [
|
"editor.rulers": [
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
version: '>=1.29.0 <=1.36.0'
|
version: '>=1.29.0 <=1.37.0'
|
||||||
|
|||||||
+3358
File diff suppressed because it is too large
Load Diff
@@ -2,9 +2,6 @@ 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);
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
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)';
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
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,15 +1,7 @@
|
|||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
|
||||||
enum Setting<T> {
|
enum Setting<T> {
|
||||||
tilesPerRow<int>(StoreKey.tilesPerRow, 4),
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, false);
|
||||||
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,97 +4,59 @@ 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),
|
||||||
|
|
||||||
autoPlayVideo<bool>._(139),
|
syncMigrationStatus<String>._(1013),
|
||||||
albumGridView<bool>._(140),
|
|
||||||
|
|
||||||
// Image viewer navigation settings
|
// Legacy keys that have been migrated to the new metadata store
|
||||||
tapToNavigate<bool>._(141),
|
legacyBackupRequireCharging<bool>._(7),
|
||||||
|
legacyBackupTriggerDelay<int>._(8),
|
||||||
// Experimental stuff
|
legacySyncAlbums<bool>._(131),
|
||||||
photoManagerCustomFilter<bool>._(1000),
|
legacyEnableBackup<bool>._(1003),
|
||||||
betaPromptShown<bool>._(1001),
|
legacyUseWifiForUploadVideos<bool>._(1004),
|
||||||
betaTimeline<bool>._(1002),
|
legacyUseWifiForUploadPhotos<bool>._(1005),
|
||||||
enableBackup<bool>._(1003),
|
legacySelectedAlbumSortOrder<int>._(113),
|
||||||
useWifiForUploadVideos<bool>._(1004),
|
legacySelectedAlbumSortReverse<bool>._(123),
|
||||||
useWifiForUploadPhotos<bool>._(1005),
|
legacyAlbumGridView<bool>._(140),
|
||||||
needBetaMigration<bool>._(1006),
|
legacyAutoEndpointSwitching<bool>._(132),
|
||||||
// TODO: Remove this after patching open-api
|
legacyPreferredWifiName<String>._(133),
|
||||||
shouldResetSync<bool>._(1007),
|
legacyLocalEndpoint<String>._(134),
|
||||||
|
legacyExternalEndpointList<String>._(135),
|
||||||
// Free up space
|
legacyCustomHeaders<String>._(127),
|
||||||
cleanupKeepFavorites<bool>._(1008),
|
legacyLoopVideo<bool>._(117),
|
||||||
cleanupKeepMediaType<int>._(1009),
|
legacyLoadOriginalVideo<bool>._(136),
|
||||||
cleanupKeepAlbumIds<String>._(1010),
|
legacyAutoPlayVideo<bool>._(139),
|
||||||
cleanupCutoffDaysAgo<int>._(1011),
|
legacyTapToNavigate<bool>._(141),
|
||||||
cleanupDefaultsInitialized<bool>._(1012),
|
legacyPreferRemoteImage<bool>._(116),
|
||||||
|
legacyLoadOriginal<bool>._(101),
|
||||||
syncMigrationStatus<String>._(1013);
|
legacyPrimaryColor<String>._(128),
|
||||||
|
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,15 +11,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/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';
|
||||||
@@ -39,16 +38,15 @@ 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}) => _foregroundHostApi.configure(
|
Future<void> configure({int? minimumDelaySeconds, bool? requireCharging}) {
|
||||||
|
final backup = MetadataStore.appConfig.backup;
|
||||||
|
return _foregroundHostApi.configure(
|
||||||
BackgroundWorkerSettings(
|
BackgroundWorkerSettings(
|
||||||
minimumDelaySeconds:
|
minimumDelaySeconds: minimumDelaySeconds ?? backup.triggerDelay,
|
||||||
minimumDelaySeconds ??
|
requiresCharging: requireCharging ?? backup.requireCharging,
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -71,7 +69,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
BackgroundWorkerFlutterApi.setUp(this);
|
BackgroundWorkerFlutterApi.setUp(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get _isBackupEnabled => _ref?.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup) ?? false;
|
bool get _isBackupEnabled => MetadataStore.appConfig.backup.enabled;
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -93,8 +93,7 @@ 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. If ignoreIcloudAssets is enabled,
|
// does not include changes for cloud albums.
|
||||||
// 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/store.model.dart';
|
import 'package:immich_mobile/domain/models/metadata_key.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/store.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/metadata.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 [ILogRepository], and manages log levels
|
/// writes them to a persistent [LogRepository], and manages log levels via
|
||||||
/// via [IStoreRepository]
|
/// [MetadataRepository].
|
||||||
class LogService {
|
class LogService {
|
||||||
final LogRepository _logRepository;
|
final LogRepository _logRepository;
|
||||||
final DriftStoreRepository _storeRepository;
|
final MetadataRepository _metadataRepository;
|
||||||
|
|
||||||
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 DriftStoreRepository storeRepository,
|
required MetadataRepository metadataRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
_instance ??= await create(
|
_instance ??= await create(
|
||||||
logRepository: logRepository,
|
logRepository: logRepository,
|
||||||
storeRepository: storeRepository,
|
metadataRepository: metadataRepository,
|
||||||
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 DriftStoreRepository storeRepository,
|
required MetadataRepository metadataRepository,
|
||||||
bool shouldBuffer = true,
|
bool shouldBuffer = true,
|
||||||
}) async {
|
}) async {
|
||||||
final instance = LogService._(logRepository, storeRepository, shouldBuffer);
|
final instance = LogService._(logRepository, metadataRepository, shouldBuffer);
|
||||||
await logRepository.truncate(limit: kLogTruncateLimit);
|
await logRepository.truncate(limit: kLogTruncateLimit);
|
||||||
final level = await instance._storeRepository.tryGet(StoreKey.logLevel) ?? LogLevel.info.index;
|
final level = instance._metadataRepository.systemConfig.logLevel;
|
||||||
Logger.root.level = Level.LEVELS.elementAtOrNull(level) ?? Level.INFO;
|
Logger.root.level = Level.LEVELS.elementAtOrNull(level.index) ?? Level.INFO;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogService._(this._logRepository, this._storeRepository, this._shouldBuffer) {
|
LogService._(this._logRepository, this._metadataRepository, 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 _storeRepository.upsert(StoreKey.logLevel, level.index);
|
await _metadataRepository.write(MetadataKey.logLevel, level);
|
||||||
Logger.root.level = level.toLevel();
|
Logger.root.level = level.toLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -192,23 +192,23 @@ class SyncStreamService {
|
|||||||
case SyncEntityType.assetV1:
|
case SyncEntityType.assetV1:
|
||||||
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
||||||
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
||||||
await _runWithManageMediaPermission(
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
logContext: "Trashed Assets",
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
action: () async {
|
}
|
||||||
await _handleRemoteDeleted(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id));
|
return;
|
||||||
await _applyRemoteRestoreToLocal();
|
case SyncEntityType.assetV2:
|
||||||
},
|
final remoteSyncAssets = data.cast<SyncAssetV2>();
|
||||||
);
|
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets);
|
||||||
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case SyncEntityType.assetDeleteV1:
|
case SyncEntityType.assetDeleteV1:
|
||||||
await _runWithManageMediaPermission(
|
|
||||||
logContext: "Deleted Assets",
|
|
||||||
action: () async {
|
|
||||||
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
||||||
await _handleRemoteDeleted(remoteSyncAssets.map((e) => e.assetId));
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
},
|
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
|
||||||
);
|
}
|
||||||
return _syncStreamRepository.deleteAssetsV1(data.cast());
|
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
|
||||||
case SyncEntityType.assetExifV1:
|
case SyncEntityType.assetExifV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
||||||
case SyncEntityType.assetEditV1:
|
case SyncEntityType.assetEditV1:
|
||||||
@@ -221,8 +221,12 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
|
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
|
||||||
case SyncEntityType.partnerAssetV1:
|
case SyncEntityType.partnerAssetV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
|
||||||
|
case SyncEntityType.partnerAssetV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner');
|
||||||
case SyncEntityType.partnerAssetBackfillV1:
|
case SyncEntityType.partnerAssetBackfillV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
|
||||||
|
case SyncEntityType.partnerAssetBackfillV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner backfill');
|
||||||
case SyncEntityType.partnerAssetDeleteV1:
|
case SyncEntityType.partnerAssetDeleteV1:
|
||||||
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
|
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
|
||||||
case SyncEntityType.partnerAssetExifV1:
|
case SyncEntityType.partnerAssetExifV1:
|
||||||
@@ -243,10 +247,16 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
|
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
|
||||||
case SyncEntityType.albumAssetCreateV1:
|
case SyncEntityType.albumAssetCreateV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
|
||||||
|
case SyncEntityType.albumAssetCreateV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset create');
|
||||||
case SyncEntityType.albumAssetUpdateV1:
|
case SyncEntityType.albumAssetUpdateV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
|
||||||
|
case SyncEntityType.albumAssetUpdateV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset update');
|
||||||
case SyncEntityType.albumAssetBackfillV1:
|
case SyncEntityType.albumAssetBackfillV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
|
||||||
|
case SyncEntityType.albumAssetBackfillV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset backfill');
|
||||||
case SyncEntityType.albumAssetExifCreateV1:
|
case SyncEntityType.albumAssetExifCreateV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
|
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
|
||||||
case SyncEntityType.albumAssetExifUpdateV1:
|
case SyncEntityType.albumAssetExifUpdateV1:
|
||||||
@@ -348,6 +358,47 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) async {
|
||||||
|
if (batchData.isEmpty) return;
|
||||||
|
|
||||||
|
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events');
|
||||||
|
|
||||||
|
final List<SyncAssetV2> assets = [];
|
||||||
|
final List<SyncAssetExifV1> exifs = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (final data in batchData) {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
final assetData = payload['asset'];
|
||||||
|
final exifData = payload['exif'];
|
||||||
|
|
||||||
|
if (assetData == null || exifData == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV2.fromJson(assetData);
|
||||||
|
final exif = SyncAssetExifV1.fromJson(exifData);
|
||||||
|
|
||||||
|
if (asset != null && exif != null) {
|
||||||
|
assets.add(asset);
|
||||||
|
exifs.add(exif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.isNotEmpty && exifs.isNotEmpty) {
|
||||||
|
await _syncStreamRepository.updateAssetsV2(assets, debugLabel: 'websocket-batch');
|
||||||
|
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
|
||||||
|
_logger.info('Successfully processed ${assets.length} assets in batch');
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetUploadReadyV2 websocket batch events", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> handleWsAssetEditReadyV1(dynamic data) async {
|
Future<void> handleWsAssetEditReadyV1(dynamic data) async {
|
||||||
_logger.info('Processing AssetEditReadyV1 event');
|
_logger.info('Processing AssetEditReadyV1 event');
|
||||||
|
|
||||||
@@ -388,6 +439,41 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleWsAssetEditReadyV2(dynamic data) async {
|
||||||
|
_logger.info('Processing AssetEditReadyV2 event');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
throw ArgumentError("Invalid data format for AssetEditReadyV2 event");
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
|
||||||
|
if (payload['asset'] == null) {
|
||||||
|
throw ArgumentError("Missing 'asset' field in AssetEditReadyV2 event data");
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV2.fromJson(payload['asset']);
|
||||||
|
if (asset == null) {
|
||||||
|
throw ArgumentError("Failed to parse 'asset' field in AssetEditReadyV2 event data");
|
||||||
|
}
|
||||||
|
|
||||||
|
final assetEdits = (payload['edit'] as List<dynamic>)
|
||||||
|
.map((e) => SyncAssetEditV1.fromJson(e))
|
||||||
|
.whereType<SyncAssetEditV1>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
|
||||||
|
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
||||||
if (remoteIds.isEmpty) {
|
if (remoteIds.isEmpty) {
|
||||||
return Future.value();
|
return Future.value();
|
||||||
@@ -424,20 +510,22 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runWithManageMediaPermission({
|
Future<void> _syncAssetTrashStatus(List<String> remoteIds) async {
|
||||||
required String logContext,
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
required Future<void> Function() action,
|
_logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
}) async {
|
|
||||||
if (!CurrentPlatform.isAndroid || !Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final hasPermission = await _localFilesManager.hasManageMediaPermission();
|
await _handleRemoteDeleted(remoteIds);
|
||||||
if (!hasPermission) {
|
await _applyRemoteRestoreToLocal();
|
||||||
_logger.warning("sync $logContext cannot proceed because MANAGE_MEDIA permission is missing");
|
}
|
||||||
|
|
||||||
|
Future<void> _syncAssetDeletion(List<String> remoteIds) async {
|
||||||
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
|
_logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await action();
|
await _handleRemoteDeleted(remoteIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,9 @@ 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';
|
||||||
|
|
||||||
@@ -39,14 +38,16 @@ enum TimelineOrigin {
|
|||||||
|
|
||||||
class TimelineFactory {
|
class TimelineFactory {
|
||||||
final DriftTimelineRepository _timelineRepository;
|
final DriftTimelineRepository _timelineRepository;
|
||||||
final SettingsService _settingsService;
|
final MetadataRepository _metadataRepository;
|
||||||
|
|
||||||
const TimelineFactory({required DriftTimelineRepository timelineRepository, required SettingsService settingsService})
|
const TimelineFactory({
|
||||||
: _timelineRepository = timelineRepository,
|
required DriftTimelineRepository timelineRepository,
|
||||||
_settingsService = settingsService;
|
required MetadataRepository metadataRepository,
|
||||||
|
}) : _timelineRepository = timelineRepository,
|
||||||
|
_metadataRepository = metadataRepository;
|
||||||
|
|
||||||
GroupAssetsBy get groupBy {
|
GroupAssetsBy get groupBy {
|
||||||
final group = GroupAssetsBy.values[_settingsService.get(Setting.groupAssetsBy)];
|
final group = _metadataRepository.appConfig.timeline.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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncWebsocketBatch(List<dynamic> batchData) {
|
Future<void> syncWebsocketBatchV1(List<dynamic> batchData) {
|
||||||
if (_syncWebsocketTask != null) {
|
if (_syncWebsocketTask != null) {
|
||||||
return _syncWebsocketTask!.future;
|
return _syncWebsocketTask!.future;
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,17 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncWebsocketEdit(dynamic data) {
|
Future<void> syncWebsocketBatchV2(List<dynamic> batchData) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetUploadReadyV2Batch(batchData);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditV1(dynamic data) {
|
||||||
if (_syncWebsocketTask != null) {
|
if (_syncWebsocketTask != null) {
|
||||||
return _syncWebsocketTask!.future;
|
return _syncWebsocketTask!.future;
|
||||||
}
|
}
|
||||||
@@ -206,6 +216,16 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditV2(dynamic data) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetEditReadyV2(data);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> syncLinkedAlbum() {
|
Future<void> syncLinkedAlbum() {
|
||||||
if (_linkedAlbumSyncTask != null) {
|
if (_linkedAlbumSyncTask != null) {
|
||||||
return _linkedAlbumSyncTask!.future;
|
return _linkedAlbumSyncTask!.future;
|
||||||
@@ -242,7 +262,17 @@ Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => ru
|
|||||||
debugLabel: 'websocket-batch',
|
debugLabel: 'websocket-batch',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV2Batch(batchData),
|
||||||
|
debugLabel: 'websocket-batch',
|
||||||
|
);
|
||||||
|
|
||||||
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
|
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
|
||||||
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
|
||||||
debugLabel: 'websocket-edit',
|
debugLabel: 'websocket-edit',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetEditReadyV2(dynamic data) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV2(data),
|
||||||
|
debugLabel: 'websocket-edit',
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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/exif.model.dart';
|
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
import 'package:openapi/api.dart' as api;
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
@@ -36,7 +35,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ 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 {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
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";
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
// 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,6 +13,7 @@ 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';
|
||||||
@@ -53,6 +54,7 @@ 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'},
|
||||||
)
|
)
|
||||||
@@ -84,7 +86,7 @@ class Drift extends $Drift {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 24;
|
int get schemaVersion => 25;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -250,6 +252,9 @@ 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);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+12
-4
@@ -43,9 +43,11 @@ 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/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/metadata.entity.drift.dart'
|
||||||
as i22;
|
as i22;
|
||||||
import 'package:drift/internal/modular.dart' as i23;
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i23;
|
||||||
|
import 'package:drift/internal/modular.dart' as i24;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -89,9 +91,12 @@ 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);
|
||||||
i22.MergedAssetDrift get mergedAssetDrift => i23.ReadDatabaseContainer(
|
late final i22.$MetadataEntityTable metadataEntity = i22.$MetadataEntityTable(
|
||||||
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?>>();
|
||||||
@@ -129,6 +134,7 @@ 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,
|
||||||
@@ -389,4 +395,6 @@ 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,6 +12375,574 @@ 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,
|
||||||
@@ -12399,6 +12967,7 @@ 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) {
|
||||||
@@ -12517,6 +13086,11 @@ 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');
|
||||||
}
|
}
|
||||||
@@ -12547,6 +13121,7 @@ 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,
|
||||||
@@ -12572,5 +13147,6 @@ i1.OnUpgrade stepByStep({
|
|||||||
from21To22: from21To22,
|
from21To22: from21To22,
|
||||||
from22To23: from22To23,
|
from22To23: from22To23,
|
||||||
from23To24: from23To24,
|
from23To24: from23To24,
|
||||||
|
from24To25: from24To25,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
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,9 +3,7 @@ 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';
|
||||||
@@ -38,7 +36,6 @@ 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(
|
||||||
@@ -46,19 +43,25 @@ class SyncApiRepository {
|
|||||||
types: [
|
types: [
|
||||||
SyncRequestType.authUsersV1,
|
SyncRequestType.authUsersV1,
|
||||||
SyncRequestType.usersV1,
|
SyncRequestType.usersV1,
|
||||||
SyncRequestType.assetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.assetsV2
|
||||||
|
: SyncRequestType.assetsV1,
|
||||||
SyncRequestType.assetExifsV1,
|
SyncRequestType.assetExifsV1,
|
||||||
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
|
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
|
||||||
SyncRequestType.assetMetadataV1,
|
SyncRequestType.assetMetadataV1,
|
||||||
SyncRequestType.partnersV1,
|
SyncRequestType.partnersV1,
|
||||||
SyncRequestType.partnerAssetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.partnerAssetsV2
|
||||||
|
: SyncRequestType.partnerAssetsV1,
|
||||||
SyncRequestType.partnerAssetExifsV1,
|
SyncRequestType.partnerAssetExifsV1,
|
||||||
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
|
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
|
||||||
SyncRequestType.albumsV1
|
SyncRequestType.albumsV1
|
||||||
else
|
else
|
||||||
SyncRequestType.albumsV2,
|
SyncRequestType.albumsV2,
|
||||||
SyncRequestType.albumUsersV1,
|
SyncRequestType.albumUsersV1,
|
||||||
SyncRequestType.albumAssetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.albumAssetsV2
|
||||||
|
: SyncRequestType.albumAssetsV1,
|
||||||
SyncRequestType.albumAssetExifsV1,
|
SyncRequestType.albumAssetExifsV1,
|
||||||
SyncRequestType.albumToAssetsV1,
|
SyncRequestType.albumToAssetsV1,
|
||||||
SyncRequestType.memoriesV1,
|
SyncRequestType.memoriesV1,
|
||||||
@@ -67,10 +70,10 @@ class SyncApiRepository {
|
|||||||
SyncRequestType.partnerStacksV1,
|
SyncRequestType.partnerStacksV1,
|
||||||
SyncRequestType.userMetadataV1,
|
SyncRequestType.userMetadataV1,
|
||||||
SyncRequestType.peopleV1,
|
SyncRequestType.peopleV1,
|
||||||
if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1,
|
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)
|
||||||
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2,
|
? SyncRequestType.assetFacesV2
|
||||||
|
: SyncRequestType.assetFacesV1,
|
||||||
],
|
],
|
||||||
reset: shouldReset,
|
|
||||||
).toJson(),
|
).toJson(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -94,9 +97,6 @@ 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;
|
||||||
@@ -153,6 +153,7 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
||||||
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
|
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
|
||||||
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.assetV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
|
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
|
||||||
@@ -160,7 +161,9 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
||||||
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.partnerAssetV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.partnerAssetBackfillV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
@@ -171,8 +174,11 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
||||||
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
|
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
|
||||||
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetCreateV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetUpdateV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetBackfillV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
|
|||||||
@@ -220,6 +220,44 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateAssetsV2(Iterable<SyncAssetV2> data, {String debugLabel = 'user'}) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final asset in data) {
|
||||||
|
final companion = RemoteAssetEntityCompanion(
|
||||||
|
name: Value(asset.originalFileName),
|
||||||
|
type: Value(asset.type.toAssetType()),
|
||||||
|
createdAt: Value.absentIfNull(asset.fileCreatedAt),
|
||||||
|
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
|
||||||
|
durationMs: Value(asset.duration),
|
||||||
|
checksum: Value(asset.checksum),
|
||||||
|
isFavorite: Value(asset.isFavorite),
|
||||||
|
ownerId: Value(asset.ownerId),
|
||||||
|
localDateTime: Value(asset.localDateTime),
|
||||||
|
thumbHash: Value(asset.thumbhash),
|
||||||
|
deletedAt: Value(asset.deletedAt),
|
||||||
|
visibility: Value(asset.visibility.toAssetVisibility()),
|
||||||
|
livePhotoVideoId: Value(asset.livePhotoVideoId),
|
||||||
|
stackId: Value(asset.stackId),
|
||||||
|
libraryId: Value(asset.libraryId),
|
||||||
|
width: Value(asset.width),
|
||||||
|
height: Value(asset.height),
|
||||||
|
isEdited: Value(asset.isEdited),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
companion.copyWith(id: Value(asset.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAssetsV2 - $debugLabel', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ 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';
|
||||||
@@ -53,7 +54,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();
|
await migrateDatabaseIfNeeded(drift);
|
||||||
|
|
||||||
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) {
|
||||||
@@ -241,7 +242,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(immichThemeModeProvider),
|
themeMode: ref.watch(appConfigProvider.select((config) => config.theme.mode)),
|
||||||
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/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.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(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
_enableSyncUploadAlbum.value = ref.read(metadataProvider).appConfig.backup.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(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
final enableSyncUploadAlbum = ref.read(metadataProvider).appConfig.backup.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 = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
final isBackupEnabled = MetadataStore.appConfig.backup.enabled;
|
||||||
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,14 +3,12 @@ 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/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.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';
|
||||||
|
|
||||||
@@ -21,18 +19,20 @@ 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 previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
final previousBackup = ref.read(metadataProvider).appConfig.backup;
|
||||||
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
final previousCellularForVideos = previousBackup.useCellularForVideos;
|
||||||
|
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 currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
final currentBackup = ref.read(metadataProvider).appConfig.backup;
|
||||||
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
final currentCellularForVideos = currentBackup.useCellularForVideos;
|
||||||
|
final currentCellularForPhotos = currentBackup.useCellularForPhotos;
|
||||||
|
|
||||||
if (currentWifiReqForVideos == previousWifiReqForVideos &&
|
if (currentCellularForVideos == previousCellularForVideos &&
|
||||||
currentWifiReqForPhotos == previousWifiReqForPhotos) {
|
currentCellularForPhotos == previousCellularForPhotos) {
|
||||||
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 = ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
final isBackupEnabled = MetadataStore.appConfig.backup.enabled;
|
||||||
if (!isBackupEnabled) {
|
if (!isBackupEnabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
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' hide Store;
|
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/store.model.dart';
|
import 'package:immich_mobile/domain/models/metadata_key.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 = "";
|
||||||
@@ -24,17 +22,14 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
final headers = useState<List<SettingsHeader>>([]);
|
final headers = useState<List<SettingsHeader>>([]);
|
||||||
final setInitialHeaders = useState(false);
|
final setInitialHeaders = useState(false);
|
||||||
|
|
||||||
var headersStr = Store.get(StoreKey.customHeaders, "");
|
final storedHeaders = ref.read(metadataProvider).systemConfig.network.customHeaders;
|
||||||
if (!setInitialHeaders.value) {
|
if (!setInitialHeaders.value) {
|
||||||
if (headersStr.isNotEmpty) {
|
storedHeaders.forEach((k, v) {
|
||||||
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) {
|
||||||
@@ -88,18 +83,17 @@ class HeaderSettingsPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveHeaders(WidgetRef ref, List<SettingsHeader> headers) async {
|
saveHeaders(WidgetRef ref, List<SettingsHeader> headers) async {
|
||||||
final headersMap = {};
|
final headersMap = <String, String>{};
|
||||||
for (var header in headers) {
|
for (final 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
var encoded = jsonEncode(headersMap);
|
final apiService = ref.read(apiServiceProvider);
|
||||||
await Store.put(StoreKey.customHeaders, encoded);
|
await ref.read(metadataProvider).write(MetadataKey.networkCustomHeaders, headersMap);
|
||||||
await ref.read(apiServiceProvider).updateHeaders();
|
await apiService.updateHeaders();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ 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';
|
||||||
@@ -35,7 +36,7 @@ class BootstrapErrorWidget extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext _) {
|
Widget build(BuildContext _) {
|
||||||
final immichTheme = defaultColorPreset.themeOfPreset;
|
final immichTheme = MetadataKey.themePrimaryColor.defaultValue.themeOfPreset;
|
||||||
|
|
||||||
return EasyLocalization(
|
return EasyLocalization(
|
||||||
supportedLocales: locales.values.toList(),
|
supportedLocales: locales.values.toList(),
|
||||||
@@ -340,7 +341,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
await backgroundManager.hashAssets();
|
await backgroundManager.hashAssets();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
if (MetadataStore.appConfig.backup.syncAlbums) {
|
||||||
await backgroundManager.syncLinkedAlbum();
|
await backgroundManager.syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -369,7 +370,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
Future<void> _resumeBackup(DriftBackupNotifier notifier) async {
|
||||||
final isEnableBackup = Store.get(StoreKey.enableBackup, false);
|
final isEnableBackup = MetadataStore.appConfig.backup.enabled;
|
||||||
|
|
||||||
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,19 +58,11 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
final appSettings = ref.read(appSettingsServiceProvider);
|
final albumConfig = ref.read(metadataProvider).appConfig.album;
|
||||||
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: albumSortMode, isReverse: savedIsReverse);
|
sort = AlbumSort(mode: albumConfig.sortMode, isReverse: albumConfig.isReverse);
|
||||||
isGrid = savedIsGrid;
|
isGrid = albumConfig.isGrid;
|
||||||
});
|
});
|
||||||
|
|
||||||
ref.read(remoteAlbumProvider.notifier).refresh();
|
ref.read(remoteAlbumProvider.notifier).refresh();
|
||||||
@@ -102,7 +94,7 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
isGrid = !isGrid;
|
isGrid = !isGrid;
|
||||||
});
|
});
|
||||||
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.albumGridView, isGrid);
|
ref.read(metadataProvider).write(MetadataKey.albumIsGrid, isGrid);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeFilter(QuickFilterMode mode) {
|
void changeFilter(QuickFilterMode mode) {
|
||||||
@@ -118,9 +110,9 @@ class _AlbumSelectorState extends ConsumerState<AlbumSelector> {
|
|||||||
this.sort = sort;
|
this.sort = sort;
|
||||||
});
|
});
|
||||||
|
|
||||||
final appSettings = ref.read(appSettingsServiceProvider);
|
final metadata = ref.read(metadataProvider);
|
||||||
await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortOrder, sort.mode.storeIndex);
|
await metadata.write(MetadataKey.albumSortMode, sort.mode);
|
||||||
await appSettings.setSetting(AppSettingsEnum.selectedAlbumSortReverse, sort.isReverse);
|
await metadata.write(MetadataKey.albumIsReverse, sort.isReverse);
|
||||||
|
|
||||||
await sortAlbums();
|
await sortAlbums();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ 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/services/app_settings.service.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.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';
|
||||||
@@ -217,7 +216,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(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.tapToNavigate);
|
final tapToNavigate = ref.read(metadataProvider).appConfig.viewer.tapToNavigate;
|
||||||
if (!tapToNavigate) {
|
if (!tapToNavigate) {
|
||||||
_viewer.toggleControls();
|
_viewer.toggleControls();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,21 +3,17 @@ 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';
|
||||||
|
|
||||||
@@ -120,7 +116,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(settingsProvider).get<bool>(Setting.loadOriginalVideo);
|
final isOriginalVideo = ref.read(metadataProvider).appConfig.viewer.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'
|
||||||
@@ -147,7 +143,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
|||||||
|
|
||||||
if (ref.read(assetViewerProvider).showingDetails) return;
|
if (ref.read(assetViewerProvider).showingDetails) return;
|
||||||
|
|
||||||
final autoPlayVideo = AppSetting.get(Setting.autoPlayVideo);
|
final autoPlayVideo = ref.read(metadataProvider).appConfig.viewer.autoPlayVideo;
|
||||||
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play();
|
if (autoPlayVideo || widget.asset.isMotionPhoto) await _notifier.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +182,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(appSettingsServiceProvider).getSetting<bool>(AppSettingsEnum.loopVideo);
|
final loopVideo = ref.read(metadataProvider).appConfig.viewer.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/services/app_settings.service.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.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(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
_isEnabled = ref.read(metadataProvider).appConfig.backup.enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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(appSettingsServiceProvider).setSetting(AppSettingsEnum.enableBackup, value);
|
await ref.read(metadataProvider).write(MetadataKey.backupEnabled, value);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_isEnabled = value;
|
_isEnabled = value;
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ 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';
|
||||||
@@ -189,4 +188,4 @@ ImageProvider? getThumbnailImageProvider(BaseAsset asset, {Size size = kThumbnai
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
bool _shouldUseLocalAsset(BaseAsset asset) =>
|
||||||
asset.hasLocal && (!asset.hasRemote || !AppSetting.get(Setting.preferRemoteImage)) && !asset.isEdited;
|
asset.hasLocal && (!asset.hasRemote || !MetadataStore.appConfig.image.preferRemote) && !asset.isEdited;
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
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';
|
||||||
@@ -103,7 +102,7 @@ class LocalFullImageProvider extends CancellableImageProvider<LocalFullImageProv
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final loadOriginal = Store.get(StoreKey.loadOriginal, false);
|
final loadOriginal = MetadataStore.appConfig.image.loadOriginal;
|
||||||
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,9 +1,8 @@
|
|||||||
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';
|
||||||
@@ -121,7 +120,7 @@ class RemoteFullImageProvider extends CancellableImageProvider<RemoteFullImagePr
|
|||||||
edited: key.edited,
|
edited: key.edited,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
final loadOriginal = assetType == AssetType.image && AppSetting.get(Setting.loadOriginal);
|
final loadOriginal = assetType == AssetType.image && MetadataStore.appConfig.image.loadOriginal;
|
||||||
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
yield* loadRequest(previewRequest, decode, isFinal: !loadOriginal);
|
||||||
|
|
||||||
if (!loadOriginal) {
|
if (!loadOriginal) {
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ 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/setting.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';
|
||||||
|
|
||||||
class ThumbnailTile extends ConsumerStatefulWidget {
|
class ThumbnailTile extends ConsumerStatefulWidget {
|
||||||
@@ -61,7 +60,7 @@ class _ThumbnailTileState extends ConsumerState<ThumbnailTile> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final bool storageIndicator =
|
final bool storageIndicator =
|
||||||
ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator))) && widget.showStorageIndicator;
|
ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)) && 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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly);
|
ref.read(metadataProvider).write(MetadataKey.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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived);
|
ref.read(metadataProvider).write(MetadataKey.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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners);
|
ref.read(metadataProvider).write(MetadataKey.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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeDays);
|
ref.read(metadataProvider).write(MetadataKey.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 appSettingsService = ref.read(appSettingsServiceProvider);
|
final mapConfig = ref.read(appConfigProvider.select((config) => config.map));
|
||||||
return MapState(
|
return MapState(
|
||||||
themeMode: ThemeMode.values[appSettingsService.getSetting(AppSettingsEnum.mapThemeMode)],
|
themeMode: mapConfig.themeMode,
|
||||||
onlyFavorites: appSettingsService.getSetting(AppSettingsEnum.mapShowFavoriteOnly),
|
onlyFavorites: mapConfig.favoritesOnly,
|
||||||
includeArchived: appSettingsService.getSetting(AppSettingsEnum.mapIncludeArchived),
|
includeArchived: mapConfig.includeArchived,
|
||||||
withPartners: appSettingsService.getSetting(AppSettingsEnum.mapwithPartners),
|
withPartners: mapConfig.withPartners,
|
||||||
relativeDays: appSettingsService.getSetting(AppSettingsEnum.mapRelativeDate),
|
relativeDays: mapConfig.relativeDays,
|
||||||
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,12 +1,11 @@
|
|||||||
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/setting.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||||
|
|
||||||
class TimelineArgs {
|
class TimelineArgs {
|
||||||
@@ -93,7 +92,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 ?? GroupAssetsBy.values[ref.watch(settingsProvider).get(Setting.groupAssetsBy)];
|
final groupBy = args.groupBy ?? ref.watch(appConfigProvider.select((config) => config.timeline.groupAssetsBy));
|
||||||
|
|
||||||
final timelineService = ref.watch(timelineServiceProvider);
|
final timelineService = ref.watch(timelineServiceProvider);
|
||||||
yield* timelineService.watchBuckets().map((buckets) {
|
yield* timelineService.watchBuckets().map((buckets) {
|
||||||
@@ -102,7 +101,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/setting.model.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/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(settingsProvider.select((s) => s.get(Setting.tilesPerRow))),
|
columnCount: ref.watch(appConfigProvider.select((config) => config.timeline.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(settingsProvider).get(Setting.tilesPerRow);
|
final currentTilesPerRow = ref.read(appConfigProvider.select((config) => config.timeline.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(settingsProvider.notifier).set(Setting.tilesPerRow, _perRow);
|
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, _perRow);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,16 +5,15 @@ 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 }
|
||||||
@@ -106,7 +105,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(appSettingsServiceProvider).getSetting(AppSettingsEnum.syncAlbums);
|
final isAlbumLinkedSyncEnable = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bool syncSuccess = false;
|
bool syncSuccess = false;
|
||||||
@@ -136,7 +135,7 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _resumeBackup() async {
|
Future<void> _resumeBackup() async {
|
||||||
final isEnableBackup = _ref.read(appSettingsServiceProvider).getSetting(AppSettingsEnum.enableBackup);
|
final isEnableBackup = _ref.read(metadataProvider).appConfig.backup.enabled;
|
||||||
|
|
||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
@@ -127,7 +131,8 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
await _apiService.updateHeaders();
|
await _apiService.updateHeaders();
|
||||||
|
|
||||||
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
final serverEndpoint = Store.get(StoreKey.serverEndpoint);
|
||||||
final customHeaders = Store.tryGet(StoreKey.customHeaders);
|
final headerMap = MetadataStore.systemConfig.network.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
|
||||||
@@ -144,7 +149,6 @@ 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) {
|
||||||
@@ -176,19 +180,19 @@ class AuthNotifier extends StateNotifier<AuthState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveWifiName(String wifiName) async {
|
Future<void> saveWifiName(String wifiName) async {
|
||||||
await Store.put(StoreKey.preferredWifiName, wifiName);
|
await _ref.read(metadataProvider).write(MetadataKey.networkPreferredWifiName, wifiName);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveLocalEndpoint(String url) async {
|
Future<void> saveLocalEndpoint(String url) async {
|
||||||
await Store.put(StoreKey.localEndpoint, url);
|
await _ref.read(metadataProvider).write(MetadataKey.networkLocalEndpoint, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getSavedWifiName() {
|
String? getSavedWifiName() {
|
||||||
return Store.tryGet(StoreKey.preferredWifiName);
|
return _ref.read(metadataProvider).systemConfig.network.preferredWifiName;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getSavedLocalEndpoint() {
|
String? getSavedLocalEndpoint() {
|
||||||
return Store.tryGet(StoreKey.localEndpoint);
|
return _ref.read(metadataProvider).systemConfig.network.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/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/infrastructure/repositories/metadata.repository.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,27 +54,25 @@ 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(appSettingsServiceProvider),
|
ref.watch(metadataProvider),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
class CleanupNotifier extends StateNotifier<CleanupState> {
|
class CleanupNotifier extends StateNotifier<CleanupState> {
|
||||||
final CleanupService _cleanupService;
|
final CleanupService _cleanupService;
|
||||||
final String? _userId;
|
final String? _userId;
|
||||||
final AppSettingsService _appSettingsService;
|
final MetadataRepository _metadataRepository;
|
||||||
|
|
||||||
CleanupNotifier(this._cleanupService, this._userId, this._appSettingsService) : super(const CleanupState()) {
|
CleanupNotifier(this._cleanupService, this._userId, this._metadataRepository) : super(const CleanupState()) {
|
||||||
_loadPersistedSettings();
|
_loadPersistedSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _loadPersistedSettings() {
|
void _loadPersistedSettings() {
|
||||||
final keepFavorites = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepFavorites);
|
final cleanup = _metadataRepository.appConfig.cleanup;
|
||||||
final keepMediaTypeIndex = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepMediaType);
|
final keepFavorites = cleanup.keepFavorites;
|
||||||
final keepAlbumIdsString = _appSettingsService.getSetting(AppSettingsEnum.cleanupKeepAlbumIds);
|
final keepMediaType = cleanup.keepMediaType;
|
||||||
final cutoffDaysAgo = _appSettingsService.getSetting(AppSettingsEnum.cleanupCutoffDaysAgo);
|
final keepAlbumIds = cleanup.keepAlbumIds.toSet();
|
||||||
|
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(
|
||||||
@@ -89,18 +87,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;
|
||||||
_appSettingsService.setSetting(AppSettingsEnum.cleanupCutoffDaysAgo, daysAgo);
|
_metadataRepository.write(.cleanupCutoffDaysAgo, daysAgo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeepMediaType(AssetKeepType keepMediaType) {
|
void setKeepMediaType(AssetKeepType keepMediaType) {
|
||||||
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
|
state = state.copyWith(keepMediaType: keepMediaType, assetsToDelete: []);
|
||||||
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepMediaType, keepMediaType.index);
|
_metadataRepository.write(.cleanupKeepMediaType, keepMediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setKeepFavorites(bool keepFavorites) {
|
void setKeepFavorites(bool keepFavorites) {
|
||||||
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
|
state = state.copyWith(keepFavorites: keepFavorites, assetsToDelete: []);
|
||||||
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepFavorites, keepFavorites);
|
_metadataRepository.write(.cleanupKeepFavorites, keepFavorites);
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleKeepAlbum(String albumId) {
|
void toggleKeepAlbum(String albumId) {
|
||||||
@@ -120,7 +118,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _persistExcludedAlbumIds(Set<String> albumIds) {
|
void _persistExcludedAlbumIds(Set<String> albumIds) {
|
||||||
_appSettingsService.setSetting(AppSettingsEnum.cleanupKeepAlbumIds, albumIds.join(','));
|
_metadataRepository.write(.cleanupKeepAlbumIds, albumIds.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
|
void cleanupStaleAlbumIds(Set<String> existingAlbumIds) {
|
||||||
@@ -133,7 +131,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
|
void applyDefaultAlbumSelections(List<(String id, String name)> albums) {
|
||||||
final isInitialized = _appSettingsService.getSetting(AppSettingsEnum.cleanupDefaultsInitialized);
|
final isInitialized = _metadataRepository.appConfig.cleanup.defaultsInitialized;
|
||||||
if (isInitialized) return;
|
if (isInitialized) return;
|
||||||
|
|
||||||
final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums);
|
final toKeep = _cleanupService.getDefaultKeepAlbumIds(albums);
|
||||||
@@ -144,7 +142,7 @@ class CleanupNotifier extends StateNotifier<CleanupState> {
|
|||||||
_persistExcludedAlbumIds(keepAlbumIds);
|
_persistExcludedAlbumIds(keepAlbumIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_appSettingsService.setSetting(AppSettingsEnum.cleanupDefaultsInitialized, true);
|
_metadataRepository.write(.cleanupDefaultsInitialized, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> scanAssets() async {
|
Future<void> scanAssets() async {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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/setting.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';
|
||||||
|
|
||||||
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),
|
||||||
settingsService: ref.watch(settingsProvider),
|
metadataRepository: ref.watch(metadataProvider),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -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/app_settings.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/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 appSettingsProvider = ref.read(appSettingsServiceProvider);
|
final mapConfig = ref.read(appConfigProvider.select((config) => config.map));
|
||||||
|
|
||||||
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: ThemeMode.values[appSettingsProvider.getSetting<int>(AppSettingsEnum.mapThemeMode)],
|
themeMode: mapConfig.themeMode,
|
||||||
showFavoriteOnly: appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapShowFavoriteOnly),
|
showFavoriteOnly: mapConfig.favoritesOnly,
|
||||||
includeArchived: appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapIncludeArchived),
|
includeArchived: mapConfig.includeArchived,
|
||||||
withPartners: appSettingsProvider.getSetting<bool>(AppSettingsEnum.mapwithPartners),
|
withPartners: mapConfig.withPartners,
|
||||||
relativeTime: appSettingsProvider.getSetting<int>(AppSettingsEnum.mapRelativeDate),
|
relativeTime: mapConfig.relativeDays,
|
||||||
lightStyleFetched: AsyncData(lightStyleUrl),
|
lightStyleFetched: AsyncData(lightStyleUrl),
|
||||||
darkStyleFetched: AsyncData(darkStyleUrl),
|
darkStyleFetched: AsyncData(darkStyleUrl),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchTheme(ThemeMode mode) {
|
void switchTheme(ThemeMode mode) {
|
||||||
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapThemeMode, mode.index);
|
ref.read(metadataProvider).write(MetadataKey.mapThemeMode, mode);
|
||||||
state = state.copyWith(themeMode: mode);
|
state = state.copyWith(themeMode: mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
void switchFavoriteOnly(bool isFavoriteOnly) {
|
void switchFavoriteOnly(bool isFavoriteOnly) {
|
||||||
ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapShowFavoriteOnly, isFavoriteOnly);
|
ref.read(metadataProvider).write(MetadataKey.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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapIncludeArchived, isIncludeArchived);
|
ref.read(metadataProvider).write(MetadataKey.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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapwithPartners, isWithPartners);
|
ref.read(metadataProvider).write(MetadataKey.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(appSettingsServiceProvider).setSetting(AppSettingsEnum.mapRelativeDate, relativeTime);
|
ref.read(metadataProvider).write(MetadataKey.mapRelativeDate, relativeTime);
|
||||||
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
|
state = state.copyWith(relativeTime: relativeTime, shouldRefetchMarkers: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,17 @@
|
|||||||
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/theme_data.dart';
|
|
||||||
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
import 'package:immich_mobile/theme/dynamic_theme.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/theme/theme_data.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 primaryColorPreset = ref.read(immichThemePresetProvider);
|
final themeConfig = ref.watch(appConfigProvider.select((config) => config.theme));
|
||||||
final useSystemColor = ref.watch(dynamicThemeSettingProvider);
|
|
||||||
final useColorfulInterface = ref.watch(colorfulInterfaceSettingProvider);
|
|
||||||
final ImmichTheme? dynamicTheme = DynamicTheme.theme;
|
|
||||||
final currentTheme = (useSystemColor && dynamicTheme != null) ? dynamicTheme : primaryColorPreset.themeOfPreset;
|
|
||||||
|
|
||||||
return useColorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme);
|
final ImmichTheme? dynamicTheme = DynamicTheme.theme;
|
||||||
|
final currentTheme = (themeConfig.dynamicTheme && dynamicTheme != null)
|
||||||
|
? dynamicTheme
|
||||||
|
: themeConfig.primaryColor.themeOfPreset;
|
||||||
|
|
||||||
|
return themeConfig.colorfulInterface ? currentTheme : decolorizeSurfaces(theme: currentTheme);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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';
|
||||||
@@ -94,8 +95,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
state = const WebsocketState(isConnected: false, socket: null);
|
state = const WebsocketState(isConnected: false, socket: null);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReady);
|
socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReadyV1);
|
||||||
socket.on('AssetEditReadyV1', _handleSyncAssetEditReady);
|
socket.on('AssetUploadReadyV2', _handleSyncAssetUploadReadyV2);
|
||||||
|
socket.on('AssetEditReadyV1', _handleSyncAssetEditReadyV1);
|
||||||
|
socket.on('AssetEditReadyV2', _handleSyncAssetEditReadyV2);
|
||||||
socket.on('on_config_update', _handleOnConfigUpdate);
|
socket.on('on_config_update', _handleOnConfigUpdate);
|
||||||
socket.on('on_new_release', _handleReleaseUpdates);
|
socket.on('on_new_release', _handleReleaseUpdates);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -163,24 +166,33 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
_ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion);
|
_ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetUploadReady(dynamic data) {
|
void _handleSyncAssetUploadReadyV1(dynamic data) {
|
||||||
_batchedAssetUploadReady.add(data);
|
_batchedAssetUploadReady.add(data);
|
||||||
_batchDebouncer.run(_processBatchedAssetUploadReady);
|
_batchDebouncer.run(_processBatchedAssetUploadReadyV1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetEditReady(dynamic data) {
|
void _handleSyncAssetUploadReadyV2(dynamic data) {
|
||||||
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEdit(data));
|
_batchedAssetUploadReady.add(data);
|
||||||
|
_batchDebouncer.run(_processBatchedAssetUploadReadyV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _processBatchedAssetUploadReady() {
|
void _handleSyncAssetEditReadyV1(dynamic data) {
|
||||||
|
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSyncAssetEditReadyV2(dynamic data) {
|
||||||
|
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _processBatchedAssetUploadReadyV1() {
|
||||||
if (_batchedAssetUploadReady.isEmpty) {
|
if (_batchedAssetUploadReady.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
_ref.read(backgroundSyncProvider).syncWebsocketBatch(_batchedAssetUploadReady.toList()).then((_) {
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
if (isSyncAlbumEnabled) {
|
if (isSyncAlbumEnabled) {
|
||||||
_ref.read(backgroundSyncProvider).syncLinkedAlbum();
|
_ref.read(backgroundSyncProvider).syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
@@ -192,6 +204,27 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
|
|
||||||
_batchedAssetUploadReady.clear();
|
_batchedAssetUploadReady.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _processBatchedAssetUploadReadyV2() {
|
||||||
|
if (_batchedAssetUploadReady.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isSyncAlbumEnabled = _ref.read(metadataProvider).appConfig.backup.syncAlbums;
|
||||||
|
try {
|
||||||
|
unawaited(
|
||||||
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
|
if (isSyncAlbumEnabled) {
|
||||||
|
_ref.read(backgroundSyncProvider).syncLinkedAlbum();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
_log.severe("Error processing batched AssetUploadReadyV2 events: $error");
|
||||||
|
}
|
||||||
|
|
||||||
|
_batchedAssetUploadReady.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebsocketState>((ref) {
|
final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebsocketState>((ref) {
|
||||||
|
|||||||
@@ -1,46 +1,40 @@
|
|||||||
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>((ref) => AuthRepository(ref.watch(driftProvider)));
|
final authRepositoryProvider = Provider<AuthRepository>(
|
||||||
|
(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);
|
const AuthRepository(this._drift, this._metadata);
|
||||||
|
|
||||||
Future<void> clearLocalData() async {
|
Future<void> clearLocalData() async {
|
||||||
await SyncStreamRepository(_drift).reset();
|
await SyncStreamRepository(_drift).reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getEndpointSwitchingFeature() {
|
bool getEndpointSwitchingFeature() {
|
||||||
return Store.tryGet(StoreKey.autoEndpointSwitching) ?? false;
|
return _metadata.systemConfig.network.autoEndpointSwitching;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getPreferredWifiName() {
|
String? getPreferredWifiName() {
|
||||||
return Store.tryGet(StoreKey.preferredWifiName);
|
return _metadata.systemConfig.network.preferredWifiName;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getLocalEndpoint() {
|
String? getLocalEndpoint() {
|
||||||
return Store.tryGet(StoreKey.localEndpoint);
|
return _metadata.systemConfig.network.localEndpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AuxilaryEndpoint> getExternalEndpointList() {
|
List<AuxilaryEndpoint> getExternalEndpointList() {
|
||||||
final jsonString = Store.tryGet(StoreKey.externalEndpointList);
|
return _metadata.systemConfig.network.externalEndpointList
|
||||||
|
.map((url) => AuxilaryEndpoint(url: url, status: .valid))
|
||||||
if (jsonString == null) {
|
.toList();
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<dynamic> jsonList = jsonDecode(jsonString);
|
|
||||||
final endpointList = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList();
|
|
||||||
|
|
||||||
return endpointList;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,28 +177,19 @@ class ApiService {
|
|||||||
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
if (serverEndpoint != null && serverEndpoint.isNotEmpty) {
|
||||||
urls.add(serverEndpoint);
|
urls.add(serverEndpoint);
|
||||||
}
|
}
|
||||||
final localEndpoint = Store.tryGet(StoreKey.localEndpoint);
|
final network = MetadataStore.systemConfig.network;
|
||||||
if (localEndpoint != null && localEndpoint.isNotEmpty) {
|
final localEndpoint = network.localEndpoint;
|
||||||
|
if (localEndpoint != null) {
|
||||||
urls.add(localEndpoint);
|
urls.add(localEndpoint);
|
||||||
}
|
}
|
||||||
final externalJson = Store.tryGet(StoreKey.externalEndpointList);
|
for (final url in network.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() {
|
||||||
var customHeadersStr = Store.get(StoreKey.customHeaders, "");
|
return MetadataStore.systemConfig.network.customHeaders;
|
||||||
if (customHeadersStr.isEmpty) {
|
|
||||||
return const {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return (jsonDecode(customHeadersStr) as Map).cast<String, String>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiClient get apiClient => _apiClient;
|
ApiClient get apiClient => _apiClient;
|
||||||
|
|||||||
@@ -1,66 +1,11 @@
|
|||||||
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),
|
||||||
syncAlbums<bool>(StoreKey.syncAlbums, null, false),
|
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", 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,7 +25,6 @@ final authServiceProvider = Provider(
|
|||||||
ref.watch(apiServiceProvider),
|
ref.watch(apiServiceProvider),
|
||||||
ref.watch(networkServiceProvider),
|
ref.watch(networkServiceProvider),
|
||||||
ref.watch(backgroundSyncProvider),
|
ref.watch(backgroundSyncProvider),
|
||||||
ref.watch(appSettingsServiceProvider),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -35,7 +34,6 @@ 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(
|
||||||
@@ -44,7 +42,6 @@ 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.
|
||||||
@@ -103,7 +100,7 @@ class AuthService {
|
|||||||
_log.severe("Error clearing local data", error, stackTrace);
|
_log.severe("Error clearing local data", error, stackTrace);
|
||||||
});
|
});
|
||||||
|
|
||||||
await _appSettingsService.setSetting(AppSettingsEnum.enableBackup, false);
|
await MetadataStore.instance.write(MetadataKey.backupEnabled, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,11 +120,6 @@ 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,14 +13,13 @@ 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;
|
||||||
@@ -31,7 +30,6 @@ 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),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -103,7 +101,6 @@ 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;
|
||||||
@@ -114,7 +111,6 @@ 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');
|
||||||
|
|
||||||
@@ -361,15 +357,10 @@ class BackgroundUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||||
bool requiresWiFi = true;
|
final backup = MetadataStore.appConfig.backup;
|
||||||
|
if (asset.isVideo && backup.useCellularForVideos) return false;
|
||||||
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
if (!asset.isVideo && backup.useCellularForPhotos) return false;
|
||||||
requiresWiFi = false;
|
return true;
|
||||||
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
|
||||||
requiresWiFi = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requiresWiFi;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadTask> buildUploadTask(
|
Future<UploadTask> buildUploadTask(
|
||||||
|
|||||||
@@ -7,18 +7,17 @@ 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/platform_extensions.dart';
|
|
||||||
import 'package:immich_mobile/extensions/network_capability_extensions.dart';
|
import 'package:immich_mobile/extensions/network_capability_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/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;
|
||||||
@@ -39,7 +38,6 @@ 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),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -55,7 +53,6 @@ class ForegroundUploadService {
|
|||||||
this._storageRepository,
|
this._storageRepository,
|
||||||
this._backupRepository,
|
this._backupRepository,
|
||||||
this._connectivityApi,
|
this._connectivityApi,
|
||||||
this._appSettingsService,
|
|
||||||
this._assetMediaRepository,
|
this._assetMediaRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -63,7 +60,6 @@ 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');
|
||||||
|
|
||||||
@@ -453,14 +449,9 @@ class ForegroundUploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
bool _shouldRequireWiFi(LocalAsset asset) {
|
||||||
bool requiresWiFi = true;
|
final backup = MetadataStore.appConfig.backup;
|
||||||
|
if (asset.isVideo && backup.useCellularForVideos) return false;
|
||||||
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
if (!asset.isVideo && backup.useCellularForPhotos) return false;
|
||||||
requiresWiFi = false;
|
return true;
|
||||||
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
|
||||||
requiresWiFi = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requiresWiFi;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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';
|
||||||
@@ -48,9 +49,11 @@ 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),
|
||||||
storeRepository: storeRepo,
|
metadataRepository: metadataRepo,
|
||||||
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/services/app_settings.service.dart';
|
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/services/app_settings.service.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,25 +1,271 @@
|
|||||||
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/services/api.service.dart';
|
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||||
|
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
|
||||||
const int targetVersion = 25;
|
const int targetVersion = 26;
|
||||||
|
|
||||||
Future<void> migrateDatabaseIfNeeded() async {
|
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||||
final int version = Store.get(StoreKey.version, targetVersion);
|
final int version = Store.get(StoreKey.version, targetVersion);
|
||||||
|
|
||||||
if (version < 25) {
|
if (version < 25) {
|
||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
await _migrateTo25();
|
||||||
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,13 +6,12 @@ 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';
|
||||||
@@ -193,18 +192,14 @@ class _BackupIndicator extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
|
Widget? _getBackupBadgeIcon(BuildContext context, WidgetRef ref) {
|
||||||
final backupStateStream = ref.watch(settingsProvider).watch(Setting.enableBackup);
|
final backupEnabled = ref.watch(appConfigProvider.select((c) => c.backup.enabled));
|
||||||
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 StreamBuilder(
|
return Builder(
|
||||||
stream: backupStateStream,
|
builder: (ctx) {
|
||||||
initialData: false,
|
|
||||||
builder: (ctx, snapshot) {
|
|
||||||
final backupEnabled = snapshot.data ?? false;
|
|
||||||
|
|
||||||
if (!backupEnabled) {
|
if (!backupEnabled) {
|
||||||
return _BadgeLabel(
|
return _BadgeLabel(
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ 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';
|
||||||
@@ -178,7 +179,7 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
await backgroundManager.syncRemote();
|
await backgroundManager.syncRemote();
|
||||||
await backgroundManager.hashAssets();
|
await backgroundManager.hashAssets();
|
||||||
|
|
||||||
if (Store.get(StoreKey.syncAlbums, false)) {
|
if (MetadataStore.appConfig.backup.syncAlbums) {
|
||||||
await backgroundManager.syncLinkedAlbum();
|
await backgroundManager.syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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';
|
||||||
@@ -30,8 +31,12 @@ 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 = useAppSettingsState(AppSettingsEnum.logLevel);
|
final levelId = useState<int>(ref.read(systemConfigProvider).logLevel.index);
|
||||||
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
|
final preferRemote = useState(ref.read(appConfigProvider).image.preferRemote);
|
||||||
|
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,12 +1,13 @@
|
|||||||
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/services/app_settings.service.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.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';
|
||||||
|
|
||||||
@@ -15,18 +16,17 @@ class GroupSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final groupByIndex = useAppSettingsState(AppSettingsEnum.groupAssetsBy);
|
final groupBy = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.groupAssetsBy)));
|
||||||
final groupBy = GroupAssetsBy.values[groupByIndex.value];
|
|
||||||
|
|
||||||
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
Future<void> updateAppSettings(GroupAssetsBy groupBy) async {
|
||||||
await ref.watch(appSettingsServiceProvider).setSetting(AppSettingsEnum.groupAssetsBy, groupBy.index);
|
await ref.read(metadataProvider).write(MetadataKey.timelineGroupAssetsBy, groupBy);
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
ref.invalidate(appSettingsServiceProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeGroupValue(GroupAssetsBy? value) {
|
void changeGroupValue(GroupAssetsBy? value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
groupByIndex.value = value.index;
|
groupBy.value = value;
|
||||||
unawaited(updateAppSettings(groupBy));
|
unawaited(updateAppSettings(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class GroupSettings extends HookConsumerWidget {
|
|||||||
value: GroupAssetsBy.auto,
|
value: GroupAssetsBy.auto,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
groupBy: groupBy,
|
groupBy: groupBy.value,
|
||||||
onRadioChanged: changeGroupValue,
|
onRadioChanged: changeGroupValue,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
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/services/app_settings.service.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.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';
|
||||||
|
|
||||||
@@ -13,7 +14,10 @@ class LayoutSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final tilesPerRow = useAppSettingsState(AppSettingsEnum.tilesPerRow);
|
final tilesPerRow = useState(ref.read(appConfigProvider.select((s) => s.timeline.tilesPerRow)));
|
||||||
|
useValueChanged<int, void>(tilesPerRow.value, (_, __) {
|
||||||
|
ref.read(metadataProvider).write(MetadataKey.timelineTilesPerRow, tilesPerRow.value);
|
||||||
|
});
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -29,7 +33,9 @@ class LayoutSettings extends HookConsumerWidget {
|
|||||||
maxValue: 6,
|
maxValue: 6,
|
||||||
minValue: 2,
|
minValue: 2,
|
||||||
noDivisons: 4,
|
noDivisons: 4,
|
||||||
onChangeEnd: (_) => ref.invalidate(appSettingsServiceProvider),
|
onChangeEnd: (value) {
|
||||||
|
ref.invalidate(appSettingsServiceProvider);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
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';
|
||||||
@@ -15,13 +16,14 @@ class AssetListSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final showStorageIndicator = useAppSettingsState(AppSettingsEnum.storageIndicator);
|
final storageIndicator = useValueNotifier(ref.watch(appConfigProvider.select((s) => s.timeline.storageIndicator)));
|
||||||
|
|
||||||
final assetListSetting = [
|
final assetListSetting = [
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: showStorageIndicator,
|
valueNotifier: storageIndicator,
|
||||||
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
|
||||||
onChanged: (_) {
|
onChanged: (value) {
|
||||||
|
ref.read(metadataProvider).write(MetadataKey.timelineStorageIndicator, value);
|
||||||
ref.invalidate(appSettingsServiceProvider);
|
ref.invalidate(appSettingsServiceProvider);
|
||||||
ref.invalidate(settingsProvider);
|
ref.invalidate(settingsProvider);
|
||||||
},
|
},
|
||||||
|
|||||||
+6
-10
@@ -1,19 +1,21 @@
|
|||||||
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/services/app_settings.service.dart';
|
import 'package:immich_mobile/providers/infrastructure/metadata.provider.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 isPreview = useAppSettingsState(AppSettingsEnum.loadPreview);
|
final isOriginal = useState(ref.read(appConfigProvider).image.loadOriginal);
|
||||||
final isOriginal = useAppSettingsState(AppSettingsEnum.loadOriginal);
|
useValueChanged<bool, void>(isOriginal.value, (_, __) {
|
||||||
|
ref.read(metadataProvider).write(.imageLoadOriginal, isOriginal.value);
|
||||||
|
});
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -23,12 +25,6 @@ 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),
|
||||||
|
|||||||
+6
-5
@@ -1,18 +1,20 @@
|
|||||||
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/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/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 = useAppSettingsState(AppSettingsEnum.tapToNavigate);
|
final tapToNavigate = useState(ref.read(appConfigProvider).viewer.tapToNavigate);
|
||||||
|
useValueChanged<bool, void>(tapToNavigate.value, (_, __) {
|
||||||
|
ref.read(metadataProvider).write(.viewerTapToNavigate, tapToNavigate.value);
|
||||||
|
});
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -22,7 +24,6 @@ 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,20 +1,30 @@
|
|||||||
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/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 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 useLoopVideo = useAppSettingsState(AppSettingsEnum.loopVideo);
|
final viewer = ref.read(appConfigProvider).viewer;
|
||||||
final useOriginalVideo = useAppSettingsState(AppSettingsEnum.loadOriginalVideo);
|
final useAutoPlayVideo = useState(viewer.autoPlayVideo);
|
||||||
final useAutoPlayVideo = useAppSettingsState(AppSettingsEnum.autoPlayVideo);
|
final useLoopVideo = useState(viewer.loopVideo);
|
||||||
|
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,
|
||||||
@@ -27,19 +37,16 @@ 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,18 +4,17 @@ 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/store.model.dart';
|
import 'package:immich_mobile/domain/models/config/app_config.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';
|
||||||
@@ -31,8 +30,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 _UseWifiForUploadVideosButton(),
|
const _UseCellularForVideosButton(),
|
||||||
const _UseWifiForUploadPhotosButton(),
|
const _UseCellularForPhotosButton(),
|
||||||
if (CurrentPlatform.isAndroid) ...[
|
if (CurrentPlatform.isAndroid) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
SettingGroupTitle(
|
SettingGroupTitle(
|
||||||
@@ -96,17 +95,13 @@ 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: [
|
||||||
StreamBuilder(
|
Column(
|
||||||
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),
|
||||||
@@ -114,7 +109,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(appSettingsServiceProvider).setSetting(AppSettingsEnum.syncAlbums, newValue);
|
await ref.read(metadataProvider).write(MetadataKey.backupSyncAlbums, newValue);
|
||||||
|
|
||||||
if (newValue == true) {
|
if (newValue == true) {
|
||||||
await _manageLinkedAlbums();
|
await _manageLinkedAlbums();
|
||||||
@@ -152,8 +147,6 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -161,60 +154,34 @@ class _AlbumSyncActionButtonState extends ConsumerState<_AlbumSyncActionButton>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsSwitchTile extends ConsumerStatefulWidget {
|
class _BackupSwitchTile extends ConsumerWidget {
|
||||||
final AppSettingsEnum<bool> appSettingsEnum;
|
final MetadataKey<bool> metadataKey;
|
||||||
|
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 _SettingsSwitchTile({
|
const _BackupSwitchTile({
|
||||||
required this.appSettingsEnum,
|
required this.metadataKey,
|
||||||
|
required this.selector,
|
||||||
required this.titleKey,
|
required this.titleKey,
|
||||||
required this.subtitleKey,
|
required this.subtitleKey,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState createState() => _SettingsSwitchTileState();
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
}
|
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: widget.titleKey.t(context: context),
|
title: titleKey.t(context: context),
|
||||||
subtitle: widget.subtitleKey.t(context: context),
|
subtitle: subtitleKey.t(context: context),
|
||||||
trailing: StreamBuilder(
|
trailing: Switch(
|
||||||
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(appSettingsServiceProvider).setSetting(widget.appSettingsEnum, newValue);
|
await ref.read(metadataProvider).write(metadataKey, newValue);
|
||||||
},
|
onChanged?.call(newValue);
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -222,26 +189,28 @@ class _SettingsSwitchTileState extends ConsumerState<_SettingsSwitchTile> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
class _UseCellularForVideosButton extends StatelessWidget {
|
||||||
const _UseWifiForUploadVideosButton();
|
const _UseCellularForVideosButton();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
return const _SettingsSwitchTile(
|
return _BackupSwitchTile(
|
||||||
appSettingsEnum: AppSettingsEnum.useCellularForUploadVideos,
|
metadataKey: MetadataKey.backupUseCellularForVideos,
|
||||||
|
selector: (c) => c.backup.useCellularForVideos,
|
||||||
titleKey: "videos",
|
titleKey: "videos",
|
||||||
subtitleKey: "network_requirement_videos_upload",
|
subtitleKey: "network_requirement_videos_upload",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
class _UseCellularForPhotosButton extends StatelessWidget {
|
||||||
const _UseWifiForUploadPhotosButton();
|
const _UseCellularForPhotosButton();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
return const _SettingsSwitchTile(
|
return _BackupSwitchTile(
|
||||||
appSettingsEnum: AppSettingsEnum.useCellularForUploadPhotos,
|
metadataKey: MetadataKey.backupUseCellularForPhotos,
|
||||||
|
selector: (c) => c.backup.useCellularForPhotos,
|
||||||
titleKey: "photos",
|
titleKey: "photos",
|
||||||
subtitleKey: "network_requirement_photos_upload",
|
subtitleKey: "network_requirement_photos_upload",
|
||||||
);
|
);
|
||||||
@@ -253,29 +222,22 @@ class _BackupOnlyWhenChargingButton extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return _SettingsSwitchTile(
|
final fgService = ref.read(backgroundWorkerFgServiceProvider);
|
||||||
appSettingsEnum: AppSettingsEnum.backupRequireCharging,
|
return _BackupSwitchTile(
|
||||||
|
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) {
|
||||||
ref.read(backgroundWorkerFgServiceProvider).configure(requireCharging: value ?? false);
|
fgService.configure(requireCharging: value);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BackupDelaySlider extends ConsumerStatefulWidget {
|
class _BackupDelaySlider extends ConsumerWidget {
|
||||||
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,
|
||||||
@@ -298,30 +260,9 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
super.initState();
|
final triggerDelay = ref.watch(appConfigProvider.select((c) => c.backup.triggerDelay));
|
||||||
final initialValue =
|
final currentValue = backupDelayToSliderValue(triggerDelay);
|
||||||
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: [
|
||||||
@@ -336,14 +277,13 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> {
|
|||||||
),
|
),
|
||||||
Slider(
|
Slider(
|
||||||
value: currentValue.toDouble(),
|
value: currentValue.toDouble(),
|
||||||
onChanged: (double v) {
|
onChanged: (double v) async {
|
||||||
setState(() {
|
final seconds = backupDelayToSeconds(v.toInt());
|
||||||
currentValue = v.toInt();
|
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onChangeEnd: (double v) async {
|
onChangeEnd: (double v) async {
|
||||||
final milliseconds = backupDelayToSeconds(v.toInt());
|
final seconds = backupDelayToSeconds(v.toInt());
|
||||||
await ref.read(appSettingsServiceProvider).setSetting(AppSettingsEnum.backupTriggerDelay, milliseconds);
|
await ref.read(metadataProvider).write(MetadataKey.backupTriggerDelay, seconds);
|
||||||
},
|
},
|
||||||
max: 3.0,
|
max: 3.0,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
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/store.model.dart';
|
import 'package:immich_mobile/domain/models/metadata_key.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 {
|
||||||
@@ -23,11 +21,12 @@ 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 endpointList = entries.value.where((url) => url.status == AuxCheckStatus.valid).toList();
|
final urls = entries.value
|
||||||
|
.where((e) => e.status == AuxCheckStatus.valid && e.url.isNotEmpty)
|
||||||
|
.map((e) => e.url)
|
||||||
|
.toList();
|
||||||
|
|
||||||
final jsonString = jsonEncode(endpointList);
|
ref.read(metadataProvider).write(MetadataKey.networkExternalEndpointList, urls);
|
||||||
|
|
||||||
Store.put(StoreKey.externalEndpointList, jsonString);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateValidationStatus(String url, int index, AuxCheckStatus status) {
|
updateValidationStatus(String url, int index, AuxCheckStatus status) {
|
||||||
@@ -69,14 +68,13 @@ class ExternalNetworkPreference extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
final jsonString = Store.tryGet(StoreKey.externalEndpointList);
|
final urls = ref.read(metadataProvider).systemConfig.network.externalEndpointList;
|
||||||
|
|
||||||
if (jsonString == null) {
|
if (urls.isEmpty) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
final List<dynamic> jsonList = jsonDecode(jsonString);
|
entries.value = urls.map((url) => AuxilaryEndpoint(url: url, status: .valid)).toList();
|
||||||
entries.value = jsonList.map((e) => AuxilaryEndpoint.fromJson(e)).toList();
|
|
||||||
return null;
|
return null;
|
||||||
}, const []);
|
}, const []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
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' hide Store;
|
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/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';
|
||||||
@@ -20,7 +19,10 @@ 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 = useAppSettingsState(AppSettingsEnum.autoEndpointSwitching);
|
final featureEnabled = useState(ref.read(systemConfigProvider).network.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,12 +3,8 @@ 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 {
|
||||||
@@ -17,11 +13,6 @@ 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) {
|
||||||
@@ -44,8 +35,6 @@ class NotificationSetting extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final String formattedValue = _formatSliderValue(sliderValue.value.toDouble());
|
|
||||||
|
|
||||||
final notificationSettings = [
|
final notificationSettings = [
|
||||||
if (!hasPermission)
|
if (!hasPermission)
|
||||||
SettingsButtonListTile(
|
SettingsButtonListTile(
|
||||||
@@ -60,44 +49,8 @@ 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,15 +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: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});
|
||||||
@@ -17,18 +15,10 @@ 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);
|
||||||
@@ -36,23 +26,18 @@ class PrimaryColorSetting extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUseSystemColorChange(bool newValue) {
|
onUseSystemColorChange(bool newValue) {
|
||||||
systemPrimaryColorSetting.value = newValue;
|
ref.read(metadataProvider).write(.themeDynamic, newValue);
|
||||||
ref.watch(dynamicThemeSettingProvider.notifier).state = newValue;
|
|
||||||
ref.invalidate(immichThemeProvider);
|
|
||||||
popBottomSheet();
|
popBottomSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
onPrimaryColorChange(ImmichColorPreset colorPreset) {
|
onPrimaryColorChange(ImmichColorPreset colorPreset) {
|
||||||
primaryColorSetting.value = colorPreset.name;
|
ref.read(metadataProvider).write(.themePrimaryColor, colorPreset);
|
||||||
ref.watch(immichThemePresetProvider.notifier).state = colorPreset;
|
|
||||||
ref.invalidate(immichThemeProvider);
|
|
||||||
|
|
||||||
//turn off system color setting
|
//turn off system color setting
|
||||||
if (systemPrimaryColorSetting.value) {
|
if (themeConfig.dynamicTheme) {
|
||||||
onUseSystemColorChange(false);
|
ref.read(metadataProvider).write(.themeDynamic, false);
|
||||||
} else {
|
|
||||||
popBottomSheet();
|
|
||||||
}
|
}
|
||||||
|
popBottomSheet();
|
||||||
}
|
}
|
||||||
|
|
||||||
buildPrimaryColorTile({
|
buildPrimaryColorTile({
|
||||||
@@ -122,7 +107,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: systemPrimaryColorSetting.value,
|
value: themeConfig.dynamicTheme,
|
||||||
onChanged: onUseSystemColorChange,
|
onChanged: onUseSystemColorChange,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -140,7 +125,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: currentPreset.value == preset && !systemPrimaryColorSetting.value,
|
showSelector: themeConfig.primaryColor == preset && !themeConfig.dynamicTheme,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
|
|||||||
@@ -3,72 +3,48 @@ 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/theme.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/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 currentThemeString = useAppSettingsState(AppSettingsEnum.themeMode);
|
final currentTheme = useState(ref.read(appConfigProvider.select((config) => config.theme.mode)));
|
||||||
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(
|
||||||
final applyThemeToBackgroundSetting = useAppSettingsState(AppSettingsEnum.colorfulInterface);
|
ref.watch(appConfigProvider.select((config) => config.theme.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) {
|
||||||
if (isDark) {
|
currentTheme.value = isDark ? ThemeMode.dark : ThemeMode.light;
|
||||||
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
|
ref.read(metadataProvider).write(.themeMode, currentTheme.value);
|
||||||
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) {
|
||||||
currentThemeString.value = "system";
|
currentTheme.value = ThemeMode.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) {
|
||||||
currentThemeString.value = "light";
|
currentTheme.value = ThemeMode.light;
|
||||||
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.light;
|
|
||||||
} else if (currentSystemBrightness == Brightness.dark) {
|
} else if (currentSystemBrightness == Brightness.dark) {
|
||||||
currentThemeString.value = "dark";
|
currentTheme.value = ThemeMode.dark;
|
||||||
ref.watch(immichThemeModeProvider.notifier).state = ThemeMode.dark;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ref.read(metadataProvider).write(.themeMode, currentTheme.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSurfaceColorSettingChange(bool useColorfulInterface) {
|
void onSurfaceColorSettingChange(bool useColorfulInterface) {
|
||||||
applyThemeToBackgroundSetting.value = useColorfulInterface;
|
ref.read(metadataProvider).write(.themeColorfulInterface, useColorfulInterface);
|
||||||
ref.watch(colorfulInterfaceSettingProvider.notifier).state = useColorfulInterface;
|
colorfulInterface.value = useColorfulInterface;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -91,7 +67,7 @@ class ThemeSetting extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
const PrimaryColorSetting(),
|
const PrimaryColorSetting(),
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
valueNotifier: applyThemeToBackgroundProvider,
|
valueNotifier: colorfulInterface,
|
||||||
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
@@ -1,5 +1,5 @@
|
|||||||
[tools]
|
[tools]
|
||||||
flutter = "3.41.7"
|
flutter = "3.41.9"
|
||||||
|
|
||||||
[tools."github:CQLabs/homebrew-dcm"]
|
[tools."github:CQLabs/homebrew-dcm"]
|
||||||
version = "1.30.0"
|
version = "1.30.0"
|
||||||
|
|||||||
Generated
+1
@@ -579,6 +579,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
|
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
|
||||||
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
|
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
|
||||||
- [SyncAssetV1](doc//SyncAssetV1.md)
|
- [SyncAssetV1](doc//SyncAssetV1.md)
|
||||||
|
- [SyncAssetV2](doc//SyncAssetV2.md)
|
||||||
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
|
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
|
||||||
- [SyncEntityType](doc//SyncEntityType.md)
|
- [SyncEntityType](doc//SyncEntityType.md)
|
||||||
- [SyncMemoryAssetDeleteV1](doc//SyncMemoryAssetDeleteV1.md)
|
- [SyncMemoryAssetDeleteV1](doc//SyncMemoryAssetDeleteV1.md)
|
||||||
|
|||||||
Generated
+1
@@ -327,6 +327,7 @@ part 'model/sync_asset_face_v2.dart';
|
|||||||
part 'model/sync_asset_metadata_delete_v1.dart';
|
part 'model/sync_asset_metadata_delete_v1.dart';
|
||||||
part 'model/sync_asset_metadata_v1.dart';
|
part 'model/sync_asset_metadata_v1.dart';
|
||||||
part 'model/sync_asset_v1.dart';
|
part 'model/sync_asset_v1.dart';
|
||||||
|
part 'model/sync_asset_v2.dart';
|
||||||
part 'model/sync_auth_user_v1.dart';
|
part 'model/sync_auth_user_v1.dart';
|
||||||
part 'model/sync_entity_type.dart';
|
part 'model/sync_entity_type.dart';
|
||||||
part 'model/sync_memory_asset_delete_v1.dart';
|
part 'model/sync_memory_asset_delete_v1.dart';
|
||||||
|
|||||||
Generated
+6
-6
@@ -1234,8 +1234,8 @@ class AssetsApi {
|
|||||||
/// * [String] xImmichChecksum:
|
/// * [String] xImmichChecksum:
|
||||||
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
||||||
///
|
///
|
||||||
/// * [String] duration:
|
/// * [int] duration:
|
||||||
/// Duration (for videos)
|
/// Duration in milliseconds (for videos)
|
||||||
///
|
///
|
||||||
/// * [String] filename:
|
/// * [String] filename:
|
||||||
/// Filename
|
/// Filename
|
||||||
@@ -1253,7 +1253,7 @@ class AssetsApi {
|
|||||||
/// Sidecar file data
|
/// Sidecar file data
|
||||||
///
|
///
|
||||||
/// * [AssetVisibility] visibility:
|
/// * [AssetVisibility] visibility:
|
||||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/assets';
|
final apiPath = r'/assets';
|
||||||
|
|
||||||
@@ -1358,8 +1358,8 @@ class AssetsApi {
|
|||||||
/// * [String] xImmichChecksum:
|
/// * [String] xImmichChecksum:
|
||||||
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
||||||
///
|
///
|
||||||
/// * [String] duration:
|
/// * [int] duration:
|
||||||
/// Duration (for videos)
|
/// Duration in milliseconds (for videos)
|
||||||
///
|
///
|
||||||
/// * [String] filename:
|
/// * [String] filename:
|
||||||
/// Filename
|
/// Filename
|
||||||
@@ -1377,7 +1377,7 @@ class AssetsApi {
|
|||||||
/// Sidecar file data
|
/// Sidecar file data
|
||||||
///
|
///
|
||||||
/// * [AssetVisibility] visibility:
|
/// * [AssetVisibility] visibility:
|
||||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||||
final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, );
|
final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user