Compare commits

..

2 Commits

Author SHA1 Message Date
Zack Pollard 4cac0a7449 wip 2025-08-04 18:12:32 +01:00
Zack Pollard 93aaf92c55 wip 2025-08-04 12:00:35 +01:00
49 changed files with 105 additions and 254 deletions
-96
View File
@@ -1,96 +0,0 @@
on:
issues:
types: [opened]
discussion:
types: [created]
name: Close likely duplicates
permissions: {}
jobs:
get_body:
runs-on: ubuntu-latest
env:
EVENT: ${{ toJSON(github.event) }}
outputs:
body: ${{ steps.get_body.outputs.body }}
steps:
- id: get_body
run: |
BODY=$(echo """$EVENT""" | jq -r '.issue // .discussion | .body' | base64 -w 0)
echo "body=$BODY" >> $GITHUB_OUTPUT
get_checkbox_json:
runs-on: ubuntu-latest
needs: get_body
container:
image: yshavit/mdq:0.7.2
outputs:
json: ${{ steps.get_checkbox.outputs.json }}
steps:
- id: get_checkbox
env:
BODY: ${{ needs.get_body.outputs.body }}
run: |
JSON=$(echo "$BODY" | base64 -d | /mdq --output json '# I have searched | - [?] Yes')
echo "json=$JSON" >> $GITHUB_OUTPUT
close_and_comment:
runs-on: ubuntu-latest
needs: get_checkbox_json
if: ${{ !fromJSON(needs.get_checkbox_json.outputs.json).items[0].list[0].checked }}
permissions:
issues: write
discussions: write
steps:
- name: Close issue
if: ${{ github.event_name == 'issues' }}
env:
GH_TOKEN: ${{ github.token }}
NODE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql \
-f issueId="$NODE_ID" \
-f body="This issue has automatically been closed as it is likely a duplicate. We get a lot of duplicate threads each day, which is why we ask you in the template to confirm that you searched for duplicates before opening one." \
-f query='
mutation CommentAndCloseIssue($issueId: ID!, $body: String!) {
addComment(input: {
subjectId: $issueId,
body: $body
}) {
__typename
}
closeIssue(input: {
issueId: $issueId,
stateReason: DUPLICATE
}) {
__typename
}
}'
- name: Close discussion
if: ${{ github.event_name == 'discussion' && github.event.discussion.category.name == 'Feature Request' }}
env:
GH_TOKEN: ${{ github.token }}
NODE_ID: ${{ github.event.discussion.node_id }}
run: |
gh api graphql \
-f discussionId="$NODE_ID" \
-f body="This discussion has automatically been closed as it is likely a duplicate. We get a lot of duplicate threads each day, which is why we ask you in the template to confirm that you searched for duplicates before opening one." \
-f query='
mutation CommentAndCloseDiscussion($discussionId: ID!, $body: String!) {
addDiscussionComment(input: {
discussionId: $discussionId,
body: $body
}) {
__typename
}
closeDiscussion(input: {
discussionId: $discussionId,
reason: DUPLICATE
}) {
__typename
}
}'
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.77", "version": "2.2.76",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.77", "version": "2.2.76",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
@@ -54,7 +54,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.137.3", "version": "1.137.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.77", "version": "2.2.76",
"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",
-4
View File
@@ -1,8 +1,4 @@
[ [
{
"label": "v1.137.3",
"url": "https://v1.137.3.archive.immich.app"
},
{ {
"label": "v1.137.2", "label": "v1.137.2",
"url": "https://v1.137.2.archive.immich.app" "url": "https://v1.137.2.archive.immich.app"
+4 -4
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.137.3", "version": "1.137.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.137.3", "version": "1.137.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.1.0", "@eslint/eslintrc": "^3.1.0",
@@ -46,7 +46,7 @@
}, },
"../cli": { "../cli": {
"name": "@immich/cli", "name": "@immich/cli",
"version": "2.2.77", "version": "2.2.76",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
@@ -95,7 +95,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.137.3", "version": "1.137.2",
"dev": true, "dev": true,
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-e2e", "name": "immich-e2e",
"version": "1.137.3", "version": "1.137.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module", "type": "module",
+2 -1
View File
@@ -1252,7 +1252,7 @@
"manage_your_devices": "Manage your logged-in devices", "manage_your_devices": "Manage your logged-in devices",
"manage_your_oauth_connection": "Manage your OAuth connection", "manage_your_oauth_connection": "Manage your OAuth connection",
"map": "Map", "map": "Map",
"map_assets_in_bounds": "{count, plural, =0 {No photos in this area} one {# photo} other {# photos}}", "map_assets_in_bounds": "{count, plural, one {# photo} other {# photos}}",
"map_cannot_get_user_location": "Cannot get user's location", "map_cannot_get_user_location": "Cannot get user's location",
"map_location_dialog_yes": "Yes", "map_location_dialog_yes": "Yes",
"map_location_picker_page_use_location": "Use this location", "map_location_picker_page_use_location": "Use this location",
@@ -1260,6 +1260,7 @@
"map_location_service_disabled_title": "Location Service disabled", "map_location_service_disabled_title": "Location Service disabled",
"map_marker_for_images": "Map marker for images taken in {city}, {country}", "map_marker_for_images": "Map marker for images taken in {city}, {country}",
"map_marker_with_image": "Map marker with image", "map_marker_with_image": "Map marker with image",
"map_no_assets_in_bounds": "No photos in this area",
"map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?", "map_no_location_permission_content": "Location permission is needed to display assets from your current location. Do you want to allow it now?",
"map_no_location_permission_title": "Location Permission denied", "map_no_location_permission_title": "Location Permission denied",
"map_settings": "Map settings", "map_settings": "Map settings",
+1 -1
View File
@@ -36,7 +36,7 @@ platform :android do
build_type: 'Release', build_type: 'Release',
properties: { properties: {
"android.injected.version.code" => 3002, "android.injected.version.code" => 3002,
"android.injected.version.name" => "1.137.3", "android.injected.version.name" => "1.137.2",
} }
) )
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')
+1 -1
View File
@@ -22,7 +22,7 @@ platform :ios do
path: "./Runner.xcodeproj", path: "./Runner.xcodeproj",
) )
increment_version_number( increment_version_number(
version_number: "1.137.3" version_number: "1.137.2"
) )
increment_build_number( increment_build_number(
build_number: latest_testflight_build_number + 1, build_number: latest_testflight_build_number + 1,
+1 -1
View File
@@ -96,7 +96,7 @@ class HashService {
if (hash?.length == 20) { if (hash?.length == 20) {
hashed.add(asset.copyWith(checksum: base64.encode(hash!))); hashed.add(asset.copyWith(checksum: base64.encode(hash!)));
} else { } else {
_log.warning("Failed to hash file for ${asset.id}: ${asset.name} created at ${asset.createdAt}"); _log.warning("Failed to hash file for ${asset.id}");
} }
} }
+2 -6
View File
@@ -37,7 +37,7 @@ class BackgroundSyncManager {
this.onHashingError, this.onHashingError,
}); });
Future<void> cancel() async { Future<void> cancel() {
final futures = <Future>[]; final futures = <Future>[];
if (_syncTask != null) { if (_syncTask != null) {
@@ -52,11 +52,7 @@ class BackgroundSyncManager {
_syncWebsocketTask?.cancel(); _syncWebsocketTask?.cancel();
_syncWebsocketTask = null; _syncWebsocketTask = null;
try { return Future.wait(futures);
await Future.wait(futures);
} on CanceledError {
// Ignore cancellation errors
}
} }
// No need to cancel the task, as it can also be run when the user logs out // No need to cancel the task, as it can also be run when the user logs out
+1
View File
@@ -83,6 +83,7 @@ Future<void> initApp() async {
}; };
PlatformDispatcher.instance.onError = (error, stack) { PlatformDispatcher.instance.onError = (error, stack) {
debugPrint("FlutterError - Catch all: $error \n $stack");
log.severe('PlatformDispatcher - Catch all', error, stack); log.severe('PlatformDispatcher - Catch all', error, stack);
return true; return true;
}; };
+7 -40
View File
@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -11,7 +9,6 @@ import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; import 'package:immich_mobile/providers/haptic_feedback.provider.dart';
import 'package:immich_mobile/providers/infrastructure/album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:immich_mobile/providers/tab.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
@@ -125,11 +122,6 @@ void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) {
EventStream.shared.emit(const ScrollToTopEvent()); EventStream.shared.emit(const ScrollToTopEvent());
} }
// On Photos page navigation, invalidate memories provider to get the most up-to-date data
if (router.activeIndex == 0) {
ref.invalidate(driftMemoryFutureProvider);
}
// On Search page tapped // On Search page tapped
if (router.activeIndex == 1 && index == 1) { if (router.activeIndex == 1 && index == 1) {
ref.read(searchInputFocusProvider).requestFocus(); ref.read(searchInputFocusProvider).requestFocus();
@@ -145,50 +137,25 @@ void _onNavigationSelected(TabsRouter router, int index, WidgetRef ref) {
ref.read(tabProvider.notifier).state = TabEnum.values[index]; ref.read(tabProvider.notifier).state = TabEnum.values[index];
} }
class _BottomNavigationBar extends ConsumerStatefulWidget { class _BottomNavigationBar extends ConsumerWidget {
const _BottomNavigationBar({required this.tabsRouter, required this.destinations}); const _BottomNavigationBar({required this.tabsRouter, required this.destinations});
final List<Widget> destinations; final List<Widget> destinations;
final TabsRouter tabsRouter; final TabsRouter tabsRouter;
@override @override
ConsumerState createState() => _BottomNavigationBarState(); Widget build(BuildContext context, WidgetRef ref) {
}
class _BottomNavigationBarState extends ConsumerState<_BottomNavigationBar> {
bool hideNavigationBar = false;
StreamSubscription? _eventSubscription;
@override
void initState() {
super.initState();
_eventSubscription = EventStream.shared.listen<MultiSelectToggleEvent>(_onEvent);
}
void _onEvent(MultiSelectToggleEvent event) {
setState(() {
hideNavigationBar = event.isEnabled;
});
}
@override
void dispose() {
_eventSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isScreenLandscape = context.orientation == Orientation.landscape; final isScreenLandscape = context.orientation == Orientation.landscape;
final isMultiselectEnabled = ref.watch(multiSelectProvider.select((s) => s.isEnabled));
if (isScreenLandscape || hideNavigationBar) { if (isScreenLandscape || isMultiselectEnabled) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return NavigationBar( return NavigationBar(
selectedIndex: widget.tabsRouter.activeIndex, selectedIndex: tabsRouter.activeIndex,
onDestinationSelected: (index) => _onNavigationSelected(widget.tabsRouter, index, ref), onDestinationSelected: (index) => _onNavigationSelected(tabsRouter, index, ref),
destinations: widget.destinations, destinations: destinations,
); );
} }
} }
@@ -264,15 +264,11 @@ class SharedLinkEditPage extends HookConsumerWidget {
expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(), expiresAt: expiryAfter.value == 0 ? null : calculateExpiry(),
); );
ref.invalidate(sharedLinksStateProvider); ref.invalidate(sharedLinksStateProvider);
await ref.read(serverInfoProvider.notifier).getServerConfig();
final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain)); final externalDomain = ref.read(serverInfoProvider.select((s) => s.serverConfig.externalDomain));
var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl(); var serverUrl = externalDomain.isNotEmpty ? externalDomain : getServerUrl();
if (serverUrl != null && !serverUrl.endsWith('/')) { if (serverUrl != null && !serverUrl.endsWith('/')) {
serverUrl += '/'; serverUrl += '/';
} }
if (newLink != null && serverUrl != null) { if (newLink != null && serverUrl != null) {
newShareLink.value = "${serverUrl}share/${newLink.key}"; newShareLink.value = "${serverUrl}share/${newLink.key}";
copyLinkToClipboard(); copyLinkToClipboard();
@@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/presentation/widgets/memory/memory_lane.widget.dart'; import 'package:immich_mobile/presentation/widgets/memory/memory_lane.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/memory.provider.dart'; import 'package:immich_mobile/providers/infrastructure/memory.provider.dart';
import 'package:immich_mobile/providers/user.provider.dart';
@RoutePage() @RoutePage()
class MainTimelinePage extends ConsumerWidget { class MainTimelinePage extends ConsumerWidget {
@@ -13,24 +12,21 @@ class MainTimelinePage extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final memoryLaneProvider = ref.watch(driftMemoryFutureProvider); final memoryLaneProvider = ref.watch(driftMemoryFutureProvider);
final memoriesEnabled = ref.watch(currentUserProvider.select((user) => user?.memoryEnabled ?? true));
// TODO: the user preferences need to be updated
// from the server to get live hiding/showing of memory lane
return memoryLaneProvider.maybeWhen( return memoryLaneProvider.maybeWhen(
data: (memories) { data: (memories) {
return memories.isEmpty || !memoriesEnabled return memories.isEmpty
? const Timeline() ? const Timeline(showStorageIndicator: true)
: Timeline( : Timeline(
topSliverWidget: SliverToBoxAdapter( topSliverWidget: SliverToBoxAdapter(
key: Key('memory-lane-${memories.first.assets.first.id}'), key: Key('memory-lane-${memories.first.assets.first.id}'),
child: DriftMemoryLane(memories: memories), child: DriftMemoryLane(memories: memories),
), ),
topSliverWidgetHeight: 200, topSliverWidgetHeight: 200,
showStorageIndicator: true,
); );
}, },
orElse: () => const Timeline(), orElse: () => const Timeline(showStorageIndicator: true),
); );
} }
} }
@@ -28,6 +28,7 @@ class DriftTrashPage extends StatelessWidget {
}), }),
], ],
child: Timeline( child: Timeline(
showStorageIndicator: true,
appBar: SliverAppBar( appBar: SliverAppBar(
title: Text('trash'.t(context: context)), title: Text('trash'.t(context: context)),
floating: true, floating: true,
@@ -26,6 +26,7 @@ class LocalTimelinePage extends StatelessWidget {
child: Timeline( child: Timeline(
appBar: MesmerizingSliverAppBar(title: album.name), appBar: MesmerizingSliverAppBar(title: album.name),
bottomSheet: const LocalAlbumBottomSheet(), bottomSheet: const LocalAlbumBottomSheet(),
showStorageIndicator: true,
), ),
); );
} }
@@ -13,7 +13,6 @@ 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/models/search/search_filter.model.dart'; import 'package:immich_mobile/models/search/search_filter.model.dart';
import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart'; import 'package:immich_mobile/presentation/pages/search/paginated_search.provider.dart';
import 'package:immich_mobile/presentation/widgets/bottom_sheet/general_bottom_sheet.widget.dart';
import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart'; import 'package:immich_mobile/presentation/widgets/timeline/timeline.widget.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/search/search_input_focus.provider.dart'; import 'package:immich_mobile/providers/search/search_input_focus.provider.dart';
@@ -628,12 +627,7 @@ class _SearchResultGrid extends ConsumerWidget {
return timelineService; return timelineService;
}), }),
], ],
child: Timeline( child: Timeline(key: ValueKey(searchResult.totalAssets), appBar: null, groupBy: GroupAssetsBy.none),
key: ValueKey(searchResult.totalAssets),
groupBy: GroupAssetsBy.none,
appBar: null,
bottomSheet: const GeneralBottomSheet(minChildSize: 0.20),
),
), ),
), ),
); );
@@ -580,7 +580,6 @@ class AddToAlbumHeader extends ConsumerWidget {
} }
ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum); ref.read(currentRemoteAlbumProvider.notifier).setAlbum(newAlbum);
ref.read(multiSelectProvider.notifier).reset();
context.pushRoute(RemoteAlbumRoute(album: newAlbum)); context.pushRoute(RemoteAlbumRoute(album: newAlbum));
} }
@@ -22,13 +22,13 @@ class BaseBottomSheet extends ConsumerStatefulWidget {
this.slivers, this.slivers,
this.controller, this.controller,
this.initialChildSize = 0.35, this.initialChildSize = 0.35,
double? minChildSize, this.minChildSize = 0.15,
this.maxChildSize = 0.65, this.maxChildSize = 0.65,
this.expand = true, this.expand = true,
this.shouldCloseOnMinExtent = true, this.shouldCloseOnMinExtent = true,
this.resizeOnScroll = true, this.resizeOnScroll = true,
this.backgroundColor, this.backgroundColor,
}) : minChildSize = minChildSize ?? 0.15; });
@override @override
ConsumerState<BaseBottomSheet> createState() => _BaseDraggableScrollableSheetState(); ConsumerState<BaseBottomSheet> createState() => _BaseDraggableScrollableSheetState();
@@ -6,8 +6,8 @@ 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/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';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_date_time_action_button.widget.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/edit_location_action_button.widget.dart';
@@ -26,8 +26,7 @@ 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 GeneralBottomSheet extends ConsumerWidget { class GeneralBottomSheet extends ConsumerWidget {
final double? minChildSize; const GeneralBottomSheet({super.key});
const GeneralBottomSheet({super.key, this.minChildSize});
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
@@ -61,7 +60,6 @@ class GeneralBottomSheet extends ConsumerWidget {
return BaseBottomSheet( return BaseBottomSheet(
initialChildSize: 0.45, initialChildSize: 0.45,
minChildSize: minChildSize,
maxChildSize: 0.85, maxChildSize: 0.85,
shouldCloseOnMinExtent: false, shouldCloseOnMinExtent: false,
actions: [ actions: [
@@ -2,12 +2,10 @@ import 'package:auto_route/auto_route.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/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/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart';
import 'package:immich_mobile/extensions/duration_extensions.dart'; import 'package:immich_mobile/extensions/duration_extensions.dart';
import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart';
import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart'; import 'package:immich_mobile/presentation/widgets/images/thumbnail.widget.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
class ThumbnailTile extends ConsumerWidget { class ThumbnailTile extends ConsumerWidget {
@@ -15,7 +13,7 @@ class ThumbnailTile extends ConsumerWidget {
this.asset, { this.asset, {
this.size = const Size.square(256), this.size = const Size.square(256),
this.fit = BoxFit.cover, this.fit = BoxFit.cover,
this.showStorageIndicator, this.showStorageIndicator = true,
this.lockSelection = false, this.lockSelection = false,
this.heroOffset, this.heroOffset,
super.key, super.key,
@@ -24,7 +22,7 @@ class ThumbnailTile extends ConsumerWidget {
final BaseAsset asset; final BaseAsset asset;
final Size size; final Size size;
final BoxFit fit; final BoxFit fit;
final bool? showStorageIndicator; final bool showStorageIndicator;
final bool lockSelection; final bool lockSelection;
final int? heroOffset; final int? heroOffset;
@@ -54,9 +52,6 @@ class ThumbnailTile extends ConsumerWidget {
final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null; final hasStack = asset is RemoteAsset && (asset as RemoteAsset).stackId != null;
final bool storageIndicator =
showStorageIndicator ?? ref.watch(settingsProvider.select((s) => s.get(Setting.showStorageIndicator)));
return Stack( return Stack(
children: [ children: [
AnimatedContainer( AnimatedContainer(
@@ -91,7 +86,7 @@ class ThumbnailTile extends ConsumerWidget {
child: _VideoIndicator(asset.duration), child: _VideoIndicator(asset.duration),
), ),
), ),
if (storageIndicator) if (showStorageIndicator)
switch (asset.storage) { switch (asset.storage) {
AssetState.local => const Align( AssetState.local => const Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
@@ -14,7 +14,7 @@ class TimelineArgs {
final double maxHeight; final double maxHeight;
final double spacing; final double spacing;
final int columnCount; final int columnCount;
final bool? showStorageIndicator; final bool showStorageIndicator;
final bool withStack; final bool withStack;
final GroupAssetsBy? groupBy; final GroupAssetsBy? groupBy;
@@ -23,7 +23,7 @@ class TimelineArgs {
required this.maxHeight, required this.maxHeight,
this.spacing = kTimelineSpacing, this.spacing = kTimelineSpacing,
this.columnCount = kTimelineColumnCount, this.columnCount = kTimelineColumnCount,
this.showStorageIndicator, this.showStorageIndicator = false,
this.withStack = false, this.withStack = false,
this.groupBy, this.groupBy,
}); });
@@ -31,7 +31,7 @@ class Timeline extends StatelessWidget {
super.key, super.key,
this.topSliverWidget, this.topSliverWidget,
this.topSliverWidgetHeight, this.topSliverWidgetHeight,
this.showStorageIndicator, this.showStorageIndicator = false,
this.withStack = false, this.withStack = false,
this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false), this.appBar = const ImmichSliverAppBar(floating: true, pinned: false, snap: false),
this.bottomSheet = const GeneralBottomSheet(), this.bottomSheet = const GeneralBottomSheet(),
@@ -40,7 +40,7 @@ class Timeline extends StatelessWidget {
final Widget? topSliverWidget; final Widget? topSliverWidget;
final double? topSliverWidgetHeight; final double? topSliverWidgetHeight;
final bool? showStorageIndicator; final bool showStorageIndicator;
final Widget? appBar; final Widget? appBar;
final Widget? bottomSheet; final Widget? bottomSheet;
final bool withStack; final bool withStack;
@@ -115,8 +115,6 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
_baseScaleFactor = _scaleFactor; _baseScaleFactor = _scaleFactor;
}); });
}); });
ref.listenManual(multiSelectProvider.select((s) => s.isEnabled), _onMultiSelectionToggled);
} }
void _onEvent(Event event) { void _onEvent(Event event) {
@@ -132,10 +130,6 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
} }
} }
void _onMultiSelectionToggled(_, bool isEnabled) {
EventStream.shared.emit(MultiSelectToggleEvent(isEnabled));
}
@override @override
void dispose() { void dispose() {
_scrollController.dispose(); _scrollController.dispose();
@@ -86,12 +86,11 @@ class AppLifeCycleNotifier extends StateNotifier<AppLifeCycleEnum> {
// Ensure proper cleanup before starting new background tasks // Ensure proper cleanup before starting new background tasks
try { try {
await Future.wait([ await Future.wait([
Future(() async { backgroundManager.syncLocal().then((_) {
await backgroundManager.syncLocal();
Logger("AppLifeCycleNotifier").fine("Hashing assets after syncLocal"); Logger("AppLifeCycleNotifier").fine("Hashing assets after syncLocal");
// Check if app is still active before hashing // Check if app is still active before hashing
if ([AppLifeCycleEnum.resumed, AppLifeCycleEnum.active].contains(state)) { if (state == AppLifeCycleEnum.resumed) {
await backgroundManager.hashAssets(); backgroundManager.hashAssets();
} }
}), }),
backgroundManager.syncRemote(), backgroundManager.syncRemote(),
@@ -1,8 +1,8 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
import 'package:immich_mobile/domain/services/timeline.service.dart'; import 'package:immich_mobile/domain/services/timeline.service.dart';
import 'package:immich_mobile/domain/utils/event_stream.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
final multiSelectProvider = NotifierProvider<MultiSelectNotifier, MultiSelectState>( final multiSelectProvider = NotifierProvider<MultiSelectNotifier, MultiSelectState>(
@@ -10,11 +10,6 @@ final multiSelectProvider = NotifierProvider<MultiSelectNotifier, MultiSelectSta
dependencies: [timelineServiceProvider], dependencies: [timelineServiceProvider],
); );
class MultiSelectToggleEvent extends Event {
final bool isEnabled;
const MultiSelectToggleEvent(this.isEnabled);
}
class MultiSelectState { class MultiSelectState {
final Set<BaseAsset> selectedAssets; final Set<BaseAsset> selectedAssets;
final Set<BaseAsset> lockedSelectionAssets; final Set<BaseAsset> lockedSelectionAssets;
@@ -25,8 +25,6 @@ class AuthApiRepository extends ApiRepository {
} }
Future<void> logout() async { Future<void> logout() async {
if (_apiService.apiClient.basePath.isEmpty) return;
await _apiService.authenticationApi.logout().timeout(const Duration(seconds: 7)); await _apiService.authenticationApi.logout().timeout(const Duration(seconds: 7));
} }
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:ui'; import 'dart:ui';
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -19,7 +18,6 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da
import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart'; import 'package:immich_mobile/providers/infrastructure/remote_album.provider.dart';
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart'; import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/widgets/album/remote_album_shared_user_icons.dart'; import 'package:immich_mobile/widgets/album/remote_album_shared_user_icons.dart';
class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget { class RemoteAlbumSliverAppBar extends ConsumerStatefulWidget {
@@ -95,7 +93,7 @@ class _MesmerizingSliverAppBarState extends ConsumerState<RemoteAlbumSliverAppBa
), ),
onPressed: () { onPressed: () {
ref.read(remoteAlbumProvider.notifier).refresh(); ref.read(remoteAlbumProvider.notifier).refresh();
context.navigateTo(const TabShellRoute(children: [DriftAlbumsRoute()])); context.pop();
}, },
), ),
actions: [ actions: [
@@ -1,3 +1,4 @@
import 'package:auto_route/auto_route.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/asset/base_asset.model.dart'; import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
@@ -25,7 +26,7 @@ class _SelectionSliverAppBarState extends ConsumerState<SelectionSliverAppBar> {
onDone(Set<BaseAsset> selected) { onDone(Set<BaseAsset> selected) {
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
context.pop<Set<BaseAsset>>(selected); context.maybePop<Set<BaseAsset>>(selected);
} }
return SliverAppBar( return SliverAppBar(
+3 -2
View File
@@ -8,7 +8,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/entities/asset.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/collection_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart';
import 'package:immich_mobile/extensions/translate_extensions.dart';
import 'package:immich_mobile/models/map/map_event.model.dart'; import 'package:immich_mobile/models/map/map_event.model.dart';
import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/db.provider.dart';
import 'package:immich_mobile/providers/timeline.provider.dart'; import 'package:immich_mobile/providers/timeline.provider.dart';
@@ -230,7 +229,9 @@ class _MapSheetDragRegion extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final assetsInBoundsText = "map_assets_in_bounds".t(context: context, args: {'count': assetsInBoundCount}); final assetsInBoundsText = assetsInBoundCount > 0
? "map_assets_in_bounds".tr(namedArgs: {'count': assetsInBoundCount.toString()})
: "map_no_assets_in_bounds".tr();
return SingleChildScrollView( return SingleChildScrollView(
controller: controller, controller: controller,
@@ -2,13 +2,11 @@ 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/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/setting.provider.dart';
import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart'; import 'package:immich_mobile/widgets/settings/asset_list_settings/asset_list_group_settings.dart';
import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart';
import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_switch_list_tile.dart';
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
import 'asset_list_layout_settings.dart'; import 'asset_list_layout_settings.dart';
class AssetListSettings extends HookConsumerWidget { class AssetListSettings extends HookConsumerWidget {
@@ -22,10 +20,7 @@ class AssetListSettings extends HookConsumerWidget {
SettingsSwitchListTile( SettingsSwitchListTile(
valueNotifier: showStorageIndicator, valueNotifier: showStorageIndicator,
title: 'theme_setting_asset_list_storage_indicator_title'.tr(), title: 'theme_setting_asset_list_storage_indicator_title'.tr(),
onChanged: (_) { onChanged: (_) => ref.invalidate(appSettingsServiceProvider),
ref.invalidate(appSettingsServiceProvider);
ref.invalidate(settingsProvider);
},
), ),
const LayoutSettings(), const LayoutSettings(),
const GroupSettings(), const GroupSettings(),
+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: This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.137.3 - API version: 1.137.2
- 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
View File
@@ -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.137.3+3002 version: 1.137.2+3002
environment: environment:
sdk: '>=3.8.0 <4.0.0' sdk: '>=3.8.0 <4.0.0'
+1 -1
View File
@@ -9469,7 +9469,7 @@
"info": { "info": {
"title": "Immich", "title": "Immich",
"description": "Immich API", "description": "Immich API",
"version": "1.137.3", "version": "1.137.2",
"contact": {} "contact": {}
}, },
"tags": [], "tags": [],
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.137.3", "version": "1.137.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.137.3", "version": "1.137.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.137.3", "version": "1.137.2",
"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 -1
View File
@@ -1,6 +1,6 @@
/** /**
* Immich * Immich
* 1.137.3 * 1.137.2
* 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
*/ */
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich", "name": "immich",
"version": "1.137.3", "version": "1.137.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich", "name": "immich",
"version": "1.137.3", "version": "1.137.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@nestjs/bullmq": "^11.0.1", "@nestjs/bullmq": "^11.0.1",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich", "name": "immich",
"version": "1.137.3", "version": "1.137.2",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
@@ -16,7 +16,9 @@ export class AssetUploadInterceptor implements NestInterceptor {
const res = context.switchToHttp().getResponse<Response<AssetMediaResponseDto>>(); const res = context.switchToHttp().getResponse<Response<AssetMediaResponseDto>>();
const checksum = fromMaybeArray(req.headers[ImmichHeader.Checksum]); const checksum = fromMaybeArray(req.headers[ImmichHeader.Checksum]);
console.log('AssetUploadInterceptor checksum:', checksum);
const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum); const response = await this.service.getUploadAssetIdByChecksum(req.user, checksum);
console.log('AssetUploadInterceptor response:', response);
if (response) { if (response) {
res.status(200); res.status(200);
return of({ status: AssetMediaStatus.DUPLICATE, id: response.id }); return of({ status: AssetMediaStatus.DUPLICATE, id: response.id });
@@ -103,6 +103,23 @@ export class FileUploadInterceptor implements NestInterceptor {
} }
private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) {
console.log('File upload started:', file.originalname);
request.on('data', () => {
console.log('Data event triggered for file upload:', file.originalname);
});
request.on('close', () => {
console.log('Request closed');
});
request.on('aborted', () => {
console.log('Request aborted, cleaning up file');
this.defaultStorage._removeFile(request, file, (error) => {
if (error) {
this.logger.warn('Request aborted, failed to cleanup file', error);
} else {
this.logger.log('Request aborted, file cleaned up successfully');
}
});
});
return callbackify( return callbackify(
() => this.assetService.getUploadFilename(asRequest(request, file)), () => this.assetService.getUploadFilename(asRequest(request, file)),
callback as Callback<string>, callback as Callback<string>,
@@ -128,8 +145,15 @@ export class FileUploadInterceptor implements NestInterceptor {
const hash = createHash('sha1'); const hash = createHash('sha1');
file.stream.on('data', (chunk) => hash.update(chunk)); file.stream.on('data', (chunk) => hash.update(chunk));
file.stream.on('error', (error) => {
this.logger.warn('Stream error while uploading file, cleaning up', error);
this.assetService.onUploadError(request, file).catch(this.logger.error);
callback(error);
});
console.log('File upload started:', file.originalname);
this.defaultStorage._handleFile(request, file, (error, info) => { this.defaultStorage._handleFile(request, file, (error, info) => {
if (error) { if (error) {
console.error('Error handling file upload:', error);
hash.destroy(); hash.destroy();
callback(error); callback(error);
} else { } else {
@@ -131,6 +131,7 @@ export class AssetMediaService extends BaseService {
sidecarFile?: UploadFile, sidecarFile?: UploadFile,
): Promise<AssetMediaResponseDto> { ): Promise<AssetMediaResponseDto> {
try { try {
console.log(`Uploading asset: ${file.originalPath}, size: ${file.size}`);
await this.requireAccess({ await this.requireAccess({
auth, auth,
permission: Permission.AssetUpload, permission: Permission.AssetUpload,
@@ -138,20 +139,25 @@ export class AssetMediaService extends BaseService {
ids: [auth.user.id], ids: [auth.user.id],
}); });
console.log(`User quota: ${auth.user.quotaSizeInBytes}, usage: ${auth.user.quotaUsageInBytes}`);
this.requireQuota(auth, file.size); this.requireQuota(auth, file.size);
console.log(`Asset type: ${file.originalName}, checksum: ${file.checksum}`);
if (dto.livePhotoVideoId) { if (dto.livePhotoVideoId) {
await onBeforeLink( await onBeforeLink(
{ asset: this.assetRepository, event: this.eventRepository }, { asset: this.assetRepository, event: this.eventRepository },
{ userId: auth.user.id, livePhotoVideoId: dto.livePhotoVideoId }, { userId: auth.user.id, livePhotoVideoId: dto.livePhotoVideoId },
); );
} }
console.log(`Creating asset with deviceAssetId: ${dto.deviceAssetId}, deviceId: ${dto.deviceId}`);
const asset = await this.create(auth.user.id, dto, file, sidecarFile); const asset = await this.create(auth.user.id, dto, file, sidecarFile);
console.log(`Asset created with id: ${asset.id}, originalPath: ${asset.originalPath}`);
await this.userRepository.updateUsage(auth.user.id, file.size); await this.userRepository.updateUsage(auth.user.id, file.size);
return { id: asset.id, status: AssetMediaStatus.CREATED }; return { id: asset.id, status: AssetMediaStatus.CREATED };
} catch (error: any) { } catch (error: any) {
console.log(`Error uploading asset: ${error.message}, ${file.originalPath}`, error);
return this.handleUploadError(error, auth, file, sidecarFile); return this.handleUploadError(error, auth, file, sidecarFile);
} }
} }
+1 -1
View File
@@ -101,7 +101,7 @@ const validateRange = (value: number | undefined, min: number, max: number): Non
return null; return null;
} }
return Math.round(val); return val;
}; };
const getLensModel = (exifTags: ImmichTags): string | null => { const getLensModel = (exifTags: ImmichTags): string | null => {
-4
View File
@@ -101,10 +101,6 @@ export class StorageService extends BaseService {
const savedValue = await this.systemMetadataRepository.get(SystemMetadataKey.MediaLocation); const savedValue = await this.systemMetadataRepository.get(SystemMetadataKey.MediaLocation);
let previous = savedValue?.location || ''; let previous = savedValue?.location || '';
if (!previous && this.configRepository.getEnv().storage.mediaLocation) {
previous = current;
}
if (!previous) { if (!previous) {
previous = path.startsWith('upload/') ? 'upload' : '/usr/src/app/upload'; previous = path.startsWith('upload/') ? 'upload' : '/usr/src/app/upload';
} }
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.137.3", "version": "1.137.2",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "immich-web", "name": "immich-web",
"version": "1.137.3", "version": "1.137.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@formatjs/icu-messageformat-parser": "^2.9.8", "@formatjs/icu-messageformat-parser": "^2.9.8",
@@ -94,7 +94,7 @@
}, },
"../open-api/typescript-sdk": { "../open-api/typescript-sdk": {
"name": "@immich/sdk", "name": "@immich/sdk",
"version": "1.137.3", "version": "1.137.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"dependencies": { "dependencies": {
"@oazapfts/runtime": "^1.0.2" "@oazapfts/runtime": "^1.0.2"
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "immich-web", "name": "immich-web",
"version": "1.137.3", "version": "1.137.2",
"license": "GNU Affero General Public License version 3", "license": "GNU Affero General Public License version 3",
"type": "module", "type": "module",
"scripts": { "scripts": {
@@ -63,9 +63,8 @@
touchmoveTwoFingers: false, touchmoveTwoFingers: false,
mousewheelCtrlKey: false, mousewheelCtrlKey: false,
navbar, navbar,
minFov: 15, minFov: 10,
maxFov: 90, maxFov: 120,
zoomSpeed: 0.5,
fisheye: false, fisheye: false,
}); });
const resolutionPlugin = viewer.getPlugin(ResolutionPlugin) as ResolutionPlugin; const resolutionPlugin = viewer.getPlugin(ResolutionPlugin) as ResolutionPlugin;
@@ -97,7 +97,7 @@
{@render children?.()} {@render children?.()}
</div> </div>
<div class="max-[350px]:me-0 max-[350px]:gap-0 me-4 flex place-items-center gap-1 justify-self-end"> <div class="me-4 flex place-items-center gap-1 justify-self-end">
{@render trailing?.()} {@render trailing?.()}
</div> </div>
</nav> </nav>
@@ -230,8 +230,7 @@
type="text" type="text"
name="q" name="q"
id="main-search-bar" id="main-search-bar"
class="w-full transition-all border-2 ps-14 py-4 max-md:py-2 text-immich-fg/75 dark:text-immich-dark-fg class="w-full transition-all border-2 px-14 py-4 max-md:py-2 text-immich-fg/75 dark:text-immich-dark-fg
{showClearIcon ? 'pe-[90px]' : 'pe-14'}
{grayTheme ? 'dark:bg-immich-dark-gray' : 'dark:bg-immich-dark-bg'} {grayTheme ? 'dark:bg-immich-dark-gray' : 'dark:bg-immich-dark-bg'}
{showSuggestions && isSearchSuggestions ? 'rounded-t-3xl' : 'rounded-3xl bg-gray-200'} {showSuggestions && isSearchSuggestions ? 'rounded-t-3xl' : 'rounded-3xl bg-gray-200'}
{searchStore.isSearchEnabled ? 'border-gray-200 dark:border-gray-700 bg-white' : 'border-transparent'}" {searchStore.isSearchEnabled ? 'border-gray-200 dark:border-gray-700 bg-white' : 'border-transparent'}"
@@ -286,7 +285,6 @@
{#if isFocus} {#if isFocus}
<div <div
class="absolute inset-y-0 flex items-center" class="absolute inset-y-0 flex items-center"
class:max-md:hidden={value}
class:end-16={isFocus} class:end-16={isFocus}
class:end-28={isFocus && value.length > 0} class:end-28={isFocus && value.length > 0}
> >