Compare commits
107 Commits
v1.9.0_13-
...
v1.17.0_25
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f3032f74a4 | ||
|
|
58ec7553ea | ||
|
|
357f7d1c31 | ||
|
|
e6d30d72fa | ||
|
|
355038a91a | ||
|
|
97d9b80baa | ||
|
|
b6814fad57 | ||
|
|
7586c65103 | ||
|
|
633170d743 | ||
|
|
c5be7827c3 | ||
|
|
e84c705e31 | ||
|
|
36162509e0 | ||
|
|
76bf1c0379 | ||
|
|
32b847c26e | ||
|
|
a45d6fdf57 | ||
|
|
c071e64a7e | ||
|
|
663f12851e | ||
|
|
c4ef523564 | ||
|
|
992f792c0a | ||
|
|
97611fa057 | ||
|
|
32240777c3 | ||
|
|
6065ff8caa | ||
|
|
8db073941d | ||
|
|
5e281b44e9 | ||
|
|
142ede350e | ||
|
|
a2e1d4caa2 | ||
|
|
5f00d8b9c6 | ||
|
|
2e85e18020 | ||
|
|
40a8115101 | ||
|
|
d02b97e1c1 | ||
|
|
485b152beb | ||
|
|
c918f5b001 | ||
|
|
cca2f7d178 | ||
|
|
baf533de35 | ||
|
|
dfc0d6eee7 | ||
|
|
7948cb8110 | ||
|
|
568436f188 | ||
|
|
04b59318f9 | ||
|
|
1a3d05ffc3 | ||
|
|
2f2db74d73 | ||
|
|
ef097d15dd | ||
|
|
caaa474c23 | ||
|
|
63bebd92e0 | ||
|
|
ad36b8b10f | ||
|
|
18c22d2a6c | ||
|
|
73024edba9 | ||
|
|
a360c0a3d7 | ||
|
|
34657f820f | ||
|
|
8840911f22 | ||
|
|
4aa66f4156 | ||
|
|
799a1c99f2 | ||
|
|
c4247bfea3 | ||
|
|
1e3464fe47 | ||
|
|
b7603fd150 | ||
|
|
517a3363d6 | ||
|
|
3511b69fc8 | ||
|
|
e6efc61b3b | ||
|
|
360c1d9a15 | ||
|
|
e3449f9c8f | ||
|
|
a8e723d722 | ||
|
|
d116234523 | ||
|
|
dce2bc7508 | ||
|
|
2bf764f560 | ||
|
|
587b77e70b | ||
|
|
53cd9fd8bf | ||
|
|
a8220172f8 | ||
|
|
397f8c70b4 | ||
|
|
68ff5377b0 | ||
|
|
b359dc3cb6 | ||
|
|
5b036067ed | ||
|
|
b9f38162d5 | ||
|
|
ab6909bfbd | ||
|
|
53c3c916a6 | ||
|
|
6924aa5eb1 | ||
|
|
a3b45d62b6 | ||
|
|
b34de624ce | ||
|
|
7886c42742 | ||
|
|
d476b15312 | ||
|
|
bdf38e7668 | ||
|
|
e33566a04a | ||
|
|
c28251b8b4 | ||
|
|
337db1c508 | ||
|
|
ad2a1ba901 | ||
|
|
fa6f6f8e9f | ||
|
|
a44043a4e5 | ||
|
|
87b15c60c0 | ||
|
|
2c83e52c15 | ||
|
|
55c5027539 | ||
|
|
ce06af0c9b | ||
|
|
baaf7ad153 | ||
|
|
4a25e7dc22 | ||
|
|
f90563d18c | ||
|
|
8352ecd3b9 | ||
|
|
69b34a4364 | ||
|
|
6023c3c624 | ||
|
|
171e7ffa77 | ||
|
|
d9f918005a | ||
|
|
e8ade4866b | ||
|
|
bbfa789a4e | ||
|
|
a779c3803c | ||
|
|
79dea504b0 | ||
|
|
4900fecd10 | ||
|
|
adfaab7eb2 | ||
|
|
c5adbea6e1 | ||
|
|
bb89fa4aab | ||
|
|
43d639104d | ||
|
|
a1792a7d94 |
15
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -1,15 +1,26 @@
|
|||||||
---
|
---
|
||||||
name: Bug report
|
name: Bug report
|
||||||
about: Create a report to help us improve
|
about: Create a report to help us improve
|
||||||
title: "[BUG]"
|
title: '[BUG] <title>'
|
||||||
labels: bug
|
labels: bug, need triage
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Note: Please search to see if an issue already exists for the bug you encountered.
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
**Describe the bug**
|
**Describe the bug**
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**Task List**
|
||||||
|
- [ ] I have read thoroughly the README setup and installation instructions.
|
||||||
|
- [ ] If my setup is different, I have included my docker-compose file.
|
||||||
|
- [ ] I have included my redacted `.env` file.
|
||||||
|
- [ ] I have included information on my machine, and environment.
|
||||||
|
|
||||||
**To Reproduce**
|
**To Reproduce**
|
||||||
Steps to reproduce the behavior:
|
Steps to reproduce the behavior:
|
||||||
1. Go to '...'
|
1. Go to '...'
|
||||||
|
|||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
32
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
name: Feature Request
|
||||||
|
description: Request a feature that you would like for the app
|
||||||
|
title: "[Feature]: "
|
||||||
|
labels: ["feature", "need triage"]
|
||||||
|
assignees:
|
||||||
|
- ""
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this feature request!
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: feature-detail
|
||||||
|
attributes:
|
||||||
|
label: Feature detail
|
||||||
|
placeholder: Describe the feature you would like to see for the app
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: dropdown
|
||||||
|
id: platform
|
||||||
|
attributes:
|
||||||
|
label: Platform
|
||||||
|
description: Choose the platform for the feature
|
||||||
|
options:
|
||||||
|
- Web
|
||||||
|
- Mobile App
|
||||||
|
- Server
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
122
.github/workflows/build_push_docker_latest.yml
vendored
@@ -4,18 +4,18 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
pull_request:
|
|
||||||
branches: [main]
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# This image include both the server and microservices - the two containers can be slitted into separated
|
||||||
build_and_push_server_latest:
|
# service with its coressponding entry file.
|
||||||
|
build_and_push_server_monorepo_latest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "main" # branch
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
@@ -26,39 +26,95 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- name: Build and push Immich
|
- name: Build and push Immich Mono Repo
|
||||||
uses: docker/build-push-action@v3.0.0
|
uses: docker/build-push-action@v3.0.0
|
||||||
with:
|
with:
|
||||||
context: ./server
|
context: ./server
|
||||||
file: ./server/Dockerfile
|
file: ./server/Dockerfile
|
||||||
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
altran1502/immich-server:latest
|
altran1502/immich-server:latest
|
||||||
|
|
||||||
build_and_push_microservice_latest:
|
build_and_push_machine_learning_latest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "main" # branch
|
fetch-depth: 0
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
- name: Set up QEMU
|
||||||
- name: Set up Docker Buildx
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
id: buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v2.0.0
|
id: buildx
|
||||||
- name: Login to Docker Hub
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
uses: docker/login-action@v2
|
- name: Login to Docker Hub
|
||||||
with:
|
uses: docker/login-action@v2
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
with:
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
- name: Build and Push Microservices
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
uses: docker/build-push-action@v3.0.0
|
- name: Build and Push Machine Learning
|
||||||
with:
|
uses: docker/build-push-action@v3.0.0
|
||||||
context: ./microservices
|
with:
|
||||||
file: ./microservices/Dockerfile
|
context: ./machine-learning
|
||||||
platforms: linux/arm/v7,linux/amd64
|
file: ./machine-learning/Dockerfile
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
platforms: linux/arm/v7,linux/amd64
|
||||||
tags: |
|
push: true
|
||||||
altran1502/immich-microservices:latest
|
tags: |
|
||||||
|
altran1502/immich-machine-learning:latest
|
||||||
|
|
||||||
|
build_and_push_web_latest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and Push Web
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./web
|
||||||
|
file: ./web/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
target: prod
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-web:latest
|
||||||
|
|
||||||
|
build_and_push_nginx_latest:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and Push Proxy
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./nginx
|
||||||
|
file: ./nginx/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-proxy:latest
|
||||||
|
|||||||
122
.github/workflows/build_push_docker_staging.yml
vendored
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
name: Build and Push Docker Image - Staging
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# This image include both the server and microservices - the two containers can be slitted into separated
|
||||||
|
# service with its coressponding entry file.
|
||||||
|
build_and_push_server_monorepo_staging:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push Immich Mono Repo
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./server
|
||||||
|
file: ./server/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name == 'pull_request' }}
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-server:staging
|
||||||
|
|
||||||
|
build_and_push_machine_learning_staging:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and Push Machine Learning
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./machine-learning
|
||||||
|
file: ./machine-learning/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64
|
||||||
|
push: ${{ github.event_name == 'pull_request' }}
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-machine-learning:staging
|
||||||
|
|
||||||
|
build_and_push_web_staging:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and Push Web
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./web
|
||||||
|
file: ./web/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
target: prod
|
||||||
|
push: ${{ github.event_name == 'pull_request' }}
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-web:staging
|
||||||
|
|
||||||
|
build_and_push_nginx_staging:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and Push Proxy
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./nginx
|
||||||
|
file: ./nginx/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name == 'pull_request' }}
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-proxy:staging
|
||||||
105
.github/workflows/build_push_server_release.yml
vendored
@@ -6,20 +6,20 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_and_push_server_release:
|
build_and_push_server_monorepo_release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "main"
|
ref: "main"
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: 'Get Previous tag'
|
- name: "Get Previous tag"
|
||||||
id: previoustag
|
id: previoustag
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
with:
|
with:
|
||||||
fallback: latest
|
fallback: latest
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
@@ -43,21 +43,55 @@ jobs:
|
|||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: |
|
tags: |
|
||||||
altran1502/immich-server:${{ steps.previoustag.outputs.tag }}
|
altran1502/immich-server:${{ steps.previoustag.outputs.tag }}
|
||||||
|
altran1502/immich-server:release
|
||||||
|
|
||||||
build_and_push_microservice_release:
|
build_and_push_machine_learning_release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: "main"
|
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
- name: "Get Previous tag"
|
||||||
- name: 'Get Previous tag'
|
|
||||||
id: previoustag
|
id: previoustag
|
||||||
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
with:
|
with:
|
||||||
fallback: latest
|
fallback: latest
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and Push Machine Learning
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./machine-learning
|
||||||
|
file: ./machine-learning/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-machine-learning:${{ steps.previoustag.outputs.tag }}
|
||||||
|
altran1502/immich-machine-learning:release
|
||||||
|
|
||||||
|
build_and_push_web_release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "main"
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: "Get Previous tag"
|
||||||
|
id: previoustag
|
||||||
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
|
with:
|
||||||
|
fallback: latest
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v2.0.0
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
@@ -72,12 +106,53 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push immich-microservices release
|
- name: Build and push immich-web release
|
||||||
uses: docker/build-push-action@v3.0.0
|
uses: docker/build-push-action@v3.0.0
|
||||||
with:
|
with:
|
||||||
context: ./microservices
|
context: ./web
|
||||||
file: ./microservices/Dockerfile
|
file: ./web/Dockerfile
|
||||||
platforms: linux/arm/v7,linux/amd64
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
target: prod
|
||||||
|
tags: |
|
||||||
|
altran1502/immich-web:${{ steps.previoustag.outputs.tag }}
|
||||||
|
altran1502/immich-web:release
|
||||||
|
|
||||||
|
build_and_push_nginx_release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
ref: "main"
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: "Get Previous tag"
|
||||||
|
id: previoustag
|
||||||
|
uses: "WyriHaximus/github-action-get-previous-tag@v1"
|
||||||
|
with:
|
||||||
|
fallback: latest
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v2.0.0
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v2.0.0
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v2
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push immich-proxy release
|
||||||
|
uses: docker/build-push-action@v3.0.0
|
||||||
|
with:
|
||||||
|
context: ./nginx
|
||||||
|
file: ./nginx/Dockerfile
|
||||||
|
platforms: linux/arm/v7,linux/amd64,linux/arm64
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: |
|
tags: |
|
||||||
altran1502/immich-microservices:${{ steps.previoustag.outputs.tag }}
|
altran1502/immich-proxy:release
|
||||||
|
altran1502/immich-proxy:${{ steps.previoustag.outputs.tag }}
|
||||||
|
|||||||
17
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Test
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push: { branches: master }
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-server-e2e:
|
||||||
|
name: Run test suite
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Run Immich Server 2E2 Test
|
||||||
|
run: docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test up --abort-on-container-exit --exit-code-from immich_server_test
|
||||||
14
Makefile
@@ -1,14 +1,20 @@
|
|||||||
dev:
|
dev:
|
||||||
docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||||
|
|
||||||
dev-update:
|
dev-update:
|
||||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
dev-scale:
|
dev-scale:
|
||||||
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich_server=3 --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich-server=3 --remove-orphans
|
||||||
|
|
||||||
|
stage:
|
||||||
|
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
|
test-e2e:
|
||||||
|
docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich_server_test --remove-orphans
|
||||||
|
|
||||||
prod:
|
prod:
|
||||||
docker-compose -f ./docker/docker-compose.yml up --build -V --remove-orphans
|
docker-compose -f ./docker/docker-compose.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
prod-scale:
|
prod-scale:
|
||||||
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich_server=3 --scale immich_microservices=3 --remove-orphans
|
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
||||||
9
NOTES.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# TODO
|
||||||
|
|
||||||
|
Server scenario with web
|
||||||
|
|
||||||
|
[ ] 1 user exist without admin right -> make admin on first check
|
||||||
|
|
||||||
|
[ ] 2 users exist without admin right -> ask user to choose which account will be the admin
|
||||||
|
|
||||||
|
[ X ] No users exist -> prompt signup form for Admin
|
||||||
145
README.md
@@ -8,22 +8,24 @@
|
|||||||
<img src="https://img.shields.io/teamcity/http/immichci.little-home.net/s/Immich_BuildAndPublishIOSToTestFlight.svg?style=for-the-badge&label=iOS&logo=teamcity&logoColor=000000&labelColor=ececec" alt="iOS Build"/>
|
<img src="https://img.shields.io/teamcity/http/immichci.little-home.net/s/Immich_BuildAndPublishIOSToTestFlight.svg?style=for-the-badge&label=iOS&logo=teamcity&logoColor=000000&labelColor=ececec" alt="iOS Build"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://actions-badge.atrox.dev/alextran1502/immich/goto?ref=main">
|
<a href="https://actions-badge.atrox.dev/alextran1502/immich/goto?ref=main">
|
||||||
<img alt="Build Status" src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Falextran1502%2Fimmich%2Fbadge%3Fref%3Dmain&style=for-the-badge&label=Server Docker&logo=docker&labelColor=ececec" />
|
<img alt="Build Status" src="https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Falextran1502%2Fimmich%2Fbadge%3Fref%3Dmain&style=for-the-badge&label=Github Action&logo=github&labelColor=ececec&logoColor=000000" />
|
||||||
|
</a>
|
||||||
|
<a href="https://discord.gg/D8JsnBEuKb">
|
||||||
|
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Immich%20Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Immich Discord"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="design/immich-logo.svg" width="200" title="Immich Logo">
|
<img src="design/feature-panel.png" title="Immich Logo">
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Immich
|
# Immich
|
||||||
|
|
||||||
Self-hosted photo and video backup solution directly from your mobile phone.
|
**High performance self-hosted photo and video backup solution.**
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -31,6 +33,7 @@ Loading ~4000 images/videos
|
|||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
|
||||||
|
### Mobile
|
||||||
<p align="left">
|
<p align="left">
|
||||||
<img src="design/login-screen.png" width="150" title="Login With Custom URL">
|
<img src="design/login-screen.png" width="150" title="Login With Custom URL">
|
||||||
<img src="design/backup-screen.png" width="150" title="Backup Setting Info">
|
<img src="design/backup-screen.png" width="150" title="Backup Setting Info">
|
||||||
@@ -39,57 +42,59 @@ Loading ~4000 images/videos
|
|||||||
<img src="design/search-screen.jpeg" width="150" title="Curated Search Info">
|
<img src="design/search-screen.jpeg" width="150" title="Curated Search Info">
|
||||||
<img src="design/shared-albums.png" width="150" title="Shared Albums">
|
<img src="design/shared-albums.png" width="150" title="Shared Albums">
|
||||||
<img src="design/nsc6.png" width="150" title="EXIF Info">
|
<img src="design/nsc6.png" width="150" title="EXIF Info">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
### Web
|
||||||
|
<p align="left">
|
||||||
|
<img src="design/web-home.jpeg" width="49%" title="Home Dashboard">
|
||||||
|
<img src="design/web-detail.jpeg" width="49%" title="Detail">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Note
|
# Note
|
||||||
|
|
||||||
**!! NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS !!**
|
**!! NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS !!**
|
||||||
|
|
||||||
This project is under heavy development, there will be continous functions, features and api changes.
|
This project is under heavy development, there will be continuous functions, features and api changes.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Upload and view assets (videos/images).
|
| | Mobile | Web |
|
||||||
- Auto Backup.
|
| - | - | - |
|
||||||
- Download asset to local device.
|
| Upload and view videos and photos | Yes | Yes
|
||||||
- Multi-user supported.
|
| Auto backup when app is opened | Yes | N/A
|
||||||
- Quick navigation with drag scroll bar.
|
| Selective album(s) for backup | Yes | N/A
|
||||||
- Support HEIC/HEIF Backup.
|
| Download photos and videos to local device | Yes | Yes
|
||||||
- Extract and display EXIF info.
|
| Multi-user support | Yes | Yes
|
||||||
- Real-time render from multi-device upload event.
|
| Shared Albums | Yes | No
|
||||||
- Image Tagging/Classification based on ImageNet dataset
|
| Quick navigation with draggable scrollbar | Yes | Yes
|
||||||
- Object detection based on COCO SSD.
|
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
|
||||||
- Search assets based on tags and exif data (lens, make, model, orientation)
|
| Metadata view (EXIF, map) | Yes | Yes
|
||||||
- [Optional] Reverse geocoding using Mapbox (Generous free-tier of 100,000 search/month)
|
| Search by metadata, objects and image tags | Yes | No
|
||||||
- Show asset's location information on map (OpenStreetMap).
|
| Administrative functions (user management) | No | Yes
|
||||||
- Show curated places on the search page
|
|
||||||
- Show curated objects on the search page
|
|
||||||
- Shared album with users on the same server
|
|
||||||
- Selective backup - albums can be included and excluded during the backup process.
|
|
||||||
|
|
||||||
|
|
||||||
# System Requirement
|
# System Requirement
|
||||||
|
|
||||||
**OS**: Preferred Linux-based operating system (Ubuntu, Debian, MacOS...etc).
|
**OS**: Preferred unix-based operating system (Ubuntu, Debian, MacOS...etc).
|
||||||
|
|
||||||
I haven't tested with `Docker for Windows` as well as `WSL` on Windows
|
I haven't tested with `Docker for Windows` as well as `WSL` on Windows
|
||||||
|
|
||||||
*Raspberry Pi can be used but `microservices` container has to be comment out in `docker-compose` since TensorFlow has not been supported in Dockec image on arm64v7 yet.*
|
*Raspberry Pi can be used but `microservices` container has to be comment out in `docker-compose` since TensorFlow has not been supported in Docker image on arm64v7 yet.*
|
||||||
|
|
||||||
**RAM**: At least 2GB, preffered 4GB.
|
**RAM**: At least 2GB, preffered 4GB.
|
||||||
|
|
||||||
**Core**: At least 2 cores, preffered 4 cores.
|
**Core**: At least 2 cores, preffered 4 cores.
|
||||||
|
|
||||||
# Development and Testing out the application
|
# Getting Started
|
||||||
|
|
||||||
You can use docker compose for development and testing out the application, there are several services that compose Immich:
|
You can use docker compose for development and testing out the application, there are several services that compose Immich:
|
||||||
|
|
||||||
1. **NestJs** - Backend of the application
|
1. **NestJs** - Backend of the application
|
||||||
2. **PostgreSQL** - Main database of the application
|
2. **SvelteKit** - Web frontend of the application
|
||||||
3. **Redis** - For sharing websocket instance between docker instances and background tasks message queue.
|
3. **PostgreSQL** - Main database of the application
|
||||||
4. **Nginx** - Load balancing and optimized file uploading.
|
4. **Redis** - For sharing websocket instance between docker instances and background tasks message queue.
|
||||||
5. **TensorFlow** - Object Detection and Image Classification.
|
5. **Nginx** - Load balancing and optimized file uploading.
|
||||||
|
6. **TensorFlow** - Object Detection (COCO SSD) and Image Classification (ImageNet).
|
||||||
|
|
||||||
## Step 1: Populate .env file
|
## Step 1: Populate .env file
|
||||||
|
|
||||||
@@ -108,56 +113,83 @@ Pay attention to the key `UPLOAD_LOCATION`, this directory must exist and is own
|
|||||||
**Example**
|
**Example**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
###################################################################################
|
||||||
# Database
|
# Database
|
||||||
|
###################################################################################
|
||||||
DB_USERNAME=postgres
|
DB_USERNAME=postgres
|
||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
DB_DATABASE_NAME=immich
|
DB_DATABASE_NAME=immich
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
# Upload File Config
|
# Upload File Config
|
||||||
|
###################################################################################
|
||||||
UPLOAD_LOCATION=<put-the-path-of-the-upload-folder-here>
|
UPLOAD_LOCATION=<put-the-path-of-the-upload-folder-here>
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
# JWT SECRET
|
# JWT SECRET
|
||||||
|
###################################################################################
|
||||||
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
# MAPBOX
|
# MAPBOX
|
||||||
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
####################################################################################
|
||||||
|
# ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||||
ENABLE_MAPBOX=false
|
ENABLE_MAPBOX=false
|
||||||
MAPBOX_KEY=
|
MAPBOX_KEY=
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# WEB
|
||||||
|
###################################################################################
|
||||||
|
# This is the URL of your vm/server where you host Immich, so that the web frontend
|
||||||
|
# know where can it make the request to.
|
||||||
|
# For example: If your server IP address is 10.1.11.50, the environment variable will
|
||||||
|
# be VITE_SERVER_ENDPOINT=http://10.1.11.50:2283/api
|
||||||
|
VITE_SERVER_ENDPOINT=http://192.168.1.216:2283/api
|
||||||
```
|
```
|
||||||
|
|
||||||
## Step 2: Start the server
|
## Step 2: Start the server
|
||||||
|
|
||||||
To start, run
|
To **start**, run
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f ./docker/docker-compose.yml up
|
docker-compose -f ./docker/docker-compose.yml up
|
||||||
```
|
```
|
||||||
|
|
||||||
If you have a few thousand photos/videos, I suggest running docker-compose with scaling option for the `immich_server` container to handle high I/O load when using fast scrolling.
|
If you have a few thousand photos/videos, I suggest running docker-compose with *scaling* option for the `immich_server` container to handle high I/O load when using fast scrolling.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker-compose -f ./docker/docker-compose.yml up --scale immich_server=5
|
docker-compose -f ./docker/docker-compose.yml up --scale immich-server=5
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To *update* docker-compose with newest image (if you have started the docker-compose previously)
|
||||||
|
|
||||||
The server will be running at `http://your-ip:2283` through `Nginx`
|
```bash
|
||||||
|
docker-compose -f ./docker/docker-compose.yml pull && docker-compose -f ./docker/docker-compose.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will be running at `http://your-ip:2283/api` through `Nginx`
|
||||||
|
|
||||||
## Step 3: Register User
|
## Step 3: Register User
|
||||||
|
|
||||||
Use the command below on your terminal to create user as we don't have user interface for this function yet.
|
Access the web interface at `http://your-ip:2283` to register an admin account.
|
||||||
|
|
||||||
```bash
|
<p align="left">
|
||||||
curl --location --request POST 'http://your-server-ip:2283/auth/signUp' \
|
<img src="design/admin-registration-form.png" width="300" title="Admin Registration">
|
||||||
--header 'Content-Type: application/json' \
|
<p/>
|
||||||
--data-raw '{
|
|
||||||
"email": "testuser@email.com",
|
Additional accounts on the server can be created by the admin account.
|
||||||
"password": "password"
|
|
||||||
}'
|
<p align="left">
|
||||||
```
|
<img src="design/admin-interface.png" width="500" title="Admin User Management">
|
||||||
|
<p/>
|
||||||
|
|
||||||
## Step 4: Run mobile app
|
## Step 4: Run mobile app
|
||||||
|
|
||||||
The app is distributed on several platforms below.
|
Login the mobile app with your server address
|
||||||
|
|
||||||
|
<p align="left">
|
||||||
|
<img src="design/login-screen.jpeg" width="250" title="Example login screen">
|
||||||
|
<p/>
|
||||||
|
|
||||||
## F-Droid
|
## F-Droid
|
||||||
You can get the app on F-droid by clicking the image below.
|
You can get the app on F-droid by clicking the image below.
|
||||||
@@ -188,15 +220,26 @@ You can get the app on F-droid by clicking the image below.
|
|||||||
<img src="design/ios-qr-code.png" width="200" title="Apple App Store">
|
<img src="design/ios-qr-code.png" width="200" title="Apple App Store">
|
||||||
<p/>
|
<p/>
|
||||||
|
|
||||||
|
|
||||||
|
# Development
|
||||||
|
|
||||||
|
The development environment can be started from the root of the project after populating the `.env` file with the command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make dev # required Makefile installed on the system.
|
||||||
|
```
|
||||||
|
|
||||||
|
All servers and web container are hot reload for quick feedback loop.
|
||||||
|
|
||||||
# Support
|
# Support
|
||||||
|
|
||||||
If you like the app, find it helpful, and want to support me to offset the cost of publishing to AppStores, you can sponsor the project with [**Github Sponsore**](https://github.com/sponsors/alextran1502), or one time donation with Buy Me a coffee link below.
|
If you like the app, find it helpful, and want to support me to offset the cost of publishing to AppStores, you can sponsor the project with [**Github Sponsor**](https://github.com/sponsors/alextran1502), or a one time donation with the Buy Me a coffee link below.
|
||||||
|
|
||||||
[](https://www.buymeacoffee.com/altran1502)
|
[](https://www.buymeacoffee.com/altran1502)
|
||||||
|
|
||||||
This is also a meaningful way to give me motivation and encounragment to continue working on the app.
|
This is also a meaningful way to give me motivation and encouragement to continue working on the app.
|
||||||
|
|
||||||
Cheer! 🎉
|
Cheers! 🎉
|
||||||
|
|
||||||
# Known Issue
|
# Known Issue
|
||||||
|
|
||||||
@@ -204,18 +247,18 @@ Cheer! 🎉
|
|||||||
|
|
||||||
*This is a known issue on RaspberryPi 4 arm64-v7 and incorrect Promox setup*
|
*This is a known issue on RaspberryPi 4 arm64-v7 and incorrect Promox setup*
|
||||||
|
|
||||||
TensorFlow doesn't run with older CPU architecture, it requires CPU with AVX and AVX2 instruction set. If you encounter the error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command and make sure you see `AVX` and `AVX2`:
|
TensorFlow doesn't run with older CPU architecture, it requires a CPU with AVX and AVX2 instruction set. If you encounter the error `illegal instruction core dump` when running the docker-compose command above, check for your CPU flags with the command and make sure you see `AVX` and `AVX2`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
more /proc/cpuinfo | grep flags
|
more /proc/cpuinfo | grep flags
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are running virtualization in Promox, the VM doesn't have the flag enable.
|
If you are running virtualization in Promox, the VM doesn't have the flag enabled.
|
||||||
|
|
||||||
You need to change the CPU type from `kvm64` to `host` under VMs hardware tab.
|
You need to change the CPU type from `kvm64` to `host` under VMs hardware tab.
|
||||||
|
|
||||||
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
|
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
|
||||||
|
|
||||||
Otherwise you can:
|
Otherwise you can:
|
||||||
- edit `docker-compose.yml` file and comment the whole `immich_microservices` service **which will disable machine learning features like object detection and image classification**
|
- edit `docker-compose.yml` file and comment the whole `immich-machine-learning` service **which will disable machine learning features like object detection and image classification**
|
||||||
- switch to a different VM/desktop with different architecture.
|
- switch to a different VM/desktop with different architecture.
|
||||||
|
|||||||
BIN
design/admin-interface.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
design/admin-registration-form.png
Normal file
|
After Width: | Height: | Size: 105 KiB |
BIN
design/feature-panel.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
design/login-screen.jpeg
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
design/web-admin.jpeg
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
design/web-detail.jpeg
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
design/web-home.jpeg
Normal file
|
After Width: | Height: | Size: 206 KiB |
@@ -1,15 +1,63 @@
|
|||||||
|
###################################################################################
|
||||||
# Database
|
# Database
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
|
DB_HOSTNAME=immich_postgres
|
||||||
DB_USERNAME=postgres
|
DB_USERNAME=postgres
|
||||||
DB_PASSWORD=postgres
|
DB_PASSWORD=postgres
|
||||||
DB_DATABASE_NAME=immich
|
DB_DATABASE_NAME=immich
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# Redis
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
|
REDIS_HOSTNAME=immich_redis
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
# Upload File Config
|
# Upload File Config
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
|
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
# JWT SECRET
|
# JWT SECRET
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
# MAPBOX
|
# MAPBOX
|
||||||
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
####################################################################################
|
||||||
|
|
||||||
|
# ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||||
ENABLE_MAPBOX=false
|
ENABLE_MAPBOX=false
|
||||||
MAPBOX_KEY=
|
MAPBOX_KEY=
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# WEB
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
|
# This is the URL of your vm/server where you host Immich, so that the web frontend
|
||||||
|
# know where can it make the request to.
|
||||||
|
# For example: If your server IP address is 10.1.11.50, the environment variable will
|
||||||
|
# be VITE_SERVER_ENDPOINT=http://10.1.11.50:2283/api
|
||||||
|
# !CAUTION! THERE IS NO FORWARD SLASH AT THE END
|
||||||
|
|
||||||
|
VITE_SERVER_ENDPOINT=
|
||||||
|
|||||||
22
docker/.env.test
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Database
|
||||||
|
DB_HOSTNAME=immich_postgres_test
|
||||||
|
DB_USERNAME=postgres
|
||||||
|
DB_PASSWORD=postgres
|
||||||
|
DB_DATABASE_NAME=e2e_test
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
REDIS_HOSTNAME=immich_redis_test
|
||||||
|
|
||||||
|
# Upload File Config
|
||||||
|
UPLOAD_LOCATION=./upload
|
||||||
|
|
||||||
|
# JWT SECRET
|
||||||
|
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
|
||||||
|
|
||||||
|
# MAPBOX
|
||||||
|
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
|
||||||
|
ENABLE_MAPBOX=false
|
||||||
|
|
||||||
|
# WEB
|
||||||
|
MAPBOX_KEY=
|
||||||
|
VITE_SERVER_ENDPOINT=http://localhost:2283/api
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
immich_server:
|
immich-server:
|
||||||
image: immich-server-dev:1.9.0
|
image: immich-server-dev:latest
|
||||||
build:
|
build:
|
||||||
context: ../server
|
context: ../server
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
command: npm run start:dev
|
command: npm run start:dev immich
|
||||||
expose:
|
|
||||||
- "3000"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../server:/usr/src/app
|
- ../server:/usr/src/app
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
@@ -20,19 +18,15 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
immich_microservices:
|
immich-machine-learning:
|
||||||
image: immich-microservices-dev:1.9.0
|
image: immich-machine-learning-dev:latest
|
||||||
build:
|
build:
|
||||||
context: ../microservices
|
context: ../machine-learning
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
command: npm run start:dev
|
command: npm run start:dev
|
||||||
expose:
|
|
||||||
- "3001"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ../microservices:/usr/src/app
|
- ../machine-learning:/usr/src/app
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
- /usr/src/app/node_modules
|
- /usr/src/app/node_modules
|
||||||
env_file:
|
env_file:
|
||||||
@@ -41,15 +35,45 @@ services:
|
|||||||
- NODE_ENV=development
|
- NODE_ENV=development
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
|
immich-microservices:
|
||||||
|
image: immich-microservices:latest
|
||||||
|
build:
|
||||||
|
context: ../server
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
command: npm run start:dev microservices
|
||||||
|
volumes:
|
||||||
|
- ../server:/usr/src/app
|
||||||
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
|
- /usr/src/app/node_modules
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
- immich-server
|
||||||
|
|
||||||
|
immich-web:
|
||||||
|
image: immich-web-dev:1.9.0
|
||||||
|
build:
|
||||||
|
context: ../web
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: dev
|
||||||
|
command: npm run dev --host
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
ports:
|
||||||
|
- 3000:3000
|
||||||
|
- 24678:24678
|
||||||
|
volumes:
|
||||||
|
- ../web:/usr/src/app
|
||||||
|
- /usr/src/app/node_modules
|
||||||
|
restart: always
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2
|
image: redis:6.2
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
@@ -65,25 +89,21 @@ services:
|
|||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
nginx:
|
immich-proxy:
|
||||||
container_name: proxy_nginx
|
container_name: immich_proxy
|
||||||
image: nginx:latest
|
image: immich-proxy-dev:latest
|
||||||
volumes:
|
build:
|
||||||
- ./settings/nginx-conf:/etc/nginx/conf.d
|
context: ../nginx
|
||||||
|
dockerfile: Dockerfile
|
||||||
ports:
|
ports:
|
||||||
- 2283:80
|
- 2283:80
|
||||||
- 2284:443
|
- 2284:443
|
||||||
logging:
|
logging:
|
||||||
driver: none
|
driver: none
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- immich_server
|
- immich-server
|
||||||
|
restart: always
|
||||||
|
|
||||||
networks:
|
|
||||||
immich_network:
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
version: "3.8"
|
|
||||||
|
|
||||||
services:
|
|
||||||
immich_server:
|
|
||||||
image: immich-server-dev:1.9.0
|
|
||||||
build:
|
|
||||||
context: ../server
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
command: npm run start:dev
|
|
||||||
expose:
|
|
||||||
- "3000"
|
|
||||||
volumes:
|
|
||||||
- ../server:/usr/src/app
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
- /usr/src/app/node_modules
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- database
|
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
immich_microservices:
|
|
||||||
image: immich-microservices-dev:1.9.0
|
|
||||||
build:
|
|
||||||
context: ../microservices
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
command: npm run start:dev
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
reservations:
|
|
||||||
devices:
|
|
||||||
- driver: nvidia
|
|
||||||
count: 1
|
|
||||||
capabilities: [ gpu ]
|
|
||||||
expose:
|
|
||||||
- "3001"
|
|
||||||
volumes:
|
|
||||||
- ../microservices:/usr/src/app
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
- /usr/src/app/node_modules
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
- database
|
|
||||||
- immich_server
|
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
redis:
|
|
||||||
container_name: immich_redis
|
|
||||||
image: redis:6.2
|
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
database:
|
|
||||||
container_name: immich_postgres
|
|
||||||
image: postgres:14
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
|
||||||
POSTGRES_USER: ${DB_USERNAME}
|
|
||||||
POSTGRES_DB: ${DB_DATABASE_NAME}
|
|
||||||
PG_DATA: /var/lib/postgresql/data
|
|
||||||
volumes:
|
|
||||||
- pgdata:/var/lib/postgresql/data
|
|
||||||
ports:
|
|
||||||
- 5432:5432
|
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
nginx:
|
|
||||||
container_name: proxy_nginx
|
|
||||||
image: nginx:latest
|
|
||||||
volumes:
|
|
||||||
- ./settings/nginx-conf:/etc/nginx/conf.d
|
|
||||||
ports:
|
|
||||||
- 2283:80
|
|
||||||
- 2284:443
|
|
||||||
logging:
|
|
||||||
driver: none
|
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
depends_on:
|
|
||||||
- immich_server
|
|
||||||
|
|
||||||
networks:
|
|
||||||
immich_network:
|
|
||||||
volumes:
|
|
||||||
pgdata:
|
|
||||||
84
docker/docker-compose.staging.yml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
immich-server:
|
||||||
|
image: altran1502/immich-server:staging
|
||||||
|
entrypoint: ["/bin/sh", "./start-server.sh"]
|
||||||
|
volumes:
|
||||||
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- database
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
immich-microservices:
|
||||||
|
image: altran1502/immich-server:staging
|
||||||
|
entrypoint: ["/bin/sh", "./start-microservices.sh"]
|
||||||
|
volumes:
|
||||||
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- database
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
immich-machine-learning:
|
||||||
|
image: altran1502/immich-machine-learning:staging
|
||||||
|
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||||
|
volumes:
|
||||||
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
depends_on:
|
||||||
|
- database
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
immich-web:
|
||||||
|
image: altran1502/immich-web:staging
|
||||||
|
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: immich_redis
|
||||||
|
image: redis:6.2
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
database:
|
||||||
|
container_name: immich_postgres
|
||||||
|
image: postgres:14
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
POSTGRES_USER: ${DB_USERNAME}
|
||||||
|
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||||
|
PG_DATA: /var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
immich-proxy:
|
||||||
|
container_name: immich_proxy
|
||||||
|
image: altran1502/immich-proxy:staging
|
||||||
|
ports:
|
||||||
|
- 2283:80
|
||||||
|
- 2284:443
|
||||||
|
logging:
|
||||||
|
driver: none
|
||||||
|
depends_on:
|
||||||
|
- immich-server
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
40
docker/docker-compose.test.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
immich_server_test:
|
||||||
|
image: immich-server-dev:latest
|
||||||
|
build:
|
||||||
|
context: ../server
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
command: npm run test:e2e
|
||||||
|
expose:
|
||||||
|
- "3000"
|
||||||
|
volumes:
|
||||||
|
- ../server:/usr/src/app
|
||||||
|
- /usr/src/app/node_modules
|
||||||
|
env_file:
|
||||||
|
- .env.test
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- database
|
||||||
|
|
||||||
|
redis:
|
||||||
|
container_name: immich_redis_test
|
||||||
|
image: redis:6.2
|
||||||
|
|
||||||
|
database:
|
||||||
|
container_name: immich_postgres_test
|
||||||
|
image: postgres:14
|
||||||
|
env_file:
|
||||||
|
- .env.test
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD}
|
||||||
|
POSTGRES_USER: ${DB_USERNAME}
|
||||||
|
POSTGRES_DB: ${DB_DATABASE_NAME}
|
||||||
|
PG_DATA: /var/lib/postgresql/data
|
||||||
|
volumes:
|
||||||
|
- /var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
version: "3.8"
|
version: "3.8"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
immich_server:
|
immich-server:
|
||||||
image: altran1502/immich-server:v1.8.0_12-dev
|
image: altran1502/immich-server:release
|
||||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
entrypoint: ["/bin/sh", "./start-server.sh"]
|
||||||
expose:
|
|
||||||
- "3000"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
env_file:
|
env_file:
|
||||||
@@ -15,15 +13,25 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- database
|
- database
|
||||||
networks:
|
restart: always
|
||||||
- immich_network
|
|
||||||
restart: unless-stopped
|
|
||||||
|
|
||||||
immich_microservices:
|
immich-microservices:
|
||||||
image: altran1502/immich-microservices:v1.8.0_12-dev
|
image: altran1502/immich-server:release
|
||||||
|
entrypoint: ["/bin/sh", "./start-microservices.sh"]
|
||||||
|
volumes:
|
||||||
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
depends_on:
|
||||||
|
- redis
|
||||||
|
- database
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
immich-machine-learning:
|
||||||
|
image: altran1502/immich-machine-learning:release
|
||||||
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||||
expose:
|
|
||||||
- "3001"
|
|
||||||
volumes:
|
volumes:
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
||||||
env_file:
|
env_file:
|
||||||
@@ -32,15 +40,19 @@ services:
|
|||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
depends_on:
|
depends_on:
|
||||||
- database
|
- database
|
||||||
networks:
|
restart: always
|
||||||
- immich_network
|
|
||||||
restart: unless-stopped
|
immich-web:
|
||||||
|
image: altran1502/immich-web:release
|
||||||
|
entrypoint: ["/bin/sh", "./entrypoint.sh"]
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
restart: always
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2
|
image: redis:6.2
|
||||||
networks:
|
restart: always
|
||||||
- immich_network
|
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
@@ -54,27 +66,18 @@ services:
|
|||||||
PG_DATA: /var/lib/postgresql/data
|
PG_DATA: /var/lib/postgresql/data
|
||||||
volumes:
|
volumes:
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
ports:
|
restart: always
|
||||||
- 5432:5432
|
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
|
|
||||||
nginx:
|
immich-proxy:
|
||||||
container_name: proxy_nginx
|
container_name: immich_proxy
|
||||||
image: nginx:latest
|
image: altran1502/immich-proxy:release
|
||||||
volumes:
|
|
||||||
- ./settings/nginx-conf:/etc/nginx/conf.d
|
|
||||||
ports:
|
ports:
|
||||||
- 2283:80
|
- 2283:80
|
||||||
- 2284:443
|
|
||||||
logging:
|
logging:
|
||||||
driver: none
|
driver: none
|
||||||
networks:
|
|
||||||
- immich_network
|
|
||||||
depends_on:
|
depends_on:
|
||||||
- immich_server
|
- immich-server
|
||||||
|
restart: always
|
||||||
|
|
||||||
networks:
|
|
||||||
immich_network:
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
map $http_upgrade $connection_upgrade {
|
|
||||||
default upgrade;
|
|
||||||
'' close;
|
|
||||||
}
|
|
||||||
|
|
||||||
# events {
|
|
||||||
# worker_connections 1000;
|
|
||||||
# }
|
|
||||||
|
|
||||||
server {
|
|
||||||
|
|
||||||
gzip on;
|
|
||||||
gzip_min_length 1000;
|
|
||||||
gunzip on;
|
|
||||||
|
|
||||||
client_max_body_size 50000M;
|
|
||||||
|
|
||||||
listen 80;
|
|
||||||
access_log off;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
|
|
||||||
# Compression
|
|
||||||
gzip_static on;
|
|
||||||
gzip_min_length 1000;
|
|
||||||
gzip_comp_level 2;
|
|
||||||
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_buffer_size 16k;
|
|
||||||
proxy_busy_buffers_size 24k;
|
|
||||||
proxy_buffers 64 4k;
|
|
||||||
proxy_force_ranges on;
|
|
||||||
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "upgrade";
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
|
|
||||||
proxy_pass http://immich_server:3000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,4 +32,6 @@ lerna-debug.log*
|
|||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
upload/
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM node:16-bullseye-slim
|
FROM node:16-bullseye-slim
|
||||||
|
|
||||||
ARG DEBIAN_FRONTEND=noninteractive
|
ARG DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ COPY package.json package-lock.json ./
|
|||||||
RUN apt-get update
|
RUN apt-get update
|
||||||
RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
|
RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
|
||||||
|
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
@@ -2,7 +2,7 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
export const databaseConfig: TypeOrmModuleOptions = {
|
export const databaseConfig: TypeOrmModuleOptions = {
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
host: 'immich_postgres',
|
host: process.env.DB_HOSTNAME || 'immich_postgres',
|
||||||
port: 5432,
|
port: 5432,
|
||||||
username: process.env.DB_USERNAME,
|
username: process.env.DB_USERNAME,
|
||||||
password: process.env.DB_PASSWORD,
|
password: process.env.DB_PASSWORD,
|
||||||
@@ -5,9 +5,9 @@ import { ImageClassifierService } from './image-classifier.service';
|
|||||||
export class ImageClassifierController {
|
export class ImageClassifierController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly imageClassifierService: ImageClassifierService,
|
private readonly imageClassifierService: ImageClassifierService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
@Post('/tagImage')
|
@Post('/tag-image')
|
||||||
async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
|
async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
|
||||||
return await this.imageClassifierService.tagImage(thumbnailPath);
|
return await this.imageClassifierService.tagImage(thumbnailPath);
|
||||||
}
|
}
|
||||||
@@ -5,17 +5,17 @@ import { Logger } from '@nestjs/common';
|
|||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
await app.listen(3001, () => {
|
await app.listen(3003, () => {
|
||||||
if (process.env.NODE_ENV == 'development') {
|
if (process.env.NODE_ENV == 'development') {
|
||||||
Logger.log(
|
Logger.log(
|
||||||
'Running Immich Microservices in DEVELOPMENT environment',
|
'Running Immich Machine Learning in DEVELOPMENT environment',
|
||||||
'IMMICH MICROSERVICES',
|
'IMMICH MICROSERVICES',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.NODE_ENV == 'production') {
|
if (process.env.NODE_ENV == 'production') {
|
||||||
Logger.log(
|
Logger.log(
|
||||||
'Running Immich Microservices in PRODUCTION environment',
|
'Running Immich Machine Learning in PRODUCTION environment',
|
||||||
'IMMICH MICROSERVICES',
|
'IMMICH MICROSERVICES',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import { Body, Controller, Post } from '@nestjs/common';
|
import { Body, Controller, Post } from '@nestjs/common';
|
||||||
import { ObjectDetectionService } from './object-detection.service';
|
import { ObjectDetectionService } from './object-detection.service';
|
||||||
|
import { Logger } from '@nestjs/common';
|
||||||
|
|
||||||
@Controller('object-detection')
|
@Controller('object-detection')
|
||||||
export class ObjectDetectionController {
|
export class ObjectDetectionController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly objectDetectionService: ObjectDetectionService,
|
private readonly objectDetectionService: ObjectDetectionService,
|
||||||
) {}
|
) { }
|
||||||
|
|
||||||
@Post('/detectObject')
|
@Post('/detect-object')
|
||||||
async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
|
async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
|
||||||
return await this.objectDetectionService.detectObject(thumbnailPath);
|
return await this.objectDetectionService.detectObject(thumbnailPath);
|
||||||
}
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
devenv/
|
|
||||||
3
machine_learning/.gitignore
vendored
@@ -1,3 +0,0 @@
|
|||||||
__pycache__/
|
|
||||||
devenv/
|
|
||||||
app/upload
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
## GPU Build
|
|
||||||
# FROM tensorflow/tensorflow:latest-gpu as gpu
|
|
||||||
|
|
||||||
# WORKDIR /code
|
|
||||||
|
|
||||||
# COPY ./requirements.txt /code/requirements.txt
|
|
||||||
|
|
||||||
# RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
|
||||||
|
|
||||||
# COPY ./app /code/app
|
|
||||||
|
|
||||||
|
|
||||||
## CPU BUILD
|
|
||||||
FROM python:3.8 as cpu
|
|
||||||
|
|
||||||
RUN apt-get update
|
|
||||||
RUN apt-get install ffmpeg libsm6 libxext6 -y
|
|
||||||
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
COPY ./requirements.txt /code/requirements.txt
|
|
||||||
|
|
||||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
|
||||||
|
|
||||||
COPY ./app /code/app
|
|
||||||
|
Before Width: | Height: | Size: 193 KiB |
@@ -1,37 +0,0 @@
|
|||||||
from tensorflow.keras.applications import InceptionV3
|
|
||||||
from tensorflow.keras.applications.inception_v3 import preprocess_input, decode_predictions
|
|
||||||
from tensorflow.keras.preprocessing import image
|
|
||||||
import numpy as np
|
|
||||||
from PIL import Image
|
|
||||||
import cv2
|
|
||||||
IMG_SIZE = 299
|
|
||||||
PREDICTION_MODEL = InceptionV3(weights='imagenet')
|
|
||||||
|
|
||||||
|
|
||||||
def classify_image(image_path: str):
|
|
||||||
img_path = f'./app/{image_path}'
|
|
||||||
# img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))
|
|
||||||
|
|
||||||
target_image = cv2.imread(img_path, cv2.IMREAD_UNCHANGED)
|
|
||||||
resized_target_image = cv2.resize(target_image, (IMG_SIZE, IMG_SIZE))
|
|
||||||
|
|
||||||
x = image.img_to_array(resized_target_image)
|
|
||||||
x = np.expand_dims(x, axis=0)
|
|
||||||
x = preprocess_input(x)
|
|
||||||
|
|
||||||
preds = PREDICTION_MODEL.predict(x)
|
|
||||||
result = decode_predictions(preds, top=3)[0]
|
|
||||||
payload = []
|
|
||||||
for _, value, _ in result:
|
|
||||||
payload.append(value)
|
|
||||||
|
|
||||||
return payload
|
|
||||||
|
|
||||||
|
|
||||||
def warm_up():
|
|
||||||
img_path = f'./app/test.png'
|
|
||||||
img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))
|
|
||||||
x = image.img_to_array(img)
|
|
||||||
x = np.expand_dims(x, axis=0)
|
|
||||||
x = preprocess_input(x)
|
|
||||||
PREDICTION_MODEL.predict(x)
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
from pydantic import BaseModel
|
|
||||||
from fastapi import FastAPI
|
|
||||||
|
|
||||||
from .object_detection import object_detection
|
|
||||||
from .image_classifier import image_classifier
|
|
||||||
|
|
||||||
from tf2_yolov4.anchors import YOLOV4_ANCHORS
|
|
||||||
from tf2_yolov4.model import YOLOv4
|
|
||||||
|
|
||||||
|
|
||||||
HEIGHT, WIDTH = (640, 960)
|
|
||||||
|
|
||||||
# Warm up model
|
|
||||||
image_classifier.warm_up()
|
|
||||||
app = FastAPI()
|
|
||||||
|
|
||||||
|
|
||||||
class TagImagePayload(BaseModel):
|
|
||||||
thumbnail_path: str
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/tagImage")
|
|
||||||
async def post_root(payload: TagImagePayload):
|
|
||||||
image_path = payload.thumbnail_path
|
|
||||||
|
|
||||||
if image_path[0] == '.':
|
|
||||||
image_path = image_path[2:]
|
|
||||||
|
|
||||||
return image_classifier.classify_image(image_path=image_path)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
async def test():
|
|
||||||
|
|
||||||
object_detection.run_detection()
|
|
||||||
# image = tf.io.read_file("./app/cars.jpg")
|
|
||||||
# image = tf.image.decode_image(image)
|
|
||||||
# image = tf.image.resize(image, (HEIGHT, WIDTH))
|
|
||||||
# images = tf.expand_dims(image, axis=0) / 255.0
|
|
||||||
|
|
||||||
# model = YOLOv4(
|
|
||||||
# (HEIGHT, WIDTH, 3),
|
|
||||||
# 80,
|
|
||||||
# YOLOV4_ANCHORS,
|
|
||||||
# "darknet",
|
|
||||||
# )
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
def run_detection():
|
|
||||||
print("run detection")
|
|
||||||
|
Before Width: | Height: | Size: 345 KiB |
@@ -1,8 +0,0 @@
|
|||||||
opencv-python==4.5.5.64
|
|
||||||
fastapi>=0.68.0,<0.69.0
|
|
||||||
pydantic>=1.8.0,<2.0.0
|
|
||||||
uvicorn>=0.15.0,<0.16.0
|
|
||||||
tensorflow==2.8.0
|
|
||||||
numpy==1.22.2
|
|
||||||
pillow==9.0.1
|
|
||||||
tf2_yolov4==0.1.0
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { INestApplication } from '@nestjs/common';
|
|
||||||
import * as request from 'supertest';
|
|
||||||
import { AppModule } from '../src/app.module';
|
|
||||||
|
|
||||||
// End to End test
|
|
||||||
describe('AppController (e2e)', () => {
|
|
||||||
let app: INestApplication;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
|
||||||
imports: [AppModule],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
app = moduleFixture.createNestApplication();
|
|
||||||
await app.init();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('/ (GET)', () => {
|
|
||||||
return request(app.getHttpServer())
|
|
||||||
.get('/')
|
|
||||||
.expect(200)
|
|
||||||
.expect('Hello World!');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,10 +1,45 @@
|
|||||||
# This file tracks properties of this Flutter project.
|
# This file tracks properties of this Flutter project.
|
||||||
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
#
|
#
|
||||||
# This file should be version controlled and should not be manually edited.
|
# This file should be version controlled.
|
||||||
|
|
||||||
version:
|
version:
|
||||||
revision: 77d935af4db863f6abd0b9c31c7e6df2a13de57b
|
revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
channel: stable
|
channel: stable
|
||||||
|
|
||||||
project_type: app
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
- platform: android
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
- platform: ios
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
- platform: linux
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
- platform: macos
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
- platform: web
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
- platform: windows
|
||||||
|
create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ linter:
|
|||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
use_build_context_synchronously: false
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Additional information about this file can be found at
|
||||||
# https://dart.dev/guides/language/analysis-options
|
# https://dart.dev/guides/language/analysis-options
|
||||||
|
|||||||
@@ -81,5 +81,4 @@ flutter {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'com.android.support:multidex:1.0.3'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="app.alextran.immich">
|
||||||
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher">
|
<application android:label="Immich" android:name="${applicationName}" android:usesCleartextTraffic="true" android:icon="@mipmap/ic_launcher" android:requestLegacyExternalStorage="true">
|
||||||
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize">
|
||||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
the Android process has started. This theme is visible to the user
|
the Android process has started. This theme is visible to the user
|
||||||
@@ -23,4 +23,11 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<data android:scheme="https" />
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
// Generated file.
|
|
||||||
// If you wish to remove Flutter's multidex support, delete this entire file.
|
|
||||||
|
|
||||||
package io.flutter.app;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import androidx.annotation.CallSuper;
|
|
||||||
import androidx.multidex.MultiDex;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support.
|
|
||||||
*/
|
|
||||||
public class FlutterMultiDexApplication extends FlutterApplication {
|
|
||||||
@Override
|
|
||||||
@CallSuper
|
|
||||||
protected void attachBaseContext(Context base) {
|
|
||||||
super.attachBaseContext(base);
|
|
||||||
MultiDex.install(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
* Hotfix: Permission is being requested now when open backup screen on Android10
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* User can now upload profile picture from the home page control drawer.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* Update to Material Design 3
|
||||||
|
* Fixed back button navigation - no longer return back to the home page
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Added announcement pop-up when a new released is pushed out in Github.
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* Fixed crash issue when upload large file on slow network
|
||||||
|
* Updated album to conform with server refactoring of SharedAlbum to Album
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* Added zoom functionality to the image viewer
|
||||||
|
* Fixed issue with the user is logged out after turning off the app
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Fixed WebSocket endpoint to confirm with the new settings on the server
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
* Fixed app does not resume back up when reopening a closed app
|
||||||
|
* Fixed wrong asset count on the upload page
|
||||||
|
* Added mechanism to change the password of new user on the first login (except Admin)
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* Fixed admin is forced to change password upon logging in on mobile app
|
||||||
|
* Fixed change password form validation
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Removed thumbnail generation on mobile - the operation now will be on the server to reduce CPU load and battery usage.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Hot fix: Restore shared album functionality
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
* Add information for uploading asset and error indication with error message for each failed upload.
|
||||||
@@ -3,5 +3,5 @@ distributionBase=GRADLE_USER_HOME
|
|||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
||||||
distributionSha256Sum=0080de8491f0918e4f529a6db6820fa0b9e818ee2386117f4394f95feb1d5583
|
distributionSha256Sum=cd5c2958a107ee7f0722004a12d0f8559b4564c34daad7df06cffd4d12a426d0
|
||||||
@@ -9,6 +9,8 @@ PODS:
|
|||||||
- FMDB (2.7.5):
|
- FMDB (2.7.5):
|
||||||
- FMDB/standard (= 2.7.5)
|
- FMDB/standard (= 2.7.5)
|
||||||
- FMDB/standard (2.7.5)
|
- FMDB/standard (2.7.5)
|
||||||
|
- image_picker_ios (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):
|
||||||
@@ -21,6 +23,8 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- FMDB (>= 2.7.5)
|
- FMDB (>= 2.7.5)
|
||||||
- Toast (4.0.0)
|
- Toast (4.0.0)
|
||||||
|
- url_launcher_ios (0.0.1):
|
||||||
|
- Flutter
|
||||||
- video_player_avfoundation (0.0.1):
|
- video_player_avfoundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- wakelock (0.0.1):
|
- wakelock (0.0.1):
|
||||||
@@ -30,10 +34,12 @@ DEPENDENCIES:
|
|||||||
- Flutter (from `Flutter`)
|
- Flutter (from `Flutter`)
|
||||||
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
|
||||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||||
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/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`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||||
|
|
||||||
@@ -50,6 +56,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/flutter_udid/ios"
|
:path: ".symlinks/plugins/flutter_udid/ios"
|
||||||
fluttertoast:
|
fluttertoast:
|
||||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||||
|
image_picker_ios:
|
||||||
|
:path: ".symlinks/plugins/image_picker_ios/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:
|
||||||
@@ -58,6 +66,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/photo_manager/ios"
|
:path: ".symlinks/plugins/photo_manager/ios"
|
||||||
sqflite:
|
sqflite:
|
||||||
:path: ".symlinks/plugins/sqflite/ios"
|
:path: ".symlinks/plugins/sqflite/ios"
|
||||||
|
url_launcher_ios:
|
||||||
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
||||||
wakelock:
|
wakelock:
|
||||||
@@ -66,14 +76,16 @@ EXTERNAL SOURCES:
|
|||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
||||||
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
||||||
fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58
|
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
|
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
|
||||||
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
|
||||||
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
|
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
|
||||||
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
|
||||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||||
|
|
||||||
|
|||||||
@@ -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 = 2;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
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 = 2;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
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 = 2;
|
CURRENT_PROJECT_VERSION = 14;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
<string>1.10.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>2</string>
|
<string>14</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true />
|
<true />
|
||||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||||
@@ -43,6 +43,12 @@
|
|||||||
<key>NSPhotoLibraryAddUsageDescription</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>
|
||||||
|
<string>We need to access the camera to let you take beautiful video using this app</string>
|
||||||
|
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>We need to access the microphone to let you take beautiful video using this app</string>
|
||||||
|
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -52,7 +58,7 @@
|
|||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
@@ -68,5 +74,13 @@
|
|||||||
<true />
|
<true />
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false />
|
<false />
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true />
|
||||||
|
|
||||||
|
|
||||||
|
<key>LSApplicationQueriesSchemes</key>
|
||||||
|
<array>
|
||||||
|
<string>https</string>
|
||||||
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</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.9.0"
|
version_number: "1.17.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
|
|||||||
[bundle exec] fastlane ios beta
|
[bundle exec] fastlane ios beta
|
||||||
```
|
```
|
||||||
|
|
||||||
iOS deployment
|
iOS Beta
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
|||||||
@@ -5,27 +5,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000332">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000946">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: latest_testflight_build_number" time="4.608292">
|
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="16.3225">
|
||||||
|
|
||||||
</testcase>
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: increment_build_number" time="0.747162">
|
|
||||||
|
|
||||||
</testcase>
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: build_app" time="88.727281">
|
|
||||||
|
|
||||||
</testcase>
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: upload_to_testflight" time="7.79397">
|
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
const String userInfoBox = "immichBoxUserInfo"; // Box
|
const String userInfoBox = "immichBoxUserInfo"; // Box
|
||||||
const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
|
const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
|
||||||
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
||||||
|
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
||||||
// Server endpoint
|
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
||||||
const String serverEndpointKey = 'immichBoxServerEndpoint';
|
|
||||||
|
|
||||||
// Login Info
|
// Login Info
|
||||||
const String hiveLoginInfoBox = "immichLoginInfoBox";
|
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
||||||
const String savedLoginInfoKey = "immichSavedLoginInfoKey";
|
const String savedLoginInfoKey = "immichSavedLoginInfoKey"; // Key 1
|
||||||
|
|
||||||
// Backup Info
|
// Backup Info
|
||||||
const String hiveBackupInfoBox = "immichBackupAlbumInfoBox";
|
const String hiveBackupInfoBox = "immichBackupAlbumInfoBox"; // Box
|
||||||
const String backupInfoKey = "immichBackupAlbumInfoKey";
|
const String backupInfoKey = "immichBackupAlbumInfoKey"; // Key 1
|
||||||
|
|
||||||
|
// Github Release Info
|
||||||
|
const String hiveGithubReleaseInfoBox = "immichGithubReleaseInfoBox"; // Box
|
||||||
|
const String githubReleaseInfoKey = "immichGithubReleaseInfoKey"; // Key 1
|
||||||
|
|||||||
@@ -4,15 +4,19 @@ import 'package:hive_flutter/hive_flutter.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||||
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||||
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
||||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||||
|
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||||
|
|
||||||
import 'constants/hive_box.dart';
|
import 'constants/hive_box.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -24,6 +28,7 @@ void main() async {
|
|||||||
await Hive.openBox(userInfoBox);
|
await Hive.openBox(userInfoBox);
|
||||||
await Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox);
|
await Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox);
|
||||||
await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
||||||
|
await Hive.openBox(hiveGithubReleaseInfoBox);
|
||||||
|
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
const SystemUiOverlayStyle(
|
const SystemUiOverlayStyle(
|
||||||
@@ -35,23 +40,32 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ImmichApp extends ConsumerStatefulWidget {
|
class ImmichApp extends ConsumerStatefulWidget {
|
||||||
const ImmichApp({Key? key}) : super(key: key);
|
const ImmichApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ImmichAppState createState() => _ImmichAppState();
|
ImmichAppState createState() => ImmichAppState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserver {
|
class ImmichAppState extends ConsumerState<ImmichApp>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
@override
|
@override
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case AppLifecycleState.resumed:
|
case AppLifecycleState.resumed:
|
||||||
debugPrint("[APP STATE] resumed");
|
debugPrint("[APP STATE] resumed");
|
||||||
ref.watch(appStateProvider.notifier).state = AppStateEnum.resumed;
|
ref.watch(appStateProvider.notifier).state = AppStateEnum.resumed;
|
||||||
ref.watch(backupProvider.notifier).resumeBackup();
|
|
||||||
|
var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;
|
||||||
|
|
||||||
|
if (isAuthenticated) {
|
||||||
|
ref.watch(backupProvider.notifier).resumeBackup();
|
||||||
|
ref.watch(assetProvider.notifier).getAllAsset();
|
||||||
|
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
||||||
|
}
|
||||||
|
|
||||||
ref.watch(websocketProvider.notifier).connect();
|
ref.watch(websocketProvider.notifier).connect();
|
||||||
ref.watch(assetProvider.notifier).getAllAsset();
|
|
||||||
ref.watch(serverInfoProvider.notifier).getServerVersion();
|
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -76,7 +90,7 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
WidgetsBinding.instance?.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -87,7 +101,7 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance?.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +109,8 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
|
||||||
|
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
home: Stack(
|
home: Stack(
|
||||||
@@ -103,10 +119,12 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
|
|||||||
title: 'Immich',
|
title: 'Immich',
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primarySwatch: Colors.indigo,
|
primarySwatch: Colors.indigo,
|
||||||
fontFamily: 'WorkSans',
|
fontFamily: 'WorkSans',
|
||||||
snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
|
snackBarTheme: const SnackBarThemeData(
|
||||||
|
contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
|
||||||
scaffoldBackgroundColor: immichBackgroundColor,
|
scaffoldBackgroundColor: immichBackgroundColor,
|
||||||
appBarTheme: const AppBarTheme(
|
appBarTheme: const AppBarTheme(
|
||||||
backgroundColor: immichBackgroundColor,
|
backgroundColor: immichBackgroundColor,
|
||||||
@@ -117,9 +135,11 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
routeInformationParser: _immichRouter.defaultRouteParser(),
|
routeInformationParser: _immichRouter.defaultRouteParser(),
|
||||||
routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
|
routerDelegate: _immichRouter.delegate(
|
||||||
|
navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
|
||||||
),
|
),
|
||||||
const ImmichLoadingOverlay(),
|
const ImmichLoadingOverlay(),
|
||||||
|
const VersionAnnouncementOverlay(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,22 +28,26 @@ class ImageViewerPageState {
|
|||||||
|
|
||||||
factory ImageViewerPageState.fromMap(Map<String, dynamic> map) {
|
factory ImageViewerPageState.fromMap(Map<String, dynamic> map) {
|
||||||
return ImageViewerPageState(
|
return ImageViewerPageState(
|
||||||
downloadAssetStatus: DownloadAssetStatus.values[map['downloadAssetStatus'] ?? 0],
|
downloadAssetStatus:
|
||||||
|
DownloadAssetStatus.values[map['downloadAssetStatus'] ?? 0],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String toJson() => json.encode(toMap());
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
factory ImageViewerPageState.fromJson(String source) => ImageViewerPageState.fromMap(json.decode(source));
|
factory ImageViewerPageState.fromJson(String source) =>
|
||||||
|
ImageViewerPageState.fromMap(json.decode(source));
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'ImageViewerPageState(downloadAssetStatus: $downloadAssetStatus)';
|
String toString() =>
|
||||||
|
'ImageViewerPageState(downloadAssetStatus: $downloadAssetStatus)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other is ImageViewerPageState && other.downloadAssetStatus == downloadAssetStatus;
|
return other is ImageViewerPageState &&
|
||||||
|
other.downloadAssetStatus == downloadAssetStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
|||||||
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
||||||
final ImageViewerService _imageViewerService = ImageViewerService();
|
final ImageViewerService _imageViewerService = ImageViewerService();
|
||||||
|
|
||||||
ImageViewerStateNotifier() : super(ImageViewerPageState(downloadAssetStatus: DownloadAssetStatus.idle));
|
ImageViewerStateNotifier()
|
||||||
|
: super(ImageViewerPageState(
|
||||||
|
downloadAssetStatus: DownloadAssetStatus.idle));
|
||||||
|
|
||||||
void downloadAsset(ImmichAsset asset, BuildContext context) async {
|
void downloadAsset(ImmichAsset asset, BuildContext context) async {
|
||||||
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.loading);
|
state = state.copyWith(downloadAssetStatus: DownloadAssetStatus.loading);
|
||||||
@@ -40,4 +42,5 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final imageViewerStateProvider =
|
final imageViewerStateProvider =
|
||||||
StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(((ref) => ImageViewerStateNotifier()));
|
StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(
|
||||||
|
((ref) => ImageViewerStateNotifier()));
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ class ImageViewerService {
|
|||||||
try {
|
try {
|
||||||
String fileName = p.basename(asset.originalPath);
|
String fileName = p.basename(asset.originalPath);
|
||||||
var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
var savedEndpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
||||||
Uri filePath =
|
Uri filePath = Uri.parse(
|
||||||
Uri.parse("$savedEndpoint/asset/download?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false");
|
"$savedEndpoint/asset/download?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false");
|
||||||
|
|
||||||
var res = await http.get(
|
var res = await http.get(
|
||||||
filePath,
|
filePath,
|
||||||
headers: {"Authorization": "Bearer ${Hive.box(userInfoBox).get(accessTokenKey)}"},
|
headers: {
|
||||||
|
"Authorization": "Bearer ${Hive.box(userInfoBox).get(accessTokenKey)}"
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final AssetEntity? entity;
|
final AssetEntity? entity;
|
||||||
@@ -37,14 +39,10 @@ class ImageViewerService {
|
|||||||
entity = await PhotoManager.editor.saveVideo(tempFile, title: fileName);
|
entity = await PhotoManager.editor.saveVideo(tempFile, title: fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity != null) {
|
return entity != null;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error saving file $e");
|
debugPrint("Error saving file $e");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,77 +9,78 @@ import 'package:latlong2/latlong.dart';
|
|||||||
class ExifBottomSheet extends ConsumerWidget {
|
class ExifBottomSheet extends ConsumerWidget {
|
||||||
final ImmichAssetWithExif assetDetail;
|
final ImmichAssetWithExif assetDetail;
|
||||||
|
|
||||||
const ExifBottomSheet({Key? key, required this.assetDetail}) : super(key: key);
|
const ExifBottomSheet({Key? key, required this.assetDetail})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
_buildMap() {
|
_buildMap() {
|
||||||
return (assetDetail.exifInfo!.latitude != null && assetDetail.exifInfo!.longitude != null)
|
return Padding(
|
||||||
? Padding(
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
child: Container(
|
||||||
child: Container(
|
height: 150,
|
||||||
height: 150,
|
width: MediaQuery.of(context).size.width,
|
||||||
width: MediaQuery.of(context).size.width,
|
decoration: const BoxDecoration(
|
||||||
decoration: const BoxDecoration(
|
borderRadius: BorderRadius.all(Radius.circular(15)),
|
||||||
borderRadius: BorderRadius.all(Radius.circular(15)),
|
),
|
||||||
),
|
child: FlutterMap(
|
||||||
child: FlutterMap(
|
options: MapOptions(
|
||||||
options: MapOptions(
|
center: LatLng(assetDetail.exifInfo!.latitude!,
|
||||||
center: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!),
|
assetDetail.exifInfo!.longitude!),
|
||||||
zoom: 16.0,
|
zoom: 16.0,
|
||||||
),
|
),
|
||||||
layers: [
|
layers: [
|
||||||
TileLayerOptions(
|
TileLayerOptions(
|
||||||
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
urlTemplate:
|
||||||
subdomains: ['a', 'b', 'c'],
|
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||||
attributionBuilder: (_) {
|
subdomains: ['a', 'b', 'c'],
|
||||||
return const Text(
|
attributionBuilder: (_) {
|
||||||
"© OpenStreetMap",
|
return const Text(
|
||||||
style: TextStyle(fontSize: 10),
|
"© OpenStreetMap",
|
||||||
);
|
style: TextStyle(fontSize: 10),
|
||||||
},
|
);
|
||||||
),
|
},
|
||||||
MarkerLayerOptions(
|
|
||||||
markers: [
|
|
||||||
Marker(
|
|
||||||
anchorPos: AnchorPos.align(AnchorAlign.top),
|
|
||||||
point: LatLng(assetDetail.exifInfo!.latitude!, assetDetail.exifInfo!.longitude!),
|
|
||||||
builder: (ctx) => const Image(image: AssetImage('assets/location-pin.png')),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
MarkerLayerOptions(
|
||||||
: Container();
|
markers: [
|
||||||
|
Marker(
|
||||||
|
anchorPos: AnchorPos.align(AnchorAlign.top),
|
||||||
|
point: LatLng(assetDetail.exifInfo!.latitude!,
|
||||||
|
assetDetail.exifInfo!.longitude!),
|
||||||
|
builder: (ctx) => const Image(
|
||||||
|
image: AssetImage('assets/location-pin.png')),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_buildLocationText() {
|
_buildLocationText() {
|
||||||
return (assetDetail.exifInfo!.city != null && assetDetail.exifInfo!.state != null)
|
return Text(
|
||||||
? Text(
|
"${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}",
|
||||||
"${assetDetail.exifInfo!.city}, ${assetDetail.exifInfo!.state}",
|
style: TextStyle(
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold),
|
fontSize: 12, color: Colors.grey[200], fontWeight: FontWeight.bold),
|
||||||
)
|
);
|
||||||
: Container();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8),
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
assetDetail.exifInfo?.dateTimeOriginal != null
|
if (assetDetail.exifInfo?.dateTimeOriginal != null)
|
||||||
? Text(
|
Text(
|
||||||
DateFormat('E, LLL d, y • h:mm a').format(
|
DateFormat('E, LLL d, y • h:mm a').format(
|
||||||
DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
|
DateTime.parse(assetDetail.exifInfo!.dateTimeOriginal!),
|
||||||
),
|
),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.grey[400],
|
color: Colors.grey[400],
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
: Container(),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 16.0),
|
padding: const EdgeInsets.only(top: 16.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
@@ -92,82 +93,83 @@ class ExifBottomSheet extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
assetDetail.exifInfo?.latitude != null
|
if (assetDetail.exifInfo?.latitude != null)
|
||||||
? Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 32.0),
|
padding: const EdgeInsets.only(top: 32.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Divider(
|
Divider(
|
||||||
thickness: 1,
|
thickness: 1,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
),
|
|
||||||
Text(
|
|
||||||
"LOCATION",
|
|
||||||
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
|
|
||||||
),
|
|
||||||
_buildMap(),
|
|
||||||
_buildLocationText(),
|
|
||||||
Text(
|
|
||||||
"${assetDetail.exifInfo!.latitude!.toStringAsFixed(4)}, ${assetDetail.exifInfo!.longitude!.toStringAsFixed(4)}",
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
Text(
|
||||||
: Container(),
|
"LOCATION",
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
|
||||||
|
),
|
||||||
|
if (assetDetail.exifInfo?.latitude != null &&
|
||||||
|
assetDetail.exifInfo?.longitude != null)
|
||||||
|
_buildMap(),
|
||||||
|
if (assetDetail.exifInfo?.city != null &&
|
||||||
|
assetDetail.exifInfo?.state != null)
|
||||||
|
_buildLocationText(),
|
||||||
|
Text(
|
||||||
|
"${assetDetail.exifInfo?.latitude?.toStringAsFixed(4)}, ${assetDetail.exifInfo?.longitude?.toStringAsFixed(4)}",
|
||||||
|
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
// Detail
|
// Detail
|
||||||
assetDetail.exifInfo != null
|
if (assetDetail.exifInfo != null)
|
||||||
? Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 32.0),
|
padding: const EdgeInsets.only(top: 32.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Divider(
|
Divider(
|
||||||
thickness: 1,
|
thickness: 1,
|
||||||
color: Colors.grey[600],
|
color: Colors.grey[600],
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 8.0),
|
|
||||||
child: Text(
|
|
||||||
"DETAILS",
|
|
||||||
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
textColor: Colors.grey[300],
|
|
||||||
iconColor: Colors.grey[300],
|
|
||||||
leading: const Icon(Icons.image),
|
|
||||||
title: Text(
|
|
||||||
"${assetDetail.exifInfo?.imageName!}${p.extension(assetDetail.originalPath)}",
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
subtitle: assetDetail.exifInfo?.exifImageHeight != null
|
|
||||||
? Text(
|
|
||||||
"${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth} ${assetDetail.exifInfo?.fileSizeInByte!}B ")
|
|
||||||
: Container(),
|
|
||||||
),
|
|
||||||
assetDetail.exifInfo?.make != null
|
|
||||||
? ListTile(
|
|
||||||
contentPadding: const EdgeInsets.all(0),
|
|
||||||
dense: true,
|
|
||||||
textColor: Colors.grey[300],
|
|
||||||
iconColor: Colors.grey[300],
|
|
||||||
leading: const Icon(Icons.camera),
|
|
||||||
title: Text(
|
|
||||||
"${assetDetail.exifInfo?.make} ${assetDetail.exifInfo?.model}",
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
"ƒ/${assetDetail.exifInfo?.fNumber} 1/${(1 / assetDetail.exifInfo!.exposureTime!).toStringAsFixed(0)} ${assetDetail.exifInfo?.focalLength}mm ISO${assetDetail.exifInfo?.iso} "),
|
|
||||||
)
|
|
||||||
: Container()
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
)
|
Padding(
|
||||||
: Container()
|
padding: const EdgeInsets.only(bottom: 8.0),
|
||||||
|
child: Text(
|
||||||
|
"DETAILS",
|
||||||
|
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
textColor: Colors.grey[300],
|
||||||
|
iconColor: Colors.grey[300],
|
||||||
|
leading: const Icon(Icons.image),
|
||||||
|
title: Text(
|
||||||
|
"${assetDetail.exifInfo?.imageName!}${p.extension(assetDetail.originalPath)}",
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
subtitle: assetDetail.exifInfo?.exifImageHeight != null
|
||||||
|
? Text(
|
||||||
|
"${assetDetail.exifInfo?.exifImageHeight} x ${assetDetail.exifInfo?.exifImageWidth} ${assetDetail.exifInfo?.fileSizeInByte!}B ")
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
if (assetDetail.exifInfo?.make != null)
|
||||||
|
ListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
textColor: Colors.grey[300],
|
||||||
|
iconColor: Colors.grey[300],
|
||||||
|
leading: const Icon(Icons.camera),
|
||||||
|
title: Text(
|
||||||
|
"${assetDetail.exifInfo?.make} ${assetDetail.exifInfo?.model}",
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
"ƒ/${assetDetail.exifInfo?.fNumber} 1/${(1 / (assetDetail.exifInfo?.exposureTime ?? 1)).toStringAsFixed(0)} ${assetDetail.exifInfo?.focalLength}mm ISO${assetDetail.exifInfo?.iso} "),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
113
mobile/lib/modules/asset_viewer/ui/remote_photo_view.dart
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
|
||||||
|
enum _RemoteImageStatus { empty, thumbnail, full }
|
||||||
|
|
||||||
|
class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||||
|
late CachedNetworkImageProvider _imageProvider;
|
||||||
|
_RemoteImageStatus _status = _RemoteImageStatus.empty;
|
||||||
|
bool _zoomedIn = false;
|
||||||
|
|
||||||
|
static const int swipeThreshold = 100;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
bool allowMoving = _status == _RemoteImageStatus.full;
|
||||||
|
|
||||||
|
return PhotoView(
|
||||||
|
imageProvider: _imageProvider,
|
||||||
|
minScale: PhotoViewComputedScale.contained,
|
||||||
|
maxScale: allowMoving ? 1.0 : PhotoViewComputedScale.contained,
|
||||||
|
enablePanAlways: true,
|
||||||
|
scaleStateChangedCallback: _scaleStateChanged,
|
||||||
|
onScaleEnd: _onScaleListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onScaleListener(BuildContext context, ScaleEndDetails details,
|
||||||
|
PhotoViewControllerValue controllerValue) {
|
||||||
|
// Disable swipe events when zoomed in
|
||||||
|
if (_zoomedIn) return;
|
||||||
|
|
||||||
|
if (controllerValue.position.dy > swipeThreshold) {
|
||||||
|
widget.onSwipeDown();
|
||||||
|
} else if (controllerValue.position.dy < -swipeThreshold) {
|
||||||
|
widget.onSwipeUp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _scaleStateChanged(PhotoViewScaleState state) {
|
||||||
|
_zoomedIn = state == PhotoViewScaleState.zoomedIn;
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedNetworkImageProvider _authorizedImageProvider(String url) {
|
||||||
|
return CachedNetworkImageProvider(url,
|
||||||
|
headers: {"Authorization": widget.authToken}, cacheKey: url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _performStateTransition(
|
||||||
|
_RemoteImageStatus newStatus, CachedNetworkImageProvider provider) {
|
||||||
|
// Transition to same status is forbidden
|
||||||
|
if (_status == newStatus) return;
|
||||||
|
// Transition full -> thumbnail is forbidden
|
||||||
|
if (_status == _RemoteImageStatus.full &&
|
||||||
|
newStatus == _RemoteImageStatus.thumbnail) return;
|
||||||
|
|
||||||
|
if (!mounted) return;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_status = newStatus;
|
||||||
|
_imageProvider = provider;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadImages() {
|
||||||
|
CachedNetworkImageProvider thumbnailProvider =
|
||||||
|
_authorizedImageProvider(widget.thumbnailUrl);
|
||||||
|
_imageProvider = thumbnailProvider;
|
||||||
|
|
||||||
|
thumbnailProvider
|
||||||
|
.resolve(const ImageConfiguration())
|
||||||
|
.addListener(ImageStreamListener((ImageInfo imageInfo, _) {
|
||||||
|
_performStateTransition(_RemoteImageStatus.thumbnail, thumbnailProvider);
|
||||||
|
}));
|
||||||
|
|
||||||
|
CachedNetworkImageProvider fullProvider =
|
||||||
|
_authorizedImageProvider(widget.imageUrl);
|
||||||
|
fullProvider
|
||||||
|
.resolve(const ImageConfiguration())
|
||||||
|
.addListener(ImageStreamListener((ImageInfo imageInfo, _) {
|
||||||
|
_performStateTransition(_RemoteImageStatus.full, fullProvider);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_loadImages();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RemotePhotoView extends StatefulWidget {
|
||||||
|
const RemotePhotoView(
|
||||||
|
{Key? key,
|
||||||
|
required this.thumbnailUrl,
|
||||||
|
required this.imageUrl,
|
||||||
|
required this.authToken,
|
||||||
|
required this.onSwipeDown,
|
||||||
|
required this.onSwipeUp})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final String thumbnailUrl;
|
||||||
|
final String imageUrl;
|
||||||
|
final String authToken;
|
||||||
|
|
||||||
|
final void Function() onSwipeDown;
|
||||||
|
final void Function() onSwipeUp;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() {
|
||||||
|
return _RemotePhotoViewState();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -5,7 +7,10 @@ import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
|||||||
|
|
||||||
class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
||||||
const TopControlAppBar(
|
const TopControlAppBar(
|
||||||
{Key? key, required this.asset, required this.onMoreInfoPressed, required this.onDownloadPressed})
|
{Key? key,
|
||||||
|
required this.asset,
|
||||||
|
required this.onMoreInfoPressed,
|
||||||
|
required this.onDownloadPressed})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
|
||||||
final ImmichAsset asset;
|
final ImmichAsset asset;
|
||||||
@@ -42,9 +47,11 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
splashRadius: iconSize,
|
splashRadius: iconSize,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print("favorite");
|
log("favorite");
|
||||||
},
|
},
|
||||||
icon: asset.isFavorite ? const Icon(Icons.favorite_rounded) : const Icon(Icons.favorite_border_rounded),
|
icon: asset.isFavorite
|
||||||
|
? const Icon(Icons.favorite_rounded)
|
||||||
|
: const Icon(Icons.favorite_border_rounded),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.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:flutter_swipe_detector/flutter_swipe_detector.dart';
|
|
||||||
import 'package:hive/hive.dart';
|
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';
|
||||||
@@ -10,6 +8,7 @@ import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_stat
|
|||||||
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/download_loading_indicator.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/download_loading_indicator.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/remote_photo_view.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';
|
||||||
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
||||||
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
import 'package:immich_mobile/shared/models/immich_asset.model.dart';
|
||||||
@@ -21,20 +20,26 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
final String heroTag;
|
final String heroTag;
|
||||||
final String thumbnailUrl;
|
final String thumbnailUrl;
|
||||||
final ImmichAsset asset;
|
final ImmichAsset asset;
|
||||||
final AssetService _assetService = AssetService();
|
|
||||||
ImmichAssetWithExif? assetDetail;
|
ImmichAssetWithExif? assetDetail;
|
||||||
|
|
||||||
ImageViewerPage(
|
ImageViewerPage({
|
||||||
{Key? key, required this.imageUrl, required this.heroTag, required this.thumbnailUrl, required this.asset})
|
Key? key,
|
||||||
: super(key: key);
|
required this.imageUrl,
|
||||||
|
required this.heroTag,
|
||||||
|
required this.thumbnailUrl,
|
||||||
|
required this.asset,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final downloadAssetStatus = ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
final downloadAssetStatus =
|
||||||
|
ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
||||||
var box = Hive.box(userInfoBox);
|
var box = Hive.box(userInfoBox);
|
||||||
|
|
||||||
getAssetExif() async {
|
getAssetExif() async {
|
||||||
assetDetail = await _assetService.getAssetById(asset.id);
|
assetDetail =
|
||||||
|
await ref.watch(assetServiceProvider).getAssetById(asset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
showInfo() {
|
showInfo() {
|
||||||
@@ -60,75 +65,30 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
asset: asset,
|
asset: asset,
|
||||||
onMoreInfoPressed: showInfo,
|
onMoreInfoPressed: showInfo,
|
||||||
onDownloadPressed: () {
|
onDownloadPressed: () {
|
||||||
ref.watch(imageViewerStateProvider.notifier).downloadAsset(asset, context);
|
ref
|
||||||
|
.watch(imageViewerStateProvider.notifier)
|
||||||
|
.downloadAsset(asset, context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: SwipeDetector(
|
body: SafeArea(
|
||||||
onSwipeDown: (_) {
|
child: Stack(
|
||||||
AutoRouter.of(context).pop();
|
children: [
|
||||||
},
|
Center(
|
||||||
onSwipeUp: (_) {
|
child: Hero(
|
||||||
showInfo();
|
|
||||||
},
|
|
||||||
child: SafeArea(
|
|
||||||
child: Stack(
|
|
||||||
children: [
|
|
||||||
Center(
|
|
||||||
child: Hero(
|
|
||||||
tag: heroTag,
|
tag: heroTag,
|
||||||
child: CachedNetworkImage(
|
child: RemotePhotoView(
|
||||||
fit: BoxFit.cover,
|
thumbnailUrl: thumbnailUrl,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
authToken: "Bearer ${box.get(accessTokenKey)}",
|
||||||
fadeInDuration: const Duration(milliseconds: 250),
|
onSwipeDown: () => AutoRouter.of(context).pop(),
|
||||||
errorWidget: (context, url, error) => ConstrainedBox(
|
onSwipeUp: () => showInfo(),
|
||||||
constraints: const BoxConstraints(maxWidth: 300),
|
)),
|
||||||
child: Wrap(
|
),
|
||||||
spacing: 32,
|
if (downloadAssetStatus == DownloadAssetStatus.loading)
|
||||||
runSpacing: 32,
|
const Center(
|
||||||
alignment: WrapAlignment.center,
|
child: DownloadLoadingIndicator(),
|
||||||
children: [
|
|
||||||
const Text(
|
|
||||||
"Failed To Render Image - Possibly Corrupted Data",
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 16, color: Colors.white),
|
|
||||||
),
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Text(
|
|
||||||
error.toString(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
placeholder: (context, url) {
|
|
||||||
return CachedNetworkImage(
|
|
||||||
cacheKey: thumbnailUrl,
|
|
||||||
fit: BoxFit.cover,
|
|
||||||
imageUrl: thumbnailUrl,
|
|
||||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
|
||||||
placeholderFadeInDuration: const Duration(milliseconds: 0),
|
|
||||||
progressIndicatorBuilder: (context, url, downloadProgress) => Transform.scale(
|
|
||||||
scale: 0.2,
|
|
||||||
child: CircularProgressIndicator(value: downloadProgress.progress),
|
|
||||||
),
|
|
||||||
errorWidget: (context, url, error) => Icon(
|
|
||||||
Icons.error,
|
|
||||||
color: Colors.grey[300],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (downloadAssetStatus == DownloadAssetStatus.loading)
|
],
|
||||||
const Center(
|
|
||||||
child: DownloadLoadingIndicator(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -21,13 +21,14 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
final String videoUrl;
|
final String videoUrl;
|
||||||
final ImmichAsset asset;
|
final ImmichAsset asset;
|
||||||
ImmichAssetWithExif? assetDetail;
|
ImmichAssetWithExif? assetDetail;
|
||||||
final AssetService _assetService = AssetService();
|
|
||||||
|
|
||||||
VideoViewerPage({Key? key, required this.videoUrl, required this.asset}) : super(key: key);
|
VideoViewerPage({Key? key, required this.videoUrl, required this.asset})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final downloadAssetStatus = ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
final downloadAssetStatus =
|
||||||
|
ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
||||||
|
|
||||||
String jwtToken = Hive.box(userInfoBox).get(accessTokenKey);
|
String jwtToken = Hive.box(userInfoBox).get(accessTokenKey);
|
||||||
|
|
||||||
@@ -44,7 +45,8 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getAssetExif() async {
|
getAssetExif() async {
|
||||||
assetDetail = await _assetService.getAssetById(asset.id);
|
assetDetail =
|
||||||
|
await ref.watch(assetServiceProvider).getAssetById(asset.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() {
|
useEffect(() {
|
||||||
@@ -60,7 +62,9 @@ class VideoViewerPage extends HookConsumerWidget {
|
|||||||
showInfo();
|
showInfo();
|
||||||
},
|
},
|
||||||
onDownloadPressed: () {
|
onDownloadPressed: () {
|
||||||
ref.watch(imageViewerStateProvider.notifier).downloadAsset(asset, context);
|
ref
|
||||||
|
.watch(imageViewerStateProvider.notifier)
|
||||||
|
.downloadAsset(asset, context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
body: SwipeDetector(
|
body: SwipeDetector(
|
||||||
@@ -93,7 +97,8 @@ class VideoThumbnailPlayer extends StatefulWidget {
|
|||||||
final String url;
|
final String url;
|
||||||
final String? jwtToken;
|
final String? jwtToken;
|
||||||
|
|
||||||
const VideoThumbnailPlayer({Key? key, required this.url, this.jwtToken}) : super(key: key);
|
const VideoThumbnailPlayer({Key? key, required this.url, this.jwtToken})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<VideoThumbnailPlayer> createState() => _VideoThumbnailPlayerState();
|
State<VideoThumbnailPlayer> createState() => _VideoThumbnailPlayerState();
|
||||||
@@ -111,8 +116,8 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
|
|||||||
|
|
||||||
Future<void> initializePlayer() async {
|
Future<void> initializePlayer() async {
|
||||||
try {
|
try {
|
||||||
videoPlayerController =
|
videoPlayerController = VideoPlayerController.network(widget.url,
|
||||||
VideoPlayerController.network(widget.url, httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"});
|
httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"});
|
||||||
|
|
||||||
await videoPlayerController.initialize();
|
await videoPlayerController.initialize();
|
||||||
_createChewieController();
|
_createChewieController();
|
||||||
@@ -142,7 +147,7 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return chewieController != null && chewieController!.videoPlayerController.value.isInitialized
|
return chewieController?.videoPlayerController.value.isInitialized == true
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
child: Chewie(
|
child: Chewie(
|
||||||
controller: chewieController!,
|
controller: chewieController!,
|
||||||
|
|||||||
@@ -21,13 +21,16 @@ class AvailableAlbum {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AvailableAlbum(albumEntity: $albumEntity, thumbnailData: $thumbnailData)';
|
String toString() =>
|
||||||
|
'AvailableAlbum(albumEntity: $albumEntity, thumbnailData: $thumbnailData)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other is AvailableAlbum && other.albumEntity == albumEntity && other.thumbnailData == thumbnailData;
|
return other is AvailableAlbum &&
|
||||||
|
other.albumEntity == albumEntity &&
|
||||||
|
other.thumbnailData == thumbnailData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import 'package:dio/dio.dart';
|
import 'package:cancellation_token_http/http.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||||
|
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
|
||||||
import 'package:immich_mobile/shared/models/server_info.model.dart';
|
import 'package:immich_mobile/shared/models/server_info.model.dart';
|
||||||
|
|
||||||
enum BackUpProgressEnum { idle, inProgress, done }
|
enum BackUpProgressEnum { idle, inProgress, done }
|
||||||
|
|
||||||
class BackUpState extends Equatable {
|
class BackUpState {
|
||||||
// enum
|
// enum
|
||||||
final BackUpProgressEnum backupProgress;
|
final BackUpProgressEnum backupProgress;
|
||||||
final List<String> allAssetOnDatabase;
|
final List<String> allAssetsInDatabase;
|
||||||
final double progressInPercentage;
|
final double progressInPercentage;
|
||||||
final CancelToken cancelToken;
|
final CancellationToken cancelToken;
|
||||||
final ServerInfo serverInfo;
|
final ServerInfo serverInfo;
|
||||||
|
|
||||||
/// All available albums on the device
|
/// All available albums on the device
|
||||||
@@ -26,9 +27,12 @@ class BackUpState extends Equatable {
|
|||||||
/// All assets from the selected albums that have been backup
|
/// All assets from the selected albums that have been backup
|
||||||
final Set<String> selectedAlbumsBackupAssetsIds;
|
final Set<String> selectedAlbumsBackupAssetsIds;
|
||||||
|
|
||||||
|
// Current Backup Asset
|
||||||
|
final CurrentUploadAsset currentUploadAsset;
|
||||||
|
|
||||||
const BackUpState({
|
const BackUpState({
|
||||||
required this.backupProgress,
|
required this.backupProgress,
|
||||||
required this.allAssetOnDatabase,
|
required this.allAssetsInDatabase,
|
||||||
required this.progressInPercentage,
|
required this.progressInPercentage,
|
||||||
required this.cancelToken,
|
required this.cancelToken,
|
||||||
required this.serverInfo,
|
required this.serverInfo,
|
||||||
@@ -37,23 +41,25 @@ class BackUpState extends Equatable {
|
|||||||
required this.excludedBackupAlbums,
|
required this.excludedBackupAlbums,
|
||||||
required this.allUniqueAssets,
|
required this.allUniqueAssets,
|
||||||
required this.selectedAlbumsBackupAssetsIds,
|
required this.selectedAlbumsBackupAssetsIds,
|
||||||
|
required this.currentUploadAsset,
|
||||||
});
|
});
|
||||||
|
|
||||||
BackUpState copyWith({
|
BackUpState copyWith({
|
||||||
BackUpProgressEnum? backupProgress,
|
BackUpProgressEnum? backupProgress,
|
||||||
List<String>? allAssetOnDatabase,
|
List<String>? allAssetsInDatabase,
|
||||||
double? progressInPercentage,
|
double? progressInPercentage,
|
||||||
CancelToken? cancelToken,
|
CancellationToken? cancelToken,
|
||||||
ServerInfo? serverInfo,
|
ServerInfo? serverInfo,
|
||||||
List<AvailableAlbum>? availableAlbums,
|
List<AvailableAlbum>? availableAlbums,
|
||||||
Set<AssetPathEntity>? selectedBackupAlbums,
|
Set<AssetPathEntity>? selectedBackupAlbums,
|
||||||
Set<AssetPathEntity>? excludedBackupAlbums,
|
Set<AssetPathEntity>? excludedBackupAlbums,
|
||||||
Set<AssetEntity>? allUniqueAssets,
|
Set<AssetEntity>? allUniqueAssets,
|
||||||
Set<String>? selectedAlbumsBackupAssetsIds,
|
Set<String>? selectedAlbumsBackupAssetsIds,
|
||||||
|
CurrentUploadAsset? currentUploadAsset,
|
||||||
}) {
|
}) {
|
||||||
return BackUpState(
|
return BackUpState(
|
||||||
backupProgress: backupProgress ?? this.backupProgress,
|
backupProgress: backupProgress ?? this.backupProgress,
|
||||||
allAssetOnDatabase: allAssetOnDatabase ?? this.allAssetOnDatabase,
|
allAssetsInDatabase: allAssetsInDatabase ?? this.allAssetsInDatabase,
|
||||||
progressInPercentage: progressInPercentage ?? this.progressInPercentage,
|
progressInPercentage: progressInPercentage ?? this.progressInPercentage,
|
||||||
cancelToken: cancelToken ?? this.cancelToken,
|
cancelToken: cancelToken ?? this.cancelToken,
|
||||||
serverInfo: serverInfo ?? this.serverInfo,
|
serverInfo: serverInfo ?? this.serverInfo,
|
||||||
@@ -61,28 +67,49 @@ class BackUpState extends Equatable {
|
|||||||
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
||||||
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
||||||
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
|
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
|
||||||
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
|
selectedAlbumsBackupAssetsIds:
|
||||||
|
selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
|
||||||
|
currentUploadAsset: currentUploadAsset ?? this.currentUploadAsset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'BackUpState(backupProgress: $backupProgress, allAssetOnDatabase: $allAssetOnDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds)';
|
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props {
|
bool operator ==(Object other) {
|
||||||
return [
|
if (identical(this, other)) return true;
|
||||||
backupProgress,
|
final collectionEquals = const DeepCollectionEquality().equals;
|
||||||
allAssetOnDatabase,
|
|
||||||
progressInPercentage,
|
return other is BackUpState &&
|
||||||
cancelToken,
|
other.backupProgress == backupProgress &&
|
||||||
serverInfo,
|
collectionEquals(other.allAssetsInDatabase, allAssetsInDatabase) &&
|
||||||
availableAlbums,
|
other.progressInPercentage == progressInPercentage &&
|
||||||
selectedBackupAlbums,
|
other.cancelToken == cancelToken &&
|
||||||
excludedBackupAlbums,
|
other.serverInfo == serverInfo &&
|
||||||
allUniqueAssets,
|
collectionEquals(other.availableAlbums, availableAlbums) &&
|
||||||
selectedAlbumsBackupAssetsIds,
|
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
||||||
];
|
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
||||||
|
collectionEquals(other.allUniqueAssets, allUniqueAssets) &&
|
||||||
|
collectionEquals(other.selectedAlbumsBackupAssetsIds,
|
||||||
|
selectedAlbumsBackupAssetsIds) &&
|
||||||
|
other.currentUploadAsset == currentUploadAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return backupProgress.hashCode ^
|
||||||
|
allAssetsInDatabase.hashCode ^
|
||||||
|
progressInPercentage.hashCode ^
|
||||||
|
cancelToken.hashCode ^
|
||||||
|
serverInfo.hashCode ^
|
||||||
|
availableAlbums.hashCode ^
|
||||||
|
selectedBackupAlbums.hashCode ^
|
||||||
|
excludedBackupAlbums.hashCode ^
|
||||||
|
allUniqueAssets.hashCode ^
|
||||||
|
selectedAlbumsBackupAssetsIds.hashCode ^
|
||||||
|
currentUploadAsset.hashCode;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class CheckDuplicateAssetResponse {
|
||||||
|
final bool isExist;
|
||||||
|
CheckDuplicateAssetResponse({
|
||||||
|
required this.isExist,
|
||||||
|
});
|
||||||
|
|
||||||
|
CheckDuplicateAssetResponse copyWith({
|
||||||
|
bool? isExist,
|
||||||
|
}) {
|
||||||
|
return CheckDuplicateAssetResponse(
|
||||||
|
isExist: isExist ?? this.isExist,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
|
result.addAll({'isExist': isExist});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CheckDuplicateAssetResponse.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CheckDuplicateAssetResponse(
|
||||||
|
isExist: map['isExist'] ?? false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory CheckDuplicateAssetResponse.fromJson(String source) =>
|
||||||
|
CheckDuplicateAssetResponse.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'CheckDuplicateAssetResponse(isExist: $isExist)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other is CheckDuplicateAssetResponse && other.isExist == isExist;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => isExist.hashCode;
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
class CurrentUploadAsset {
|
||||||
|
final String id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final String fileName;
|
||||||
|
final String fileType;
|
||||||
|
|
||||||
|
CurrentUploadAsset({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.fileName,
|
||||||
|
required this.fileType,
|
||||||
|
});
|
||||||
|
|
||||||
|
CurrentUploadAsset copyWith({
|
||||||
|
String? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
String? fileName,
|
||||||
|
String? fileType,
|
||||||
|
}) {
|
||||||
|
return CurrentUploadAsset(
|
||||||
|
id: id ?? this.id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
fileName: fileName ?? this.fileName,
|
||||||
|
fileType: fileType ?? this.fileType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
final result = <String, dynamic>{};
|
||||||
|
|
||||||
|
result.addAll({'id': id});
|
||||||
|
result.addAll({'createdAt': createdAt.millisecondsSinceEpoch});
|
||||||
|
result.addAll({'fileName': fileName});
|
||||||
|
result.addAll({'fileType': fileType});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CurrentUploadAsset.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CurrentUploadAsset(
|
||||||
|
id: map['id'] ?? '',
|
||||||
|
createdAt: DateTime.fromMillisecondsSinceEpoch(map['createdAt']),
|
||||||
|
fileName: map['fileName'] ?? '',
|
||||||
|
fileType: map['fileType'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson() => json.encode(toMap());
|
||||||
|
|
||||||
|
factory CurrentUploadAsset.fromJson(String source) =>
|
||||||
|
CurrentUploadAsset.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CurrentUploadAsset(id: $id, createdAt: $createdAt, fileName: $fileName, fileType: $fileType)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
|
return other is CurrentUploadAsset &&
|
||||||
|
other.id == id &&
|
||||||
|
other.createdAt == createdAt &&
|
||||||
|
other.fileName == fileName &&
|
||||||
|
other.fileType == fileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
return id.hashCode ^
|
||||||
|
createdAt.hashCode ^
|
||||||
|
fileName.hashCode ^
|
||||||
|
fileType.hashCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
|
class ErrorUploadAsset extends Equatable {
|
||||||
|
final String id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final String fileName;
|
||||||
|
final String fileType;
|
||||||
|
final AssetEntity asset;
|
||||||
|
final String errorMessage;
|
||||||
|
|
||||||
|
const ErrorUploadAsset({
|
||||||
|
required this.id,
|
||||||
|
required this.createdAt,
|
||||||
|
required this.fileName,
|
||||||
|
required this.fileType,
|
||||||
|
required this.asset,
|
||||||
|
required this.errorMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
ErrorUploadAsset copyWith({
|
||||||
|
String? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
String? fileName,
|
||||||
|
String? fileType,
|
||||||
|
AssetEntity? asset,
|
||||||
|
String? errorMessage,
|
||||||
|
}) {
|
||||||
|
return ErrorUploadAsset(
|
||||||
|
id: id ?? this.id,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
fileName: fileName ?? this.fileName,
|
||||||
|
fileType: fileType ?? this.fileType,
|
||||||
|
asset: asset ?? this.asset,
|
||||||
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'ErrorUploadAsset(id: $id, createdAt: $createdAt, fileName: $fileName, fileType: $fileType, asset: $asset, errorMessage: $errorMessage)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object> get props {
|
||||||
|
return [
|
||||||
|
id,
|
||||||
|
fileName,
|
||||||
|
fileType,
|
||||||
|
errorMessage,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||