Compare commits

..

1 Commits

Author SHA1 Message Date
bwees 9ed3bbf12f fix: deep links when using the beta timeline 2025-07-23 16:31:45 -05:00
26 changed files with 264 additions and 131 deletions
+3 -3
View File
@@ -1,12 +1,12 @@
{
"name": "@immich/cli",
"version": "2.2.73",
"version": "2.2.72",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/cli",
"version": "2.2.73",
"version": "2.2.72",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.136.0",
"version": "1.135.3",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@immich/cli",
"version": "2.2.73",
"version": "2.2.72",
"description": "Command Line Interface (CLI) for Immich",
"type": "module",
"exports": "./dist/index.js",
-4
View File
@@ -1,8 +1,4 @@
[
{
"label": "v1.136.0",
"url": "https://v1.136.0.archive.immich.app"
},
{
"label": "v1.135.3",
"url": "https://v1.135.3.archive.immich.app"
+4 -4
View File
@@ -1,12 +1,12 @@
{
"name": "immich-e2e",
"version": "1.136.0",
"version": "1.135.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-e2e",
"version": "1.136.0",
"version": "1.135.3",
"license": "GNU Affero General Public License version 3",
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
@@ -46,7 +46,7 @@
},
"../cli": {
"name": "@immich/cli",
"version": "2.2.73",
"version": "2.2.72",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
@@ -95,7 +95,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.136.0",
"version": "1.135.3",
"dev": true,
"license": "GNU Affero General Public License version 3",
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.136.0",
"version": "1.135.3",
"description": "",
"main": "index.js",
"type": "module",
+2 -2
View File
@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 205,
"android.injected.version.name" => "1.136.0",
"android.injected.version.code" => 204,
"android.injected.version.name" => "1.135.3",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
+1 -1
View File
@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj",
)
increment_version_number(
version_number: "1.136.0"
version_number: "1.135.3"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,
@@ -24,6 +24,10 @@ class AssetService {
: _remoteAssetRepository.watchAsset(id);
}
Future<RemoteAsset?> getRemoteAsset(String id) async {
return await _remoteAssetRepository.get(id);
}
Future<List<RemoteAsset>> getStack(RemoteAsset asset) async {
if (asset.stackId == null) {
return [];
@@ -13,6 +13,10 @@ class DriftMemoryService {
return _repository.getAll(ownerId);
}
Future<DriftMemory?> get(String memoryId) {
return _repository.get(memoryId);
}
Future<int> getCount() {
return _repository.getCount();
}
@@ -22,6 +22,10 @@ class RemoteAlbumService {
return _repository.getAll();
}
Future<RemoteAlbum?> get(String albumId) {
return _repository.get(albumId);
}
List<RemoteAlbum> sortAlbums(
List<RemoteAlbum> albums,
RemoteAlbumSortMode sortMode, {
@@ -59,6 +59,44 @@ class DriftMemoryRepository extends DriftDatabaseRepository {
return memoriesMap.values.toList();
}
Future<DriftMemory?> get(String memoryId) async {
final query = _db.select(_db.memoryEntity).join([
leftOuterJoin(
_db.memoryAssetEntity,
_db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id),
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) &
_db.remoteAssetEntity.deletedAt.isNull() &
_db.remoteAssetEntity.visibility
.equalsValue(AssetVisibility.timeline),
),
])
..where(_db.memoryEntity.id.equals(memoryId))
..where(_db.memoryEntity.deletedAt.isNull())
..orderBy([
OrderingTerm.desc(_db.memoryEntity.memoryAt),
OrderingTerm.asc(_db.remoteAssetEntity.createdAt),
]);
final rows = await query.get();
if (rows.isEmpty) {
return null;
}
final memory = rows.first.readTable(_db.memoryEntity);
final assets = <RemoteAsset>[];
for (final row in rows) {
final asset = row.readTable(_db.remoteAssetEntity);
assets.add(asset.toDto());
}
return memory.toDto().copyWith(assets: assets);
}
Future<int> getCount() {
return _db.managers.memoryEntity.count();
}
@@ -68,6 +68,41 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
.get();
}
Future<RemoteAlbum> get(String albumId) {
final assetCount = _db.remoteAlbumAssetEntity.assetId.count();
final query = _db.remoteAlbumEntity.select().join([
leftOuterJoin(
_db.remoteAlbumAssetEntity,
_db.remoteAlbumAssetEntity.albumId.equalsExp(_db.remoteAlbumEntity.id),
useColumns: false,
),
leftOuterJoin(
_db.remoteAssetEntity,
_db.remoteAssetEntity.id.equalsExp(_db.remoteAlbumAssetEntity.assetId),
useColumns: false,
),
leftOuterJoin(
_db.userEntity,
_db.userEntity.id.equalsExp(_db.remoteAlbumEntity.ownerId),
useColumns: false,
),
])
..where(_db.remoteAlbumEntity.id.equals(albumId))
..addColumns([assetCount])
..addColumns([_db.userEntity.name])
..groupBy([_db.remoteAlbumEntity.id]);
return query
.map(
(row) => row.readTable(_db.remoteAlbumEntity).toDto(
assetCount: row.read(assetCount) ?? 0,
ownerName: row.read(_db.userEntity.name)!,
),
)
.getSingle();
}
Future<void> create(
RemoteAlbum album,
List<String> assetIds,
@@ -31,6 +31,25 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
return query.map((row) => row.toDto()).get();
}
Future<RemoteAsset?> get(String id) {
final query = _db.remoteAssetEntity.select().addColumns([
_db.localAssetEntity.id,
]).join([
leftOuterJoin(
_db.localAssetEntity,
_db.remoteAssetEntity.checksum.equalsExp(_db.localAssetEntity.checksum),
useColumns: false,
),
])
..where(_db.remoteAssetEntity.id.equals(id))
..limit(1);
return query.map((row) {
final asset = row.readTable(_db.remoteAssetEntity).toDto();
return asset.copyWith(localId: row.read(_db.localAssetEntity.id));
}).getSingleOrNull();
}
Stream<RemoteAsset?> watchAsset(String id) {
final query = _db.remoteAssetEntity.select().addColumns([
_db.localAssetEntity.id,
+107 -35
View File
@@ -1,6 +1,18 @@
import 'package:auto_route/auto_route.dart';
import 'package:immich_mobile/domain/services/asset.service.dart'
as beta_asset_service;
import 'package:immich_mobile/domain/services/memory.service.dart';
import 'package:immich_mobile/domain/services/remote_album.service.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/entities/store.entity.dart';
import 'package:immich_mobile/providers/album/current_album.provider.dart';
import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart';
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart'
as beta_asset_provider;
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/asset.service.dart';
@@ -15,24 +27,56 @@ final deepLinkServiceProvider = Provider(
ref.watch(albumServiceProvider),
ref.watch(currentAssetProvider.notifier),
ref.watch(currentAlbumProvider.notifier),
// Below is used for beta timeline
ref.watch(timelineFactoryProvider),
ref.watch(beta_asset_provider.assetServiceProvider),
ref.watch(currentRemoteAlbumProvider.notifier),
ref.watch(remoteAlbumServiceProvider),
ref.watch(driftMemoryServiceProvider),
),
);
class DeepLinkService {
/// TODO: Remove this when beta is default
final MemoryService _memoryService;
final AssetService _assetService;
final AlbumService _albumService;
final CurrentAsset _currentAsset;
final CurrentAlbum _currentAlbum;
/// Used for beta timeline
final TimelineFactory _betaTimelineFactory;
final beta_asset_service.AssetService _betaAssetService;
final CurrentAlbumNotifier _betaCurrentAlbumNotifier;
final RemoteAlbumService _betaRemoteAlbumService;
final DriftMemoryService _betaMemoryServiceProvider;
const DeepLinkService(
this._memoryService,
this._assetService,
this._albumService,
this._currentAsset,
this._currentAlbum,
this._betaTimelineFactory,
this._betaAssetService,
this._betaCurrentAlbumNotifier,
this._betaRemoteAlbumService,
this._betaMemoryServiceProvider,
);
DeepLink _handleColdStart(PageRouteInfo<dynamic> route, bool isColdStart) {
return DeepLink([
// we need something to segue back to if the app was cold started
// TODO: use MainTimelineRoute this when beta is default
if (isColdStart)
(Store.isBetaTimelineEnabled)
? const MainTimelineRoute()
: const PhotosRoute(),
route,
]);
}
Future<DeepLink> handleScheme(PlatformDeepLink link, bool isColdStart) async {
// get everything after the scheme, since Uri cannot parse path
final intent = link.uri.host;
@@ -54,11 +98,7 @@ class DeepLinkService {
return DeepLink.none;
}
return DeepLink([
// we need something to segue back to if the app was cold started
if (isColdStart) const PhotosRoute(),
deepLinkRoute,
]);
return _handleColdStart(deepLinkRoute, isColdStart);
}
Future<DeepLink> handleMyImmichApp(
@@ -87,49 +127,81 @@ class DeepLinkService {
return DeepLink.none;
}
return DeepLink([
// we need something to segue back to if the app was cold started
if (isColdStart) const PhotosRoute(),
deepLinkRoute,
]);
return _handleColdStart(deepLinkRoute, isColdStart);
}
Future<PageRouteInfo?> _buildMemoryDeepLink(String memoryId) async {
final memory = await _memoryService.getMemoryById(memoryId);
if (Store.isBetaTimelineEnabled) {
final memory = await _betaMemoryServiceProvider.get(memoryId);
if (memory == null) {
return null;
if (memory == null) {
return null;
}
return DriftMemoryRoute(memories: [memory], memoryIndex: 0);
} else {
// TODO: Remove this when beta is default
final memory = await _memoryService.getMemoryById(memoryId);
if (memory == null) {
return null;
}
return MemoryRoute(memories: [memory], memoryIndex: 0);
}
return MemoryRoute(memories: [memory], memoryIndex: 0);
}
Future<PageRouteInfo?> _buildAssetDeepLink(String assetId) async {
final asset = await _assetService.getAssetByRemoteId(assetId);
if (asset == null) {
return null;
if (Store.isBetaTimelineEnabled) {
final asset = await _betaAssetService.getRemoteAsset(assetId);
if (asset == null) {
return null;
}
return AssetViewerRoute(
initialIndex: 0,
timelineService: _betaTimelineFactory.fromAssets([asset]),
);
} else {
// TODO: Remove this when beta is default
final asset = await _assetService.getAssetByRemoteId(assetId);
if (asset == null) {
return null;
}
_currentAsset.set(asset);
final renderList =
await RenderList.fromAssets([asset], GroupAssetsBy.auto);
return GalleryViewerRoute(
renderList: renderList,
initialIndex: 0,
heroOffset: 0,
showStack: true,
);
}
_currentAsset.set(asset);
final renderList = await RenderList.fromAssets([asset], GroupAssetsBy.auto);
return GalleryViewerRoute(
renderList: renderList,
initialIndex: 0,
heroOffset: 0,
showStack: true,
);
}
Future<PageRouteInfo?> _buildAlbumDeepLink(String albumId) async {
final album = await _albumService.getAlbumByRemoteId(albumId);
if (Store.isBetaTimelineEnabled) {
final album = await _betaRemoteAlbumService.get(albumId);
if (album == null) {
return null;
if (album == null) {
return null;
}
_betaCurrentAlbumNotifier.setAlbum(album);
return RemoteAlbumRoute(album: album);
} else {
// TODO: Remove this when beta is default
final album = await _albumService.getAlbumByRemoteId(albumId);
if (album == null) {
return null;
}
_currentAlbum.set(album);
return AlbumViewerRoute(albumId: album.id);
}
_currentAlbum.set(album);
return AlbumViewerRoute(albumId: album.id);
}
}
+1 -1
View File
@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.136.0
- API version: 1.135.3
- Generator version: 7.8.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
+1 -1
View File
@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: 'none'
version: 1.136.0+205
version: 1.135.3+204
environment:
sdk: '>=3.3.0 <4.0.0'
+1 -1
View File
@@ -8663,7 +8663,7 @@
"info": {
"title": "Immich",
"description": "Immich API",
"version": "1.136.0",
"version": "1.135.3",
"contact": {}
},
"tags": [],
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "@immich/sdk",
"version": "1.136.0",
"version": "1.135.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@immich/sdk",
"version": "1.136.0",
"version": "1.135.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@immich/sdk",
"version": "1.136.0",
"version": "1.135.3",
"description": "Auto-generated TypeScript SDK for the Immich API",
"type": "module",
"main": "./build/index.js",
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* Immich
* 1.136.0
* 1.135.3
* DO NOT MODIFY - This file has been generated using oazapfts.
* See https://www.npmjs.com/package/oazapfts
*/
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "immich",
"version": "1.136.0",
"version": "1.135.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich",
"version": "1.136.0",
"version": "1.135.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@nestjs/bullmq": "^11.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich",
"version": "1.136.0",
"version": "1.135.3",
"description": "",
"author": "",
"private": true,
@@ -1,10 +1,22 @@
// this file used to try to reset the `vchordrq.prewarm_dim;` parameter
// that ends up being a problem on pg 15 + since the extension is not installed.
import { Kysely, sql } from 'kysely';
import { LoggingRepository } from 'src/repositories/logging.repository';
export async function up(): Promise<void> {
// noop
const logger = LoggingRepository.create('Migrations');
export async function up(db: Kysely<any>): Promise<void> {
const { rows } = await sql<{ db: string }>`SELECT current_database() as db;`.execute(db);
const databaseName = rows[0].db;
try {
await sql.raw(`ALTER DATABASE "${databaseName}" RESET vchordrq.prewarm_dim;`).execute(db);
} catch {
logger.warn('Failed to reset vchordrq.prewarm_dim, skipping');
}
}
export async function down(): Promise<void> {
// noop
export async function down(db: Kysely<any>): Promise<void> {
const { rows } = await sql<{ db: string }>`SELECT current_database() as db;`.execute(db);
const databaseName = rows[0].db;
await sql
.raw(`ALTER DATABASE "${databaseName}" SET vchordrq.prewarm_dim = '512,640,768,1024,1152,1536';`)
.execute(db);
}
+3 -63
View File
@@ -1,12 +1,12 @@
{
"name": "immich-web",
"version": "1.136.0",
"version": "1.135.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "immich-web",
"version": "1.136.0",
"version": "1.135.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8",
@@ -94,7 +94,7 @@
},
"../open-api/typescript-sdk": {
"name": "@immich/sdk",
"version": "1.136.0",
"version": "1.135.3",
"license": "GNU Affero General Public License version 3",
"dependencies": {
"@oazapfts/runtime": "^1.0.2"
@@ -2512,66 +2512,6 @@
"node": ">=14.0.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.4.3",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.0.2",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.4.3",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.0.2",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "0.2.11",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.4.3",
"@emnapi/runtime": "^1.4.3",
"@tybys/wasm-util": "^0.9.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.9.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.0",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.11",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.11.tgz",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "immich-web",
"version": "1.136.0",
"version": "1.135.3",
"license": "GNU Affero General Public License version 3",
"type": "module",
"scripts": {
+9
View File
@@ -21,6 +21,11 @@
html {
height: 100%;
width: 100%;
background-color: rgb(255, 255, 255);
}
html.dark {
background-color: rgb(10, 10, 10);
}
body,
@@ -29,6 +34,10 @@
padding: 0;
}
body {
transition: background-color 0.15s ease;
}
@keyframes delayedVisibility {
to {
visibility: visible;