Compare commits
1 Commits
fix/bring-
...
fix/no-ann
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
002c82b8fc |
@@ -5,7 +5,7 @@ sidebar_position: 65
|
||||
# One-Click [Cloud Service]
|
||||
|
||||
:::note
|
||||
This version of Immich is provided via cloud service providers' one-click marketplaces. Hosting costs are set by the cloud service providers.
|
||||
This version of Immich is provided via cloud service provider's one-click marketplaces. Hosting costs are set by the cloud service providers.
|
||||
Support for these are provided by the individual cloud service providers.
|
||||
|
||||
**Please report issues to the corresponding [Github Repository][github].**
|
||||
@@ -13,7 +13,7 @@ Support for these are provided by the individual cloud service providers.
|
||||
|
||||
## Installation
|
||||
|
||||
Go to the provider's marketplace and choose Immich, then follow the provided instructions.
|
||||
Simply goto the providers marketplace and choose Immich, then follow the provided instructions.
|
||||
|
||||
## One-Click Immich marketplace providers
|
||||
|
||||
@@ -29,4 +29,4 @@ https://www.vultr.com/marketplace/apps/immich
|
||||
|
||||
For issues, open an issue on the associated [GitHub Repository][github].
|
||||
|
||||
[github]: https://github.com/immich-app/immich/
|
||||
[github]: https://github.com/imagegenius/docker-immich/
|
||||
|
||||
@@ -56,8 +56,6 @@ sealed class BaseAsset {
|
||||
|
||||
// Overridden in subclasses
|
||||
AssetState get storage;
|
||||
String? get localId;
|
||||
String? get remoteId;
|
||||
String get heroTag;
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,12 +2,12 @@ part of 'base_asset.model.dart';
|
||||
|
||||
class LocalAsset extends BaseAsset {
|
||||
final String id;
|
||||
final String? remoteAssetId;
|
||||
final String? remoteId;
|
||||
final int orientation;
|
||||
|
||||
const LocalAsset({
|
||||
required this.id,
|
||||
String? remoteId,
|
||||
this.remoteId,
|
||||
required super.name,
|
||||
super.checksum,
|
||||
required super.type,
|
||||
@@ -19,13 +19,7 @@ class LocalAsset extends BaseAsset {
|
||||
super.isFavorite = false,
|
||||
super.livePhotoVideoId,
|
||||
this.orientation = 0,
|
||||
}) : remoteAssetId = remoteId;
|
||||
|
||||
@override
|
||||
String? get localId => id;
|
||||
|
||||
@override
|
||||
String? get remoteId => remoteAssetId;
|
||||
});
|
||||
|
||||
@override
|
||||
AssetState get storage => remoteId == null ? AssetState.local : AssetState.merged;
|
||||
|
||||
@@ -5,7 +5,7 @@ enum AssetVisibility { timeline, hidden, archive, locked }
|
||||
// Model for an asset stored in the server
|
||||
class RemoteAsset extends BaseAsset {
|
||||
final String id;
|
||||
final String? localAssetId;
|
||||
final String? localId;
|
||||
final String? thumbHash;
|
||||
final AssetVisibility visibility;
|
||||
final String ownerId;
|
||||
@@ -13,7 +13,7 @@ class RemoteAsset extends BaseAsset {
|
||||
|
||||
const RemoteAsset({
|
||||
required this.id,
|
||||
String? localId,
|
||||
this.localId,
|
||||
required super.name,
|
||||
required this.ownerId,
|
||||
required super.checksum,
|
||||
@@ -28,13 +28,7 @@ class RemoteAsset extends BaseAsset {
|
||||
this.visibility = AssetVisibility.timeline,
|
||||
super.livePhotoVideoId,
|
||||
this.stackId,
|
||||
}) : localAssetId = localId;
|
||||
|
||||
@override
|
||||
String? get localId => localAssetId;
|
||||
|
||||
@override
|
||||
String? get remoteId => id;
|
||||
});
|
||||
|
||||
@override
|
||||
AssetState get storage => localId == null ? AssetState.remote : AssetState.merged;
|
||||
|
||||
@@ -62,7 +62,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
.toDto(
|
||||
assetCount: row.read(assetCount) ?? 0,
|
||||
ownerName: row.read(_db.userEntity.name)!,
|
||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 2,
|
||||
),
|
||||
)
|
||||
.get();
|
||||
@@ -107,7 +107,7 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
.toDto(
|
||||
assetCount: row.read(assetCount) ?? 0,
|
||||
ownerName: row.read(_db.userEntity.name)!,
|
||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 2,
|
||||
),
|
||||
)
|
||||
.getSingleOrNull();
|
||||
@@ -305,9 +305,8 @@ class DriftRemoteAlbumRepository extends DriftDatabaseRepository {
|
||||
.readTable(_db.remoteAlbumEntity)
|
||||
.toDto(
|
||||
ownerName: row.read(_db.userEntity.name)!,
|
||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 0,
|
||||
isShared: row.read(_db.remoteAlbumUserEntity.userId.count(distinct: true))! > 2,
|
||||
);
|
||||
|
||||
return album;
|
||||
}).watchSingleOrNull();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/action.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/widgets/asset_grid/delete_dialog.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
/// This delete action has the following behavior:
|
||||
@@ -23,17 +22,7 @@ class DeleteLocalActionButton extends ConsumerWidget {
|
||||
return;
|
||||
}
|
||||
|
||||
bool? backedUpOnly = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => DeleteLocalOnlyDialog(onDeleteLocal: (_) {}),
|
||||
);
|
||||
|
||||
if (backedUpOnly == null) {
|
||||
// User cancelled the dialog
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await ref.read(actionProvider.notifier).deleteLocal(source, backedUpOnly);
|
||||
final result = await ref.read(actionProvider.notifier).deleteLocal(source);
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
|
||||
if (source == ActionSource.viewer) {
|
||||
|
||||
@@ -257,15 +257,8 @@ class ActionNotifier extends Notifier<void> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<ActionResult> deleteLocal(ActionSource source, bool backedUpOnly) async {
|
||||
final List<String> ids;
|
||||
if (backedUpOnly) {
|
||||
final assets = _getAssets(source);
|
||||
ids = assets.where((asset) => asset.storage == AssetState.merged).map((asset) => asset.localId!).toList();
|
||||
} else {
|
||||
ids = _getLocalIdsForSource(source);
|
||||
}
|
||||
|
||||
Future<ActionResult> deleteLocal(ActionSource source) async {
|
||||
final ids = _getLocalIdsForSource(source);
|
||||
try {
|
||||
final deletedCount = await _service.deleteLocal(ids);
|
||||
return ActionResult(count: deletedCount, success: true);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
@@ -22,26 +21,11 @@ final assetMediaRepositoryProvider = Provider((ref) => AssetMediaRepository(ref.
|
||||
|
||||
class AssetMediaRepository {
|
||||
final AssetApiRepository _assetApiRepository;
|
||||
|
||||
static final Logger _log = Logger("AssetMediaRepository");
|
||||
|
||||
const AssetMediaRepository(this._assetApiRepository);
|
||||
|
||||
Future<List<String>> deleteAll(List<String> ids) async {
|
||||
if (CurrentPlatform.isIOS) {
|
||||
return PhotoManager.editor.deleteWithIds(ids);
|
||||
} else if (CurrentPlatform.isAndroid) {
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
if (androidInfo.version.sdkInt < 30) {
|
||||
return PhotoManager.editor.deleteWithIds(ids);
|
||||
}
|
||||
return PhotoManager.editor.android.moveToTrash(
|
||||
// Only the id is needed
|
||||
ids.map((id) => AssetEntity(id: id, width: 1, height: 1, typeInt: 0)).toList(),
|
||||
);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
Future<List<String>> deleteAll(List<String> ids) => PhotoManager.editor.deleteWithIds(ids);
|
||||
|
||||
Future<asset_entity.Asset?> get(String id) async {
|
||||
final entity = await AssetEntity.fromId(id);
|
||||
|
||||
@@ -22,12 +22,12 @@ class DeleteLocalOnlyDialog extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
void onDeleteBackedUpOnly() {
|
||||
context.pop(true);
|
||||
context.pop();
|
||||
onDeleteLocal(true);
|
||||
}
|
||||
|
||||
void onForceDelete() {
|
||||
context.pop(false);
|
||||
context.pop();
|
||||
onDeleteLocal(false);
|
||||
}
|
||||
|
||||
@@ -36,44 +36,26 @@ class DeleteLocalOnlyDialog extends StatelessWidget {
|
||||
title: const Text("delete_dialog_title").tr(),
|
||||
content: const Text("delete_dialog_alert_local_non_backed_up").tr(),
|
||||
actions: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: FilledButton(
|
||||
onPressed: () => context.pop(),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: context.colorScheme.surfaceDim,
|
||||
foregroundColor: context.primaryColor,
|
||||
),
|
||||
child: const Text("cancel", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: Text(
|
||||
"cancel",
|
||||
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
|
||||
child: FilledButton(
|
||||
onPressed: onDeleteBackedUpOnly,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: context.colorScheme.errorContainer,
|
||||
foregroundColor: context.colorScheme.onErrorContainer,
|
||||
),
|
||||
child: const Text(
|
||||
"delete_local_dialog_ok_backed_up_only",
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onDeleteBackedUpOnly,
|
||||
child: Text(
|
||||
"delete_local_dialog_ok_backed_up_only",
|
||||
style: TextStyle(color: context.colorScheme.tertiary, fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: FilledButton(
|
||||
onPressed: onForceDelete,
|
||||
style: FilledButton.styleFrom(backgroundColor: Colors.red[400], foregroundColor: Colors.white),
|
||||
child: const Text("delete_local_dialog_ok_force", style: TextStyle(fontWeight: FontWeight.bold)).tr(),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: onForceDelete,
|
||||
child: Text(
|
||||
"delete_local_dialog_ok_force",
|
||||
style: TextStyle(color: Colors.red[400], fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -25,7 +25,7 @@ export interface Events {
|
||||
on_person_thumbnail: (personId: string) => void;
|
||||
on_server_version: (serverVersion: ServerVersionResponseDto) => void;
|
||||
on_config_update: () => void;
|
||||
on_new_release: (newRelase: ReleaseEvent) => void;
|
||||
on_new_release: (newRelease: ReleaseEvent) => void;
|
||||
on_session_delete: (sessionId: string) => void;
|
||||
on_notification: (notification: NotificationDto) => void;
|
||||
}
|
||||
|
||||
25
web/src/lib/utils.spec.ts
Normal file
25
web/src/lib/utils.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { getReleaseType } from '$lib/utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe(getReleaseType.name, () => {
|
||||
it('should return "major" for major version changes', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 2, minor: 0, patch: 0 })).toBe('major');
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 3, minor: 2, patch: 1 })).toBe('major');
|
||||
});
|
||||
|
||||
it('should return "minor" for minor version changes', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 1, patch: 0 })).toBe('minor');
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 2, patch: 1 })).toBe('minor');
|
||||
});
|
||||
|
||||
it('should return "patch" for patch version changes', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 0, patch: 1 })).toBe('patch');
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 0, patch: 5 })).toBe('patch');
|
||||
});
|
||||
|
||||
it('should return "none" for matching versions', () => {
|
||||
expect(getReleaseType({ major: 1, minor: 0, patch: 0 }, { major: 1, minor: 0, patch: 0 })).toBe('none');
|
||||
expect(getReleaseType({ major: 1, minor: 2, patch: 3 }, { major: 1, minor: 2, patch: 3 })).toBe('none');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
unlinkOAuthAccount,
|
||||
type MemoryResponseDto,
|
||||
type PersonResponseDto,
|
||||
type ServerVersionResponseDto,
|
||||
type SharedLinkResponseDto,
|
||||
type UserResponseDto,
|
||||
} from '@immich/sdk';
|
||||
@@ -385,3 +386,22 @@ export function createDateFormatter(localeCode: string | undefined): DateFormatt
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const getReleaseType = (
|
||||
current: ServerVersionResponseDto,
|
||||
newVersion: ServerVersionResponseDto,
|
||||
): 'major' | 'minor' | 'patch' | 'none' => {
|
||||
if (current.major !== newVersion.major) {
|
||||
return 'major';
|
||||
}
|
||||
|
||||
if (current.minor !== newVersion.minor) {
|
||||
return 'minor';
|
||||
}
|
||||
|
||||
if (current.patch !== newVersion.patch) {
|
||||
return 'patch';
|
||||
}
|
||||
|
||||
return 'none';
|
||||
};
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
websocketStore,
|
||||
type ReleaseEvent,
|
||||
} from '$lib/stores/websocket';
|
||||
import { copyToClipboard } from '$lib/utils';
|
||||
import { copyToClipboard, getReleaseType } from '$lib/utils';
|
||||
import { isAssetViewerRoute } from '$lib/utils/navigation';
|
||||
import type { ServerVersionResponseDto } from '@immich/sdk';
|
||||
import { modalManager, setTranslations } from '@immich/ui';
|
||||
@@ -85,8 +85,9 @@
|
||||
|
||||
const releaseVersion = semverToName(release.releaseVersion);
|
||||
const serverVersion = semverToName(release.serverVersion);
|
||||
const type = getReleaseType(release.serverVersion, release.releaseVersion);
|
||||
|
||||
if (localStorage.getItem('appVersion') === releaseVersion) {
|
||||
if (type === 'none' || type === 'patch' || localStorage.getItem('appVersion') === releaseVersion) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user