Compare commits

...

74 Commits

Author SHA1 Message Date
Alex
b7603fd150 Update mobile changelog 2022-06-18 11:00:35 -05:00
Jaime Baez
517a3363d6 Refactor API for albums feature (#155)
* Rename "shared" to "album"

Prepare moving "SharedAlbums" to "Albums"

* Update server album API endpoints

* Update mobile app album endpoints

Also add `putRequest` to mobile network.service

* Add GET album collection filter

- allow to filter by owner = 'mine' | 'their'
- make sharedWithUserIds no longer required when creating an album

* Rename remaining variables to "album"

* Add ParseMeUUIDPipe to validate uuid or `me`

* Add album params validation

* Update todo in mobile album service.

* Setup e2e testing

* Add user e2e tests

* Rename database host env variable to DB_HOST

* Add some `Album` e2e tests

Also fix issues found with the tests

* Force push (try to recover DB_HOST env)

* Rename db host env variable to `DB_HOSTNAME`

* Remove unnecessary `initDb` from test-utils

The current database.config is running the migrations:
`migrationsRun: true`

* Remove `initDb` usage from album e2e test

* Update GET albums filter to `shared`

- add filter by all / shared / not shared
- add response DTOs
- add GET albums e2e tests

* Update album e2e tests for user.service changes

* Update mobile app to use album response DTOs

* Refactor album-service DB into album-registry

- DB logic refactored into album-repository making it easier to test
- add some album-service unit tests
- add `clearMocks` to jest configuration

* Finish implementing album.service unit tests

* Rename response DTO

Make them consistent with rest of the project naming

* Update debug log messages in mobile network service

* Rename table `shared_albums` to `albums`

* Rename table `asset_shared_album`

* Rename Albums `sharedAssets` to `assets`

* Update tests to match updated "delete" response

* Fixed asset cannot be compared in Set by adding Equatable package

* Remove hero effect to fixed janky animation

Co-authored-by: Alex <alex.tran1502@gmail.com>
2022-06-18 10:56:36 -05:00
Alex
3511b69fc8 Up Minor Version 2022-06-18 09:56:56 -05:00
Zack Pollard
e6efc61b3b fix: out of memory error when uploading large assets on slow internet (#224) 2022-06-18 07:36:58 -05:00
Alex Tran
360c1d9a15 Update Readme 2022-06-14 09:41:06 -05:00
Jaime Baez
e3449f9c8f Fix 500 error on login with email not in DB (#212)
* Fix 500 error on login with email not in DB

* Fix type declarations in ServerInfoDto
2022-06-12 18:29:10 -05:00
Alex
a8e723d722 Fixed ENABLE_MAPBOX value is ignored (#223) 2022-06-12 18:19:53 -05:00
Alex Tran
d116234523 Update Readme with discord tag and new logo panel 2022-06-11 23:42:28 -05:00
Alex
dce2bc7508 Added account info panel with sign out button (#219) 2022-06-11 23:17:20 -05:00
Matthias Rupp
2bf764f560 Input validation for email and server endpoint in mobile app (#211) 2022-06-11 22:12:20 -05:00
Alex Tran
587b77e70b Fixex announcement web not close after acknowledgement 2022-06-11 19:58:16 -05:00
Alex Tran
53cd9fd8bf Fixed Github Aaction build release 2022-06-11 17:47:01 -05:00
Alex
a8220172f8 WIP refactor container and queuing system (#206)
* refactor microservices to machine-learning

* Update tGithub issue template with correct task syntax

* Added microservices container

* Communicate between service based on queue system

* added dependency

* Fixed problem with having to import BullQueue into the individual service

* Added todo

* refactor server into monorepo with microservices

* refactor database and entity to library

* added simple migration

* Move migrations and database config to library

* Migration works in library

* Cosmetic change in logging message

* added user dto

* Fixed issue with testing not able to find the shared library

* Clean up library mapping path

* Added webp generator to microservices

* Update Github Action build latest

* Fixed issue NPM cannot install due to conflict witl Bull Queue

* format project with prettier

* Modified docker-compose file

* Add GH Action for Staging build:

* Fixed GH action job name

* Modified GH Action to only build & push latest when pushing to main

* Added Test 2e2 Github Action

* Added Test 2e2 Github Action

* Implemented microservice to extract exif

* Added cronjob to scan and generate webp thumbnail  at midnight

* Refactor to ireduce hit time to database when running microservices

* Added error handling to asset services that handle read file from disk

* Added video transcoding queue to process one video at a time

* Fixed loading spinner on web while loading covering the info panel

* Add mechanism to show new release announcement to web and mobile app (#209)

* Added changelog page

* Fixed issues based on PR comments

* Fixed issue with video transcoding run on the server

* Change entry point content for backward combatibility when starting up server

* Added announcement box

* Added error handling to failed silently when the app version checking is not able to make the request to GITHUB

* Added new version announcement overlay

* Update message

* Added messages

* Added logic to check and show announcement

* Add method to handle saving new version

* Added button to dimiss the acknowledge message

* Up version for deployment to the app store
2022-06-11 16:12:06 -05:00
Alex
397f8c70b4 Fixed NPM build dependency conflict for server 2022-06-07 08:08:22 -05:00
Matthias Rupp
68ff5377b0 Minor improvements to the detail-panel component (#205)
* Fix roudning behavior in details panel

* Add lat,lon-popup to map in details

* Refactor map code in detail-panel to avoid duplicate code
2022-06-06 16:40:12 -05:00
Jaime Baez
b359dc3cb6 Fix user e2e tests (#194)
* WIP fix user e2e tests

The e2e tests still don't seem to work due to migrations not running.

Changes:
- update user.e2e tests to use new `userService.createUser` method
- fix server `typeorm` command to use ORM config
- update make test-e2e to re-create database volume every time
- add User DTO
- update auth.service and user.service to use User DTO
- update CreateUserDto making optional properties that are optional

* Fix migrations
- add missing `.ts` extension to migrations path
- update user e2e test for the new returned User resource
2022-06-06 11:16:03 -05:00
Zack Pollard
5b036067ed Fix sidebar layout (#204)
* fix: sidebar margins with more than one item incorrect

* fix: api url in sidebar shouldn't overflow the sidebar width
2022-06-05 21:12:12 -05:00
Alex
b9f38162d5 Implemented status box on the side bar (#201) 2022-06-05 05:15:39 -05:00
Alex
ab6909bfbd 20 video conversion for web view (#200)
* Added job for video conversion every 1 minute

* Handle get video as mp4 on the web

* Auto play video on web on hovered

* Added video player

* Added animation and video duration to thumbnail player

* Fixed issue with video not playing on hover

* Added animation when loading thumbnail
2022-06-04 18:34:11 -05:00
Alex
53c3c916a6 View assets detail and download operation (#198)
* Fixed not displaying default user profile picture

* Added buttons to close viewer and micro-interaction for navigating assets left, right

* Add additional buttons to the control bar

* Display EXIF info

* Added map to detail info

* Handle user input keyboard

* Fixed incorrect file name when downloading multiple files

* Implemented download panel
2022-06-03 11:04:30 -05:00
Alex
6924aa5eb1 Update font size of 'create shared album' button to keep the text on one line 2022-05-29 18:15:16 -05:00
Alex
a3b45d62b6 175 Fixed issue back button android return to login page (#193)
* Back button is no longer return to login page

* Update to material 3

* Update to material 3

* Up version for deployment

* Added F-droid changelog
2022-05-29 17:32:30 -05:00
Alex Tran
b34de624ce Added changelog for F-droid 2022-05-29 09:11:22 -05:00
Alex Tran
7886c42742 Update Fastlane iOS build version + speicify database container to restart always 2022-05-29 08:42:27 -05:00
Alex
d476b15312 Implemented user profile upload and show on web/mobile (#191)
* Update mobile dependencies

* Added image picker

* Added mechanism to upload profile image

* Added image type to send to web

* Added styling for circle avatar

* Fixxed issue with sharp cannot resize image properly

* Finished displaying and uploading user profile

* Added user profile to web
2022-05-28 22:35:45 -05:00
Alex Tran
bdf38e7668 Added endpoint for getting user profile image 2022-05-27 22:24:58 -05:00
Alex Tran
e33566a04a Upload profile picture and convert into webp 2022-05-27 22:15:35 -05:00
Alex
c28251b8b4 [WEB] View large images on web (#189)
* Added selection icon to thumbnail

* Added micro-interaction and video file indication

* Added page to add page

* Added image viewer

* navigate assets

* Added separate component for viewing the video file

* Added FFmpeg modules

* Added correct content-type header for serving image file

* Added loading spinner
2022-05-27 14:02:06 -05:00
Alex Tran
337db1c508 fixed typo for bug template 2022-05-26 15:12:08 -05:00
Alex Tran
ad2a1ba901 Update and add issue templates 2022-05-26 15:10:50 -05:00
Alex Tran
fa6f6f8e9f Fixed default DB_HOSTNAME to be the correct database container 2022-05-24 08:49:38 -05:00
Pavle Portic
a44043a4e5 Add ability to pass redis hostname as env var (#174)
* Add ability to pass redis hostname as env var

* Read postgres host from env var in microservices

* Update .env.example with postgres and redis hostname vars
2022-05-23 17:23:02 -05:00
shruuub
87b15c60c0 Fix typos and grammar, plus change Linux to Unix (#180)
Fixed Readme typo
2022-05-23 11:58:00 -05:00
Alex
2c83e52c15 Added dependency in docker-compose (#173)
* Added dependency in docker-compose
* downgrade sharp version to 0.28
2022-05-22 10:15:38 -05:00
Alex
55c5027539 Add webp thumbnail conversion task to optimize performance of fast scrolling (#172)
* Update readme

* Added webp to table and entity

* Added cronjob and sharp dependencies

* Added conversion of webp every 5 minutes and endpoint will now server webp image if exist
2022-05-22 06:56:36 -05:00
Alex
ce06af0c9b Fixed lodash library not invoking in production build (#171)
* Added staging docker-compose file
* Use lodash-es and remove hydration option on photos page fixed the problem
2022-05-22 04:48:38 -05:00
Alex Tran
baaf7ad153 disable hydration 2022-05-21 23:50:54 -05:00
Alex Tran
4a25e7dc22 remove footer to fix error when reloading the page 2022-05-21 23:36:58 -05:00
Alex Tran
f90563d18c not prerender photos page 2022-05-21 23:28:02 -05:00
Alex Tran
8352ecd3b9 Update readme 2022-05-21 18:53:30 -05:00
Alex Tran
69b34a4364 Update readme 2022-05-21 18:49:23 -05:00
Alex
6023c3c624 Show assets on web (#168)
* Implemented lazy loading thumbnail
* Display assets as date-time grouping
* Update Readme
* Modify GitHub action to run from the latest update
2022-05-21 16:50:56 -05:00
Alex Tran
171e7ffa77 Update readme 2022-05-21 08:30:27 -05:00
Alex Tran
d9f918005a Added python3 to prod target of web Dockerfile 2022-05-21 02:34:39 -05:00
Alex Tran
e8ade4866b Added python3 to docker image of web 2022-05-21 02:30:00 -05:00
Alex Tran
bbfa789a4e update readme 2022-05-21 02:25:15 -05:00
Alex
a779c3803c Add web interface with admin functionality (#167) 2022-05-21 02:23:55 -05:00
Jaime Baez
79dea504b0 Add e2e testing setup (#163)
* Setup e2e testing

* Add user e2e tests

* Rename database host env variable to DB_HOST

* Force push (try to recover DB_HOST env)

* Rename db host env variable to `DB_HOSTNAME`

* Remove unnecessary `initDb` from test-utils

The current database.config is running the migrations:
`migrationsRun: true`
2022-05-19 18:30:47 -05:00
Migelo
4900fecd10 fix immich-server service name in README (#166) 2022-05-18 06:26:37 -04:00
Alex
adfaab7eb2 Update to flutter 3 (#162) 2022-05-14 09:25:19 -05:00
Alex
c5adbea6e1 Fixed incorrect microservices URLs after updating dockerfiles (#159) 2022-05-11 06:18:11 -05:00
Alex
bb89fa4aab Modify docker-compose to be compatible with k8s (#149)
* Modify docker-compose file using a hyphen for services instead of underscore

* Change URL in Nginx setting
2022-05-08 07:07:58 -05:00
Alex
43d639104d Bug/fixed permission not requested android 10 (#150)
* Added  android:requestLegacyExternalStorage=true to manifest

* Up pubspec version code for android build
2022-05-08 06:47:38 -05:00
Alex
a1792a7d94 Pump docker-compose container version 2022-05-06 07:47:19 -05:00
Alex
373b6918f8 Feature #120 #89 selective backup in app (#137) 2022-05-06 07:22:23 -05:00
dependabot[bot]
f1396761b0 Bump docker/setup-buildx-action from 1.6.0 to 2.0.0 (#141)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 1.6.0 to 2.0.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v1.6.0...v2.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 07:21:33 -05:00
dependabot[bot]
335bb0707c Bump docker/build-push-action from 2.10.0 to 3.0.0 (#142)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 2.10.0 to 3.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v2.10.0...v3.0.0)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 07:21:21 -05:00
dependabot[bot]
7a51e0dd4d Bump docker/login-action from 1 to 2 (#143)
Bumps [docker/login-action](https://github.com/docker/login-action) from 1 to 2.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 07:21:11 -05:00
dependabot[bot]
5b42899dde Bump docker/setup-qemu-action from 1.2.0 to 2.0.0 (#140)
Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 1.2.0 to 2.0.0.
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/v1.2.0...v2.0.0)

---
updated-dependencies:
- dependency-name: docker/setup-qemu-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-06 07:20:50 -05:00
Alex
229357df2b Appbar on homepage is fixed so the cursor won't be overlapping when scrolling 2022-04-30 17:03:45 -05:00
Alex
8d5626620b Refactor homepage widget 2022-04-30 10:55:27 -05:00
Alex
2c4243b3d0 Deploy 1.8.0_12-dev (#132)
* Update 1.8.0_12
* Update readme
2022-04-29 13:10:42 -05:00
Alex
38e0178c81 Implemented editable album title (#130)
* Replace static title text with a text edit field
* Implement endpoint for updating album info
* Implement changing title
* Only the owner can change the title
2022-04-28 23:46:37 -05:00
Alex
c5c7a134dd Update docker-compose file for faster and cleaner build; update ios version for deployment to test flight 2022-04-24 21:43:45 -05:00
Alex
da9eb61532 Implemented remembering login data with radio button (#126) 2022-04-24 21:33:10 -05:00
Alex
c1ccf026f0 Fixed typo in readme 2022-04-23 21:47:53 -05:00
Alex
4309104925 118 - Implement shared album feature (#124)
* New features 
  - Share album. Users can now create albums to share with existing people on the network.
  - Owner can delete the album.
  - Owner can invite the additional users to the album.
  - Shared users and the owner can add additional assets to the album.
* In the asset viewer, the user can swipe up to see detailed information and swip down to dismiss.
* Several UI enhancements.
2022-04-23 21:08:45 -05:00
Alex Tran
a3b84b3ca7 Update readme 2022-04-05 17:29:02 -05:00
Alex Tran
f6630163b1 update readme 2022-04-05 17:27:37 -05:00
Chiogros
aebeb37fb0 Legacy CPUs architecture alternative tip. (#93) 2022-04-05 17:26:42 -05:00
Alex
b74ad69288 Fixed duplicated filename on upload 2022-04-05 14:57:54 -05:00
Alex Tran
b6579cd38e Fixed incorrect image name when push to dockerhub 2022-04-05 11:43:09 -05:00
Alex Tran
46a2032b9a Update release workflow 2022-04-05 11:08:30 -05:00
Alex Tran
0eb548f115 Update readme 2022-04-05 11:04:53 -05:00
388 changed files with 19557 additions and 3112 deletions

1
.github/FUNDING.yml vendored
View File

@@ -1,3 +1,4 @@
# These are supported funding model platforms
github: alextran1502
custom: https://www.buymeacoffee.com/altran1502?new=1

View File

@@ -1,15 +1,26 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG]"
labels: bug
title: '[BUG] <title>'
labels: bug, need triage
assignees: ''
---
<!--
Note: Please search to see if an issue already exists for the bug you encountered.
-->
**Describe the bug**
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**
Steps to reproduce the behavior:
1. Go to '...'

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View 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

View File

@@ -4,61 +4,90 @@ on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build_and_push_server_latest:
# 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_latest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "main" # branch
fetch-depth: 0
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1.6.0
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Immich
uses: docker/build-push-action@v2.10.0
- 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' }}
push: true
tags: |
altran1502/immich-server:latest
build_and_push_microservice_latest:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "main" # branch
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1.6.0
- name: Login to Docker Hub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Microservices
uses: docker/build-push-action@v2.10.0
with:
context: ./microservices
file: ./microservices/Dockerfile
platforms: linux/arm/v7,linux/amd64
push: ${{ github.event_name != 'pull_request' }}
tags: |
altran1502/immich-microservices:latest
build_and_push_machine_learning_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 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: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

View File

@@ -0,0 +1,95 @@
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

View File

@@ -1,4 +1,4 @@
name: Build Server - Release
name: Build and push Docker image - Release
on:
workflow_dispatch:
@@ -6,90 +6,111 @@ on:
types: [published]
jobs:
build_and_push_server_release:
build_and_push_server_monorepo_release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "main"
ref: "main"
fetch-depth: 0
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: altran1502/immich-server
- name: 'Get Previous tag'
- name: "Get Previous tag"
id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
with:
fallback: latest
fallback: latest
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1.6.0
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-server release
uses: docker/build-push-action@v2.10.0
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: ${{ steps.previoustag.outputs.tag }}
labels: ${{ steps.meta.outputs.labels }}
tags: |
altran1502/immich-server:${{ steps.previoustag.outputs.tag }}
build_and_push_microservice_release:
build_and_push_machine_learning_release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
ref: "main"
fetch-depth: 0
- name: Docker meta
id: meta
uses: docker/metadata-action@v3
with:
images: altran1502/immich-microservices
- name: 'Get Previous tag'
- name: "Get Previous tag"
id: previoustag
uses: "WyriHaximus/github-action-get-previous-tag@v1"
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 }}
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
uses: docker/setup-qemu-action@v1.2.0
uses: docker/setup-qemu-action@v2.0.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1.6.0
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-microservices release
uses: docker/build-push-action@v2.10.0
- name: Build and push immich-web release
uses: docker/build-push-action@v3.0.0
with:
context: ./microservices
file: ./microservices/Dockerfile
platforms: linux/arm/v7,linux/amd64
context: ./web
file: ./web/Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.previoustag.outputs.tag }}
labels: ${{ steps.meta.outputs.labels }}
target: prod
tags: |
altran1502/immich-web:${{ steps.previoustag.outputs.tag }}

View File

@@ -5,10 +5,16 @@ dev-update:
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
dev-scale:
docker-compose -f ./docker/docker-compose.dev.yml up --build -V --scale immich_server=3 --remove-orphans
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:
docker-compose -f ./docker/docker-compose.yml up --build -V --remove-orphans
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=5 --scale immich-microservices=3 --remove-orphans

9
NOTES.md Normal file
View 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

View File

@@ -6,7 +6,7 @@
[ ] Up version in [docker/docker-compose.gpu.yml](/docker/docker-compose.gpu.yml) for `immich_server` service
[ ] Up version in [docker/docker-compose.dev.yml](/docker/docker-compose.gpu.yml) for `immich_server` service
[ ] Up version in [docker/docker-compose.dev.yml](/docker/docker-compose.dev.yml) for `immich_server` service
[ ] Up version in [server/src/constants/server_version.constant.ts](/server/src/constants/server_version.constant.ts)

221
README.md
View File

@@ -10,14 +10,16 @@
<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" />
</a>
<a href="https://discord.gg/rxnyVTXGbM">
<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>
<br/>
<br/>
<br/>
<br/>
<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>
@@ -31,50 +33,73 @@ Loading ~4000 images/videos
## Screenshots
### Mobile client
<p align="left">
<img src="design/nsc1.png" width="150" title="Login With Custom URL">
<img src="design/nsc2.png" width="150" title="Backup Setting Info">
<img src="design/nsc3.png" width="150" title="Multiple select">
<img src="design/nsc4.jpeg" width="150" title="Curated Search Info">
<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/selective-backup-screen.png" width="150" title="Backup Setting Info">
<img src="design/home-screen.jpeg" width="150" title="Home Screen">
<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/nsc6.png" width="150" title="EXIF Info">
</p>
### Web client
<p align="center">
<img src="design/dashboard_photos.jpeg" width="100%" title="Home Dashboard">
</p>
# Note
**!! 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
- Upload and view assets(videos/images).
- Upload and view assets (videos/images).
- Auto Backup.
- Download asset to local device.
- Multi-user supported.
- Quick navigation with drag scroll bar.
- Auto Backup.
- Support HEIC/HEIF Backup.
- Extract and display EXIF info.
- Real-time render from multi-device upload event.
- Image Tagging/Classification based on ImageNet dataset
- Object detection based on COCO SSD.
- Search assets based on tags and exif data (lens, make, model, orientation)
- Upload assets from your local computer/server using [immich cli tools](https://www.npmjs.com/package/immich)
- [Optional] Reverse geocoding using Mapbox (Generous free-tier of 100,000 search/month)
- Show asset's location information on map (OpenStreetMap).
- 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.
- Web interface is available for administrative tasks (creating new users) and viewing assets on the server - additional features are coming.
# Development
# System Requirement
You can use docker compose for development, there are several services that compose Immich
**OS**: Preferred unix-based operating system (Ubuntu, Debian, MacOS...etc).
1. NestJs
2. PostgreSQL
3. Redis
4. Nginx
5. TensorFlow
I haven't tested with `Docker for Windows` as well as `WSL` on Windows
## Populate .env file
*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.
**Core**: At least 2 cores, preffered 4 cores.
# Getting Started
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
2. **SvelteKit** - Web frontend of the application
3. **PostgreSQL** - Main database of the application
4. **Redis** - For sharing websocket instance between docker instances and background tasks message queue.
5. **Nginx** - Load balancing and optimized file uploading.
6. **TensorFlow** - Object Detection and Image Classification.
## Step 1: Populate .env file
Navigate to `docker` directory and run
@@ -88,61 +113,151 @@ Notice that if set `ENABLE_MAPBOX` to `true`, you will have to provide `MAPBOX_K
Pay attention to the key `UPLOAD_LOCATION`, this directory must exist and is owned by the user that run the `docker-compose` command below.
To start, run
**Example**
```bash
docker-compose -f ./docker/docker-compose.yml up --build -V
###################################################################################
# Database
###################################################################################
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE_NAME=immich
###################################################################################
# Upload File Config
###################################################################################
UPLOAD_LOCATION=<put-the-path-of-the-upload-folder-here>
###################################################################################
# JWT SECRET
###################################################################################
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
###################################################################################
# MAPBOX
####################################################################################
# ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
ENABLE_MAPBOX=false
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
VITE_SERVER_ENDPOINT=http://192.168.1.216:2283
```
## Step 2: Start the server
To **start**, run
```bash
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.
```bash
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)
```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` through `Nginx`
## 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.
```bash
curl --location --request POST 'http://your-server-ip:2283/auth/signUp' \
--header 'Content-Type: application/json' \
--data-raw '{
"email": "testuser@email.com",
"password": "password"
}'
```
## Run mobile app
### Android
Download `apk` in release tab and run on your phone. You can follow this guide on how to do that
- [Run APK on Android](https://www.lifewire.com/install-apk-on-android-4177185)
### iOS
You can download the app from Apple AppStore [here](https://apps.apple.com/us/app/immich/id1613945652):
Access the web interface at `http://your-ip:2285` to register an admin account.
<p align="left">
<img src="design/ios-qr-code.png" width="250" title="Apple App Store">
<img src="design/admin-registration-form.png" width="300" title="Admin Registration">
<p/>
Additional accounts on the server can be created by the admin account.
<p align="left">
<img src="design/admin-interface.png" width="500" title="Admin User Management">
<p/>
## Step 4: Run mobile app
The app is distributed on several platforms below.
## F-Droid
You can get the app on F-droid by clicking the image below.
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png"
alt="Get it on F-Droid"
height="80">](https://f-droid.org/packages/app.alextran.immich)
## Android
#### Get the app on Google Play Store [here](https://play.google.com/store/apps/details?id=app.alextran.immich)
*The App version might be lagging behind the latest release due to the review process.*
<p align="left">
<img src="design/google-play-qr-code.png" width="200" title="Google Play Store">
<p/>
## iOS
#### Get the app on Apple AppStore [here](https://apps.apple.com/us/app/immich/id1613945652):
*The App version might be lagging behind the latest release due to the review process.*
<p align="left">
<img src="design/ios-qr-code.png" width="200" title="Apple App Store">
<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
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).
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.
This is also a meaningful way to give me motivation and encounragment to continue working on the app.
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/altran1502)
Cheer! 🎉
This is also a meaningful way to give me motivation and encouragement to continue working on the app.
Cheers! 🎉
# Known Issue
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`. Otherwise, switch to a different VM/desktop with different architecture.
## TensorFlow Build Issue
*This is a known issue on RaspberryPi 4 arm64-v7 and incorrect Promox setup*
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
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.
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
Otherwise you can:
- 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.

BIN
design/admin-interface.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
design/backup-screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

BIN
design/feature-panel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
design/home-screen.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

BIN
design/login-screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

BIN
design/search-screen.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 570 KiB

BIN
design/shared-albums.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

View File

@@ -1,15 +1,63 @@
###################################################################################
# Database
###################################################################################
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE_NAME=
DB_DATABASE_NAME=immich
###################################################################################
# Redis
###################################################################################
REDIS_HOSTNAME=immich_redis
###################################################################################
# Upload File Config
###################################################################################
UPLOAD_LOCATION=absolute_location_on_your_machine_where_you_want_to_store_the_backup
# JWT SECRET
JWT_SECRET=
###################################################################################
# JWT SECRET
###################################################################################
JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
###################################################################################
# MAPBOX
## ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
ENABLE_MAPBOX=
MAPBOX_KEY=
####################################################################################
# ENABLE_MAPBOX is either true of false -> if true, you have to provide MAPBOX_KEY
ENABLE_MAPBOX=false
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
# !CAUTION! THERE IS NO FORWARD SLASH AT THE END
VITE_SERVER_ENDPOINT=

22
docker/.env.test Normal file
View 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

View File

@@ -1,12 +1,12 @@
version: "3.8"
services:
immich_server:
image: immich-server-dev:1.6.0
immich-server:
image: immich-server-dev:1.9.0
build:
context: ../server
dockerfile: Dockerfile
command: npm run start:dev
command: npm run start:dev immich
expose:
- "3000"
volumes:
@@ -21,18 +21,18 @@ services:
- redis
- database
networks:
- immich_network
- immich-network
immich_microservices:
image: immich-microservices-dev:1.6.0
immich-machine-learning:
image: immich-machine-learning-dev:1.9.0
build:
context: ../microservices
context: ../machine-learning
dockerfile: Dockerfile
command: npm run start:dev
expose:
- "3001"
volumes:
- ../microservices:/usr/src/app
- ../machine-learning:/usr/src/app
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /usr/src/app/node_modules
env_file:
@@ -42,14 +42,51 @@ services:
depends_on:
- database
networks:
- immich_network
- immich-network
immich-microservices:
image: immich-microservices:1.9.0
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
networks:
- immich-network
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:
- 3002:3002
- 24678:24678
volumes:
- ../web:/usr/src/app
- /usr/src/app/node_modules
networks:
- immich-network
restart: always
redis:
container_name: immich_redis
image: redis:6.2
networks:
- immich_network
- immich-network
database:
container_name: immich_postgres
@@ -66,7 +103,7 @@ services:
ports:
- 5432:5432
networks:
- immich_network
- immich-network
nginx:
container_name: proxy_nginx
@@ -79,11 +116,11 @@ services:
logging:
driver: none
networks:
- immich_network
- immich-network
depends_on:
- immich_server
- immich-server
networks:
immich_network:
immich-network:
volumes:
pgdata:

View File

@@ -1,8 +1,8 @@
version: "3.8"
services:
immich_server:
image: immich-server-dev:1.6.0
immich-server:
image: immich-server-dev:1.9.0
build:
context: ../server
dockerfile: Dockerfile
@@ -19,10 +19,10 @@ services:
- redis
- database
networks:
- immich_network
- immich-network
immich_microservices:
image: immich-microservices-dev:1.6.0
immich-microservices:
image: immich-microservices-dev:1.9.0
build:
context: ../microservices
dockerfile: Dockerfile
@@ -46,13 +46,13 @@ services:
- database
- immich_server
networks:
- immich_network
- immich-network
redis:
container_name: immich_redis
image: redis:6.2
networks:
- immich_network
- immich-network
database:
container_name: immich_postgres
@@ -69,7 +69,7 @@ services:
ports:
- 5432:5432
networks:
- immich_network
- immich-network
nginx:
container_name: proxy_nginx
@@ -82,11 +82,11 @@ services:
logging:
driver: none
networks:
- immich_network
- immich-network
depends_on:
- immich_server
- immich-server
networks:
immich_network:
immich-network:
volumes:
pgdata:

View File

@@ -0,0 +1,110 @@
version: "3.8"
services:
immich-server:
image: altran1502/immich-server:staging
entrypoint: ["/bin/sh", "./start-server.sh"]
expose:
- "3000"
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
environment:
- NODE_ENV=production
depends_on:
- redis
- database
networks:
- immich-network
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
networks:
- immich-network
restart: always
immich-machine-learning:
image: altran1502/immich-machine-learning:staging
entrypoint: ["/bin/sh", "./entrypoint.sh"]
expose:
- "3001"
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
environment:
- NODE_ENV=production
depends_on:
- database
networks:
- immich-network
restart: always
immich-web:
image: altran1502/immich-web:staging
entrypoint: ["/bin/sh", "./entrypoint.sh"]
env_file:
- .env
ports:
- 2285:3000
networks:
- immich-network
restart: always
redis:
container_name: immich_redis
image: redis:6.2
networks:
- immich-network
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
ports:
- 5432:5432
networks:
- immich-network
restart: always
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
restart: always
networks:
immich-network:
volumes:
pgdata:

View File

@@ -0,0 +1,50 @@
version: "3.8"
services:
immich_server_test:
image: immich-server-dev:1.9.0
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
networks:
- immich_network_test
redis:
container_name: immich_redis_test
image: redis:6.2
networks:
- immich_network_test
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
networks:
- immich_network_test
networks:
immich_network_test:

View File

@@ -1,12 +1,9 @@
version: "3.8"
services:
immich_server:
image: immich-server:1.6.0
build:
context: ../server
dockerfile: Dockerfile
entrypoint: ["/bin/sh", "./entrypoint.sh"]
immich-server:
image: altran1502/immich-server:latest
entrypoint: ["/bin/sh", "./start-server.sh"]
expose:
- "3000"
volumes:
@@ -19,14 +16,27 @@ services:
- redis
- database
networks:
- immich_network
restart: unless-stopped
- immich-network
restart: always
immich_microservices:
image: immich-microservices:1.6.0
build:
context: ../microservices
dockerfile: Dockerfile
immich-microservices:
image: altran1502/immich-server:latest
entrypoint: ["/bin/sh", "./start-microservices.sh"]
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
env_file:
- .env
environment:
- NODE_ENV=production
depends_on:
- redis
- database
networks:
- immich-network
restart: always
immich-machine-learning:
image: altran1502/immich-machine-learning:latest
entrypoint: ["/bin/sh", "./entrypoint.sh"]
expose:
- "3001"
@@ -39,14 +49,26 @@ services:
depends_on:
- database
networks:
- immich_network
restart: unless-stopped
- immich-network
restart: always
immich-web:
image: altran1502/immich-web:latest
entrypoint: ["/bin/sh", "./entrypoint.sh"]
env_file:
- .env
ports:
- 2285:3000
networks:
- immich-network
restart: always
redis:
container_name: immich_redis
image: redis:6.2
networks:
- immich_network
- immich-network
restart: always
database:
container_name: immich_postgres
@@ -63,7 +85,8 @@ services:
ports:
- 5432:5432
networks:
- immich_network
- immich-network
restart: always
nginx:
container_name: proxy_nginx
@@ -76,11 +99,12 @@ services:
logging:
driver: none
networks:
- immich_network
- immich-network
depends_on:
- immich_server
- immich-server
restart: always
networks:
immich_network:
immich-network:
volumes:
pgdata:
pgdata:

View File

@@ -10,11 +10,22 @@ map $http_upgrade $connection_upgrade {
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;
@@ -30,6 +41,6 @@ server {
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_pass http://immich_server:3000;
proxy_pass http://immich-server:3000;
}
}

View File

@@ -32,4 +32,6 @@ lerna-debug.log*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/extensions.json
upload/

View File

@@ -2,7 +2,7 @@ import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export const databaseConfig: TypeOrmModuleOptions = {
type: 'postgres',
host: 'immich_postgres',
host: process.env.DB_HOSTNAME || 'immich_postgres',
port: 5432,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,

View File

@@ -5,9 +5,9 @@ import { ImageClassifierService } from './image-classifier.service';
export class ImageClassifierController {
constructor(
private readonly imageClassifierService: ImageClassifierService,
) {}
) { }
@Post('/tagImage')
@Post('/tag-image')
async tagImage(@Body('thumbnailPath') thumbnailPath: string) {
return await this.imageClassifierService.tagImage(thumbnailPath);
}

View File

@@ -8,14 +8,14 @@ async function bootstrap() {
await app.listen(3001, () => {
if (process.env.NODE_ENV == 'development') {
Logger.log(
'Running Immich Microservices in DEVELOPMENT environment',
'Running Immich Machine Learning in DEVELOPMENT environment',
'IMMICH MICROSERVICES',
);
}
if (process.env.NODE_ENV == 'production') {
Logger.log(
'Running Immich Microservices in PRODUCTION environment',
'Running Immich Machine Learning in PRODUCTION environment',
'IMMICH MICROSERVICES',
);
}

View File

@@ -1,13 +1,14 @@
import { Body, Controller, Post } from '@nestjs/common';
import { ObjectDetectionService } from './object-detection.service';
import { Logger } from '@nestjs/common';
@Controller('object-detection')
export class ObjectDetectionController {
constructor(
private readonly objectDetectionService: ObjectDetectionService,
) {}
) { }
@Post('/detectObject')
@Post('/detect-object')
async detectObject(@Body('thumbnailPath') thumbnailPath: string) {
return await this.objectDetectionService.detectObject(thumbnailPath);
}

View File

@@ -1 +0,0 @@
devenv/

View File

@@ -1,3 +0,0 @@
__pycache__/
devenv/
app/upload

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

View File

@@ -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)

File diff suppressed because it is too large Load Diff

View File

@@ -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",
# )

View File

@@ -1,4 +0,0 @@
def run_detection():
print("run detection")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 KiB

View File

@@ -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

View File

@@ -81,4 +81,5 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3'
}

View File

@@ -1,5 +1,5 @@
<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">
<!-- Specifies an Android theme to apply to this Activity as soon as
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.WRITE_EXTERNAL_STORAGE" />
<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>

View File

@@ -1,16 +1,21 @@
// Generated file.
//
// If you wish to remove Flutter's multidex support, delete this entire file.
//
// Modifications to this file should be done in a copy under a different name
// as this file may be regenerated.
package io.flutter.app;
import android.app.Application;
import android.content.Context;
import androidx.annotation.CallSuper;
import androidx.multidex.MultiDex;
/**
* Extension of {@link io.flutter.app.FlutterApplication}, adding multidex support.
* Extension of {@link android.app.Application}, adding multidex support.
*/
public class FlutterMultiDexApplication extends FlutterApplication {
public class FlutterMultiDexApplication extends Application {
@Override
@CallSuper
protected void attachBaseContext(Context base) {

View File

@@ -0,0 +1,7 @@
* New features
- Share album. Users can now create albums to share with existing people on the network.
- Owner can delete the album.
- Owner can invite the additional users to the album.
- Shared users and the owner can add additional assets to the album.
* In the asset viewer, the user can swipe up to see detailed information and swip down to dismiss.
* Several UI enhancements.

View File

@@ -0,0 +1 @@
* Album name is now editable

View File

@@ -0,0 +1,2 @@
* New Feature - Selection backup. User can now select a combination of albums to be included or excluded during the backup process, and only unique photos, and videos that are not overlapping between the two groups will be backup.
* Bug fix - Show correct count of backup and remainder assets.

View File

@@ -0,0 +1 @@
* Hotfix: Permission is being requested now when open backup screen on Android10

View File

@@ -0,0 +1 @@
* User can now upload profile picture from the home page control drawer.

View File

@@ -0,0 +1,2 @@
* Update to Material Design 3
* Fixed back button navigation - no longer return back to the home page

View File

@@ -0,0 +1 @@
* Added announcement pop-up when a new released is pushed out in Github.

View File

@@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 KiB

After

Width:  |  Height:  |  Size: 681 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 570 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

View File

@@ -9,6 +9,8 @@ PODS:
- FMDB (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):
- Flutter
- path_provider_ios (0.0.1):
@@ -21,6 +23,8 @@ PODS:
- Flutter
- FMDB (>= 2.7.5)
- Toast (4.0.0)
- url_launcher_ios (0.0.1):
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- wakelock (0.0.1):
@@ -30,10 +34,12 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/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`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/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`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
@@ -50,6 +56,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_udid/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
@@ -58,6 +66,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/photo_manager/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock:
@@ -66,14 +76,16 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
fluttertoast: 6122fa75143e992b1d3470f61000f591a798cc58
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f

View File

@@ -360,7 +360,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -495,7 +495,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -522,7 +522,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 14;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<string>1.10.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>2</string>
<string>14</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
@@ -43,6 +43,12 @@
<key>NSPhotoLibraryAddUsageDescription</key>
<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>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
@@ -52,7 +58,7 @@
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
@@ -68,5 +74,13 @@
<true />
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
</dict>
</plist>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.6.0"
version_number: "1.12.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -21,7 +21,7 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
[bundle exec] fastlane ios beta
```
iOS deployment
iOS Beta
----

View File

@@ -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 classname="fastlane.lanes" name="1: latest_testflight_build_number" time="4.608292">
</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 classname="fastlane.lanes" name="1: increment_version_number" time="16.3225">
</testcase>

View File

@@ -3,9 +3,17 @@ const String userInfoBox = "immichBoxUserInfo"; // Box
const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
// SERVER ENDPOINT
// Server endpoint
const String serverEndpointKey = 'immichBoxServerEndpoint';
// KEY
const String hiveAllAsssetKey = "allAssets";
const String hiveBackupProgressKey = "backupProgressAssets";
// Login Info
const String hiveLoginInfoBox = "immichLoginInfoBox";
const String savedLoginInfoKey = "immichSavedLoginInfoKey";
// Backup Info
const String hiveBackupInfoBox = "immichBackupAlbumInfoBox";
const String backupInfoKey = "immichBackupAlbumInfoKey";
// Github Release Info
const String hiveGithubReleaseInfoBox = "immichGithubReleaseInfoBox";
const String githubReleaseInfoKey = "immichGithubReleaseInfoKey";

View File

@@ -0,0 +1,3 @@
import 'package:flutter/material.dart';
const immichBackgroundColor = Color(0xFFf6f8fe);

View File

@@ -2,20 +2,32 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/providers/asset.provider.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/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/routing/router.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/backup.provider.dart';
import 'package:immich_mobile/modules/backup/providers/backup.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/websocket.provider.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';
void main() async {
await Hive.initFlutter();
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
Hive.registerAdapter(HiveBackupAlbumsAdapter());
await Hive.openBox(userInfoBox);
// Hive.registerAdapter(ImmichBackUpAssetAdapter());
// Hive.deleteBoxFromDisk(hiveImmichBox);
await Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox);
await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
await Hive.openBox(hiveGithubReleaseInfoBox);
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
@@ -40,10 +52,18 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
case AppLifecycleState.resumed:
debugPrint("[APP STATE] 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(assetProvider.notifier).getAllAsset();
ref.watch(serverInfoProvider.notifier).getServerVersion();
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
break;
@@ -68,7 +88,7 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
}
Future<void> initApp() async {
WidgetsBinding.instance?.addObserver(this);
WidgetsBinding.instance.addObserver(this);
}
@override
@@ -79,7 +99,7 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
@override
void dispose() {
WidgetsBinding.instance?.removeObserver(this);
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@@ -87,28 +107,37 @@ class _ImmichAppState extends ConsumerState<ImmichApp> with WidgetsBindingObserv
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Immich',
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.indigo,
// textTheme: GoogleFonts.workSansTextTheme(
// Theme.of(context).textTheme.apply(fontSizeFactor: 1.0),
// ),
fontFamily: 'WorkSans',
snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
scaffoldBackgroundColor: const Color(0xFFf6f8fe),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.indigo,
elevation: 1,
centerTitle: true,
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
home: Stack(
children: [
MaterialApp.router(
title: 'Immich',
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
brightness: Brightness.light,
primarySwatch: Colors.indigo,
fontFamily: 'WorkSans',
snackBarTheme: const SnackBarThemeData(contentTextStyle: TextStyle(fontFamily: 'WorkSans')),
scaffoldBackgroundColor: immichBackgroundColor,
appBarTheme: const AppBarTheme(
backgroundColor: immichBackgroundColor,
foregroundColor: Colors.indigo,
elevation: 1,
centerTitle: true,
systemOverlayStyle: SystemUiOverlayStyle.dark,
),
),
routeInformationParser: _immichRouter.defaultRouteParser(),
routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
),
const ImmichLoadingOverlay(),
const VersionAnnouncementOverlay(),
],
),
routeInformationParser: _immichRouter.defaultRouteParser(),
routerDelegate: _immichRouter.delegate(navigatorObservers: () => [TabNavigationObserver(ref: ref)]),
);
}
}

View File

@@ -1,6 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_swipe_detector/flutter_swipe_detector.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
@@ -35,6 +37,18 @@ class ImageViewerPage extends HookConsumerWidget {
assetDetail = await _assetService.getAssetById(asset.id);
}
showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
},
);
}
useEffect(() {
getAssetExif();
return null;
@@ -44,79 +58,77 @@ class ImageViewerPage extends HookConsumerWidget {
backgroundColor: Colors.black,
appBar: TopControlAppBar(
asset: asset,
onMoreInfoPressed: () {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
},
);
},
onMoreInfoPressed: showInfo,
onDownloadPressed: () {
ref.watch(imageViewerStateProvider.notifier).downloadAsset(asset, context);
},
),
body: SafeArea(
child: Stack(
children: [
Center(
child: Hero(
tag: heroTag,
child: CachedNetworkImage(
fit: BoxFit.cover,
imageUrl: imageUrl,
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
fadeInDuration: const Duration(milliseconds: 250),
errorWidget: (context, url, error) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300),
child: Wrap(
spacing: 32,
runSpacing: 32,
alignment: WrapAlignment.center,
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(),
body: SwipeDetector(
onSwipeDown: (_) {
AutoRouter.of(context).pop();
},
onSwipeUp: (_) {
showInfo();
},
child: SafeArea(
child: Stack(
children: [
Center(
child: Hero(
tag: heroTag,
child: CachedNetworkImage(
fit: BoxFit.cover,
imageUrl: imageUrl,
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
fadeInDuration: const Duration(milliseconds: 250),
errorWidget: (context, url, error) => ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 300),
child: Wrap(
spacing: 32,
runSpacing: 32,
alignment: WrapAlignment.center,
children: [
const Text(
"Failed To Render Image - Possibly Corrupted Data",
textAlign: TextAlign.center,
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
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],
),
);
},
),
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(),
),
],
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(
child: DownloadLoadingIndicator(),
),
],
),
),
),
);

View File

@@ -1,5 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_swipe_detector/flutter_swipe_detector.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
@@ -29,6 +31,18 @@ class VideoViewerPage extends HookConsumerWidget {
String jwtToken = Hive.box(userInfoBox).get(accessTokenKey);
void showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
},
);
}
getAssetExif() async {
assetDetail = await _assetService.getAssetById(asset.id);
}
@@ -43,32 +57,32 @@ class VideoViewerPage extends HookConsumerWidget {
appBar: TopControlAppBar(
asset: asset,
onMoreInfoPressed: () {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
},
);
showInfo();
},
onDownloadPressed: () {
ref.watch(imageViewerStateProvider.notifier).downloadAsset(asset, context);
},
),
body: SafeArea(
child: Stack(
children: [
VideoThumbnailPlayer(
url: videoUrl,
jwtToken: jwtToken,
),
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(
child: DownloadLoadingIndicator(),
body: SwipeDetector(
onSwipeDown: (_) {
AutoRouter.of(context).pop();
},
onSwipeUp: (_) {
showInfo();
},
child: SafeArea(
child: Stack(
children: [
VideoThumbnailPlayer(
url: videoUrl,
jwtToken: jwtToken,
),
],
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(
child: DownloadLoadingIndicator(),
),
],
),
),
),
);

View File

@@ -0,0 +1,35 @@
import 'dart:typed_data';
import 'package:photo_manager/photo_manager.dart';
class AvailableAlbum {
final AssetPathEntity albumEntity;
final Uint8List? thumbnailData;
AvailableAlbum({
required this.albumEntity,
this.thumbnailData,
});
AvailableAlbum copyWith({
AssetPathEntity? albumEntity,
Uint8List? thumbnailData,
}) {
return AvailableAlbum(
albumEntity: albumEntity ?? this.albumEntity,
thumbnailData: thumbnailData ?? this.thumbnailData,
);
}
@override
String toString() => 'AvailableAlbum(albumEntity: $albumEntity, thumbnailData: $thumbnailData)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is AvailableAlbum && other.albumEntity == albumEntity && other.thumbnailData == thumbnailData;
}
@override
int get hashCode => albumEntity.hashCode ^ thumbnailData.hashCode;
}

View File

@@ -0,0 +1,88 @@
import 'package:cancellation_token_http/http.dart';
import 'package:equatable/equatable.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
import 'package:immich_mobile/shared/models/server_info.model.dart';
enum BackUpProgressEnum { idle, inProgress, done }
class BackUpState extends Equatable {
// enum
final BackUpProgressEnum backupProgress;
final List<String> allAssetOnDatabase;
final double progressInPercentage;
final CancellationToken cancelToken;
final ServerInfo serverInfo;
/// All available albums on the device
final List<AvailableAlbum> availableAlbums;
final Set<AssetPathEntity> selectedBackupAlbums;
final Set<AssetPathEntity> excludedBackupAlbums;
/// Assets that are not overlapping in selected backup albums and excluded backup albums
final Set<AssetEntity> allUniqueAssets;
/// All assets from the selected albums that have been backup
final Set<String> selectedAlbumsBackupAssetsIds;
const BackUpState({
required this.backupProgress,
required this.allAssetOnDatabase,
required this.progressInPercentage,
required this.cancelToken,
required this.serverInfo,
required this.availableAlbums,
required this.selectedBackupAlbums,
required this.excludedBackupAlbums,
required this.allUniqueAssets,
required this.selectedAlbumsBackupAssetsIds,
});
BackUpState copyWith({
BackUpProgressEnum? backupProgress,
List<String>? allAssetOnDatabase,
double? progressInPercentage,
CancellationToken? cancelToken,
ServerInfo? serverInfo,
List<AvailableAlbum>? availableAlbums,
Set<AssetPathEntity>? selectedBackupAlbums,
Set<AssetPathEntity>? excludedBackupAlbums,
Set<AssetEntity>? allUniqueAssets,
Set<String>? selectedAlbumsBackupAssetsIds,
}) {
return BackUpState(
backupProgress: backupProgress ?? this.backupProgress,
allAssetOnDatabase: allAssetOnDatabase ?? this.allAssetOnDatabase,
progressInPercentage: progressInPercentage ?? this.progressInPercentage,
cancelToken: cancelToken ?? this.cancelToken,
serverInfo: serverInfo ?? this.serverInfo,
availableAlbums: availableAlbums ?? this.availableAlbums,
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
allUniqueAssets: allUniqueAssets ?? this.allUniqueAssets,
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssetsIds ?? this.selectedAlbumsBackupAssetsIds,
);
}
@override
String toString() {
return 'BackUpState(backupProgress: $backupProgress, allAssetOnDatabase: $allAssetOnDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds)';
}
@override
List<Object> get props {
return [
backupProgress,
allAssetOnDatabase,
progressInPercentage,
cancelToken,
serverInfo,
availableAlbums,
selectedBackupAlbums,
excludedBackupAlbums,
allUniqueAssets,
selectedAlbumsBackupAssetsIds,
];
}
}

View File

@@ -0,0 +1,66 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
part 'hive_backup_albums.model.g.dart';
@HiveType(typeId: 1)
class HiveBackupAlbums {
@HiveField(0)
List<String> selectedAlbumIds;
@HiveField(1)
List<String> excludedAlbumsIds;
HiveBackupAlbums({
required this.selectedAlbumIds,
required this.excludedAlbumsIds,
});
@override
String toString() => 'HiveBackupAlbums(selectedAlbumIds: $selectedAlbumIds, excludedAlbumsIds: $excludedAlbumsIds)';
HiveBackupAlbums copyWith({
List<String>? selectedAlbumIds,
List<String>? excludedAlbumsIds,
}) {
return HiveBackupAlbums(
selectedAlbumIds: selectedAlbumIds ?? this.selectedAlbumIds,
excludedAlbumsIds: excludedAlbumsIds ?? this.excludedAlbumsIds,
);
}
Map<String, dynamic> toMap() {
final result = <String, dynamic>{};
result.addAll({'selectedAlbumIds': selectedAlbumIds});
result.addAll({'excludedAlbumsIds': excludedAlbumsIds});
return result;
}
factory HiveBackupAlbums.fromMap(Map<String, dynamic> map) {
return HiveBackupAlbums(
selectedAlbumIds: List<String>.from(map['selectedAlbumIds']),
excludedAlbumsIds: List<String>.from(map['excludedAlbumsIds']),
);
}
String toJson() => json.encode(toMap());
factory HiveBackupAlbums.fromJson(String source) => HiveBackupAlbums.fromMap(json.decode(source));
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
final listEquals = const DeepCollectionEquality().equals;
return other is HiveBackupAlbums &&
listEquals(other.selectedAlbumIds, selectedAlbumIds) &&
listEquals(other.excludedAlbumsIds, excludedAlbumsIds);
}
@override
int get hashCode => selectedAlbumIds.hashCode ^ excludedAlbumsIds.hashCode;
}

Some files were not shown because too many files have changed in this diff Show More