mirror of
https://github.com/immich-app/immich.git
synced 2026-01-26 03:14:39 -08:00
Compare commits
60 Commits
open-api-f
...
feat/csp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34906d636d | ||
|
|
99bd7d5f27 | ||
|
|
fe1d0edf4c | ||
|
|
4ef699e9fa | ||
|
|
3e21174dd8 | ||
|
|
1b56bb84f9 | ||
|
|
b3f5b8ede8 | ||
|
|
2b77dc8e1f | ||
|
|
97a594556b | ||
|
|
4a7c4b6d15 | ||
|
|
a8198f9934 | ||
|
|
b123beae38 | ||
|
|
1ada7a8340 | ||
|
|
5d81cace23 | ||
|
|
65f9a228ba | ||
|
|
e6eca895ba | ||
|
|
8196bd9bbd | ||
|
|
07675a2de4 | ||
|
|
a2b03f7650 | ||
|
|
fdff591a11 | ||
|
|
e4443fa43e | ||
|
|
843d563178 | ||
|
|
256d62e22d | ||
|
|
91592aa48e | ||
|
|
2ac113624b | ||
|
|
0052979853 | ||
|
|
79b6c4ac70 | ||
|
|
95eb3e26c3 | ||
|
|
613dc858cb | ||
|
|
2f3fbd7dc5 | ||
|
|
80a5444bf4 | ||
|
|
d59ee7d2ae | ||
|
|
7b3a298c6a | ||
|
|
0a62ec7e29 | ||
|
|
21802ab5ba | ||
|
|
56dfdfd033 | ||
|
|
2190921c85 | ||
|
|
9fa8de7baa | ||
|
|
ed9448a6ee | ||
|
|
15224a9ac5 | ||
|
|
6e00fd92ef | ||
|
|
6fdd1ce41a | ||
|
|
91d4cd6824 | ||
|
|
c7254a0c30 | ||
|
|
38f01a6b7d | ||
|
|
f194a7ea3e | ||
|
|
05a7ba98c1 | ||
|
|
edc513a3df | ||
|
|
39212a049c | ||
|
|
9b4f370834 | ||
|
|
aba85b036c | ||
|
|
6e86697996 | ||
|
|
cc90c912f5 | ||
|
|
efd20ef0d4 | ||
|
|
0c0aa1f3c3 | ||
|
|
231a475a17 | ||
|
|
94ea83c415 | ||
|
|
4b5b9baa78 | ||
|
|
3bf0d5b99f | ||
|
|
8ed81ac3e1 |
2
.github/.nvmrc
vendored
2
.github/.nvmrc
vendored
@@ -1 +1 @@
|
|||||||
24.12.0
|
24.13.0
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24.12.0
|
24.13.0
|
||||||
|
|||||||
@@ -69,6 +69,6 @@
|
|||||||
"micromatch": "^4.0.8"
|
"micromatch": "^4.0.8"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.12.0"
|
"node": "24.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ services:
|
|||||||
container_name: immich_prometheus
|
container_name: immich_prometheus
|
||||||
ports:
|
ports:
|
||||||
- 9090:9090
|
- 9090:9090
|
||||||
image: prom/prometheus@sha256:2b6f734e372c1b4717008f7d0a0152316aedd4d13ae17ef1e3268dbfaf68041b
|
image: prom/prometheus@sha256:1f0f50f06acaceb0f5670d2c8a658a599affe7b0d8e78b898c1035653849a702
|
||||||
volumes:
|
volumes:
|
||||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
- prometheus-data:/prometheus
|
- prometheus-data:/prometheus
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
24.12.0
|
24.13.0
|
||||||
|
|||||||
@@ -95,11 +95,3 @@ Enter the cloud on the top right -> cog wheel on the top right -> select the syn
|
|||||||
If you delete/move photos in the local album on your device, it will not be reflected in the album on the server **even if** you click Sync albums
|
If you delete/move photos in the local album on your device, it will not be reflected in the album on the server **even if** you click Sync albums
|
||||||
It will only reflect files you add.
|
It will only reflect files you add.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
If the same asset is in more than one album it will only sync to the first album it's in, after that it won't sync again even if the user clicks sync albums manually.
|
|
||||||
To overcome this limitation, the files must be removed from the ignore list by
|
|
||||||
App settings -> Advanced -> Duplicate Assets -> Clear
|
|
||||||
|
|
||||||
:::info
|
|
||||||
Cleaning duplicate assets from the list will cause all the previously uploaded duplicate files to be re-uploaded, the files will not actually be uploaded and will be rejected on the server side (due to duplication) but will be synchronized to the album and at the end will be added to the ignore list again at the end of the synchronization.
|
|
||||||
:::
|
|
||||||
|
|||||||
@@ -17,12 +17,17 @@ Hardware and software requirements for Immich:
|
|||||||
- Immich runs well in a virtualized environment when running in a full virtual machine.
|
- Immich runs well in a virtualized environment when running in a full virtual machine.
|
||||||
The use of Docker in LXC containers is [not recommended](https://pve.proxmox.com/wiki/Linux_Container), but may be possible for advanced users.
|
The use of Docker in LXC containers is [not recommended](https://pve.proxmox.com/wiki/Linux_Container), but may be possible for advanced users.
|
||||||
If you have issues, we recommend that you switch to a supported VM deployment.
|
If you have issues, we recommend that you switch to a supported VM deployment.
|
||||||
- **RAM**: Minimum 4GB, recommended 6GB.
|
- **RAM**: Minimum 6GB, recommended 8GB.
|
||||||
- **CPU**: Minimum 2 cores, recommended 4 cores.
|
- **CPU**: Minimum 2 cores, recommended 4 cores.
|
||||||
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
|
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
|
||||||
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
|
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
|
||||||
|
|
||||||
:::tip
|
:::note RAM requirements
|
||||||
|
For a smooth experience, especially during asset upload, Immich requires at least 6GB of RAM.
|
||||||
|
For systems with only 4GB of RAM, Immich can be run with machine learning features disabled.
|
||||||
|
:::
|
||||||
|
|
||||||
|
:::tip Postgres setup
|
||||||
Good performance and a stable connection to the Postgres database is critical to a smooth Immich experience.
|
Good performance and a stable connection to the Postgres database is critical to a smooth Immich experience.
|
||||||
The Postgres database files are typically between 1-3 GB in size.
|
The Postgres database files are typically between 1-3 GB in size.
|
||||||
For this reason, the Postgres database (`DB_DATA_LOCATION`) should ideally use local SSD storage, and never a network share of any kind.
|
For this reason, the Postgres database (`DB_DATA_LOCATION`) should ideally use local SSD storage, and never a network share of any kind.
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ const config = {
|
|||||||
locales: ['en'],
|
locales: ['en'],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Mermaid diagrams
|
||||||
|
markdown: {
|
||||||
|
mermaid: true,
|
||||||
|
},
|
||||||
|
themes: ['@docusaurus/theme-mermaid'],
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
async function myPlugin(context, options) {
|
async function myPlugin(context, options) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
"@docusaurus/core": "~3.9.0",
|
"@docusaurus/core": "~3.9.0",
|
||||||
"@docusaurus/preset-classic": "~3.9.0",
|
"@docusaurus/preset-classic": "~3.9.0",
|
||||||
"@docusaurus/theme-common": "~3.9.0",
|
"@docusaurus/theme-common": "~3.9.0",
|
||||||
|
"@docusaurus/theme-mermaid": "~3.9.0",
|
||||||
"@mdi/js": "^7.3.67",
|
"@mdi/js": "^7.3.67",
|
||||||
"@mdi/react": "^1.6.1",
|
"@mdi/react": "^1.6.1",
|
||||||
"@mdx-js/react": "^3.0.0",
|
"@mdx-js/react": "^3.0.0",
|
||||||
@@ -57,6 +58,6 @@
|
|||||||
"node": ">=20"
|
"node": ">=20"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.12.0"
|
"node": "24.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,19 +8,19 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Overpass';
|
font-family: 'GoogleSans';
|
||||||
src: url('/fonts/overpass/Overpass.ttf') format('truetype-variations');
|
src: url('/fonts/GoogleSans/GoogleSans.ttf') format('truetype-variations');
|
||||||
font-weight: 1 999;
|
font-weight: 410 900;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
ascent-override: 106.25%;
|
ascent-override: 106.25%;
|
||||||
size-adjust: 106.25%;
|
size-adjust: 106.25%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'Overpass Mono';
|
font-family: 'GoogleSansCode';
|
||||||
src: url('/fonts/overpass/OverpassMono.ttf') format('truetype-variations');
|
src: url('/fonts/GoogleSansCode/GoogleSansCode.ttf') format('truetype-variations');
|
||||||
font-weight: 1 999;
|
font-weight: 1 900;
|
||||||
font-style: normal;
|
font-style: monospace;
|
||||||
ascent-override: 106.25%;
|
ascent-override: 106.25%;
|
||||||
size-adjust: 106.25%;
|
size-adjust: 106.25%;
|
||||||
}
|
}
|
||||||
@@ -37,7 +37,8 @@ img {
|
|||||||
|
|
||||||
/* You can override the default Infima variables here. */
|
/* You can override the default Infima variables here. */
|
||||||
:root {
|
:root {
|
||||||
font-family: 'Overpass', sans-serif;
|
font-family: 'GoogleSans', sans-serif;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
--ifm-color-primary: #4250af;
|
--ifm-color-primary: #4250af;
|
||||||
--ifm-color-primary-dark: #4250af;
|
--ifm-color-primary-dark: #4250af;
|
||||||
--ifm-color-primary-darker: #4250af;
|
--ifm-color-primary-darker: #4250af;
|
||||||
@@ -48,6 +49,16 @@ img {
|
|||||||
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: 'GoogleSans', sans-serif;
|
||||||
|
letter-spacing: 0.1px;
|
||||||
|
}
|
||||||
|
|
||||||
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
/* For readability concerns, you should choose a lighter palette in dark mode. */
|
||||||
[data-theme='dark'] {
|
[data-theme='dark'] {
|
||||||
--ifm-color-primary: #adcbfa;
|
--ifm-color-primary: #adcbfa;
|
||||||
@@ -71,15 +82,22 @@ div[class^='announcementBar_'] {
|
|||||||
padding: 10px 10px 10px 16px;
|
padding: 10px 10px 10px 16px;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu__list-item-collapsible {
|
.menu__list-item-collapsible {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu__link--active {
|
.menu__link--active {
|
||||||
font-weight: 500;
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-of-contents__link {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 450;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* workaround for version switcher PR 15894 */
|
/* workaround for version switcher PR 15894 */
|
||||||
@@ -88,13 +106,14 @@ div[class*='navbar__items'] > li:has(a[class*='version-switcher-34ab39']) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
code {
|
code {
|
||||||
font-weight: 600;
|
font-weight: 500;
|
||||||
|
font-family: 'GoogleSansCode';
|
||||||
}
|
}
|
||||||
|
|
||||||
.buy-button {
|
.buy-button {
|
||||||
padding: 8px 14px;
|
padding: 8px 14px;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
font-family: 'Overpass', sans-serif;
|
font-family: 'GoogleSans', sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 0 5px 2px rgba(181, 206, 254, 0.4);
|
box-shadow: 0 0 5px 2px rgba(181, 206, 254, 0.4);
|
||||||
|
|||||||
BIN
docs/static/fonts/GoogleSans/GoogleSans.ttf
vendored
Normal file
BIN
docs/static/fonts/GoogleSans/GoogleSans.ttf
vendored
Normal file
Binary file not shown.
BIN
docs/static/fonts/GoogleSansCode/GoogleSansCode.ttf
vendored
Normal file
BIN
docs/static/fonts/GoogleSansCode/GoogleSansCode.ttf
vendored
Normal file
Binary file not shown.
BIN
docs/static/fonts/overpass/Overpass-Italic.ttf
vendored
BIN
docs/static/fonts/overpass/Overpass-Italic.ttf
vendored
Binary file not shown.
BIN
docs/static/fonts/overpass/Overpass.ttf
vendored
BIN
docs/static/fonts/overpass/Overpass.ttf
vendored
Binary file not shown.
BIN
docs/static/fonts/overpass/OverpassMono.ttf
vendored
BIN
docs/static/fonts/overpass/OverpassMono.ttf
vendored
Binary file not shown.
@@ -1 +1 @@
|
|||||||
24.12.0
|
24.13.0
|
||||||
|
|||||||
@@ -52,6 +52,6 @@
|
|||||||
"vitest": "^3.0.0"
|
"vitest": "^3.0.0"
|
||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "24.12.0"
|
"node": "24.13.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
import {
|
import {
|
||||||
Changes,
|
Changes,
|
||||||
createDefaultTimelineConfig,
|
createDefaultTimelineConfig,
|
||||||
@@ -58,6 +58,120 @@ test.describe('asset-viewer', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.describe('/photos/:id', () => {
|
test.describe('/photos/:id', () => {
|
||||||
|
test('Navigate to next asset via button', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${asset.id}`);
|
||||||
|
|
||||||
|
await page.getByLabel('View next asset').click();
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index + 1]);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${assets[index + 1].id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Navigate to previous asset via button', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${asset.id}`);
|
||||||
|
|
||||||
|
await page.getByLabel('View previous asset').click();
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index - 1]);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${assets[index - 1].id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Navigate to next asset via keyboard (ArrowRight)', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${asset.id}`);
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowRight');
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index + 1]);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${assets[index + 1].id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Navigate to previous asset via keyboard (ArrowLeft)', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${asset.id}`);
|
||||||
|
|
||||||
|
await page.keyboard.press('ArrowLeft');
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index - 1]);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${assets[index - 1].id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Navigate forward 5 times via button', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
await page.getByLabel('View next asset').click();
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index + i]);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${assets[index + i].id}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Navigate backward 5 times via button', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
|
||||||
|
for (let i = 1; i <= 5; i++) {
|
||||||
|
await page.getByLabel('View previous asset').click();
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index - i]);
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${assets[index - i].id}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Navigate forward then backward via keyboard', async ({ page }) => {
|
||||||
|
const asset = selectRandom(assets, rng);
|
||||||
|
const index = assets.indexOf(asset);
|
||||||
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, asset);
|
||||||
|
|
||||||
|
// Navigate forward 3 times
|
||||||
|
for (let i = 1; i <= 3; i++) {
|
||||||
|
await page.keyboard.press('ArrowRight');
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate backward 3 times to return to original
|
||||||
|
for (let i = 2; i >= 0; i--) {
|
||||||
|
await page.keyboard.press('ArrowLeft');
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, assets[index + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we're back at the original asset
|
||||||
|
await expect.poll(() => new URL(page.url()).pathname).toBe(`/photos/${asset.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Verify no next button on last asset', async ({ page }) => {
|
||||||
|
const lastAsset = assets.at(-1)!;
|
||||||
|
await page.goto(`/photos/${lastAsset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, lastAsset);
|
||||||
|
|
||||||
|
// Verify next button doesn't exist
|
||||||
|
await expect(page.getByLabel('View next asset')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Verify no previous button on first asset', async ({ page }) => {
|
||||||
|
const firstAsset = assets[0];
|
||||||
|
await page.goto(`/photos/${firstAsset.id}`);
|
||||||
|
await assetViewerUtils.waitForViewerLoad(page, firstAsset);
|
||||||
|
|
||||||
|
// Verify previous button doesn't exist
|
||||||
|
await expect(page.getByLabel('View previous asset')).toHaveCount(0);
|
||||||
|
});
|
||||||
|
|
||||||
test('Delete photo advances to next', async ({ page }) => {
|
test('Delete photo advances to next', async ({ page }) => {
|
||||||
const asset = selectRandom(assets, rng);
|
const asset = selectRandom(assets, rng);
|
||||||
await page.goto(`/photos/${asset.id}`);
|
await page.goto(`/photos/${asset.id}`);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Page, expect, test } from '@playwright/test';
|
|||||||
import { utils } from 'src/utils';
|
import { utils } from 'src/utils';
|
||||||
|
|
||||||
function imageLocator(page: Page) {
|
function imageLocator(page: Page) {
|
||||||
return page.getByAltText('Image taken on').locator('visible=true');
|
return page.getByAltText('Image taken').locator('visible=true');
|
||||||
}
|
}
|
||||||
test.describe('Photo Viewer', () => {
|
test.describe('Photo Viewer', () => {
|
||||||
let admin: LoginResponseDto;
|
let admin: LoginResponseDto;
|
||||||
|
|||||||
@@ -603,7 +603,7 @@
|
|||||||
"backup_album_selection_page_select_albums": "Select albums",
|
"backup_album_selection_page_select_albums": "Select albums",
|
||||||
"backup_album_selection_page_selection_info": "Selection Info",
|
"backup_album_selection_page_selection_info": "Selection Info",
|
||||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
"backup_album_selection_page_total_assets": "Total unique assets",
|
||||||
"backup_albums_sync": "Backup albums synchronization",
|
"backup_albums_sync": "Backup Albums Synchronization",
|
||||||
"backup_all": "All",
|
"backup_all": "All",
|
||||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||||
"backup_background_service_complete_notification": "Asset backup complete",
|
"backup_background_service_complete_notification": "Asset backup complete",
|
||||||
@@ -928,6 +928,7 @@
|
|||||||
"download_include_embedded_motion_videos": "Embedded videos",
|
"download_include_embedded_motion_videos": "Embedded videos",
|
||||||
"download_include_embedded_motion_videos_description": "Include videos embedded in motion photos as a separate file",
|
"download_include_embedded_motion_videos_description": "Include videos embedded in motion photos as a separate file",
|
||||||
"download_notfound": "Download not found",
|
"download_notfound": "Download not found",
|
||||||
|
"download_original": "Download original",
|
||||||
"download_paused": "Download paused",
|
"download_paused": "Download paused",
|
||||||
"download_settings": "Download",
|
"download_settings": "Download",
|
||||||
"download_settings_description": "Manage settings related to asset download",
|
"download_settings_description": "Manage settings related to asset download",
|
||||||
@@ -937,6 +938,7 @@
|
|||||||
"download_waiting_to_retry": "Waiting to retry",
|
"download_waiting_to_retry": "Waiting to retry",
|
||||||
"downloading": "Downloading",
|
"downloading": "Downloading",
|
||||||
"downloading_asset_filename": "Downloading asset {filename}",
|
"downloading_asset_filename": "Downloading asset {filename}",
|
||||||
|
"downloading_from_icloud": "Downloading from iCloud",
|
||||||
"downloading_media": "Downloading media",
|
"downloading_media": "Downloading media",
|
||||||
"drop_files_to_upload": "Drop files anywhere to upload",
|
"drop_files_to_upload": "Drop files anywhere to upload",
|
||||||
"duplicates": "Duplicates",
|
"duplicates": "Duplicates",
|
||||||
@@ -1122,6 +1124,7 @@
|
|||||||
"unable_to_update_workflow": "Unable to update workflow",
|
"unable_to_update_workflow": "Unable to update workflow",
|
||||||
"unable_to_upload_file": "Unable to upload file"
|
"unable_to_upload_file": "Unable to upload file"
|
||||||
},
|
},
|
||||||
|
"errors_text": "Errors",
|
||||||
"exclusion_pattern": "Exclusion pattern",
|
"exclusion_pattern": "Exclusion pattern",
|
||||||
"exif": "Exif",
|
"exif": "Exif",
|
||||||
"exif_bottom_sheet_description": "Add Description...",
|
"exif_bottom_sheet_description": "Add Description...",
|
||||||
@@ -2236,7 +2239,6 @@
|
|||||||
"updated_at": "Updated",
|
"updated_at": "Updated",
|
||||||
"updated_password": "Updated password",
|
"updated_password": "Updated password",
|
||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"upload_action_prompt": "{count} queued for upload",
|
|
||||||
"upload_concurrency": "Upload concurrency",
|
"upload_concurrency": "Upload concurrency",
|
||||||
"upload_details": "Upload Details",
|
"upload_details": "Upload Details",
|
||||||
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
"upload_dialog_info": "Do you want to backup the selected Asset(s) to the server?",
|
||||||
@@ -2255,7 +2257,7 @@
|
|||||||
"url": "URL",
|
"url": "URL",
|
||||||
"usage": "Usage",
|
"usage": "Usage",
|
||||||
"use_biometric": "Use biometric",
|
"use_biometric": "Use biometric",
|
||||||
"use_current_connection": "use current connection",
|
"use_current_connection": "Use current connection",
|
||||||
"use_custom_date_range": "Use custom date range instead",
|
"use_custom_date_range": "Use custom date range instead",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"user_has_been_deleted": "This user has been deleted.",
|
"user_has_been_deleted": "This user has been deleted.",
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ FROM python:3.13-slim-trixie@sha256:0222b795db95bf7412cede36ab46a266cfb31f632e64
|
|||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
apt-get install --no-install-recommends -yqq ocl-icd-libopencl1 wget && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.24.8/intel-igc-core-2_2.24.8+20344_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-core-2_2.27.10+20617_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.24.8/intel-igc-opencl-2_2.24.8+20344_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/v2.27.10/intel-igc-opencl-2_2.27.10+20617_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/25.48.36300.8/intel-opencl-icd_25.48.36300.8-0_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/intel-opencl-icd_26.01.36711.4-0_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-core_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
wget -nv https://github.com/intel/intel-graphics-compiler/releases/download/igc-1.0.17537.24/intel-igc-opencl_1.0.17537.24_amd64.deb && \
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/24.35.30872.36/intel-opencl-icd-legacy1_24.35.30872.36_amd64.deb && \
|
||||||
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
# TODO: Figure out how to get renovate to manage this differently versioned libigdgmm file
|
||||||
wget -nv https://github.com/intel/compute-runtime/releases/download/25.48.36300.8/libigdgmm12_22.8.2_amd64.deb && \
|
wget -nv https://github.com/intel/compute-runtime/releases/download/26.01.36711.4/libigdgmm12_22.9.0_amd64.deb && \
|
||||||
dpkg -i *.deb && \
|
dpkg -i *.deb && \
|
||||||
rm *.deb && \
|
rm *.deb && \
|
||||||
apt-get remove wget -yqq && \
|
apt-get remove wget -yqq && \
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
experimental_monorepo_root = true
|
experimental_monorepo_root = true
|
||||||
|
|
||||||
[tools]
|
[tools]
|
||||||
node = "24.12.0"
|
node = "24.13.0"
|
||||||
flutter = "3.35.7"
|
flutter = "3.35.7"
|
||||||
pnpm = "10.27.0"
|
pnpm = "10.27.0"
|
||||||
terragrunt = "0.93.10"
|
terragrunt = "0.93.10"
|
||||||
|
|||||||
@@ -117,6 +117,9 @@
|
|||||||
<data
|
<data
|
||||||
android:host="my.immich.app"
|
android:host="my.immich.app"
|
||||||
android:pathPrefix="/memories/" />
|
android:pathPrefix="/memories/" />
|
||||||
|
<data
|
||||||
|
android:host="my.immich.app"
|
||||||
|
android:path="/memory" />
|
||||||
<data
|
<data
|
||||||
android:host="my.immich.app"
|
android:host="my.immich.app"
|
||||||
android:pathPrefix="/photos/" />
|
android:pathPrefix="/photos/" />
|
||||||
|
|||||||
@@ -252,6 +252,40 @@ data class HashResult (
|
|||||||
|
|
||||||
override fun hashCode(): Int = toList().hashCode()
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Generated class from Pigeon that represents data sent in messages. */
|
||||||
|
data class CloudIdResult (
|
||||||
|
val assetId: String,
|
||||||
|
val error: String? = null,
|
||||||
|
val cloudId: String? = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
companion object {
|
||||||
|
fun fromList(pigeonVar_list: List<Any?>): CloudIdResult {
|
||||||
|
val assetId = pigeonVar_list[0] as String
|
||||||
|
val error = pigeonVar_list[1] as String?
|
||||||
|
val cloudId = pigeonVar_list[2] as String?
|
||||||
|
return CloudIdResult(assetId, error, cloudId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun toList(): List<Any?> {
|
||||||
|
return listOf(
|
||||||
|
assetId,
|
||||||
|
error,
|
||||||
|
cloudId,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is CloudIdResult) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this === other) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return MessagesPigeonUtils.deepEquals(toList(), other.toList()) }
|
||||||
|
|
||||||
|
override fun hashCode(): Int = toList().hashCode()
|
||||||
|
}
|
||||||
private open class MessagesPigeonCodec : StandardMessageCodec() {
|
private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
@@ -275,6 +309,11 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||||||
HashResult.fromList(it)
|
HashResult.fromList(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
133.toByte() -> {
|
||||||
|
return (readValue(buffer) as? List<Any?>)?.let {
|
||||||
|
CloudIdResult.fromList(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> super.readValueOfType(type, buffer)
|
else -> super.readValueOfType(type, buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -296,6 +335,10 @@ private open class MessagesPigeonCodec : StandardMessageCodec() {
|
|||||||
stream.write(132)
|
stream.write(132)
|
||||||
writeValue(stream, value.toList())
|
writeValue(stream, value.toList())
|
||||||
}
|
}
|
||||||
|
is CloudIdResult -> {
|
||||||
|
stream.write(133)
|
||||||
|
writeValue(stream, value.toList())
|
||||||
|
}
|
||||||
else -> super.writeValue(stream, value)
|
else -> super.writeValue(stream, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,6 +358,7 @@ interface NativeSyncApi {
|
|||||||
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
fun hashAssets(assetIds: List<String>, allowNetworkAccess: Boolean, callback: (Result<List<HashResult>>) -> Unit)
|
||||||
fun cancelHashing()
|
fun cancelHashing()
|
||||||
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
|
fun getTrashedAssets(): Map<String, List<PlatformAsset>>
|
||||||
|
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult>
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** The codec used by NativeSyncApi. */
|
/** The codec used by NativeSyncApi. */
|
||||||
@@ -508,6 +552,23 @@ interface NativeSyncApi {
|
|||||||
channel.setMessageHandler(null)
|
channel.setMessageHandler(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$separatedMessageChannelSuffix", codec, taskQueue)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { message, reply ->
|
||||||
|
val args = message as List<Any?>
|
||||||
|
val assetIdsArg = args[0] as List<String>
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.getCloudIdForAssetIds(assetIdsArg))
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
MessagesPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Semaphore
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
@@ -21,7 +22,6 @@ import kotlinx.coroutines.sync.withPermit
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import kotlin.coroutines.cancellation.CancellationException
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
import kotlin.coroutines.coroutineContext
|
|
||||||
|
|
||||||
sealed class AssetResult {
|
sealed class AssetResult {
|
||||||
data class ValidAsset(val asset: PlatformAsset, val albumId: String) : AssetResult()
|
data class ValidAsset(val asset: PlatformAsset, val albumId: String) : AssetResult()
|
||||||
@@ -298,7 +298,7 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
var bytesRead: Int
|
var bytesRead: Int
|
||||||
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
val buffer = ByteArray(HASH_BUFFER_SIZE)
|
||||||
while (inputStream.read(buffer).also { bytesRead = it } > 0) {
|
while (inputStream.read(buffer).also { bytesRead = it } > 0) {
|
||||||
coroutineContext.ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
digest.update(buffer, 0, bytesRead)
|
digest.update(buffer, 0, bytesRead)
|
||||||
}
|
}
|
||||||
} ?: return HashResult(assetId, "Cannot open input stream for asset", null)
|
} ?: return HashResult(assetId, "Cannot open input stream for asset", null)
|
||||||
@@ -316,4 +316,10 @@ open class NativeSyncApiImplBase(context: Context) : ImmichPlugin() {
|
|||||||
hashTask?.cancel()
|
hashTask?.cancel()
|
||||||
hashTask = null
|
hashTask = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method is only implemented on iOS; on Android, we do not have a concept of cloud IDs
|
||||||
|
@Suppress("unused", "UNUSED_PARAMETER")
|
||||||
|
fun getCloudIdForAssetIds(assetIds: List<String>): List<CloudIdResult> {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
mobile/drift_schemas/main/drift_schema_v15.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v15.json
generated
Normal file
File diff suppressed because one or more lines are too long
1
mobile/drift_schemas/main/drift_schema_v16.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v16.json
generated
Normal file
File diff suppressed because one or more lines are too long
1
mobile/drift_schemas/main/drift_schema_v17.json
generated
Normal file
1
mobile/drift_schemas/main/drift_schema_v17.json
generated
Normal file
File diff suppressed because one or more lines are too long
BIN
mobile/fonts/GoogleSans/GoogleSans-Bold.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Bold.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-Italic.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Italic.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-Medium.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Medium.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-Regular.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-Regular.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSans/GoogleSans-SemiBold.ttf
Normal file
BIN
mobile/fonts/GoogleSans/GoogleSans-SemiBold.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Medium.ttf
Normal file
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Medium.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Regular.ttf
Normal file
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-Regular.ttf
Normal file
Binary file not shown.
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf
Normal file
BIN
mobile/fonts/GoogleSansCode/GoogleSansCode-SemiBold.ttf
Normal file
Binary file not shown.
@@ -55,6 +55,7 @@ import UIKit
|
|||||||
NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
NativeSyncApiImpl.register(with: engine.registrar(forPlugin: NativeSyncApiImpl.name)!)
|
||||||
ThumbnailApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ThumbnailApiImpl())
|
ThumbnailApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ThumbnailApiImpl())
|
||||||
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl())
|
BackgroundWorkerFgHostApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: BackgroundWorkerApiImpl())
|
||||||
|
ConnectivityApiSetup.setUp(binaryMessenger: engine.binaryMessenger, api: ConnectivityApiImpl())
|
||||||
}
|
}
|
||||||
|
|
||||||
public static func cancelPlugins(with engine: FlutterEngine) {
|
public static func cancelPlugins(with engine: FlutterEngine) {
|
||||||
|
|||||||
@@ -1,6 +1,60 @@
|
|||||||
|
import Network
|
||||||
|
|
||||||
class ConnectivityApiImpl: ConnectivityApi {
|
class ConnectivityApiImpl: ConnectivityApi {
|
||||||
|
private let monitor = NWPathMonitor()
|
||||||
|
private let queue = DispatchQueue(label: "ConnectivityMonitor")
|
||||||
|
private var currentPath: NWPath?
|
||||||
|
|
||||||
|
init() {
|
||||||
|
monitor.pathUpdateHandler = { [weak self] path in
|
||||||
|
self?.currentPath = path
|
||||||
|
}
|
||||||
|
monitor.start(queue: queue)
|
||||||
|
// Get initial state synchronously
|
||||||
|
currentPath = monitor.currentPath
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
monitor.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
func getCapabilities() throws -> [NetworkCapability] {
|
func getCapabilities() throws -> [NetworkCapability] {
|
||||||
[]
|
guard let path = currentPath else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
guard path.status == .satisfied else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
var capabilities: [NetworkCapability] = []
|
||||||
|
|
||||||
|
if path.usesInterfaceType(.wifi) {
|
||||||
|
capabilities.append(.wifi)
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.usesInterfaceType(.cellular) {
|
||||||
|
capabilities.append(.cellular)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for VPN - iOS reports VPN as .other interface type in many cases
|
||||||
|
// or through the path's expensive property when on cellular with VPN
|
||||||
|
if path.usesInterfaceType(.other) {
|
||||||
|
capabilities.append(.vpn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine if connection is unmetered:
|
||||||
|
// - Must be on WiFi (not cellular)
|
||||||
|
// - Must not be expensive (rules out personal hotspot)
|
||||||
|
// - Must not be constrained (Low Data Mode)
|
||||||
|
// Note: VPN over cellular should still be considered metered
|
||||||
|
let isOnCellular = path.usesInterfaceType(.cellular)
|
||||||
|
let isOnWifi = path.usesInterfaceType(.wifi)
|
||||||
|
|
||||||
|
if isOnWifi && !isOnCellular && !path.isExpensive && !path.isConstrained {
|
||||||
|
capabilities.append(.unmetered)
|
||||||
|
}
|
||||||
|
|
||||||
|
return capabilities
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,6 +312,39 @@ struct HashResult: Hashable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generated class from Pigeon that represents data sent in messages.
|
||||||
|
struct CloudIdResult: Hashable {
|
||||||
|
var assetId: String
|
||||||
|
var error: String? = nil
|
||||||
|
var cloudId: String? = nil
|
||||||
|
|
||||||
|
|
||||||
|
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||||
|
static func fromList(_ pigeonVar_list: [Any?]) -> CloudIdResult? {
|
||||||
|
let assetId = pigeonVar_list[0] as! String
|
||||||
|
let error: String? = nilOrValue(pigeonVar_list[1])
|
||||||
|
let cloudId: String? = nilOrValue(pigeonVar_list[2])
|
||||||
|
|
||||||
|
return CloudIdResult(
|
||||||
|
assetId: assetId,
|
||||||
|
error: error,
|
||||||
|
cloudId: cloudId
|
||||||
|
)
|
||||||
|
}
|
||||||
|
func toList() -> [Any?] {
|
||||||
|
return [
|
||||||
|
assetId,
|
||||||
|
error,
|
||||||
|
cloudId,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
static func == (lhs: CloudIdResult, rhs: CloudIdResult) -> Bool {
|
||||||
|
return deepEqualsMessages(lhs.toList(), rhs.toList()) }
|
||||||
|
func hash(into hasher: inout Hasher) {
|
||||||
|
deepHashMessages(value: toList(), hasher: &hasher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class MessagesPigeonCodecReader: FlutterStandardReader {
|
private class MessagesPigeonCodecReader: FlutterStandardReader {
|
||||||
override func readValue(ofType type: UInt8) -> Any? {
|
override func readValue(ofType type: UInt8) -> Any? {
|
||||||
switch type {
|
switch type {
|
||||||
@@ -323,6 +356,8 @@ private class MessagesPigeonCodecReader: FlutterStandardReader {
|
|||||||
return SyncDelta.fromList(self.readValue() as! [Any?])
|
return SyncDelta.fromList(self.readValue() as! [Any?])
|
||||||
case 132:
|
case 132:
|
||||||
return HashResult.fromList(self.readValue() as! [Any?])
|
return HashResult.fromList(self.readValue() as! [Any?])
|
||||||
|
case 133:
|
||||||
|
return CloudIdResult.fromList(self.readValue() as! [Any?])
|
||||||
default:
|
default:
|
||||||
return super.readValue(ofType: type)
|
return super.readValue(ofType: type)
|
||||||
}
|
}
|
||||||
@@ -343,6 +378,9 @@ private class MessagesPigeonCodecWriter: FlutterStandardWriter {
|
|||||||
} else if let value = value as? HashResult {
|
} else if let value = value as? HashResult {
|
||||||
super.writeByte(132)
|
super.writeByte(132)
|
||||||
super.writeValue(value.toList())
|
super.writeValue(value.toList())
|
||||||
|
} else if let value = value as? CloudIdResult {
|
||||||
|
super.writeByte(133)
|
||||||
|
super.writeValue(value.toList())
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(value)
|
super.writeValue(value)
|
||||||
}
|
}
|
||||||
@@ -377,6 +415,7 @@ protocol NativeSyncApi {
|
|||||||
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
func hashAssets(assetIds: [String], allowNetworkAccess: Bool, completion: @escaping (Result<[HashResult], Error>) -> Void)
|
||||||
func cancelHashing() throws
|
func cancelHashing() throws
|
||||||
func getTrashedAssets() throws -> [String: [PlatformAsset]]
|
func getTrashedAssets() throws -> [String: [PlatformAsset]]
|
||||||
|
func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||||
@@ -560,5 +599,22 @@ class NativeSyncApiSetup {
|
|||||||
} else {
|
} else {
|
||||||
getTrashedAssetsChannel.setMessageHandler(nil)
|
getTrashedAssetsChannel.setMessageHandler(nil)
|
||||||
}
|
}
|
||||||
|
let getCloudIdForAssetIdsChannel = taskQueue == nil
|
||||||
|
? FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||||
|
: FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec, taskQueue: taskQueue)
|
||||||
|
if let api = api {
|
||||||
|
getCloudIdForAssetIdsChannel.setMessageHandler { message, reply in
|
||||||
|
let args = message as! [Any?]
|
||||||
|
let assetIdsArg = args[0] as! [String]
|
||||||
|
do {
|
||||||
|
let result = try api.getCloudIdForAssetIds(assetIds: assetIdsArg)
|
||||||
|
reply(wrapResult(result))
|
||||||
|
} catch {
|
||||||
|
reply(wrapError(error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getCloudIdForAssetIdsChannel.setMessageHandler(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -390,4 +390,28 @@ class NativeSyncApiImpl: ImmichPlugin, NativeSyncApi, FlutterPlugin {
|
|||||||
return PHAsset.fetchAssets(in: album, options: options)
|
return PHAsset.fetchAssets(in: album, options: options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getCloudIdForAssetIds(assetIds: [String]) throws -> [CloudIdResult] {
|
||||||
|
guard #available(iOS 16, *) else {
|
||||||
|
return assetIds.map { CloudIdResult(assetId: $0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
var mappings: [CloudIdResult] = []
|
||||||
|
let result = PHPhotoLibrary.shared().cloudIdentifierMappings(forLocalIdentifiers: assetIds)
|
||||||
|
for (key, value) in result {
|
||||||
|
switch value {
|
||||||
|
case .success(let cloudIdentifier):
|
||||||
|
let cloudId = cloudIdentifier.stringValue
|
||||||
|
// Ignores invalid cloud ids of the format "GUID:ID:". Valid Ids are of the form "GUID:ID:HASH"
|
||||||
|
if !cloudId.hasSuffix(":") {
|
||||||
|
mappings.append(CloudIdResult(assetId: key, cloudId: cloudId))
|
||||||
|
} else {
|
||||||
|
mappings.append(CloudIdResult(assetId: key, error: "Incomplete Cloud Id: \(cloudId)"))
|
||||||
|
}
|
||||||
|
case .failure(let error):
|
||||||
|
mappings.append(CloudIdResult(assetId: key, error: "Error getting Cloud Id: \(error.localizedDescription)"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mappings;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ const int noDbId = -9223372036854775808; // from Isar
|
|||||||
const double downloadCompleted = -1;
|
const double downloadCompleted = -1;
|
||||||
const double downloadFailed = -2;
|
const double downloadFailed = -2;
|
||||||
|
|
||||||
|
const String kMobileMetadataKey = "mobile-app";
|
||||||
|
|
||||||
// Number of log entries to retain on app start
|
// Number of log entries to retain on app start
|
||||||
const int kLogTruncateLimit = 2000;
|
const int kLogTruncateLimit = 2000;
|
||||||
|
|
||||||
|
|||||||
@@ -51,4 +51,4 @@ const Map<String, Locale> locales = {
|
|||||||
|
|
||||||
const String translationsPath = 'assets/i18n';
|
const String translationsPath = 'assets/i18n';
|
||||||
|
|
||||||
const List<Locale> localesNotSupportedByOverpass = [Locale('el', 'GR'), Locale('sr', 'Cyrl')];
|
const List<Locale> localesNotSupportedByAppFont = [Locale('el', 'GR'), Locale('sr', 'Cyrl')];
|
||||||
|
|||||||
62
mobile/lib/domain/models/asset/asset_metadata.model.dart
Normal file
62
mobile/lib/domain/models/asset/asset_metadata.model.dart
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
enum RemoteAssetMetadataKey {
|
||||||
|
mobileApp("mobile-app");
|
||||||
|
|
||||||
|
final String key;
|
||||||
|
|
||||||
|
const RemoteAssetMetadataKey(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class RemoteAssetMetadataValue {
|
||||||
|
const RemoteAssetMetadataValue();
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteAssetMetadataItem {
|
||||||
|
final RemoteAssetMetadataKey key;
|
||||||
|
final RemoteAssetMetadataValue value;
|
||||||
|
|
||||||
|
const RemoteAssetMetadataItem({required this.key, required this.value});
|
||||||
|
|
||||||
|
Map<String, Object?> toJson() {
|
||||||
|
return {'key': key.key, 'value': value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteAssetMobileAppMetadata extends RemoteAssetMetadataValue {
|
||||||
|
final String? cloudId;
|
||||||
|
final String? createdAt;
|
||||||
|
final String? adjustmentTime;
|
||||||
|
final String? latitude;
|
||||||
|
final String? longitude;
|
||||||
|
|
||||||
|
const RemoteAssetMobileAppMetadata({
|
||||||
|
this.cloudId,
|
||||||
|
this.createdAt,
|
||||||
|
this.adjustmentTime,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final map = <String, Object?>{};
|
||||||
|
if (cloudId != null) {
|
||||||
|
map["iCloudId"] = cloudId;
|
||||||
|
}
|
||||||
|
if (createdAt != null) {
|
||||||
|
map["createdAt"] = createdAt;
|
||||||
|
}
|
||||||
|
if (adjustmentTime != null) {
|
||||||
|
map["adjustmentTime"] = adjustmentTime;
|
||||||
|
}
|
||||||
|
if (latitude != null) {
|
||||||
|
map["latitude"] = latitude;
|
||||||
|
}
|
||||||
|
if (longitude != null) {
|
||||||
|
map["longitude"] = longitude;
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,7 @@ sealed class BaseAsset {
|
|||||||
final int? durationInSeconds;
|
final int? durationInSeconds;
|
||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final String? livePhotoVideoId;
|
final String? livePhotoVideoId;
|
||||||
|
final bool isEdited;
|
||||||
|
|
||||||
const BaseAsset({
|
const BaseAsset({
|
||||||
required this.name,
|
required this.name,
|
||||||
@@ -34,6 +35,7 @@ sealed class BaseAsset {
|
|||||||
this.durationInSeconds,
|
this.durationInSeconds,
|
||||||
this.isFavorite = false,
|
this.isFavorite = false,
|
||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
|
required this.isEdited,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool get isImage => type == AssetType.image;
|
bool get isImage => type == AssetType.image;
|
||||||
@@ -71,6 +73,7 @@ sealed class BaseAsset {
|
|||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
isFavorite: $isFavorite,
|
isFavorite: $isFavorite,
|
||||||
|
isEdited: $isEdited,
|
||||||
}''';
|
}''';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +88,8 @@ sealed class BaseAsset {
|
|||||||
width == other.width &&
|
width == other.width &&
|
||||||
height == other.height &&
|
height == other.height &&
|
||||||
durationInSeconds == other.durationInSeconds &&
|
durationInSeconds == other.durationInSeconds &&
|
||||||
isFavorite == other.isFavorite;
|
isFavorite == other.isFavorite &&
|
||||||
|
isEdited == other.isEdited;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -99,6 +103,7 @@ sealed class BaseAsset {
|
|||||||
width.hashCode ^
|
width.hashCode ^
|
||||||
height.hashCode ^
|
height.hashCode ^
|
||||||
durationInSeconds.hashCode ^
|
durationInSeconds.hashCode ^
|
||||||
isFavorite.hashCode;
|
isFavorite.hashCode ^
|
||||||
|
isEdited.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ part of 'base_asset.model.dart';
|
|||||||
class LocalAsset extends BaseAsset {
|
class LocalAsset extends BaseAsset {
|
||||||
final String id;
|
final String id;
|
||||||
final String? remoteAssetId;
|
final String? remoteAssetId;
|
||||||
|
final String? cloudId;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
|
|
||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
@@ -12,6 +13,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
const LocalAsset({
|
const LocalAsset({
|
||||||
required this.id,
|
required this.id,
|
||||||
String? remoteId,
|
String? remoteId,
|
||||||
|
this.cloudId,
|
||||||
required super.name,
|
required super.name,
|
||||||
super.checksum,
|
super.checksum,
|
||||||
required super.type,
|
required super.type,
|
||||||
@@ -26,6 +28,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
|
required super.isEdited,
|
||||||
}) : remoteAssetId = remoteId;
|
}) : remoteAssetId = remoteId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -53,12 +56,14 @@ class LocalAsset extends BaseAsset {
|
|||||||
width: ${width ?? "<NA>"},
|
width: ${width ?? "<NA>"},
|
||||||
height: ${height ?? "<NA>"},
|
height: ${height ?? "<NA>"},
|
||||||
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
durationInSeconds: ${durationInSeconds ?? "<NA>"},
|
||||||
remoteId: ${remoteId ?? "<NA>"}
|
remoteId: ${remoteId ?? "<NA>"},
|
||||||
|
cloudId: ${cloudId ?? "<NA>"},
|
||||||
|
checksum: ${checksum ?? "<NA>"},
|
||||||
isFavorite: $isFavorite,
|
isFavorite: $isFavorite,
|
||||||
orientation: $orientation,
|
orientation: $orientation,
|
||||||
adjustmentTime: $adjustmentTime,
|
adjustmentTime: $adjustmentTime,
|
||||||
latitude: ${latitude ?? "<NA>"},
|
latitude: ${latitude ?? "<NA>"},
|
||||||
longitude: ${longitude ?? "<NA>"},
|
longitude: ${longitude ?? "<NA>"},
|
||||||
}''';
|
}''';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +74,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
return super == other &&
|
return super == other &&
|
||||||
id == other.id &&
|
id == other.id &&
|
||||||
|
cloudId == other.cloudId &&
|
||||||
orientation == other.orientation &&
|
orientation == other.orientation &&
|
||||||
adjustmentTime == other.adjustmentTime &&
|
adjustmentTime == other.adjustmentTime &&
|
||||||
latitude == other.latitude &&
|
latitude == other.latitude &&
|
||||||
@@ -88,6 +94,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
LocalAsset copyWith({
|
LocalAsset copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
String? remoteId,
|
String? remoteId,
|
||||||
|
String? cloudId,
|
||||||
String? name,
|
String? name,
|
||||||
String? checksum,
|
String? checksum,
|
||||||
AssetType? type,
|
AssetType? type,
|
||||||
@@ -101,10 +108,12 @@ class LocalAsset extends BaseAsset {
|
|||||||
DateTime? adjustmentTime,
|
DateTime? adjustmentTime,
|
||||||
double? latitude,
|
double? latitude,
|
||||||
double? longitude,
|
double? longitude,
|
||||||
|
bool? isEdited,
|
||||||
}) {
|
}) {
|
||||||
return LocalAsset(
|
return LocalAsset(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
remoteId: remoteId ?? this.remoteId,
|
remoteId: remoteId ?? this.remoteId,
|
||||||
|
cloudId: cloudId ?? this.cloudId,
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
checksum: checksum ?? this.checksum,
|
checksum: checksum ?? this.checksum,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -118,6 +127,7 @@ class LocalAsset extends BaseAsset {
|
|||||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
|
isEdited: isEdited ?? this.isEdited,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
this.visibility = AssetVisibility.timeline,
|
this.visibility = AssetVisibility.timeline,
|
||||||
super.livePhotoVideoId,
|
super.livePhotoVideoId,
|
||||||
this.stackId,
|
this.stackId,
|
||||||
|
required super.isEdited,
|
||||||
}) : localAssetId = localId;
|
}) : localAssetId = localId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -104,6 +105,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
AssetVisibility? visibility,
|
AssetVisibility? visibility,
|
||||||
String? livePhotoVideoId,
|
String? livePhotoVideoId,
|
||||||
String? stackId,
|
String? stackId,
|
||||||
|
bool? isEdited,
|
||||||
}) {
|
}) {
|
||||||
return RemoteAsset(
|
return RemoteAsset(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
@@ -122,6 +124,7 @@ class RemoteAsset extends BaseAsset {
|
|||||||
visibility: visibility ?? this.visibility,
|
visibility: visibility ?? this.visibility,
|
||||||
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
livePhotoVideoId: livePhotoVideoId ?? this.livePhotoVideoId,
|
||||||
stackId: stackId ?? this.stackId,
|
stackId: stackId ?? this.stackId,
|
||||||
|
isEdited: isEdited ?? this.isEdited,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ 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/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/network_capability_extensions.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';
|
||||||
@@ -20,13 +19,13 @@ 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/db.provider.dart';
|
import 'package:immich_mobile/providers/db.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';
|
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/repositories/file_media.repository.dart';
|
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.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/localization.service.dart';
|
import 'package:immich_mobile/services/localization.service.dart';
|
||||||
import 'package:immich_mobile/services/upload.service.dart';
|
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||||
import 'package:immich_mobile/utils/bootstrap.dart';
|
import 'package:immich_mobile/utils/bootstrap.dart';
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
import 'package:immich_mobile/utils/http_ssl_options.dart';
|
||||||
@@ -243,13 +242,12 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
return _ref?.read(driftBackupProvider.notifier).handleBackupResume(currentUser.id);
|
return _ref?.read(driftBackupProvider.notifier).startBackupWithURLSession(currentUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
final networkCapabilities = await _ref?.read(connectivityApiProvider).getCapabilities() ?? [];
|
|
||||||
return _ref
|
return _ref
|
||||||
?.read(uploadServiceProvider)
|
?.read(foregroundUploadServiceProvider)
|
||||||
.startBackupWithHttpClient(currentUser.id, networkCapabilities.isUnmetered, _cancellationToken);
|
.uploadCandidates(currentUser.id, _cancellationToken, useSequentialUpload: true);
|
||||||
},
|
},
|
||||||
(error, stack) {
|
(error, stack) {
|
||||||
dPrint(() => "Error in backup zone $error, $stack");
|
dPrint(() => "Error in backup zone $error, $stack");
|
||||||
|
|||||||
@@ -40,6 +40,9 @@ class HashService {
|
|||||||
_log.info("Starting hashing of assets");
|
_log.info("Starting hashing of assets");
|
||||||
final Stopwatch stopwatch = Stopwatch()..start();
|
final Stopwatch stopwatch = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
|
// Migrate hashes from cloud ID to local ID so we don't have to re-hash them
|
||||||
|
await _migrateHashes();
|
||||||
|
|
||||||
// Sorted by backupSelection followed by isCloud
|
// Sorted by backupSelection followed by isCloud
|
||||||
final localAlbums = await _localAlbumRepository.getBackupAlbums();
|
final localAlbums = await _localAlbumRepository.getBackupAlbums();
|
||||||
|
|
||||||
@@ -75,6 +78,15 @@ class HashService {
|
|||||||
_log.info("Hashing took - ${stopwatch.elapsedMilliseconds}ms");
|
_log.info("Hashing took - ${stopwatch.elapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _migrateHashes() async {
|
||||||
|
final hashMappings = await _localAssetRepository.getHashMappingFromCloudId();
|
||||||
|
if (hashMappings.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _localAssetRepository.updateHashes(hashMappings);
|
||||||
|
}
|
||||||
|
|
||||||
/// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB
|
/// Processes a list of [LocalAsset]s, storing their hash and updating the assets in the DB
|
||||||
/// with hash for those that were successfully hashed. Hashes are looked up in a table
|
/// with hash for those that were successfully hashed. Hashes are looked up in a table
|
||||||
/// [LocalAssetHashEntity] by local id. Only missing entries are newly hashed and added to the DB.
|
/// [LocalAssetHashEntity] by local id. Only missing entries are newly hashed and added to the DB.
|
||||||
|
|||||||
@@ -8,6 +8,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/platform_extensions.dart';
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/trashed_local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
@@ -18,6 +19,7 @@ import 'package:logging/logging.dart';
|
|||||||
|
|
||||||
class LocalSyncService {
|
class LocalSyncService {
|
||||||
final DriftLocalAlbumRepository _localAlbumRepository;
|
final DriftLocalAlbumRepository _localAlbumRepository;
|
||||||
|
final DriftLocalAssetRepository _localAssetRepository;
|
||||||
final NativeSyncApi _nativeSyncApi;
|
final NativeSyncApi _nativeSyncApi;
|
||||||
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
final DriftTrashedLocalAssetRepository _trashedLocalAssetRepository;
|
||||||
final LocalFilesManagerRepository _localFilesManager;
|
final LocalFilesManagerRepository _localFilesManager;
|
||||||
@@ -26,11 +28,13 @@ class LocalSyncService {
|
|||||||
|
|
||||||
LocalSyncService({
|
LocalSyncService({
|
||||||
required DriftLocalAlbumRepository localAlbumRepository,
|
required DriftLocalAlbumRepository localAlbumRepository,
|
||||||
|
required DriftLocalAssetRepository localAssetRepository,
|
||||||
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
required DriftTrashedLocalAssetRepository trashedLocalAssetRepository,
|
||||||
required LocalFilesManagerRepository localFilesManager,
|
required LocalFilesManagerRepository localFilesManager,
|
||||||
required StorageRepository storageRepository,
|
required StorageRepository storageRepository,
|
||||||
required NativeSyncApi nativeSyncApi,
|
required NativeSyncApi nativeSyncApi,
|
||||||
}) : _localAlbumRepository = localAlbumRepository,
|
}) : _localAlbumRepository = localAlbumRepository,
|
||||||
|
_localAssetRepository = localAssetRepository,
|
||||||
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
_trashedLocalAssetRepository = trashedLocalAssetRepository,
|
||||||
_localFilesManager = localFilesManager,
|
_localFilesManager = localFilesManager,
|
||||||
_storageRepository = storageRepository,
|
_storageRepository = storageRepository,
|
||||||
@@ -47,6 +51,12 @@ class LocalSyncService {
|
|||||||
_log.warning("syncTrashedAssets cannot proceed because MANAGE_MEDIA permission is missing");
|
_log.warning("syncTrashedAssets cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CurrentPlatform.isIOS) {
|
||||||
|
final assets = await _localAssetRepository.getEmptyCloudIdAssets();
|
||||||
|
await _mapIosCloudIds(assets);
|
||||||
|
}
|
||||||
|
|
||||||
if (full || await _nativeSyncApi.shouldFullSync()) {
|
if (full || await _nativeSyncApi.shouldFullSync()) {
|
||||||
_log.fine("Full sync request from ${full ? "user" : "native"}");
|
_log.fine("Full sync request from ${full ? "user" : "native"}");
|
||||||
return await fullSync();
|
return await fullSync();
|
||||||
@@ -63,8 +73,9 @@ class LocalSyncService {
|
|||||||
|
|
||||||
final deviceAlbums = await _nativeSyncApi.getAlbums();
|
final deviceAlbums = await _nativeSyncApi.getAlbums();
|
||||||
await _localAlbumRepository.updateAll(deviceAlbums.toLocalAlbums());
|
await _localAlbumRepository.updateAll(deviceAlbums.toLocalAlbums());
|
||||||
|
final newAssets = delta.updates.toLocalAssets();
|
||||||
await _localAlbumRepository.processDelta(
|
await _localAlbumRepository.processDelta(
|
||||||
updates: delta.updates.toLocalAssets(),
|
updates: newAssets,
|
||||||
deletes: delta.deletes,
|
deletes: delta.deletes,
|
||||||
assetAlbums: delta.assetAlbums,
|
assetAlbums: delta.assetAlbums,
|
||||||
);
|
);
|
||||||
@@ -92,6 +103,8 @@ class LocalSyncService {
|
|||||||
}
|
}
|
||||||
await updateAlbum(dbAlbum, album);
|
await updateAlbum(dbAlbum, album);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _mapIosCloudIds(newAssets);
|
||||||
}
|
}
|
||||||
await _nativeSyncApi.checkpointSync();
|
await _nativeSyncApi.checkpointSync();
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -130,9 +143,12 @@ class LocalSyncService {
|
|||||||
try {
|
try {
|
||||||
_log.fine("Adding device album ${album.name}");
|
_log.fine("Adding device album ${album.name}");
|
||||||
|
|
||||||
final assets = album.assetCount > 0 ? await _nativeSyncApi.getAssetsForAlbum(album.id) : <PlatformAsset>[];
|
final assets = album.assetCount > 0
|
||||||
|
? await _nativeSyncApi.getAssetsForAlbum(album.id).then((a) => a.toLocalAssets())
|
||||||
|
: <LocalAsset>[];
|
||||||
|
|
||||||
await _localAlbumRepository.upsert(album, toUpsert: assets.toLocalAssets());
|
await _localAlbumRepository.upsert(album, toUpsert: assets);
|
||||||
|
await _mapIosCloudIds(assets);
|
||||||
_log.fine("Successfully added device album ${album.name}");
|
_log.fine("Successfully added device album ${album.name}");
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.warning("Error while adding device album", e, s);
|
_log.warning("Error while adding device album", e, s);
|
||||||
@@ -202,13 +218,16 @@ class LocalSyncService {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final newAssets = await _nativeSyncApi.getAssetsForAlbum(deviceAlbum.id, updatedTimeCond: updatedTime);
|
final newAssets = await _nativeSyncApi
|
||||||
|
.getAssetsForAlbum(deviceAlbum.id, updatedTimeCond: updatedTime)
|
||||||
|
.then((a) => a.toLocalAssets());
|
||||||
|
|
||||||
await _localAlbumRepository.upsert(
|
await _localAlbumRepository.upsert(
|
||||||
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
deviceAlbum.copyWith(backupSelection: dbAlbum.backupSelection),
|
||||||
toUpsert: newAssets.toLocalAssets(),
|
toUpsert: newAssets,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await _mapIosCloudIds(newAssets);
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
_log.warning("Error on fast syncing local album: ${dbAlbum.name}", e, s);
|
_log.warning("Error on fast syncing local album: ${dbAlbum.name}", e, s);
|
||||||
@@ -240,6 +259,7 @@ class LocalSyncService {
|
|||||||
if (dbAlbum.assetCount == 0) {
|
if (dbAlbum.assetCount == 0) {
|
||||||
_log.fine("Device album ${deviceAlbum.name} is empty. Adding assets to DB.");
|
_log.fine("Device album ${deviceAlbum.name} is empty. Adding assets to DB.");
|
||||||
await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsInDevice);
|
await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsInDevice);
|
||||||
|
await _mapIosCloudIds(assetsInDevice);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +297,7 @@ class LocalSyncService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsToUpsert, toDelete: assetsToDelete);
|
await _localAlbumRepository.upsert(updatedDeviceAlbum, toUpsert: assetsToUpsert, toDelete: assetsToDelete);
|
||||||
|
await _mapIosCloudIds(assetsToUpsert);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
@@ -285,6 +306,29 @@ class LocalSyncService {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _mapIosCloudIds(List<LocalAsset> assets) async {
|
||||||
|
if (!CurrentPlatform.isIOS || assets.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final assetIds = assets.map((a) => a.id).toList();
|
||||||
|
final cloudMapping = <String, String>{};
|
||||||
|
final cloudIds = await _nativeSyncApi.getCloudIdForAssetIds(assetIds);
|
||||||
|
for (int i = 0; i < cloudIds.length; i++) {
|
||||||
|
final cloudIdResult = cloudIds[i];
|
||||||
|
if (cloudIdResult.cloudId != null) {
|
||||||
|
cloudMapping[cloudIdResult.assetId] = cloudIdResult.cloudId!;
|
||||||
|
} else {
|
||||||
|
final asset = assets.firstWhereOrNull((a) => a.id == cloudIdResult.assetId);
|
||||||
|
_log.fine(
|
||||||
|
"Cannot fetch cloudId for asset with id: ${cloudIdResult.assetId}, name: ${asset?.name}, createdAt: ${asset?.createdAt}. Error: ${cloudIdResult.error ?? "unknown"}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _localAlbumRepository.updateCloudMapping(cloudMapping);
|
||||||
|
}
|
||||||
|
|
||||||
bool _assetsEqual(LocalAsset a, LocalAsset b) {
|
bool _assetsEqual(LocalAsset a, LocalAsset b) {
|
||||||
if (CurrentPlatform.isAndroid) {
|
if (CurrentPlatform.isAndroid) {
|
||||||
return a.updatedAt.isAtSameMomentAs(b.updatedAt) &&
|
return a.updatedAt.isAtSameMomentAs(b.updatedAt) &&
|
||||||
@@ -392,5 +436,6 @@ extension PlatformToLocalAsset on PlatformAsset {
|
|||||||
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
|
adjustmentTime: tryFromSecondsSinceEpoch(adjustmentTime, isUtc: true),
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ extension on AssetResponseDto {
|
|||||||
thumbHash: thumbhash,
|
thumbHash: thumbhash,
|
||||||
localId: null,
|
localId: null,
|
||||||
type: type.toAssetType(),
|
type: type.toAssetType(),
|
||||||
|
isEdited: isEdited,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,10 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAssetsV1(data.cast());
|
return _syncStreamRepository.deleteAssetsV1(data.cast());
|
||||||
case SyncEntityType.assetExifV1:
|
case SyncEntityType.assetExifV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
||||||
|
case SyncEntityType.assetMetadataV1:
|
||||||
|
return _syncStreamRepository.updateAssetsMetadataV1(data.cast());
|
||||||
|
case SyncEntityType.assetMetadataDeleteV1:
|
||||||
|
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.partnerAssetBackfillV1:
|
case SyncEntityType.partnerAssetBackfillV1:
|
||||||
@@ -243,6 +247,42 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleWsAssetEditReadyV1Batch(List<dynamic> batchData) async {
|
||||||
|
if (batchData.isEmpty) return;
|
||||||
|
|
||||||
|
_logger.info('Processing batch of ${batchData.length} AssetEditReadyV1 events');
|
||||||
|
|
||||||
|
final List<SyncAssetV1> assets = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (final data in batchData) {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
final assetData = payload['asset'];
|
||||||
|
|
||||||
|
if (assetData == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV1.fromJson(assetData);
|
||||||
|
|
||||||
|
if (asset != null) {
|
||||||
|
assets.add(asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.isNotEmpty) {
|
||||||
|
await _syncStreamRepository.updateAssetsV1(assets, debugLabel: 'websocket-edit');
|
||||||
|
_logger.info('Successfully processed ${assets.length} edited assets');
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetEditReadyV1 websocket batch events", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleRemoteTrashed(Iterable<String> checksums) async {
|
Future<void> _handleRemoteTrashed(Iterable<String> checksums) async {
|
||||||
if (checksums.isEmpty) {
|
if (checksums.isEmpty) {
|
||||||
return Future.value();
|
return Future.value();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:immich_mobile/domain/utils/migrate_cloud_ids.dart' as m;
|
||||||
import 'package:immich_mobile/domain/utils/sync_linked_album.dart';
|
import 'package:immich_mobile/domain/utils/sync_linked_album.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/sync.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/sync.provider.dart';
|
||||||
import 'package:immich_mobile/utils/isolate.dart';
|
import 'package:immich_mobile/utils/isolate.dart';
|
||||||
@@ -22,8 +23,13 @@ class BackgroundSyncManager {
|
|||||||
final SyncCallback? onHashingComplete;
|
final SyncCallback? onHashingComplete;
|
||||||
final SyncErrorCallback? onHashingError;
|
final SyncErrorCallback? onHashingError;
|
||||||
|
|
||||||
|
final SyncCallback? onCloudIdSyncStart;
|
||||||
|
final SyncCallback? onCloudIdSyncComplete;
|
||||||
|
final SyncErrorCallback? onCloudIdSyncError;
|
||||||
|
|
||||||
Cancelable<bool?>? _syncTask;
|
Cancelable<bool?>? _syncTask;
|
||||||
Cancelable<void>? _syncWebsocketTask;
|
Cancelable<void>? _syncWebsocketTask;
|
||||||
|
Cancelable<void>? _cloudIdSyncTask;
|
||||||
Cancelable<void>? _deviceAlbumSyncTask;
|
Cancelable<void>? _deviceAlbumSyncTask;
|
||||||
Cancelable<void>? _linkedAlbumSyncTask;
|
Cancelable<void>? _linkedAlbumSyncTask;
|
||||||
Cancelable<void>? _hashTask;
|
Cancelable<void>? _hashTask;
|
||||||
@@ -38,6 +44,9 @@ class BackgroundSyncManager {
|
|||||||
this.onHashingStart,
|
this.onHashingStart,
|
||||||
this.onHashingComplete,
|
this.onHashingComplete,
|
||||||
this.onHashingError,
|
this.onHashingError,
|
||||||
|
this.onCloudIdSyncStart,
|
||||||
|
this.onCloudIdSyncComplete,
|
||||||
|
this.onCloudIdSyncError,
|
||||||
});
|
});
|
||||||
|
|
||||||
Future<void> cancel() async {
|
Future<void> cancel() async {
|
||||||
@@ -55,6 +64,12 @@ class BackgroundSyncManager {
|
|||||||
_syncWebsocketTask?.cancel();
|
_syncWebsocketTask?.cancel();
|
||||||
_syncWebsocketTask = null;
|
_syncWebsocketTask = null;
|
||||||
|
|
||||||
|
if (_cloudIdSyncTask != null) {
|
||||||
|
futures.add(_cloudIdSyncTask!.future);
|
||||||
|
}
|
||||||
|
_cloudIdSyncTask?.cancel();
|
||||||
|
_cloudIdSyncTask = null;
|
||||||
|
|
||||||
if (_linkedAlbumSyncTask != null) {
|
if (_linkedAlbumSyncTask != null) {
|
||||||
futures.add(_linkedAlbumSyncTask!.future);
|
futures.add(_linkedAlbumSyncTask!.future);
|
||||||
}
|
}
|
||||||
@@ -121,7 +136,6 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to cancel the task, as it can also be run when the user logs out
|
|
||||||
Future<void> hashAssets() {
|
Future<void> hashAssets() {
|
||||||
if (_hashTask != null) {
|
if (_hashTask != null) {
|
||||||
return _hashTask!.future;
|
return _hashTask!.future;
|
||||||
@@ -182,6 +196,16 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditBatch(List<dynamic> batchData) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetEditReadyV1Batch(batchData);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> syncLinkedAlbum() {
|
Future<void> syncLinkedAlbum() {
|
||||||
if (_linkedAlbumSyncTask != null) {
|
if (_linkedAlbumSyncTask != null) {
|
||||||
return _linkedAlbumSyncTask!.future;
|
return _linkedAlbumSyncTask!.future;
|
||||||
@@ -192,9 +216,33 @@ class BackgroundSyncManager {
|
|||||||
_linkedAlbumSyncTask = null;
|
_linkedAlbumSyncTask = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> syncCloudIds() {
|
||||||
|
if (_cloudIdSyncTask != null) {
|
||||||
|
return _cloudIdSyncTask!.future;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCloudIdSyncStart?.call();
|
||||||
|
|
||||||
|
_cloudIdSyncTask = runInIsolateGentle(computation: m.syncCloudIds);
|
||||||
|
return _cloudIdSyncTask!
|
||||||
|
.whenComplete(() {
|
||||||
|
onCloudIdSyncComplete?.call();
|
||||||
|
_cloudIdSyncTask = null;
|
||||||
|
})
|
||||||
|
.catchError((error) {
|
||||||
|
onCloudIdSyncError?.call(error.toString());
|
||||||
|
_cloudIdSyncTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => runInIsolateGentle(
|
Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => runInIsolateGentle(
|
||||||
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData),
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV1Batch(batchData),
|
||||||
debugLabel: 'websocket-batch',
|
debugLabel: 'websocket-batch',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetEditReadyV1Batch(List<dynamic> batchData) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1Batch(batchData),
|
||||||
|
debugLabel: 'websocket-edit',
|
||||||
|
);
|
||||||
|
|||||||
175
mobile/lib/domain/utils/migrate_cloud_ids.dart
Normal file
175
mobile/lib/domain/utils/migrate_cloud_ids.dart
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
|
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/extensions/platform_extensions.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/repositories/local_album.repository.dart';
|
||||||
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
|
import 'package:immich_mobile/providers/api.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/sync.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/server_info.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
// ignore: import_rule_openapi
|
||||||
|
import 'package:openapi/api.dart' hide AssetVisibility;
|
||||||
|
|
||||||
|
Future<void> syncCloudIds(ProviderContainer ref) async {
|
||||||
|
if (!CurrentPlatform.isIOS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final logger = Logger('migrateCloudIds');
|
||||||
|
|
||||||
|
final db = ref.read(driftProvider);
|
||||||
|
// Populate cloud IDs for local assets that don't have one yet
|
||||||
|
await _populateCloudIds(db);
|
||||||
|
|
||||||
|
final serverInfo = await ref.read(serverInfoProvider.notifier).getServerInfo();
|
||||||
|
final canUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 4);
|
||||||
|
if (!canUpdateMetadata) {
|
||||||
|
logger.fine('Server version does not support asset metadata updates. Skipping cloudId migration.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final canBulkUpdateMetadata = serverInfo.serverVersion.isAtLeast(major: 2, minor: 5);
|
||||||
|
|
||||||
|
// Wait for remote sync to complete, so we have up-to-date asset metadata entries
|
||||||
|
try {
|
||||||
|
await ref.read(syncStreamServiceProvider).sync();
|
||||||
|
} catch (e, s) {
|
||||||
|
logger.fine('Failed to complete remote sync before cloudId migration.', e, s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the mapping for backed up assets that have a cloud ID locally but do not have a cloud ID on the server
|
||||||
|
final currentUser = ref.read(currentUserProvider);
|
||||||
|
if (currentUser == null) {
|
||||||
|
logger.warning('Current user is null. Aborting cloudId migration.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final mappingsToUpdate = await _fetchCloudIdMappings(db, currentUser.id);
|
||||||
|
// Deduplicate mappings as a single remote asset ID can match multiple local assets
|
||||||
|
final seenRemoteAssetIds = <String>{};
|
||||||
|
final uniqueMapping = mappingsToUpdate.where((mapping) {
|
||||||
|
if (!seenRemoteAssetIds.add(mapping.remoteAssetId)) {
|
||||||
|
logger.fine('Duplicate remote asset ID found: ${mapping.remoteAssetId}. Skipping duplicate entry.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
final assetApi = ref.read(apiServiceProvider).assetsApi;
|
||||||
|
|
||||||
|
if (canBulkUpdateMetadata) {
|
||||||
|
await _bulkUpdateCloudIds(assetApi, uniqueMapping);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await _sequentialUpdateCloudIds(assetApi, uniqueMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _sequentialUpdateCloudIds(AssetsApi assetsApi, List<_CloudIdMapping> mappings) async {
|
||||||
|
for (final mapping in mappings) {
|
||||||
|
final item = AssetMetadataUpsertItemDto(
|
||||||
|
key: kMobileMetadataKey,
|
||||||
|
value: RemoteAssetMobileAppMetadata(
|
||||||
|
cloudId: mapping.localAsset.cloudId,
|
||||||
|
createdAt: mapping.localAsset.createdAt.toIso8601String(),
|
||||||
|
adjustmentTime: mapping.localAsset.adjustmentTime?.toIso8601String(),
|
||||||
|
latitude: mapping.localAsset.latitude?.toString(),
|
||||||
|
longitude: mapping.localAsset.longitude?.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
await assetsApi.updateAssetMetadata(mapping.remoteAssetId, AssetMetadataUpsertDto(items: [item]));
|
||||||
|
} catch (error, stack) {
|
||||||
|
Logger('migrateCloudIds').warning('Failed to update metadata for asset ${mapping.remoteAssetId}', error, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _bulkUpdateCloudIds(AssetsApi assetsApi, List<_CloudIdMapping> mappings) async {
|
||||||
|
const batchSize = 10000;
|
||||||
|
for (int i = 0; i < mappings.length; i += batchSize) {
|
||||||
|
final endIndex = (i + batchSize > mappings.length) ? mappings.length : i + batchSize;
|
||||||
|
final batch = mappings.sublist(i, endIndex);
|
||||||
|
final items = <AssetMetadataBulkUpsertItemDto>[];
|
||||||
|
for (final mapping in batch) {
|
||||||
|
items.add(
|
||||||
|
AssetMetadataBulkUpsertItemDto(
|
||||||
|
assetId: mapping.remoteAssetId,
|
||||||
|
key: kMobileMetadataKey,
|
||||||
|
value: RemoteAssetMobileAppMetadata(
|
||||||
|
cloudId: mapping.localAsset.cloudId,
|
||||||
|
createdAt: mapping.localAsset.createdAt.toIso8601String(),
|
||||||
|
adjustmentTime: mapping.localAsset.adjustmentTime?.toIso8601String(),
|
||||||
|
latitude: mapping.localAsset.latitude?.toString(),
|
||||||
|
longitude: mapping.localAsset.longitude?.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await assetsApi.updateBulkAssetMetadata(AssetMetadataBulkUpsertDto(items: items));
|
||||||
|
} catch (error, stack) {
|
||||||
|
Logger('migrateCloudIds').warning('Failed to bulk update metadata', error, stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _populateCloudIds(Drift drift) async {
|
||||||
|
final query = drift.localAssetEntity.selectOnly()
|
||||||
|
..addColumns([drift.localAssetEntity.id])
|
||||||
|
..where(drift.localAssetEntity.iCloudId.isNull());
|
||||||
|
final ids = await query.map((row) => row.read(drift.localAssetEntity.id)!).get();
|
||||||
|
final cloudMapping = <String, String>{};
|
||||||
|
final cloudIds = await NativeSyncApi().getCloudIdForAssetIds(ids);
|
||||||
|
for (int i = 0; i < cloudIds.length; i++) {
|
||||||
|
final cloudIdResult = cloudIds[i];
|
||||||
|
if (cloudIdResult.cloudId != null) {
|
||||||
|
cloudMapping[cloudIdResult.assetId] = cloudIdResult.cloudId!;
|
||||||
|
} else {
|
||||||
|
Logger('migrateCloudIds').fine(
|
||||||
|
"Cannot fetch cloudId for asset with id: ${cloudIdResult.assetId}. Error: ${cloudIdResult.error ?? "unknown"}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await DriftLocalAlbumRepository(drift).updateCloudMapping(cloudMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef _CloudIdMapping = ({String remoteAssetId, LocalAsset localAsset});
|
||||||
|
|
||||||
|
Future<List<_CloudIdMapping>> _fetchCloudIdMappings(Drift drift, String userId) async {
|
||||||
|
final query =
|
||||||
|
drift.remoteAssetEntity.select().join([
|
||||||
|
leftOuterJoin(
|
||||||
|
drift.localAssetEntity,
|
||||||
|
drift.localAssetEntity.checksum.equalsExp(drift.remoteAssetEntity.checksum),
|
||||||
|
),
|
||||||
|
leftOuterJoin(
|
||||||
|
drift.remoteAssetCloudIdEntity,
|
||||||
|
drift.remoteAssetEntity.id.equalsExp(drift.remoteAssetCloudIdEntity.assetId),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
])..where(
|
||||||
|
// Only select assets that have a local cloud ID but either no remote cloud ID or a mismatched eTag
|
||||||
|
drift.localAssetEntity.id.isNotNull() &
|
||||||
|
drift.localAssetEntity.iCloudId.isNotNull() &
|
||||||
|
drift.remoteAssetEntity.ownerId.equals(userId) &
|
||||||
|
// Skip locked assets as we cannot update them without unlocking first
|
||||||
|
drift.remoteAssetEntity.visibility.isNotValue(AssetVisibility.locked.index) &
|
||||||
|
(drift.remoteAssetCloudIdEntity.cloudId.isNull() |
|
||||||
|
drift.remoteAssetCloudIdEntity.adjustmentTime.isNotExp(drift.localAssetEntity.adjustmentTime) |
|
||||||
|
drift.remoteAssetCloudIdEntity.latitude.isNotExp(drift.localAssetEntity.latitude) |
|
||||||
|
drift.remoteAssetCloudIdEntity.longitude.isNotExp(drift.localAssetEntity.longitude) |
|
||||||
|
drift.remoteAssetCloudIdEntity.createdAt.isNotExp(drift.localAssetEntity.createdAt)),
|
||||||
|
);
|
||||||
|
return query.map((row) {
|
||||||
|
return (
|
||||||
|
remoteAssetId: row.read(drift.remoteAssetEntity.id)!,
|
||||||
|
localAsset: row.readTable(drift.localAssetEntity).toDto(),
|
||||||
|
);
|
||||||
|
}).get();
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)')
|
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_checksum ON local_asset_entity (checksum)')
|
||||||
|
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)')
|
||||||
class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||||
const LocalAssetEntity();
|
const LocalAssetEntity();
|
||||||
|
|
||||||
@@ -16,6 +17,8 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
|||||||
|
|
||||||
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
|
TextColumn get iCloudId => text().nullable()();
|
||||||
|
|
||||||
DateTimeColumn get adjustmentTime => dateTime().nullable()();
|
DateTimeColumn get adjustmentTime => dateTime().nullable()();
|
||||||
|
|
||||||
RealColumn get latitude => real().nullable()();
|
RealColumn get latitude => real().nullable()();
|
||||||
@@ -43,5 +46,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
|||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
|
cloudId: iCloudId,
|
||||||
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ typedef $$LocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<String?> checksum,
|
i0.Value<String?> checksum,
|
||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
|
i0.Value<String?> iCloudId,
|
||||||
i0.Value<DateTime?> adjustmentTime,
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
i0.Value<double?> latitude,
|
i0.Value<double?> latitude,
|
||||||
i0.Value<double?> longitude,
|
i0.Value<double?> longitude,
|
||||||
@@ -38,6 +39,7 @@ typedef $$LocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<String?> checksum,
|
i0.Value<String?> checksum,
|
||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
|
i0.Value<String?> iCloudId,
|
||||||
i0.Value<DateTime?> adjustmentTime,
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
i0.Value<double?> latitude,
|
i0.Value<double?> latitude,
|
||||||
i0.Value<double?> longitude,
|
i0.Value<double?> longitude,
|
||||||
@@ -108,6 +110,11 @@ class $$LocalAssetEntityTableFilterComposer
|
|||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<String> get iCloudId => $composableBuilder(
|
||||||
|
column: $table.iCloudId,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
i0.ColumnFilters<DateTime> get adjustmentTime => $composableBuilder(
|
i0.ColumnFilters<DateTime> get adjustmentTime => $composableBuilder(
|
||||||
column: $table.adjustmentTime,
|
column: $table.adjustmentTime,
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
@@ -188,6 +195,11 @@ class $$LocalAssetEntityTableOrderingComposer
|
|||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<String> get iCloudId => $composableBuilder(
|
||||||
|
column: $table.iCloudId,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
i0.ColumnOrderings<DateTime> get adjustmentTime => $composableBuilder(
|
i0.ColumnOrderings<DateTime> get adjustmentTime => $composableBuilder(
|
||||||
column: $table.adjustmentTime,
|
column: $table.adjustmentTime,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
@@ -252,6 +264,9 @@ class $$LocalAssetEntityTableAnnotationComposer
|
|||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<String> get iCloudId =>
|
||||||
|
$composableBuilder(column: $table.iCloudId, builder: (column) => column);
|
||||||
|
|
||||||
i0.GeneratedColumn<DateTime> get adjustmentTime => $composableBuilder(
|
i0.GeneratedColumn<DateTime> get adjustmentTime => $composableBuilder(
|
||||||
column: $table.adjustmentTime,
|
column: $table.adjustmentTime,
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
@@ -315,6 +330,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> iCloudId = const i0.Value.absent(),
|
||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
@@ -330,6 +346,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
iCloudId: iCloudId,
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
@@ -347,6 +364,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> iCloudId = const i0.Value.absent(),
|
||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
@@ -362,6 +380,7 @@ class $$LocalAssetEntityTableTableManager
|
|||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
iCloudId: iCloudId,
|
||||||
adjustmentTime: adjustmentTime,
|
adjustmentTime: adjustmentTime,
|
||||||
latitude: latitude,
|
latitude: latitude,
|
||||||
longitude: longitude,
|
longitude: longitude,
|
||||||
@@ -532,6 +551,17 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
defaultValue: const i4.Constant(0),
|
defaultValue: const i4.Constant(0),
|
||||||
);
|
);
|
||||||
|
static const i0.VerificationMeta _iCloudIdMeta = const i0.VerificationMeta(
|
||||||
|
'iCloudId',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> iCloudId = i0.GeneratedColumn<String>(
|
||||||
|
'i_cloud_id',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
static const i0.VerificationMeta _adjustmentTimeMeta =
|
static const i0.VerificationMeta _adjustmentTimeMeta =
|
||||||
const i0.VerificationMeta('adjustmentTime');
|
const i0.VerificationMeta('adjustmentTime');
|
||||||
@override
|
@override
|
||||||
@@ -578,6 +608,7 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
checksum,
|
checksum,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
|
iCloudId,
|
||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
@@ -661,6 +692,12 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('i_cloud_id')) {
|
||||||
|
context.handle(
|
||||||
|
_iCloudIdMeta,
|
||||||
|
iCloudId.isAcceptableOrUnknown(data['i_cloud_id']!, _iCloudIdMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
if (data.containsKey('adjustment_time')) {
|
if (data.containsKey('adjustment_time')) {
|
||||||
context.handle(
|
context.handle(
|
||||||
_adjustmentTimeMeta,
|
_adjustmentTimeMeta,
|
||||||
@@ -740,6 +777,10 @@ class $LocalAssetEntityTable extends i3.LocalAssetEntity
|
|||||||
i0.DriftSqlType.int,
|
i0.DriftSqlType.int,
|
||||||
data['${effectivePrefix}orientation'],
|
data['${effectivePrefix}orientation'],
|
||||||
)!,
|
)!,
|
||||||
|
iCloudId: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}i_cloud_id'],
|
||||||
|
),
|
||||||
adjustmentTime: attachedDatabase.typeMapping.read(
|
adjustmentTime: attachedDatabase.typeMapping.read(
|
||||||
i0.DriftSqlType.dateTime,
|
i0.DriftSqlType.dateTime,
|
||||||
data['${effectivePrefix}adjustment_time'],
|
data['${effectivePrefix}adjustment_time'],
|
||||||
@@ -781,6 +822,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
final String? checksum;
|
final String? checksum;
|
||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
|
final String? iCloudId;
|
||||||
final DateTime? adjustmentTime;
|
final DateTime? adjustmentTime;
|
||||||
final double? latitude;
|
final double? latitude;
|
||||||
final double? longitude;
|
final double? longitude;
|
||||||
@@ -796,6 +838,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
this.checksum,
|
this.checksum,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
|
this.iCloudId,
|
||||||
this.adjustmentTime,
|
this.adjustmentTime,
|
||||||
this.latitude,
|
this.latitude,
|
||||||
this.longitude,
|
this.longitude,
|
||||||
@@ -826,6 +869,9 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
}
|
}
|
||||||
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
||||||
map['orientation'] = i0.Variable<int>(orientation);
|
map['orientation'] = i0.Variable<int>(orientation);
|
||||||
|
if (!nullToAbsent || iCloudId != null) {
|
||||||
|
map['i_cloud_id'] = i0.Variable<String>(iCloudId);
|
||||||
|
}
|
||||||
if (!nullToAbsent || adjustmentTime != null) {
|
if (!nullToAbsent || adjustmentTime != null) {
|
||||||
map['adjustment_time'] = i0.Variable<DateTime>(adjustmentTime);
|
map['adjustment_time'] = i0.Variable<DateTime>(adjustmentTime);
|
||||||
}
|
}
|
||||||
@@ -857,6 +903,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
checksum: serializer.fromJson<String?>(json['checksum']),
|
checksum: serializer.fromJson<String?>(json['checksum']),
|
||||||
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||||
orientation: serializer.fromJson<int>(json['orientation']),
|
orientation: serializer.fromJson<int>(json['orientation']),
|
||||||
|
iCloudId: serializer.fromJson<String?>(json['iCloudId']),
|
||||||
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
||||||
latitude: serializer.fromJson<double?>(json['latitude']),
|
latitude: serializer.fromJson<double?>(json['latitude']),
|
||||||
longitude: serializer.fromJson<double?>(json['longitude']),
|
longitude: serializer.fromJson<double?>(json['longitude']),
|
||||||
@@ -879,6 +926,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
'checksum': serializer.toJson<String?>(checksum),
|
'checksum': serializer.toJson<String?>(checksum),
|
||||||
'isFavorite': serializer.toJson<bool>(isFavorite),
|
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||||
'orientation': serializer.toJson<int>(orientation),
|
'orientation': serializer.toJson<int>(orientation),
|
||||||
|
'iCloudId': serializer.toJson<String?>(iCloudId),
|
||||||
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
||||||
'latitude': serializer.toJson<double?>(latitude),
|
'latitude': serializer.toJson<double?>(latitude),
|
||||||
'longitude': serializer.toJson<double?>(longitude),
|
'longitude': serializer.toJson<double?>(longitude),
|
||||||
@@ -897,6 +945,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
|
i0.Value<String?> iCloudId = const i0.Value.absent(),
|
||||||
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
i0.Value<double?> latitude = const i0.Value.absent(),
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
i0.Value<double?> longitude = const i0.Value.absent(),
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
@@ -914,6 +963,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
checksum: checksum.present ? checksum.value : this.checksum,
|
checksum: checksum.present ? checksum.value : this.checksum,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
iCloudId: iCloudId.present ? iCloudId.value : this.iCloudId,
|
||||||
adjustmentTime: adjustmentTime.present
|
adjustmentTime: adjustmentTime.present
|
||||||
? adjustmentTime.value
|
? adjustmentTime.value
|
||||||
: this.adjustmentTime,
|
: this.adjustmentTime,
|
||||||
@@ -939,6 +989,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
orientation: data.orientation.present
|
orientation: data.orientation.present
|
||||||
? data.orientation.value
|
? data.orientation.value
|
||||||
: this.orientation,
|
: this.orientation,
|
||||||
|
iCloudId: data.iCloudId.present ? data.iCloudId.value : this.iCloudId,
|
||||||
adjustmentTime: data.adjustmentTime.present
|
adjustmentTime: data.adjustmentTime.present
|
||||||
? data.adjustmentTime.value
|
? data.adjustmentTime.value
|
||||||
: this.adjustmentTime,
|
: this.adjustmentTime,
|
||||||
@@ -961,6 +1012,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation, ')
|
..write('orientation: $orientation, ')
|
||||||
|
..write('iCloudId: $iCloudId, ')
|
||||||
..write('adjustmentTime: $adjustmentTime, ')
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
..write('latitude: $latitude, ')
|
..write('latitude: $latitude, ')
|
||||||
..write('longitude: $longitude')
|
..write('longitude: $longitude')
|
||||||
@@ -981,6 +1033,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
checksum,
|
checksum,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
|
iCloudId,
|
||||||
adjustmentTime,
|
adjustmentTime,
|
||||||
latitude,
|
latitude,
|
||||||
longitude,
|
longitude,
|
||||||
@@ -1000,6 +1053,7 @@ class LocalAssetEntityData extends i0.DataClass
|
|||||||
other.checksum == this.checksum &&
|
other.checksum == this.checksum &&
|
||||||
other.isFavorite == this.isFavorite &&
|
other.isFavorite == this.isFavorite &&
|
||||||
other.orientation == this.orientation &&
|
other.orientation == this.orientation &&
|
||||||
|
other.iCloudId == this.iCloudId &&
|
||||||
other.adjustmentTime == this.adjustmentTime &&
|
other.adjustmentTime == this.adjustmentTime &&
|
||||||
other.latitude == this.latitude &&
|
other.latitude == this.latitude &&
|
||||||
other.longitude == this.longitude);
|
other.longitude == this.longitude);
|
||||||
@@ -1018,6 +1072,7 @@ class LocalAssetEntityCompanion
|
|||||||
final i0.Value<String?> checksum;
|
final i0.Value<String?> checksum;
|
||||||
final i0.Value<bool> isFavorite;
|
final i0.Value<bool> isFavorite;
|
||||||
final i0.Value<int> orientation;
|
final i0.Value<int> orientation;
|
||||||
|
final i0.Value<String?> iCloudId;
|
||||||
final i0.Value<DateTime?> adjustmentTime;
|
final i0.Value<DateTime?> adjustmentTime;
|
||||||
final i0.Value<double?> latitude;
|
final i0.Value<double?> latitude;
|
||||||
final i0.Value<double?> longitude;
|
final i0.Value<double?> longitude;
|
||||||
@@ -1033,6 +1088,7 @@ class LocalAssetEntityCompanion
|
|||||||
this.checksum = const i0.Value.absent(),
|
this.checksum = const i0.Value.absent(),
|
||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
|
this.iCloudId = const i0.Value.absent(),
|
||||||
this.adjustmentTime = const i0.Value.absent(),
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
this.latitude = const i0.Value.absent(),
|
this.latitude = const i0.Value.absent(),
|
||||||
this.longitude = const i0.Value.absent(),
|
this.longitude = const i0.Value.absent(),
|
||||||
@@ -1049,6 +1105,7 @@ class LocalAssetEntityCompanion
|
|||||||
this.checksum = const i0.Value.absent(),
|
this.checksum = const i0.Value.absent(),
|
||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
|
this.iCloudId = const i0.Value.absent(),
|
||||||
this.adjustmentTime = const i0.Value.absent(),
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
this.latitude = const i0.Value.absent(),
|
this.latitude = const i0.Value.absent(),
|
||||||
this.longitude = const i0.Value.absent(),
|
this.longitude = const i0.Value.absent(),
|
||||||
@@ -1067,6 +1124,7 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Expression<String>? checksum,
|
i0.Expression<String>? checksum,
|
||||||
i0.Expression<bool>? isFavorite,
|
i0.Expression<bool>? isFavorite,
|
||||||
i0.Expression<int>? orientation,
|
i0.Expression<int>? orientation,
|
||||||
|
i0.Expression<String>? iCloudId,
|
||||||
i0.Expression<DateTime>? adjustmentTime,
|
i0.Expression<DateTime>? adjustmentTime,
|
||||||
i0.Expression<double>? latitude,
|
i0.Expression<double>? latitude,
|
||||||
i0.Expression<double>? longitude,
|
i0.Expression<double>? longitude,
|
||||||
@@ -1083,6 +1141,7 @@ class LocalAssetEntityCompanion
|
|||||||
if (checksum != null) 'checksum': checksum,
|
if (checksum != null) 'checksum': checksum,
|
||||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||||
if (orientation != null) 'orientation': orientation,
|
if (orientation != null) 'orientation': orientation,
|
||||||
|
if (iCloudId != null) 'i_cloud_id': iCloudId,
|
||||||
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
||||||
if (latitude != null) 'latitude': latitude,
|
if (latitude != null) 'latitude': latitude,
|
||||||
if (longitude != null) 'longitude': longitude,
|
if (longitude != null) 'longitude': longitude,
|
||||||
@@ -1101,6 +1160,7 @@ class LocalAssetEntityCompanion
|
|||||||
i0.Value<String?>? checksum,
|
i0.Value<String?>? checksum,
|
||||||
i0.Value<bool>? isFavorite,
|
i0.Value<bool>? isFavorite,
|
||||||
i0.Value<int>? orientation,
|
i0.Value<int>? orientation,
|
||||||
|
i0.Value<String?>? iCloudId,
|
||||||
i0.Value<DateTime?>? adjustmentTime,
|
i0.Value<DateTime?>? adjustmentTime,
|
||||||
i0.Value<double?>? latitude,
|
i0.Value<double?>? latitude,
|
||||||
i0.Value<double?>? longitude,
|
i0.Value<double?>? longitude,
|
||||||
@@ -1117,6 +1177,7 @@ class LocalAssetEntityCompanion
|
|||||||
checksum: checksum ?? this.checksum,
|
checksum: checksum ?? this.checksum,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
iCloudId: iCloudId ?? this.iCloudId,
|
||||||
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
latitude: latitude ?? this.latitude,
|
latitude: latitude ?? this.latitude,
|
||||||
longitude: longitude ?? this.longitude,
|
longitude: longitude ?? this.longitude,
|
||||||
@@ -1161,6 +1222,9 @@ class LocalAssetEntityCompanion
|
|||||||
if (orientation.present) {
|
if (orientation.present) {
|
||||||
map['orientation'] = i0.Variable<int>(orientation.value);
|
map['orientation'] = i0.Variable<int>(orientation.value);
|
||||||
}
|
}
|
||||||
|
if (iCloudId.present) {
|
||||||
|
map['i_cloud_id'] = i0.Variable<String>(iCloudId.value);
|
||||||
|
}
|
||||||
if (adjustmentTime.present) {
|
if (adjustmentTime.present) {
|
||||||
map['adjustment_time'] = i0.Variable<DateTime>(adjustmentTime.value);
|
map['adjustment_time'] = i0.Variable<DateTime>(adjustmentTime.value);
|
||||||
}
|
}
|
||||||
@@ -1187,6 +1251,7 @@ class LocalAssetEntityCompanion
|
|||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation, ')
|
..write('orientation: $orientation, ')
|
||||||
|
..write('iCloudId: $iCloudId, ')
|
||||||
..write('adjustmentTime: $adjustmentTime, ')
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
..write('latitude: $latitude, ')
|
..write('latitude: $latitude, ')
|
||||||
..write('longitude: $longitude')
|
..write('longitude: $longitude')
|
||||||
@@ -1194,3 +1259,8 @@ class LocalAssetEntityCompanion
|
|||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i0.Index get idxLocalAssetCloudId => i0.Index(
|
||||||
|
'idx_local_asset_cloud_id',
|
||||||
|
'CREATE INDEX IF NOT EXISTS idx_local_asset_cloud_id ON local_asset_entity (i_cloud_id)',
|
||||||
|
);
|
||||||
|
|||||||
@@ -21,7 +21,12 @@ SELECT
|
|||||||
rae.owner_id,
|
rae.owner_id,
|
||||||
rae.live_photo_video_id,
|
rae.live_photo_video_id,
|
||||||
0 as orientation,
|
0 as orientation,
|
||||||
rae.stack_id
|
rae.stack_id,
|
||||||
|
NULL as i_cloud_id,
|
||||||
|
NULL as latitude,
|
||||||
|
NULL as longitude,
|
||||||
|
NULL as adjustmentTime,
|
||||||
|
rae.is_edited
|
||||||
FROM
|
FROM
|
||||||
remote_asset_entity rae
|
remote_asset_entity rae
|
||||||
LEFT JOIN
|
LEFT JOIN
|
||||||
@@ -53,7 +58,12 @@ SELECT
|
|||||||
NULL as owner_id,
|
NULL as owner_id,
|
||||||
NULL as live_photo_video_id,
|
NULL as live_photo_video_id,
|
||||||
lae.orientation,
|
lae.orientation,
|
||||||
NULL as stack_id
|
NULL as stack_id,
|
||||||
|
lae.i_cloud_id,
|
||||||
|
lae.latitude,
|
||||||
|
lae.longitude,
|
||||||
|
lae.adjustment_time,
|
||||||
|
0 as is_edited
|
||||||
FROM
|
FROM
|
||||||
local_asset_entity lae
|
local_asset_entity lae
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||||||
);
|
);
|
||||||
$arrayStartIndex += generatedlimit.amountOfVariables;
|
$arrayStartIndex += generatedlimit.amountOfVariables;
|
||||||
return customSelect(
|
return customSelect(
|
||||||
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
'SELECT rae.id AS remote_id, (SELECT lae.id FROM local_asset_entity AS lae WHERE lae.checksum = rae.checksum LIMIT 1) AS local_id, rae.name, rae.type, rae.created_at AS created_at, rae.updated_at, rae.width, rae.height, rae.duration_in_seconds, rae.is_favorite, rae.thumb_hash, rae.checksum, rae.owner_id, rae.live_photo_video_id, 0 AS orientation, rae.stack_id, NULL AS i_cloud_id, NULL AS latitude, NULL AS longitude, NULL AS adjustmentTime, rae.is_edited FROM remote_asset_entity AS rae LEFT JOIN stack_entity AS se ON rae.stack_id = se.id WHERE rae.deleted_at IS NULL AND rae.visibility = 0 AND rae.owner_id IN ($expandeduserIds) AND(rae.stack_id IS NULL OR rae.id = se.primary_asset_id)UNION ALL SELECT NULL AS remote_id, lae.id AS local_id, lae.name, lae.type, lae.created_at AS created_at, lae.updated_at, lae.width, lae.height, lae.duration_in_seconds, lae.is_favorite, NULL AS thumb_hash, lae.checksum, NULL AS owner_id, NULL AS live_photo_video_id, lae.orientation, NULL AS stack_id, lae.i_cloud_id, lae.latitude, lae.longitude, lae.adjustment_time, 0 AS is_edited FROM local_asset_entity AS lae WHERE NOT EXISTS (SELECT 1 FROM remote_asset_entity AS rae WHERE rae.checksum = lae.checksum AND rae.owner_id IN ($expandeduserIds)) AND EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 0) AND NOT EXISTS (SELECT 1 FROM local_album_asset_entity AS laa INNER JOIN local_album_entity AS la ON laa.album_id = la.id WHERE laa.asset_id = lae.id AND la.backup_selection = 2) ORDER BY created_at DESC ${generatedlimit.sql}',
|
||||||
variables: [
|
variables: [
|
||||||
for (var $ in userIds) i0.Variable<String>($),
|
for (var $ in userIds) i0.Variable<String>($),
|
||||||
...generatedlimit.introducedVariables,
|
...generatedlimit.introducedVariables,
|
||||||
@@ -62,6 +62,11 @@ class MergedAssetDrift extends i1.ModularAccessor {
|
|||||||
livePhotoVideoId: row.readNullable<String>('live_photo_video_id'),
|
livePhotoVideoId: row.readNullable<String>('live_photo_video_id'),
|
||||||
orientation: row.read<int>('orientation'),
|
orientation: row.read<int>('orientation'),
|
||||||
stackId: row.readNullable<String>('stack_id'),
|
stackId: row.readNullable<String>('stack_id'),
|
||||||
|
iCloudId: row.readNullable<String>('i_cloud_id'),
|
||||||
|
latitude: row.readNullable<double>('latitude'),
|
||||||
|
longitude: row.readNullable<double>('longitude'),
|
||||||
|
adjustmentTime: row.readNullable<DateTime>('adjustmentTime'),
|
||||||
|
isEdited: row.read<bool>('is_edited'),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -129,6 +134,11 @@ class MergedAssetResult {
|
|||||||
final String? livePhotoVideoId;
|
final String? livePhotoVideoId;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
final String? stackId;
|
final String? stackId;
|
||||||
|
final String? iCloudId;
|
||||||
|
final double? latitude;
|
||||||
|
final double? longitude;
|
||||||
|
final DateTime? adjustmentTime;
|
||||||
|
final bool isEdited;
|
||||||
MergedAssetResult({
|
MergedAssetResult({
|
||||||
this.remoteId,
|
this.remoteId,
|
||||||
this.localId,
|
this.localId,
|
||||||
@@ -146,6 +156,11 @@ class MergedAssetResult {
|
|||||||
this.livePhotoVideoId,
|
this.livePhotoVideoId,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
this.stackId,
|
this.stackId,
|
||||||
|
this.iCloudId,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
this.adjustmentTime,
|
||||||
|
required this.isEdited,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
|
|||||||
|
|
||||||
TextColumn get libraryId => text().nullable()();
|
TextColumn get libraryId => text().nullable()();
|
||||||
|
|
||||||
|
BoolColumn get isEdited => boolean().withDefault(const Constant(false))();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id};
|
Set<Column> get primaryKey => {id};
|
||||||
}
|
}
|
||||||
@@ -66,5 +68,6 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
|||||||
livePhotoVideoId: livePhotoVideoId,
|
livePhotoVideoId: livePhotoVideoId,
|
||||||
localId: localId,
|
localId: localId,
|
||||||
stackId: stackId,
|
stackId: stackId,
|
||||||
|
isEdited: isEdited,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ typedef $$RemoteAssetEntityTableCreateCompanionBuilder =
|
|||||||
required i2.AssetVisibility visibility,
|
required i2.AssetVisibility visibility,
|
||||||
i0.Value<String?> stackId,
|
i0.Value<String?> stackId,
|
||||||
i0.Value<String?> libraryId,
|
i0.Value<String?> libraryId,
|
||||||
|
i0.Value<bool> isEdited,
|
||||||
});
|
});
|
||||||
typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
|
typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.RemoteAssetEntityCompanion Function({
|
i1.RemoteAssetEntityCompanion Function({
|
||||||
@@ -52,6 +53,7 @@ typedef $$RemoteAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<i2.AssetVisibility> visibility,
|
i0.Value<i2.AssetVisibility> visibility,
|
||||||
i0.Value<String?> stackId,
|
i0.Value<String?> stackId,
|
||||||
i0.Value<String?> libraryId,
|
i0.Value<String?> libraryId,
|
||||||
|
i0.Value<bool> isEdited,
|
||||||
});
|
});
|
||||||
|
|
||||||
final class $$RemoteAssetEntityTableReferences
|
final class $$RemoteAssetEntityTableReferences
|
||||||
@@ -196,6 +198,11 @@ class $$RemoteAssetEntityTableFilterComposer
|
|||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<bool> get isEdited => $composableBuilder(
|
||||||
|
column: $table.isEdited,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
i5.$$UserEntityTableFilterComposer get ownerId {
|
i5.$$UserEntityTableFilterComposer get ownerId {
|
||||||
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
final i5.$$UserEntityTableFilterComposer composer = $composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
@@ -318,6 +325,11 @@ class $$RemoteAssetEntityTableOrderingComposer
|
|||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<bool> get isEdited => $composableBuilder(
|
||||||
|
column: $table.isEdited,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
i5.$$UserEntityTableOrderingComposer get ownerId {
|
i5.$$UserEntityTableOrderingComposer get ownerId {
|
||||||
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
final i5.$$UserEntityTableOrderingComposer composer = $composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
@@ -417,6 +429,9 @@ class $$RemoteAssetEntityTableAnnotationComposer
|
|||||||
i0.GeneratedColumn<String> get libraryId =>
|
i0.GeneratedColumn<String> get libraryId =>
|
||||||
$composableBuilder(column: $table.libraryId, builder: (column) => column);
|
$composableBuilder(column: $table.libraryId, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<bool> get isEdited =>
|
||||||
|
$composableBuilder(column: $table.isEdited, builder: (column) => column);
|
||||||
|
|
||||||
i5.$$UserEntityTableAnnotationComposer get ownerId {
|
i5.$$UserEntityTableAnnotationComposer get ownerId {
|
||||||
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
final i5.$$UserEntityTableAnnotationComposer composer = $composerBuilder(
|
||||||
composer: this,
|
composer: this,
|
||||||
@@ -497,6 +512,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||||||
const i0.Value.absent(),
|
const i0.Value.absent(),
|
||||||
i0.Value<String?> stackId = const i0.Value.absent(),
|
i0.Value<String?> stackId = const i0.Value.absent(),
|
||||||
i0.Value<String?> libraryId = const i0.Value.absent(),
|
i0.Value<String?> libraryId = const i0.Value.absent(),
|
||||||
|
i0.Value<bool> isEdited = const i0.Value.absent(),
|
||||||
}) => i1.RemoteAssetEntityCompanion(
|
}) => i1.RemoteAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -516,6 +532,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
stackId: stackId,
|
stackId: stackId,
|
||||||
libraryId: libraryId,
|
libraryId: libraryId,
|
||||||
|
isEdited: isEdited,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -537,6 +554,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||||||
required i2.AssetVisibility visibility,
|
required i2.AssetVisibility visibility,
|
||||||
i0.Value<String?> stackId = const i0.Value.absent(),
|
i0.Value<String?> stackId = const i0.Value.absent(),
|
||||||
i0.Value<String?> libraryId = const i0.Value.absent(),
|
i0.Value<String?> libraryId = const i0.Value.absent(),
|
||||||
|
i0.Value<bool> isEdited = const i0.Value.absent(),
|
||||||
}) => i1.RemoteAssetEntityCompanion.insert(
|
}) => i1.RemoteAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -556,6 +574,7 @@ class $$RemoteAssetEntityTableTableManager
|
|||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
stackId: stackId,
|
stackId: stackId,
|
||||||
libraryId: libraryId,
|
libraryId: libraryId,
|
||||||
|
isEdited: isEdited,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map(
|
.map(
|
||||||
@@ -844,6 +863,21 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||||||
type: i0.DriftSqlType.string,
|
type: i0.DriftSqlType.string,
|
||||||
requiredDuringInsert: false,
|
requiredDuringInsert: false,
|
||||||
);
|
);
|
||||||
|
static const i0.VerificationMeta _isEditedMeta = const i0.VerificationMeta(
|
||||||
|
'isEdited',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<bool> isEdited = i0.GeneratedColumn<bool>(
|
||||||
|
'is_edited',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.bool,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||||
|
'CHECK ("is_edited" IN (0, 1))',
|
||||||
|
),
|
||||||
|
defaultValue: const i4.Constant(false),
|
||||||
|
);
|
||||||
@override
|
@override
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
@@ -864,6 +898,7 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||||||
visibility,
|
visibility,
|
||||||
stackId,
|
stackId,
|
||||||
libraryId,
|
libraryId,
|
||||||
|
isEdited,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -987,6 +1022,12 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||||||
libraryId.isAcceptableOrUnknown(data['library_id']!, _libraryIdMeta),
|
libraryId.isAcceptableOrUnknown(data['library_id']!, _libraryIdMeta),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (data.containsKey('is_edited')) {
|
||||||
|
context.handle(
|
||||||
|
_isEditedMeta,
|
||||||
|
isEdited.isAcceptableOrUnknown(data['is_edited']!, _isEditedMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1075,6 +1116,10 @@ class $RemoteAssetEntityTable extends i3.RemoteAssetEntity
|
|||||||
i0.DriftSqlType.string,
|
i0.DriftSqlType.string,
|
||||||
data['${effectivePrefix}library_id'],
|
data['${effectivePrefix}library_id'],
|
||||||
),
|
),
|
||||||
|
isEdited: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.bool,
|
||||||
|
data['${effectivePrefix}is_edited'],
|
||||||
|
)!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1115,6 +1160,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
final i2.AssetVisibility visibility;
|
final i2.AssetVisibility visibility;
|
||||||
final String? stackId;
|
final String? stackId;
|
||||||
final String? libraryId;
|
final String? libraryId;
|
||||||
|
final bool isEdited;
|
||||||
const RemoteAssetEntityData({
|
const RemoteAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -1134,6 +1180,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
required this.visibility,
|
required this.visibility,
|
||||||
this.stackId,
|
this.stackId,
|
||||||
this.libraryId,
|
this.libraryId,
|
||||||
|
required this.isEdited,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -1182,6 +1229,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
if (!nullToAbsent || libraryId != null) {
|
if (!nullToAbsent || libraryId != null) {
|
||||||
map['library_id'] = i0.Variable<String>(libraryId);
|
map['library_id'] = i0.Variable<String>(libraryId);
|
||||||
}
|
}
|
||||||
|
map['is_edited'] = i0.Variable<bool>(isEdited);
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1213,6 +1261,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
),
|
),
|
||||||
stackId: serializer.fromJson<String?>(json['stackId']),
|
stackId: serializer.fromJson<String?>(json['stackId']),
|
||||||
libraryId: serializer.fromJson<String?>(json['libraryId']),
|
libraryId: serializer.fromJson<String?>(json['libraryId']),
|
||||||
|
isEdited: serializer.fromJson<bool>(json['isEdited']),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -1241,6 +1290,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
),
|
),
|
||||||
'stackId': serializer.toJson<String?>(stackId),
|
'stackId': serializer.toJson<String?>(stackId),
|
||||||
'libraryId': serializer.toJson<String?>(libraryId),
|
'libraryId': serializer.toJson<String?>(libraryId),
|
||||||
|
'isEdited': serializer.toJson<bool>(isEdited),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1263,6 +1313,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
i2.AssetVisibility? visibility,
|
i2.AssetVisibility? visibility,
|
||||||
i0.Value<String?> stackId = const i0.Value.absent(),
|
i0.Value<String?> stackId = const i0.Value.absent(),
|
||||||
i0.Value<String?> libraryId = const i0.Value.absent(),
|
i0.Value<String?> libraryId = const i0.Value.absent(),
|
||||||
|
bool? isEdited,
|
||||||
}) => i1.RemoteAssetEntityData(
|
}) => i1.RemoteAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -1288,6 +1339,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
visibility: visibility ?? this.visibility,
|
visibility: visibility ?? this.visibility,
|
||||||
stackId: stackId.present ? stackId.value : this.stackId,
|
stackId: stackId.present ? stackId.value : this.stackId,
|
||||||
libraryId: libraryId.present ? libraryId.value : this.libraryId,
|
libraryId: libraryId.present ? libraryId.value : this.libraryId,
|
||||||
|
isEdited: isEdited ?? this.isEdited,
|
||||||
);
|
);
|
||||||
RemoteAssetEntityData copyWithCompanion(i1.RemoteAssetEntityCompanion data) {
|
RemoteAssetEntityData copyWithCompanion(i1.RemoteAssetEntityCompanion data) {
|
||||||
return RemoteAssetEntityData(
|
return RemoteAssetEntityData(
|
||||||
@@ -1319,6 +1371,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
: this.visibility,
|
: this.visibility,
|
||||||
stackId: data.stackId.present ? data.stackId.value : this.stackId,
|
stackId: data.stackId.present ? data.stackId.value : this.stackId,
|
||||||
libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId,
|
libraryId: data.libraryId.present ? data.libraryId.value : this.libraryId,
|
||||||
|
isEdited: data.isEdited.present ? data.isEdited.value : this.isEdited,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1342,7 +1395,8 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
..write('livePhotoVideoId: $livePhotoVideoId, ')
|
..write('livePhotoVideoId: $livePhotoVideoId, ')
|
||||||
..write('visibility: $visibility, ')
|
..write('visibility: $visibility, ')
|
||||||
..write('stackId: $stackId, ')
|
..write('stackId: $stackId, ')
|
||||||
..write('libraryId: $libraryId')
|
..write('libraryId: $libraryId, ')
|
||||||
|
..write('isEdited: $isEdited')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -1367,6 +1421,7 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
visibility,
|
visibility,
|
||||||
stackId,
|
stackId,
|
||||||
libraryId,
|
libraryId,
|
||||||
|
isEdited,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -1389,7 +1444,8 @@ class RemoteAssetEntityData extends i0.DataClass
|
|||||||
other.livePhotoVideoId == this.livePhotoVideoId &&
|
other.livePhotoVideoId == this.livePhotoVideoId &&
|
||||||
other.visibility == this.visibility &&
|
other.visibility == this.visibility &&
|
||||||
other.stackId == this.stackId &&
|
other.stackId == this.stackId &&
|
||||||
other.libraryId == this.libraryId);
|
other.libraryId == this.libraryId &&
|
||||||
|
other.isEdited == this.isEdited);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteAssetEntityCompanion
|
class RemoteAssetEntityCompanion
|
||||||
@@ -1412,6 +1468,7 @@ class RemoteAssetEntityCompanion
|
|||||||
final i0.Value<i2.AssetVisibility> visibility;
|
final i0.Value<i2.AssetVisibility> visibility;
|
||||||
final i0.Value<String?> stackId;
|
final i0.Value<String?> stackId;
|
||||||
final i0.Value<String?> libraryId;
|
final i0.Value<String?> libraryId;
|
||||||
|
final i0.Value<bool> isEdited;
|
||||||
const RemoteAssetEntityCompanion({
|
const RemoteAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -1431,6 +1488,7 @@ class RemoteAssetEntityCompanion
|
|||||||
this.visibility = const i0.Value.absent(),
|
this.visibility = const i0.Value.absent(),
|
||||||
this.stackId = const i0.Value.absent(),
|
this.stackId = const i0.Value.absent(),
|
||||||
this.libraryId = const i0.Value.absent(),
|
this.libraryId = const i0.Value.absent(),
|
||||||
|
this.isEdited = const i0.Value.absent(),
|
||||||
});
|
});
|
||||||
RemoteAssetEntityCompanion.insert({
|
RemoteAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -1451,6 +1509,7 @@ class RemoteAssetEntityCompanion
|
|||||||
required i2.AssetVisibility visibility,
|
required i2.AssetVisibility visibility,
|
||||||
this.stackId = const i0.Value.absent(),
|
this.stackId = const i0.Value.absent(),
|
||||||
this.libraryId = const i0.Value.absent(),
|
this.libraryId = const i0.Value.absent(),
|
||||||
|
this.isEdited = const i0.Value.absent(),
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id),
|
id = i0.Value(id),
|
||||||
@@ -1476,6 +1535,7 @@ class RemoteAssetEntityCompanion
|
|||||||
i0.Expression<int>? visibility,
|
i0.Expression<int>? visibility,
|
||||||
i0.Expression<String>? stackId,
|
i0.Expression<String>? stackId,
|
||||||
i0.Expression<String>? libraryId,
|
i0.Expression<String>? libraryId,
|
||||||
|
i0.Expression<bool>? isEdited,
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -1496,6 +1556,7 @@ class RemoteAssetEntityCompanion
|
|||||||
if (visibility != null) 'visibility': visibility,
|
if (visibility != null) 'visibility': visibility,
|
||||||
if (stackId != null) 'stack_id': stackId,
|
if (stackId != null) 'stack_id': stackId,
|
||||||
if (libraryId != null) 'library_id': libraryId,
|
if (libraryId != null) 'library_id': libraryId,
|
||||||
|
if (isEdited != null) 'is_edited': isEdited,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1518,6 +1579,7 @@ class RemoteAssetEntityCompanion
|
|||||||
i0.Value<i2.AssetVisibility>? visibility,
|
i0.Value<i2.AssetVisibility>? visibility,
|
||||||
i0.Value<String?>? stackId,
|
i0.Value<String?>? stackId,
|
||||||
i0.Value<String?>? libraryId,
|
i0.Value<String?>? libraryId,
|
||||||
|
i0.Value<bool>? isEdited,
|
||||||
}) {
|
}) {
|
||||||
return i1.RemoteAssetEntityCompanion(
|
return i1.RemoteAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -1538,6 +1600,7 @@ class RemoteAssetEntityCompanion
|
|||||||
visibility: visibility ?? this.visibility,
|
visibility: visibility ?? this.visibility,
|
||||||
stackId: stackId ?? this.stackId,
|
stackId: stackId ?? this.stackId,
|
||||||
libraryId: libraryId ?? this.libraryId,
|
libraryId: libraryId ?? this.libraryId,
|
||||||
|
isEdited: isEdited ?? this.isEdited,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1602,6 +1665,9 @@ class RemoteAssetEntityCompanion
|
|||||||
if (libraryId.present) {
|
if (libraryId.present) {
|
||||||
map['library_id'] = i0.Variable<String>(libraryId.value);
|
map['library_id'] = i0.Variable<String>(libraryId.value);
|
||||||
}
|
}
|
||||||
|
if (isEdited.present) {
|
||||||
|
map['is_edited'] = i0.Variable<bool>(isEdited.value);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1625,7 +1691,8 @@ class RemoteAssetEntityCompanion
|
|||||||
..write('livePhotoVideoId: $livePhotoVideoId, ')
|
..write('livePhotoVideoId: $livePhotoVideoId, ')
|
||||||
..write('visibility: $visibility, ')
|
..write('visibility: $visibility, ')
|
||||||
..write('stackId: $stackId, ')
|
..write('stackId: $stackId, ')
|
||||||
..write('libraryId: $libraryId')
|
..write('libraryId: $libraryId, ')
|
||||||
|
..write('isEdited: $isEdited')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
class RemoteAssetCloudIdEntity extends Table with DriftDefaultsMixin {
|
||||||
|
TextColumn get assetId => text().references(RemoteAssetEntity, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
TextColumn get cloudId => text().nullable()();
|
||||||
|
|
||||||
|
DateTimeColumn get createdAt => dateTime().nullable()();
|
||||||
|
|
||||||
|
DateTimeColumn get adjustmentTime => dateTime().nullable()();
|
||||||
|
|
||||||
|
RealColumn get latitude => real().nullable()();
|
||||||
|
|
||||||
|
RealColumn get longitude => real().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column> get primaryKey => {assetId};
|
||||||
|
}
|
||||||
826
mobile/lib/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart
generated
Normal file
826
mobile/lib/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart
generated
Normal file
@@ -0,0 +1,826 @@
|
|||||||
|
// dart format width=80
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
import 'package:drift/drift.dart' as i0;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart'
|
||||||
|
as i1;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart'
|
||||||
|
as i2;
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'
|
||||||
|
as i3;
|
||||||
|
import 'package:drift/internal/modular.dart' as i4;
|
||||||
|
|
||||||
|
typedef $$RemoteAssetCloudIdEntityTableCreateCompanionBuilder =
|
||||||
|
i1.RemoteAssetCloudIdEntityCompanion Function({
|
||||||
|
required String assetId,
|
||||||
|
i0.Value<String?> cloudId,
|
||||||
|
i0.Value<DateTime?> createdAt,
|
||||||
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
|
i0.Value<double?> latitude,
|
||||||
|
i0.Value<double?> longitude,
|
||||||
|
});
|
||||||
|
typedef $$RemoteAssetCloudIdEntityTableUpdateCompanionBuilder =
|
||||||
|
i1.RemoteAssetCloudIdEntityCompanion Function({
|
||||||
|
i0.Value<String> assetId,
|
||||||
|
i0.Value<String?> cloudId,
|
||||||
|
i0.Value<DateTime?> createdAt,
|
||||||
|
i0.Value<DateTime?> adjustmentTime,
|
||||||
|
i0.Value<double?> latitude,
|
||||||
|
i0.Value<double?> longitude,
|
||||||
|
});
|
||||||
|
|
||||||
|
final class $$RemoteAssetCloudIdEntityTableReferences
|
||||||
|
extends
|
||||||
|
i0.BaseReferences<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$RemoteAssetCloudIdEntityTable,
|
||||||
|
i1.RemoteAssetCloudIdEntityData
|
||||||
|
> {
|
||||||
|
$$RemoteAssetCloudIdEntityTableReferences(
|
||||||
|
super.$_db,
|
||||||
|
super.$_table,
|
||||||
|
super.$_typedResult,
|
||||||
|
);
|
||||||
|
|
||||||
|
static i3.$RemoteAssetEntityTable _assetIdTable(i0.GeneratedDatabase db) =>
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity')
|
||||||
|
.createAlias(
|
||||||
|
i0.$_aliasNameGenerator(
|
||||||
|
i4.ReadDatabaseContainer(db)
|
||||||
|
.resultSet<i1.$RemoteAssetCloudIdEntityTable>(
|
||||||
|
'remote_asset_cloud_id_entity',
|
||||||
|
)
|
||||||
|
.assetId,
|
||||||
|
i4.ReadDatabaseContainer(
|
||||||
|
db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity').id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
i3.$$RemoteAssetEntityTableProcessedTableManager get assetId {
|
||||||
|
final $_column = $_itemColumn<String>('asset_id')!;
|
||||||
|
|
||||||
|
final manager = i3
|
||||||
|
.$$RemoteAssetEntityTableTableManager(
|
||||||
|
$_db,
|
||||||
|
i4.ReadDatabaseContainer(
|
||||||
|
$_db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
)
|
||||||
|
.filter((f) => f.id.sqlEquals($_column));
|
||||||
|
final item = $_typedResult.readTableOrNull(_assetIdTable($_db));
|
||||||
|
if (item == null) return manager;
|
||||||
|
return i0.ProcessedTableManager(
|
||||||
|
manager.$state.copyWith(prefetchedData: [item]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteAssetCloudIdEntityTableFilterComposer
|
||||||
|
extends
|
||||||
|
i0.Composer<i0.GeneratedDatabase, i1.$RemoteAssetCloudIdEntityTable> {
|
||||||
|
$$RemoteAssetCloudIdEntityTableFilterComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnFilters<String> get cloudId => $composableBuilder(
|
||||||
|
column: $table.cloudId,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<DateTime> get adjustmentTime => $composableBuilder(
|
||||||
|
column: $table.adjustmentTime,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<double> get latitude => $composableBuilder(
|
||||||
|
column: $table.latitude,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnFilters<double> get longitude => $composableBuilder(
|
||||||
|
column: $table.longitude,
|
||||||
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i3.$$RemoteAssetEntityTableFilterComposer get assetId {
|
||||||
|
final i3.$$RemoteAssetEntityTableFilterComposer composer = $composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder:
|
||||||
|
(
|
||||||
|
joinBuilder, {
|
||||||
|
$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
}) => i3.$$RemoteAssetEntityTableFilterComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteAssetCloudIdEntityTableOrderingComposer
|
||||||
|
extends
|
||||||
|
i0.Composer<i0.GeneratedDatabase, i1.$RemoteAssetCloudIdEntityTable> {
|
||||||
|
$$RemoteAssetCloudIdEntityTableOrderingComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.ColumnOrderings<String> get cloudId => $composableBuilder(
|
||||||
|
column: $table.cloudId,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||||
|
column: $table.createdAt,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<DateTime> get adjustmentTime => $composableBuilder(
|
||||||
|
column: $table.adjustmentTime,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<double> get latitude => $composableBuilder(
|
||||||
|
column: $table.latitude,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<double> get longitude => $composableBuilder(
|
||||||
|
column: $table.longitude,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
|
|
||||||
|
i3.$$RemoteAssetEntityTableOrderingComposer get assetId {
|
||||||
|
final i3.$$RemoteAssetEntityTableOrderingComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder:
|
||||||
|
(
|
||||||
|
joinBuilder, {
|
||||||
|
$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
}) => i3.$$RemoteAssetEntityTableOrderingComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteAssetCloudIdEntityTableAnnotationComposer
|
||||||
|
extends
|
||||||
|
i0.Composer<i0.GeneratedDatabase, i1.$RemoteAssetCloudIdEntityTable> {
|
||||||
|
$$RemoteAssetCloudIdEntityTableAnnotationComposer({
|
||||||
|
required super.$db,
|
||||||
|
required super.$table,
|
||||||
|
super.joinBuilder,
|
||||||
|
super.$addJoinBuilderToRootComposer,
|
||||||
|
super.$removeJoinBuilderFromRootComposer,
|
||||||
|
});
|
||||||
|
i0.GeneratedColumn<String> get cloudId =>
|
||||||
|
$composableBuilder(column: $table.cloudId, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get createdAt =>
|
||||||
|
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<DateTime> get adjustmentTime => $composableBuilder(
|
||||||
|
column: $table.adjustmentTime,
|
||||||
|
builder: (column) => column,
|
||||||
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<double> get latitude =>
|
||||||
|
$composableBuilder(column: $table.latitude, builder: (column) => column);
|
||||||
|
|
||||||
|
i0.GeneratedColumn<double> get longitude =>
|
||||||
|
$composableBuilder(column: $table.longitude, builder: (column) => column);
|
||||||
|
|
||||||
|
i3.$$RemoteAssetEntityTableAnnotationComposer get assetId {
|
||||||
|
final i3.$$RemoteAssetEntityTableAnnotationComposer composer =
|
||||||
|
$composerBuilder(
|
||||||
|
composer: this,
|
||||||
|
getCurrentColumn: (t) => t.assetId,
|
||||||
|
referencedTable: i4.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
getReferencedColumn: (t) => t.id,
|
||||||
|
builder:
|
||||||
|
(
|
||||||
|
joinBuilder, {
|
||||||
|
$addJoinBuilderToRootComposer,
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
}) => i3.$$RemoteAssetEntityTableAnnotationComposer(
|
||||||
|
$db: $db,
|
||||||
|
$table: i4.ReadDatabaseContainer(
|
||||||
|
$db,
|
||||||
|
).resultSet<i3.$RemoteAssetEntityTable>('remote_asset_entity'),
|
||||||
|
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||||
|
joinBuilder: joinBuilder,
|
||||||
|
$removeJoinBuilderFromRootComposer:
|
||||||
|
$removeJoinBuilderFromRootComposer,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return composer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class $$RemoteAssetCloudIdEntityTableTableManager
|
||||||
|
extends
|
||||||
|
i0.RootTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$RemoteAssetCloudIdEntityTable,
|
||||||
|
i1.RemoteAssetCloudIdEntityData,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableFilterComposer,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableOrderingComposer,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableAnnotationComposer,
|
||||||
|
$$RemoteAssetCloudIdEntityTableCreateCompanionBuilder,
|
||||||
|
$$RemoteAssetCloudIdEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.RemoteAssetCloudIdEntityData,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableReferences,
|
||||||
|
),
|
||||||
|
i1.RemoteAssetCloudIdEntityData,
|
||||||
|
i0.PrefetchHooks Function({bool assetId})
|
||||||
|
> {
|
||||||
|
$$RemoteAssetCloudIdEntityTableTableManager(
|
||||||
|
i0.GeneratedDatabase db,
|
||||||
|
i1.$RemoteAssetCloudIdEntityTable table,
|
||||||
|
) : super(
|
||||||
|
i0.TableManagerState(
|
||||||
|
db: db,
|
||||||
|
table: table,
|
||||||
|
createFilteringComposer: () =>
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableFilterComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
createOrderingComposer: () =>
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableOrderingComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
createComputedFieldComposer: () =>
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableAnnotationComposer(
|
||||||
|
$db: db,
|
||||||
|
$table: table,
|
||||||
|
),
|
||||||
|
updateCompanionCallback:
|
||||||
|
({
|
||||||
|
i0.Value<String> assetId = const i0.Value.absent(),
|
||||||
|
i0.Value<String?> cloudId = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime?> createdAt = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
|
}) => i1.RemoteAssetCloudIdEntityCompanion(
|
||||||
|
assetId: assetId,
|
||||||
|
cloudId: cloudId,
|
||||||
|
createdAt: createdAt,
|
||||||
|
adjustmentTime: adjustmentTime,
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
),
|
||||||
|
createCompanionCallback:
|
||||||
|
({
|
||||||
|
required String assetId,
|
||||||
|
i0.Value<String?> cloudId = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime?> createdAt = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
|
}) => i1.RemoteAssetCloudIdEntityCompanion.insert(
|
||||||
|
assetId: assetId,
|
||||||
|
cloudId: cloudId,
|
||||||
|
createdAt: createdAt,
|
||||||
|
adjustmentTime: adjustmentTime,
|
||||||
|
latitude: latitude,
|
||||||
|
longitude: longitude,
|
||||||
|
),
|
||||||
|
withReferenceMapper: (p0) => p0
|
||||||
|
.map(
|
||||||
|
(e) => (
|
||||||
|
e.readTable(table),
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableReferences(db, table, e),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
prefetchHooksCallback: ({assetId = false}) {
|
||||||
|
return i0.PrefetchHooks(
|
||||||
|
db: db,
|
||||||
|
explicitlyWatchedTables: [],
|
||||||
|
addJoins:
|
||||||
|
<
|
||||||
|
T extends i0.TableManagerState<
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic,
|
||||||
|
dynamic
|
||||||
|
>
|
||||||
|
>(state) {
|
||||||
|
if (assetId) {
|
||||||
|
state =
|
||||||
|
state.withJoin(
|
||||||
|
currentTable: table,
|
||||||
|
currentColumn: table.assetId,
|
||||||
|
referencedTable: i1
|
||||||
|
.$$RemoteAssetCloudIdEntityTableReferences
|
||||||
|
._assetIdTable(db),
|
||||||
|
referencedColumn: i1
|
||||||
|
.$$RemoteAssetCloudIdEntityTableReferences
|
||||||
|
._assetIdTable(db)
|
||||||
|
.id,
|
||||||
|
)
|
||||||
|
as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
},
|
||||||
|
getPrefetchedDataCallback: (items) async {
|
||||||
|
return [];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef $$RemoteAssetCloudIdEntityTableProcessedTableManager =
|
||||||
|
i0.ProcessedTableManager<
|
||||||
|
i0.GeneratedDatabase,
|
||||||
|
i1.$RemoteAssetCloudIdEntityTable,
|
||||||
|
i1.RemoteAssetCloudIdEntityData,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableFilterComposer,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableOrderingComposer,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableAnnotationComposer,
|
||||||
|
$$RemoteAssetCloudIdEntityTableCreateCompanionBuilder,
|
||||||
|
$$RemoteAssetCloudIdEntityTableUpdateCompanionBuilder,
|
||||||
|
(
|
||||||
|
i1.RemoteAssetCloudIdEntityData,
|
||||||
|
i1.$$RemoteAssetCloudIdEntityTableReferences,
|
||||||
|
),
|
||||||
|
i1.RemoteAssetCloudIdEntityData,
|
||||||
|
i0.PrefetchHooks Function({bool assetId})
|
||||||
|
>;
|
||||||
|
|
||||||
|
class $RemoteAssetCloudIdEntityTable extends i2.RemoteAssetCloudIdEntity
|
||||||
|
with
|
||||||
|
i0.TableInfo<
|
||||||
|
$RemoteAssetCloudIdEntityTable,
|
||||||
|
i1.RemoteAssetCloudIdEntityData
|
||||||
|
> {
|
||||||
|
@override
|
||||||
|
final i0.GeneratedDatabase attachedDatabase;
|
||||||
|
final String? _alias;
|
||||||
|
$RemoteAssetCloudIdEntityTable(this.attachedDatabase, [this._alias]);
|
||||||
|
static const i0.VerificationMeta _assetIdMeta = const i0.VerificationMeta(
|
||||||
|
'assetId',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> assetId = i0.GeneratedColumn<String>(
|
||||||
|
'asset_id',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
defaultConstraints: i0.GeneratedColumn.constraintIsAlways(
|
||||||
|
'REFERENCES remote_asset_entity (id) ON DELETE CASCADE',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _cloudIdMeta = const i0.VerificationMeta(
|
||||||
|
'cloudId',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<String> cloudId = i0.GeneratedColumn<String>(
|
||||||
|
'cloud_id',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.string,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _createdAtMeta = const i0.VerificationMeta(
|
||||||
|
'createdAt',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> createdAt =
|
||||||
|
i0.GeneratedColumn<DateTime>(
|
||||||
|
'created_at',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _adjustmentTimeMeta =
|
||||||
|
const i0.VerificationMeta('adjustmentTime');
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<DateTime> adjustmentTime =
|
||||||
|
i0.GeneratedColumn<DateTime>(
|
||||||
|
'adjustment_time',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.dateTime,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _latitudeMeta = const i0.VerificationMeta(
|
||||||
|
'latitude',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<double> latitude = i0.GeneratedColumn<double>(
|
||||||
|
'latitude',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.double,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
static const i0.VerificationMeta _longitudeMeta = const i0.VerificationMeta(
|
||||||
|
'longitude',
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
late final i0.GeneratedColumn<double> longitude = i0.GeneratedColumn<double>(
|
||||||
|
'longitude',
|
||||||
|
aliasedName,
|
||||||
|
true,
|
||||||
|
type: i0.DriftSqlType.double,
|
||||||
|
requiredDuringInsert: false,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
|
assetId,
|
||||||
|
cloudId,
|
||||||
|
createdAt,
|
||||||
|
adjustmentTime,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
];
|
||||||
|
@override
|
||||||
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
|
@override
|
||||||
|
String get actualTableName => $name;
|
||||||
|
static const String $name = 'remote_asset_cloud_id_entity';
|
||||||
|
@override
|
||||||
|
i0.VerificationContext validateIntegrity(
|
||||||
|
i0.Insertable<i1.RemoteAssetCloudIdEntityData> instance, {
|
||||||
|
bool isInserting = false,
|
||||||
|
}) {
|
||||||
|
final context = i0.VerificationContext();
|
||||||
|
final data = instance.toColumns(true);
|
||||||
|
if (data.containsKey('asset_id')) {
|
||||||
|
context.handle(
|
||||||
|
_assetIdMeta,
|
||||||
|
assetId.isAcceptableOrUnknown(data['asset_id']!, _assetIdMeta),
|
||||||
|
);
|
||||||
|
} else if (isInserting) {
|
||||||
|
context.missing(_assetIdMeta);
|
||||||
|
}
|
||||||
|
if (data.containsKey('cloud_id')) {
|
||||||
|
context.handle(
|
||||||
|
_cloudIdMeta,
|
||||||
|
cloudId.isAcceptableOrUnknown(data['cloud_id']!, _cloudIdMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('created_at')) {
|
||||||
|
context.handle(
|
||||||
|
_createdAtMeta,
|
||||||
|
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('adjustment_time')) {
|
||||||
|
context.handle(
|
||||||
|
_adjustmentTimeMeta,
|
||||||
|
adjustmentTime.isAcceptableOrUnknown(
|
||||||
|
data['adjustment_time']!,
|
||||||
|
_adjustmentTimeMeta,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('latitude')) {
|
||||||
|
context.handle(
|
||||||
|
_latitudeMeta,
|
||||||
|
latitude.isAcceptableOrUnknown(data['latitude']!, _latitudeMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (data.containsKey('longitude')) {
|
||||||
|
context.handle(
|
||||||
|
_longitudeMeta,
|
||||||
|
longitude.isAcceptableOrUnknown(data['longitude']!, _longitudeMeta),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<i0.GeneratedColumn> get $primaryKey => {assetId};
|
||||||
|
@override
|
||||||
|
i1.RemoteAssetCloudIdEntityData map(
|
||||||
|
Map<String, dynamic> data, {
|
||||||
|
String? tablePrefix,
|
||||||
|
}) {
|
||||||
|
final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
|
||||||
|
return i1.RemoteAssetCloudIdEntityData(
|
||||||
|
assetId: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}asset_id'],
|
||||||
|
)!,
|
||||||
|
cloudId: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.string,
|
||||||
|
data['${effectivePrefix}cloud_id'],
|
||||||
|
),
|
||||||
|
createdAt: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}created_at'],
|
||||||
|
),
|
||||||
|
adjustmentTime: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.dateTime,
|
||||||
|
data['${effectivePrefix}adjustment_time'],
|
||||||
|
),
|
||||||
|
latitude: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.double,
|
||||||
|
data['${effectivePrefix}latitude'],
|
||||||
|
),
|
||||||
|
longitude: attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.double,
|
||||||
|
data['${effectivePrefix}longitude'],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
$RemoteAssetCloudIdEntityTable createAlias(String alias) {
|
||||||
|
return $RemoteAssetCloudIdEntityTable(attachedDatabase, alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get withoutRowId => true;
|
||||||
|
@override
|
||||||
|
bool get isStrict => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteAssetCloudIdEntityData extends i0.DataClass
|
||||||
|
implements i0.Insertable<i1.RemoteAssetCloudIdEntityData> {
|
||||||
|
final String assetId;
|
||||||
|
final String? cloudId;
|
||||||
|
final DateTime? createdAt;
|
||||||
|
final DateTime? adjustmentTime;
|
||||||
|
final double? latitude;
|
||||||
|
final double? longitude;
|
||||||
|
const RemoteAssetCloudIdEntityData({
|
||||||
|
required this.assetId,
|
||||||
|
this.cloudId,
|
||||||
|
this.createdAt,
|
||||||
|
this.adjustmentTime,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
map['asset_id'] = i0.Variable<String>(assetId);
|
||||||
|
if (!nullToAbsent || cloudId != null) {
|
||||||
|
map['cloud_id'] = i0.Variable<String>(cloudId);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || createdAt != null) {
|
||||||
|
map['created_at'] = i0.Variable<DateTime>(createdAt);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || adjustmentTime != null) {
|
||||||
|
map['adjustment_time'] = i0.Variable<DateTime>(adjustmentTime);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || latitude != null) {
|
||||||
|
map['latitude'] = i0.Variable<double>(latitude);
|
||||||
|
}
|
||||||
|
if (!nullToAbsent || longitude != null) {
|
||||||
|
map['longitude'] = i0.Variable<double>(longitude);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory RemoteAssetCloudIdEntityData.fromJson(
|
||||||
|
Map<String, dynamic> json, {
|
||||||
|
i0.ValueSerializer? serializer,
|
||||||
|
}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return RemoteAssetCloudIdEntityData(
|
||||||
|
assetId: serializer.fromJson<String>(json['assetId']),
|
||||||
|
cloudId: serializer.fromJson<String?>(json['cloudId']),
|
||||||
|
createdAt: serializer.fromJson<DateTime?>(json['createdAt']),
|
||||||
|
adjustmentTime: serializer.fromJson<DateTime?>(json['adjustmentTime']),
|
||||||
|
latitude: serializer.fromJson<double?>(json['latitude']),
|
||||||
|
longitude: serializer.fromJson<double?>(json['longitude']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson({i0.ValueSerializer? serializer}) {
|
||||||
|
serializer ??= i0.driftRuntimeOptions.defaultSerializer;
|
||||||
|
return <String, dynamic>{
|
||||||
|
'assetId': serializer.toJson<String>(assetId),
|
||||||
|
'cloudId': serializer.toJson<String?>(cloudId),
|
||||||
|
'createdAt': serializer.toJson<DateTime?>(createdAt),
|
||||||
|
'adjustmentTime': serializer.toJson<DateTime?>(adjustmentTime),
|
||||||
|
'latitude': serializer.toJson<double?>(latitude),
|
||||||
|
'longitude': serializer.toJson<double?>(longitude),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.RemoteAssetCloudIdEntityData copyWith({
|
||||||
|
String? assetId,
|
||||||
|
i0.Value<String?> cloudId = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime?> createdAt = const i0.Value.absent(),
|
||||||
|
i0.Value<DateTime?> adjustmentTime = const i0.Value.absent(),
|
||||||
|
i0.Value<double?> latitude = const i0.Value.absent(),
|
||||||
|
i0.Value<double?> longitude = const i0.Value.absent(),
|
||||||
|
}) => i1.RemoteAssetCloudIdEntityData(
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
cloudId: cloudId.present ? cloudId.value : this.cloudId,
|
||||||
|
createdAt: createdAt.present ? createdAt.value : this.createdAt,
|
||||||
|
adjustmentTime: adjustmentTime.present
|
||||||
|
? adjustmentTime.value
|
||||||
|
: this.adjustmentTime,
|
||||||
|
latitude: latitude.present ? latitude.value : this.latitude,
|
||||||
|
longitude: longitude.present ? longitude.value : this.longitude,
|
||||||
|
);
|
||||||
|
RemoteAssetCloudIdEntityData copyWithCompanion(
|
||||||
|
i1.RemoteAssetCloudIdEntityCompanion data,
|
||||||
|
) {
|
||||||
|
return RemoteAssetCloudIdEntityData(
|
||||||
|
assetId: data.assetId.present ? data.assetId.value : this.assetId,
|
||||||
|
cloudId: data.cloudId.present ? data.cloudId.value : this.cloudId,
|
||||||
|
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
|
||||||
|
adjustmentTime: data.adjustmentTime.present
|
||||||
|
? data.adjustmentTime.value
|
||||||
|
: this.adjustmentTime,
|
||||||
|
latitude: data.latitude.present ? data.latitude.value : this.latitude,
|
||||||
|
longitude: data.longitude.present ? data.longitude.value : this.longitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('RemoteAssetCloudIdEntityData(')
|
||||||
|
..write('assetId: $assetId, ')
|
||||||
|
..write('cloudId: $cloudId, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
|
..write('latitude: $latitude, ')
|
||||||
|
..write('longitude: $longitude')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(
|
||||||
|
assetId,
|
||||||
|
cloudId,
|
||||||
|
createdAt,
|
||||||
|
adjustmentTime,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
(other is i1.RemoteAssetCloudIdEntityData &&
|
||||||
|
other.assetId == this.assetId &&
|
||||||
|
other.cloudId == this.cloudId &&
|
||||||
|
other.createdAt == this.createdAt &&
|
||||||
|
other.adjustmentTime == this.adjustmentTime &&
|
||||||
|
other.latitude == this.latitude &&
|
||||||
|
other.longitude == this.longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemoteAssetCloudIdEntityCompanion
|
||||||
|
extends i0.UpdateCompanion<i1.RemoteAssetCloudIdEntityData> {
|
||||||
|
final i0.Value<String> assetId;
|
||||||
|
final i0.Value<String?> cloudId;
|
||||||
|
final i0.Value<DateTime?> createdAt;
|
||||||
|
final i0.Value<DateTime?> adjustmentTime;
|
||||||
|
final i0.Value<double?> latitude;
|
||||||
|
final i0.Value<double?> longitude;
|
||||||
|
const RemoteAssetCloudIdEntityCompanion({
|
||||||
|
this.assetId = const i0.Value.absent(),
|
||||||
|
this.cloudId = const i0.Value.absent(),
|
||||||
|
this.createdAt = const i0.Value.absent(),
|
||||||
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
|
this.latitude = const i0.Value.absent(),
|
||||||
|
this.longitude = const i0.Value.absent(),
|
||||||
|
});
|
||||||
|
RemoteAssetCloudIdEntityCompanion.insert({
|
||||||
|
required String assetId,
|
||||||
|
this.cloudId = const i0.Value.absent(),
|
||||||
|
this.createdAt = const i0.Value.absent(),
|
||||||
|
this.adjustmentTime = const i0.Value.absent(),
|
||||||
|
this.latitude = const i0.Value.absent(),
|
||||||
|
this.longitude = const i0.Value.absent(),
|
||||||
|
}) : assetId = i0.Value(assetId);
|
||||||
|
static i0.Insertable<i1.RemoteAssetCloudIdEntityData> custom({
|
||||||
|
i0.Expression<String>? assetId,
|
||||||
|
i0.Expression<String>? cloudId,
|
||||||
|
i0.Expression<DateTime>? createdAt,
|
||||||
|
i0.Expression<DateTime>? adjustmentTime,
|
||||||
|
i0.Expression<double>? latitude,
|
||||||
|
i0.Expression<double>? longitude,
|
||||||
|
}) {
|
||||||
|
return i0.RawValuesInsertable({
|
||||||
|
if (assetId != null) 'asset_id': assetId,
|
||||||
|
if (cloudId != null) 'cloud_id': cloudId,
|
||||||
|
if (createdAt != null) 'created_at': createdAt,
|
||||||
|
if (adjustmentTime != null) 'adjustment_time': adjustmentTime,
|
||||||
|
if (latitude != null) 'latitude': latitude,
|
||||||
|
if (longitude != null) 'longitude': longitude,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
i1.RemoteAssetCloudIdEntityCompanion copyWith({
|
||||||
|
i0.Value<String>? assetId,
|
||||||
|
i0.Value<String?>? cloudId,
|
||||||
|
i0.Value<DateTime?>? createdAt,
|
||||||
|
i0.Value<DateTime?>? adjustmentTime,
|
||||||
|
i0.Value<double?>? latitude,
|
||||||
|
i0.Value<double?>? longitude,
|
||||||
|
}) {
|
||||||
|
return i1.RemoteAssetCloudIdEntityCompanion(
|
||||||
|
assetId: assetId ?? this.assetId,
|
||||||
|
cloudId: cloudId ?? this.cloudId,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
adjustmentTime: adjustmentTime ?? this.adjustmentTime,
|
||||||
|
latitude: latitude ?? this.latitude,
|
||||||
|
longitude: longitude ?? this.longitude,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
|
final map = <String, i0.Expression>{};
|
||||||
|
if (assetId.present) {
|
||||||
|
map['asset_id'] = i0.Variable<String>(assetId.value);
|
||||||
|
}
|
||||||
|
if (cloudId.present) {
|
||||||
|
map['cloud_id'] = i0.Variable<String>(cloudId.value);
|
||||||
|
}
|
||||||
|
if (createdAt.present) {
|
||||||
|
map['created_at'] = i0.Variable<DateTime>(createdAt.value);
|
||||||
|
}
|
||||||
|
if (adjustmentTime.present) {
|
||||||
|
map['adjustment_time'] = i0.Variable<DateTime>(adjustmentTime.value);
|
||||||
|
}
|
||||||
|
if (latitude.present) {
|
||||||
|
map['latitude'] = i0.Variable<double>(latitude.value);
|
||||||
|
}
|
||||||
|
if (longitude.present) {
|
||||||
|
map['longitude'] = i0.Variable<double>(longitude.value);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return (StringBuffer('RemoteAssetCloudIdEntityCompanion(')
|
||||||
|
..write('assetId: $assetId, ')
|
||||||
|
..write('cloudId: $cloudId, ')
|
||||||
|
..write('createdAt: $createdAt, ')
|
||||||
|
..write('adjustmentTime: $adjustmentTime, ')
|
||||||
|
..write('latitude: $latitude, ')
|
||||||
|
..write('longitude: $longitude')
|
||||||
|
..write(')'))
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,13 @@ import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity
|
|||||||
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/asset.mixin.dart';
|
||||||
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
import 'package:immich_mobile/infrastructure/utils/drift_default.mixin.dart';
|
||||||
|
|
||||||
|
enum TrashOrigin {
|
||||||
|
// do not change this order!
|
||||||
|
localSync,
|
||||||
|
remoteSync,
|
||||||
|
localUser,
|
||||||
|
}
|
||||||
|
|
||||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)')
|
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_checksum ON trashed_local_asset_entity (checksum)')
|
||||||
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)')
|
@TableIndex.sql('CREATE INDEX IF NOT EXISTS idx_trashed_local_asset_album ON trashed_local_asset_entity (album_id)')
|
||||||
class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
||||||
@@ -19,6 +26,8 @@ class TrashedLocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntity
|
|||||||
|
|
||||||
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
IntColumn get orientation => integer().withDefault(const Constant(0))();
|
||||||
|
|
||||||
|
IntColumn get source => intEnum<TrashOrigin>()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column> get primaryKey => {id, albumId};
|
Set<Column> get primaryKey => {id, albumId};
|
||||||
}
|
}
|
||||||
@@ -36,5 +45,6 @@ extension TrashedLocalAssetEntityDataDomainExtension on TrashedLocalAssetEntityD
|
|||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
isEdited: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ typedef $$TrashedLocalAssetEntityTableCreateCompanionBuilder =
|
|||||||
i0.Value<String?> checksum,
|
i0.Value<String?> checksum,
|
||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
|
required i3.TrashOrigin source,
|
||||||
});
|
});
|
||||||
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
||||||
i1.TrashedLocalAssetEntityCompanion Function({
|
i1.TrashedLocalAssetEntityCompanion Function({
|
||||||
@@ -37,6 +38,7 @@ typedef $$TrashedLocalAssetEntityTableUpdateCompanionBuilder =
|
|||||||
i0.Value<String?> checksum,
|
i0.Value<String?> checksum,
|
||||||
i0.Value<bool> isFavorite,
|
i0.Value<bool> isFavorite,
|
||||||
i0.Value<int> orientation,
|
i0.Value<int> orientation,
|
||||||
|
i0.Value<i3.TrashOrigin> source,
|
||||||
});
|
});
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableFilterComposer
|
class $$TrashedLocalAssetEntityTableFilterComposer
|
||||||
@@ -109,6 +111,12 @@ class $$TrashedLocalAssetEntityTableFilterComposer
|
|||||||
column: $table.orientation,
|
column: $table.orientation,
|
||||||
builder: (column) => i0.ColumnFilters(column),
|
builder: (column) => i0.ColumnFilters(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnWithTypeConverterFilters<i3.TrashOrigin, i3.TrashOrigin, int>
|
||||||
|
get source => $composableBuilder(
|
||||||
|
column: $table.source,
|
||||||
|
builder: (column) => i0.ColumnWithTypeConverterFilters(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableOrderingComposer
|
class $$TrashedLocalAssetEntityTableOrderingComposer
|
||||||
@@ -180,6 +188,11 @@ class $$TrashedLocalAssetEntityTableOrderingComposer
|
|||||||
column: $table.orientation,
|
column: $table.orientation,
|
||||||
builder: (column) => i0.ColumnOrderings(column),
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.ColumnOrderings<int> get source => $composableBuilder(
|
||||||
|
column: $table.source,
|
||||||
|
builder: (column) => i0.ColumnOrderings(column),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableAnnotationComposer
|
class $$TrashedLocalAssetEntityTableAnnotationComposer
|
||||||
@@ -233,6 +246,9 @@ class $$TrashedLocalAssetEntityTableAnnotationComposer
|
|||||||
column: $table.orientation,
|
column: $table.orientation,
|
||||||
builder: (column) => column,
|
builder: (column) => column,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> get source =>
|
||||||
|
$composableBuilder(column: $table.source, builder: (column) => column);
|
||||||
}
|
}
|
||||||
|
|
||||||
class $$TrashedLocalAssetEntityTableTableManager
|
class $$TrashedLocalAssetEntityTableTableManager
|
||||||
@@ -293,6 +309,7 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
|
i0.Value<i3.TrashOrigin> source = const i0.Value.absent(),
|
||||||
}) => i1.TrashedLocalAssetEntityCompanion(
|
}) => i1.TrashedLocalAssetEntityCompanion(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -306,6 +323,7 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
source: source,
|
||||||
),
|
),
|
||||||
createCompanionCallback:
|
createCompanionCallback:
|
||||||
({
|
({
|
||||||
@@ -321,6 +339,7 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
i0.Value<bool> isFavorite = const i0.Value.absent(),
|
||||||
i0.Value<int> orientation = const i0.Value.absent(),
|
i0.Value<int> orientation = const i0.Value.absent(),
|
||||||
|
required i3.TrashOrigin source,
|
||||||
}) => i1.TrashedLocalAssetEntityCompanion.insert(
|
}) => i1.TrashedLocalAssetEntityCompanion.insert(
|
||||||
name: name,
|
name: name,
|
||||||
type: type,
|
type: type,
|
||||||
@@ -334,6 +353,7 @@ class $$TrashedLocalAssetEntityTableTableManager
|
|||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
|
source: source,
|
||||||
),
|
),
|
||||||
withReferenceMapper: (p0) => p0
|
withReferenceMapper: (p0) => p0
|
||||||
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
.map((e) => (e.readTable(table), i0.BaseReferences(db, table, e)))
|
||||||
@@ -519,6 +539,17 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
defaultValue: const i4.Constant(0),
|
defaultValue: const i4.Constant(0),
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
|
late final i0.GeneratedColumnWithTypeConverter<i3.TrashOrigin, int> source =
|
||||||
|
i0.GeneratedColumn<int>(
|
||||||
|
'source',
|
||||||
|
aliasedName,
|
||||||
|
false,
|
||||||
|
type: i0.DriftSqlType.int,
|
||||||
|
requiredDuringInsert: true,
|
||||||
|
).withConverter<i3.TrashOrigin>(
|
||||||
|
i1.$TrashedLocalAssetEntityTable.$convertersource,
|
||||||
|
);
|
||||||
|
@override
|
||||||
List<i0.GeneratedColumn> get $columns => [
|
List<i0.GeneratedColumn> get $columns => [
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
@@ -532,6 +563,7 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
checksum,
|
checksum,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
|
source,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
String get aliasedName => _alias ?? actualTableName;
|
String get aliasedName => _alias ?? actualTableName;
|
||||||
@@ -682,6 +714,12 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
i0.DriftSqlType.int,
|
i0.DriftSqlType.int,
|
||||||
data['${effectivePrefix}orientation'],
|
data['${effectivePrefix}orientation'],
|
||||||
)!,
|
)!,
|
||||||
|
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromSql(
|
||||||
|
attachedDatabase.typeMapping.read(
|
||||||
|
i0.DriftSqlType.int,
|
||||||
|
data['${effectivePrefix}source'],
|
||||||
|
)!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,6 +730,8 @@ class $TrashedLocalAssetEntityTable extends i3.TrashedLocalAssetEntity
|
|||||||
|
|
||||||
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
static i0.JsonTypeConverter2<i2.AssetType, int, int> $convertertype =
|
||||||
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
const i0.EnumIndexConverter<i2.AssetType>(i2.AssetType.values);
|
||||||
|
static i0.JsonTypeConverter2<i3.TrashOrigin, int, int> $convertersource =
|
||||||
|
const i0.EnumIndexConverter<i3.TrashOrigin>(i3.TrashOrigin.values);
|
||||||
@override
|
@override
|
||||||
bool get withoutRowId => true;
|
bool get withoutRowId => true;
|
||||||
@override
|
@override
|
||||||
@@ -712,6 +752,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
final String? checksum;
|
final String? checksum;
|
||||||
final bool isFavorite;
|
final bool isFavorite;
|
||||||
final int orientation;
|
final int orientation;
|
||||||
|
final i3.TrashOrigin source;
|
||||||
const TrashedLocalAssetEntityData({
|
const TrashedLocalAssetEntityData({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.type,
|
required this.type,
|
||||||
@@ -725,6 +766,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
this.checksum,
|
this.checksum,
|
||||||
required this.isFavorite,
|
required this.isFavorite,
|
||||||
required this.orientation,
|
required this.orientation,
|
||||||
|
required this.source,
|
||||||
});
|
});
|
||||||
@override
|
@override
|
||||||
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
Map<String, i0.Expression> toColumns(bool nullToAbsent) {
|
||||||
@@ -753,6 +795,11 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
}
|
}
|
||||||
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
map['is_favorite'] = i0.Variable<bool>(isFavorite);
|
||||||
map['orientation'] = i0.Variable<int>(orientation);
|
map['orientation'] = i0.Variable<int>(orientation);
|
||||||
|
{
|
||||||
|
map['source'] = i0.Variable<int>(
|
||||||
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source),
|
||||||
|
);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,6 +823,9 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
checksum: serializer.fromJson<String?>(json['checksum']),
|
checksum: serializer.fromJson<String?>(json['checksum']),
|
||||||
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
isFavorite: serializer.fromJson<bool>(json['isFavorite']),
|
||||||
orientation: serializer.fromJson<int>(json['orientation']),
|
orientation: serializer.fromJson<int>(json['orientation']),
|
||||||
|
source: i1.$TrashedLocalAssetEntityTable.$convertersource.fromJson(
|
||||||
|
serializer.fromJson<int>(json['source']),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@override
|
@override
|
||||||
@@ -796,6 +846,9 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
'checksum': serializer.toJson<String?>(checksum),
|
'checksum': serializer.toJson<String?>(checksum),
|
||||||
'isFavorite': serializer.toJson<bool>(isFavorite),
|
'isFavorite': serializer.toJson<bool>(isFavorite),
|
||||||
'orientation': serializer.toJson<int>(orientation),
|
'orientation': serializer.toJson<int>(orientation),
|
||||||
|
'source': serializer.toJson<int>(
|
||||||
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toJson(source),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -812,6 +865,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
i0.Value<String?> checksum = const i0.Value.absent(),
|
i0.Value<String?> checksum = const i0.Value.absent(),
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
int? orientation,
|
int? orientation,
|
||||||
|
i3.TrashOrigin? source,
|
||||||
}) => i1.TrashedLocalAssetEntityData(
|
}) => i1.TrashedLocalAssetEntityData(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
type: type ?? this.type,
|
type: type ?? this.type,
|
||||||
@@ -827,6 +881,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
checksum: checksum.present ? checksum.value : this.checksum,
|
checksum: checksum.present ? checksum.value : this.checksum,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
source: source ?? this.source,
|
||||||
);
|
);
|
||||||
TrashedLocalAssetEntityData copyWithCompanion(
|
TrashedLocalAssetEntityData copyWithCompanion(
|
||||||
i1.TrashedLocalAssetEntityCompanion data,
|
i1.TrashedLocalAssetEntityCompanion data,
|
||||||
@@ -850,6 +905,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
orientation: data.orientation.present
|
orientation: data.orientation.present
|
||||||
? data.orientation.value
|
? data.orientation.value
|
||||||
: this.orientation,
|
: this.orientation,
|
||||||
|
source: data.source.present ? data.source.value : this.source,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -867,7 +923,8 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
..write('albumId: $albumId, ')
|
..write('albumId: $albumId, ')
|
||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation')
|
..write('orientation: $orientation, ')
|
||||||
|
..write('source: $source')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
@@ -886,6 +943,7 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
checksum,
|
checksum,
|
||||||
isFavorite,
|
isFavorite,
|
||||||
orientation,
|
orientation,
|
||||||
|
source,
|
||||||
);
|
);
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
@@ -902,7 +960,8 @@ class TrashedLocalAssetEntityData extends i0.DataClass
|
|||||||
other.albumId == this.albumId &&
|
other.albumId == this.albumId &&
|
||||||
other.checksum == this.checksum &&
|
other.checksum == this.checksum &&
|
||||||
other.isFavorite == this.isFavorite &&
|
other.isFavorite == this.isFavorite &&
|
||||||
other.orientation == this.orientation);
|
other.orientation == this.orientation &&
|
||||||
|
other.source == this.source);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrashedLocalAssetEntityCompanion
|
class TrashedLocalAssetEntityCompanion
|
||||||
@@ -919,6 +978,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
final i0.Value<String?> checksum;
|
final i0.Value<String?> checksum;
|
||||||
final i0.Value<bool> isFavorite;
|
final i0.Value<bool> isFavorite;
|
||||||
final i0.Value<int> orientation;
|
final i0.Value<int> orientation;
|
||||||
|
final i0.Value<i3.TrashOrigin> source;
|
||||||
const TrashedLocalAssetEntityCompanion({
|
const TrashedLocalAssetEntityCompanion({
|
||||||
this.name = const i0.Value.absent(),
|
this.name = const i0.Value.absent(),
|
||||||
this.type = const i0.Value.absent(),
|
this.type = const i0.Value.absent(),
|
||||||
@@ -932,6 +992,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
this.checksum = const i0.Value.absent(),
|
this.checksum = const i0.Value.absent(),
|
||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
|
this.source = const i0.Value.absent(),
|
||||||
});
|
});
|
||||||
TrashedLocalAssetEntityCompanion.insert({
|
TrashedLocalAssetEntityCompanion.insert({
|
||||||
required String name,
|
required String name,
|
||||||
@@ -946,10 +1007,12 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
this.checksum = const i0.Value.absent(),
|
this.checksum = const i0.Value.absent(),
|
||||||
this.isFavorite = const i0.Value.absent(),
|
this.isFavorite = const i0.Value.absent(),
|
||||||
this.orientation = const i0.Value.absent(),
|
this.orientation = const i0.Value.absent(),
|
||||||
|
required i3.TrashOrigin source,
|
||||||
}) : name = i0.Value(name),
|
}) : name = i0.Value(name),
|
||||||
type = i0.Value(type),
|
type = i0.Value(type),
|
||||||
id = i0.Value(id),
|
id = i0.Value(id),
|
||||||
albumId = i0.Value(albumId);
|
albumId = i0.Value(albumId),
|
||||||
|
source = i0.Value(source);
|
||||||
static i0.Insertable<i1.TrashedLocalAssetEntityData> custom({
|
static i0.Insertable<i1.TrashedLocalAssetEntityData> custom({
|
||||||
i0.Expression<String>? name,
|
i0.Expression<String>? name,
|
||||||
i0.Expression<int>? type,
|
i0.Expression<int>? type,
|
||||||
@@ -963,6 +1026,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i0.Expression<String>? checksum,
|
i0.Expression<String>? checksum,
|
||||||
i0.Expression<bool>? isFavorite,
|
i0.Expression<bool>? isFavorite,
|
||||||
i0.Expression<int>? orientation,
|
i0.Expression<int>? orientation,
|
||||||
|
i0.Expression<int>? source,
|
||||||
}) {
|
}) {
|
||||||
return i0.RawValuesInsertable({
|
return i0.RawValuesInsertable({
|
||||||
if (name != null) 'name': name,
|
if (name != null) 'name': name,
|
||||||
@@ -977,6 +1041,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
if (checksum != null) 'checksum': checksum,
|
if (checksum != null) 'checksum': checksum,
|
||||||
if (isFavorite != null) 'is_favorite': isFavorite,
|
if (isFavorite != null) 'is_favorite': isFavorite,
|
||||||
if (orientation != null) 'orientation': orientation,
|
if (orientation != null) 'orientation': orientation,
|
||||||
|
if (source != null) 'source': source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -993,6 +1058,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
i0.Value<String?>? checksum,
|
i0.Value<String?>? checksum,
|
||||||
i0.Value<bool>? isFavorite,
|
i0.Value<bool>? isFavorite,
|
||||||
i0.Value<int>? orientation,
|
i0.Value<int>? orientation,
|
||||||
|
i0.Value<i3.TrashOrigin>? source,
|
||||||
}) {
|
}) {
|
||||||
return i1.TrashedLocalAssetEntityCompanion(
|
return i1.TrashedLocalAssetEntityCompanion(
|
||||||
name: name ?? this.name,
|
name: name ?? this.name,
|
||||||
@@ -1007,6 +1073,7 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
checksum: checksum ?? this.checksum,
|
checksum: checksum ?? this.checksum,
|
||||||
isFavorite: isFavorite ?? this.isFavorite,
|
isFavorite: isFavorite ?? this.isFavorite,
|
||||||
orientation: orientation ?? this.orientation,
|
orientation: orientation ?? this.orientation,
|
||||||
|
source: source ?? this.source,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1051,6 +1118,11 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
if (orientation.present) {
|
if (orientation.present) {
|
||||||
map['orientation'] = i0.Variable<int>(orientation.value);
|
map['orientation'] = i0.Variable<int>(orientation.value);
|
||||||
}
|
}
|
||||||
|
if (source.present) {
|
||||||
|
map['source'] = i0.Variable<int>(
|
||||||
|
i1.$TrashedLocalAssetEntityTable.$convertersource.toSql(source.value),
|
||||||
|
);
|
||||||
|
}
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1068,7 +1140,8 @@ class TrashedLocalAssetEntityCompanion
|
|||||||
..write('albumId: $albumId, ')
|
..write('albumId: $albumId, ')
|
||||||
..write('checksum: $checksum, ')
|
..write('checksum: $checksum, ')
|
||||||
..write('isFavorite: $isFavorite, ')
|
..write('isFavorite: $isFavorite, ')
|
||||||
..write('orientation: $orientation')
|
..write('orientation: $orientation, ')
|
||||||
|
..write('source: $source')
|
||||||
..write(')'))
|
..write(')'))
|
||||||
.toString();
|
.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart';
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/stack.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
|
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart';
|
||||||
@@ -57,6 +58,7 @@ class IsarDatabaseRepository implements IDatabaseRepository {
|
|||||||
RemoteAlbumEntity,
|
RemoteAlbumEntity,
|
||||||
RemoteAlbumAssetEntity,
|
RemoteAlbumAssetEntity,
|
||||||
RemoteAlbumUserEntity,
|
RemoteAlbumUserEntity,
|
||||||
|
RemoteAssetCloudIdEntity,
|
||||||
MemoryEntity,
|
MemoryEntity,
|
||||||
MemoryAssetEntity,
|
MemoryAssetEntity,
|
||||||
StackEntity,
|
StackEntity,
|
||||||
@@ -95,7 +97,7 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get schemaVersion => 14;
|
int get schemaVersion => 17;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
MigrationStrategy get migration => MigrationStrategy(
|
MigrationStrategy get migration => MigrationStrategy(
|
||||||
@@ -190,6 +192,18 @@ class Drift extends $Drift implements IDatabaseRepository {
|
|||||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.latitude);
|
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.latitude);
|
||||||
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.longitude);
|
await m.addColumn(v14.localAssetEntity, v14.localAssetEntity.longitude);
|
||||||
},
|
},
|
||||||
|
from14To15: (m, v15) async {
|
||||||
|
await m.addColumn(v15.trashedLocalAssetEntity, v15.trashedLocalAssetEntity.source);
|
||||||
|
},
|
||||||
|
from15To16: (m, v16) async {
|
||||||
|
// Add i_cloud_id to local and remote asset tables
|
||||||
|
await m.addColumn(v16.localAssetEntity, v16.localAssetEntity.iCloudId);
|
||||||
|
await m.createIndex(v16.idxLocalAssetCloudId);
|
||||||
|
await m.createTable(v16.remoteAssetCloudIdEntity);
|
||||||
|
},
|
||||||
|
from16To17: (m, v17) async {
|
||||||
|
await m.addColumn(v17.remoteAssetEntity, v17.remoteAssetEntity.isEdited);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -27,21 +27,23 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.
|
|||||||
as i12;
|
as i12;
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart'
|
||||||
as i13;
|
as i13;
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart'
|
||||||
as i14;
|
as i14;
|
||||||
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/memory.entity.drift.dart'
|
||||||
as i15;
|
as i15;
|
||||||
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.drift.dart'
|
||||||
as i16;
|
as i16;
|
||||||
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/person.entity.drift.dart'
|
||||||
as i17;
|
as i17;
|
||||||
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/asset_face.entity.drift.dart'
|
||||||
as i18;
|
as i18;
|
||||||
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/store.entity.drift.dart'
|
||||||
as i19;
|
as i19;
|
||||||
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.drift.dart'
|
||||||
as i20;
|
as i20;
|
||||||
import 'package:drift/internal/modular.dart' as i21;
|
import 'package:immich_mobile/infrastructure/entities/merged_asset.drift.dart'
|
||||||
|
as i21;
|
||||||
|
import 'package:drift/internal/modular.dart' as i22;
|
||||||
|
|
||||||
abstract class $Drift extends i0.GeneratedDatabase {
|
abstract class $Drift extends i0.GeneratedDatabase {
|
||||||
$Drift(i0.QueryExecutor e) : super(e);
|
$Drift(i0.QueryExecutor e) : super(e);
|
||||||
@@ -72,18 +74,20 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
.$RemoteAlbumAssetEntityTable(this);
|
.$RemoteAlbumAssetEntityTable(this);
|
||||||
late final i13.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i13
|
late final i13.$RemoteAlbumUserEntityTable remoteAlbumUserEntity = i13
|
||||||
.$RemoteAlbumUserEntityTable(this);
|
.$RemoteAlbumUserEntityTable(this);
|
||||||
late final i14.$MemoryEntityTable memoryEntity = i14.$MemoryEntityTable(this);
|
late final i14.$RemoteAssetCloudIdEntityTable remoteAssetCloudIdEntity = i14
|
||||||
late final i15.$MemoryAssetEntityTable memoryAssetEntity = i15
|
.$RemoteAssetCloudIdEntityTable(this);
|
||||||
|
late final i15.$MemoryEntityTable memoryEntity = i15.$MemoryEntityTable(this);
|
||||||
|
late final i16.$MemoryAssetEntityTable memoryAssetEntity = i16
|
||||||
.$MemoryAssetEntityTable(this);
|
.$MemoryAssetEntityTable(this);
|
||||||
late final i16.$PersonEntityTable personEntity = i16.$PersonEntityTable(this);
|
late final i17.$PersonEntityTable personEntity = i17.$PersonEntityTable(this);
|
||||||
late final i17.$AssetFaceEntityTable assetFaceEntity = i17
|
late final i18.$AssetFaceEntityTable assetFaceEntity = i18
|
||||||
.$AssetFaceEntityTable(this);
|
.$AssetFaceEntityTable(this);
|
||||||
late final i18.$StoreEntityTable storeEntity = i18.$StoreEntityTable(this);
|
late final i19.$StoreEntityTable storeEntity = i19.$StoreEntityTable(this);
|
||||||
late final i19.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i19
|
late final i20.$TrashedLocalAssetEntityTable trashedLocalAssetEntity = i20
|
||||||
.$TrashedLocalAssetEntityTable(this);
|
.$TrashedLocalAssetEntityTable(this);
|
||||||
i20.MergedAssetDrift get mergedAssetDrift => i21.ReadDatabaseContainer(
|
i21.MergedAssetDrift get mergedAssetDrift => i22.ReadDatabaseContainer(
|
||||||
this,
|
this,
|
||||||
).accessor<i20.MergedAssetDrift>(i20.MergedAssetDrift.new);
|
).accessor<i21.MergedAssetDrift>(i21.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?>>();
|
||||||
@@ -97,6 +101,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
localAlbumEntity,
|
localAlbumEntity,
|
||||||
localAlbumAssetEntity,
|
localAlbumAssetEntity,
|
||||||
i4.idxLocalAssetChecksum,
|
i4.idxLocalAssetChecksum,
|
||||||
|
i4.idxLocalAssetCloudId,
|
||||||
i2.idxRemoteAssetOwnerChecksum,
|
i2.idxRemoteAssetOwnerChecksum,
|
||||||
i2.uQRemoteAssetsOwnerChecksum,
|
i2.uQRemoteAssetsOwnerChecksum,
|
||||||
i2.uQRemoteAssetsOwnerLibraryChecksum,
|
i2.uQRemoteAssetsOwnerLibraryChecksum,
|
||||||
@@ -107,6 +112,7 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
remoteExifEntity,
|
remoteExifEntity,
|
||||||
remoteAlbumAssetEntity,
|
remoteAlbumAssetEntity,
|
||||||
remoteAlbumUserEntity,
|
remoteAlbumUserEntity,
|
||||||
|
remoteAssetCloudIdEntity,
|
||||||
memoryEntity,
|
memoryEntity,
|
||||||
memoryAssetEntity,
|
memoryAssetEntity,
|
||||||
personEntity,
|
personEntity,
|
||||||
@@ -114,8 +120,8 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
storeEntity,
|
storeEntity,
|
||||||
trashedLocalAssetEntity,
|
trashedLocalAssetEntity,
|
||||||
i11.idxLatLng,
|
i11.idxLatLng,
|
||||||
i19.idxTrashedLocalAssetChecksum,
|
i20.idxTrashedLocalAssetChecksum,
|
||||||
i19.idxTrashedLocalAssetAlbum,
|
i20.idxTrashedLocalAssetAlbum,
|
||||||
];
|
];
|
||||||
@override
|
@override
|
||||||
i0.StreamQueryUpdateRules
|
i0.StreamQueryUpdateRules
|
||||||
@@ -249,6 +255,18 @@ abstract class $Drift extends i0.GeneratedDatabase {
|
|||||||
i0.TableUpdate('remote_album_user_entity', kind: i0.UpdateKind.delete),
|
i0.TableUpdate('remote_album_user_entity', kind: i0.UpdateKind.delete),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
i0.WritePropagation(
|
||||||
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
|
'remote_asset_entity',
|
||||||
|
limitUpdateKind: i0.UpdateKind.delete,
|
||||||
|
),
|
||||||
|
result: [
|
||||||
|
i0.TableUpdate(
|
||||||
|
'remote_asset_cloud_id_entity',
|
||||||
|
kind: i0.UpdateKind.delete,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
i0.WritePropagation(
|
i0.WritePropagation(
|
||||||
on: i0.TableUpdateQuery.onTableName(
|
on: i0.TableUpdateQuery.onTableName(
|
||||||
'user_entity',
|
'user_entity',
|
||||||
@@ -333,18 +351,24 @@ class $DriftManager {
|
|||||||
);
|
);
|
||||||
i13.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i13
|
i13.$$RemoteAlbumUserEntityTableTableManager get remoteAlbumUserEntity => i13
|
||||||
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
.$$RemoteAlbumUserEntityTableTableManager(_db, _db.remoteAlbumUserEntity);
|
||||||
i14.$$MemoryEntityTableTableManager get memoryEntity =>
|
i14.$$RemoteAssetCloudIdEntityTableTableManager
|
||||||
i14.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
get remoteAssetCloudIdEntity =>
|
||||||
i15.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
i14.$$RemoteAssetCloudIdEntityTableTableManager(
|
||||||
i15.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
_db,
|
||||||
i16.$$PersonEntityTableTableManager get personEntity =>
|
_db.remoteAssetCloudIdEntity,
|
||||||
i16.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
);
|
||||||
i17.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
i15.$$MemoryEntityTableTableManager get memoryEntity =>
|
||||||
i17.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
i15.$$MemoryEntityTableTableManager(_db, _db.memoryEntity);
|
||||||
i18.$$StoreEntityTableTableManager get storeEntity =>
|
i16.$$MemoryAssetEntityTableTableManager get memoryAssetEntity =>
|
||||||
i18.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
i16.$$MemoryAssetEntityTableTableManager(_db, _db.memoryAssetEntity);
|
||||||
i19.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity =>
|
i17.$$PersonEntityTableTableManager get personEntity =>
|
||||||
i19.$$TrashedLocalAssetEntityTableTableManager(
|
i17.$$PersonEntityTableTableManager(_db, _db.personEntity);
|
||||||
|
i18.$$AssetFaceEntityTableTableManager get assetFaceEntity =>
|
||||||
|
i18.$$AssetFaceEntityTableTableManager(_db, _db.assetFaceEntity);
|
||||||
|
i19.$$StoreEntityTableTableManager get storeEntity =>
|
||||||
|
i19.$$StoreEntityTableTableManager(_db, _db.storeEntity);
|
||||||
|
i20.$$TrashedLocalAssetEntityTableTableManager get trashedLocalAssetEntity =>
|
||||||
|
i20.$$TrashedLocalAssetEntityTableTableManager(
|
||||||
_db,
|
_db,
|
||||||
_db.trashedLocalAssetEntity,
|
_db.trashedLocalAssetEntity,
|
||||||
);
|
);
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -246,6 +246,25 @@ class DriftLocalAlbumRepository extends DriftDatabaseRepository {
|
|||||||
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
|
return query.map((row) => row.readTable(_db.localAssetEntity).toDto()).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateCloudMapping(Map<String, String> cloudMapping) {
|
||||||
|
if (cloudMapping.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
return _db.batch((batch) {
|
||||||
|
for (final entry in cloudMapping.entries) {
|
||||||
|
final assetId = entry.key;
|
||||||
|
final cloudId = entry.value;
|
||||||
|
|
||||||
|
batch.update(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
LocalAssetEntityCompanion(iCloudId: Value(cloudId)),
|
||||||
|
where: (f) => f.id.equals(assetId),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> Function(Iterable<LocalAsset>) get _upsertAssets =>
|
Future<void> Function(Iterable<LocalAsset>) get _upsertAssets =>
|
||||||
CurrentPlatform.isIOS ? _upsertAssetsDarwin : _upsertAssetsAndroid;
|
CurrentPlatform.isIOS ? _upsertAssetsDarwin : _upsertAssetsAndroid;
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:immich_mobile/constants/constants.dart';
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
@@ -172,4 +174,40 @@ class DriftLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
final rows = await query.get();
|
final rows = await query.get();
|
||||||
return rows.map((row) => row.readTable(_db.localAssetEntity).toDto()).toList();
|
return rows.map((row) => row.readTable(_db.localAssetEntity).toDto()).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<LocalAsset>> getEmptyCloudIdAssets() {
|
||||||
|
final query = _db.localAssetEntity.select()..where((row) => row.iCloudId.isNull());
|
||||||
|
return query.map((row) => row.toDto()).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Map<String, String>> getHashMappingFromCloudId() async {
|
||||||
|
final query =
|
||||||
|
_db.localAssetEntity.selectOnly().join([
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.remoteAssetCloudIdEntity,
|
||||||
|
_db.localAssetEntity.iCloudId.equalsExp(_db.remoteAssetCloudIdEntity.cloudId),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
_db.remoteAssetCloudIdEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
..addColumns([_db.localAssetEntity.id, _db.remoteAssetEntity.checksum])
|
||||||
|
..where(
|
||||||
|
_db.remoteAssetCloudIdEntity.cloudId.isNotNull() &
|
||||||
|
_db.localAssetEntity.checksum.isNull() &
|
||||||
|
((_db.remoteAssetCloudIdEntity.adjustmentTime.isExp(_db.localAssetEntity.adjustmentTime)) &
|
||||||
|
(_db.remoteAssetCloudIdEntity.latitude.isExp(_db.localAssetEntity.latitude)) &
|
||||||
|
(_db.remoteAssetCloudIdEntity.longitude.isExp(_db.localAssetEntity.longitude)) &
|
||||||
|
(_db.remoteAssetCloudIdEntity.createdAt.isExp(_db.localAssetEntity.createdAt))),
|
||||||
|
);
|
||||||
|
final mapping = await query
|
||||||
|
.map(
|
||||||
|
(row) => (assetId: row.read(_db.localAssetEntity.id)!, checksum: row.read(_db.remoteAssetEntity.checksum)!),
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
return {for (final entry in mapping) entry.assetId: entry.checksum};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import 'package:logging/logging.dart';
|
|||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
class StorageRepository {
|
class StorageRepository {
|
||||||
const StorageRepository();
|
final log = Logger('StorageRepository');
|
||||||
|
|
||||||
|
StorageRepository();
|
||||||
|
|
||||||
Future<File?> getFileForAsset(String assetId) async {
|
Future<File?> getFileForAsset(String assetId) async {
|
||||||
File? file;
|
File? file;
|
||||||
@@ -82,6 +84,51 @@ class StorageRepository {
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> isAssetAvailableLocally(String assetId) async {
|
||||||
|
try {
|
||||||
|
final entity = await AssetEntity.fromId(assetId);
|
||||||
|
if (entity == null) {
|
||||||
|
log.warning("Cannot get AssetEntity for asset $assetId");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await entity.isLocallyAvailable(isOrigin: true);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log.warning("Error checking if asset is locally available $assetId", error, stackTrace);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> loadFileFromCloud(String assetId, {PMProgressHandler? progressHandler}) async {
|
||||||
|
try {
|
||||||
|
final entity = await AssetEntity.fromId(assetId);
|
||||||
|
if (entity == null) {
|
||||||
|
log.warning("Cannot get AssetEntity for asset $assetId");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await entity.loadFile(progressHandler: progressHandler);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log.warning("Error loading file from cloud for asset $assetId", error, stackTrace);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<File?> loadMotionFileFromCloud(String assetId, {PMProgressHandler? progressHandler}) async {
|
||||||
|
try {
|
||||||
|
final entity = await AssetEntity.fromId(assetId);
|
||||||
|
if (entity == null) {
|
||||||
|
log.warning("Cannot get AssetEntity for asset $assetId");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await entity.loadFile(withSubtype: true, progressHandler: progressHandler);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log.warning("Error loading motion file from cloud for asset $assetId", error, stackTrace);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> clearCache() async {
|
Future<void> clearCache() async {
|
||||||
final log = Logger('StorageRepository');
|
final log = Logger('StorageRepository');
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class SyncApiRepository {
|
|||||||
SyncRequestType.usersV1,
|
SyncRequestType.usersV1,
|
||||||
SyncRequestType.assetsV1,
|
SyncRequestType.assetsV1,
|
||||||
SyncRequestType.assetExifsV1,
|
SyncRequestType.assetExifsV1,
|
||||||
|
SyncRequestType.assetMetadataV1,
|
||||||
SyncRequestType.partnersV1,
|
SyncRequestType.partnersV1,
|
||||||
SyncRequestType.partnerAssetsV1,
|
SyncRequestType.partnerAssetsV1,
|
||||||
SyncRequestType.partnerAssetExifsV1,
|
SyncRequestType.partnerAssetExifsV1,
|
||||||
@@ -148,6 +149,8 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
||||||
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
||||||
|
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
||||||
|
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
||||||
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:immich_mobile/constants/constants.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.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/memory.model.dart';
|
import 'package:immich_mobile/domain/models/memory.model.dart';
|
||||||
@@ -18,6 +19,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album.entity.drift.
|
|||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album_asset.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart';
|
||||||
|
import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/stack.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/user.entity.drift.dart';
|
||||||
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart';
|
import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.drift.dart';
|
||||||
@@ -55,6 +57,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
await _db.authUserEntity.deleteAll();
|
await _db.authUserEntity.deleteAll();
|
||||||
await _db.userEntity.deleteAll();
|
await _db.userEntity.deleteAll();
|
||||||
await _db.userMetadataEntity.deleteAll();
|
await _db.userMetadataEntity.deleteAll();
|
||||||
|
await _db.remoteAssetCloudIdEntity.deleteAll();
|
||||||
});
|
});
|
||||||
await _db.customStatement('PRAGMA foreign_keys = ON');
|
await _db.customStatement('PRAGMA foreign_keys = ON');
|
||||||
});
|
});
|
||||||
@@ -197,6 +200,7 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
libraryId: Value(asset.libraryId),
|
libraryId: Value(asset.libraryId),
|
||||||
width: Value(asset.width),
|
width: Value(asset.width),
|
||||||
height: Value(asset.height),
|
height: Value(asset.height),
|
||||||
|
isEdited: Value(asset.isEdited),
|
||||||
);
|
);
|
||||||
|
|
||||||
batch.insert(
|
batch.insert(
|
||||||
@@ -272,6 +276,50 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteAssetsMetadataV1(Iterable<SyncAssetMetadataDeleteV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final metadata in data) {
|
||||||
|
if (metadata.key == kMobileMetadataKey) {
|
||||||
|
batch.deleteWhere(_db.remoteAssetCloudIdEntity, (row) => row.assetId.equals(metadata.assetId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: deleteAssetsMetadataV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> updateAssetsMetadataV1(Iterable<SyncAssetMetadataV1> data) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final metadata in data) {
|
||||||
|
if (metadata.key == kMobileMetadataKey) {
|
||||||
|
final map = metadata.value as Map<String, Object?>;
|
||||||
|
final companion = RemoteAssetCloudIdEntityCompanion(
|
||||||
|
cloudId: Value(map['iCloudId']?.toString()),
|
||||||
|
createdAt: Value(map['createdAt'] != null ? DateTime.parse(map['createdAt'] as String) : null),
|
||||||
|
adjustmentTime: Value(
|
||||||
|
map['adjustmentTime'] != null ? DateTime.parse(map['adjustmentTime'] as String) : null,
|
||||||
|
),
|
||||||
|
latitude: Value(map['latitude'] != null ? (double.tryParse(map['latitude'] as String)) : null),
|
||||||
|
longitude: Value(map['longitude'] != null ? (double.tryParse(map['longitude'] as String)) : null),
|
||||||
|
);
|
||||||
|
batch.insert(
|
||||||
|
_db.remoteAssetCloudIdEntity,
|
||||||
|
companion.copyWith(assetId: Value(metadata.assetId)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAssetsMetadataV1', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> deleteAlbumsV1(Iterable<SyncAlbumDeleteV1> data) async {
|
Future<void> deleteAlbumsV1(Iterable<SyncAlbumDeleteV1> data) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
durationInSeconds: row.durationInSeconds,
|
durationInSeconds: row.durationInSeconds,
|
||||||
livePhotoVideoId: row.livePhotoVideoId,
|
livePhotoVideoId: row.livePhotoVideoId,
|
||||||
stackId: row.stackId,
|
stackId: row.stackId,
|
||||||
|
isEdited: row.isEdited,
|
||||||
)
|
)
|
||||||
: LocalAsset(
|
: LocalAsset(
|
||||||
id: row.localId!,
|
id: row.localId!,
|
||||||
@@ -84,6 +85,11 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
isFavorite: row.isFavorite,
|
isFavorite: row.isFavorite,
|
||||||
durationInSeconds: row.durationInSeconds,
|
durationInSeconds: row.durationInSeconds,
|
||||||
orientation: row.orientation,
|
orientation: row.orientation,
|
||||||
|
cloudId: row.iCloudId,
|
||||||
|
latitude: row.latitude,
|
||||||
|
longitude: row.longitude,
|
||||||
|
adjustmentTime: row.adjustmentTime,
|
||||||
|
isEdited: row.isEdited,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.get();
|
.get();
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
_db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum),
|
_db.remoteAssetEntity.checksum.equalsExp(_db.trashedLocalAssetEntity.checksum),
|
||||||
),
|
),
|
||||||
])..where(
|
])..where(
|
||||||
_db.trashedLocalAssetEntity.albumId.isInQuery(selectedAlbumIds) &
|
_db.trashedLocalAssetEntity.source.equalsValue(TrashOrigin.remoteSync) &
|
||||||
|
_db.trashedLocalAssetEntity.albumId.isInQuery(selectedAlbumIds) &
|
||||||
_db.remoteAssetEntity.deletedAt.isNull(),
|
_db.remoteAssetEntity.deletedAt.isNull(),
|
||||||
))
|
))
|
||||||
.get();
|
.get();
|
||||||
@@ -84,6 +85,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
durationInSeconds: Value(item.asset.durationInSeconds),
|
durationInSeconds: Value(item.asset.durationInSeconds),
|
||||||
isFavorite: Value(item.asset.isFavorite),
|
isFavorite: Value(item.asset.isFavorite),
|
||||||
orientation: Value(item.asset.orientation),
|
orientation: Value(item.asset.orientation),
|
||||||
|
source: TrashOrigin.localSync,
|
||||||
);
|
);
|
||||||
|
|
||||||
batch.insert<$TrashedLocalAssetEntityTable, TrashedLocalAssetEntityData>(
|
batch.insert<$TrashedLocalAssetEntityTable, TrashedLocalAssetEntityData>(
|
||||||
@@ -124,7 +126,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
Future<void> trashLocalAsset(Map<String, List<LocalAsset>> assetsByAlbums) async {
|
Future<void> trashLocalAsset(Map<String, List<LocalAsset>> assetsByAlbums) async {
|
||||||
if (assetsByAlbums.isEmpty) {
|
if (assetsByAlbums.isEmpty) {
|
||||||
return;
|
return Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
final companions = <TrashedLocalAssetEntityCompanion>[];
|
final companions = <TrashedLocalAssetEntityCompanion>[];
|
||||||
@@ -147,6 +149,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
orientation: Value(asset.orientation),
|
orientation: Value(asset.orientation),
|
||||||
createdAt: Value(asset.createdAt),
|
createdAt: Value(asset.createdAt),
|
||||||
updatedAt: Value(asset.updatedAt),
|
updatedAt: Value(asset.updatedAt),
|
||||||
|
source: const Value(TrashOrigin.remoteSync),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -165,7 +168,7 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
Future<void> applyRestoredAssets(List<String> idList) async {
|
Future<void> applyRestoredAssets(List<String> idList) async {
|
||||||
if (idList.isEmpty) {
|
if (idList.isEmpty) {
|
||||||
return;
|
return Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
final trashedAssets = <TrashedLocalAssetEntityData>[];
|
final trashedAssets = <TrashedLocalAssetEntityData>[];
|
||||||
@@ -205,6 +208,58 @@ class DriftTrashedLocalAssetRepository extends DriftDatabaseRepository {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> applyTrashedAssets(List<String> idList) async {
|
||||||
|
if (idList.isEmpty) {
|
||||||
|
return Future.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
final trashedAssets = <({LocalAssetEntityData asset, String albumId})>[];
|
||||||
|
|
||||||
|
for (final slice in idList.slices(kDriftMaxChunk)) {
|
||||||
|
final rows = await (_db.select(_db.localAlbumAssetEntity).join([
|
||||||
|
innerJoin(_db.localAssetEntity, _db.localAlbumAssetEntity.assetId.equalsExp(_db.localAssetEntity.id)),
|
||||||
|
])..where(_db.localAlbumAssetEntity.assetId.isIn(slice))).get();
|
||||||
|
|
||||||
|
final assetsWithAlbum = rows.map(
|
||||||
|
(row) =>
|
||||||
|
(albumId: row.readTable(_db.localAlbumAssetEntity).albumId, asset: row.readTable(_db.localAssetEntity)),
|
||||||
|
);
|
||||||
|
|
||||||
|
trashedAssets.addAll(assetsWithAlbum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trashedAssets.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final companions = trashedAssets.map((e) {
|
||||||
|
return TrashedLocalAssetEntityCompanion.insert(
|
||||||
|
id: e.asset.id,
|
||||||
|
name: e.asset.name,
|
||||||
|
type: e.asset.type,
|
||||||
|
createdAt: Value(e.asset.createdAt),
|
||||||
|
updatedAt: Value(e.asset.updatedAt),
|
||||||
|
width: Value(e.asset.width),
|
||||||
|
height: Value(e.asset.height),
|
||||||
|
durationInSeconds: Value(e.asset.durationInSeconds),
|
||||||
|
checksum: Value(e.asset.checksum),
|
||||||
|
isFavorite: Value(e.asset.isFavorite),
|
||||||
|
orientation: Value(e.asset.orientation),
|
||||||
|
source: TrashOrigin.localUser,
|
||||||
|
albumId: e.albumId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await _db.transaction(() async {
|
||||||
|
for (final companion in companions) {
|
||||||
|
await _db.into(_db.trashedLocalAssetEntity).insertOnConflictUpdate(companion);
|
||||||
|
}
|
||||||
|
for (final slice in idList.slices(kDriftMaxChunk)) {
|
||||||
|
await (_db.delete(_db.localAssetEntity)..where((t) => t.id.isIn(slice))).go();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<Map<String, List<LocalAsset>>> getToTrash() async {
|
Future<Map<String, List<LocalAsset>>> getToTrash() async {
|
||||||
final result = <String, List<LocalAsset>>{};
|
final result = <String, List<LocalAsset>>{};
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,8 @@ class ServerVersion extends SemVer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_);
|
ServerVersion.fromDto(ServerVersionResponseDto dto) : super(major: dto.major, minor: dto.minor, patch: dto.patch_);
|
||||||
|
|
||||||
|
bool isAtLeast({int major = 0, int minor = 0, int patch = 0}) {
|
||||||
|
return this >= SemVer(major: major, minor: minor, patch: patch);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import 'package:path/path.dart';
|
|||||||
|
|
||||||
enum ShareIntentAttachmentType { image, video }
|
enum ShareIntentAttachmentType { image, video }
|
||||||
|
|
||||||
enum UploadStatus { enqueued, running, complete, notFound, failed, canceled, waitingToRetry, paused }
|
enum UploadStatus { enqueued, running, complete, failed }
|
||||||
|
|
||||||
class ShareIntentAttachment {
|
class ShareIntentAttachment {
|
||||||
final String path;
|
final String path;
|
||||||
|
|||||||
@@ -93,11 +93,11 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
Logger("DriftBackupPage").warning("Remote sync did not complete successfully, skipping backup");
|
Logger("DriftBackupPage").warning("Remote sync did not complete successfully, skipping backup");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await backupNotifier.startBackup(currentUser.id);
|
await backupNotifier.startForegroundBackup(currentUser.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> stopBackup() async {
|
Future<void> stopBackup() async {
|
||||||
await backupNotifier.cancel();
|
await backupNotifier.stopForegroundBackup();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -113,10 +113,10 @@ class _DriftBackupAlbumSelectionPageState extends ConsumerState<DriftBackupAlbum
|
|||||||
unawaited(nativeSync.cancelHashing().whenComplete(() => backgroundSync.hashAssets()));
|
unawaited(nativeSync.cancelHashing().whenComplete(() => backgroundSync.hashAssets()));
|
||||||
if (isBackupEnabled) {
|
if (isBackupEnabled) {
|
||||||
unawaited(
|
unawaited(
|
||||||
backupNotifier.cancel().whenComplete(
|
backupNotifier.stopForegroundBackup().whenComplete(
|
||||||
() => backgroundSync.syncRemote().then((success) {
|
() => backgroundSync.syncRemote().then((success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
return backupNotifier.startBackup(user.id);
|
return backupNotifier.startForegroundBackup(user.id);
|
||||||
} else {
|
} else {
|
||||||
Logger('DriftBackupAlbumSelectionPage').warning('Background sync failed, not starting backup');
|
Logger('DriftBackupAlbumSelectionPage').warning('Background sync failed, not starting backup');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,10 +60,10 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
final backupNotifier = ref.read(driftBackupProvider.notifier);
|
||||||
final backgroundSync = ref.read(backgroundSyncProvider);
|
final backgroundSync = ref.read(backgroundSyncProvider);
|
||||||
unawaited(
|
unawaited(
|
||||||
backupNotifier.cancel().whenComplete(
|
backupNotifier.stopForegroundBackup().whenComplete(
|
||||||
() => backgroundSync.syncRemote().then((success) {
|
() => backgroundSync.syncRemote().then((success) {
|
||||||
if (success) {
|
if (success) {
|
||||||
return backupNotifier.startBackup(currentUser.id);
|
return backupNotifier.startForegroundBackup(currentUser.id);
|
||||||
} else {
|
} else {
|
||||||
Logger('DriftBackupOptionsPage').warning('Background sync failed, not starting backup');
|
Logger('DriftBackupOptionsPage').warning('Background sync failed, not starting backup');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,70 @@ import 'package:immich_mobile/utils/bytes_units.dart';
|
|||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class DriftUploadDetailPage extends ConsumerWidget {
|
class DriftUploadDetailPage extends ConsumerStatefulWidget {
|
||||||
const DriftUploadDetailPage({super.key});
|
const DriftUploadDetailPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
ConsumerState<DriftUploadDetailPage> createState() => _DriftUploadDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DriftUploadDetailPageState extends ConsumerState<DriftUploadDetailPage> {
|
||||||
|
final Set<String> _seenTaskIds = {};
|
||||||
|
final Set<String> _failedTaskIds = {};
|
||||||
|
|
||||||
|
final Map<String, int> _taskSlotAssignments = {};
|
||||||
|
static const int _maxSlots = 3;
|
||||||
|
|
||||||
|
/// Assigns uploading items to fixed slots to prevent jumping when items complete
|
||||||
|
List<DriftUploadStatus?> _assignItemsToSlots(List<DriftUploadStatus> uploadingItems) {
|
||||||
|
final slots = List<DriftUploadStatus?>.filled(_maxSlots, null);
|
||||||
|
final currentTaskIds = uploadingItems.map((e) => e.taskId).toSet();
|
||||||
|
|
||||||
|
_taskSlotAssignments.removeWhere((taskId, _) => !currentTaskIds.contains(taskId));
|
||||||
|
|
||||||
|
for (final item in uploadingItems) {
|
||||||
|
final existingSlot = _taskSlotAssignments[item.taskId];
|
||||||
|
if (existingSlot != null && existingSlot < _maxSlots) {
|
||||||
|
slots[existingSlot] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final item in uploadingItems) {
|
||||||
|
if (_taskSlotAssignments.containsKey(item.taskId)) continue;
|
||||||
|
|
||||||
|
for (int i = 0; i < _maxSlots; i++) {
|
||||||
|
if (slots[i] == null) {
|
||||||
|
slots[i] = item;
|
||||||
|
_taskSlotAssignments[item.taskId] = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return slots;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
final uploadItems = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
final uploadItems = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
||||||
|
final iCloudProgress = ref.watch(driftBackupProvider.select((state) => state.iCloudDownloadProgress));
|
||||||
|
|
||||||
|
for (final item in uploadItems.values) {
|
||||||
|
if (item.isFailed == true) {
|
||||||
|
_failedTaskIds.add(item.taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final item in uploadItems.values) {
|
||||||
|
if (item.progress >= 1.0 && item.isFailed != true && !_failedTaskIds.contains(item.taskId)) {
|
||||||
|
if (!_seenTaskIds.contains(item.taskId)) {
|
||||||
|
_seenTaskIds.add(item.taskId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final uploadingItems = uploadItems.values.where((item) => item.progress < 1.0 && item.isFailed != true).toList();
|
||||||
|
final failedItems = uploadItems.values.where((item) => item.isFailed == true).toList();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
@@ -25,98 +83,326 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 1,
|
scrolledUnderElevation: 1,
|
||||||
),
|
),
|
||||||
body: uploadItems.isEmpty ? _buildEmptyState(context) : _buildUploadList(uploadItems),
|
body: _buildTwoSectionLayout(context, uploadingItems, failedItems, iCloudProgress),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildEmptyState(BuildContext context) {
|
Widget _buildTwoSectionLayout(
|
||||||
return Center(
|
BuildContext context,
|
||||||
child: Column(
|
List<DriftUploadStatus> uploadingItems,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
List<DriftUploadStatus> failedItems,
|
||||||
children: [
|
Map<String, double> iCloudProgress,
|
||||||
Icon(Icons.cloud_off_rounded, size: 80, color: context.colorScheme.onSurface.withValues(alpha: 0.3)),
|
) {
|
||||||
const SizedBox(height: 16),
|
return CustomScrollView(
|
||||||
Text(
|
slivers: [
|
||||||
"no_uploads_in_progress".t(context: context),
|
// iCloud Downloads Section
|
||||||
style: context.textTheme.titleMedium?.copyWith(color: context.colorScheme.onSurface.withValues(alpha: 0.6)),
|
if (iCloudProgress.isNotEmpty) ...[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _buildSectionHeader(
|
||||||
|
context,
|
||||||
|
title: "Downloading from iCloud",
|
||||||
|
count: iCloudProgress.length,
|
||||||
|
color: context.colorScheme.tertiary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
final entry = iCloudProgress.entries.elementAt(index);
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: _buildICloudDownloadCard(context, entry.key, entry.value),
|
||||||
|
);
|
||||||
|
}, childCount: iCloudProgress.length),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Uploading Section
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _buildSectionHeader(
|
||||||
|
context,
|
||||||
|
title: "uploading".t(context: context),
|
||||||
|
count: uploadingItems.length,
|
||||||
|
color: context.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
// Use slot-based assignment to prevent items from jumping
|
||||||
|
final slots = _assignItemsToSlots(uploadingItems);
|
||||||
|
final item = slots[index];
|
||||||
|
if (item != null) {
|
||||||
|
return _buildCurrentUploadCard(context, item);
|
||||||
|
} else {
|
||||||
|
return _buildPlaceholderCard(context);
|
||||||
|
}
|
||||||
|
}, childCount: 3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Errors Section
|
||||||
|
if (failedItems.isNotEmpty) ...[
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: _buildSectionHeader(
|
||||||
|
context,
|
||||||
|
title: "errors_text".t(context: context),
|
||||||
|
count: failedItems.length,
|
||||||
|
color: context.colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverPadding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
sliver: SliverList(
|
||||||
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
|
final item = failedItems[index];
|
||||||
|
return Padding(padding: const EdgeInsets.only(bottom: 8), child: _buildErrorCard(context, item));
|
||||||
|
}, childCount: failedItems.length),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Bottom padding
|
||||||
|
const SliverToBoxAdapter(child: SizedBox(height: 24)),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildSectionHeader(BuildContext context, {required String title, int? count, required Color color}) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600, color: color),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
count != null
|
||||||
|
? Container(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: color.withValues(alpha: 0.15),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
count.toString(),
|
||||||
|
style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.bold, color: color),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUploadList(Map<String, DriftUploadStatus> uploadItems) {
|
Widget _buildICloudDownloadCard(BuildContext context, String assetId, double progress) {
|
||||||
return ListView.separated(
|
final double progressPercentage = (progress * 100).clamp(0, 100);
|
||||||
addAutomaticKeepAlives: true,
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
itemCount: uploadItems.length,
|
|
||||||
separatorBuilder: (context, index) => const SizedBox(height: 4),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final item = uploadItems.values.elementAt(index);
|
|
||||||
return _buildUploadCard(context, item);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildUploadCard(BuildContext context, DriftUploadStatus item) {
|
|
||||||
final isCompleted = item.progress >= 1.0;
|
|
||||||
final double progressPercentage = (item.progress * 100).clamp(0, 100);
|
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
color: item.isFailed != null ? context.colorScheme.errorContainer : context.colorScheme.surfaceContainer,
|
color: context.colorScheme.tertiaryContainer.withValues(alpha: 0.5),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
side: BorderSide(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1),
|
side: BorderSide(color: context.colorScheme.tertiary.withValues(alpha: 0.3), width: 1),
|
||||||
),
|
),
|
||||||
child: InkWell(
|
child: Padding(
|
||||||
onTap: () => _showFileDetailDialog(context, item),
|
padding: const EdgeInsets.all(12),
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
child: Row(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(16),
|
Container(
|
||||||
child: Column(
|
width: 40,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
height: 40,
|
||||||
children: [
|
decoration: BoxDecoration(
|
||||||
Row(
|
color: context.colorScheme.tertiary.withValues(alpha: 0.2),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
child: Icon(Icons.cloud_download_rounded, size: 24, color: context.colorScheme.tertiary),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Text(
|
||||||
child: Column(
|
"downloading_from_icloud".t(context: context),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w500),
|
||||||
spacing: 4,
|
maxLines: 1,
|
||||||
children: [
|
overflow: TextOverflow.ellipsis,
|
||||||
Text(
|
|
||||||
path.basename(item.filename),
|
|
||||||
style: context.textTheme.titleSmall?.copyWith(fontWeight: FontWeight.w600),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
if (item.error != null)
|
|
||||||
Text(
|
|
||||||
item.error!,
|
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
|
||||||
color: context.colorScheme.onErrorContainer.withValues(alpha: 0.6),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"backup_upload_details_page_more_details".t(context: context),
|
|
||||||
style: context.textTheme.bodySmall?.copyWith(
|
|
||||||
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
|
||||||
),
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
_buildProgressIndicator(
|
const SizedBox(height: 4),
|
||||||
context,
|
Text(
|
||||||
item.progress,
|
assetId,
|
||||||
progressPercentage,
|
style: context.textTheme.bodySmall?.copyWith(
|
||||||
isCompleted,
|
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
item.networkSpeedAsString,
|
),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: progress,
|
||||||
|
backgroundColor: context.colorScheme.tertiary.withValues(alpha: 0.2),
|
||||||
|
valueColor: AlwaysStoppedAnimation(context.colorScheme.tertiary),
|
||||||
|
minHeight: 4,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
SizedBox(
|
||||||
|
width: 48,
|
||||||
|
child: Text(
|
||||||
|
"${progressPercentage.toStringAsFixed(0)}%",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: context.colorScheme.tertiary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildCurrentUploadCard(BuildContext context, DriftUploadStatus item) {
|
||||||
|
final double progressPercentage = (item.progress * 100).clamp(0, 100);
|
||||||
|
final isFailed = item.isFailed == true;
|
||||||
|
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: isFailed
|
||||||
|
? context.colorScheme.errorContainer
|
||||||
|
: context.colorScheme.primaryContainer.withValues(alpha: 0.5),
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
side: BorderSide(
|
||||||
|
color: isFailed
|
||||||
|
? context.colorScheme.error.withValues(alpha: 0.3)
|
||||||
|
: context.colorScheme.primary.withValues(alpha: 0.3),
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _showFileDetailDialog(context, item),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 64,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_CurrentUploadThumbnail(taskId: item.taskId),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
path.basename(item.filename),
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
isFailed
|
||||||
|
? item.error ?? "unable_to_upload_file".t(context: context)
|
||||||
|
: "${formatHumanReadableBytes(item.fileSize, 1)} • ${item.networkSpeedAsString}",
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
|
color: isFailed
|
||||||
|
? context.colorScheme.error
|
||||||
|
: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||||
|
),
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
if (!isFailed) ...[
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
ClipRRect(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: item.progress,
|
||||||
|
backgroundColor: context.colorScheme.primary.withValues(alpha: 0.2),
|
||||||
|
valueColor: AlwaysStoppedAnimation(context.colorScheme.primary),
|
||||||
|
minHeight: 4,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
SizedBox(
|
||||||
|
width: 48,
|
||||||
|
child: isFailed
|
||||||
|
? Icon(Icons.error_rounded, color: context.colorScheme.error, size: 28)
|
||||||
|
: Text(
|
||||||
|
"${progressPercentage.toStringAsFixed(0)}%",
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: context.colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildErrorCard(BuildContext context, DriftUploadStatus item) {
|
||||||
|
return Card(
|
||||||
|
elevation: 0,
|
||||||
|
color: context.colorScheme.errorContainer,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
side: BorderSide(color: context.colorScheme.error.withValues(alpha: 0.3), width: 1),
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => _showFileDetailDialog(context, item),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
_CurrentUploadThumbnail(taskId: item.taskId),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
path.basename(item.filename),
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
item.error ?? "unable_to_upload_file".t(context: context),
|
||||||
|
style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.error),
|
||||||
|
maxLines: 4,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Icon(Icons.error_rounded, color: context.colorScheme.error, size: 28),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -124,49 +410,84 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildProgressIndicator(
|
Widget _buildPlaceholderCard(BuildContext context) {
|
||||||
BuildContext context,
|
return Card(
|
||||||
double progress,
|
elevation: 0,
|
||||||
double percentage,
|
color: context.colorScheme.surfaceContainerLow.withValues(alpha: 0.5),
|
||||||
bool isCompleted,
|
shape: RoundedRectangleBorder(
|
||||||
String networkSpeedAsString,
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
) {
|
side: BorderSide(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1, style: BorderStyle.solid),
|
||||||
return Column(
|
),
|
||||||
children: [
|
child: Padding(
|
||||||
Stack(
|
padding: const EdgeInsets.all(12),
|
||||||
alignment: AlignmentDirectional.center,
|
child: SizedBox(
|
||||||
children: [
|
height: 64,
|
||||||
SizedBox(
|
child: Row(
|
||||||
width: 36,
|
children: [
|
||||||
height: 36,
|
SizedBox(
|
||||||
child: TweenAnimationBuilder(
|
width: 48,
|
||||||
tween: Tween<double>(begin: 0.0, end: progress),
|
height: 48,
|
||||||
duration: const Duration(milliseconds: 300),
|
child: Container(
|
||||||
builder: (context, value, _) => CircularProgressIndicator(
|
decoration: BoxDecoration(
|
||||||
backgroundColor: context.colorScheme.outline.withValues(alpha: 0.2),
|
color: context.colorScheme.outline.withValues(alpha: 0.1),
|
||||||
strokeWidth: 3,
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
value: value,
|
),
|
||||||
color: isCompleted ? context.colorScheme.primary : context.colorScheme.secondary,
|
child: Icon(
|
||||||
|
Icons.hourglass_empty_rounded,
|
||||||
|
size: 24,
|
||||||
|
color: context.colorScheme.outline.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(width: 12),
|
||||||
if (isCompleted)
|
Expanded(
|
||||||
Icon(Icons.check_circle_rounded, size: 28, color: context.colorScheme.primary)
|
child: Column(
|
||||||
else
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Text(
|
children: [
|
||||||
percentage.toStringAsFixed(0),
|
Container(
|
||||||
style: context.textTheme.labelSmall?.copyWith(fontWeight: FontWeight.bold, fontSize: 10),
|
height: 14,
|
||||||
|
width: 120,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.outline.withValues(alpha: 0.1),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Container(
|
||||||
|
height: 10,
|
||||||
|
width: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.outline.withValues(alpha: 0.08),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Container(
|
||||||
|
height: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.outline.withValues(alpha: 0.1),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(width: 12),
|
||||||
),
|
SizedBox(
|
||||||
Text(
|
width: 48,
|
||||||
networkSpeedAsString,
|
child: Text(
|
||||||
style: context.textTheme.labelSmall?.copyWith(
|
"0%",
|
||||||
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
textAlign: TextAlign.right,
|
||||||
fontSize: 10,
|
style: context.textTheme.titleMedium?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: context.colorScheme.outline.withValues(alpha: 0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +499,44 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CurrentUploadThumbnail extends ConsumerWidget {
|
||||||
|
final String taskId;
|
||||||
|
const _CurrentUploadThumbnail({required this.taskId});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return FutureBuilder<LocalAsset?>(
|
||||||
|
future: _getAsset(ref),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return SizedBox(
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.primary.withValues(alpha: 0.2),
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||||
|
),
|
||||||
|
clipBehavior: Clip.antiAlias,
|
||||||
|
child: snapshot.data != null
|
||||||
|
? Thumbnail.fromAsset(asset: snapshot.data!, size: const Size(48, 48), fit: BoxFit.cover)
|
||||||
|
: Icon(Icons.image, size: 24, color: context.colorScheme.primary),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<LocalAsset?> _getAsset(WidgetRef ref) async {
|
||||||
|
try {
|
||||||
|
return await ref.read(localAssetRepository).getById(taskId);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class FileDetailDialog extends ConsumerWidget {
|
class FileDetailDialog extends ConsumerWidget {
|
||||||
final DriftUploadStatus uploadStatus;
|
final DriftUploadStatus uploadStatus;
|
||||||
|
|
||||||
const FileDetailDialog({super.key, required this.uploadStatus});
|
const FileDetailDialog({super.key, required this.uploadStatus});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -212,14 +568,12 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
|
return const SizedBox(height: 200, child: Center(child: CircularProgressIndicator()));
|
||||||
}
|
}
|
||||||
|
|
||||||
final asset = snapshot.data;
|
final asset = snapshot.data;
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
// Thumbnail at the top center
|
|
||||||
Center(
|
Center(
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
@@ -237,7 +591,7 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
if (asset != null) ...[
|
if (asset != null)
|
||||||
_buildInfoSection(context, [
|
_buildInfoSection(context, [
|
||||||
_buildInfoRow(context, "filename".t(context: context), path.basename(uploadStatus.filename)),
|
_buildInfoRow(context, "filename".t(context: context), path.basename(uploadStatus.filename)),
|
||||||
_buildInfoRow(context, "local_id".t(context: context), asset.id),
|
_buildInfoRow(context, "local_id".t(context: context), asset.id),
|
||||||
@@ -254,7 +608,6 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
if (asset.checksum != null)
|
if (asset.checksum != null)
|
||||||
_buildInfoRow(context, "checksum".t(context: context), asset.checksum!),
|
_buildInfoRow(context, "checksum".t(context: context), asset.checksum!),
|
||||||
]),
|
]),
|
||||||
],
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -282,7 +635,7 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
border: Border.all(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1),
|
border: Border.all(color: context.colorScheme.outline.withValues(alpha: 0.1), width: 1),
|
||||||
),
|
),
|
||||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [...children]),
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: children),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,12 +656,7 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(value, style: context.textTheme.labelMedium, maxLines: 3, overflow: TextOverflow.ellipsis),
|
||||||
value,
|
|
||||||
style: context.textTheme.labelMedium?.copyWith(),
|
|
||||||
maxLines: 3,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -317,8 +665,7 @@ class FileDetailDialog extends ConsumerWidget {
|
|||||||
|
|
||||||
Future<LocalAsset?> _getAssetDetails(WidgetRef ref, String localAssetId) async {
|
Future<LocalAsset?> _getAssetDetails(WidgetRef ref, String localAssetId) async {
|
||||||
try {
|
try {
|
||||||
final repository = ref.read(localAssetRepository);
|
return await ref.read(localAssetRepository).getById(localAssetId);
|
||||||
return await repository.getById(localAssetId);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class AppLogPage extends HookConsumerWidget {
|
|||||||
minLeadingWidth: 10,
|
minLeadingWidth: 10,
|
||||||
title: Text(
|
title: Text(
|
||||||
truncateLogMessage(logMessage.message, 4),
|
truncateLogMessage(logMessage.message, 4),
|
||||||
style: TextStyle(fontSize: 14.0, color: context.colorScheme.onSurface, fontFamily: "Inconsolata"),
|
style: TextStyle(fontSize: 14.0, color: context.colorScheme.onSurface, fontFamily: "GoogleSansCode"),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.logger}",
|
"at ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)} in ${logMessage.logger}",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
text,
|
text,
|
||||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "GoogleSansCode"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -88,7 +88,7 @@ class AppLogDetailPage extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
logger.toString(),
|
logger.toString(),
|
||||||
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "Inconsolata"),
|
style: const TextStyle(fontSize: 12.0, fontWeight: FontWeight.bold, fontFamily: "GoogleSansCode"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ class _MobileLayout extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 16), children: [...settings]);
|
return ListView(padding: const EdgeInsets.only(top: 10.0, bottom: 60), children: [...settings]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,7 +142,7 @@ class SettingsSubPage extends StatelessWidget {
|
|||||||
context.locale;
|
context.locale;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(centerTitle: false, title: Text(section.title).tr()),
|
appBar: AppBar(centerTitle: false, title: Text(section.title).tr()),
|
||||||
body: section.widget,
|
body: Padding(padding: const EdgeInsets.only(bottom: 60.0), child: section.widget),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import 'package:immich_mobile/providers/background_sync.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/backup/backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/backup.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/server_info.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
@@ -50,7 +49,6 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
final accessToken = Store.tryGet(StoreKey.accessToken);
|
final accessToken = Store.tryGet(StoreKey.accessToken);
|
||||||
|
|
||||||
if (accessToken != null && serverUrl != null && endpoint != null) {
|
if (accessToken != null && serverUrl != null && endpoint != null) {
|
||||||
final infoProvider = ref.read(serverInfoProvider.notifier);
|
|
||||||
final wsProvider = ref.read(websocketProvider.notifier);
|
final wsProvider = ref.read(websocketProvider.notifier);
|
||||||
final backgroundManager = ref.read(backgroundSyncProvider);
|
final backgroundManager = ref.read(backgroundSyncProvider);
|
||||||
final backupProvider = ref.read(driftBackupProvider.notifier);
|
final backupProvider = ref.read(driftBackupProvider.notifier);
|
||||||
@@ -60,7 +58,6 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
(_) async {
|
(_) async {
|
||||||
try {
|
try {
|
||||||
wsProvider.connect();
|
wsProvider.connect();
|
||||||
unawaited(infoProvider.getServerInfo());
|
|
||||||
|
|
||||||
if (Store.isBetaTimelineEnabled) {
|
if (Store.isBetaTimelineEnabled) {
|
||||||
bool syncSuccess = false;
|
bool syncSuccess = false;
|
||||||
@@ -75,6 +72,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
_resumeBackup(backupProvider);
|
_resumeBackup(backupProvider);
|
||||||
}),
|
}),
|
||||||
_resumeBackup(backupProvider),
|
_resumeBackup(backupProvider),
|
||||||
|
backgroundManager.syncCloudIds(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
await backgroundManager.hashAssets();
|
await backgroundManager.hashAssets();
|
||||||
@@ -132,7 +130,7 @@ class SplashScreenPageState extends ConsumerState<SplashScreenPage> {
|
|||||||
if (isEnableBackup) {
|
if (isEnableBackup) {
|
||||||
final currentUser = Store.tryGet(StoreKey.currentUser);
|
final currentUser = Store.tryGet(StoreKey.currentUser);
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
unawaited(notifier.handleBackupResume(currentUser.id));
|
unawaited(notifier.startForegroundBackup(currentUser.id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -234,7 +234,7 @@ class FolderPath extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
currentFolder.path,
|
currentFolder.path,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontFamily: 'Inconsolata',
|
fontFamily: 'GoogleSansCode',
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: context.colorScheme.onSurface.withAlpha(175),
|
color: context.colorScheme.onSurface.withAlpha(175),
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class LoginPage extends HookConsumerWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.colorScheme.onSurfaceSecondary,
|
color: context.colorScheme.onSurfaceSecondary,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontFamily: "Inconsolata",
|
fontFamily: "GoogleSansCode",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Text(' '),
|
const Text(' '),
|
||||||
@@ -51,7 +51,7 @@ class LoginPage extends HookConsumerWidget {
|
|||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: context.primaryColor,
|
color: context.primaryColor,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontFamily: "Inconsolata",
|
fontFamily: "GoogleSansCode",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class SyncStatusPage extends StatelessWidget {
|
|||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
icon: const Icon(Icons.arrow_back_ios_rounded),
|
icon: const Icon(Icons.arrow_back_ios_rounded),
|
||||||
),
|
),
|
||||||
|
centerTitle: false,
|
||||||
),
|
),
|
||||||
body: const SyncStatusAndActions(),
|
body: const SyncStatusAndActions(),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.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';
|
||||||
@@ -12,7 +11,7 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
class ShareIntentPage extends HookConsumerWidget {
|
class ShareIntentPage extends ConsumerWidget {
|
||||||
const ShareIntentPage({super.key, required this.attachments});
|
const ShareIntentPage({super.key, required this.attachments});
|
||||||
|
|
||||||
final List<ShareIntentAttachment> attachments;
|
final List<ShareIntentAttachment> attachments;
|
||||||
@@ -21,12 +20,13 @@ class ShareIntentPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final currentEndpoint = getServerUrl() ?? '--';
|
final currentEndpoint = getServerUrl() ?? '--';
|
||||||
final candidates = ref.watch(shareIntentUploadProvider);
|
final candidates = ref.watch(shareIntentUploadProvider);
|
||||||
final isUploaded = useState(false);
|
|
||||||
useOnAppLifecycleStateChange((previous, current) {
|
final isUploading = candidates.any((candidate) => candidate.status == UploadStatus.running);
|
||||||
if (current == AppLifecycleState.resumed) {
|
final isUploaded =
|
||||||
isUploaded.value = false;
|
candidates.isNotEmpty &&
|
||||||
}
|
candidates.every(
|
||||||
});
|
(candidate) => candidate.status == UploadStatus.complete || candidate.status == UploadStatus.failed,
|
||||||
|
);
|
||||||
|
|
||||||
void removeAttachment(ShareIntentAttachment attachment) {
|
void removeAttachment(ShareIntentAttachment attachment) {
|
||||||
ref.read(shareIntentUploadProvider.notifier).removeAttachment(attachment);
|
ref.read(shareIntentUploadProvider.notifier).removeAttachment(attachment);
|
||||||
@@ -37,11 +37,8 @@ class ShareIntentPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void upload() async {
|
void upload() async {
|
||||||
for (final attachment in candidates) {
|
final files = candidates.map((candidate) => candidate.file).toList();
|
||||||
await ref.read(shareIntentUploadProvider.notifier).upload(attachment.file);
|
await ref.read(shareIntentUploadProvider.notifier).uploadAll(files);
|
||||||
}
|
|
||||||
|
|
||||||
isUploaded.value = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSelected(ShareIntentAttachment attachment) {
|
bool isSelected(ShareIntentAttachment attachment) {
|
||||||
@@ -84,7 +81,7 @@ class ShareIntentPage extends HookConsumerWidget {
|
|||||||
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
|
padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16),
|
||||||
child: LargeLeadingTile(
|
child: LargeLeadingTile(
|
||||||
onTap: () => toggleSelection(attachment),
|
onTap: () => toggleSelection(attachment),
|
||||||
disabled: isUploaded.value,
|
disabled: isUploading || isUploaded,
|
||||||
selected: isSelected(attachment),
|
selected: isSelected(attachment),
|
||||||
leading: Stack(
|
leading: Stack(
|
||||||
children: [
|
children: [
|
||||||
@@ -131,8 +128,8 @@ class ShareIntentPage extends HookConsumerWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 48,
|
height: 48,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: isUploaded.value ? null : upload,
|
onPressed: (isUploading || isUploaded) ? null : upload,
|
||||||
child: isUploaded.value ? UploadingText(candidates: candidates) : const Text('upload').tr(),
|
child: (isUploading || isUploaded) ? UploadingText(candidates: candidates) : const Text('upload').tr(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -204,14 +201,7 @@ class UploadStatusIcon extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
UploadStatus.complete => Icon(Icons.check_circle_rounded, color: Colors.green, semanticLabel: 'completed'.tr()),
|
UploadStatus.complete => Icon(Icons.check_circle_rounded, color: Colors.green, semanticLabel: 'completed'.tr()),
|
||||||
UploadStatus.notFound ||
|
|
||||||
UploadStatus.failed => Icon(Icons.error_rounded, color: Colors.red, semanticLabel: 'failed'.tr()),
|
UploadStatus.failed => Icon(Icons.error_rounded, color: Colors.red, semanticLabel: 'failed'.tr()),
|
||||||
UploadStatus.canceled => Icon(Icons.cancel_rounded, color: Colors.red, semanticLabel: 'canceled'.tr()),
|
|
||||||
UploadStatus.waitingToRetry || UploadStatus.paused => Icon(
|
|
||||||
Icons.pause_circle_rounded,
|
|
||||||
color: context.primaryColor,
|
|
||||||
semanticLabel: 'paused'.tr(),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return statusIcon;
|
return statusIcon;
|
||||||
|
|||||||
72
mobile/lib/platform/native_sync_api.g.dart
generated
72
mobile/lib/platform/native_sync_api.g.dart
generated
@@ -270,6 +270,45 @@ class HashResult {
|
|||||||
int get hashCode => Object.hashAll(_toList());
|
int get hashCode => Object.hashAll(_toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CloudIdResult {
|
||||||
|
CloudIdResult({required this.assetId, this.error, this.cloudId});
|
||||||
|
|
||||||
|
String assetId;
|
||||||
|
|
||||||
|
String? error;
|
||||||
|
|
||||||
|
String? cloudId;
|
||||||
|
|
||||||
|
List<Object?> _toList() {
|
||||||
|
return <Object?>[assetId, error, cloudId];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object encode() {
|
||||||
|
return _toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
static CloudIdResult decode(Object result) {
|
||||||
|
result as List<Object?>;
|
||||||
|
return CloudIdResult(assetId: result[0]! as String, error: result[1] as String?, cloudId: result[2] as String?);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is! CloudIdResult || other.runtimeType != runtimeType) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (identical(this, other)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return _deepEquals(encode(), other.encode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
int get hashCode => Object.hashAll(_toList());
|
||||||
|
}
|
||||||
|
|
||||||
class _PigeonCodec extends StandardMessageCodec {
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
const _PigeonCodec();
|
const _PigeonCodec();
|
||||||
@override
|
@override
|
||||||
@@ -289,6 +328,9 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
} else if (value is HashResult) {
|
} else if (value is HashResult) {
|
||||||
buffer.putUint8(132);
|
buffer.putUint8(132);
|
||||||
writeValue(buffer, value.encode());
|
writeValue(buffer, value.encode());
|
||||||
|
} else if (value is CloudIdResult) {
|
||||||
|
buffer.putUint8(133);
|
||||||
|
writeValue(buffer, value.encode());
|
||||||
} else {
|
} else {
|
||||||
super.writeValue(buffer, value);
|
super.writeValue(buffer, value);
|
||||||
}
|
}
|
||||||
@@ -305,6 +347,8 @@ class _PigeonCodec extends StandardMessageCodec {
|
|||||||
return SyncDelta.decode(readValue(buffer)!);
|
return SyncDelta.decode(readValue(buffer)!);
|
||||||
case 132:
|
case 132:
|
||||||
return HashResult.decode(readValue(buffer)!);
|
return HashResult.decode(readValue(buffer)!);
|
||||||
|
case 133:
|
||||||
|
return CloudIdResult.decode(readValue(buffer)!);
|
||||||
default:
|
default:
|
||||||
return super.readValueOfType(type, buffer);
|
return super.readValueOfType(type, buffer);
|
||||||
}
|
}
|
||||||
@@ -616,4 +660,32 @@ class NativeSyncApi {
|
|||||||
return (pigeonVar_replyList[0] as Map<Object?, Object?>?)!.cast<String, List<PlatformAsset>>();
|
return (pigeonVar_replyList[0] as Map<Object?, Object?>?)!.cast<String, List<PlatformAsset>>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<CloudIdResult>> getCloudIdForAssetIds(List<String> assetIds) async {
|
||||||
|
final String pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.NativeSyncApi.getCloudIdForAssetIds$pigeonVar_messageChannelSuffix';
|
||||||
|
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[assetIds]);
|
||||||
|
final List<Object?>? pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
if (pigeonVar_replyList == null) {
|
||||||
|
throw _createConnectionError(pigeonVar_channelName);
|
||||||
|
} else if (pigeonVar_replyList.length > 1) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: pigeonVar_replyList[0]! as String,
|
||||||
|
message: pigeonVar_replyList[1] as String?,
|
||||||
|
details: pigeonVar_replyList[2],
|
||||||
|
);
|
||||||
|
} else if (pigeonVar_replyList[0] == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (pigeonVar_replyList[0] as List<Object?>?)!.cast<CloudIdResult>();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection
|
|||||||
),
|
),
|
||||||
_PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()),
|
_PropertyItem(label: 'Is Favorite', value: asset.isFavorite.toString()),
|
||||||
_PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId),
|
_PropertyItem(label: 'Live Photo Video ID', value: asset.livePhotoVideoId),
|
||||||
|
_PropertyItem(label: 'Is Edited', value: asset.isEdited.toString()),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +132,7 @@ class _AssetPropertiesSectionState extends ConsumerState<_AssetPropertiesSection
|
|||||||
final albums = await ref.read(assetServiceProvider).getSourceAlbums(asset.id);
|
final albums = await ref.read(assetServiceProvider).getSourceAlbums(asset.id);
|
||||||
properties.add(_PropertyItem(label: 'Album', value: albums.map((a) => a.name).join(', ')));
|
properties.add(_PropertyItem(label: 'Album', value: albums.map((a) => a.name).join(', ')));
|
||||||
if (CurrentPlatform.isIOS) {
|
if (CurrentPlatform.isIOS) {
|
||||||
|
properties.add(_PropertyItem(label: 'Cloud ID', value: asset.cloudId));
|
||||||
properties.add(_PropertyItem(label: 'Adjustment Time', value: asset.adjustmentTime?.toString()));
|
properties.add(_PropertyItem(label: 'Adjustment Time', value: asset.adjustmentTime?.toString()));
|
||||||
}
|
}
|
||||||
properties.add(
|
properties.add(
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ class _PlaceTile extends StatelessWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 80,
|
width: 80,
|
||||||
height: 80,
|
height: 80,
|
||||||
child: Thumbnail.remote(remoteId: place.$2, fit: BoxFit.cover),
|
child: Thumbnail.remote(remoteId: place.$2, fit: BoxFit.cover, thumbhash: ""),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:cancellation_token_http/http.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/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@@ -12,7 +13,7 @@ import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
|||||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
import 'package:immich_mobile/repositories/file_media.repository.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/services/upload.service.dart';
|
import 'package:immich_mobile/services/foreground_upload.service.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.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;
|
||||||
@@ -78,7 +79,7 @@ class DriftEditImagePage extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await ref.read(uploadServiceProvider).manualBackup([localAsset]);
|
await ref.read(foregroundUploadServiceProvider).uploadManual([localAsset], CancellationToken());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
durationInSecond: 6,
|
durationInSecond: 6,
|
||||||
|
|||||||
@@ -1,12 +1,17 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/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/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||||
|
import 'package:immich_mobile/providers/backup/asset_upload_progress.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/action.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_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
|
import 'package:immich_ui/immich_ui.dart';
|
||||||
|
|
||||||
class UploadActionButton extends ConsumerWidget {
|
class UploadActionButton extends ConsumerWidget {
|
||||||
final ActionSource source;
|
final ActionSource source;
|
||||||
@@ -20,19 +25,38 @@ class UploadActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).upload(source);
|
final isTimeline = source == ActionSource.timeline;
|
||||||
|
List<LocalAsset>? assets;
|
||||||
|
|
||||||
final successMessage = 'upload_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
if (source == ActionSource.timeline) {
|
||||||
|
assets = ref.read(multiSelectProvider).selectedAssets.whereType<LocalAsset>().toList();
|
||||||
|
if (assets.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
} else {
|
||||||
|
unawaited(
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (dialogContext) => const _UploadProgressDialog(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
final result = await ref.read(actionProvider.notifier).upload(source, assets: assets);
|
||||||
|
|
||||||
|
if (!isTimeline && context.mounted) {
|
||||||
|
Navigator.of(context, rootNavigator: true).pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.mounted && !result.success) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: result.success ? successMessage : 'scaffold_body_error_occurred'.t(context: context),
|
msg: 'scaffold_body_error_occurred'.t(context: context),
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastType: result.success ? ToastType.success : ToastType.error,
|
toastType: ToastType.error,
|
||||||
);
|
);
|
||||||
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,3 +71,42 @@ class UploadActionButton extends ConsumerWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _UploadProgressDialog extends ConsumerWidget {
|
||||||
|
const _UploadProgressDialog();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final progressMap = ref.watch(assetUploadProgressProvider);
|
||||||
|
|
||||||
|
// Calculate overall progress from all assets
|
||||||
|
final values = progressMap.values.where((v) => v >= 0).toList();
|
||||||
|
final progress = values.isEmpty ? 0.0 : values.reduce((a, b) => a + b) / values.length;
|
||||||
|
final hasError = progressMap.values.any((v) => v < 0);
|
||||||
|
final percentage = (progress * 100).toInt();
|
||||||
|
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text('uploading'.t(context: context)),
|
||||||
|
content: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (hasError)
|
||||||
|
const Icon(Icons.error_outline, color: Colors.red, size: 48)
|
||||||
|
else
|
||||||
|
CircularProgressIndicator(value: progress > 0 ? progress : null),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
Text(hasError ? 'Error' : '$percentage%'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
ImmichTextButton(
|
||||||
|
onPressed: () {
|
||||||
|
ref.read(manualUploadCancelTokenProvider)?.cancel();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
labelText: 'cancel'.t(context: context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,14 +14,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/providers/album/album_sort_by_options.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/app_settings.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_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.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/providers/album/album_sort_by_options.provider.dart';
|
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
|
||||||
import 'package:immich_mobile/services/app_settings.service.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';
|
||||||
@@ -310,18 +311,17 @@ class _SortButtonState extends ConsumerState<_SortButton> {
|
|||||||
: const Icon(Icons.abc, color: Colors.transparent),
|
: const Icon(Icons.abc, color: Colors.transparent),
|
||||||
onPressed: () => onMenuTapped(sortMode),
|
onPressed: () => onMenuTapped(sortMode),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: WidgetStateProperty.all(const EdgeInsets.fromLTRB(16, 16, 32, 16)),
|
padding: WidgetStateProperty.all(const EdgeInsets.fromLTRB(12, 12, 24, 12)),
|
||||||
backgroundColor: WidgetStateProperty.all(
|
backgroundColor: WidgetStateProperty.all(
|
||||||
albumSortOption == sortMode ? context.colorScheme.primary : Colors.transparent,
|
albumSortOption == sortMode ? context.colorScheme.primary : Colors.transparent,
|
||||||
),
|
),
|
||||||
shape: WidgetStateProperty.all(
|
shape: WidgetStateProperty.all(
|
||||||
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(24))),
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
sortMode.label.t(context: context),
|
sortMode.label.t(context: context),
|
||||||
style: context.textTheme.titleSmall?.copyWith(
|
style: context.textTheme.labelLarge?.copyWith(
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
color: albumSortOption == sortMode
|
color: albumSortOption == sortMode
|
||||||
? context.colorScheme.onPrimary
|
? context.colorScheme.onPrimary
|
||||||
: context.colorScheme.onSurface.withAlpha(185),
|
: context.colorScheme.onSurface.withAlpha(185),
|
||||||
@@ -344,15 +344,12 @@ class _SortButtonState extends ConsumerState<_SortButton> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 5),
|
padding: const EdgeInsets.only(right: 5),
|
||||||
child: albumSortIsReverse
|
child: albumSortIsReverse
|
||||||
? const Icon(Icons.keyboard_arrow_down)
|
? Icon(Icons.keyboard_arrow_down, color: context.colorScheme.onSurface)
|
||||||
: const Icon(Icons.keyboard_arrow_up_rounded),
|
: Icon(Icons.keyboard_arrow_up_rounded, color: context.colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
albumSortOption.label.t(context: context),
|
albumSortOption.label.t(context: context),
|
||||||
style: context.textTheme.bodyLarge?.copyWith(
|
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurface.withAlpha(225)),
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
color: context.colorScheme.onSurface.withAlpha(225),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
isSorting
|
isSorting
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
@@ -542,7 +539,11 @@ class _QuickSortAndViewMode extends StatelessWidget {
|
|||||||
initialIsReverse: currentIsReverse,
|
initialIsReverse: currentIsReverse,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(isGrid ? Icons.view_list_outlined : Icons.grid_view_outlined, size: 24),
|
icon: Icon(
|
||||||
|
isGrid ? Icons.view_list_outlined : Icons.grid_view_outlined,
|
||||||
|
size: 24,
|
||||||
|
color: context.colorScheme.onSurface,
|
||||||
|
),
|
||||||
onPressed: onToggleViewMode,
|
onPressed: onToggleViewMode,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -662,6 +663,8 @@ class _GridAlbumCard extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final albumThumbnailAsset = ref.read(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? "");
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => onAlbumSelected(album),
|
onTap: () => onAlbumSelected(album),
|
||||||
child: Card(
|
child: Card(
|
||||||
@@ -680,12 +683,22 @@ class _GridAlbumCard extends ConsumerWidget {
|
|||||||
borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
|
borderRadius: const BorderRadius.vertical(top: Radius.circular(15)),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
child: album.thumbnailAssetId != null
|
child: FutureBuilder(
|
||||||
? Thumbnail.remote(remoteId: album.thumbnailAssetId!)
|
future: albumThumbnailAsset,
|
||||||
: Container(
|
builder: (context, snapshot) {
|
||||||
color: context.colorScheme.surfaceContainerHighest,
|
if (snapshot.hasData && snapshot.data != null) {
|
||||||
child: const Icon(Icons.photo_album_rounded, size: 40, color: Colors.grey),
|
return Thumbnail.remote(
|
||||||
),
|
remoteId: album.thumbnailAssetId!,
|
||||||
|
thumbhash: snapshot.data!.thumbHash ?? "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
color: context.colorScheme.surfaceContainerHighest,
|
||||||
|
child: const Icon(Icons.photo_album_rounded, size: 40, color: Colors.grey),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
import 'package:immich_mobile/domain/models/album/album.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/theme_extensions.dart';
|
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/pages/common/large_leading_tile.dart';
|
import 'package:immich_mobile/pages/common/large_leading_tile.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/infrastructure/asset.provider.dart';
|
||||||
|
|
||||||
class AlbumTile extends StatelessWidget {
|
class AlbumTile extends ConsumerWidget {
|
||||||
const AlbumTile({super.key, required this.album, required this.isOwner, this.onAlbumSelected});
|
const AlbumTile({super.key, required this.album, required this.isOwner, this.onAlbumSelected});
|
||||||
|
|
||||||
final RemoteAlbum album;
|
final RemoteAlbum album;
|
||||||
@@ -14,7 +16,9 @@ class AlbumTile extends StatelessWidget {
|
|||||||
final Function(RemoteAlbum)? onAlbumSelected;
|
final Function(RemoteAlbum)? onAlbumSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final albumThumbnailAsset = ref.read(assetServiceProvider).getRemoteAsset(album.thumbnailAssetId ?? "");
|
||||||
|
|
||||||
return LargeLeadingTile(
|
return LargeLeadingTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
album.name,
|
album.name,
|
||||||
@@ -29,23 +33,35 @@ class AlbumTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onTap: () => onAlbumSelected?.call(album),
|
onTap: () => onAlbumSelected?.call(album),
|
||||||
leadingPadding: const EdgeInsets.only(right: 16),
|
leadingPadding: const EdgeInsets.only(right: 16),
|
||||||
leading: album.thumbnailAssetId != null
|
leading: FutureBuilder(
|
||||||
? ClipRRect(
|
future: albumThumbnailAsset,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
builder: (context, snapshot) {
|
||||||
child: SizedBox(width: 80, height: 80, child: Thumbnail.remote(remoteId: album.thumbnailAssetId!)),
|
return snapshot.hasData && snapshot.data != null
|
||||||
)
|
? ClipRRect(
|
||||||
: SizedBox(
|
borderRadius: const BorderRadius.all(Radius.circular(15)),
|
||||||
width: 80,
|
child: SizedBox(
|
||||||
height: 80,
|
width: 80,
|
||||||
child: Container(
|
height: 80,
|
||||||
decoration: BoxDecoration(
|
child: Thumbnail.remote(
|
||||||
color: context.colorScheme.surfaceContainer,
|
remoteId: album.thumbnailAssetId!,
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
thumbhash: snapshot.data!.thumbHash ?? "",
|
||||||
border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1),
|
),
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey),
|
)
|
||||||
),
|
: SizedBox(
|
||||||
),
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: context.colorScheme.surfaceContainer,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(16)),
|
||||||
|
border: Border.all(color: context.colorScheme.outline.withAlpha(50), width: 1),
|
||||||
|
),
|
||||||
|
child: const Icon(Icons.photo_album_rounded, size: 24, color: Colors.grey),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -527,7 +527,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
|
|
||||||
void _onScaleStateChanged(PhotoViewScaleState scaleState) {
|
void _onScaleStateChanged(PhotoViewScaleState scaleState) {
|
||||||
if (scaleState != PhotoViewScaleState.initial) {
|
if (scaleState != PhotoViewScaleState.initial) {
|
||||||
ref.read(assetViewerProvider.notifier).setControls(false);
|
if (!dragInProgress) {
|
||||||
|
ref.read(assetViewerProvider.notifier).setControls(false);
|
||||||
|
}
|
||||||
ref.read(videoPlayerControlsProvider.notifier).pause();
|
ref.read(videoPlayerControlsProvider.notifier).pause();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ 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/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/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.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/asset_viewer/asset_viewer.state.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||||
@@ -164,11 +165,8 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
if (albums.isNotEmpty)
|
if (albums.isNotEmpty)
|
||||||
SheetTile(
|
SheetTile(
|
||||||
title: 'appears_in'.t(context: context).toUpperCase(),
|
title: 'appears_in'.t(context: context),
|
||||||
titleStyle: context.textTheme.labelMedium?.copyWith(
|
titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 24),
|
padding: const EdgeInsets.only(left: 24),
|
||||||
@@ -224,9 +222,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
color: context.textTheme.labelLarge?.color,
|
color: context.textTheme.labelLarge?.color,
|
||||||
),
|
),
|
||||||
subtitle: _getFileInfo(asset, exifInfo),
|
subtitle: _getFileInfo(asset, exifInfo),
|
||||||
subtitleStyle: context.textTheme.labelMedium?.copyWith(
|
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -241,9 +237,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
color: context.textTheme.labelLarge?.color,
|
color: context.textTheme.labelLarge?.color,
|
||||||
),
|
),
|
||||||
subtitle: _getFileInfo(asset, exifInfo),
|
subtitle: _getFileInfo(asset, exifInfo),
|
||||||
subtitleStyle: context.textTheme.labelMedium?.copyWith(
|
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,11 +256,8 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
const SheetLocationDetails(),
|
const SheetLocationDetails(),
|
||||||
// Details header
|
// Details header
|
||||||
SheetTile(
|
SheetTile(
|
||||||
title: 'details'.t(context: context).toUpperCase(),
|
title: 'details'.t(context: context),
|
||||||
titleStyle: context.textTheme.labelMedium?.copyWith(
|
titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
// File info
|
// File info
|
||||||
buildFileInfoTile(),
|
buildFileInfoTile(),
|
||||||
@@ -278,9 +269,7 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
titleStyle: context.textTheme.labelLarge,
|
titleStyle: context.textTheme.labelLarge,
|
||||||
leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
leading: Icon(Icons.camera_alt_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||||
subtitle: _getCameraInfoSubtitle(exifInfo),
|
subtitle: _getCameraInfoSubtitle(exifInfo),
|
||||||
subtitleStyle: context.textTheme.labelMedium?.copyWith(
|
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// Lens info
|
// Lens info
|
||||||
@@ -291,15 +280,13 @@ class _AssetDetailBottomSheet extends ConsumerWidget {
|
|||||||
titleStyle: context.textTheme.labelLarge,
|
titleStyle: context.textTheme.labelLarge,
|
||||||
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
leading: Icon(Icons.camera_outlined, size: 24, color: context.textTheme.labelLarge?.color),
|
||||||
subtitle: _getLensInfoSubtitle(exifInfo),
|
subtitle: _getLensInfoSubtitle(exifInfo),
|
||||||
subtitleStyle: context.textTheme.labelMedium?.copyWith(
|
subtitleStyle: context.textTheme.bodyMedium?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
// Appears in (Albums)
|
// Appears in (Albums)
|
||||||
Padding(padding: const EdgeInsets.only(top: 16.0), child: _buildAppearsInList(ref, context)),
|
Padding(padding: const EdgeInsets.only(top: 16.0), child: _buildAppearsInList(ref, context)),
|
||||||
// padding at the bottom to avoid cut-off
|
// padding at the bottom to avoid cut-off
|
||||||
const SizedBox(height: 30),
|
const SizedBox(height: 60),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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/domain/models/exif.model.dart';
|
import 'package:immich_mobile/domain/models/exif.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/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/asset_viewer/sheet_tile.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||||
@@ -64,11 +65,10 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
|||||||
final hasCoordinates = exifInfo?.hasCoordinates ?? false;
|
final hasCoordinates = exifInfo?.hasCoordinates ?? false;
|
||||||
|
|
||||||
// Guard local assets
|
// Guard local assets
|
||||||
if (asset != null && asset is LocalAsset && asset.hasRemote) {
|
if (asset is! RemoteAsset) {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
final remoteAsset = asset as RemoteAsset;
|
|
||||||
final locationName = _getLocationName(exifInfo);
|
final locationName = _getLocationName(exifInfo);
|
||||||
final coordinates = "${exifInfo?.latitude?.toStringAsFixed(4)}, ${exifInfo?.longitude?.toStringAsFixed(4)}";
|
final coordinates = "${exifInfo?.latitude?.toStringAsFixed(4)}, ${exifInfo?.longitude?.toStringAsFixed(4)}";
|
||||||
|
|
||||||
@@ -78,11 +78,8 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SheetTile(
|
SheetTile(
|
||||||
title: 'location'.t(context: context).toUpperCase(),
|
title: 'location'.t(context: context),
|
||||||
titleStyle: context.textTheme.labelMedium?.copyWith(
|
titleStyle: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
trailing: hasCoordinates ? const Icon(Icons.edit_location_alt, size: 20) : null,
|
trailing: hasCoordinates ? const Icon(Icons.edit_location_alt, size: 20) : null,
|
||||||
onTap: editLocation,
|
onTap: editLocation,
|
||||||
),
|
),
|
||||||
@@ -94,8 +91,8 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
|||||||
children: [
|
children: [
|
||||||
ExifMap(
|
ExifMap(
|
||||||
exifInfo: exifInfo!,
|
exifInfo: exifInfo!,
|
||||||
markerId: remoteAsset.id,
|
markerId: asset.id,
|
||||||
markerAssetThumbhash: remoteAsset.thumbHash,
|
markerAssetThumbhash: asset.thumbHash,
|
||||||
onMapCreated: _onMapCreated,
|
onMapCreated: _onMapCreated,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -106,9 +103,7 @@ class _SheetLocationDetailsState extends ConsumerState<SheetLocationDetails> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
coordinates,
|
coordinates,
|
||||||
style: context.textTheme.labelMedium?.copyWith(
|
style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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/person.model.dart';
|
import 'package:immich_mobile/domain/models/person.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/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/people/person_edit_name_modal.widget.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||||
@@ -53,11 +54,8 @@ class _SheetPeopleDetailsState extends ConsumerState<SheetPeopleDetails> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16),
|
padding: const EdgeInsets.only(left: 16, top: 16, bottom: 16),
|
||||||
child: Text(
|
child: Text(
|
||||||
"people".t(context: context).toUpperCase(),
|
"people".t(context: context),
|
||||||
style: context.textTheme.labelMedium?.copyWith(
|
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceSecondary),
|
||||||
color: context.textTheme.labelMedium?.color?.withAlpha(200),
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class NativeVideoViewer extends HookConsumerWidget {
|
|||||||
try {
|
try {
|
||||||
if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) {
|
if (videoAsset.hasLocal && videoAsset.livePhotoVideoId == null) {
|
||||||
final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!;
|
final id = videoAsset is LocalAsset ? videoAsset.id : (videoAsset as RemoteAsset).localId!;
|
||||||
final file = await const StorageRepository().getFileForAsset(id);
|
final file = await StorageRepository().getFileForAsset(id);
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
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/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/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
@@ -57,17 +56,13 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final enqueueCount = ref.watch(driftBackupProvider.select((state) => state.enqueueCount));
|
|
||||||
|
|
||||||
final enqueueTotalCount = ref.watch(driftBackupProvider.select((state) => state.enqueueTotalCount));
|
|
||||||
|
|
||||||
final isCanceling = ref.watch(driftBackupProvider.select((state) => state.isCanceling));
|
|
||||||
|
|
||||||
final uploadTasks = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
final uploadTasks = ref.watch(driftBackupProvider.select((state) => state.uploadItems));
|
||||||
|
|
||||||
final isSyncing = ref.watch(driftBackupProvider.select((state) => state.isSyncing));
|
final isSyncing = ref.watch(driftBackupProvider.select((state) => state.isSyncing));
|
||||||
|
|
||||||
final isProcessing = uploadTasks.isNotEmpty || isSyncing;
|
final iCloudProgress = ref.watch(driftBackupProvider.select((state) => state.iCloudDownloadProgress));
|
||||||
|
|
||||||
|
final isProcessing = uploadTasks.isNotEmpty || isSyncing || iCloudProgress.isNotEmpty;
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: _animationController,
|
animation: _animationController,
|
||||||
@@ -115,7 +110,7 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
borderRadius: const BorderRadius.all(Radius.circular(20.5)),
|
||||||
onTap: () => isCanceling ? null : _onToggle(!_isEnabled),
|
onTap: () => _onToggle(!_isEnabled),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
@@ -154,35 +149,10 @@ class BackupToggleButtonState extends ConsumerState<BackupToggleButton> with Sin
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (enqueueCount != enqueueTotalCount)
|
|
||||||
Text(
|
|
||||||
"queue_status".t(
|
|
||||||
context: context,
|
|
||||||
args: {'count': enqueueCount.toString(), 'total': enqueueTotalCount.toString()},
|
|
||||||
),
|
|
||||||
style: context.textTheme.labelLarge?.copyWith(
|
|
||||||
color: context.colorScheme.onSurfaceSecondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isCanceling)
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Text("canceling".t(), style: context.textTheme.labelLarge),
|
|
||||||
const SizedBox(width: 4),
|
|
||||||
SizedBox(
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
strokeWidth: 2,
|
|
||||||
backgroundColor: context.colorScheme.onSurface.withValues(alpha: 0.2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Switch.adaptive(value: _isEnabled, onChanged: (value) => isCanceling ? null : _onToggle(value)),
|
Switch.adaptive(value: _isEnabled, onChanged: (value) => _onToggle(value)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user