Compare commits
12 Commits
feat/serve
...
fix/respon
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
277eb5b1aa | ||
|
|
7d8cd05bc2 | ||
|
|
30a378c580 | ||
|
|
8a3684c127 | ||
|
|
61e5c6349c | ||
|
|
3bcb4b7af7 | ||
|
|
5116b215a2 | ||
|
|
c5fbbee8f6 | ||
|
|
d73aabc494 | ||
|
|
b62feb726b | ||
|
|
972e9cc039 | ||
|
|
ee49136e97 |
6
.github/workflows/build-mobile.yml
vendored
6
.github/workflows/build-mobile.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1
|
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
@@ -73,7 +73,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Restore Gradle Cache
|
- name: Restore Gradle Cache
|
||||||
id: cache-gradle-restore
|
id: cache-gradle-restore
|
||||||
uses: actions/cache/restore@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
~/.gradle/caches
|
~/.gradle/caches
|
||||||
@@ -130,7 +130,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Save Gradle Cache
|
- name: Save Gradle Cache
|
||||||
id: cache-gradle-save
|
id: cache-gradle-save
|
||||||
uses: actions/cache/save@0400d5f644dc74513175e3cd8d07132dd4860809 # v4
|
uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
|
||||||
if: github.ref == 'refs/heads/main'
|
if: github.ref == 'refs/heads/main'
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
2
.github/workflows/docker.yml
vendored
2
.github/workflows/docker.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1
|
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
server:
|
server:
|
||||||
|
|||||||
2
.github/workflows/docs-build.yml
vendored
2
.github/workflows/docs-build.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1
|
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
docs:
|
docs:
|
||||||
|
|||||||
2
.github/workflows/fix-format.yml
vendored
2
.github/workflows/fix-format.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
2
.github/workflows/merge-translations.yml
vendored
2
.github/workflows/merge-translations.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate_token
|
id: generate_token
|
||||||
if: ${{ inputs.skip != true }}
|
if: ${{ inputs.skip != true }}
|
||||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
4
.github/workflows/prepare-release.yml
vendored
4
.github/workflows/prepare-release.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
@@ -111,7 +111,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Generate a token
|
- name: Generate a token
|
||||||
id: generate-token
|
id: generate-token
|
||||||
uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1
|
uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
app-id: ${{ secrets.PUSH_O_MATIC_APP_ID }}
|
||||||
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
private-key: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||||
|
|||||||
2
.github/workflows/static_analysis.yml
vendored
2
.github/workflows/static_analysis.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1
|
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
mobile:
|
mobile:
|
||||||
|
|||||||
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1
|
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
|
|||||||
2
.github/workflows/weblate-lock.yml
vendored
2
.github/workflows/weblate-lock.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Check what should run
|
- name: Check what should run
|
||||||
id: check
|
id: check
|
||||||
uses: immich-app/devtools/actions/pre-job@24820aa4ef67959b0dcf69a438cccf00d7c7042b # pre-job-action-v1.0.1
|
uses: immich-app/devtools/actions/pre-job@5f91b52dfbb92b8d96ca411ab59c896cd59714ca # pre-job-action-v1.1.0
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
i18n:
|
i18n:
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:c44be5f2871c59362966d71eab4268170eb6f5653c0e6170184e72b38ffdf107
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:41eacbe83eca995561fe43814fd4891e16e39632806253848efaf04d3c8a8b84
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:c44be5f2871c59362966d71eab4268170eb6f5653c0e6170184e72b38ffdf107
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:41eacbe83eca995561fe43814fd4891e16e39632806253848efaf04d3c8a8b84
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ services:
|
|||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:c44be5f2871c59362966d71eab4268170eb6f5653c0e6170184e72b38ffdf107
|
image: ghcr.io/immich-app/postgres:14-vectorchord0.4.3-pgvectors0.2.0@sha256:41eacbe83eca995561fe43814fd4891e16e39632806253848efaf04d3c8a8b84
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
POSTGRES_USER: ${DB_USERNAME}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"add_to_album": "Add to album",
|
"add_to_album": "Add to album",
|
||||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||||
|
"add_to_album_bottom_sheet_some_local_assets": "Some local assets could not be added to album",
|
||||||
"add_to_album_toggle": "Toggle selection for {album}",
|
"add_to_album_toggle": "Toggle selection for {album}",
|
||||||
"add_to_albums": "Add to albums",
|
"add_to_albums": "Add to albums",
|
||||||
"add_to_albums_count": "Add to albums ({count})",
|
"add_to_albums_count": "Add to albums ({count})",
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class BackgroundWorkerBgService extends BackgroundWorkerFlutterApi {
|
|||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
await _backgroundHostApi.showNotification(
|
await _backgroundHostApi.showNotification(
|
||||||
IntlKeys.uploading_media.t(),
|
IntlKeys.uploading_media.t(),
|
||||||
IntlKeys.backup_background_service_in_progress_notification.t(),
|
IntlKeys.backup_background_service_default_notification.t(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class LocalAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
||||||
LocalAsset toDto() => LocalAsset(
|
LocalAsset toDto({String? remoteId}) => LocalAsset(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
checksum: checksum,
|
checksum: checksum,
|
||||||
@@ -32,7 +32,7 @@ extension LocalAssetEntityDataDomainExtension on LocalAssetEntityData {
|
|||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
height: height,
|
height: height,
|
||||||
width: width,
|
width: width,
|
||||||
remoteId: null,
|
remoteId: remoteId,
|
||||||
orientation: orientation,
|
orientation: orientation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class RemoteAssetEntity extends Table with DriftDefaultsMixin, AssetEntityMixin
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
||||||
RemoteAsset toDto() => RemoteAsset(
|
RemoteAsset toDto({String? localId}) => RemoteAsset(
|
||||||
id: id,
|
id: id,
|
||||||
name: name,
|
name: name,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
@@ -64,7 +64,7 @@ extension RemoteAssetEntityDataDomainEx on RemoteAssetEntityData {
|
|||||||
thumbHash: thumbHash,
|
thumbHash: thumbHash,
|
||||||
visibility: visibility,
|
visibility: visibility,
|
||||||
livePhotoVideoId: livePhotoVideoId,
|
livePhotoVideoId: livePhotoVideoId,
|
||||||
localId: null,
|
localId: localId,
|
||||||
stackId: stackId,
|
stackId: stackId,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,10 +148,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)])
|
..orderBy([OrderingTerm.desc(_db.localAssetEntity.createdAt)])
|
||||||
..limit(count, offset: offset);
|
..limit(count, offset: offset);
|
||||||
|
|
||||||
return query.map((row) {
|
return query
|
||||||
final asset = row.readTable(_db.localAssetEntity).toDto();
|
.map((row) => row.readTable(_db.localAssetEntity).toDto(remoteId: row.read(_db.remoteAssetEntity.id)))
|
||||||
return asset.copyWith(remoteId: row.read(_db.remoteAssetEntity.id));
|
.get();
|
||||||
}).get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => (
|
TimelineQuery remoteAlbum(String albumId, GroupAssetsBy groupBy) => (
|
||||||
@@ -165,17 +164,15 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
.count(where: (row) => row.albumId.equals(albumId))
|
.count(where: (row) => row.albumId.equals(albumId))
|
||||||
.map(_generateBuckets)
|
.map(_generateBuckets)
|
||||||
.watch()
|
.watch()
|
||||||
.map((results) => results.isNotEmpty ? results.first : <Bucket>[])
|
.map((results) => results.isNotEmpty ? results.first : const <Bucket>[])
|
||||||
.handleError((error) {
|
.handleError((error) => const <Bucket>[]);
|
||||||
return [];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId)))
|
return (_db.remoteAlbumEntity.select()..where((row) => row.id.equals(albumId)))
|
||||||
.watch()
|
.watch()
|
||||||
.switchMap((albums) {
|
.switchMap((albums) {
|
||||||
if (albums.isEmpty) {
|
if (albums.isEmpty) {
|
||||||
return Stream.value(<Bucket>[]);
|
return Stream.value(const <Bucket>[]);
|
||||||
}
|
}
|
||||||
|
|
||||||
final album = albums.first;
|
final album = albums.first;
|
||||||
@@ -207,10 +204,8 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
return TimeBucket(date: timeline, assetCount: assetCount);
|
return TimeBucket(date: timeline, assetCount: assetCount);
|
||||||
}).watch();
|
}).watch();
|
||||||
})
|
})
|
||||||
.handleError((error) {
|
// If there's an error (e.g., album was deleted), return empty buckets
|
||||||
// If there's an error (e.g., album was deleted), return empty buckets
|
.handleError((error) => const <Bucket>[]);
|
||||||
return <Bucket>[];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<BaseAsset>> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async {
|
Future<List<BaseAsset>> _getRemoteAlbumBucketAssets(String albumId, {required int offset, required int count}) async {
|
||||||
@@ -218,17 +213,22 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
// If album doesn't exist (was deleted), return empty list
|
// If album doesn't exist (was deleted), return empty list
|
||||||
if (albumData == null) {
|
if (albumData == null) {
|
||||||
return <BaseAsset>[];
|
return const <BaseAsset>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
final isAscending = albumData.order == AlbumAssetOrder.asc;
|
final isAscending = albumData.order == AlbumAssetOrder.asc;
|
||||||
|
|
||||||
final query = _db.remoteAssetEntity.select().join([
|
final query = _db.remoteAssetEntity.select().addColumns([_db.localAssetEntity.id]).join([
|
||||||
innerJoin(
|
innerJoin(
|
||||||
_db.remoteAlbumAssetEntity,
|
_db.remoteAlbumAssetEntity,
|
||||||
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
|
_db.remoteAlbumAssetEntity.assetId.equalsExp(_db.remoteAssetEntity.id),
|
||||||
useColumns: false,
|
useColumns: false,
|
||||||
),
|
),
|
||||||
|
leftOuterJoin(
|
||||||
|
_db.localAssetEntity,
|
||||||
|
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
|
||||||
|
useColumns: false,
|
||||||
|
),
|
||||||
])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId));
|
])..where(_db.remoteAssetEntity.deletedAt.isNull() & _db.remoteAlbumAssetEntity.albumId.equals(albumId));
|
||||||
|
|
||||||
if (isAscending) {
|
if (isAscending) {
|
||||||
@@ -239,12 +239,14 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
query.limit(count, offset: offset);
|
query.limit(count, offset: offset);
|
||||||
|
|
||||||
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
|
return query
|
||||||
|
.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id)))
|
||||||
|
.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineQuery fromAssets(List<BaseAsset> assets) => (
|
TimelineQuery fromAssets(List<BaseAsset> assets) => (
|
||||||
bucketSource: () => Stream.value(_generateBuckets(assets.length)),
|
bucketSource: () => Stream.value(_generateBuckets(assets.length)),
|
||||||
assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList()),
|
assetSource: (offset, count) => Future.value(assets.skip(offset).take(count).toList(growable: false)),
|
||||||
);
|
);
|
||||||
|
|
||||||
TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
|
TimelineQuery remote(String ownerId, GroupAssetsBy groupBy) => _remoteQueryBuilder(
|
||||||
@@ -486,6 +488,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
|
return query.map((row) => row.readTable(_db.remoteAssetEntity).toDto()).get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
TimelineQuery _remoteQueryBuilder({
|
TimelineQuery _remoteQueryBuilder({
|
||||||
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
|
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
|
||||||
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
GroupAssetsBy groupBy = GroupAssetsBy.day,
|
||||||
@@ -523,6 +526,7 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
}).watch();
|
}).watch();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
Future<List<BaseAsset>> _getRemoteAssets({
|
Future<List<BaseAsset>> _getRemoteAssets({
|
||||||
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
|
required Expression<bool> Function($RemoteAssetEntityTable row) filter,
|
||||||
required int offset,
|
required int offset,
|
||||||
@@ -543,11 +547,9 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
|
..orderBy([OrderingTerm.desc(_db.remoteAssetEntity.createdAt)])
|
||||||
..limit(count, offset: offset);
|
..limit(count, offset: offset);
|
||||||
|
|
||||||
return query.map((row) {
|
return query
|
||||||
final asset = row.readTable(_db.remoteAssetEntity).toDto();
|
.map((row) => row.readTable(_db.remoteAssetEntity).toDto(localId: row.read(_db.localAssetEntity.id)))
|
||||||
final localId = row.read(_db.localAssetEntity.id);
|
.get();
|
||||||
return asset.copyWith(localId: localId);
|
|
||||||
}).get();
|
|
||||||
} else {
|
} else {
|
||||||
final query = _db.remoteAssetEntity.select()
|
final query = _db.remoteAssetEntity.select()
|
||||||
..where(filter)
|
..where(filter)
|
||||||
@@ -560,12 +562,12 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Bucket> _generateBuckets(int count) {
|
List<Bucket> _generateBuckets(int count) {
|
||||||
final buckets = List.generate(
|
final buckets = List.filled(
|
||||||
(count / kTimelineNoneSegmentSize).floor(),
|
(count / kTimelineNoneSegmentSize).ceil(),
|
||||||
(_) => const Bucket(assetCount: kTimelineNoneSegmentSize),
|
const Bucket(assetCount: kTimelineNoneSegmentSize),
|
||||||
);
|
);
|
||||||
if (count % kTimelineNoneSegmentSize != 0) {
|
if (count % kTimelineNoneSegmentSize != 0) {
|
||||||
buckets.add(Bucket(assetCount: count % kTimelineNoneSegmentSize));
|
buckets[buckets.length - 1] = Bucket(assetCount: count % kTimelineNoneSegmentSize);
|
||||||
}
|
}
|
||||||
return buckets;
|
return buckets;
|
||||||
}
|
}
|
||||||
@@ -590,10 +592,6 @@ extension on String {
|
|||||||
GroupAssetsBy.month => "y-M",
|
GroupAssetsBy.month => "y-M",
|
||||||
GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"),
|
GroupAssetsBy.none => throw ArgumentError("GroupAssetsBy.none is not supported for date formatting"),
|
||||||
};
|
};
|
||||||
try {
|
return DateFormat(format, 'en').parse(this);
|
||||||
return DateFormat(format, 'en').parse(this);
|
|
||||||
} catch (e) {
|
|
||||||
throw FormatException("Invalid date format: $this", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,10 @@ class DriftMapPage extends StatelessWidget {
|
|||||||
onPressed: () => context.pop(),
|
onPressed: () => context.pop(),
|
||||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||||
style: IconButton.styleFrom(
|
style: IconButton.styleFrom(
|
||||||
shape: const CircleBorder(side: BorderSide(width: 1, color: Colors.black26)),
|
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
backgroundColor: Colors.indigo.withValues(alpha: 0.7),
|
backgroundColor: Colors.indigo,
|
||||||
|
shadowColor: Colors.black26,
|
||||||
|
elevation: 4,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:immich_mobile/constants/enums.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/setting.model.dart';
|
import 'package:immich_mobile/domain/models/setting.model.dart';
|
||||||
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
||||||
@@ -62,11 +63,19 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final remoteAssets = selectedAssets.whereType<RemoteAsset>();
|
||||||
final addedCount = await ref
|
final addedCount = await ref
|
||||||
.read(remoteAlbumProvider.notifier)
|
.read(remoteAlbumProvider.notifier)
|
||||||
.addAssets(album.id, selectedAssets.map((e) => (e as RemoteAsset).id).toList());
|
.addAssets(album.id, remoteAssets.map((e) => e.id).toList());
|
||||||
|
|
||||||
if (addedCount != selectedAssets.length) {
|
if (selectedAssets.length != remoteAssets.length) {
|
||||||
|
ImmichToast.show(
|
||||||
|
context: context,
|
||||||
|
msg: 'add_to_album_bottom_sheet_some_local_assets'.t(context: context),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (addedCount != remoteAssets.length) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}),
|
msg: 'add_to_album_bottom_sheet_already_exists'.tr(namedArgs: {"album": album.name}),
|
||||||
@@ -113,10 +122,12 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||||
if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline),
|
if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline),
|
||||||
],
|
],
|
||||||
slivers: [
|
slivers: multiselect.hasRemote
|
||||||
const AddToAlbumHeader(),
|
? [
|
||||||
AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand),
|
const AddToAlbumHeader(),
|
||||||
],
|
AlbumSelector(onAlbumSelected: addAssetsToAlbum, onKeyboardExpanded: onKeyboardExpand),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +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/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
|
||||||
@@ -10,13 +11,14 @@ class MapBottomSheet extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return const BaseBottomSheet(
|
return BaseBottomSheet(
|
||||||
initialChildSize: 0.25,
|
initialChildSize: 0.25,
|
||||||
maxChildSize: 0.9,
|
maxChildSize: 0.9,
|
||||||
shouldCloseOnMinExtent: false,
|
shouldCloseOnMinExtent: false,
|
||||||
resizeOnScroll: false,
|
resizeOnScroll: false,
|
||||||
actions: [],
|
actions: [],
|
||||||
slivers: [SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())],
|
backgroundColor: context.themeData.colorScheme.surface,
|
||||||
|
slivers: [const SliverFillRemaining(hasScrollBody: false, child: _ScopedMapTimeline())],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
@@ -9,8 +10,8 @@ import 'package:immich_mobile/extensions/asyncvalue_extensions.dart';
|
|||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
|
import 'package:immich_mobile/presentation/widgets/bottom_sheet/map_bottom_sheet.widget.dart';
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
|
||||||
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
import 'package:immich_mobile/presentation/widgets/map/map.state.dart';
|
||||||
|
import 'package:immich_mobile/presentation/widgets/map/map_utils.dart';
|
||||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||||
import 'package:immich_mobile/utils/debounce.dart';
|
import 'package:immich_mobile/utils/debounce.dart';
|
||||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||||
@@ -187,6 +188,8 @@ class _Map extends StatelessWidget {
|
|||||||
styleString: style,
|
styleString: style,
|
||||||
onMapCreated: onMapCreated,
|
onMapCreated: onMapCreated,
|
||||||
onStyleLoadedCallback: onMapReady,
|
onStyleLoadedCallback: onMapReady,
|
||||||
|
attributionButtonPosition: AttributionButtonPosition.topRight,
|
||||||
|
attributionButtonMargins: Platform.isIOS ? const Point(40, 12) : const Point(40, 72),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -212,11 +212,14 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
|||||||
if (fallbackSegment != null) {
|
if (fallbackSegment != null) {
|
||||||
// Scroll to the segment with a small offset to show the header
|
// Scroll to the segment with a small offset to show the header
|
||||||
final targetOffset = fallbackSegment.startOffset - 50;
|
final targetOffset = fallbackSegment.startOffset - 50;
|
||||||
_scrollController.animateTo(
|
ref.read(timelineStateProvider.notifier).setScrubbing(true);
|
||||||
targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent),
|
_scrollController
|
||||||
duration: const Duration(milliseconds: 500),
|
.animateTo(
|
||||||
curve: Curves.easeInOut,
|
targetOffset.clamp(0.0, _scrollController.position.maxScrollExtent),
|
||||||
);
|
duration: const Duration(milliseconds: 500),
|
||||||
|
curve: Curves.easeInOut,
|
||||||
|
)
|
||||||
|
.whenComplete(() => ref.read(timelineStateProvider.notifier).setScrubbing(false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
// ignore_for_file: public_member_api_docs, sort_constructors_first
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -12,8 +11,8 @@ import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart
|
|||||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/services/upload.service.dart';
|
import 'package:immich_mobile/services/upload.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class EnqueueStatus {
|
class EnqueueStatus {
|
||||||
final int enqueueCount;
|
final int enqueueCount;
|
||||||
@@ -90,33 +89,6 @@ class DriftUploadStatus {
|
|||||||
networkSpeedAsString.hashCode ^
|
networkSpeedAsString.hashCode ^
|
||||||
isFailed.hashCode;
|
isFailed.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return <String, dynamic>{
|
|
||||||
'taskId': taskId,
|
|
||||||
'filename': filename,
|
|
||||||
'progress': progress,
|
|
||||||
'fileSize': fileSize,
|
|
||||||
'networkSpeedAsString': networkSpeedAsString,
|
|
||||||
'isFailed': isFailed,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
factory DriftUploadStatus.fromMap(Map<String, dynamic> map) {
|
|
||||||
return DriftUploadStatus(
|
|
||||||
taskId: map['taskId'] as String,
|
|
||||||
filename: map['filename'] as String,
|
|
||||||
progress: map['progress'] as double,
|
|
||||||
fileSize: map['fileSize'] as int,
|
|
||||||
networkSpeedAsString: map['networkSpeedAsString'] as String,
|
|
||||||
isFailed: map['isFailed'] != null ? map['isFailed'] as bool : null,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
|
||||||
|
|
||||||
factory DriftUploadStatus.fromJson(String source) =>
|
|
||||||
DriftUploadStatus.fromMap(json.decode(source) as Map<String, dynamic>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class DriftBackupState {
|
class DriftBackupState {
|
||||||
@@ -267,6 +239,7 @@ class DriftBackupNotifier extends StateNotifier<DriftBackupState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state = state.copyWith(uploadItems: {...state.uploadItems, taskId: currentItem.copyWith(isFailed: true)});
|
state = state.copyWith(uploadItems: {...state.uploadItems, taskId: currentItem.copyWith(isFailed: true)});
|
||||||
|
_logger.fine("Upload failed for taskId: $taskId, exception: ${update.exception}");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case TaskStatus.canceled:
|
case TaskStatus.canceled:
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import 'package:immich_mobile/constants/constants.dart';
|
|||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
|
import 'package:immich_mobile/extensions/platform_extensions.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/backup.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/local_asset.repository.dart';
|
||||||
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
import 'package:immich_mobile/infrastructure/repositories/storage.repository.dart';
|
||||||
@@ -19,9 +20,9 @@ import 'package:immich_mobile/providers/infrastructure/storage.provider.dart';
|
|||||||
import 'package:immich_mobile/repositories/upload.repository.dart';
|
import 'package:immich_mobile/repositories/upload.repository.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/debug_print.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:immich_mobile/utils/debug_print.dart';
|
|
||||||
|
|
||||||
final uploadServiceProvider = Provider((ref) {
|
final uploadServiceProvider = Provider((ref) {
|
||||||
final service = UploadService(
|
final service = UploadService(
|
||||||
@@ -205,10 +206,20 @@ class UploadService {
|
|||||||
return _uploadRepository.start();
|
return _uploadRepository.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleTaskStatusUpdate(TaskStatusUpdate update) {
|
void _handleTaskStatusUpdate(TaskStatusUpdate update) async {
|
||||||
switch (update.status) {
|
switch (update.status) {
|
||||||
case TaskStatus.complete:
|
case TaskStatus.complete:
|
||||||
_handleLivePhoto(update);
|
_handleLivePhoto(update);
|
||||||
|
|
||||||
|
if (CurrentPlatform.isIOS) {
|
||||||
|
try {
|
||||||
|
final path = await update.task.filePath();
|
||||||
|
await File(path).delete();
|
||||||
|
} catch (e) {
|
||||||
|
_logger.severe('Error deleting file path for iOS: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -129,19 +129,24 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
|||||||
title: Builder(
|
title: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return Row(
|
return Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Builder(
|
Padding(
|
||||||
builder: (context) {
|
padding: const EdgeInsets.only(top: 3.0),
|
||||||
return Padding(
|
child: SvgPicture.asset(
|
||||||
padding: const EdgeInsets.only(top: 3.0),
|
context.isDarkTheme ? 'assets/immich-logo-inline-dark.svg' : 'assets/immich-logo-inline-light.svg',
|
||||||
child: SvgPicture.asset(
|
height: 40,
|
||||||
context.isDarkTheme
|
),
|
||||||
? 'assets/immich-logo-inline-dark.svg'
|
),
|
||||||
: 'assets/immich-logo-inline-light.svg',
|
const Tooltip(
|
||||||
height: 40,
|
triggerMode: TooltipTriggerMode.tap,
|
||||||
),
|
showDuration: Duration(seconds: 4),
|
||||||
);
|
message:
|
||||||
},
|
"The old timeline is deprecated and will be removed in a future release. Kindly switch to the new timeline under Advanced Settings.",
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: 3.0),
|
||||||
|
child: Icon(Icons.error_rounded, fill: 1, color: Colors.amber, size: 20),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
<div
|
<div
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
class="relative z-0 grid grid-cols-[--spacing(0)_auto] overflow-hidden sidebar:grid-cols-[--spacing(64)_auto]
|
class="relative z-0 grid grid-cols-[--spacing(0)_auto] overflow-hidden sidebar:grid-cols-[--spacing(64)_auto]
|
||||||
{hideNavbar ? 'h-dvh' : 'h-[calc(100dvh-var(--navbar-height))]'}
|
{hideNavbar ? 'h-dvh' : 'h-[calc(100dvh-var(--navbar-height))] max-md:h-[calc(100dvh-var(--navbar-height-md))]'}
|
||||||
{hideNavbar ? 'pt-(--navbar-height)' : ''}
|
{hideNavbar ? 'pt-(--navbar-height)' : ''}
|
||||||
{hideNavbar ? 'max-md:pt-(--navbar-height-md)' : ''}"
|
{hideNavbar ? 'max-md:pt-(--navbar-height-md)' : ''}"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user