Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f3af54c6c9 |
@@ -11,7 +11,7 @@ run_cmd pnpm --filter immich install
|
|||||||
log "Starting Nest API Server"
|
log "Starting Nest API Server"
|
||||||
log ""
|
log ""
|
||||||
cd "${IMMICH_WORKSPACE}/server" || (
|
cd "${IMMICH_WORKSPACE}/server" || (
|
||||||
log "Immich workspace not found"
|
log "Immich workspace not found"jj
|
||||||
exit 1
|
exit 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.86",
|
"version": "2.2.85",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
|||||||
Vendored
-4
@@ -1,8 +1,4 @@
|
|||||||
[
|
[
|
||||||
{
|
|
||||||
"label": "v1.140.1",
|
|
||||||
"url": "https://v1.140.1.archive.immich.app"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"label": "v1.140.0",
|
"label": "v1.140.0",
|
||||||
"url": "https://v1.140.0.archive.immich.app"
|
"url": "https://v1.140.0.archive.immich.app"
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.140.1",
|
"version": "1.140.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -639,6 +639,8 @@
|
|||||||
"cannot_update_the_description": "Cannot update the description",
|
"cannot_update_the_description": "Cannot update the description",
|
||||||
"cast": "Cast",
|
"cast": "Cast",
|
||||||
"cast_description": "Configure available cast destinations",
|
"cast_description": "Configure available cast destinations",
|
||||||
|
"cellular_data_for_photos": "Cellular data for photos",
|
||||||
|
"cellular_data_for_videos": "Cellular data for videos",
|
||||||
"change_date": "Change date",
|
"change_date": "Change date",
|
||||||
"change_description": "Change description",
|
"change_description": "Change description",
|
||||||
"change_display_order": "Change display order",
|
"change_display_order": "Change display order",
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 3011,
|
"android.injected.version.code" => 3010,
|
||||||
"android.injected.version.name" => "1.140.1",
|
"android.injected.version.name" => "1.140.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
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')
|
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')
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 77;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@@ -507,14 +507,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Copy Pods Resources";
|
name = "[CP] Copy Pods Resources";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||||
@@ -543,14 +539,10 @@
|
|||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
@@ -697,7 +689,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -841,7 +833,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -871,7 +863,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@@ -905,7 +897,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -948,7 +940,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -988,7 +980,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = WidgetExtension/WidgetExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||||
@@ -1027,7 +1019,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1071,7 +1063,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@@ -1112,7 +1104,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 219;
|
CURRENT_PROJECT_VERSION = 218;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.140.0</string>
|
<string>1.139.3</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>219</string>
|
<string>217</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true />
|
<true />
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ platform :ios do
|
|||||||
path: "./Runner.xcodeproj",
|
path: "./Runner.xcodeproj",
|
||||||
)
|
)
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.140.1"
|
version_number: "1.140.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -75,8 +75,8 @@ enum StoreKey<T> {
|
|||||||
betaPromptShown<bool>._(1001),
|
betaPromptShown<bool>._(1001),
|
||||||
betaTimeline<bool>._(1002),
|
betaTimeline<bool>._(1002),
|
||||||
enableBackup<bool>._(1003),
|
enableBackup<bool>._(1003),
|
||||||
useWifiForUploadVideos<bool>._(1004),
|
useCellularForUploadVideos<bool>._(1004),
|
||||||
useWifiForUploadPhotos<bool>._(1005);
|
useCellularForUploadPhotos<bool>._(1005);
|
||||||
|
|
||||||
const StoreKey._(this.id);
|
const StoreKey._(this.id);
|
||||||
final int id;
|
final int id;
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ class DriftMemoryRepository extends DriftDatabaseRepository {
|
|||||||
|
|
||||||
final query =
|
final query =
|
||||||
_db.select(_db.memoryEntity).join([
|
_db.select(_db.memoryEntity).join([
|
||||||
innerJoin(_db.memoryAssetEntity, _db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id)),
|
leftOuterJoin(_db.memoryAssetEntity, _db.memoryAssetEntity.memoryId.equalsExp(_db.memoryEntity.id)),
|
||||||
innerJoin(
|
leftOuterJoin(
|
||||||
_db.remoteAssetEntity,
|
_db.remoteAssetEntity,
|
||||||
_db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) &
|
_db.remoteAssetEntity.id.equalsExp(_db.memoryAssetEntity.assetId) &
|
||||||
_db.remoteAssetEntity.deletedAt.isNull() &
|
_db.remoteAssetEntity.deletedAt.isNull() &
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
import 'package:immich_mobile/domain/models/album/local_album.model.dart';
|
||||||
|
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||||
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/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';
|
||||||
@@ -93,7 +95,11 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
const _BackupCard(),
|
const _BackupCard(),
|
||||||
const _RemainderCard(),
|
const _RemainderCard(),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
const _CellularBackupStatus(),
|
||||||
|
const SizedBox(height: 4),
|
||||||
BackupToggleButton(onStart: () async => await startBackup(), onStop: () async => await stopBackup()),
|
BackupToggleButton(onStart: () async => await startBackup(), onStop: () async => await stopBackup()),
|
||||||
|
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.info_outline_rounded),
|
icon: const Icon(Icons.info_outline_rounded),
|
||||||
onPressed: () => context.pushRoute(const DriftUploadDetailRoute()),
|
onPressed: () => context.pushRoute(const DriftUploadDetailRoute()),
|
||||||
@@ -109,6 +115,64 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _CellularBackupStatus extends ConsumerWidget {
|
||||||
|
const _CellularBackupStatus();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final cellularReqForVideos = Store.watch(StoreKey.useCellularForUploadVideos);
|
||||||
|
final cellularReqForPhotos = Store.watch(StoreKey.useCellularForUploadPhotos);
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () => context.pushRoute(const DriftBackupOptionsRoute()),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
StreamBuilder(
|
||||||
|
stream: cellularReqForVideos,
|
||||||
|
initialData: Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Expanded(
|
||||||
|
child: ListTile(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
leading: Icon(
|
||||||
|
snapshot.data ?? false ? Icons.check_circle : Icons.cancel_outlined,
|
||||||
|
size: 16,
|
||||||
|
color: snapshot.data ?? false ? Colors.green : context.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
"cellular_data_for_videos".t(context: context),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceVariant),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
StreamBuilder(
|
||||||
|
stream: cellularReqForPhotos,
|
||||||
|
initialData: Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Expanded(
|
||||||
|
child: ListTile(
|
||||||
|
visualDensity: VisualDensity.compact,
|
||||||
|
leading: Icon(
|
||||||
|
snapshot.data ?? false ? Icons.check_circle : Icons.cancel_outlined,
|
||||||
|
size: 16,
|
||||||
|
color: snapshot.data ?? false ? Colors.green : context.colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
"cellular_data_for_photos".t(context: context),
|
||||||
|
style: context.textTheme.labelLarge?.copyWith(color: context.colorScheme.onSurfaceVariant),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class _BackupAlbumSelectionCard extends ConsumerWidget {
|
class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||||
const _BackupAlbumSelectionCard();
|
const _BackupAlbumSelectionCard();
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ class DriftBackupOptionsPage extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
bool hasPopped = false;
|
bool hasPopped = false;
|
||||||
final previousWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
final previousWifiReqForVideos = Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false;
|
||||||
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
final previousWifiReqForPhotos = Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false;
|
||||||
return PopScope(
|
return PopScope(
|
||||||
onPopInvokedWithResult: (didPop, result) async {
|
onPopInvokedWithResult: (didPop, result) async {
|
||||||
// There is an issue with Flutter where the pop event
|
// There is an issue with Flutter where the pop event
|
||||||
// can be triggered multiple times, so we guard it with _hasPopped
|
// can be triggered multiple times, so we guard it with _hasPopped
|
||||||
|
|
||||||
final currentWifiReqForVideos = Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false;
|
final currentWifiReqForVideos = Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false;
|
||||||
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false;
|
final currentWifiReqForPhotos = Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false;
|
||||||
|
|
||||||
if (currentWifiReqForVideos == previousWifiReqForVideos &&
|
if (currentWifiReqForVideos == previousWifiReqForVideos &&
|
||||||
currentWifiReqForPhotos == previousWifiReqForPhotos) {
|
currentWifiReqForPhotos == previousWifiReqForPhotos) {
|
||||||
|
|||||||
@@ -1,34 +1,15 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
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/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/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/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';
|
||||||
|
|
||||||
class _SharePreparingDialog extends StatelessWidget {
|
|
||||||
const _SharePreparingDialog();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
content: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const CircularProgressIndicator(),
|
|
||||||
Container(margin: const EdgeInsets.only(top: 12), child: const Text('share_dialog_preparing').tr()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ShareActionButton extends ConsumerWidget {
|
class ShareActionButton extends ConsumerWidget {
|
||||||
final ActionSource source;
|
final ActionSource source;
|
||||||
|
|
||||||
@@ -39,34 +20,28 @@ class ShareActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showDialog(
|
final result = await ref.read(actionProvider.notifier).shareAssets(source);
|
||||||
context: context,
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
builder: (BuildContext buildContext) {
|
|
||||||
ref.read(actionProvider.notifier).shareAssets(source).then((ActionResult result) {
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (!context.mounted) {
|
if (!context.mounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
context: context,
|
context: context,
|
||||||
msg: 'scaffold_body_error_occurred'.t(context: context),
|
msg: 'scaffold_body_error_occurred'.t(context: context),
|
||||||
gravity: ToastGravity.BOTTOM,
|
gravity: ToastGravity.BOTTOM,
|
||||||
toastType: ToastType.error,
|
toastType: ToastType.error,
|
||||||
);
|
);
|
||||||
}
|
} else if (result.count > 0) {
|
||||||
|
ImmichToast.show(
|
||||||
buildContext.pop();
|
context: context,
|
||||||
});
|
msg: 'share_action_prompt'.t(context: context, args: {'count': result.count.toString()}),
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
// show a loading spinner with a "Preparing" message
|
toastType: ToastType.success,
|
||||||
return const _SharePreparingDialog();
|
);
|
||||||
},
|
}
|
||||||
barrierDismissible: false,
|
|
||||||
useRootNavigator: false,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -334,8 +334,8 @@ class ActionNotifier extends Notifier<void> {
|
|||||||
final ids = _getAssets(source).toList(growable: false);
|
final ids = _getAssets(source).toList(growable: false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _service.shareAssets(ids);
|
final count = await _service.shareAssets(ids);
|
||||||
return ActionResult(count: ids.length, success: true);
|
return ActionResult(count: count, success: true);
|
||||||
} catch (error, stack) {
|
} catch (error, stack) {
|
||||||
_logger.severe('Failed to share assets', error, stack);
|
_logger.severe('Failed to share assets', error, stack);
|
||||||
return ActionResult(count: ids.length, success: false, error: error.toString());
|
return ActionResult(count: ids.length, success: false, error: error.toString());
|
||||||
|
|||||||
@@ -104,18 +104,15 @@ class AssetMediaRepository {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// we dont want to await the share result since the
|
final result = await Share.shareXFiles(downloadedXFiles);
|
||||||
// "preparing" dialog will not disappear unti
|
|
||||||
Share.shareXFiles(downloadedXFiles).then((result) async {
|
|
||||||
for (var file in downloadedXFiles) {
|
|
||||||
try {
|
|
||||||
await File(file.path).delete();
|
|
||||||
} catch (e) {
|
|
||||||
_log.warning("Failed to delete temporary file: ${file.path}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return downloadedXFiles.length;
|
for (var file in downloadedXFiles) {
|
||||||
|
try {
|
||||||
|
await File(file.path).delete();
|
||||||
|
} catch (e) {
|
||||||
|
_log.warning("Failed to delete temporary file: ${file.path}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.status == ShareResultStatus.success ? downloadedXFiles.length : 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ enum AppSettingsEnum<T> {
|
|||||||
photoManagerCustomFilter<bool>(StoreKey.photoManagerCustomFilter, null, true),
|
photoManagerCustomFilter<bool>(StoreKey.photoManagerCustomFilter, null, true),
|
||||||
betaTimeline<bool>(StoreKey.betaTimeline, null, false),
|
betaTimeline<bool>(StoreKey.betaTimeline, null, false),
|
||||||
enableBackup<bool>(StoreKey.enableBackup, null, false),
|
enableBackup<bool>(StoreKey.enableBackup, null, false),
|
||||||
useCellularForUploadVideos<bool>(StoreKey.useWifiForUploadVideos, null, false),
|
useCellularForUploadVideos<bool>(StoreKey.useCellularForUploadVideos, null, false),
|
||||||
useCellularForUploadPhotos<bool>(StoreKey.useWifiForUploadPhotos, null, false),
|
useCellularForUploadPhotos<bool>(StoreKey.useCellularForUploadPhotos, null, false),
|
||||||
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false);
|
readonlyModeEnabled<bool>(StoreKey.readonlyModeEnabled, "readonlyModeEnabled", false);
|
||||||
|
|
||||||
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
const AppSettingsEnum(this.storeKey, this.hiveKey, this.defaultValue);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ 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:logging/logging.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
final uploadServiceProvider = Provider((ref) {
|
final uploadServiceProvider = Provider((ref) {
|
||||||
@@ -57,6 +58,7 @@ class UploadService {
|
|||||||
|
|
||||||
Stream<TaskStatusUpdate> get taskStatusStream => _taskStatusController.stream;
|
Stream<TaskStatusUpdate> get taskStatusStream => _taskStatusController.stream;
|
||||||
Stream<TaskProgressUpdate> get taskProgressStream => _taskProgressController.stream;
|
Stream<TaskProgressUpdate> get taskProgressStream => _taskProgressController.stream;
|
||||||
|
final Logger _log = Logger('UploadService');
|
||||||
|
|
||||||
bool shouldAbortQueuingTasks = false;
|
bool shouldAbortQueuingTasks = false;
|
||||||
|
|
||||||
@@ -127,11 +129,16 @@ class UploadService {
|
|||||||
|
|
||||||
final candidates = await _backupRepository.getCandidates(userId);
|
final candidates = await _backupRepository.getCandidates(userId);
|
||||||
if (candidates.isEmpty) {
|
if (candidates.isEmpty) {
|
||||||
|
_log.info("No backup candidates found for user $userId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_log.info("Starting backup for ${candidates.length} candidates");
|
||||||
|
onEnqueueTasks(EnqueueStatus(enqueueCount: 0, totalCount: candidates.length));
|
||||||
|
|
||||||
const batchSize = 100;
|
const batchSize = 100;
|
||||||
int count = 0;
|
int count = 0;
|
||||||
|
int skippedAssets = 0;
|
||||||
for (int i = 0; i < candidates.length; i += batchSize) {
|
for (int i = 0; i < candidates.length; i += batchSize) {
|
||||||
if (shouldAbortQueuingTasks) {
|
if (shouldAbortQueuingTasks) {
|
||||||
break;
|
break;
|
||||||
@@ -144,16 +151,22 @@ class UploadService {
|
|||||||
final task = await _getUploadTask(asset);
|
final task = await _getUploadTask(asset);
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
tasks.add(task);
|
tasks.add(task);
|
||||||
|
} else {
|
||||||
|
skippedAssets++;
|
||||||
|
_log.warning("Skipped asset ${asset.id} (${asset.name}) - unable to create upload task");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tasks.isNotEmpty && !shouldAbortQueuingTasks) {
|
count += tasks.length;
|
||||||
count += tasks.length;
|
onEnqueueTasks(EnqueueStatus(enqueueCount: count, totalCount: candidates.length));
|
||||||
await enqueueTasks(tasks);
|
|
||||||
|
|
||||||
onEnqueueTasks(EnqueueStatus(enqueueCount: count, totalCount: candidates.length));
|
if (tasks.isNotEmpty && !shouldAbortQueuingTasks) {
|
||||||
|
_log.info("Enqueuing ${tasks.length} upload tasks");
|
||||||
|
await enqueueTasks(tasks);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_log.info("Upload queueing completed: $count tasks enqueued, $skippedAssets assets skipped");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enqueue All does not work from the background on Android yet. This method is a temporary workaround
|
// Enqueue All does not work from the background on Android yet. This method is a temporary workaround
|
||||||
@@ -165,9 +178,14 @@ class UploadService {
|
|||||||
|
|
||||||
final candidates = await _backupRepository.getCandidates(userId);
|
final candidates = await _backupRepository.getCandidates(userId);
|
||||||
if (candidates.isEmpty) {
|
if (candidates.isEmpty) {
|
||||||
|
debugPrint("No backup candidates found for serial backup");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugPrint("Starting serial backup for ${candidates.length} candidates");
|
||||||
|
int skippedAssets = 0;
|
||||||
|
int enqueuedTasks = 0;
|
||||||
|
|
||||||
for (final asset in candidates) {
|
for (final asset in candidates) {
|
||||||
if (shouldAbortQueuingTasks) {
|
if (shouldAbortQueuingTasks) {
|
||||||
break;
|
break;
|
||||||
@@ -176,8 +194,13 @@ class UploadService {
|
|||||||
final task = await _getUploadTask(asset);
|
final task = await _getUploadTask(asset);
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
await _uploadRepository.enqueueBackground(task);
|
await _uploadRepository.enqueueBackground(task);
|
||||||
|
enqueuedTasks++;
|
||||||
|
} else {
|
||||||
|
skippedAssets++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugPrint("Serial backup completed: $enqueuedTasks tasks enqueued, $skippedAssets assets skipped");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel all ongoing uploads and reset the upload queue
|
/// Cancel all ongoing uploads and reset the upload queue
|
||||||
@@ -245,6 +268,7 @@ class UploadService {
|
|||||||
Future<UploadTask?> _getUploadTask(LocalAsset asset, {String group = kBackupGroup, int? priority}) async {
|
Future<UploadTask?> _getUploadTask(LocalAsset asset, {String group = kBackupGroup, int? priority}) async {
|
||||||
final entity = await _storageRepository.getAssetEntityForAsset(asset);
|
final entity = await _storageRepository.getAssetEntityForAsset(asset);
|
||||||
if (entity == null) {
|
if (entity == null) {
|
||||||
|
_log.warning("Cannot get AssetEntity for asset ${asset.id} (${asset.name}) created on ${asset.createdAt}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +291,9 @@ class UploadService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
|
_log.warning(
|
||||||
|
"Cannot get file for asset ${asset.id} (${asset.name}) created on ${asset.createdAt} - file may have been deleted or moved",
|
||||||
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +305,13 @@ class UploadService {
|
|||||||
livePhotoVideoId: '',
|
livePhotoVideoId: '',
|
||||||
).toJson();
|
).toJson();
|
||||||
|
|
||||||
final requiresWiFi = _shouldRequireWiFi(asset);
|
bool requiresWiFi = true;
|
||||||
|
|
||||||
|
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
||||||
|
requiresWiFi = false;
|
||||||
|
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
||||||
|
requiresWiFi = false;
|
||||||
|
}
|
||||||
|
|
||||||
return buildUploadTask(
|
return buildUploadTask(
|
||||||
file,
|
file,
|
||||||
@@ -305,8 +338,6 @@ class UploadService {
|
|||||||
|
|
||||||
final fields = {'livePhotoVideoId': livePhotoVideoId};
|
final fields = {'livePhotoVideoId': livePhotoVideoId};
|
||||||
|
|
||||||
final requiresWiFi = _shouldRequireWiFi(asset);
|
|
||||||
|
|
||||||
return buildUploadTask(
|
return buildUploadTask(
|
||||||
file,
|
file,
|
||||||
originalFileName: asset.name,
|
originalFileName: asset.name,
|
||||||
@@ -315,22 +346,9 @@ class UploadService {
|
|||||||
group: kBackupLivePhotoGroup,
|
group: kBackupLivePhotoGroup,
|
||||||
priority: 0, // Highest priority to get upload immediately
|
priority: 0, // Highest priority to get upload immediately
|
||||||
isFavorite: asset.isFavorite,
|
isFavorite: asset.isFavorite,
|
||||||
requiresWiFi: requiresWiFi,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _shouldRequireWiFi(LocalAsset asset) {
|
|
||||||
bool requiresWiFi = true;
|
|
||||||
|
|
||||||
if (asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadVideos)) {
|
|
||||||
requiresWiFi = false;
|
|
||||||
} else if (!asset.isVideo && _appSettingsService.getSetting(AppSettingsEnum.useCellularForUploadPhotos)) {
|
|
||||||
requiresWiFi = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requiresWiFi;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<UploadTask> buildUploadTask(
|
Future<UploadTask> buildUploadTask(
|
||||||
File file, {
|
File file, {
|
||||||
required String group,
|
required String group,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final valueStream = Store.watch(StoreKey.useWifiForUploadVideos);
|
final valueStream = Store.watch(StoreKey.useCellularForUploadVideos);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -32,7 +32,7 @@ class _UseWifiForUploadVideosButton extends ConsumerWidget {
|
|||||||
subtitle: Text("network_requirement_videos_upload".t(context: context), style: context.textTheme.labelLarge),
|
subtitle: Text("network_requirement_videos_upload".t(context: context), style: context.textTheme.labelLarge),
|
||||||
trailing: StreamBuilder(
|
trailing: StreamBuilder(
|
||||||
stream: valueStream,
|
stream: valueStream,
|
||||||
initialData: Store.tryGet(StoreKey.useWifiForUploadVideos) ?? false,
|
initialData: Store.tryGet(StoreKey.useCellularForUploadVideos) ?? false,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final value = snapshot.data ?? false;
|
final value = snapshot.data ?? false;
|
||||||
return Switch(
|
return Switch(
|
||||||
@@ -54,7 +54,7 @@ class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final valueStream = Store.watch(StoreKey.useWifiForUploadPhotos);
|
final valueStream = Store.watch(StoreKey.useCellularForUploadPhotos);
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -64,7 +64,7 @@ class _UseWifiForUploadPhotosButton extends ConsumerWidget {
|
|||||||
subtitle: Text("network_requirement_photos_upload".t(context: context), style: context.textTheme.labelLarge),
|
subtitle: Text("network_requirement_photos_upload".t(context: context), style: context.textTheme.labelLarge),
|
||||||
trailing: StreamBuilder(
|
trailing: StreamBuilder(
|
||||||
stream: valueStream,
|
stream: valueStream,
|
||||||
initialData: Store.tryGet(StoreKey.useWifiForUploadPhotos) ?? false,
|
initialData: Store.tryGet(StoreKey.useCellularForUploadPhotos) ?? false,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final value = snapshot.data ?? false;
|
final value = snapshot.data ?? false;
|
||||||
return Switch(
|
return Switch(
|
||||||
|
|||||||
Generated
+1
-1
@@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.140.1
|
- API version: 1.140.0
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.140.1+3011
|
version: 1.140.0+3010
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.8.0 <4.0.0'
|
sdk: '>=3.8.0 <4.0.0'
|
||||||
|
|||||||
@@ -9789,7 +9789,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.140.1",
|
"version": "1.140.0",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.140.1",
|
"version": "1.140.0",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.140.1
|
* 1.140.0
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.140.1",
|
"version": "1.140.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.140.1",
|
"version": "1.140.0",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import NumberRangeInput from '$lib/components/shared-components/number-range-input.svelte';
|
import NumberRangeInput from '$lib/components/shared-components/number-range-input.svelte';
|
||||||
import { render, type RenderResult } from '@testing-library/svelte';
|
import { render, type RenderResult } from '@testing-library/svelte';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
import type { Mock } from 'vitest';
|
|
||||||
|
|
||||||
describe('NumberRangeInput component', () => {
|
describe('NumberRangeInput component', () => {
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
let sut: RenderResult<NumberRangeInput>;
|
let sut: RenderResult<NumberRangeInput>;
|
||||||
let input: HTMLInputElement;
|
let input: HTMLInputElement;
|
||||||
let onInput: Mock;
|
|
||||||
let onKeyDown: Mock;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
onInput = vi.fn();
|
|
||||||
onKeyDown = vi.fn();
|
|
||||||
sut = render(NumberRangeInput, {
|
sut = render(NumberRangeInput, {
|
||||||
id: '',
|
id: '',
|
||||||
min: -90,
|
min: -90,
|
||||||
max: 90,
|
max: 90,
|
||||||
onInput,
|
onInput: () => {},
|
||||||
onKeyDown,
|
|
||||||
});
|
});
|
||||||
input = sut.getByRole('spinbutton') as HTMLInputElement;
|
input = sut.getByRole('spinbutton') as HTMLInputElement;
|
||||||
});
|
});
|
||||||
@@ -27,55 +21,35 @@ describe('NumberRangeInput component', () => {
|
|||||||
expect(input.value).toBe('');
|
expect(input.value).toBe('');
|
||||||
await sut.rerender({ value: 10 });
|
await sut.rerender({ value: 10 });
|
||||||
expect(input.value).toBe('10');
|
expect(input.value).toBe('10');
|
||||||
expect(onInput).not.toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).not.toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('restricts minimum value', async () => {
|
it('restricts minimum value', async () => {
|
||||||
await user.type(input, '-91');
|
await user.type(input, '-91');
|
||||||
expect(input.value).toBe('-90');
|
expect(input.value).toBe('-90');
|
||||||
expect(onInput).toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('restricts maximum value', async () => {
|
it('restricts maximum value', async () => {
|
||||||
await user.type(input, '09990');
|
await user.type(input, '09990');
|
||||||
expect(input.value).toBe('90');
|
expect(input.value).toBe('90');
|
||||||
expect(onInput).toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows entering negative numbers', async () => {
|
it('allows entering negative numbers', async () => {
|
||||||
await user.type(input, '-10');
|
await user.type(input, '-10');
|
||||||
expect(input.value).toBe('-10');
|
expect(input.value).toBe('-10');
|
||||||
expect(onInput).toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows entering zero', async () => {
|
it('allows entering zero', async () => {
|
||||||
await user.type(input, '0');
|
await user.type(input, '0');
|
||||||
expect(input.value).toBe('0');
|
expect(input.value).toBe('0');
|
||||||
expect(onInput).toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('allows entering decimal numbers', async () => {
|
it('allows entering decimal numbers', async () => {
|
||||||
await user.type(input, '-0.09001');
|
await user.type(input, '-0.09001');
|
||||||
expect(input.value).toBe('-0.09001');
|
expect(input.value).toBe('-0.09001');
|
||||||
expect(onInput).toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores text input', async () => {
|
it('ignores text input', async () => {
|
||||||
await user.type(input, 'test');
|
await user.type(input, 'test');
|
||||||
expect(input.value).toBe('');
|
expect(input.value).toBe('');
|
||||||
expect(onInput).toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('test', async () => {
|
|
||||||
await user.type(input, 'd');
|
|
||||||
expect(onInput).not.toHaveBeenCalled();
|
|
||||||
expect(onKeyDown).toHaveBeenCalled();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
let mapLat = $derived(assetLat ?? previousLocation?.lat ?? undefined);
|
let mapLat = $derived(assetLat ?? previousLocation?.lat ?? undefined);
|
||||||
let mapLng = $derived(assetLng ?? previousLocation?.lng ?? undefined);
|
let mapLng = $derived(assetLng ?? previousLocation?.lng ?? undefined);
|
||||||
|
|
||||||
let zoom = $derived(mapLat && mapLng ? 12.5 : 1);
|
let zoom = $derived(mapLat !== undefined && mapLng !== undefined ? 12.5 : 1);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (mapElement && initialPoint) {
|
if (mapElement && initialPoint) {
|
||||||
|
|||||||
@@ -20,10 +20,6 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event: KeyboardEvent) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
};
|
|
||||||
|
|
||||||
const onPaste = (event: ClipboardEvent) => {
|
const onPaste = (event: ClipboardEvent) => {
|
||||||
const pastedText = event.clipboardData?.getData('text/plain');
|
const pastedText = event.clipboardData?.getData('text/plain');
|
||||||
if (!pastedText) {
|
if (!pastedText) {
|
||||||
@@ -46,10 +42,10 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="immich-form-label" for="latitude-input-{id}">{$t('latitude')}</label>
|
<label class="immich-form-label" for="latitude-input-{id}">{$t('latitude')}</label>
|
||||||
<NumberRangeInput id="latitude-input-{id}" min={-90} max={90} {onKeyDown} {onInput} {onPaste} bind:value={lat} />
|
<NumberRangeInput id="latitude-input-{id}" min={-90} max={90} {onInput} {onPaste} bind:value={lat} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="immich-form-label" for="longitude-input-{id}">{$t('longitude')}</label>
|
<label class="immich-form-label" for="longitude-input-{id}">{$t('longitude')}</label>
|
||||||
<NumberRangeInput id="longitude-input-{id}" min={-180} max={180} {onKeyDown} {onInput} {onPaste} bind:value={lng} />
|
<NumberRangeInput id="longitude-input-{id}" min={-180} max={180} {onInput} {onPaste} bind:value={lng} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
import type { ClipboardEventHandler, KeyboardEventHandler } from 'svelte/elements';
|
import type { ClipboardEventHandler } from 'svelte/elements';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -11,7 +11,6 @@
|
|||||||
value?: number;
|
value?: number;
|
||||||
onInput: (value: number | null) => void;
|
onInput: (value: number | null) => void;
|
||||||
onPaste?: ClipboardEventHandler<HTMLInputElement>;
|
onPaste?: ClipboardEventHandler<HTMLInputElement>;
|
||||||
onKeyDown?: KeyboardEventHandler<HTMLInputElement>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -23,7 +22,6 @@
|
|||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
onInput,
|
onInput,
|
||||||
onPaste = undefined,
|
onPaste = undefined,
|
||||||
onKeyDown = undefined,
|
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
const oninput = () => {
|
const oninput = () => {
|
||||||
@@ -50,5 +48,4 @@
|
|||||||
bind:value
|
bind:value
|
||||||
{oninput}
|
{oninput}
|
||||||
onpaste={onPaste}
|
onpaste={onPaste}
|
||||||
onkeydown={onKeyDown}
|
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user