mirror of
https://github.com/immich-app/immich.git
synced 2026-07-21 05:44:35 +03:00
Compare commits
7 Commits
renovate/n
...
fix/librar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65365f3556 | ||
|
|
3f6897ef80 | ||
|
|
89d0a9d59f | ||
|
|
45a6ea84af | ||
|
|
bf64f3867b | ||
|
|
4a4d468aa2 | ||
|
|
41a2fa8a44 |
30
mise.lock
30
mise.lock
@@ -212,36 +212,36 @@ checksum = "sha256:b6c17e747ae78cdd6de4d7532b3164b277daee97c007d3eaa2b39cca99882
|
||||
url = "https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_windows-x64_bin.zip"
|
||||
|
||||
[[tools.node]]
|
||||
version = "24.18.0"
|
||||
version = "24.15.0"
|
||||
backend = "core:node"
|
||||
|
||||
[tools.node."platforms.linux-arm64"]
|
||||
checksum = "sha256:6b4484c2190274175df9aa8f28e2d758a819cb1c1fe6ab481e2f95b463ab8508"
|
||||
url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-arm64.tar.gz"
|
||||
checksum = "sha256:73afc234d558c24919875f51c2d1ea002a2ada4ea6f83601a383869fefa64eed"
|
||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-arm64.tar.gz"
|
||||
|
||||
[tools.node."platforms.linux-arm64-musl"]
|
||||
checksum = "sha256:b1c6c2dc31b46dd8fb2322f4fe75b07e775c5120bc37251deeea28f529d4567b"
|
||||
url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-arm64-musl.tar.gz"
|
||||
checksum = "sha256:31e98aa960a067da91edffd5d93bc46657b5d2a8029612c359f5f2ac0060152a"
|
||||
url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64-musl.tar.gz"
|
||||
|
||||
[tools.node."platforms.linux-x64"]
|
||||
checksum = "sha256:783130984963db7ba9cbd01089eaf2c2efb055c7c1693c943174b967b3050cb8"
|
||||
url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.gz"
|
||||
checksum = "sha256:44836872d9aec49f1e6b52a9a922872db9a2b02d235a616a5681b6a85fec8d89"
|
||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-linux-x64.tar.gz"
|
||||
|
||||
[tools.node."platforms.linux-x64-musl"]
|
||||
checksum = "sha256:ea58409911e141ec6b19d9178efa2d9185a13295005b1cbf5521b3157eed1d95"
|
||||
url = "https://unofficial-builds.nodejs.org/download/release/v24.18.0/node-v24.18.0-linux-x64-musl.tar.gz"
|
||||
checksum = "sha256:f55af5bd489c5347b113ca6594cae00a54b30ba57ac5875324311bfc6f4762e3"
|
||||
url = "https://unofficial-builds.nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64-musl.tar.gz"
|
||||
|
||||
[tools.node."platforms.macos-arm64"]
|
||||
checksum = "sha256:e1a97e14c99c803e96c7339403282ea05a499c32f8d83defe9ef5ec66f979ed1"
|
||||
url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-arm64.tar.gz"
|
||||
checksum = "sha256:372331b969779ab5d15b949884fc6eaf88d5afe87bde8ba881d6400b9100ffc4"
|
||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz"
|
||||
|
||||
[tools.node."platforms.macos-x64"]
|
||||
checksum = "sha256:dfd0dbd3e721503434df7b7205e719f61b3a3a31b2bcf9729b8b91fea240f080"
|
||||
url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-darwin-x64.tar.gz"
|
||||
checksum = "sha256:ffd5ee293467927f3ee731a553eb88fd1f48cf74eebc2d74a6babe4af228673b"
|
||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-darwin-x64.tar.gz"
|
||||
|
||||
[tools.node."platforms.windows-x64"]
|
||||
checksum = "sha256:0ae68406b42d7725661da979b1403ec9926da205c6770827f33aac9d8f26e821"
|
||||
url = "https://nodejs.org/dist/v24.18.0/node-v24.18.0-win-x64.zip"
|
||||
checksum = "sha256:cc5149eabd53779ce1e7bdc5401643622d0c7e6800ade18928a767e940bb0e62"
|
||||
url = "https://nodejs.org/dist/v24.15.0/node-v24.15.0-win-x64.zip"
|
||||
|
||||
[[tools."npm:oazapfts"]]
|
||||
version = "7.5.0"
|
||||
|
||||
@@ -15,7 +15,7 @@ config_roots = [
|
||||
]
|
||||
|
||||
[tools]
|
||||
node = "24.18.0"
|
||||
node = "24.15.0"
|
||||
pnpm = "11.11.0"
|
||||
terragrunt = "1.0.3"
|
||||
opentofu = "1.11.6"
|
||||
|
||||
@@ -586,7 +586,7 @@
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
||||
shellScript = "/bin/bash \"$SRCROOT/scripts/xcode_flutter_build.sh\"\n";
|
||||
};
|
||||
BAEA01ACA3F5C9CD3D732370 /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
|
||||
45
mobile/ios/scripts/xcode_flutter_build.sh
Executable file
45
mobile/ios/scripts/xcode_flutter_build.sh
Executable file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Makes Flutter's builds through the Xcode GUI properly display errors and warnings
|
||||
# in the Issue navigator
|
||||
#
|
||||
# Flutter's `xcode_backend.dart` runs `flutter assemble` with `allowFail: true`,
|
||||
# which intentionally does not prefix output with `error:`. Unsure why they do this,
|
||||
# but this script rebuilds the expected output so Xcode can parse and display the errors
|
||||
|
||||
set -o pipefail
|
||||
|
||||
# The Immich mobile root (containing the Dart `lib` directory). This is used to make
|
||||
# absolute paths for Xcode linking
|
||||
app_root="${FLUTTER_APPLICATION_PATH:-$SRCROOT/..}"
|
||||
|
||||
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build 2>&1 \
|
||||
| awk -v app_root="$app_root" '
|
||||
# Match Dart CFE diagnostics: <path>.dart:<line>:<col>: <Kind>: <message>
|
||||
# Written for macOS/POSIX/BSD awk
|
||||
{
|
||||
# Always pass the original line through to preserve the original build log
|
||||
print
|
||||
|
||||
if ($0 ~ /^.*\.dart:[0-9]+:[0-9]+: (Error|Warning|Context|Info):/) {
|
||||
# Locate the ": Kind:" separator to split location from message.
|
||||
rest = $0
|
||||
if (match(rest, /: Error:/)) { kind = "Error"; keyword = "error" }
|
||||
else if (match(rest, /: Warning:/)) { kind = "Warning"; keyword = "warning" }
|
||||
else if (match(rest, /: Context:/)) { kind = "Context"; keyword = "note" }
|
||||
else if (match(rest, /: Info:/)) { kind = "Info"; keyword = "note" }
|
||||
|
||||
# location = everything before ": Kind:" (e.g. "lib/foo.dart:12:5")
|
||||
location = substr(rest, 1, RSTART - 1)
|
||||
# message = everything after ": Kind:" (leading space preserved)
|
||||
message = substr(rest, RSTART + length(": " kind ":"))
|
||||
|
||||
# Make the path absolute so Xcode links to it
|
||||
if (location !~ /^\//)
|
||||
location = app_root "/" location
|
||||
|
||||
printf "%s: %s:%s\n", location, keyword, message
|
||||
}
|
||||
}
|
||||
'
|
||||
|
||||
exit "${PIPESTATUS[0]}"
|
||||
@@ -85,8 +85,12 @@ class TimelineFactory {
|
||||
TimelineService fromAssetsWithBuckets(List<BaseAsset> assets, TimelineOrigin type) =>
|
||||
TimelineService(_timelineRepository.fromAssetsWithBuckets(assets, type));
|
||||
|
||||
TimelineService map(List<String> userIds, TimelineMapOptions options) =>
|
||||
TimelineService(_timelineRepository.map(userIds, options, groupBy));
|
||||
/// Creates a TimelineService for serving geographical map queries, such assets within bounded locations
|
||||
TimelineService geographicMap(
|
||||
List<String> userIds,
|
||||
TimelineMapOptions Function() currentOptions,
|
||||
Stream<TimelineMapOptions> optionsStream,
|
||||
) => TimelineService(_timelineRepository.geographicMap(userIds, currentOptions, optionsStream, groupBy));
|
||||
}
|
||||
|
||||
class TimelineService {
|
||||
|
||||
@@ -508,9 +508,21 @@ class DriftTimelineRepository extends DriftDatabaseRepository {
|
||||
return query.map((row) => row.toDto()).get();
|
||||
}
|
||||
|
||||
TimelineQuery map(List<String> userIds, TimelineMapOptions options, GroupAssetsBy groupBy) => (
|
||||
bucketSource: () => _watchMapBucket(userIds, options, groupBy: groupBy),
|
||||
assetSource: (offset, count) => _getMapBucketAssets(userIds, options, offset: offset, count: count),
|
||||
/// Creates a geographic map query that can dynamically filter on changing [TimelineMapOptions]
|
||||
/// (most notably the active map bounds)
|
||||
TimelineQuery geographicMap(
|
||||
List<String> userIds,
|
||||
TimelineMapOptions Function() currentOptions,
|
||||
Stream<TimelineMapOptions> optionsStream,
|
||||
GroupAssetsBy groupBy,
|
||||
) => (
|
||||
bucketSource: () => Stream.value(currentOptions())
|
||||
.followedBy(optionsStream)
|
||||
.switchMap(
|
||||
// Any error would kill the stream for all options; make sure the stream stays alive
|
||||
(options) => _watchMapBucket(userIds, options, groupBy: groupBy).handleError((_) {}),
|
||||
),
|
||||
assetSource: (offset, count) => _getMapBucketAssets(userIds, currentOptions(), offset: offset, count: count),
|
||||
origin: TimelineOrigin.map,
|
||||
);
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
||||
}
|
||||
case AppLifecycleState.paused:
|
||||
_shouldPlayOnForeground = await _controller?.isPlaying() ?? true;
|
||||
if (_shouldPlayOnForeground) {
|
||||
if (_shouldPlayOnForeground && mounted) {
|
||||
await _notifier.pause();
|
||||
}
|
||||
default:
|
||||
@@ -268,10 +268,13 @@ class _NativeVideoViewerState extends ConsumerState<NativeVideoViewer> with Widg
|
||||
return;
|
||||
}
|
||||
|
||||
await _notifier.load(source);
|
||||
// Grab refs to prevent reading after dispose
|
||||
final loopVideo = ref.read(appConfigProvider).viewer.loopVideo;
|
||||
await _notifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
|
||||
await _notifier.setVolume(1);
|
||||
final localNotifier = _notifier;
|
||||
|
||||
await localNotifier.load(source);
|
||||
await localNotifier.setLoop(!widget.asset.isMotionPhoto && loopVideo);
|
||||
await localNotifier.setVolume(1);
|
||||
}
|
||||
|
||||
void _initController(NativeVideoPlayerController nc) {
|
||||
|
||||
@@ -35,7 +35,6 @@ class _ScopedMapTimeline extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// TODO: this causes the timeline to switch to flicker to "loading" state and back. This is both janky and inefficient.
|
||||
return ProviderScope(
|
||||
overrides: [
|
||||
timelineServiceProvider.overrideWith((ref) {
|
||||
@@ -44,13 +43,16 @@ class _ScopedMapTimeline extends StatelessWidget {
|
||||
throw Exception('User must be logged in to access archive');
|
||||
}
|
||||
|
||||
final users = ref.watch(mapStateProvider).withPartners
|
||||
? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id]
|
||||
: [user.id];
|
||||
final withPartners = ref.watch(mapStateProvider.select((s) => s.withPartners));
|
||||
final users = withPartners ? ref.watch(timelineUsersProvider).valueOrNull ?? [user.id] : [user.id];
|
||||
|
||||
final timelineService = ref
|
||||
.watch(timelineFactoryProvider)
|
||||
.map(users, ref.watch(mapStateProvider).toOptions());
|
||||
.geographicMap(
|
||||
users,
|
||||
() => ref.read(mapStateProvider).toOptions(),
|
||||
ref.read(mapStateProvider.notifier).optionsStream,
|
||||
);
|
||||
ref.onDispose(timelineService.dispose);
|
||||
return timelineService;
|
||||
}),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||
@@ -63,11 +65,16 @@ class MapState {
|
||||
class MapStateNotifier extends Notifier<MapState> {
|
||||
MapStateNotifier();
|
||||
|
||||
final StreamController<TimelineMapOptions> _optionsController = StreamController.broadcast();
|
||||
|
||||
Stream<TimelineMapOptions> get optionsStream => _optionsController.stream;
|
||||
|
||||
bool setBounds(LatLngBounds bounds) {
|
||||
if (state.bounds == bounds) {
|
||||
return false;
|
||||
}
|
||||
state = state.copyWith(bounds: bounds);
|
||||
_optionsController.add(state.toOptions());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -82,12 +89,14 @@ class MapStateNotifier extends Notifier<MapState> {
|
||||
void switchFavoriteOnly(bool isFavoriteOnly) {
|
||||
ref.read(settingsProvider).write(.mapShowFavoriteOnly, isFavoriteOnly);
|
||||
state = state.copyWith(onlyFavorites: isFavoriteOnly);
|
||||
_optionsController.add(state.toOptions());
|
||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||
}
|
||||
|
||||
void switchIncludeArchived(bool isIncludeArchived) {
|
||||
ref.read(settingsProvider).write(.mapIncludeArchived, isIncludeArchived);
|
||||
state = state.copyWith(includeArchived: isIncludeArchived);
|
||||
_optionsController.add(state.toOptions());
|
||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||
}
|
||||
|
||||
@@ -100,11 +109,13 @@ class MapStateNotifier extends Notifier<MapState> {
|
||||
void setRelativeTime(int relativeDays) {
|
||||
ref.read(settingsProvider).write(.mapRelativeDate, relativeDays);
|
||||
state = state.copyWith(relativeDays: relativeDays);
|
||||
_optionsController.add(state.toOptions());
|
||||
EventStream.shared.emit(const MapMarkerReloadEvent());
|
||||
}
|
||||
|
||||
@override
|
||||
MapState build() {
|
||||
ref.onDispose(_optionsController.close);
|
||||
final mapConfig = ref.read(appConfigProvider.select((config) => config.map));
|
||||
return MapState(
|
||||
themeMode: mapConfig.themeMode,
|
||||
|
||||
@@ -11,17 +11,20 @@ import 'package:immich_mobile/domain/models/log.model.dart';
|
||||
import 'package:immich_mobile/domain/models/settings_key.dart';
|
||||
import 'package:immich_mobile/domain/models/store.model.dart';
|
||||
import 'package:immich_mobile/domain/models/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/services/feature_message.service.dart';
|
||||
import 'package:immich_mobile/entities/store.entity.dart';
|
||||
import 'package:immich_mobile/infrastructure/entities/settings.entity.drift.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/db.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/network.repository.dart';
|
||||
import 'package:immich_mobile/infrastructure/repositories/settings.repository.dart';
|
||||
import 'package:immich_mobile/models/auth/auxilary_endpoint.model.dart';
|
||||
import 'package:immich_mobile/providers/album/album_sort_by_options.provider.dart';
|
||||
|
||||
const int targetVersion = 26;
|
||||
|
||||
Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
final int version = Store.get(StoreKey.version, targetVersion);
|
||||
final int? storedVersion = Store.tryGet(StoreKey.version);
|
||||
final version = storedVersion ?? targetVersion;
|
||||
|
||||
if (version < 25) {
|
||||
await _migrateTo25();
|
||||
@@ -31,6 +34,10 @@ Future<void> migrateDatabaseIfNeeded(Drift drift) async {
|
||||
await _migrateTo26(drift);
|
||||
}
|
||||
|
||||
if (storedVersion == null) {
|
||||
await FeatureMessageService(SettingsRepository.instance).markSeen();
|
||||
}
|
||||
|
||||
await Store.put(StoreKey.version, targetVersion);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,8 @@ export const openFilePicker = async (options: FilePickerParam = {}) => {
|
||||
fileSelector.addEventListener(
|
||||
'change',
|
||||
(e: Event) => {
|
||||
fileSelector.remove();
|
||||
|
||||
const target = e.target as HTMLInputElement;
|
||||
if (!target.files) {
|
||||
return;
|
||||
@@ -74,6 +76,11 @@ export const openFilePicker = async (options: FilePickerParam = {}) => {
|
||||
{ passive: true },
|
||||
);
|
||||
|
||||
fileSelector.addEventListener('cancel', () => fileSelector.remove(), { passive: true });
|
||||
|
||||
// Safari requires the file selector to be mounted
|
||||
fileSelector.hidden = true;
|
||||
document.body.append(fileSelector);
|
||||
fileSelector.click();
|
||||
} catch (error) {
|
||||
console.log('Error selecting file', error);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto, invalidateAll } from '$app/navigation';
|
||||
import ActionMenuItem from '$lib/components/ActionMenuItem.svelte';
|
||||
import OnEvents from '$lib/components/OnEvents.svelte';
|
||||
import UserPageLayout, { headerId } from '$lib/components/layouts/UserPageLayout.svelte';
|
||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/ButtonContextMenu.svelte';
|
||||
import GalleryViewer from '$lib/components/shared-components/gallery-viewer/GalleryViewer.svelte';
|
||||
@@ -92,6 +93,8 @@
|
||||
</Sidebar>
|
||||
{/snippet}
|
||||
|
||||
<OnEvents onAssetsDelete={invalidateAll} />
|
||||
|
||||
<Breadcrumbs node={data.tree} icon={mdiFolderHome} title={$t('folders')} getLink={getLinkForPath} />
|
||||
|
||||
<section class="mt-2 h-[calc(100%-(--spacing(25)))] immich-scrollbar overflow-auto">
|
||||
|
||||
Reference in New Issue
Block a user