Compare commits

..

1 Commits

Author SHA1 Message Date
shenlong-tanwen
a797cd6cf9 fix: incorrect asset viewer scale on image frame update 2026-01-22 00:53:42 +05:30
54 changed files with 140 additions and 159 deletions

View File

@@ -5,98 +5,53 @@ import TabItem from '@theme/TabItem';
A [3-2-1 backup strategy](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) is recommended to protect your data. You should keep copies of your uploaded photos/videos as well as the Immich database for a comprehensive backup solution. This page provides an overview on how to backup the database and the location of user-uploaded pictures and videos. A template bash script that can be run as a cron job is provided [here](/guides/template-backup-script.md)
:::danger
The instructions on this page show you how to prepare your Immich instance to be backed up, and which files to take a backup of. You still need to take care of using an actual backup tool to make a backup yourself.
:::
## Database
Immich stores [file paths in the database](https://github.com/immich-app/immich/discussions/3299), users metadata in the database, it does not scan the library folder, so database backups are essential
:::caution
Immich saves [file paths in the database](https://github.com/immich-app/immich/discussions/3299), it does not scan the library folder to update the database so backups are crucial.
:::
### Automatic Database Backups
Immich automatically creates database backups for disaster-recovery purposes. These backups are stored in `UPLOAD_LOCATION/backups` and can be managed through the web interface.
You can adjust the backup schedule and retention settings in **Administration > Settings > Backup** (default: keep last 14 backups, create daily at 2:00 AM).
:::info
Refer to the official [postgres documentation](https://www.postgresql.org/docs/current/backup.html) for details about backing up and restoring a postgres database.
:::
:::caution
Database backups do **NOT** contain photos or videos — only metadata. They must be used together with a copy of the files in `UPLOAD_LOCATION` as outlined below.
It is not recommended to directly backup the `DB_DATA_LOCATION` folder. Doing so while the database is running can lead to a corrupted backup that cannot be restored.
:::
#### Creating a Backup
You can trigger a database backup manually:
1. Go to **Administration > Queues**
2. Click **Create job** in the top right
3. Select **Create Database Backup** and click **Confirm**
The backup will appear in `UPLOAD_LOCATION/backups` and counts toward your retention limit.
### Restoring a Database Backup
Immich provides two ways to restore a database backup: through the web interface or via the command line. The web interface is the recommended method for most users.
### Restore from Settings {#restore-from-settings}
If you have an existing Immich installation:
1. Go to **Administration > Maintenance**
2. Expand the **Restore database backup** section
3. You'll see a list of available backups with their version and creation date
4. Click **Restore** next to the backup you want to restore
5. Confirm the restore operation
### Automatic Database Dumps
:::warning
Restoring a backup will wipe the current database and replace it with the backup. A restore point is automatically created before the operation begins, allowing rollback if the restore fails.
The automatic database dumps can be used to restore the database in the event of damage to the Postgres database files.
There is no monitoring for these dumps and you will not be notified if they are unsuccessful.
:::
### Restore from Onboarding {#restore-from-onboarding}
If you're setting up Immich on a fresh installation and want to restore from an existing backup:
1. On the welcome screen, click **Restore from backup**
2. Immich will enter maintenance mode and display integrity checks for your storage folders
3. Review the folder status to ensure your library files are accessible
4. Click **Next** to proceed to backup selection
5. Select a backup from the list or upload a backup file (`.sql.gz`)
6. Click **Restore** to begin the restoration process
:::tip
Before restoring, ensure your `UPLOAD_LOCATION` folders contain the same files that existed when the backup was created. The integrity check will show you which folders are readable/writable and how many files they contain.
:::caution
The database dumps do **NOT** contain any pictures or videos, only metadata. They are only usable with a copy of the other files in `UPLOAD_LOCATION` as outlined below.
:::
### Uploading a Backup File {#uploading-backup}
For disaster-recovery purposes, Immich will automatically create database dumps. The dumps are stored in `UPLOAD_LOCATION/backups`.
Please be sure to make your own, independent backup of the database together with the asset folders as noted below.
You can adjust the schedule and amount of kept database dumps in the [admin settings](http://my.immich.app/admin/system-settings?isOpen=backup).
By default, Immich will keep the last 14 database dumps and create a new dump every day at 2:00 AM.
You can upload a database backup file directly:
#### Trigger Dump
1. In the **Restore database backup** section, click **Select from computer**
2. Choose a `.sql.gz` file
3. The uploaded backup will appear in the list with an "uploaded-" prefix
4. Click **Restore** to restore from the uploaded file
You are able to trigger a database dump in the [admin job status page](http://my.immich.app/admin/queues).
Visit the page, open the "Create job" modal from the top right, select "Create Database Dump" and click "Confirm".
A job will run and trigger a dump, you can verify this worked correctly by checking the logs or the `backups/` folder.
This dumps will count towards the last `X` dumps that will be kept based on your settings.
### Backup Version Compatibility {#backup-compatibility}
#### Restoring
When viewing backups, Immich displays compatibility indicators:
We hope to make restoring simpler in future versions, for now you can find the database dumps in the `UPLOAD_LOCATION/backups` folder on your host.
Then please follow the steps in the following section for restoring the database.
-**Green checkmark**: Backup version matches current Immich version
- ⚠️ **Warning**: Backup was created with a different Immich version
-**Error**: Could not determine backup version
:::warning
Restoring a backup from a different Immich version may require database migrations. The restore process will attempt to run migrations automatically, but you should ensure you're restoring to a compatible version when possible.
:::
### Restore Process {#restore-process}
During restoration, Immich will:
1. Create a backup of the current database (restore point)
2. Restore the selected backup
3. Run database migrations if needed
4. Perform a health check to verify the restore succeeded
If the restore fails (e.g., corrupted backup or missing admin user), Immich will automatically roll back to the restore point.
### Restore via Command Line {#restore-cli}
For advanced users or automated recovery scenarios, you can restore a database backup using the command line.
### Manual Backup and Restore
<Tabs>
<TabItem value="Linux system" label="Linux system" default>
@@ -151,12 +106,10 @@ docker compose up -d # Start remainder of Immich ap
</TabItem>
</Tabs>
:::note
For the database restore to proceed properly, it requires a completely fresh install (i.e., the Immich server has never run since creating the Docker containers). If the Immich app has run, you may encounter Postgres conflicts (relation already exists, violated foreign key constraints, etc.). In this case, delete the `DB_DATA_LOCATION` folder to reset the database.
:::
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.), in which case you need to delete the `DB_DATA_LOCATION` folder to reset the database.
:::tip
Some deployment methods make it difficult to start the database without also starting the server. In these cases, set the environment variable `DB_SKIP_MIGRATIONS=true` before starting the services. This prevents the server from running migrations that interfere with the restore process. Remove this variable and restart services after the database is restored.
Some deployment methods make it difficult to start the database without also starting the server. In these cases, you may set the environment variable `DB_SKIP_MIGRATIONS=true` before starting the services. This will prevent the server from running migrations that interfere with the restore process. Be sure to remove this variable and restart the services after the database is restored.
:::
## Filesystem

View File

@@ -1,42 +1,37 @@
# Automatic Backup
## Overview
Immich supports uploading photos and videos from your mobile device to the server automatically.
By enable the backup button, Immich will upload new photos and videos from selected albums when you open or resume the app, as well as periodically in the background (iOS), or when the a new photos or videos are taken (Android).
---
<img
src={require('./img/enable-backup-button.webp').default}
width="300px"
title="Upload button"
/>
You can enable the settings by accessing the upload options from the upload page
## Platform Specific Features
<img src={require('./img/backup-settings-access.webp').default} width="50%" title="Backup option selection" />
### General
<img src={require('./img/background-foreground-backup.webp').default} width="50%" title="Foreground&Background Backup" />
By default, Immich will only upload photos and videos when connected to Wi-Fi. You can change this behavior in the backup settings page.
## Foreground backup
<img
src={require('./img/backup-options.webp').default}
width="500px"
title="Upload button"
/>
If foreground backup is enabled: whenever the app is opened or resumed, it will check if any photos or videos in the selected album(s) have yet to be uploaded to the cloud (the remainder count). If there are any, they will be uploaded.
### Android
## Background backup
<img
src={require('./img/android-backup-options.webp').default}
width="500px"
title="Upload button"
/>
This feature is intended for everyday use. For initial bulk uploading, please use the foreground upload feature. For more information on why background upload is not working as expected, please refer to the [FAQ](/FAQ#why-does-foreground-backup-stop-when-i-navigate-away-from-the-app-shouldnt-it-transfer-the-job-to-background-backup).
If background backup is enabled. The app will periodically check if there are any new photos or videos in the selected album(s) to be uploaded to the server. If there are, it will upload them to the cloud in the background.
:::info Note
#### General
- The app must be in the background for the backup worker to start running.
- If you reopen the app and the first page you see is the backup page, the counts will not reflect the background uploaded result. You have to navigate out of the page and come back to see the updated counts.
#### Android
- It is a well-known problem that some Android models are very strict with battery optimization settings, which can cause a problem with the background worker. Please visit [Don't kill my app](https://dontkillmyapp.com/) for a guide on disabling this setting on your phone.
- You can allow the background task to run when the device is charging.
- You can set the minimum delay from the time a photo is taken to when the background upload task will run.
### iOS
#### iOS
- You must enable **Background App Refresh** for the app to work in the background. You can enable it in the Settings app under General > Background App Refresh.
@@ -44,4 +39,4 @@ title="Upload button"
<img src={require('./img/background-app-refresh.webp').default} width="30%" title="background-app-refresh" />
</div>
- iOS automatically manages background tasks, the app cannot control when the background upload task will run. It is known that the more frequently you open the app, the more often the background task will run.
:::

View File

@@ -188,8 +188,6 @@ immich upload --dry-run . | tail -n +6 | jq .newFiles[]
### Obtain the API Key
The API key can be obtained in the user setting panel on the web interface. You can also specify permissions for the key to limit its access.
The API key can be obtained in the user setting panel on the web interface.
![Obtain Api Key](./img/obtain-api-key.webp)
![Specify permission for the key](./img/obtain-api-key-2.webp)

View File

@@ -21,14 +21,14 @@ The asset detail view will also show the faces that are recognized in the asset.
Additional actions you can do include:
- Changing the feature photo of the person
- Hiding the faces of a person from the Explore page and detail view
- Setting a person's date of birth, so that the age of the person can be shown at the time the photo was taken
- Setting a person's date of birth
- Merging two or more detected faces into one person
- Favoriting a person to pin them to the top of the list
- Hiding the faces of a person from the Explore page and detail view
- Assigning an unrecognized face to a person
It can be found from the app bar when you access the detail view of a person.
<img src={require('./img/facial-recognition-4.webp').default} title='Facial Recognition 4' />
<img src={require('./img/facial-recognition-4.webp').default} title='Facial Recognition 4' width="70%"/>
## How Face Detection Works

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 231 KiB

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -20,6 +20,14 @@ Below are the SHA-256 fingerprints for the certificates signing the android appl
:::
:::info Beta Program
The beta release channel allows users to test upcoming changes before they are officially released. To join the channel use the links below.
- Android: Invitation link from [web](https://play.google.com/store/apps/details?id=app.alextran.immich) or from [mobile](https://play.google.com/store/apps/details?id=app.alextran.immich)
- iOS: [TestFlight invitation link](https://testflight.apple.com/join/1vYsAa8P)
:::
## Login
<MobileAppLogin />
@@ -28,6 +36,10 @@ Below are the SHA-256 fingerprints for the certificates signing the android appl
<MobileAppBackup />
:::info
You can enable automatic backup on supported devices. For more information see [Automatic Backup](/features/automatic-backup.md).
:::
## Sync only selected photos
If you have a large number of photos on the device, and you would prefer not to backup all the photos, then it might be prudent to only backup selected photos from device to the Immich server.
@@ -45,6 +57,11 @@ This will enable a small cloud icon on the bottom right corner of the asset tile
Now make sure that the local album is selected in the backup screen (steps 1-2 above). You can find these albums listed in **<ins>Library -> On this device</ins>**. To selectively upload photos from these albums, simply select the local-only photos and tap on "Upload" button in the dynamic bottom menu.
<img
src={require('./img/mobile-upload-open-photo.webp').default}
width="50%"
title="Upload button on local asset preview"
/>
<img
src={require('./img/mobile-upload-selected-photos.webp').default}
width="40%"
@@ -57,32 +74,49 @@ The **Free Up Space** tool allows you to remove local media files from your devi
### How it works
<img src={require('./img/free-up-space.webp').default} title="Free up space" />
1. **Configuration:**
- **Cutoff Date:** You can select a cutoff date. The tool will only look for photos and videos **on or before** this date.
- **Filter Options:** You can choose to remove **All** assets, or restrict removal to **Photos only** or **Videos only**.
- **Keep Favorites:** By default, local assets marked as favorites are preserved on your device, even if they match the cutoff date.
2. **Scan & Review:** Before any files are removed, you are presented with a review screen to verify which items will be deleted.
3. **Deletion:** Confirmed items are moved to your device's native Trash/Recycle Bin.
3. **Deletion:** Confirmed items are moved to your device's native Trash/Recycle Bin. They will be permanently removed by the OS based on your system settings (usually after 30 days).
:::info reclaim storage
To permanently free up space, you must manually empty the system/gallery trash.
:::info Android Permissions
For the smoothest experience on Android, you should grant Immich special delete privileges. Without this, you may be prompted to confirm deletion for every single image.
Go to **Immich Settings > Advanced** and enable **"Media Management Access"**.
:::
### iCloud Photos
### iCloud Photos (iOS Users)
If you use **iCloud Photos** alongside Immich, it is vital to understand how deletion affects your data. iCloud utilizes a two-way sync; this means deleting a photo from your iPhone to free up space will **also delete it from iCloud**.
Assets that are part of an **iCloud Shared Album** are automatically excluded from the cleanup scan because iCloud does not allow removing the items from the device.
:::warning iCloud & Backups
If you rely on iCloud as a secondary backup (part of a 3-2-1 backup strategy), using the Free Up Space feature in Immich will remove the file from both your phone and iCloud.
Once deleted, the photo will exist **only** on your Immich server (and your phone's "Recently Deleted" folder for 30 days).
When you use iCloud Photos and delete a photo or video on one device, it's also deleted on all other devices where you're signed in with the same Apple Account.
More information on the [Apple Support](https://support.apple.com/en-us/108922#iCloud_photo_library) website
**Shared Albums**
Assets that are part of an **iCloud Shared Album** are automatically excluded from the cleanup scan to ensure they remain viewable to others in the shared album.
:::
### External App Dependencies (WhatsApp, etc.)
:::danger WhatsApp & Local Files
Android applications like **WhatsApp** rely on local files to display media in chat history.
If Immich backs up your WhatsApp folder and you run **Free Up Space**, the local copies of these images will be deleted. Consequently, **media in your WhatsApp chats will appear blurry or missing.** You will only be able to view these photos inside the Immich app; they will no longer be visible within the WhatsApp interface.
**Recommendation:** If keeping chat history intact is important, please ensure you review the deletion list carefully or consider excluding WhatsApp folders from the backup if you intend to use this feature frequently.
:::
:::info reclaim storage
You must empty the system/gallery trash manually to reclaim storage.
:::
## Album Sync

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@@ -17,11 +17,11 @@ If this does not work, try running `docker compose up -d --force-recreate`.
## Docker Compose
| Variable | Description | Default | Containers |
| :----------------- | :------------------------------ | :-----: | :----------------------- |
| `IMMICH_VERSION` | Image tags | `v2` | server, machine learning |
| `UPLOAD_LOCATION` | Host path for uploads | | server |
| `DB_DATA_LOCATION` | Host path for Postgres database | | database |
| Variable | Description | Default | Containers |
| :----------------- | :------------------------------ | :-------: | :----------------------- |
| `IMMICH_VERSION` | Image tags | `release` | server, machine learning |
| `UPLOAD_LOCATION` | Host path for uploads | | server |
| `DB_DATA_LOCATION` | Host path for Postgres database | | database |
:::tip
These environment variables are used by the `docker-compose.yml` file and do **NOT** affect the containers directly.

View File

@@ -10,7 +10,7 @@ to install and use it.
## Requirements
- A system with at least 6GB of RAM and 2 CPU cores.
- A system with at least 4GB of RAM and 2 CPU cores.
- [Docker](https://docs.docker.com/engine/install/)
> For a more detailed list of requirements, see the [requirements page](/install/requirements).
@@ -63,9 +63,9 @@ The backup time differs depending on how many photos are on your mobile device.
take quite a while.
To quickly get going, you can selectively upload few photos first, by following this [guide](/features/mobile-app#sync-only-selected-photos).
You can select the **Job Queues** tab to see Immich processing your photos.
You can select the **Jobs** tab to see Immich processing your photos.
<img src={require('/docs/guides/img/jobs-tab.webp').default} title="Job Queues Tah" width={300} />
<img src={require('/docs/guides/img/jobs-tab.webp').default} title="Jobs tab" width={300} />
---

View File

@@ -6,4 +6,4 @@
<img src={require('./img/album-selection.webp').default} width='50%' title='Backup button' />
3. Scroll down to the bottom and press "**Enable Backup**" to start the backup process. This will upload all the assets in the selected albums.
3. Scroll down to the bottom and press "**Start Backup**" to start the backup process. This will upload all the assets in the selected albums.

View File

@@ -2,6 +2,6 @@ If you have friends or family members who want to use the application as well, y
<img src={require('./img/create-new-user.webp').default} width="90%" title='New User Registration' />
In the **Administration > Users** page, you can click on the **Create user** button, and you'll be presented with the following dialog:
In the Administration panel, you can click on the **Create user** button, and you'll be presented with the following dialog:
<img src={require('./img/create-new-user-dialog.webp').default} width="40%" title='New User Registration Dialog' />
<img src={require('./img/create-new-user-dialog.webp').default} width="90%" title='New User Registration Dialog' />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

After

Width:  |  Height:  |  Size: 105 KiB

View File

@@ -76,10 +76,6 @@ const config = {
autoCollapseCategories: false,
},
},
tableOfContents: {
minHeadingLevel: 2,
maxHeadingLevel: 4,
},
navbar: {
logo: {
alt: 'Immich Logo',

View File

@@ -69,13 +69,7 @@ h6 {
--ifm-color-primary-lighter: #e9f1fe;
--ifm-color-primary-lightest: #ffffff;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
--ifm-navbar-background-color: #0c0c0c;
--ifm-footer-background-color: #0c0c0c;
}
[data-theme='dark'] body,
[data-theme='dark'] .main-wrapper {
background-color: #070707;
--ifm-background-color: #000000;
}
div[class^='announcementBar_'] {

View File

@@ -17,9 +17,9 @@ module.exports = {
// Dark Theme
'immich-dark-primary': '#adcbfa',
'immich-dark-bg': '#000000',
'immich-dark-bg': '#070a14',
'immich-dark-fg': '#e5e7eb',
'immich-dark-gray': '#111111',
'immich-dark-gray': '#212121',
},
},
},

View File

@@ -118,7 +118,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
bool dragInProgress = false;
bool shouldPopOnDrag = false;
bool assetReloadRequested = false;
double? initialScale;
double previousExtent = _kBottomSheetMinimumExtent;
Offset dragDownPosition = Offset.zero;
int totalAssets = 0;
@@ -264,7 +263,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
(context.height * bottomSheetController.size) - (context.height * _kBottomSheetMinimumExtent);
controller.position = Offset(0, -verticalOffset);
// Apply the zoom effect when the bottom sheet is showing
initialScale = controller.scale;
controller.scale = (controller.scale ?? 1.0) + 0.01;
}
}
@@ -316,7 +314,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
hasDraggedDown = null;
viewController?.animateMultiple(
position: initialPhotoViewState.position,
scale: initialPhotoViewState.scale,
scale: viewController?.initialScale ?? initialPhotoViewState.scale,
rotation: initialPhotoViewState.rotation,
);
ref.read(assetViewerProvider.notifier).setOpacity(255);
@@ -366,8 +364,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
final maxScaleDistance = ctx.height * 0.5;
final scaleReduction = (distance / maxScaleDistance).clamp(0.0, dragRatio);
double? updatedScale;
if (initialPhotoViewState.scale != null) {
updatedScale = initialPhotoViewState.scale! * (1.0 - scaleReduction);
double? initialScale = viewController?.initialScale ?? initialPhotoViewState.scale;
if (initialScale != null) {
updatedScale = initialScale * (1.0 - scaleReduction);
}
final backgroundOpacity = (255 * (1.0 - (scaleReduction / dragRatio))).round();
@@ -481,8 +480,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
void _openBottomSheet(BuildContext ctx, {double extent = _kBottomSheetMinimumExtent, bool activitiesMode = false}) {
ref.read(assetViewerProvider.notifier).setBottomSheet(true);
initialScale = viewController?.scale;
// viewController?.updateMultiple(scale: (viewController?.scale ?? 1.0) + 0.01);
previousExtent = _kBottomSheetMinimumExtent;
sheetCloseController = showBottomSheet(
context: ctx,
@@ -504,7 +501,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
void _handleSheetClose() {
viewController?.animateMultiple(position: Offset.zero);
viewController?.updateMultiple(scale: initialScale);
viewController?.updateMultiple(scale: viewController?.initialScale);
ref.read(assetViewerProvider.notifier).setBottomSheet(false);
sheetCloseController = null;
shouldPopOnDrag = false;

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:immich_mobile/widgets/photo_view/src/utils/ignorable_change_notifier.dart';
import 'package:immich_mobile/widgets/photo_view/src/utils/photo_view_utils.dart';
/// The interface in which controllers will be implemented.
///
@@ -62,6 +63,9 @@ abstract class PhotoViewControllerBase<T extends PhotoViewControllerValue> {
/// The scale factor to transform the child (image or a customChild).
late double? scale;
double? get initialScale;
ScaleBoundaries? scaleBoundaries;
/// Nevermind this method :D, look away
void setScaleInvisibly(double? scale);
@@ -141,6 +145,9 @@ class PhotoViewController implements PhotoViewControllerBase<PhotoViewController
late StreamController<PhotoViewControllerValue> _outputCtrl;
@override
ScaleBoundaries? scaleBoundaries;
late void Function(Offset)? _animatePosition;
late void Function(double)? _animateScale;
late void Function(double)? _animateRotation;
@@ -311,4 +318,7 @@ class PhotoViewController implements PhotoViewControllerBase<PhotoViewController
}
_valueNotifier.value = newValue;
}
@override
double? get initialScale => scaleBoundaries?.initialScale ?? initial.scale;
}

View File

@@ -108,6 +108,17 @@ class _ImageWrapperState extends State<ImageWrapper> {
}
}
// Should be called only when _imageSize is not null
ScaleBoundaries get scaleBoundaries {
return ScaleBoundaries(
widget.minScale ?? 0.0,
widget.maxScale ?? double.infinity,
widget.initialScale ?? PhotoViewComputedScale.contained,
widget.outerSize,
_imageSize!,
);
}
// retrieve image from the provider
void _resolveImage() {
final ImageStream newStream = widget.imageProvider.resolve(const ImageConfiguration());
@@ -133,6 +144,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
_lastStack = null;
_didLoadSynchronously = synchronousCall;
widget.controller.scaleBoundaries = scaleBoundaries;
}
synchronousCall && !_didLoadSynchronously ? setupCB() : setState(setupCB);
@@ -204,14 +216,6 @@ class _ImageWrapperState extends State<ImageWrapper> {
);
}
final scaleBoundaries = ScaleBoundaries(
widget.minScale ?? 0.0,
widget.maxScale ?? double.infinity,
widget.initialScale ?? PhotoViewComputedScale.contained,
widget.outerSize,
_imageSize!,
);
return PhotoViewCore(
imageProvider: widget.imageProvider,
backgroundDecoration: widget.backgroundDecoration,

View File

@@ -326,7 +326,7 @@ class _BackupDelaySliderState extends ConsumerState<_BackupDelaySlider> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 24.0, top: 8.0),
padding: const EdgeInsets.only(left: 16.0, top: 8.0),
child: Text(
'backup_controller_page_background_delay'.tr(
namedArgs: {'duration': formatBackupDelaySliderValue(currentValue)},