Merge branch 'immich-app:main' into feat/samsung-raw-and-fujifilm-raf
@@ -0,0 +1,55 @@
|
|||||||
|
name: Build Mobile
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-sign-android:
|
||||||
|
name: Build and sign Android
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: 'zulu'
|
||||||
|
java-version: "12.x"
|
||||||
|
cache: 'gradle'
|
||||||
|
|
||||||
|
- name: Setup Flutter SDK
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: 'stable'
|
||||||
|
flutter-version: '3.3.10'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Create the Keystore
|
||||||
|
|
||||||
|
env:
|
||||||
|
KEYSTORE_BASE64: ${{ secrets.ANDROID_SIGN_KEY_CONTENT }}
|
||||||
|
run: |
|
||||||
|
# import keystore from secrets
|
||||||
|
echo $KEYSTORE_BASE64 | base64 -d > $RUNNER_TEMP/my_production.keystore
|
||||||
|
|
||||||
|
- name: Restore packages
|
||||||
|
working-directory: ./mobile
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Build Android App Bundle
|
||||||
|
working-directory: ./mobile
|
||||||
|
run: flutter build apk --release
|
||||||
|
|
||||||
|
- name: Sign Android App Bundle
|
||||||
|
working-directory: ./mobile
|
||||||
|
run: jarsigner -keystore $RUNNER_TEMP/my_production.keystore -storepass ${{ secrets.ANDROID_KEY_PASSWORD }} -keypass ${{ secrets.ANDROID_STORE_PASSWORD }} -sigalg SHA256withRSA -digestalg SHA-256 -signedjar build/app/outputs/apk/release/app-release-signed.apk build/app/outputs/apk/release/*.apk ${{ secrets.ALIAS }}
|
||||||
|
|
||||||
|
- name: Publish Android Artifact
|
||||||
|
uses: actions/upload-artifact@v1
|
||||||
|
with:
|
||||||
|
name: release-apk-signed
|
||||||
|
path: mobile/build/app/outputs/apk/release/app-release-signed.apk
|
||||||
@@ -8,3 +8,4 @@ uploads
|
|||||||
coverage
|
coverage
|
||||||
|
|
||||||
mobile/gradle.properties
|
mobile/gradle.properties
|
||||||
|
mobile/openapi/pubspec.lock
|
||||||
@@ -13,11 +13,11 @@ Download [`docker-compose.yml`][compose-file] [`example.env`][env-file].
|
|||||||
From a directory of your choice (e.g. `./immich-app`) run the following commands:
|
From a directory of your choice (e.g. `./immich-app`) run the following commands:
|
||||||
|
|
||||||
```bash title="Get docker-compose.yml file"
|
```bash title="Get docker-compose.yml file"
|
||||||
wget https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml
|
wget https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
```bash title="Get .env file"
|
```bash title="Get .env file"
|
||||||
wget -O .env https://raw.githubusercontent.com/immich-app/immich/main/docker/example.env
|
wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2 - Populate the .env file with custom values
|
### Step 2 - Populate the .env file with custom values
|
||||||
@@ -116,6 +116,6 @@ docker-compose pull && docker-compose up -d # Or `docker compose`
|
|||||||
Immich is currently under heavy development, which means you can expect breaking changes and bugs. Therefore, we recommend reading the release notes prior to updating and to take special care when using automated tools like [Watchtower][watchtower].
|
Immich is currently under heavy development, which means you can expect breaking changes and bugs. Therefore, we recommend reading the release notes prior to updating and to take special care when using automated tools like [Watchtower][watchtower].
|
||||||
:::
|
:::
|
||||||
|
|
||||||
[compose-file]: https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml
|
[compose-file]: https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
|
||||||
[env-file]: https://raw.githubusercontent.com/immich-app/immich/main/docker/example.env
|
[env-file]: https://github.com/immich-app/immich/releases/latest/download/example.env
|
||||||
[watchtower]: https://containrrr.dev/watchtower/
|
[watchtower]: https://containrrr.dev/watchtower/
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Install Immich using Portainer's Stack feature.
|
|||||||
1. Go to "**Stacks**" in the left sidebar.
|
1. Go to "**Stacks**" in the left sidebar.
|
||||||
2. Click on "**Add stack**".
|
2. Click on "**Add stack**".
|
||||||
3. Give the stack a name (i.e. Immich), and select "**Web Editor**" as the build method.
|
3. Give the stack a name (i.e. Immich), and select "**Web Editor**" as the build method.
|
||||||
4. Copy the content of the `docker-compose.yml` file from the [GitHub repository](https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml).
|
4. Copy the content of the `docker-compose.yml` file from the [GitHub repository](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml).
|
||||||
5. Replace `.env` with `stack.env` for all containers that need to use environment variables in the web editor.
|
5. Replace `.env` with `stack.env` for all containers that need to use environment variables in the web editor.
|
||||||
|
|
||||||
<img
|
<img
|
||||||
@@ -28,7 +28,7 @@ Install Immich using Portainer's Stack feature.
|
|||||||
alt="Dot Env Example"
|
alt="Dot Env Example"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
9. Copy the content of the `example.env` file from the [GitHub repository](https://raw.githubusercontent.com/immich-app/immich/main/docker/example.env) and paste into the editor.
|
9. Copy the content of the `example.env` file from the [GitHub repository](https://github.com/immich-app/immich/releases/latest/download/example.env) and paste into the editor.
|
||||||
10. Switch back to "**Simple Mode**".
|
10. Switch back to "**Simple Mode**".
|
||||||
|
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ curl -o- https://raw.githubusercontent.com/immich-app/immich/main/install.sh | b
|
|||||||
|
|
||||||
The script will perform the following actions:
|
The script will perform the following actions:
|
||||||
|
|
||||||
1. Download [docker-compose.yml](https://github.com/immich-app/immich/blob/main/docker/docker-compose.yml), and the [.env](https://github.com/immich-app/immich/blob/main/docker/example.env) file from the main branch of the [repository](https://github.com/immich-app/immich).
|
1. Download [docker-compose.yml](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml), and the [.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file from the main branch of the [repository](https://github.com/immich-app/immich).
|
||||||
2. Populate the `.env` file with necessary information based on the current directory path.
|
2. Populate the `.env` file with necessary information based on the current directory path.
|
||||||
3. Start the containers.
|
3. Start the containers.
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,25 @@ sidebar_position: 60
|
|||||||
|
|
||||||
# Unraid
|
# Unraid
|
||||||
|
|
||||||
Immich can easily be installed and updated on Unraid using the [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps.
|
Immich can easily be installed and updated on Unraid via:
|
||||||
|
1. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps
|
||||||
|
2. Community made template on the Unraid Community Apps
|
||||||
|
|
||||||
|
## Community Applications Template
|
||||||
|
|
||||||
|
:::info
|
||||||
|
|
||||||
|
- The Unraid template uses a community made image and is not officially supported by Immich
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
|
In order to install Immich from the Unraid CA, you will need an existing Redis and PostgreSQL 14 container, If you do not already have Redis or PostgreSQL you can install them from the Unraid CA, just make sure you choose PostgreSQL **14**.
|
||||||
|
|
||||||
|
Once you have Redis and PostgreSQL running, search for Immich on the Unraid CA, Choose either of the templates listed and fill out the example variables.
|
||||||
|
|
||||||
|
For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup)
|
||||||
|
|
||||||
|
## Docker-Compose Method (Official)
|
||||||
|
|
||||||
:::info
|
:::info
|
||||||
|
|
||||||
@@ -27,7 +45,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
|
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
|
||||||
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml) file into the Unraid editor
|
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml) file into the Unraid editor
|
||||||
<details >
|
<details >
|
||||||
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 5.</summary>
|
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 5.</summary>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -53,7 +71,7 @@ alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
|||||||
</details>
|
</details>
|
||||||
5. Click "**Save Changes**", you will be promoted to edit stack UI labels, just leave this blank and click "**Ok**"
|
5. Click "**Save Changes**", you will be promoted to edit stack UI labels, just leave this blank and click "**Ok**"
|
||||||
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
|
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
|
||||||
7. Past the entire contents of the [Immich example.env](https://raw.githubusercontent.com/immich-app/immich/main/docker/example.env) file into the Unraid editor, then **before saving** edit the following:
|
7. Past the entire contents of the [Immich example.env](https://github.com/immich-app/immich/releases/latest/download/example.env) file into the Unraid editor, then **before saving** edit the following:
|
||||||
|
|
||||||
- `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION`
|
- `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION`
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
|
|||||||
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package.json
|
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package.json
|
||||||
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package-lock.json
|
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package-lock.json
|
||||||
sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile
|
sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile
|
||||||
|
sed -i "s/version_number: \"$CURRENT_SERVER\"$/version_number: \"$NEXT_SERVER\"/" mobile/ios/fastlane/Fastfile
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -57,20 +57,21 @@ android {
|
|||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
||||||
signingConfigs {
|
// signingConfigs {
|
||||||
release {
|
// release {
|
||||||
keyAlias keystoreProperties['keyAlias']
|
// keyAlias keystoreProperties['keyAlias']
|
||||||
keyPassword keystoreProperties['keyPassword']
|
// keyPassword keystoreProperties['keyPassword']
|
||||||
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
// storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
|
||||||
storePassword keystoreProperties['storePassword']
|
// storePassword keystoreProperties['storePassword']
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
// TODO: Add your own signing config for the release build.
|
// TODO: Add your own signing config for the release build.
|
||||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
signingConfig signingConfigs.release
|
// signingConfig signingConfigs.release
|
||||||
|
signingConfig null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 144 KiB |
@@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 144 KiB |
@@ -0,0 +1,70 @@
|
|||||||
|
<vector android:height="200dp"
|
||||||
|
android:viewportHeight="1300"
|
||||||
|
android:viewportWidth="1300"
|
||||||
|
android:width="200dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="#4081ef"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M578,922.6c-2.2,-0.2 -5.5,-1 -9.7,-2.2c-52.4,-15.7 -99,-46.5 -133.8,-88.5c-8.8,-10.7 -17.2,-22.4 -19.4,-27.5c-8.1,-18.1 -6.3,-38.7 4.8,-55.4c5,-7.5 13.2,-15 20.5,-18.7c1.2,-0.6 54.1,-20 55.8,-20.4c0.5,-0.1 0.5,0.2 -0.3,2.1c-0.7,1.7 -1,3.1 -1.1,5.5l-0.1,3.2l2.8,5.8c8.7,17.9 19.2,32.7 33.2,46.4c6.3,6.2 7.8,7.6 13.8,12.3c22.7,18.1 52,30.7 79.9,34.3c2.5,0.3 5,0.8 5.7,1c2.8,0.9 7.7,-0.8 11,-3.7l1.8,-1.6l-0.2,4.8c-0.1,2.7 -0.6,15.4 -1,28.3c-0.6,20.3 -0.8,24 -1.5,27.5c-3.9,20.7 -18.6,37.5 -38.4,44.1c-4.6,1.5 -8,2.2 -13.1,2.7c-4.6,0.5 -5.9,0.4 -10.7,0Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#31a452"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M707.3,922.4c-4,-0.4 -9.4,-1.6 -13.2,-2.9c-3.4,-1.2 -10,-4.4 -12.5,-6.1c-10.9,-7.4 -19,-17.9 -23.1,-30c-2.2,-6.7 -2.3,-7.5 -3.3,-36.9c-0.5,-14.9 -0.9,-27.9 -0.9,-28.9l-0,-1.9l2.3,1.8c2.6,2 6.6,3.4 8.5,3.1c0.6,-0.1 3,-0.5 5.3,-0.8c37.7,-5.3 71.2,-22.2 97.4,-49.1c12.2,-12.5 21.4,-25.5 29.9,-42.4l3.5,-7l0,-3.6c0,-3.1 -0.1,-3.8 -1,-5.7c-0.5,-1.2 -0.9,-2.1 -0.9,-2.2c0.2,-0.2 55.3,20.1 56.9,20.9c2.6,1.3 6.6,4.1 9.9,7c9.2,7.7 16.1,19.4 18.8,31.8c0.7,3.1 0.8,4.8 0.8,11.3c0,8.6 -0.5,11.7 -2.9,18.7c-1.7,5 -2.9,7.2 -7.1,13.1c-7.6,11 -15.3,20.5 -25.2,31.2c-32.8,35.4 -76.5,62.5 -123.4,76.3c-8,2.5 -12.4,3 -19.8,2.3Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#de7fb3"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M623.1,811c-25.9,-4.2 -50.7,-14.9 -71.7,-31c-5.2,-4 -8.7,-7.1 -14.1,-12.4c-12.7,-12.5 -21.9,-24.9 -30.5,-41.4c-2.3,-4.4 -2.4,-4.7 -2.4,-7.1c0,-8.8 8.5,-15.2 16.9,-12.7c5.6,1.7 9.6,6.8 9.7,12.2c0,2.6 -0.8,4.6 -2.6,6.2c-1.2,1.1 -3.2,1.9 -4.6,1.9c-1.2,0 -3.3,-0.8 -4.3,-1.6c-2.1,-1.8 -2,-1 0.4,3.2c19.3,33.8 52.3,59.1 90,69.1c5.7,1.5 11.5,2.7 11.8,2.4c0.1,-0.1 -0.4,-0.8 -1.3,-1.6c-5.1,-4.5 -2.3,-11.7 5,-12.8c5.4,-0.8 11.4,2.7 13.9,8c0.8,1.7 1,2.5 1,5.3c0,2.8 -0.1,3.5 -1,5.3c-2,4.3 -6.8,7.9 -10.3,7.8c-0.9,-0.1 -3.6,-0.5 -5.9,-0.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#4081ef"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M665.1,811.2c-3.4,-1.3 -6.4,-4.3 -7.8,-8.1c-1.1,-2.9 -0.9,-7.3 0.5,-10.2c2.6,-5.3 8.7,-8.5 14.4,-7.5c2.9,0.5 4.7,1.9 6,4.3c0.8,1.6 1,2.2 0.8,3.6c-0.3,2.2 -0.9,3.3 -2.7,4.8c-0.8,0.7 -1.4,1.4 -1.3,1.5c0.5,0.5 13.4,-2.7 21.3,-5.4c33.6,-11.3 62.5,-35.1 80.4,-66.1c2.5,-4.4 2.6,-5 0.5,-3.2c-2.8,2.4 -7,1.9 -9.6,-1c-4,-4.6 -0.7,-13.8 6.1,-16.9c2,-0.9 2.7,-1 5.5,-1c2.9,0 3.5,0.1 5.6,1.1c4.4,2.1 7.4,6.4 7.8,11c0.2,2.2 0.1,2.3 -2.2,6.9c-23,45.9 -67,78.1 -117.2,85.9c-5.5,0.9 -6.3,1 -8.1,0.3Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#31a452"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M578.6,771.5c-4.7,-0.9 -8.7,-2.7 -12.9,-5.9c-10.8,-8.1 -13.5,-22.3 -6.6,-33.7c0.7,-1.2 1.1,-2.2 1,-2.4c-0.2,-0.2 -1.2,-0.6 -2.3,-1.1c-7.6,-3 -13,-10.6 -13.5,-19.1c-0.5,-7.4 3.1,-15 9,-19.4c1,-0.7 2.2,-1.5 2.6,-1.8c0.8,-0.4 68.9,-22.7 69.4,-22.7c0.2,0 0.7,0.7 1.2,1.5c0.5,0.8 1.6,2.3 2.4,3.3c1.2,1.4 1.5,1.9 1.2,2.3c-0.2,0.3 -6.9,9.5 -14.8,20.5c-15.9,21.9 -15.5,21.3 -13.4,23.4c1.3,1.3 2.9,1.4 4.4,0.3c0.6,-0.4 7.5,-9.7 15.5,-20.7c11.2,-15.4 14.6,-19.9 15,-19.7c0.9,0.4 5.5,1.9 6.6,2.1l1,0.2l-0,35.3c-0,39.7 -0,38.8 -2.5,44c-2.6,5.3 -7.2,9.3 -12.7,11.2c-3.7,1.3 -6.8,1.6 -10.2,1c-5.5,-0.9 -9.8,-3.2 -13.7,-7.4l-2.2,-2.4l-0.6,0.9c-3,4.3 -8.6,8.1 -14,9.5c-2.8,0.9 -7.8,1.2 -9.9,0.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffb800"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M710.4,771.5c-5.5,-0.9 -9.9,-3.2 -14.3,-7.6l-3.2,-3.2l-0.7,1c-2.3,3.3 -6.8,6.5 -11.1,7.9c-3.7,1.2 -9.2,1.4 -12.6,0.3c-7.1,-2.1 -12.7,-7.4 -15.2,-14.3l-0.9,-2.6l0,-74.2l1.8,-0.4c1,-0.2 2.7,-0.8 3.9,-1.2c1.1,-0.5 2.1,-0.8 2.2,-0.7c0.1,0.1 6.5,9 14.4,19.9c7.8,10.9 14.7,20.1 15.2,20.5c2.2,1.9 5.4,0.4 5.4,-2.6c0,-1.4 -1,-2.9 -13.8,-20.5c-7.6,-10.5 -14.2,-19.6 -14.7,-20.4l-0.9,-1.3l1.4,-1.7c0.8,-0.9 1.9,-2.5 2.5,-3.4l1,-1.6l34.4,11.2c18.9,6.2 35.1,11.6 35.9,12.1c6.8,4 11.1,11.3 11.1,19.1c0,4.1 -0.5,6.4 -2.4,10.2c-2,4.1 -5.5,7.6 -9.6,9.7c-1.6,0.8 -3.2,1.5 -3.4,1.5c-1,0 -0.9,0.7 0.3,2.6c2.8,4.3 4,8.5 3.9,13.7c0,8.1 -3.7,15.2 -10.6,20.3c-6.5,4.8 -13.4,6.7 -20,5.7Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#de7fb3"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M421.4,714.9c-0.5,-0.1 -2.3,-0.4 -3.9,-0.7c-15.6,-2.6 -30.4,-12.6 -38.8,-26.2c-3.5,-5.7 -6.4,-13.2 -7.8,-19.9c-1.2,-6.1 -0.8,-28.1 0.8,-43.1c4.5,-43 19,-84.3 42.2,-120.7c6.5,-10.2 14.9,-21.5 18.2,-24.6c17.8,-16.6 43.1,-20.5 64.8,-10c4.3,2.1 8.8,5.1 12.7,8.6c2.8,2.4 5.8,6.1 20.9,25.5c9.7,12.5 17.8,22.8 17.9,23c0.2,0.2 -0.9,0.4 -3.2,0.4c-2.5,0 -4.1,0.2 -5.7,0.7c-2.1,0.7 -2.6,1.1 -7.9,6.3c-8.2,8.1 -14.4,15.3 -20.3,23.9c-15.5,22.2 -25.4,47.7 -28.8,74.8c-2.2,16.9 -1.6,37.5 1.6,52.3c0.3,1.4 0.5,2.8 0.4,3c-0.1,0.2 0.2,1.3 0.8,2.4c1.1,2.4 4.3,5.7 6.5,6.8l1.5,0.8l-1.2,0.4c-0.7,0.2 -13.1,3.8 -27.6,8c-16.4,4.7 -27.7,7.8 -29.8,8.1c-3.1,0.4 -11.1,0.6 -13.3,0.2Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffb800"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M862.2,714.7c-2.1,-0.3 -33.8,-9.1 -56.5,-15.8l-2.5,-0.7l1.6,-0.8c3.4,-1.7 7.2,-6.6 7.3,-9.6c0,-0.7 0.4,-3.3 0.8,-5.8c3.9,-22.7 3.1,-46.1 -2.5,-68.4c-6.4,-25.5 -18.6,-49.2 -35.8,-69.1c-4.6,-5.3 -14.8,-15.4 -16.4,-16.1c-2.4,-1.1 -5.1,-1.6 -8,-1.4l-2.7,0.2l1.2,-1.5c0.7,-0.8 8.5,-10.8 17.5,-22.3c8.9,-11.5 17.2,-21.8 18.5,-23.1c2.6,-2.7 7,-6.2 10.3,-8.2c19.3,-11.6 43,-11.1 61.6,1.2c5.4,3.6 8.2,6.2 12.3,11.7c26.4,34.5 44,73.7 52.3,116.2c3.4,17.6 4.9,33.3 5,52.4c0,13 -0.2,14.8 -2.5,21.8c-8.4,26.2 -34.5,42.8 -61.5,39.3Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#e64132"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M501.4,691.5c-2,-0.5 -4.6,-1.9 -6,-3.3c-2.5,-2.4 -3.1,-3.5 -3.7,-7.3c-4.4,-27.3 -2.2,-54 6.7,-79.3c5.3,-15.1 13.5,-30.5 23,-43.1c5.8,-7.8 16.6,-19.5 19,-20.7c4.7,-2.4 11.3,-1.2 15.2,2.7c5.4,5.4 5.2,13.9 -0.3,19.1c-4.3,4 -9.4,4.4 -12.6,0.9c-1.7,-1.9 -2.2,-3.9 -1.7,-6.4c0.2,-1.1 0.3,-2 0.2,-2.2c-0.3,-0.3 -3.6,3.3 -8.3,9.1c-17.6,21.8 -28.5,48 -31.9,76.5c-1.1,9.3 -1,26.4 0.1,34.6c0.3,1.8 0.8,1.9 1.4,0.1c0.9,-2.6 4,-4.7 6.8,-4.7c3,-0 5.9,2.2 7.5,5.7c0.6,1.3 0.8,2.3 0.8,5.2c-0,3.3 -0.1,3.8 -1.1,5.7c-1.4,2.7 -4.6,5.7 -7.1,6.6c-2.5,0.9 -6.1,1.2 -8,0.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#31a452"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M790.1,691.5c-3.7,-0.6 -7.7,-3.6 -9.4,-7.1c-3.8,-7.5 0.1,-16.9 6.9,-16.9c3.1,0 5.8,2 6.9,5.2c0.4,1.2 0.5,1.3 0.7,0.7c1.3,-3.7 1.7,-26.4 0.6,-35.7c-3.6,-29.6 -14.5,-55.3 -33,-77.9c-5.5,-6.7 -8.4,-9.4 -7.1,-6.6c0.7,1.4 0.5,4.3 -0.3,5.9c-0.9,1.7 -3.2,3.5 -5,3.8c-3.2,0.6 -7.9,-1.6 -10.2,-4.8c-6.5,-8.8 -0.5,-21.2 10.4,-21.4c4.6,-0.1 5.2,0.3 11.2,6.4c12.1,12.3 21.1,24.9 28.8,40.3c13.2,26.3 18.6,54.9 16.1,84.5c-0.5,5.6 -2,15.7 -2.6,17.1c-1.3,2.8 -4.8,5.5 -8.4,6.5c-2.3,0.4 -3.1,0.4 -5.6,0Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#4081ef"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M545.7,680.2c-6,-1.3 -12.2,-6.2 -14.9,-11.7c-3.4,-7 -3.1,-15.1 0.9,-21.6c0.7,-1.2 1.2,-2.3 1.1,-2.4c-0.1,-0.1 -1.1,-0.6 -2.1,-1c-3.9,-1.5 -8.1,-4.8 -10.7,-8.3c-4.6,-6.2 -6.1,-14.6 -3.9,-22.1c2.9,-10.3 9.4,-16.8 19.1,-19.3c2.8,-0.7 9,-0.8 11.7,-0c1.1,0.3 2.2,0.5 2.4,0.5c0.2,-0 0.3,-0.7 0.3,-1.5c0,-2.9 0.8,-5.8 2.4,-9.2c5.2,-10.8 18.1,-15.5 29,-10.5c2.7,1.2 6.2,3.8 7.8,5.8c0.7,0.8 10.3,14 21.5,29.4l20.3,27.9l-1.5,1.8c-0.8,1 -1.9,2.6 -2.5,3.5c-0.6,1 -1.2,1.7 -1.5,1.6c-4.5,-1.7 -46.7,-15 -47.7,-15c-1.9,0 -3.1,1.3 -3.1,3.2c0,1 0.2,1.7 0.8,2.3c0.6,0.6 7.8,3.1 24.5,8.5l23.7,7.7l-0.2,8.6l-32.6,10.5c-18,5.9 -33.9,10.9 -35.2,11.2c-3.1,0.7 -6.6,0.7 -9.6,0.1Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#e64132"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M740,679.8c-1.8,-0.5 -17.5,-5.6 -35,-11.3l-31.8,-10.4l1,-4.3l0,-4.3l22.6,-7.7c15,-4.9 24,-8 24.6,-8.5c0.7,-0.6 0.9,-1.1 0.9,-2.2c-0,-2 -1.2,-3.3 -3.1,-3.3c-0.9,-0 -10.5,2.9 -24.7,7.5c-12.8,4.1 -23.4,7.5 -23.6,7.5c-0.1,-0 -0.7,-0.8 -1.3,-1.9c-0.6,-1 -1.6,-2.5 -2.2,-3.2c-0.7,-0.7 -1.2,-1.5 -1.2,-1.6c0,-0.2 9.6,-13.5 21.4,-29.6c18.9,-26 21.6,-29.6 23.6,-31.1c5.7,-4.4 13.1,-5.8 19.7,-3.9c9,2.7 16.1,11.6 16.1,20.3c0,2.3 -0.1,2.3 3.1,1.5c4.7,-1.1 11.5,-0.5 16,1.5c4.6,2 9,6 11.5,10.2c2.1,3.6 3.9,9.4 4.2,13.2c0.3,5.2 -1.1,10.7 -4,15.3c-2.6,4.1 -7.8,8.3 -12.1,9.8c-0.9,0.3 -1.7,0.8 -1.7,1c0,0.2 0.4,1 0.9,1.7c2.4,3.6 3.6,7.7 3.5,12.7c0,5.8 -2.1,10.7 -6.4,15.1c-4,4.1 -8.9,6.3 -14.9,6.5c-3.3,0.4 -4.3,0.3 -7.1,-0.5Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#f2f5fb"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M643.7,671.9c-6.1,-1.6 -11.4,-6.8 -13.2,-12.9c-0.7,-2.4 -0.7,-7.5 0,-9.9c1.7,-5.8 6.6,-10.8 12.3,-12.5c2.7,-0.8 7.2,-0.9 10,-0.2c6.2,1.6 11.6,7.1 13.2,13.3c1.6,6 -0.3,12.6 -5,17.3c-4.6,4.6 -11.3,6.5 -17.3,4.9Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#de7fb3"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M615.8,602.8c-13.3,-18.3 -21.2,-29.6 -22,-31.1c-1.4,-3 -1.9,-5.5 -1.9,-9.4c0,-14.1 13.1,-24.4 27.1,-21.4c1.4,0.3 2.6,0.5 2.7,0.5c0.1,0 0.3,-1.3 0.4,-2.8c0.8,-10.7 8.4,-19.6 18.9,-22.4c3.9,-1 10.6,-1 14.5,-0c8.9,2.3 15.9,9.3 18.2,18.2c0.4,1.5 0.7,3.7 0.7,4.9c-0,1.2 0.1,2.1 0.3,2.1c0.2,-0 1.5,-0.3 3,-0.6c7.4,-1.6 15.2,0.7 20.5,6c4.3,4.3 6.6,9.6 6.6,15.6c-0,4 -0.6,6.5 -2.4,10c-0.6,1.2 -10.4,15 -21.7,30.7c-17.8,24.5 -20.8,28.5 -21.4,28.3c-0.4,-0.1 -1.9,-0.6 -3.4,-1.1c-1.5,-0.5 -2.9,-0.9 -3.3,-0.9c-0.7,-0 -0.7,-0.8 -0.3,-25.5l-0,-25.5l-1.4,-0.9c-1,-1.1 -2.5,-1.5 -3.8,-0.9c-2,0.8 -2,-0.5 -1.8,27.2l-0,25.8l-1.2,-0c-0.5,-0.2 -2.4,0.3 -4,0.9c-1.6,0.6 -3.1,1.1 -3.2,1.1c-0.2,-0.1 -9.6,-13 -21.1,-28.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#ffb800"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M578.4,537.8c-4.1,-0.9 -7.7,-3.6 -9.6,-7.4c-1.4,-2.8 -1.7,-7.3 -0.5,-10.3c1.7,-4.5 3.9,-6.1 15.6,-11.2c15.8,-7 31.4,-11.1 49.2,-12.9c7.3,-0.8 23.2,-0.8 30.6,0c17.4,1.8 33.3,6 49.1,13c7.3,3.2 12.5,6.1 13.6,7.5c4.3,5.6 3.8,12.7 -1.1,17.6c-5.1,5.1 -12.9,5.4 -18.1,0.7c-2,-1.8 -3,-3.5 -3.4,-5.6c-0.7,-4 2.9,-8.1 7.3,-8.2c1.4,0 1.5,-0.1 1.1,-0.5c-0.3,-0.3 -2.2,-1.2 -4.3,-2.1c-33.2,-14.5 -70.5,-16.4 -105,-5.4c-7.5,2.4 -19,7.2 -18.6,7.7c0.1,0.2 0.8,0.3 1.6,0.3c5.6,0 9.1,6.2 6.1,10.8c-2.9,4.5 -8.6,7.1 -13.6,6Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#e64132"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M542.2,496.4c-8.9,-13.1 -16.8,-25.1 -17.5,-26.6c-1.6,-3.3 -3.6,-9.2 -4.4,-13c-2.6,-12.5 -0.9,-25.8 5,-37.5c4.2,-8.3 11.2,-16.3 18.6,-21.3c5,-3.4 6.1,-3.9 12.8,-6.3c23.1,-8.2 47.2,-13.1 73.4,-15c7.5,-0.6 28.5,-0.6 36.3,-0c25.5,1.8 50.6,6.9 73,14.8c6.4,2.2 8.2,3.1 13.1,6.5c9.8,6.6 18.1,17.5 22,29.2c2.2,6.5 2.7,10 2.7,17.9c0,7.9 -0.5,11.3 -2.7,17.9c-2.3,6.8 -3.7,9.1 -20.3,33.6l-16.1,23.8l-0.4,-2.2c-0.2,-1.2 -0.9,-3 -1.4,-4c-1,-1.8 -4.4,-5.6 -4.7,-5.2c-0.1,0.1 -1.2,-0.4 -2.4,-1.1c-9.1,-5.2 -21.9,-10.5 -33.2,-13.9c-37,-11 -77.2,-8.8 -113,6.1c-4.9,2.1 -17.7,8.4 -19.2,9.5c-2.2,1.6 -5.1,6.8 -5.1,9c0,0.4 -0.1,1 -0.3,1.2c0.1,0.2 -6.2,-8.8 -16.2,-23.4Z" />
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
<vector android:height="200dp"
|
||||||
|
android:viewportHeight="1300"
|
||||||
|
android:viewportWidth="1300"
|
||||||
|
android:width="200dp"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M578,922.6c-2.2,-0.2 -5.5,-1 -9.7,-2.2c-52.4,-15.7 -99,-46.5 -133.8,-88.5c-8.8,-10.7 -17.2,-22.4 -19.4,-27.5c-8.1,-18.1 -6.3,-38.7 4.8,-55.4c5,-7.5 13.2,-15 20.5,-18.7c1.2,-0.6 54.1,-20 55.8,-20.4c0.5,-0.1 0.5,0.2 -0.3,2.1c-0.7,1.7 -1,3.1 -1.1,5.5l-0.1,3.2l2.8,5.8c8.7,17.9 19.2,32.7 33.2,46.4c6.3,6.2 7.8,7.6 13.8,12.3c22.7,18.1 52,30.7 79.9,34.3c2.5,0.3 5,0.8 5.7,1c2.8,0.9 7.7,-0.8 11,-3.7l1.8,-1.6l-0.2,4.8c-0.1,2.7 -0.6,15.4 -1,28.3c-0.6,20.3 -0.8,24 -1.5,27.5c-3.9,20.7 -18.6,37.5 -38.4,44.1c-4.6,1.5 -8,2.2 -13.1,2.7c-4.6,0.5 -5.9,0.4 -10.7,0Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M707.3,922.4c-4,-0.4 -9.4,-1.6 -13.2,-2.9c-3.4,-1.2 -10,-4.4 -12.5,-6.1c-10.9,-7.4 -19,-17.9 -23.1,-30c-2.2,-6.7 -2.3,-7.5 -3.3,-36.9c-0.5,-14.9 -0.9,-27.9 -0.9,-28.9l-0,-1.9l2.3,1.8c2.6,2 6.6,3.4 8.5,3.1c0.6,-0.1 3,-0.5 5.3,-0.8c37.7,-5.3 71.2,-22.2 97.4,-49.1c12.2,-12.5 21.4,-25.5 29.9,-42.4l3.5,-7l0,-3.6c0,-3.1 -0.1,-3.8 -1,-5.7c-0.5,-1.2 -0.9,-2.1 -0.9,-2.2c0.2,-0.2 55.3,20.1 56.9,20.9c2.6,1.3 6.6,4.1 9.9,7c9.2,7.7 16.1,19.4 18.8,31.8c0.7,3.1 0.8,4.8 0.8,11.3c0,8.6 -0.5,11.7 -2.9,18.7c-1.7,5 -2.9,7.2 -7.1,13.1c-7.6,11 -15.3,20.5 -25.2,31.2c-32.8,35.4 -76.5,62.5 -123.4,76.3c-8,2.5 -12.4,3 -19.8,2.3Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M623.1,811c-25.9,-4.2 -50.7,-14.9 -71.7,-31c-5.2,-4 -8.7,-7.1 -14.1,-12.4c-12.7,-12.5 -21.9,-24.9 -30.5,-41.4c-2.3,-4.4 -2.4,-4.7 -2.4,-7.1c0,-8.8 8.5,-15.2 16.9,-12.7c5.6,1.7 9.6,6.8 9.7,12.2c0,2.6 -0.8,4.6 -2.6,6.2c-1.2,1.1 -3.2,1.9 -4.6,1.9c-1.2,0 -3.3,-0.8 -4.3,-1.6c-2.1,-1.8 -2,-1 0.4,3.2c19.3,33.8 52.3,59.1 90,69.1c5.7,1.5 11.5,2.7 11.8,2.4c0.1,-0.1 -0.4,-0.8 -1.3,-1.6c-5.1,-4.5 -2.3,-11.7 5,-12.8c5.4,-0.8 11.4,2.7 13.9,8c0.8,1.7 1,2.5 1,5.3c0,2.8 -0.1,3.5 -1,5.3c-2,4.3 -6.8,7.9 -10.3,7.8c-0.9,-0.1 -3.6,-0.5 -5.9,-0.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M665.1,811.2c-3.4,-1.3 -6.4,-4.3 -7.8,-8.1c-1.1,-2.9 -0.9,-7.3 0.5,-10.2c2.6,-5.3 8.7,-8.5 14.4,-7.5c2.9,0.5 4.7,1.9 6,4.3c0.8,1.6 1,2.2 0.8,3.6c-0.3,2.2 -0.9,3.3 -2.7,4.8c-0.8,0.7 -1.4,1.4 -1.3,1.5c0.5,0.5 13.4,-2.7 21.3,-5.4c33.6,-11.3 62.5,-35.1 80.4,-66.1c2.5,-4.4 2.6,-5 0.5,-3.2c-2.8,2.4 -7,1.9 -9.6,-1c-4,-4.6 -0.7,-13.8 6.1,-16.9c2,-0.9 2.7,-1 5.5,-1c2.9,0 3.5,0.1 5.6,1.1c4.4,2.1 7.4,6.4 7.8,11c0.2,2.2 0.1,2.3 -2.2,6.9c-23,45.9 -67,78.1 -117.2,85.9c-5.5,0.9 -6.3,1 -8.1,0.3Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M578.6,771.5c-4.7,-0.9 -8.7,-2.7 -12.9,-5.9c-10.8,-8.1 -13.5,-22.3 -6.6,-33.7c0.7,-1.2 1.1,-2.2 1,-2.4c-0.2,-0.2 -1.2,-0.6 -2.3,-1.1c-7.6,-3 -13,-10.6 -13.5,-19.1c-0.5,-7.4 3.1,-15 9,-19.4c1,-0.7 2.2,-1.5 2.6,-1.8c0.8,-0.4 68.9,-22.7 69.4,-22.7c0.2,0 0.7,0.7 1.2,1.5c0.5,0.8 1.6,2.3 2.4,3.3c1.2,1.4 1.5,1.9 1.2,2.3c-0.2,0.3 -6.9,9.5 -14.8,20.5c-15.9,21.9 -15.5,21.3 -13.4,23.4c1.3,1.3 2.9,1.4 4.4,0.3c0.6,-0.4 7.5,-9.7 15.5,-20.7c11.2,-15.4 14.6,-19.9 15,-19.7c0.9,0.4 5.5,1.9 6.6,2.1l1,0.2l-0,35.3c-0,39.7 -0,38.8 -2.5,44c-2.6,5.3 -7.2,9.3 -12.7,11.2c-3.7,1.3 -6.8,1.6 -10.2,1c-5.5,-0.9 -9.8,-3.2 -13.7,-7.4l-2.2,-2.4l-0.6,0.9c-3,4.3 -8.6,8.1 -14,9.5c-2.8,0.9 -7.8,1.2 -9.9,0.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M710.4,771.5c-5.5,-0.9 -9.9,-3.2 -14.3,-7.6l-3.2,-3.2l-0.7,1c-2.3,3.3 -6.8,6.5 -11.1,7.9c-3.7,1.2 -9.2,1.4 -12.6,0.3c-7.1,-2.1 -12.7,-7.4 -15.2,-14.3l-0.9,-2.6l0,-74.2l1.8,-0.4c1,-0.2 2.7,-0.8 3.9,-1.2c1.1,-0.5 2.1,-0.8 2.2,-0.7c0.1,0.1 6.5,9 14.4,19.9c7.8,10.9 14.7,20.1 15.2,20.5c2.2,1.9 5.4,0.4 5.4,-2.6c0,-1.4 -1,-2.9 -13.8,-20.5c-7.6,-10.5 -14.2,-19.6 -14.7,-20.4l-0.9,-1.3l1.4,-1.7c0.8,-0.9 1.9,-2.5 2.5,-3.4l1,-1.6l34.4,11.2c18.9,6.2 35.1,11.6 35.9,12.1c6.8,4 11.1,11.3 11.1,19.1c0,4.1 -0.5,6.4 -2.4,10.2c-2,4.1 -5.5,7.6 -9.6,9.7c-1.6,0.8 -3.2,1.5 -3.4,1.5c-1,0 -0.9,0.7 0.3,2.6c2.8,4.3 4,8.5 3.9,13.7c0,8.1 -3.7,15.2 -10.6,20.3c-6.5,4.8 -13.4,6.7 -20,5.7Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M421.4,714.9c-0.5,-0.1 -2.3,-0.4 -3.9,-0.7c-15.6,-2.6 -30.4,-12.6 -38.8,-26.2c-3.5,-5.7 -6.4,-13.2 -7.8,-19.9c-1.2,-6.1 -0.8,-28.1 0.8,-43.1c4.5,-43 19,-84.3 42.2,-120.7c6.5,-10.2 14.9,-21.5 18.2,-24.6c17.8,-16.6 43.1,-20.5 64.8,-10c4.3,2.1 8.8,5.1 12.7,8.6c2.8,2.4 5.8,6.1 20.9,25.5c9.7,12.5 17.8,22.8 17.9,23c0.2,0.2 -0.9,0.4 -3.2,0.4c-2.5,0 -4.1,0.2 -5.7,0.7c-2.1,0.7 -2.6,1.1 -7.9,6.3c-8.2,8.1 -14.4,15.3 -20.3,23.9c-15.5,22.2 -25.4,47.7 -28.8,74.8c-2.2,16.9 -1.6,37.5 1.6,52.3c0.3,1.4 0.5,2.8 0.4,3c-0.1,0.2 0.2,1.3 0.8,2.4c1.1,2.4 4.3,5.7 6.5,6.8l1.5,0.8l-1.2,0.4c-0.7,0.2 -13.1,3.8 -27.6,8c-16.4,4.7 -27.7,7.8 -29.8,8.1c-3.1,0.4 -11.1,0.6 -13.3,0.2Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M862.2,714.7c-2.1,-0.3 -33.8,-9.1 -56.5,-15.8l-2.5,-0.7l1.6,-0.8c3.4,-1.7 7.2,-6.6 7.3,-9.6c0,-0.7 0.4,-3.3 0.8,-5.8c3.9,-22.7 3.1,-46.1 -2.5,-68.4c-6.4,-25.5 -18.6,-49.2 -35.8,-69.1c-4.6,-5.3 -14.8,-15.4 -16.4,-16.1c-2.4,-1.1 -5.1,-1.6 -8,-1.4l-2.7,0.2l1.2,-1.5c0.7,-0.8 8.5,-10.8 17.5,-22.3c8.9,-11.5 17.2,-21.8 18.5,-23.1c2.6,-2.7 7,-6.2 10.3,-8.2c19.3,-11.6 43,-11.1 61.6,1.2c5.4,3.6 8.2,6.2 12.3,11.7c26.4,34.5 44,73.7 52.3,116.2c3.4,17.6 4.9,33.3 5,52.4c0,13 -0.2,14.8 -2.5,21.8c-8.4,26.2 -34.5,42.8 -61.5,39.3Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M501.4,691.5c-2,-0.5 -4.6,-1.9 -6,-3.3c-2.5,-2.4 -3.1,-3.5 -3.7,-7.3c-4.4,-27.3 -2.2,-54 6.7,-79.3c5.3,-15.1 13.5,-30.5 23,-43.1c5.8,-7.8 16.6,-19.5 19,-20.7c4.7,-2.4 11.3,-1.2 15.2,2.7c5.4,5.4 5.2,13.9 -0.3,19.1c-4.3,4 -9.4,4.4 -12.6,0.9c-1.7,-1.9 -2.2,-3.9 -1.7,-6.4c0.2,-1.1 0.3,-2 0.2,-2.2c-0.3,-0.3 -3.6,3.3 -8.3,9.1c-17.6,21.8 -28.5,48 -31.9,76.5c-1.1,9.3 -1,26.4 0.1,34.6c0.3,1.8 0.8,1.9 1.4,0.1c0.9,-2.6 4,-4.7 6.8,-4.7c3,-0 5.9,2.2 7.5,5.7c0.6,1.3 0.8,2.3 0.8,5.2c-0,3.3 -0.1,3.8 -1.1,5.7c-1.4,2.7 -4.6,5.7 -7.1,6.6c-2.5,0.9 -6.1,1.2 -8,0.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M790.1,691.5c-3.7,-0.6 -7.7,-3.6 -9.4,-7.1c-3.8,-7.5 0.1,-16.9 6.9,-16.9c3.1,0 5.8,2 6.9,5.2c0.4,1.2 0.5,1.3 0.7,0.7c1.3,-3.7 1.7,-26.4 0.6,-35.7c-3.6,-29.6 -14.5,-55.3 -33,-77.9c-5.5,-6.7 -8.4,-9.4 -7.1,-6.6c0.7,1.4 0.5,4.3 -0.3,5.9c-0.9,1.7 -3.2,3.5 -5,3.8c-3.2,0.6 -7.9,-1.6 -10.2,-4.8c-6.5,-8.8 -0.5,-21.2 10.4,-21.4c4.6,-0.1 5.2,0.3 11.2,6.4c12.1,12.3 21.1,24.9 28.8,40.3c13.2,26.3 18.6,54.9 16.1,84.5c-0.5,5.6 -2,15.7 -2.6,17.1c-1.3,2.8 -4.8,5.5 -8.4,6.5c-2.3,0.4 -3.1,0.4 -5.6,0Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M545.7,680.2c-6,-1.3 -12.2,-6.2 -14.9,-11.7c-3.4,-7 -3.1,-15.1 0.9,-21.6c0.7,-1.2 1.2,-2.3 1.1,-2.4c-0.1,-0.1 -1.1,-0.6 -2.1,-1c-3.9,-1.5 -8.1,-4.8 -10.7,-8.3c-4.6,-6.2 -6.1,-14.6 -3.9,-22.1c2.9,-10.3 9.4,-16.8 19.1,-19.3c2.8,-0.7 9,-0.8 11.7,-0c1.1,0.3 2.2,0.5 2.4,0.5c0.2,-0 0.3,-0.7 0.3,-1.5c0,-2.9 0.8,-5.8 2.4,-9.2c5.2,-10.8 18.1,-15.5 29,-10.5c2.7,1.2 6.2,3.8 7.8,5.8c0.7,0.8 10.3,14 21.5,29.4l20.3,27.9l-1.5,1.8c-0.8,1 -1.9,2.6 -2.5,3.5c-0.6,1 -1.2,1.7 -1.5,1.6c-4.5,-1.7 -46.7,-15 -47.7,-15c-1.9,0 -3.1,1.3 -3.1,3.2c0,1 0.2,1.7 0.8,2.3c0.6,0.6 7.8,3.1 24.5,8.5l23.7,7.7l-0.2,8.6l-32.6,10.5c-18,5.9 -33.9,10.9 -35.2,11.2c-3.1,0.7 -6.6,0.7 -9.6,0.1Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M740,679.8c-1.8,-0.5 -17.5,-5.6 -35,-11.3l-31.8,-10.4l1,-4.3l0,-4.3l22.6,-7.7c15,-4.9 24,-8 24.6,-8.5c0.7,-0.6 0.9,-1.1 0.9,-2.2c-0,-2 -1.2,-3.3 -3.1,-3.3c-0.9,-0 -10.5,2.9 -24.7,7.5c-12.8,4.1 -23.4,7.5 -23.6,7.5c-0.1,-0 -0.7,-0.8 -1.3,-1.9c-0.6,-1 -1.6,-2.5 -2.2,-3.2c-0.7,-0.7 -1.2,-1.5 -1.2,-1.6c0,-0.2 9.6,-13.5 21.4,-29.6c18.9,-26 21.6,-29.6 23.6,-31.1c5.7,-4.4 13.1,-5.8 19.7,-3.9c9,2.7 16.1,11.6 16.1,20.3c0,2.3 -0.1,2.3 3.1,1.5c4.7,-1.1 11.5,-0.5 16,1.5c4.6,2 9,6 11.5,10.2c2.1,3.6 3.9,9.4 4.2,13.2c0.3,5.2 -1.1,10.7 -4,15.3c-2.6,4.1 -7.8,8.3 -12.1,9.8c-0.9,0.3 -1.7,0.8 -1.7,1c0,0.2 0.4,1 0.9,1.7c2.4,3.6 3.6,7.7 3.5,12.7c0,5.8 -2.1,10.7 -6.4,15.1c-4,4.1 -8.9,6.3 -14.9,6.5c-3.3,0.4 -4.3,0.3 -7.1,-0.5Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M643.7,671.9c-6.1,-1.6 -11.4,-6.8 -13.2,-12.9c-0.7,-2.4 -0.7,-7.5 0,-9.9c1.7,-5.8 6.6,-10.8 12.3,-12.5c2.7,-0.8 7.2,-0.9 10,-0.2c6.2,1.6 11.6,7.1 13.2,13.3c1.6,6 -0.3,12.6 -5,17.3c-4.6,4.6 -11.3,6.5 -17.3,4.9Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M615.8,602.8c-13.3,-18.3 -21.2,-29.6 -22,-31.1c-1.4,-3 -1.9,-5.5 -1.9,-9.4c0,-14.1 13.1,-24.4 27.1,-21.4c1.4,0.3 2.6,0.5 2.7,0.5c0.1,0 0.3,-1.3 0.4,-2.8c0.8,-10.7 8.4,-19.6 18.9,-22.4c3.9,-1 10.6,-1 14.5,-0c8.9,2.3 15.9,9.3 18.2,18.2c0.4,1.5 0.7,3.7 0.7,4.9c-0,1.2 0.1,2.1 0.3,2.1c0.2,-0 1.5,-0.3 3,-0.6c7.4,-1.6 15.2,0.7 20.5,6c4.3,4.3 6.6,9.6 6.6,15.6c-0,4 -0.6,6.5 -2.4,10c-0.6,1.2 -10.4,15 -21.7,30.7c-17.8,24.5 -20.8,28.5 -21.4,28.3c-0.4,-0.1 -1.9,-0.6 -3.4,-1.1c-1.5,-0.5 -2.9,-0.9 -3.3,-0.9c-0.7,-0 -0.7,-0.8 -0.3,-25.5l-0,-25.5l-1.4,-0.9c-1,-1.1 -2.5,-1.5 -3.8,-0.9c-2,0.8 -2,-0.5 -1.8,27.2l-0,25.8l-1.2,-0c-0.5,-0.2 -2.4,0.3 -4,0.9c-1.6,0.6 -3.1,1.1 -3.2,1.1c-0.2,-0.1 -9.6,-13 -21.1,-28.8Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M578.4,537.8c-4.1,-0.9 -7.7,-3.6 -9.6,-7.4c-1.4,-2.8 -1.7,-7.3 -0.5,-10.3c1.7,-4.5 3.9,-6.1 15.6,-11.2c15.8,-7 31.4,-11.1 49.2,-12.9c7.3,-0.8 23.2,-0.8 30.6,0c17.4,1.8 33.3,6 49.1,13c7.3,3.2 12.5,6.1 13.6,7.5c4.3,5.6 3.8,12.7 -1.1,17.6c-5.1,5.1 -12.9,5.4 -18.1,0.7c-2,-1.8 -3,-3.5 -3.4,-5.6c-0.7,-4 2.9,-8.1 7.3,-8.2c1.4,0 1.5,-0.1 1.1,-0.5c-0.3,-0.3 -2.2,-1.2 -4.3,-2.1c-33.2,-14.5 -70.5,-16.4 -105,-5.4c-7.5,2.4 -19,7.2 -18.6,7.7c0.1,0.2 0.8,0.3 1.6,0.3c5.6,0 9.1,6.2 6.1,10.8c-2.9,4.5 -8.6,7.1 -13.6,6Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#fff"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M542.2,496.4c-8.9,-13.1 -16.8,-25.1 -17.5,-26.6c-1.6,-3.3 -3.6,-9.2 -4.4,-13c-2.6,-12.5 -0.9,-25.8 5,-37.5c4.2,-8.3 11.2,-16.3 18.6,-21.3c5,-3.4 6.1,-3.9 12.8,-6.3c23.1,-8.2 47.2,-13.1 73.4,-15c7.5,-0.6 28.5,-0.6 36.3,-0c25.5,1.8 50.6,6.9 73,14.8c6.4,2.2 8.2,3.1 13.1,6.5c9.8,6.6 18.1,17.5 22,29.2c2.2,6.5 2.7,10 2.7,17.9c0,7.9 -0.5,11.3 -2.7,17.9c-2.3,6.8 -3.7,9.1 -20.3,33.6l-16.1,23.8l-0.4,-2.2c-0.2,-1.2 -0.9,-3 -1.4,-4c-1,-1.8 -4.4,-5.6 -4.7,-5.2c-0.1,0.1 -1.2,-0.4 -2.4,-1.1c-9.1,-5.2 -21.9,-10.5 -33.2,-13.9c-37,-11 -77.2,-8.8 -113,6.1c-4.9,2.1 -17.7,8.4 -19.2,9.5c-2.2,1.6 -5.1,6.8 -5.1,9c0,0.4 -0.1,1 -0.3,1.2c0.1,0.2 -6.2,-8.8 -16.2,-23.4Z" />
|
||||||
|
</vector>
|
||||||
@@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#FFFFFF</color>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
Flutter draws its first frame -->
|
Flutter draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ platform :android do
|
|||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 66,
|
"android.injected.version.code" => 66,
|
||||||
"android.injected.version.name" => "1.43.0",
|
"android.injected.version.name" => "1.43.1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
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')
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
* Fix crash at first start related to uninitialized hive key
|
||||||
|
* Fix invalid creation time on local asset show 1970 as year
|
||||||
|
* Fix Home page app bar icons don't conform to theme change
|
||||||
|
* Fix endless 'Building timeline' loop after changing the number of assets per row
|
||||||
|
* Show current upload asset
|
||||||
|
* Add to album from asset detail view
|
||||||
|
* Add multi selected assets to album
|
||||||
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,137 @@
|
|||||||
|
flutter_native_splash:
|
||||||
|
# This package generates native code to customize Flutter's default white native splash screen
|
||||||
|
# with background color and splash image.
|
||||||
|
# Customize the parameters below, and run the following command in the terminal:
|
||||||
|
# flutter pub run flutter_native_splash:create
|
||||||
|
# To restore Flutter's default white splash screen, run the following command in the terminal:
|
||||||
|
# flutter pub run flutter_native_splash:remove
|
||||||
|
|
||||||
|
# color or background_image is the only required parameter. Use color to set the background
|
||||||
|
# of your splash screen to a solid color. Use background_image to set the background of your
|
||||||
|
# splash screen to a png image. This is useful for gradients. The image will be stretch to the
|
||||||
|
# size of the app. Only one parameter can be used, color and background_image cannot both be set.
|
||||||
|
background_image: "assets/immich-logo-no-outline.png"
|
||||||
|
|
||||||
|
# Optional parameters are listed below. To enable a parameter, uncomment the line by removing
|
||||||
|
# the leading # character.
|
||||||
|
|
||||||
|
# The image parameter allows you to specify an image used in the splash screen. It must be a
|
||||||
|
# png file and should be sized for 4x pixel density.
|
||||||
|
#image: assets/splash.png
|
||||||
|
|
||||||
|
# The branding property allows you to specify an image used as branding in the splash screen.
|
||||||
|
# It must be a png file. It is supported for Android, iOS and the Web. For Android 12,
|
||||||
|
# see the Android 12 section below.
|
||||||
|
#branding: assets/dart.png
|
||||||
|
|
||||||
|
# To position the branding image at the bottom of the screen you can use bottom, bottomRight,
|
||||||
|
# and bottomLeft. The default values is bottom if not specified or specified something else.
|
||||||
|
#branding_mode: bottom
|
||||||
|
|
||||||
|
# The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background
|
||||||
|
# and image when the device is in dark mode. If they are not specified, the app will use the
|
||||||
|
# parameters from above. If the image_dark parameter is specified, color_dark or
|
||||||
|
# background_image_dark must be specified. color_dark and background_image_dark cannot both be
|
||||||
|
# set.
|
||||||
|
#color_dark: "#042a49"
|
||||||
|
#background_image_dark: "assets/dark-background.png"
|
||||||
|
#image_dark: assets/splash-invert.png
|
||||||
|
#branding_dark: assets/dart_dark.png
|
||||||
|
|
||||||
|
# Android 12 handles the splash screen differently than previous versions. Please visit
|
||||||
|
# https://developer.android.com/guide/topics/ui/splash-screen
|
||||||
|
# Following are Android 12 specific parameter.
|
||||||
|
android_12:
|
||||||
|
# The image parameter sets the splash screen icon image. If this parameter is not specified,
|
||||||
|
# the app's launcher icon will be used instead.
|
||||||
|
# Please note that the splash screen will be clipped to a circle on the center of the screen.
|
||||||
|
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
|
||||||
|
# 640 pixels in diameter.
|
||||||
|
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
|
||||||
|
# 768 pixels in diameter.
|
||||||
|
image: assets/immich-logo-no-outline-android12.png
|
||||||
|
|
||||||
|
# Splash screen background color.
|
||||||
|
#color: "#42a5f5"
|
||||||
|
|
||||||
|
# App icon background color.
|
||||||
|
#icon_background_color: "#111111"
|
||||||
|
|
||||||
|
# The branding property allows you to specify an image used as branding in the splash screen.
|
||||||
|
#branding: assets/dart.png
|
||||||
|
|
||||||
|
# The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that
|
||||||
|
# apply when the device is in dark mode. If they are not specified, the app will use the
|
||||||
|
# parameters from above.
|
||||||
|
#image_dark: assets/android12splash-invert.png
|
||||||
|
#color_dark: "#042a49"
|
||||||
|
#icon_background_color_dark: "#eeeeee"
|
||||||
|
|
||||||
|
# The android, ios and web parameters can be used to disable generating a splash screen on a given
|
||||||
|
# platform.
|
||||||
|
#android: false
|
||||||
|
#ios: false
|
||||||
|
#web: false
|
||||||
|
|
||||||
|
# Platform specific images can be specified with the following parameters, which will override
|
||||||
|
# the respective parameter. You may specify all, selected, or none of these parameters:
|
||||||
|
#color_android: "#42a5f5"
|
||||||
|
#color_dark_android: "#042a49"
|
||||||
|
#color_ios: "#42a5f5"
|
||||||
|
#color_dark_ios: "#042a49"
|
||||||
|
#color_web: "#42a5f5"
|
||||||
|
#color_dark_web: "#042a49"
|
||||||
|
#image_android: assets/splash-android.png
|
||||||
|
#image_dark_android: assets/splash-invert-android.png
|
||||||
|
#image_ios: assets/splash-ios.png
|
||||||
|
#image_dark_ios: assets/splash-invert-ios.png
|
||||||
|
#image_web: assets/splash-web.png
|
||||||
|
#image_dark_web: assets/splash-invert-web.png
|
||||||
|
#background_image_android: "assets/background-android.png"
|
||||||
|
#background_image_dark_android: "assets/dark-background-android.png"
|
||||||
|
#background_image_ios: "assets/background-ios.png"
|
||||||
|
#background_image_dark_ios: "assets/dark-background-ios.png"
|
||||||
|
#background_image_web: "assets/background-web.png"
|
||||||
|
#background_image_dark_web: "assets/dark-background-web.png"
|
||||||
|
#branding_android: assets/brand-android.png
|
||||||
|
#branding_dark_android: assets/dart_dark-android.png
|
||||||
|
#branding_ios: assets/brand-ios.png
|
||||||
|
#branding_dark_ios: assets/dart_dark-ios.png
|
||||||
|
|
||||||
|
# The position of the splash image can be set with android_gravity, ios_content_mode, and
|
||||||
|
# web_image_mode parameters. All default to center.
|
||||||
|
#
|
||||||
|
# android_gravity can be one of the following Android Gravity (see
|
||||||
|
# https://developer.android.com/reference/android/view/Gravity): bottom, center,
|
||||||
|
# center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
|
||||||
|
# fill_vertical, left, right, start, or top.
|
||||||
|
#android_gravity: center
|
||||||
|
#
|
||||||
|
# ios_content_mode can be one of the following iOS UIView.ContentMode (see
|
||||||
|
# https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
|
||||||
|
# scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
|
||||||
|
# bottomLeft, or bottomRight.
|
||||||
|
#ios_content_mode: center
|
||||||
|
#
|
||||||
|
# web_image_mode can be one of the following modes: center, contain, stretch, and cover.
|
||||||
|
#web_image_mode: center
|
||||||
|
|
||||||
|
# The screen orientation can be set in Android with the android_screen_orientation parameter.
|
||||||
|
# Valid parameters can be found here:
|
||||||
|
# https://developer.android.com/guide/topics/manifest/activity-element#screen
|
||||||
|
#android_screen_orientation: sensorLandscape
|
||||||
|
|
||||||
|
# To hide the notification bar, use the fullscreen parameter. Has no effect in web since web
|
||||||
|
# has no notification bar. Defaults to false.
|
||||||
|
# NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
|
||||||
|
# To show the notification bar, add the following code to your Flutter app:
|
||||||
|
# WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
# SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]);
|
||||||
|
#fullscreen: true
|
||||||
|
|
||||||
|
# If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
|
||||||
|
# with the info_plist_files parameter. Remove only the # characters in the three lines below,
|
||||||
|
# do not remove any spaces:
|
||||||
|
#info_plist_files:
|
||||||
|
# - 'ios/Runner/Info-Debug.plist'
|
||||||
|
# - 'ios/Runner/Info-Release.plist'
|
||||||
@@ -13,6 +13,8 @@ PODS:
|
|||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
- image_picker_ios (0.0.1):
|
- image_picker_ios (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- integration_test (0.0.1):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_ios (0.0.1):
|
- path_provider_ios (0.0.1):
|
||||||
@@ -42,6 +44,7 @@ DEPENDENCIES:
|
|||||||
- flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`)
|
- flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||||
@@ -69,6 +72,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
image_picker_ios:
|
image_picker_ios:
|
||||||
:path: ".symlinks/plugins/image_picker_ios/ios"
|
:path: ".symlinks/plugins/image_picker_ios/ios"
|
||||||
|
integration_test:
|
||||||
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_ios:
|
path_provider_ios:
|
||||||
@@ -95,6 +100,7 @@ SPEC CHECKSUMS:
|
|||||||
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
||||||
|
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
||||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||||
|
|||||||
@@ -360,7 +360,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 79;
|
CURRENT_PROJECT_VERSION = 82;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 79;
|
CURRENT_PROJECT_VERSION = 82;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 79;
|
CURRENT_PROJECT_VERSION = 82;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage.png",
|
"filename" : "LaunchImage.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage@2x.png",
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage@3x.png",
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
@@ -16,13 +16,19 @@
|
|||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
||||||
</imageView>
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
|
||||||
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
|
||||||
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
@@ -33,5 +39,6 @@
|
|||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="168" height="185"/>
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
<image name="LaunchBackground" width="1" height="1"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -1,105 +1,97 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Immich</string>
|
<string>Immich</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>immich_mobile</string>
|
<string>immich_mobile</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.42.0</string>
|
<string>1.42.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>79</string>
|
<string>79</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true />
|
<true/>
|
||||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||||
<true />
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
<true />
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>NSLocationAlwaysUsageDescription</key>
|
<key>NSLocationAlwaysUsageDescription</key>
|
||||||
<string>Enable location setting to show position of assets on map</string>
|
<string>Enable location setting to show position of assets on map</string>
|
||||||
|
<key>NSLocationWhenInUseUsageDescription</key>
|
||||||
<key>NSLocationWhenInUseUsageDescription</key>
|
<string>Enable location setting to show position of assets on map</string>
|
||||||
<string>Enable location setting to show position of assets on map</string>
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>We need to manage backup your photos album</string>
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
<key>NSPhotoLibraryAddUsageDescription</key>
|
||||||
<string>We need to manage backup your photos album</string>
|
<string>We need to manage backup your photos album</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
<key>NSPhotoLibraryAddUsageDescription</key>
|
<string>We need to access the camera to let you take beautiful video using this app</string>
|
||||||
<string>We need to manage backup your photos album</string>
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>We need to access the microphone to let you take beautiful video using this app</string>
|
||||||
<key>NSCameraUsageDescription</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>We need to access the camera to let you take beautiful video using this app</string>
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
<key>NSMicrophoneUsageDescription</key>
|
<string>Main</string>
|
||||||
<string>We need to access the microphone to let you take beautiful video using this app</string>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
<key>UILaunchStoryboardName</key>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>LaunchScreen</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<string>Main</string>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
</array>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
</array>
|
||||||
<array>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<true/>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<key>io.flutter.embedded_views_preview</key>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<true/>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
</array>
|
<false/>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<true/>
|
||||||
<true />
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
<key>io.flutter.embedded_views_preview</key>
|
<array>
|
||||||
<true />
|
<string>https</string>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
</array>
|
||||||
<false />
|
<key>CFBundleLocalizations</key>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<array>
|
||||||
<true />
|
<string>cs</string>
|
||||||
|
<string>da</string>
|
||||||
|
<string>de</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
<string>en</string>
|
||||||
<array>
|
<string>es</string>
|
||||||
<string>https</string>
|
<string>fi</string>
|
||||||
</array>
|
<string>fr</string>
|
||||||
|
<string>it</string>
|
||||||
<key>CFBundleLocalizations</key>
|
<string>ja</string>
|
||||||
<array>
|
<string>ko</string>
|
||||||
<string>cs</string>
|
<string>nl</string>
|
||||||
<string>da</string>
|
<string>pl</string>
|
||||||
<string>de</string>
|
<string>pt</string>
|
||||||
<string>en</string>
|
<string>ru</string>
|
||||||
<string>es</string>
|
<string>sk</string>
|
||||||
<string>fi</string>
|
<string>zh</string>
|
||||||
<string>fr</string>
|
</array>
|
||||||
<string>it</string>
|
<key>UIStatusBarHidden</key>
|
||||||
<string>ja</string>
|
<false/>
|
||||||
<string>ko</string>
|
</dict>
|
||||||
<string>nl</string>
|
</plist>
|
||||||
<string>pl</string>
|
|
||||||
<string>pt</string>
|
|
||||||
<string>ru</string>
|
|
||||||
<string>sk</string>
|
|
||||||
<string>zh</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.42.0"
|
version_number: "1.43.1"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -5,32 +5,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000301">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000396">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.73906">
|
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.478301">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.857767">
|
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="3.846552">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.648708">
|
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.367554">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: build_app" time="88.88212">
|
<testcase classname="fastlane.lanes" name="4: build_app" time="75.618447">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="162.957763">
|
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="47.502114">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import 'package:immich_mobile/shared/models/asset.dart';
|
|||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
|
class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
|
||||||
SharedAlbumNotifier(this._sharedAlbumService, this._sharedAlbumCacheService)
|
SharedAlbumNotifier(this._albumService, this._sharedAlbumCacheService)
|
||||||
: super([]);
|
: super([]);
|
||||||
|
|
||||||
final AlbumService _sharedAlbumService;
|
final AlbumService _albumService;
|
||||||
final SharedAlbumCacheService _sharedAlbumCacheService;
|
final SharedAlbumCacheService _sharedAlbumCacheService;
|
||||||
|
|
||||||
_cacheState() {
|
_cacheState() {
|
||||||
@@ -22,7 +22,7 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
|
|||||||
List<String> sharedUserIds,
|
List<String> sharedUserIds,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
var newAlbum = await _sharedAlbumService.createAlbum(
|
var newAlbum = await _albumService.createAlbum(
|
||||||
albumName,
|
albumName,
|
||||||
assets,
|
assets,
|
||||||
sharedUserIds,
|
sharedUserIds,
|
||||||
@@ -47,7 +47,7 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<AlbumResponseDto>? sharedAlbums =
|
List<AlbumResponseDto>? sharedAlbums =
|
||||||
await _sharedAlbumService.getAlbums(isShared: true);
|
await _albumService.getAlbums(isShared: true);
|
||||||
|
|
||||||
if (sharedAlbums != null) {
|
if (sharedAlbums != null) {
|
||||||
state = sharedAlbums;
|
state = sharedAlbums;
|
||||||
@@ -61,7 +61,7 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> leaveAlbum(String albumId) async {
|
Future<bool> leaveAlbum(String albumId) async {
|
||||||
var res = await _sharedAlbumService.leaveAlbum(albumId);
|
var res = await _albumService.leaveAlbum(albumId);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
state = state.where((album) => album.id != albumId).toList();
|
state = state.where((album) => album.id != albumId).toList();
|
||||||
@@ -76,7 +76,7 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
|
|||||||
String albumId,
|
String albumId,
|
||||||
List<String> assetIds,
|
List<String> assetIds,
|
||||||
) async {
|
) async {
|
||||||
var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds);
|
var res = await _albumService.removeAssetFromAlbum(albumId, assetIds);
|
||||||
|
|
||||||
if (res) {
|
if (res) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:immich_mobile/modules/album/providers/asset_selection.provider.d
|
|||||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
||||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
||||||
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
import 'package:immich_mobile/modules/album/ui/add_to_album_sliverlist.dart';
|
||||||
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
||||||
@@ -15,7 +14,6 @@ import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
|||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class AddToAlbumBottomSheet extends HookConsumerWidget {
|
class AddToAlbumBottomSheet extends HookConsumerWidget {
|
||||||
|
|
||||||
/// The asset to add to an album
|
/// The asset to add to an album
|
||||||
final List<Asset> assets;
|
final List<Asset> assets;
|
||||||
|
|
||||||
@@ -38,7 +36,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
void addToAlbum(AlbumResponseDto album) async {
|
void addToAlbum(AlbumResponseDto album) async {
|
||||||
@@ -46,7 +44,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||||||
assets,
|
assets,
|
||||||
album.id,
|
album.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
if (result.alreadyInAlbum.isNotEmpty) {
|
if (result.alreadyInAlbum.isNotEmpty) {
|
||||||
ImmichToast.show(
|
ImmichToast.show(
|
||||||
@@ -59,7 +57,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||||||
msg: 'Added to ${album.albumName}',
|
msg: 'Added to ${album.albumName}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ref.read(albumProvider.notifier).getAllAlbums();
|
ref.read(albumProvider.notifier).getAllAlbums();
|
||||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
||||||
@@ -67,7 +65,6 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
@@ -83,6 +80,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
const SizedBox(height: 12),
|
||||||
const Align(
|
const Align(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: CustomDraggingHandle(),
|
child: CustomDraggingHandle(),
|
||||||
@@ -91,15 +89,20 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('Add to album',
|
Text(
|
||||||
|
'Add to album',
|
||||||
style: Theme.of(context).textTheme.headline2,
|
style: Theme.of(context).textTheme.headline2,
|
||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
label: const Text('Create new album'),
|
label: const Text('Create new album'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ref.watch(assetSelectionProvider.notifier).removeAll();
|
ref
|
||||||
ref.watch(assetSelectionProvider.notifier).addNewAssets(assets);
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.removeAll();
|
||||||
|
ref
|
||||||
|
.watch(assetSelectionProvider.notifier)
|
||||||
|
.addNewAssets(assets);
|
||||||
AutoRouter.of(context).push(
|
AutoRouter.of(context).push(
|
||||||
CreateAlbumRoute(
|
CreateAlbumRoute(
|
||||||
isSharedAlbum: false,
|
isSharedAlbum: false,
|
||||||
|
|||||||
@@ -1,134 +0,0 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/services/album.service.dart';
|
|
||||||
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart';
|
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
|
||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
|
|
||||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
|
|
||||||
class AddToAlbumList extends HookConsumerWidget {
|
|
||||||
|
|
||||||
/// The asset to add to an album
|
|
||||||
final List<Asset> assets;
|
|
||||||
|
|
||||||
const AddToAlbumList({
|
|
||||||
Key? key,
|
|
||||||
required this.assets,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
final albums = ref.watch(albumProvider);
|
|
||||||
final albumService = ref.watch(albumServiceProvider);
|
|
||||||
final sharedAlbums = ref.watch(sharedAlbumProvider);
|
|
||||||
|
|
||||||
useEffect(
|
|
||||||
() {
|
|
||||||
// Fetch album updates, e.g., cover image
|
|
||||||
ref.read(albumProvider.notifier).getAllAlbums();
|
|
||||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
|
||||||
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
void addToAlbum(AlbumResponseDto album) async {
|
|
||||||
final result = await albumService.addAdditionalAssetToAlbum(
|
|
||||||
assets,
|
|
||||||
album.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result != null) {
|
|
||||||
if (result.alreadyInAlbum.isNotEmpty) {
|
|
||||||
ImmichToast.show(
|
|
||||||
context: context,
|
|
||||||
msg: 'Already in ${album.albumName}',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
ImmichToast.show(
|
|
||||||
context: context,
|
|
||||||
msg: 'Added to ${album.albumName}',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ref.read(albumProvider.notifier).getAllAlbums();
|
|
||||||
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
shape: const RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.only(
|
|
||||||
topLeft: Radius.circular(15),
|
|
||||||
topRight: Radius.circular(15),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: ListView(
|
|
||||||
padding: const EdgeInsets.all(18.0),
|
|
||||||
children: [
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const Align(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
child: CustomDraggingHandle(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text('Add to album',
|
|
||||||
style: Theme.of(context).textTheme.headline2,
|
|
||||||
),
|
|
||||||
TextButton.icon(
|
|
||||||
icon: const Icon(Icons.add),
|
|
||||||
label: const Text('New album'),
|
|
||||||
onPressed: () {
|
|
||||||
ref.watch(assetSelectionProvider.notifier).removeAll();
|
|
||||||
ref.watch(assetSelectionProvider.notifier).addNewAssets(assets);
|
|
||||||
AutoRouter.of(context).push(
|
|
||||||
CreateAlbumRoute(
|
|
||||||
isSharedAlbum: false,
|
|
||||||
initialAssets: assets,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
if (sharedAlbums.isNotEmpty)
|
|
||||||
ExpansionTile(
|
|
||||||
title: const Text('Shared'),
|
|
||||||
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
|
|
||||||
leading: const Icon(Icons.group),
|
|
||||||
children: sharedAlbums.map((album) =>
|
|
||||||
AlbumThumbnailListTile(
|
|
||||||
album: album,
|
|
||||||
onTap: () => addToAlbum(album),
|
|
||||||
),
|
|
||||||
).toList(),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
... albums.map((album) =>
|
|
||||||
AlbumThumbnailListTile(
|
|
||||||
album: album,
|
|
||||||
onTap: () => addToAlbum(album),
|
|
||||||
),
|
|
||||||
).toList(),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -56,9 +56,10 @@ class AlbumThumbnailListTile extends StatelessWidget {
|
|||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onTap: onTap ?? () {
|
onTap: onTap ??
|
||||||
AutoRouter.of(context).push(AlbumViewerRoute(albumId: album.id));
|
() {
|
||||||
},
|
AutoRouter.of(context).push(AlbumViewerRoute(albumId: album.id));
|
||||||
|
},
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 12.0),
|
padding: const EdgeInsets.only(bottom: 12.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
|||||||
if (isSuccess) {
|
if (isSuccess) {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
ref.watch(assetSelectionProvider.notifier).disableMultiselection();
|
ref.watch(assetSelectionProvider.notifier).disableMultiselection();
|
||||||
|
ref.watch(albumProvider.notifier).getAllAlbums();
|
||||||
ref.invalidate(sharedAlbumDetailProvider(albumId));
|
ref.invalidate(sharedAlbumDetailProvider(albumId));
|
||||||
} else {
|
} else {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
|
import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
|
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
|
||||||
@@ -62,6 +63,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
if (addAssetsResult != null &&
|
if (addAssetsResult != null &&
|
||||||
addAssetsResult.successfullyAdded > 0) {
|
addAssetsResult.successfullyAdded > 0) {
|
||||||
|
ref.watch(albumProvider.notifier).getAllAlbums();
|
||||||
ref.invalidate(sharedAlbumDetailProvider(albumId));
|
ref.invalidate(sharedAlbumDetailProvider(albumId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import 'package:hive/hive.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
|
import 'package:immich_mobile/modules/album/ui/add_to_album_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/modules/album/ui/add_to_album_list.dart';
|
|
||||||
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
|
||||||
|
|||||||
@@ -57,30 +57,12 @@ class SplashScreenPage extends HookConsumerWidget {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
return Scaffold(
|
return const Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Image(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
image: AssetImage('assets/immich-logo-no-outline.png'),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
width: 200,
|
||||||
children: [
|
filterQuality: FilterQuality.high,
|
||||||
const Image(
|
|
||||||
image: AssetImage('assets/immich-logo-no-outline.png'),
|
|
||||||
width: 200,
|
|
||||||
filterQuality: FilterQuality.high,
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Text(
|
|
||||||
'IMMICH',
|
|
||||||
style: TextStyle(
|
|
||||||
fontFamily: 'SnowburstOne',
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 48,
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,4 +8,7 @@ create_app_icon:
|
|||||||
flutter pub run flutter_launcher_icons:main
|
flutter pub run flutter_launcher_icons:main
|
||||||
|
|
||||||
build_release_android:
|
build_release_android:
|
||||||
flutter build appbundle
|
flutter build appbundle
|
||||||
|
|
||||||
|
create_splash:
|
||||||
|
flutter pub run flutter_native_splash:create
|
||||||
|
|||||||
@@ -43,48 +43,51 @@ class AlbumResponseDto {
|
|||||||
List<AssetResponseDto> assets;
|
List<AssetResponseDto> assets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
|
bool operator ==(Object other) =>
|
||||||
other.assetCount == assetCount &&
|
identical(this, other) ||
|
||||||
other.id == id &&
|
other is AlbumResponseDto &&
|
||||||
other.ownerId == ownerId &&
|
other.assetCount == assetCount &&
|
||||||
other.albumName == albumName &&
|
other.id == id &&
|
||||||
other.createdAt == createdAt &&
|
other.ownerId == ownerId &&
|
||||||
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
other.albumName == albumName &&
|
||||||
other.shared == shared &&
|
other.createdAt == createdAt &&
|
||||||
other.sharedUsers == sharedUsers &&
|
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
||||||
other.assets == assets;
|
other.shared == shared &&
|
||||||
|
other.sharedUsers == sharedUsers &&
|
||||||
|
other.assets == assets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(assetCount.hashCode) +
|
(assetCount.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(albumName.hashCode) +
|
(albumName.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
||||||
(shared.hashCode) +
|
(shared.hashCode) +
|
||||||
(sharedUsers.hashCode) +
|
(sharedUsers.hashCode) +
|
||||||
(assets.hashCode);
|
(assets.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
|
String toString() =>
|
||||||
|
'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'assetCount'] = this.assetCount;
|
json[r'assetCount'] = this.assetCount;
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'ownerId'] = this.ownerId;
|
json[r'ownerId'] = this.ownerId;
|
||||||
json[r'albumName'] = this.albumName;
|
json[r'albumName'] = this.albumName;
|
||||||
json[r'createdAt'] = this.createdAt;
|
json[r'createdAt'] = this.createdAt;
|
||||||
if (this.albumThumbnailAssetId != null) {
|
if (this.albumThumbnailAssetId != null) {
|
||||||
json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
|
json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
|
||||||
} else {
|
} else {
|
||||||
// json[r'albumThumbnailAssetId'] = null;
|
// json[r'albumThumbnailAssetId'] = null;
|
||||||
}
|
}
|
||||||
json[r'shared'] = this.shared;
|
json[r'shared'] = this.shared;
|
||||||
json[r'sharedUsers'] = this.sharedUsers;
|
json[r'sharedUsers'] = this.sharedUsers;
|
||||||
json[r'assets'] = this.assets;
|
json[r'assets'] = this.assets;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,13 +101,13 @@ class AlbumResponseDto {
|
|||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
assert(() {
|
// assert(() {
|
||||||
requiredKeys.forEach((key) {
|
// requiredKeys.forEach((key) {
|
||||||
assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
|
// assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
|
||||||
assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
|
// assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
|
||||||
});
|
// });
|
||||||
return true;
|
// return true;
|
||||||
}());
|
// }());
|
||||||
|
|
||||||
return AlbumResponseDto(
|
return AlbumResponseDto(
|
||||||
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||||
@@ -112,7 +115,8 @@ class AlbumResponseDto {
|
|||||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||||
albumName: mapValueOfType<String>(json, r'albumName')!,
|
albumName: mapValueOfType<String>(json, r'albumName')!,
|
||||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||||
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
albumThumbnailAssetId:
|
||||||
|
mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
||||||
shared: mapValueOfType<bool>(json, r'shared')!,
|
shared: mapValueOfType<bool>(json, r'shared')!,
|
||||||
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
|
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
|
||||||
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
||||||
@@ -121,7 +125,10 @@ class AlbumResponseDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
static List<AlbumResponseDto>? listFromJson(
|
||||||
|
dynamic json, {
|
||||||
|
bool growable = false,
|
||||||
|
}) {
|
||||||
final result = <AlbumResponseDto>[];
|
final result = <AlbumResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
@@ -149,12 +156,18 @@ class AlbumResponseDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of AlbumResponseDto-objects as value to a dart map
|
// maps a json object with a list of AlbumResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
static Map<String, List<AlbumResponseDto>> mapListFromJson(
|
||||||
|
dynamic json, {
|
||||||
|
bool growable = false,
|
||||||
|
}) {
|
||||||
final map = <String, List<AlbumResponseDto>>{};
|
final map = <String, List<AlbumResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,);
|
final value = AlbumResponseDto.listFromJson(
|
||||||
|
entry.value,
|
||||||
|
growable: growable,
|
||||||
|
);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@@ -176,4 +189,3 @@ class AlbumResponseDto {
|
|||||||
'assets',
|
'assets',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,73 +82,76 @@ class AssetResponseDto {
|
|||||||
List<TagResponseDto> tags;
|
List<TagResponseDto> tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
bool operator ==(Object other) =>
|
||||||
other.type == type &&
|
identical(this, other) ||
|
||||||
other.id == id &&
|
other is AssetResponseDto &&
|
||||||
other.deviceAssetId == deviceAssetId &&
|
other.type == type &&
|
||||||
other.ownerId == ownerId &&
|
other.id == id &&
|
||||||
other.deviceId == deviceId &&
|
other.deviceAssetId == deviceAssetId &&
|
||||||
other.originalPath == originalPath &&
|
other.ownerId == ownerId &&
|
||||||
other.resizePath == resizePath &&
|
other.deviceId == deviceId &&
|
||||||
other.createdAt == createdAt &&
|
other.originalPath == originalPath &&
|
||||||
other.modifiedAt == modifiedAt &&
|
other.resizePath == resizePath &&
|
||||||
other.isFavorite == isFavorite &&
|
other.createdAt == createdAt &&
|
||||||
other.mimeType == mimeType &&
|
other.modifiedAt == modifiedAt &&
|
||||||
other.duration == duration &&
|
other.isFavorite == isFavorite &&
|
||||||
other.webpPath == webpPath &&
|
other.mimeType == mimeType &&
|
||||||
other.encodedVideoPath == encodedVideoPath &&
|
other.duration == duration &&
|
||||||
other.exifInfo == exifInfo &&
|
other.webpPath == webpPath &&
|
||||||
other.smartInfo == smartInfo &&
|
other.encodedVideoPath == encodedVideoPath &&
|
||||||
other.livePhotoVideoId == livePhotoVideoId &&
|
other.exifInfo == exifInfo &&
|
||||||
other.tags == tags;
|
other.smartInfo == smartInfo &&
|
||||||
|
other.livePhotoVideoId == livePhotoVideoId &&
|
||||||
|
other.tags == tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(type.hashCode) +
|
(type.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(deviceAssetId.hashCode) +
|
(deviceAssetId.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(deviceId.hashCode) +
|
(deviceId.hashCode) +
|
||||||
(originalPath.hashCode) +
|
(originalPath.hashCode) +
|
||||||
(resizePath == null ? 0 : resizePath!.hashCode) +
|
(resizePath == null ? 0 : resizePath!.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(modifiedAt.hashCode) +
|
(modifiedAt.hashCode) +
|
||||||
(isFavorite.hashCode) +
|
(isFavorite.hashCode) +
|
||||||
(mimeType == null ? 0 : mimeType!.hashCode) +
|
(mimeType == null ? 0 : mimeType!.hashCode) +
|
||||||
(duration.hashCode) +
|
(duration.hashCode) +
|
||||||
(webpPath == null ? 0 : webpPath!.hashCode) +
|
(webpPath == null ? 0 : webpPath!.hashCode) +
|
||||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||||
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
||||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||||
(tags.hashCode);
|
(tags.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]';
|
String toString() =>
|
||||||
|
'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'type'] = this.type;
|
json[r'type'] = this.type;
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'deviceAssetId'] = this.deviceAssetId;
|
json[r'deviceAssetId'] = this.deviceAssetId;
|
||||||
json[r'ownerId'] = this.ownerId;
|
json[r'ownerId'] = this.ownerId;
|
||||||
json[r'deviceId'] = this.deviceId;
|
json[r'deviceId'] = this.deviceId;
|
||||||
json[r'originalPath'] = this.originalPath;
|
json[r'originalPath'] = this.originalPath;
|
||||||
if (this.resizePath != null) {
|
if (this.resizePath != null) {
|
||||||
json[r'resizePath'] = this.resizePath;
|
json[r'resizePath'] = this.resizePath;
|
||||||
} else {
|
} else {
|
||||||
// json[r'resizePath'] = null;
|
// json[r'resizePath'] = null;
|
||||||
}
|
}
|
||||||
json[r'createdAt'] = this.createdAt;
|
json[r'createdAt'] = this.createdAt;
|
||||||
json[r'modifiedAt'] = this.modifiedAt;
|
json[r'modifiedAt'] = this.modifiedAt;
|
||||||
json[r'isFavorite'] = this.isFavorite;
|
json[r'isFavorite'] = this.isFavorite;
|
||||||
if (this.mimeType != null) {
|
if (this.mimeType != null) {
|
||||||
json[r'mimeType'] = this.mimeType;
|
json[r'mimeType'] = this.mimeType;
|
||||||
} else {
|
} else {
|
||||||
// json[r'mimeType'] = null;
|
// json[r'mimeType'] = null;
|
||||||
}
|
}
|
||||||
json[r'duration'] = this.duration;
|
json[r'duration'] = this.duration;
|
||||||
if (this.webpPath != null) {
|
if (this.webpPath != null) {
|
||||||
json[r'webpPath'] = this.webpPath;
|
json[r'webpPath'] = this.webpPath;
|
||||||
} else {
|
} else {
|
||||||
@@ -174,7 +177,7 @@ class AssetResponseDto {
|
|||||||
} else {
|
} else {
|
||||||
// json[r'livePhotoVideoId'] = null;
|
// json[r'livePhotoVideoId'] = null;
|
||||||
}
|
}
|
||||||
json[r'tags'] = this.tags;
|
json[r'tags'] = this.tags;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,13 +191,13 @@ class AssetResponseDto {
|
|||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
assert(() {
|
// assert(() {
|
||||||
requiredKeys.forEach((key) {
|
// requiredKeys.forEach((key) {
|
||||||
assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
// assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
||||||
assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
// assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
||||||
});
|
// });
|
||||||
return true;
|
// return true;
|
||||||
}());
|
// }());
|
||||||
|
|
||||||
return AssetResponseDto(
|
return AssetResponseDto(
|
||||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||||
@@ -220,7 +223,10 @@ class AssetResponseDto {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
static List<AssetResponseDto>? listFromJson(
|
||||||
|
dynamic json, {
|
||||||
|
bool growable = false,
|
||||||
|
}) {
|
||||||
final result = <AssetResponseDto>[];
|
final result = <AssetResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
@@ -248,12 +254,18 @@ class AssetResponseDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
static Map<String, List<AssetResponseDto>> mapListFromJson(
|
||||||
|
dynamic json, {
|
||||||
|
bool growable = false,
|
||||||
|
}) {
|
||||||
final map = <String, List<AssetResponseDto>>{};
|
final map = <String, List<AssetResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
|
final value = AssetResponseDto.listFromJson(
|
||||||
|
entry.value,
|
||||||
|
growable: growable,
|
||||||
|
);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
@@ -280,4 +292,3 @@ class AssetResponseDto {
|
|||||||
'tags',
|
'tags',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ packages:
|
|||||||
name: flutter_launcher_icons
|
name: flutter_launcher_icons
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.2"
|
version: "0.9.3"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -345,6 +345,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.14.0"
|
version: "0.14.0"
|
||||||
|
flutter_native_splash:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_native_splash
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.17"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -450,7 +457,7 @@ packages:
|
|||||||
name: html
|
name: html
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.15.0"
|
version: "0.15.1"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -473,12 +480,12 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.1"
|
||||||
image:
|
image:
|
||||||
dependency: transitive
|
dependency: "direct overridden"
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "4.0.12"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1119,6 +1126,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1"
|
version: "0.3.1"
|
||||||
|
universal_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.4"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -56,10 +56,14 @@ dev_dependencies:
|
|||||||
hive_generator: ^1.1.2
|
hive_generator: ^1.1.2
|
||||||
build_runner: ^2.2.1
|
build_runner: ^2.2.1
|
||||||
auto_route_generator: ^5.0.2
|
auto_route_generator: ^5.0.2
|
||||||
flutter_launcher_icons: "^0.9.2"
|
flutter_launcher_icons: ^0.9.2
|
||||||
|
flutter_native_splash: ^2.2.17
|
||||||
integration_test:
|
integration_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
dependency_overrides:
|
||||||
|
image: ^4.0.12
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class APIKeyStrategy extends PassportStrategy(Strategy, API_KEY_STRATEGY)
|
|||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(token: string): Promise<AuthUserDto> {
|
validate(token: string): Promise<AuthUserDto | null> {
|
||||||
return this.apiKeyService.validate(token);
|
return this.apiKeyService.validate(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class PublicShareStrategy extends PassportStrategy(Strategy, PUBLIC_SHARE
|
|||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(key: string): Promise<AuthUserDto> {
|
validate(key: string): Promise<AuthUserDto | null> {
|
||||||
return this.shareService.validate(key);
|
return this.shareService.validate(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,18 @@
|
|||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { AuthService, AuthUserDto } from '@app/domain';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { AuthService, AuthUserDto, UserService } from '@app/domain';
|
|
||||||
import { Strategy } from 'passport-custom';
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
import { Strategy } from 'passport-custom';
|
||||||
|
|
||||||
export const AUTH_COOKIE_STRATEGY = 'auth-cookie';
|
export const AUTH_COOKIE_STRATEGY = 'auth-cookie';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserAuthStrategy extends PassportStrategy(Strategy, AUTH_COOKIE_STRATEGY) {
|
export class UserAuthStrategy extends PassportStrategy(Strategy, AUTH_COOKIE_STRATEGY) {
|
||||||
constructor(private userService: UserService, private authService: AuthService) {
|
constructor(private authService: AuthService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(request: Request): Promise<AuthUserDto> {
|
validate(request: Request): Promise<AuthUserDto | null> {
|
||||||
const authUser = await this.authService.validate(request.headers);
|
return this.authService.validate(request.headers);
|
||||||
|
|
||||||
if (!authUser) {
|
|
||||||
throw new UnauthorizedException('Incorrect token provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
return authUser;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ async function bootstrap() {
|
|||||||
logger: getLogLevels(),
|
logger: getLogLevels(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const listeningPort = Number(process.env.MACHINE_LEARNING_PORT) || 3002;
|
const listeningPort = Number(process.env.MICROSERVICES_PORT) || 3002;
|
||||||
|
|
||||||
const redisIoAdapter = new RedisIoAdapter(app);
|
const redisIoAdapter = new RedisIoAdapter(app);
|
||||||
await redisIoAdapter.connectToRedis();
|
await redisIoAdapter.connectToRedis();
|
||||||
|
|||||||
@@ -1,50 +1,13 @@
|
|||||||
import { AssetType } from '@app/infra';
|
import { IAssetUploadedJob, JobName, JobService, QueueName } from '@app/domain';
|
||||||
import {
|
import { Process, Processor } from '@nestjs/bull';
|
||||||
IAssetUploadedJob,
|
import { Job } from 'bull';
|
||||||
IMetadataExtractionJob,
|
|
||||||
IThumbnailGenerationJob,
|
|
||||||
IVideoTranscodeJob,
|
|
||||||
QueueName,
|
|
||||||
JobName,
|
|
||||||
} from '@app/domain';
|
|
||||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
|
|
||||||
import { Job, Queue } from 'bull';
|
|
||||||
|
|
||||||
@Processor(QueueName.ASSET_UPLOADED)
|
@Processor(QueueName.ASSET_UPLOADED)
|
||||||
export class AssetUploadedProcessor {
|
export class AssetUploadedProcessor {
|
||||||
constructor(
|
constructor(private jobService: JobService) {}
|
||||||
@InjectQueue(QueueName.THUMBNAIL_GENERATION)
|
|
||||||
private thumbnailGeneratorQueue: Queue<IThumbnailGenerationJob>,
|
|
||||||
|
|
||||||
@InjectQueue(QueueName.METADATA_EXTRACTION)
|
|
||||||
private metadataExtractionQueue: Queue<IMetadataExtractionJob>,
|
|
||||||
|
|
||||||
@InjectQueue(QueueName.VIDEO_CONVERSION)
|
|
||||||
private videoConversionQueue: Queue<IVideoTranscodeJob>,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Post processing uploaded asset to perform the following function if missing
|
|
||||||
* 1. Generate JPEG Thumbnail
|
|
||||||
* 2. Generate Webp Thumbnail
|
|
||||||
* 3. EXIF extractor
|
|
||||||
* 4. Reverse Geocoding
|
|
||||||
*
|
|
||||||
* @param job asset-uploaded
|
|
||||||
*/
|
|
||||||
@Process(JobName.ASSET_UPLOADED)
|
@Process(JobName.ASSET_UPLOADED)
|
||||||
async processUploadedVideo(job: Job<IAssetUploadedJob>) {
|
async processUploadedVideo(job: Job<IAssetUploadedJob>) {
|
||||||
const { asset, fileName } = job.data;
|
await this.jobService.handleUploadedAsset(job);
|
||||||
|
|
||||||
await this.thumbnailGeneratorQueue.add(JobName.GENERATE_JPEG_THUMBNAIL, { asset });
|
|
||||||
|
|
||||||
// Video Conversion
|
|
||||||
if (asset.type == AssetType.VIDEO) {
|
|
||||||
await this.videoConversionQueue.add(JobName.VIDEO_CONVERSION, { asset });
|
|
||||||
await this.metadataExtractionQueue.add(JobName.EXTRACT_VIDEO_METADATA, { asset, fileName });
|
|
||||||
} else {
|
|
||||||
// Extract Metadata/Exif for Images - Currently the EXIF library on the web cannot extract EXIF for video yet
|
|
||||||
await this.metadataExtractionQueue.add(JobName.EXIF_EXTRACTION, { asset, fileName });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2707,7 +2707,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.42.0",
|
"version": "1.43.1",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { APIKeyEntity } from '@app/infra/db/entities';
|
import { APIKeyEntity } from '@app/infra/db/entities';
|
||||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { authStub, userEntityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
import { authStub, userEntityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
||||||
import { ICryptoRepository } from '../auth';
|
import { ICryptoRepository } from '../auth';
|
||||||
import { IKeyRepository } from './api-key.repository';
|
import { IKeyRepository } from './api-key.repository';
|
||||||
@@ -124,7 +124,7 @@ describe(APIKeyService.name, () => {
|
|||||||
it('should throw an error for an invalid id', async () => {
|
it('should throw an error for an invalid id', async () => {
|
||||||
keyMock.getKey.mockResolvedValue(null);
|
keyMock.getKey.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(sut.validate(token)).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate(token)).resolves.toBeNull();
|
||||||
|
|
||||||
expect(keyMock.getKey).toHaveBeenCalledWith('bXktYXBpLWtleQ== (hashed)');
|
expect(keyMock.getKey).toHaveBeenCalledWith('bXktYXBpLWtleQ== (hashed)');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { AuthUserDto, ICryptoRepository } from '../auth';
|
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||||
import { IKeyRepository } from './api-key.repository';
|
import { IKeyRepository } from './api-key.repository';
|
||||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
||||||
@@ -56,7 +56,7 @@ export class APIKeyService {
|
|||||||
return keys.map(mapKey);
|
return keys.map(mapKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(token: string): Promise<AuthUserDto> {
|
async validate(token: string): Promise<AuthUserDto | null> {
|
||||||
const hashedToken = this.crypto.hashSha256(token);
|
const hashedToken = this.crypto.hashSha256(token);
|
||||||
const keyEntity = await this.repository.getKey(hashedToken);
|
const keyEntity = await this.repository.getKey(hashedToken);
|
||||||
if (keyEntity?.user) {
|
if (keyEntity?.user) {
|
||||||
@@ -71,6 +71,6 @@ export class APIKeyService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnauthorizedException('Invalid API Key');
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ export class AuthCore {
|
|||||||
let accessTokenCookie = '';
|
let accessTokenCookie = '';
|
||||||
|
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
|
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
|
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Secure; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
} else {
|
} else {
|
||||||
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
|
accessTokenCookie = `${IMMICH_ACCESS_COOKIE}=${loginResponse.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Strict;`;
|
authTypeCookie = `${IMMICH_AUTH_TYPE_COOKIE}=${authType}; HttpOnly; Path=/; Max-Age=${maxAge}; SameSite=Lax;`;
|
||||||
}
|
}
|
||||||
return [accessTokenCookie, authTypeCookie];
|
return [accessTokenCookie, authTypeCookie];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ describe('AuthService', () => {
|
|||||||
describe('validate - api request', () => {
|
describe('validate - api request', () => {
|
||||||
it('should throw if no user is found', async () => {
|
it('should throw if no user is found', async () => {
|
||||||
userMock.get.mockResolvedValue(null);
|
userMock.get.mockResolvedValue(null);
|
||||||
await expect(sut.validate({ email: 'a', userId: 'test' })).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate({ email: 'a', userId: 'test' })).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an auth dto', async () => {
|
it('should return an auth dto', async () => {
|
||||||
|
|||||||
@@ -115,10 +115,10 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async validate(headers: IncomingHttpHeaders): Promise<AuthUserDto> {
|
public async validate(headers: IncomingHttpHeaders): Promise<AuthUserDto | null> {
|
||||||
const tokenValue = this.extractTokenFromHeader(headers);
|
const tokenValue = this.extractTokenFromHeader(headers);
|
||||||
if (!tokenValue) {
|
if (!tokenValue) {
|
||||||
throw new UnauthorizedException('No access token provided in request');
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashedToken = this.cryptoRepository.hashSha256(tokenValue);
|
const hashedToken = this.cryptoRepository.hashSha256(tokenValue);
|
||||||
@@ -133,7 +133,7 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnauthorizedException('Invalid access token provided');
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTokenFromHeader(headers: IncomingHttpHeaders) {
|
extractTokenFromHeader(headers: IncomingHttpHeaders) {
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
|
||||||
import { APIKeyService } from './api-key';
|
import { APIKeyService } from './api-key';
|
||||||
import { ShareService } from './share';
|
|
||||||
import { AuthService } from './auth';
|
import { AuthService } from './auth';
|
||||||
|
import { JobService } from './job';
|
||||||
import { OAuthService } from './oauth';
|
import { OAuthService } from './oauth';
|
||||||
|
import { ShareService } from './share';
|
||||||
import { INITIAL_SYSTEM_CONFIG, SystemConfigService } from './system-config';
|
import { INITIAL_SYSTEM_CONFIG, SystemConfigService } from './system-config';
|
||||||
import { UserService } from './user';
|
import { UserService } from './user';
|
||||||
|
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
APIKeyService,
|
APIKeyService,
|
||||||
AuthService,
|
AuthService,
|
||||||
|
JobService,
|
||||||
OAuthService,
|
OAuthService,
|
||||||
SystemConfigService,
|
SystemConfigService,
|
||||||
UserService,
|
UserService,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export * from './interfaces';
|
export * from './interfaces';
|
||||||
export * from './job.constants';
|
export * from './job.constants';
|
||||||
export * from './job.repository';
|
export * from './job.repository';
|
||||||
|
export * from './job.service';
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export interface JobCounts {
|
|||||||
waiting: number;
|
waiting: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Job<T> {
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
export type JobItem =
|
export type JobItem =
|
||||||
| { name: JobName.ASSET_UPLOADED; data: IAssetUploadedJob }
|
| { name: JobName.ASSET_UPLOADED; data: IAssetUploadedJob }
|
||||||
| { name: JobName.VIDEO_CONVERSION; data: IVideoConversionProcessor }
|
| { name: JobName.VIDEO_CONVERSION; data: IVideoConversionProcessor }
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { AssetEntity, AssetType } from '@app/infra/db/entities';
|
||||||
|
import { newJobRepositoryMock } from '../../test';
|
||||||
|
import { IAssetUploadedJob } from './interfaces';
|
||||||
|
import { JobName } from './job.constants';
|
||||||
|
import { IJobRepository, Job } from './job.repository';
|
||||||
|
import { JobService } from './job.service';
|
||||||
|
|
||||||
|
const jobStub = {
|
||||||
|
upload: {
|
||||||
|
video: Object.freeze<Job<IAssetUploadedJob>>({
|
||||||
|
data: { asset: { type: AssetType.VIDEO } as AssetEntity, fileName: 'video.mp4' },
|
||||||
|
}),
|
||||||
|
image: Object.freeze<Job<IAssetUploadedJob>>({
|
||||||
|
data: { asset: { type: AssetType.IMAGE } as AssetEntity, fileName: 'image.jpg' },
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe(JobService.name, () => {
|
||||||
|
let sut: JobService;
|
||||||
|
let jobMock: jest.Mocked<IJobRepository>;
|
||||||
|
|
||||||
|
it('should work', () => {
|
||||||
|
expect(sut).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
jobMock = newJobRepositoryMock();
|
||||||
|
sut = new JobService(jobMock);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('handleUploadedAsset', () => {
|
||||||
|
it('should process a video', async () => {
|
||||||
|
await expect(sut.handleUploadedAsset(jobStub.upload.video)).resolves.toBeUndefined();
|
||||||
|
|
||||||
|
expect(jobMock.add).toHaveBeenCalledTimes(3);
|
||||||
|
expect(jobMock.add.mock.calls).toEqual([
|
||||||
|
[{ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset: { type: AssetType.VIDEO } } }],
|
||||||
|
[{ name: JobName.VIDEO_CONVERSION, data: { asset: { type: AssetType.VIDEO } } }],
|
||||||
|
[{ name: JobName.EXTRACT_VIDEO_METADATA, data: { asset: { type: AssetType.VIDEO }, fileName: 'video.mp4' } }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should process an image', async () => {
|
||||||
|
await sut.handleUploadedAsset(jobStub.upload.image);
|
||||||
|
|
||||||
|
expect(jobMock.add).toHaveBeenCalledTimes(2);
|
||||||
|
expect(jobMock.add.mock.calls).toEqual([
|
||||||
|
[{ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset: { type: AssetType.IMAGE } } }],
|
||||||
|
[{ name: JobName.EXIF_EXTRACTION, data: { asset: { type: AssetType.IMAGE }, fileName: 'image.jpg' } }],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { IAssetUploadedJob } from './interfaces';
|
||||||
|
import { JobUploadCore } from './job.upload.core';
|
||||||
|
import { IJobRepository, Job } from './job.repository';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JobService {
|
||||||
|
private uploadCore: JobUploadCore;
|
||||||
|
|
||||||
|
constructor(@Inject(IJobRepository) repository: IJobRepository) {
|
||||||
|
this.uploadCore = new JobUploadCore(repository);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleUploadedAsset(job: Job<IAssetUploadedJob>) {
|
||||||
|
await this.uploadCore.handleAsset(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { AssetType } from '@app/infra/db/entities';
|
||||||
|
import { IAssetUploadedJob } from './interfaces';
|
||||||
|
import { JobName } from './job.constants';
|
||||||
|
import { IJobRepository, Job } from './job.repository';
|
||||||
|
|
||||||
|
export class JobUploadCore {
|
||||||
|
constructor(private repository: IJobRepository) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Post processing uploaded asset to perform the following function
|
||||||
|
* 1. Generate JPEG Thumbnail
|
||||||
|
* 2. Generate Webp Thumbnail
|
||||||
|
* 3. EXIF extractor
|
||||||
|
* 4. Reverse Geocoding
|
||||||
|
*
|
||||||
|
* @param job asset-uploaded
|
||||||
|
*/
|
||||||
|
async handleAsset(job: Job<IAssetUploadedJob>) {
|
||||||
|
const { asset, fileName } = job.data;
|
||||||
|
|
||||||
|
await this.repository.add({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset } });
|
||||||
|
|
||||||
|
// Video Conversion
|
||||||
|
if (asset.type == AssetType.VIDEO) {
|
||||||
|
await this.repository.add({ name: JobName.VIDEO_CONVERSION, data: { asset } });
|
||||||
|
await this.repository.add({ name: JobName.EXTRACT_VIDEO_METADATA, data: { asset, fileName } });
|
||||||
|
} else {
|
||||||
|
// Extract Metadata/Exif for Images - Currently the EXIF library on the web cannot extract EXIF for video yet
|
||||||
|
await this.repository.add({ name: JobName.EXIF_EXTRACTION, data: { asset, fileName } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
authStub,
|
authStub,
|
||||||
userEntityStub,
|
userEntityStub,
|
||||||
@@ -34,18 +34,18 @@ describe(ShareService.name, () => {
|
|||||||
describe('validate', () => {
|
describe('validate', () => {
|
||||||
it('should not accept a non-existant key', async () => {
|
it('should not accept a non-existant key', async () => {
|
||||||
shareMock.getByKey.mockResolvedValue(null);
|
shareMock.getByKey.mockResolvedValue(null);
|
||||||
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate('key')).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not accept an expired key', async () => {
|
it('should not accept an expired key', async () => {
|
||||||
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
||||||
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate('key')).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not accept a key without a user', async () => {
|
it('should not accept a key without a user', async () => {
|
||||||
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
||||||
userMock.get.mockResolvedValue(null);
|
userMock.get.mockResolvedValue(null);
|
||||||
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate('key')).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept a valid key', async () => {
|
it('should accept a valid key', async () => {
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import { BadRequestException, ForbiddenException, Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
ForbiddenException,
|
|
||||||
Inject,
|
|
||||||
Injectable,
|
|
||||||
Logger,
|
|
||||||
UnauthorizedException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthUserDto, ICryptoRepository } from '../auth';
|
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||||
import { IUserRepository, UserCore } from '../user';
|
import { IUserRepository, UserCore } from '../user';
|
||||||
import { EditSharedLinkDto } from './dto';
|
import { EditSharedLinkDto } from './dto';
|
||||||
@@ -28,7 +21,7 @@ export class ShareService {
|
|||||||
this.userCore = new UserCore(userRepository, cryptoRepository);
|
this.userCore = new UserCore(userRepository, cryptoRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(key: string): Promise<AuthUserDto> {
|
async validate(key: string): Promise<AuthUserDto | null> {
|
||||||
const link = await this.shareCore.getByKey(key);
|
const link = await this.shareCore.getByKey(key);
|
||||||
if (link) {
|
if (link) {
|
||||||
if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
|
if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
|
||||||
@@ -47,7 +40,7 @@ export class ShareService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new UnauthorizedException();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
||||||
|
|||||||
@@ -233,8 +233,8 @@ export const loginResponseStub = {
|
|||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
},
|
},
|
||||||
cookie: [
|
cookie: [
|
||||||
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Strict;',
|
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Lax;',
|
||||||
'immich_auth_type=oauth; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Strict;',
|
'immich_auth_type=oauth; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Lax;',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
user1password: {
|
user1password: {
|
||||||
@@ -249,8 +249,8 @@ export const loginResponseStub = {
|
|||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
},
|
},
|
||||||
cookie: [
|
cookie: [
|
||||||
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Strict;',
|
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Lax;',
|
||||||
'immich_auth_type=password; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Strict;',
|
'immich_auth_type=password; HttpOnly; Secure; Path=/; Max-Age=604800; SameSite=Lax;',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
user1insecure: {
|
user1insecure: {
|
||||||
@@ -265,8 +265,8 @@ export const loginResponseStub = {
|
|||||||
shouldChangePassword: false,
|
shouldChangePassword: false,
|
||||||
},
|
},
|
||||||
cookie: [
|
cookie: [
|
||||||
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Path=/; Max-Age=604800; SameSite=Strict;',
|
'immich_access_token=cmFuZG9tLWJ5dGVz; HttpOnly; Path=/; Max-Age=604800; SameSite=Lax;',
|
||||||
'immich_auth_type=password; HttpOnly; Path=/; Max-Age=604800; SameSite=Strict;',
|
'immich_auth_type=password; HttpOnly; Path=/; Max-Age=604800; SameSite=Lax;',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.43.0",
|
"version": "1.43.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.43.0",
|
"version": "1.43.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ node_modules
|
|||||||
/build
|
/build
|
||||||
/.svelte-kit
|
/.svelte-kit
|
||||||
/package
|
/package
|
||||||
|
/coverage
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
const run = (includeAllAssets: boolean) => {
|
const run = (includeAllAssets: boolean) => {
|
||||||
dispatch('click', { includeAllAssets });
|
dispatch('click', { includeAllAssets });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const locale = navigator.language;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-between rounded-3xl bg-gray-100 dark:bg-immich-dark-gray">
|
<div class="flex justify-between rounded-3xl bg-gray-100 dark:bg-immich-dark-gray">
|
||||||
@@ -43,7 +45,7 @@
|
|||||||
<p>Active</p>
|
<p>Active</p>
|
||||||
<p class="text-2xl">
|
<p class="text-2xl">
|
||||||
{#if jobCounts.active !== undefined}
|
{#if jobCounts.active !== undefined}
|
||||||
{jobCounts.active}
|
{jobCounts.active.toLocaleString(locale)}
|
||||||
{:else}
|
{:else}
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{/if}
|
{/if}
|
||||||
@@ -55,7 +57,7 @@
|
|||||||
>
|
>
|
||||||
<p class="text-2xl">
|
<p class="text-2xl">
|
||||||
{#if jobCounts.waiting !== undefined}
|
{#if jobCounts.waiting !== undefined}
|
||||||
{jobCounts.waiting}
|
{jobCounts.waiting.toLocaleString(locale)}
|
||||||
{:else}
|
{:else}
|
||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -383,7 +383,7 @@
|
|||||||
>
|
>
|
||||||
<svelte:fragment slot="leading">
|
<svelte:fragment slot="leading">
|
||||||
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||||
Selected {multiSelectAsset.size}
|
Selected {multiSelectAsset.size.toLocaleString(locale)}
|
||||||
</p>
|
</p>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="trailing">
|
<svelte:fragment slot="trailing">
|
||||||
|
|||||||
@@ -28,6 +28,8 @@
|
|||||||
|
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const locale = navigator.language;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
@@ -44,7 +46,9 @@
|
|||||||
{#if $selectedAssets.size == 0}
|
{#if $selectedAssets.size == 0}
|
||||||
<p class="text-lg dark:text-immich-dark-fg">Add to album</p>
|
<p class="text-lg dark:text-immich-dark-fg">Add to album</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p class="text-lg dark:text-immich-dark-fg">{$selectedAssets.size} selected</p>
|
<p class="text-lg dark:text-immich-dark-fg">
|
||||||
|
{$selectedAssets.size.toLocaleString(locale)} selected
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,8 @@
|
|||||||
clearMultiSelectAssetAssetHandler();
|
clearMultiSelectAssetAssetHandler();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const locale = navigator.language;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
@@ -90,7 +92,7 @@
|
|||||||
>
|
>
|
||||||
<svelte:fragment slot="leading">
|
<svelte:fragment slot="leading">
|
||||||
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||||
Selected {selectedAssets.size}
|
Selected {selectedAssets.size.toLocaleString(locale)}
|
||||||
</p>
|
</p>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="trailing">
|
<svelte:fragment slot="trailing">
|
||||||
|
|||||||
@@ -42,6 +42,8 @@
|
|||||||
owned: albumCount.owned
|
owned: albumCount.owned
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const locale = navigator.language;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6 bg-immich-bg dark:bg-immich-dark-bg">
|
<section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6 bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
@@ -73,8 +75,8 @@
|
|||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then data}
|
{:then data}
|
||||||
<div>
|
<div>
|
||||||
<p>{data.videos} Videos</p>
|
<p>{data.videos.toLocaleString(locale)} Videos</p>
|
||||||
<p>{data.photos} Photos</p>
|
<p>{data.photos.toLocaleString(locale)} Photos</p>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +106,7 @@
|
|||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then data}
|
{:then data}
|
||||||
<div>
|
<div>
|
||||||
<p>{data.shared + data.sharing} Albums</p>
|
<p>{(data.shared + data.sharing).toLocaleString(locale)} Albums</p>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
@@ -174,7 +176,7 @@
|
|||||||
<LoadingSpinner />
|
<LoadingSpinner />
|
||||||
{:then data}
|
{:then data}
|
||||||
<div>
|
<div>
|
||||||
<p>{data.owned} Albums</p>
|
<p>{data.owned.toLocaleString(locale)} Albums</p>
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
|
import WindowMinimize from 'svelte-material-icons/WindowMinimize.svelte';
|
||||||
import type { UploadAsset } from '$lib/models/upload-asset';
|
import type { UploadAsset } from '$lib/models/upload-asset';
|
||||||
import { notificationController, NotificationType } from './notification/notification';
|
import { notificationController, NotificationType } from './notification/notification';
|
||||||
import { getBytesWithUnit } from '../../utils/byte-units';
|
import { asByteUnitString } from '$lib/utils/byte-units';
|
||||||
|
|
||||||
let showDetail = true;
|
let showDetail = true;
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<input
|
<input
|
||||||
disabled
|
disabled
|
||||||
class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
|
class="bg-gray-100 border w-full p-1 rounded-md text-[10px] px-2"
|
||||||
value={`[${getBytesWithUnit(uploadAsset.file.size)}] ${uploadAsset.file.name}`}
|
value={`[${asByteUnitString(uploadAsset.file.size)}] ${uploadAsset.file.name}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
|
<div class="w-full bg-gray-300 h-[15px] rounded-md mt-[5px] text-white relative">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="flex min-w-[550px] border-b border-gray-300 dark:border-immich-dark-gray place-items-center py-4 gap-6 transition-all hover:border-immich-primary dark:hover:border-immich-dark-primary"
|
class="grid grid-cols-[75px_1fr] w-[550px] border-b border-gray-300 dark:border-immich-dark-gray place-items-center py-4 gap-6 transition-all hover:border-immich-primary dark:hover:border-immich-dark-primary"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
{#await loadImageData(album.albumThumbnailAssetId)}
|
{#await loadImageData(album.albumThumbnailAssetId)}
|
||||||
@@ -46,8 +46,10 @@
|
|||||||
{/await}
|
{/await}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="justify-self-start">
|
||||||
<p class="font-medium text-gray-800 dark:text-immich-dark-primary">{album.albumName}</p>
|
<p class="font-medium text-gray-800 dark:text-immich-dark-primary">
|
||||||
|
{album.albumName}
|
||||||
|
</p>
|
||||||
|
|
||||||
{#await getAlbumOwnerInfo() then albumOwner}
|
{#await getAlbumOwnerInfo() then albumOwner}
|
||||||
{#if user.email == albumOwner.email}
|
{#if user.email == albumOwner.email}
|
||||||
|
|||||||
@@ -145,6 +145,8 @@
|
|||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
isShowCreateSharedLinkModal = false;
|
isShowCreateSharedLinkModal = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const locale = navigator.language;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
@@ -156,7 +158,7 @@
|
|||||||
>
|
>
|
||||||
<svelte:fragment slot="leading">
|
<svelte:fragment slot="leading">
|
||||||
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||||
Selected {$selectedAssets.size}
|
Selected {$selectedAssets.size.toLocaleString(locale)}
|
||||||
</p>
|
</p>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="trailing">
|
<svelte:fragment slot="trailing">
|
||||||
|
|||||||