Compare commits

..

59 Commits

Author SHA1 Message Date
Alex Tran
3dcdfa0166 Up android build version 2022-08-06 23:45:31 -05:00
Alex
2079583866 Update installation method and documentation (#424)
* Add installation script

* Populate instsall.sh

* format

* Get IP address on both macos and linux

* Update mobile version

* Remove test folder

* Added sed command for ios

* Added sed command for ios

* Fixed ios command

* Fixed ios command

* Added friendly debug message

* Update README

* Update Readme with new installation instruction

* Update message on instsallation script
2022-08-06 23:42:50 -05:00
Alex
b68358766b Remove VITE_SERVER_ENDPOINT dependency (#428)
* Move backend api to its own instance

* Remove external fetch hook

* Added endpoint for album

* Added endpoint for admin page

* Make request directly to immich-server

* Refactor unsued code
2022-08-06 18:14:54 -05:00
Alex Tran
cf2b9eddfa Pump version 1.20 2022-08-03 15:43:42 -05:00
Stevenson Chittumuri
8c184dc4d4 Enable swiping between assets (#381)
Enable swiping between assets (#381)

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Malte Kiefer <59220985+MalteKiefer@users.noreply.github.com>
Co-authored-by: Matthias Rupp <matthias.rupp@posteo.de>
2022-08-03 15:36:12 -05:00
Alex
e8d1f89a47 Implement album feature on mobile (#420)
* Refactor sharing to album

* Added library page in the bottom navigation bar

* Refactor SharedAlbumService to album service

* Refactor apiProvider to its file

* Added image grid

* render album thumbnail

* Using the wrap to render thumbnail and album info better

* Navigate to album viewer

* After deletion, navigate to the respective page of the shared and non-shared album

* Correctly remove album in local state

* Refactor create album page

* Implemented create non-shared album
2022-08-03 00:04:34 -05:00
Alex
0e85b0fd8f Remove print statement 2022-07-31 22:26:09 -05:00
Alex Tran
f7dc916e80 Fixed problem with Recent (isAll) album is both in exclude and include album list at the same time 2022-07-31 21:56:41 -05:00
Alex
03e7a254a2 Fixed logging out not redirect correctly in reverse proxy (#414)
* Remove check due to logout always success

* Added console log

* Remove console.log

* Up server version
2022-07-31 16:53:07 -05:00
Matthias Rupp
0ac9fe5a54 Load low- and high quality thumbnail in the same img tag to avoid flickering (#413) 2022-07-31 15:56:03 -05:00
Malte Kiefer
dc61fd925f fixed some German translations (#399) 2022-07-30 07:41:39 -05:00
Alex Tran
2aea08726f Update donation info 2022-07-29 13:42:39 -05:00
Alex Tran
746bec908b Update donation info 2022-07-29 13:41:29 -05:00
Alex Tran
8102e3b3f5 Fixed github action to conform with the move to org 2022-07-29 12:54:40 -05:00
Alex Tran
1ba998aa68 Added changlog for Fdroid release 2022-07-27 13:17:36 -05:00
Alex Tran
2de34f70ce Update readme 2022-07-27 13:09:52 -05:00
Alex Tran
8b9fd67d6f Remove AxiosError import due to production build error 2022-07-27 13:01:49 -05:00
Alex
97238a1621 Up version for release 2022-07-27 11:39:19 -05:00
Alex
ef4136d327 [WEB] Select album thumbnail (#383)
* Added context menu for album opionts

* choose asset for album thumbnail

* Refactor UpdateAlbumDto to accept albumThumbnailAssetId

* implemented changing album cover on web

* Fixed api change on mobile app
2022-07-27 11:16:02 -05:00
Alex
6dbca8d478 Added Japanese/Polish/Finish and fix Italian/Spanish translation 2022-07-27 11:14:21 -05:00
Alex
a305db9e6f [Localizely] Translations update (#384) 2022-07-27 11:07:37 -05:00
Alex Tran
59c1ea3097 Added 2-stage loading for album's thumbnail 2022-07-26 22:06:06 -05:00
Alex
03457f5d32 [WEB] Upload asset directly to album (#379)
* Added stores to get album assetId

* Upload assets and add to album

* Added comments

* resolve conflict when add assets from upload directly

* Filtered out duplicate asset before adding to the album
2022-07-26 20:53:25 -05:00
Alex
2336a6159c [WEB] Load thumbnail with native source property for faster load time (#378) 2022-07-26 15:13:08 -05:00
Alex Tran
e4c4b53fcd Added imageName as searchable text on database 2022-07-26 13:43:12 -05:00
Alex
83cbf51704 Use cookies for client requests (#377)
* Use cookie for frontend request

* Remove api helper to use SDK

* Added error handling to status box

* Remove additional places that check for session.user

* Refactor sending password

* prettier clean up

* remove deadcode

* Move all authentication requests to the client

* refactor upload panel to only fetch assets after the upload panel disappear

* Added keydown to remove focus on title change on album viewer
2022-07-26 12:28:07 -05:00
Alex Tran
2ebb755f00 Fixed corner radius for message board 2022-07-24 23:45:05 -05:00
Alex Tran
ec1c3a86f5 Added messages when there is no album or shared album 2022-07-24 23:30:30 -05:00
Alex
969f770df0 Delete album on web (#373)
* Show context menu

* Show context menu at the correct location

* Implement delete album button

* Delete album within album viewer
2022-07-24 22:47:12 -05:00
Alex Tran
9c3f848fa8 Use Webp for album thumbnail 2022-07-24 08:51:00 -05:00
Alex Tran
1ea6425cd1 Handle unhandled promises that lead to unable to login 2022-07-24 08:41:06 -05:00
Alex
052db5d748 Remove/Add asset in ablum on web (#371)
* Added interaction to select multiple thumbnail

* Fixed stutter transition

* Return AlbumResponseDto after removing an asset from album

* Render correctly when an array of thumbnail is updated

* Fixed wording

* Added native dialog for removing users from album

* Fixed rendering incorrect profile image on share user select dialog
2022-07-23 23:23:14 -05:00
bo0tzz
a35460cb84 Bump tfjs version to 3.19.0 for arm64 support (#368)
* Add linux/arm64 to machine-learning container build

* Bump tfjs version to 3.19.0

* Fix tfjs dependency error
2022-07-23 14:15:55 -05:00
Alex
ae93bbe2a7 Docker login only with branch from the repository (#370) 2022-07-23 13:48:53 -05:00
Alex
3b97c7729b Implement mechanism to remove and add shared user in album on web (#369)
* AFixed overlay issue of modal

* Added modal with existing user

* Added custom scrollbar to all pages

* Fixed Document is not define when access document DOM node in browswer

* Added context menu

* Added api to remove user from album

* Handle user leave album

* Added share button to non-shared album

* Added padding to album viewer:

* Fixed margin top of asset selection page

* Fixed issue cannot push to dockerhub
2022-07-23 13:08:49 -05:00
bo0tzz
6021124688 Move docker login step to after build (#367) 2022-07-23 11:05:13 -05:00
Alex
1d34976dd0 Implement album creation on web (#365)
* Added album creation button functionality

* Added input for album title

* Added select photos button

* Added page to select assets

* Show photo selection timeline

* Implemented update album name mechanism:

* Added selection mechanism

* Added selection mechanism with existing assets in album

* Refactored and added comments

* Refactored and added comments - 2

* Refactor album app bar

* Added modal for select user

* Implemented choose users

* Added additional share user button

* Added rule to show add users button
2022-07-22 09:44:22 -05:00
dependabot[bot]
02bde51caf Bump docker/build-push-action from 3.0.0 to 3.1.0 (#363)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.0.0 to 3.1.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.0.0...v3.1.0)

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-20 20:49:11 -05:00
Matthias Rupp
bef1e2e3db Api logout route (#361)
* Add logout route that deletes http only cookies

* Rebuild API
2022-07-19 13:49:58 -05:00
Alex
be3e3e5d7e Added Cookie Authentication (#360)
* Added Cookie Authentication

* Fixed issue with bearer is in lower case

* Fixed bearer to Bearer to conform with standard
2022-07-18 14:14:25 -05:00
Alex
c028c7db4e dev/add detail viewer to album (#358)
* Rename asset viewer folder

* Refactor AssetViewer to be able to user with different component

* Refactor AssetViewer to be able to user with different component

* Added viewer for album and sharing
2022-07-18 00:22:39 -05:00
Alex Tran
c129023821 Remove console.log 2022-07-17 15:10:04 -05:00
Alex Tran
cbdb8fa51f Update get user info controller to avoid conflict with /count 2022-07-17 15:09:26 -05:00
Alex
c6ecfb679a Added sharing page to web (#355)
* Added shared album

* Added list tile

* Show info of shared album owner
2022-07-16 23:52:00 -05:00
Alex
5d03e9bda8 Fix test instance cannot clear database after each test" (#354)
* Update test

* Fixed test cannot initialize database

* Added a separate network to test containers group to run test while in development mode
2022-07-16 23:43:31 -05:00
Alex Tran
d8b26c6da8 Update bug report template 2022-07-16 10:54:00 -05:00
Alex
2e61cf3183 Update README.md
Fixed incorrect info about microservices container
2022-07-16 07:15:22 -05:00
Alex Tran
45e2335b86 Allow manually run test workflow 2022-07-16 00:48:35 -05:00
Alex Tran
2bbc44c5ab Fixed test 2022-07-16 00:45:58 -05:00
Alex Tran
012428416d Remove console.log 2022-07-15 23:27:23 -05:00
Alex
7134f93eb8 Add ablum feature to web (#352)
* Added album page

* Refactor sidebar

* Added album assets count info

* Added album viewer page

* Refactor album sorting

* Fixed incorrectly showing selected asset in album selection

* Improve fetching speed with prefetch

* Refactor to use ImmichThubmnail component for all

* Update to the latest version of Svelte

* Implement fixed app bar in album viewer

* Added shared user avatar

* Correctly get all owned albums, including shared
2022-07-15 23:18:17 -05:00
Jaime Baez
1887b5a860 Add email validation in the API when creating new users (#350)
* Refactor user.service - add user-repository

* Add email validation for creating users
2022-07-15 14:30:56 -05:00
Jaime Baez
ef17668871 Update Spanish translations (#348)
- add missing translations
- remove extra white spaces
- spelling and other corrections
2022-07-14 13:59:07 -05:00
Alex
e9909b179a Up version for release 2022-07-14 11:39:06 -05:00
Alex
09f8bdef6d Up version for release 2022-07-14 11:32:07 -05:00
Alex
2a9b09f359 Added DA,ES,FR,IT (#347)
* Added DA,ES,FR,IT

* Update French translation
2022-07-14 10:20:23 -05:00
Alex
1f6a3ccac7 Added local code in localizely config file 2022-07-14 08:39:43 -05:00
Eidenz
1f40fc1de9 Add missing translation tag for search_no_objects (#344)
* feat(mobile) added french translations

* fix(mobile) added missing translation tag for search_no_objects (EN,DE,FR)

* fix(mobile) renamed search_no_objects to search_page_no_objects
2022-07-13 20:46:28 -05:00
Eidenz
20b94ef0bb feat(mobile) added french translations (#343) 2022-07-13 11:35:02 -05:00
254 changed files with 8363 additions and 3167 deletions

2
.github/FUNDING.yml vendored
View File

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

View File

@@ -16,8 +16,11 @@ Note: Please search to see if an issue already exists for the bug you encountere
A clear and concise description of what the bug is.
**Task List**
*Please complete the task list below. We need this information to help us reproduce the bug or point out problems in your setup. You are not providing enough info may delay our effort to help you.*
- [ ] 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 `docker-compose` file.
- [ ] I have included my redacted `.env` file.
- [ ] I have included information on my machine, and environment.
@@ -34,13 +37,10 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Version [e.g. 22]
**System**
- Phone OS [iOS, Android]: `<version>`
- Server Version: `<version>`
- Mobile App Version: `<version>`
**Additional context**
Add any other context about the problem here.

View File

@@ -27,7 +27,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Immich Mono Repo
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./server
file: ./server/Dockerfile
@@ -55,11 +55,11 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
platforms: linux/arm/v7,linux/amd64
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: true
tags: |
altran1502/immich-machine-learning:latest
@@ -82,7 +82,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Web
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./web
file: ./web/Dockerfile
@@ -110,7 +110,7 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./nginx
file: ./nginx/Dockerfile

View File

@@ -24,17 +24,18 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
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
uses: docker/build-push-action@v3.1.0
with:
context: ./server
file: ./server/Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: ${{ github.event_name == 'pull_request' }}
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-server:staging
@@ -52,17 +53,18 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
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
uses: docker/build-push-action@v3.1.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
platforms: linux/arm/v7,linux/amd64
push: ${{ github.event_name == 'pull_request' }}
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-machine-learning:staging
@@ -79,18 +81,19 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
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
uses: docker/build-push-action@v3.1.0
with:
context: ./web
file: ./web/Dockerfile
platforms: linux/arm/v7,linux/amd64,linux/arm64
target: prod
push: ${{ github.event_name == 'pull_request' }}
push: ${{ github.event_name == 'pull_request' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-web:staging
@@ -107,16 +110,17 @@ jobs:
id: buildx
uses: docker/setup-buildx-action@v2.0.0
- name: Login to Docker Hub
if: ${{ github.repository == 'immich-app/immich' }}
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
uses: docker/build-push-action@v3.1.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' && github.repository == 'immich-app/immich' }}
tags: |
altran1502/immich-proxy:staging
altran1502/immich-proxy:staging

View File

@@ -35,7 +35,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-server release
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./server
file: ./server/Dockerfile
@@ -68,11 +68,11 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
platforms: linux/arm/v7,linux/amd64
platforms: linux/arm/v7,linux/amd64,linux/arm64
push: true
tags: |
altran1502/immich-machine-learning:${{ steps.previoustag.outputs.tag }}
@@ -107,7 +107,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-web release
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./web
file: ./web/Dockerfile
@@ -147,7 +147,7 @@ jobs:
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push immich-proxy release
uses: docker/build-push-action@v3.0.0
uses: docker/build-push-action@v3.1.0
with:
context: ./nginx
file: ./nginx/Dockerfile

View File

@@ -1,5 +1,6 @@
name: Test
on:
workflow_dispatch:
pull_request:
push: { branches: master }
@@ -14,4 +15,4 @@ jobs:
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
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

View File

@@ -10,11 +10,17 @@ dev-scale:
stage:
docker-compose -f ./docker/docker-compose.staging.yml up --build -V --remove-orphans
pull-stage:
docker-compose -f ./docker/docker-compose.staging.yml pull
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
docker-compose -f ./docker/docker-compose.test.yml --env-file ./docker/.env.test -p immich-test-e2e up --renew-anon-volumes --abort-on-container-exit --exit-code-from immich-server-test --remove-orphans --build
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=3 --scale immich-microservices=3 --remove-orphans
api:
cd ./server && npm run api:generate

120
README.md
View File

@@ -61,33 +61,30 @@ This project is under heavy development, there will be continuous functions, fea
| | Mobile | Web |
| - | - | - |
| Upload and view videos and photos | Yes | Yes
| Auto backup when app is opened | Yes | N/A
| Auto backup when the app is opened | Yes | N/A
| Selective album(s) for backup | Yes | N/A
| Download photos and videos to local device | Yes | Yes
| Multi-user support | Yes | Yes
| Shared Albums | Yes | No
| Album | Yes | Yes
| Shared Albums | Yes | Yes
| Quick navigation with draggable scrollbar | Yes | Yes
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
| Metadata view (EXIF, map) | Yes | Yes
| Search by metadata, objects and image tags | Yes | No
| Administrative functions (user management) | No | Yes
| Administrative functions (user management) | N/A | Yes
# System Requirement
**OS**: Preferred unix-based operating system (Ubuntu, Debian, MacOS...etc).
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 Docker image on arm64v7 yet.*
**RAM**: At least 2GB, preffered 4GB.
**Core**: At least 2 cores, preffered 4 cores.
# Getting Started
# Technology Stack
You can use docker compose for development and testing out the application, there are several services that compose Immich:
There are several services that compose Immich:
1. **NestJs** - Backend of the application
2. **SvelteKit** - Web frontend of the application
@@ -96,19 +93,51 @@ You can use docker compose for development and testing out the application, ther
5. **Nginx** - Load balancing and optimized file uploading.
6. **TensorFlow** - Object Detection (COCO SSD) and Image Classification (ImageNet).
## Step 1: Populate .env file
# Installing
Navigate to `docker` directory and run
## One-step installation - for evaluating only
```
cp .env.example .env
*Applicable system: Ubuntu, Debian, MacOS*
*This installation method is for evaluating Immich before futher customization to meet the users' needs.*
In the shell, from the directory of your choice, run the following command:
```bash
curl -o- https://raw.githubusercontent.com/immich-app/immich/main/install.sh | bash
```
Then populate the value in there.
This script will download the `docker-compose.yml` file and the `.env` file, then populate the necessary information, and finally run the `docker-compose up` or `docker compose up` (based on your docker's version) command.
Notice that if set `ENABLE_MAPBOX` to `true`, you will have to provide `MAPBOX_KEY` for the server to run.
The web application will be available at `http://<machine-ip-address>:2283`, and the server URL for the mobile app will be `http://<machine-ip-address>:2283/api`.
Pay attention to the key `UPLOAD_LOCATION`, this directory must exist and is owned by the user that run the `docker-compose` command below.
The directory which is used to store the backup file is `./immich-app/immich-data`.
## Customize installation - for production usage
### Step 1 - Download necessary files
Create a directory called `immich-app` and cd into it. Then
Get `docker-compose.yml`
```bash
wget https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml
```
Get `.env`
```bash
wget -O .env wget https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example
```
### Step 2 - Populate .env file with customed information
* Populate customised database information if necessary.
* Populate `UPLOAD_LOCATION` as prefered location for storing backup assets.
* Populate a secret value for `JWT_SECRET`
* [Optional] Popluate Mapbox value.
**Example**
@@ -136,36 +165,15 @@ JWT_SECRET=randomstringthatissolongandpowerfulthatnoonecanguess
# 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/api
VITE_SERVER_ENDPOINT=http://192.168.1.216:2283/api
```
## Step 2: Start the server
### Step 3 - Start the containers
To **start**, run
Run `docker-compose up` or `docker-compose up` (based on your docker's version)
```bash
docker-compose -f ./docker/docker-compose.yml up
```
### Step 4 - Register admin user
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/api`
## Step 3: Register User
Access the web interface at `http://your-ip:2283` to register an admin account.
Navigate to the web at `http://<machine-ip-address>:2283` and follow the prompts to register admin user.
<p align="left">
<img src="design/admin-registration-form.png" width="300" title="Admin Registration">
@@ -177,14 +185,16 @@ Additional accounts on the server can be created by the admin account.
<img src="design/admin-interface.png" width="500" title="Admin User Management">
<p/>
## Step 4: Run mobile app
### Step 5 - Access the mobile app
Login the mobile app with your server address
Login the mobile app with the server endpoint URL at `http://<machine-ip-address>:2283/api`
<p align="left">
<img src="design/login-screen.jpeg" width="250" title="Example login screen">
<p/>
## Mobile app
## F-Droid
You can get the app on F-droid by clicking the image below.
@@ -236,9 +246,23 @@ You can find the generated client SDK in the [`web/src/api`](web/src/api) for Ty
# 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 Sponsor**](https://github.com/sponsors/alextran1502), or a one time donation with the 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 [**one time**](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) or monthly donation from [**Github Sponsor**](https://github.com/sponsors/alextran1502)
You can also donate using crypto currency with the following addresses:
<p align="left" style="display: flex; place-items: center; gap: 20px" title="Bitcoin(BTC)">
<img src="design/bitcoin.png" width="25" title="Bitcoin">
<code>1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX</code>
</p>
<p align="left" style="display: flex; place-items: center; gap: 15px" title="Cardano(ADA)">
<img src="design/cardano.png" width="30" title="Cardano">
<code>
addr1qyy567vqhqrr3p7vpszr5p264gw89sqcwts2z8wqy4yek87cdmy79zazyjp7tmwhkluhk3krvslkzfvg0h43tytp3f5q49nycc
</code>
</p>
[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/altran1502)
This is also a meaningful way to give me motivation and encouragement to continue working on the app.
@@ -248,7 +272,7 @@ Cheers! 🎉
## TensorFlow Build Issue
*This is a known issue on RaspberryPi 4 arm64-v7 and incorrect Promox setup*
*This is a known issue for 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`:
@@ -261,7 +285,3 @@ If you are running virtualization in Promox, the VM doesn't have the flag enable
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/bitcoin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
design/cardano.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -56,21 +56,6 @@ ENABLE_MAPBOX=false
MAPBOX_KEY=
###################################################################################
# WEB - Required
###################################################################################
# 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=
####################################################################################
# WEB - Optional
####################################################################################

View File

@@ -1,5 +1,5 @@
# Database
DB_HOSTNAME=immich_postgres_test
DB_HOSTNAME=immich-database-test
DB_USERNAME=postgres
DB_PASSWORD=postgres
DB_DATABASE_NAME=e2e_test

View File

@@ -70,6 +70,8 @@ services:
- ../web:/usr/src/app
- /usr/src/app/node_modules
restart: always
depends_on:
- immich-server
redis:
container_name: immich_redis

View File

@@ -1,8 +1,8 @@
version: "3.8"
services:
immich_server_test:
image: immich-server-dev:latest
immich-server-test:
image: immich-server-test
build:
context: ../server
dockerfile: Dockerfile
@@ -17,15 +17,17 @@ services:
environment:
- NODE_ENV=development
depends_on:
- redis
- database
redis:
container_name: immich_redis_test
- immich-redis-test
- immich-database-test
networks:
- immich-test-network
immich-redis-test:
container_name: immich-redis-test
image: redis:6.2
database:
container_name: immich_postgres_test
networks:
- immich-test-network
immich-database-test:
container_name: immich-database-test
image: postgres:14
env_file:
- .env.test
@@ -36,5 +38,8 @@ services:
PG_DATA: /var/lib/postgresql/data
volumes:
- /var/lib/postgresql/data
ports:
- 5432:5432
networks:
- immich-test-network
networks:
immich-test-network:

83
install.sh Executable file
View File

@@ -0,0 +1,83 @@
echo "Starting Immich installation..."
ip_address=$(hostname -I | awk '{print $1}')
RED='\033[0;31m'
GREEN='\032[0;31m'
NC='\033[0m' # No Color
machine_has() {
type "$1" >/dev/null 2>&1
}
create_immich_directory() {
echo "Creating Immich directory..."
mkdir -p ./immich-app/immich-data
}
download_docker_compose_file() {
echo "Downloading docker-compose.yml..."
curl -L https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml -o ./immich-app/docker-compose.yml >/dev/null 2>&1
}
download_dot_env_file() {
echo "Downloading .env file..."
curl -L https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example -o ./immich-app/.env >/dev/null 2>&1
}
populate_upload_location() {
echo "Populating default UPLOAD_LOCATION value..."
cd ./immich-app/immich-data
upload_location=$(pwd)
# Replace value of UPLOAD_LOCATION in .env with upload_location path
if [[ "$OSTYPE" == "darwin"* ]]; then
sed -i '' "s|UPLOAD_LOCATION=.*|UPLOAD_LOCATION=$upload_location|" ../.env
else
sed -i "s|UPLOAD_LOCATION=.*|UPLOAD_LOCATION=$upload_location|" ../.env
fi
cd ..
}
start_docker_compose() {
echo "Starting Immich's docker containers"
if machine_has "docker compose"; then {
docker compose up --remove-orphans -d
show_friendly_message
exit 0
}; fi
if machine_has "docker-compose"; then
docker-compose up --remove-orphans -d
show_friendly_message
exit 0
fi
}
show_friendly_message() {
echo "Succesfully deployed Immich!"
echo "You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api"
echo "The backup (or upload) location is $upload_location"
echo "---------------------------------------------------"
echo "If you want to confgure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.
1. First bring down the containers with the command 'docker-compose down' in the immich-app directory,
2. Then change the information that fits your needs in the '.env' file,
3. Finally, bring the containers back up with the command 'docker-compose up --remove-orphans -d' in the immich-app directory"
}
# MAIN
create_immich_directory
download_docker_compose_file
download_dot_env_file
populate_upload_location
start_docker_compose

View File

@@ -4,12 +4,16 @@ file_type: json
upload:
files:
- file: mobile/assets/i18n/en-US.json
locale_code: en
locale_code: en-US
- file: mobile/assets/i18n/de-DE.json
locale_code: de
locale_code: de-DE
- file: mobile/assets/i18n/fr-FR.json
locale_code: fr-FR
download:
files:
- file: mobile/assets/i18n/en-US.json
locale_code: en
locale_code: en-US
- file: mobile/assets/i18n/de-DE.json
locale_code: de
locale_code: de-DE
- file: mobile/assets/i18n/fr-FR.json
locale_code: fr-FR

View File

@@ -10,6 +10,7 @@ RUN apt-get update
RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
RUN npm ci
RUN npm rebuild @tensorflow/tfjs-node --build-from-source
COPY . .

File diff suppressed because it is too large Load Diff

View File

@@ -28,11 +28,11 @@
"@nestjs/typeorm": "^8.0.3",
"@tensorflow-models/coco-ssd": "^2.2.2",
"@tensorflow-models/mobilenet": "^2.1.0",
"@tensorflow/tfjs": "^3.15.0",
"@tensorflow/tfjs-converter": "^3.15.0",
"@tensorflow/tfjs-core": "^3.15.0",
"@tensorflow/tfjs-node": "^3.15.0",
"@tensorflow/tfjs-node-gpu": "^3.15.0",
"@tensorflow/tfjs": "^3.19.0",
"@tensorflow/tfjs-converter": "^3.19.0",
"@tensorflow/tfjs-core": "^3.19.0",
"@tensorflow/tfjs-node": "^3.19.0",
"@tensorflow/tfjs-node-gpu": "^3.19.0",
"@trpc/server": "^9.20.3",
"pg": "^8.7.3",
"reflect-metadata": "^0.1.13",

2
mobile/.gitignore vendored
View File

@@ -24,7 +24,7 @@
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
**/ios/
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies

View File

@@ -1,2 +1,2 @@
json_key_file("/Users/alex/Documents/immich-fastlane-googleplaystore-key.json") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("app.alextran.immich") # e.g. com.krausefx.app
json_key_file("/Users/alex/Documents/immich-play-store-key.json")
package_name("app.alextran.immich")

View File

@@ -16,10 +16,25 @@
default_platform(:android)
platform :android do
desc "Build Android"
lane :build do
gradle(
task: 'bundle',
build_type: 'Release',
)
end
desc "Update AAB to PlayStore"
lane :beta do
upload_to_play_store(track: 'beta', aab: '../build/app/outputs/bundle/release/app-release.aab')
desc "Build and Release Android"
lane :release do
gradle(
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 30,
"android.injected.version.name" => "1.20.0",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
end
end

View File

@@ -15,10 +15,10 @@ For _fastlane_ installation instructions, see [Installing _fastlane_](https://do
## Android
### android beta
### android release
```sh
[bundle exec] fastlane android beta
[bundle exec] fastlane android release
```
Update AAB to PlayStore

View File

@@ -0,0 +1 @@
* Refactored app to use OpenAPI SDK to improve performance and project structure.

View File

@@ -0,0 +1 @@
* Added other languages to app

View File

@@ -0,0 +1 @@
* Added French, Danish, Spanish, French, Japanese, Polish, and Finish translation to the app

View File

@@ -0,0 +1,2 @@
* New feature - Gallery view now enable with swipping action
* New feature - Add album feature

View File

@@ -5,14 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000318">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000204">
</testcase>
<testcase classname="fastlane.lanes" name="1: upload_to_play_store" time="111.253169">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="11.673502">
<failure message="/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:229:in `chdir&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:229:in `execute_action&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing&apos;&#10;Fastfile:22:in `block (2 levels) in parsing_binding&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/lane.rb:33:in `call&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:49:in `block in execute&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:45:in `chdir&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/runner.rb:45:in `execute&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/commands_generator.rb:109:in `block (2 levels) in run&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/commands_generator.rb:353:in `run&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/commands_generator.rb:42:in `start&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/fastlane/lib/fastlane/cli_tools_distributor.rb:122:in `take_off&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/gems/fastlane-2.204.3/bin/fastlane:23:in `&lt;top (required)&gt;&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/bin/fastlane:25:in `load&apos;&#10;/usr/local/Cellar/fastlane/2.204.3/libexec/bin/fastlane:25:in `&lt;main&gt;&apos;&#10;&#10;Google Api Error: Invalid request - APK specifies a version code that has already been used." />
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="37.162935">
</testcase>

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "EKSKLUDERET",
"album_info_card_backup_album_included": "INKLUDERET",
"album_viewer_appbar_share_delete": "Slet album",
"album_viewer_appbar_share_err_delete": "Fejlede sletning af album",
"album_viewer_appbar_share_err_leave": "Fejlede i at forlade album",
"album_viewer_appbar_share_err_remove": "Der er problemer med at fjerne elementer fra album",
"album_viewer_appbar_share_err_title": "Fejlede i at ændre albumtitel",
"album_viewer_appbar_share_leave": "Forlad album",
"album_viewer_appbar_share_remove": "Fjern fra album",
"album_viewer_page_share_add_users": "Tilføj brugere",
"backup_album_selection_page_albums_device": "Albummer på enhed ({})",
"backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere",
"backup_album_selection_page_assets_scatter": "Elementer kan være spredt på tværs af flere albummer. Albummer kan således inkluderes eller udelukkes under sikkerhedskopieringsprocessen.",
"backup_album_selection_page_select_albums": "Vælg albummer",
"backup_album_selection_page_selection_info": "Oplysninger om valgte",
"backup_album_selection_page_total_assets": "Samlede unikke elementer",
"backup_all": "Alt",
"backup_controller_page_albums": "Sikkerhedskopier albummer",
"backup_controller_page_backup": "Sikkerhedskopier",
"backup_controller_page_backup_selected": "Valgte: ",
"backup_controller_page_backup_sub": "Sikkerhedskopierede billeder og videoer",
"backup_controller_page_cancel": "Annuller",
"backup_controller_page_created": "Oprettet den: {}",
"backup_controller_page_desc_backup": "Slå sikkerhedskopiering til automatisk at uploade nye elementer til serveren.",
"backup_controller_page_excluded": "Ekskluderet: ",
"backup_controller_page_failed": "Felet ({})",
"backup_controller_page_filename": "Filnavn: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Sikkerhedskopieringsinformation",
"backup_controller_page_none_selected": "Ingen valgte",
"backup_controller_page_remainder": "Tilbageværende",
"backup_controller_page_remainder_sub": "Tilbageværende billeder og albummer, at sikkerhedskopiere, fra valgte",
"backup_controller_page_select": "Vælg",
"backup_controller_page_server_storage": "Serverlager",
"backup_controller_page_start_backup": "Start sikkerhedskopiering",
"backup_controller_page_status_off": "Sikkerhedskopiering er slået fra",
"backup_controller_page_status_on": "Sikkerhedskopiering er slået til",
"backup_controller_page_storage_format": "{} af {} brugt",
"backup_controller_page_to_backup": "Albummer at sikkerhedskopiere",
"backup_controller_page_total": "I alt",
"backup_controller_page_total_sub": "Alle unikke billeder og videoer fra valgte albummer",
"backup_controller_page_turn_off": "Slå sikkerhedskopiering fra",
"backup_controller_page_turn_on": "Slå sikkerhedskopiering til",
"backup_controller_page_uploading_file_info": "Uploader filinformation",
"backup_err_only_album": "Kan ikke slette det eneste album",
"backup_info_card_assets": "elementer",
"control_bottom_app_bar_delete": "Slet",
"create_shared_album_page_share": "Del",
"create_shared_album_page_share_add_assets": "TILFØJ ELEMENT",
"create_shared_album_page_share_select_photos": "Vælg billeder",
"daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM, yyyy",
"date_format": "E d. LLL y • hh:mm",
"delete_dialog_alert": "Disse elementer vil blive slettet permanent fra Immich og din enhed",
"delete_dialog_cancel": "Annuller",
"delete_dialog_ok": "Slet",
"delete_dialog_title": "Slet permanent",
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
"exif_bottom_sheet_details": "DETALJER",
"exif_bottom_sheet_location": "LOKATION",
"login_form_button_text": "Log ind",
"login_form_email_hint": "din-email@email.com",
"login_form_endpoint_hint": "http://din-server-ip:port/api",
"login_form_endpoint_url": "Server Endpoint URL",
"login_form_err_http": "Angiv venligst http:// eller https://",
"login_form_err_invalid_email": "Ugyldig email",
"login_form_err_leading_whitespace": "Mellemrum før",
"login_form_err_trailing_whitespace": "Mellemrum efter",
"login_form_failed_login": "Der opstod en vejl ved at logge ind. Tjek server URL, email og kodeordet",
"login_form_label_email": "Email",
"login_form_label_password": "Kodeord",
"login_form_password_hint": "kodeord",
"login_form_save_login": "Forbliv logget ind",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "Klient og server er ajour",
"profile_drawer_sign_out": "Log ud",
"search_bar_hint": "Søg i dine billeder",
"search_page_no_objects": "Ingen elementer er tilgængelige",
"search_page_no_places": "Ingen placeringsinformation er tilgængelig",
"search_page_places": "Steder",
"search_page_things": "Ting",
"search_result_page_new_search_hint": "Ny søgning",
"select_additional_user_for_sharing_page_suggestions": "Anbefalinger",
"select_user_for_sharing_page_err_album": "Fejlede i at oprette et nyt album",
"select_user_for_sharing_page_share_suggestions": "Anbefalinger",
"share_add": "Tilføj",
"share_add_photos": "Tilføj billeder",
"share_add_title": "Tilføj en titel",
"share_create_album": "Opret album",
"share_invite": "Inviter til album",
"sharing_page_album": "Delt albums",
"sharing_page_description": "Opret delte albummer for at dele billeder og video med personer på dit netværk.",
"sharing_page_empty_list": "TOM LISTE",
"sharing_silver_appbar_create_shared_album": "Opret delt album",
"sharing_silver_appbar_share_partner": "Del med partner",
"tab_controller_nav_photos": "Billeder",
"tab_controller_nav_search": "Søg",
"tab_controller_nav_sharing": "Deling",
"version_announcement_overlay_ack": "Vedkend",
"version_announcement_overlay_release_notes": "udgivelsesnoter",
"version_announcement_overlay_text_1": "Hej vej, der er en ny version af",
"version_announcement_overlay_text_2": "bresøg venligst ",
"version_announcement_overlay_text_3": " og sikker dig, at din dockercompose og .env-fil er opdateret, for at undgå fejlkonfiguration, specielt hvis u bruger WatchTowereller andre mekanisme, der automatisk opdaterer serverprogrammer.",
"version_announcement_overlay_title": "Ny serverversion er tilgængelig \uD83C\uDF89"
}

View File

@@ -67,20 +67,23 @@
"login_form_err_invalid_email": "Ungültige E-Mail",
"login_form_err_leading_whitespace": "Führendes Leerzichen",
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
"login_form_failed_login": "Fehler bei der Anmeldung, überprüfen Sie Server URL, E-Mail und Passwort",
"login_form_label_email": "E-Mail",
"login_form_label_password": "Passwort",
"login_form_password_hint": "password",
"login_form_password_hint": "Passwort",
"login_form_save_login": "Angemeldet bleiben",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
"profile_drawer_sign_out": "Abmelden",
"search_bar_hint": "Durchsuche deine Fotos",
"search_page_no_objects": "Keine Objektinformationen verfügbar",
"search_page_no_places": "Keine Informationen über Orte verfügbar",
"search_page_places": "Orte",
"search_page_things": "Dinge",
"search_result_page_new_search_hint": "Neue Suche",
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
"select_user_for_sharing_page_share_suggestions": "Vorschläge",
"share_add": "Hinzufügen",
"share_add_photos": "Fotos hinzufügen",
"share_add_title": "Titel hinzufügen",
@@ -100,4 +103,4 @@
"version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
"version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
"version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89"
}
}

View File

@@ -30,7 +30,7 @@
"backup_controller_page_info": "Backup Information",
"backup_controller_page_none_selected": "None selected",
"backup_controller_page_remainder": "Remainder",
"backup_controller_page_remainder_sub": "Remaining photos and albums to back up from selection",
"backup_controller_page_remainder_sub": "Remaining photos and videos to back up from selection",
"backup_controller_page_select": "Select",
"backup_controller_page_server_storage": "Server Storage",
"backup_controller_page_start_backup": "Start Backup",
@@ -47,6 +47,7 @@
"backup_info_card_assets": "assets",
"control_bottom_app_bar_delete": "Delete",
"create_shared_album_page_share": "Share",
"create_shared_album_page_create": "Create",
"create_shared_album_page_share_add_assets": "ADD ASSETS",
"create_shared_album_page_share_select_photos": "Select Photos",
"daily_title_text_date": "E, MMM dd",
@@ -67,15 +68,16 @@
"login_form_err_invalid_email": "Invalid Email",
"login_form_err_leading_whitespace": "Leading whitespace",
"login_form_err_trailing_whitespace": "Trailing whitespace",
"login_form_failed_login": "Error logging you in, check server url, email and password",
"login_form_label_email": "Email",
"login_form_label_password": "Password",
"login_form_password_hint": "password",
"login_form_save_login": "Stay logged in",
"login_form_failed_login": "Error logging you in, check server url, email and password",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
"profile_drawer_sign_out": "Sign Out",
"search_bar_hint": "Search your photos",
"search_page_no_objects": "No Objects Info Available",
"search_page_no_places": "No Places Info Available",
"search_page_places": "Places",
"search_page_things": "Things",
@@ -96,6 +98,7 @@
"tab_controller_nav_photos": "Photos",
"tab_controller_nav_search": "Search",
"tab_controller_nav_sharing": "Sharing",
"tab_controller_nav_library": "Library",
"version_announcement_overlay_ack": "Acknowledge",
"version_announcement_overlay_release_notes": "release notes",
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "EXCLUIDOS",
"album_info_card_backup_album_included": "INCLUIDOS",
"album_viewer_appbar_share_delete": "Eliminar álbum ",
"album_viewer_appbar_share_err_delete": "No ha podido eliminar el álbum",
"album_viewer_appbar_share_err_leave": "No ha podido dejar el álbum",
"album_viewer_appbar_share_err_remove": "Hay problemas para eliminar los activos del álbum",
"album_viewer_appbar_share_err_title": "Error al cambiar el título del álbum ",
"album_viewer_appbar_share_leave": "Abandonar álbum ",
"album_viewer_appbar_share_remove": "Eliminar del álbum ",
"album_viewer_page_share_add_users": "Añadir usuarios",
"backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({})",
"backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir",
"backup_album_selection_page_assets_scatter": "Los activos pueden dispersarse en varios álbumes. De este modo, los álbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.",
"backup_album_selection_page_select_albums": "Seleccionar Álbumes",
"backup_album_selection_page_selection_info": "Información sobre la Selección",
"backup_album_selection_page_total_assets": "Total de activos únicos",
"backup_all": "Todos",
"backup_controller_page_albums": "Álbumes de copia de seguridad",
"backup_controller_page_backup": "Copia de Seguridad",
"backup_controller_page_backup_selected": "Seleccionado:",
"backup_controller_page_backup_sub": "Copia de seguridad de fotos y vídeos",
"backup_controller_page_cancel": "Cancelar",
"backup_controller_page_created": "",
"backup_controller_page_desc_backup": "Active la copia de seguridad para cargar automáticamente los nuevos activos al servidor.",
"backup_controller_page_excluded": "Excluido:",
"backup_controller_page_failed": "",
"backup_controller_page_filename": "",
"backup_controller_page_id": "",
"backup_controller_page_info": "Información de la Copia de Seguridad",
"backup_controller_page_none_selected": "Ninguno seleccionado",
"backup_controller_page_remainder": "Remanente",
"backup_controller_page_remainder_sub": "Fotos y álbumes restantes para hacer una copia de seguridad de la selección",
"backup_controller_page_select": "Seleccionar",
"backup_controller_page_server_storage": "Almacenamiento en el servidor",
"backup_controller_page_start_backup": "Iniciar copia de seguridad",
"backup_controller_page_status_off": "La copia de seguridad está desactivada",
"backup_controller_page_status_on": "La copia de seguridad está activada",
"backup_controller_page_storage_format": "{} de {} usadas",
"backup_controller_page_to_backup": "Álbumes a respaldar",
"backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "Todas las fotos y vídeos únicos de los álbumes seleccionados",
"backup_controller_page_turn_off": "Apagar la copia de seguridad",
"backup_controller_page_turn_on": "Activar la copia de seguridad",
"backup_controller_page_uploading_file_info": "",
"backup_err_only_album": "No se puede eliminar el único álbum",
"backup_info_card_assets": "activos",
"control_bottom_app_bar_delete": "Eliminar",
"create_shared_album_page_share": "Compartir",
"create_shared_album_page_share_add_assets": "AÑADIR ACTIVOS",
"create_shared_album_page_share_select_photos": "Seleccionar Fotos",
"daily_title_text_date": "E dd, MMM",
"daily_title_text_date_year": "E dd de MMM, yyyy",
"date_format": "E d, LLL y • h:mm a",
"delete_dialog_alert": "Estos elementos serán eliminados permanentemente de Immich y de tu dispositivo",
"delete_dialog_cancel": "Cancelar",
"delete_dialog_ok": "Eliminar",
"delete_dialog_title": "Eliminar Permanentemente",
"exif_bottom_sheet_description": "Añadir Descripción...",
"exif_bottom_sheet_details": "DETALLES",
"exif_bottom_sheet_location": "LOCALZACIÓN",
"login_form_button_text": "Iniciar Sesión",
"login_form_email_hint": "tucorreo@correo.com",
"login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto/api",
"login_form_endpoint_url": "URL del servidor",
"login_form_err_http": "Por favor, especifique http:// o https://",
"login_form_err_invalid_email": "Correo electrónico no válido",
"login_form_err_leading_whitespace": "Espacio en blanco inicial",
"login_form_err_trailing_whitespace": "Espacio en blanco al final",
"login_form_failed_login": "",
"login_form_label_email": "Correo",
"login_form_label_password": "Contraseña",
"login_form_password_hint": "contraseña",
"login_form_save_login": "Mantener la sesión iniciada",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "El Cliente y el Servidor están actualizados",
"profile_drawer_sign_out": "Cerrar Sesión",
"search_bar_hint": "Busca tus fotos",
"search_page_no_objects": "",
"search_page_no_places": "No hay información de lugares disponibles",
"search_page_places": "Lugares",
"search_page_things": "Cosas",
"search_result_page_new_search_hint": "Nueva Busqueda",
"select_additional_user_for_sharing_page_suggestions": "Sugerencias",
"select_user_for_sharing_page_err_album": "Fallo al crear el álbum",
"select_user_for_sharing_page_share_suggestions": "",
"share_add": "Añadir",
"share_add_photos": "Añadir fotos",
"share_add_title": "Añadir un título",
"share_create_album": "Crear álbum",
"share_invite": "Invitar al álbum",
"sharing_page_album": "Álbumes compartidos",
"sharing_page_description": "Crea álbumes compartidos para compartir fotos y vídeos con las personas de tu red.",
"sharing_page_empty_list": "LISTA VACIA",
"sharing_silver_appbar_create_shared_album": "Crear un álbum compartido",
"sharing_silver_appbar_share_partner": "Compartir con el compañero",
"tab_controller_nav_photos": "Fotos",
"tab_controller_nav_search": "Buscar",
"tab_controller_nav_sharing": "Compartiendo",
"version_announcement_overlay_ack": "Reconocer",
"version_announcement_overlay_release_notes": "notas de versión",
"version_announcement_overlay_text_1": "Hola amigo, hay una nueva versión de",
"version_announcement_overlay_text_2": "tómese su tiempo para visitar la ",
"version_announcement_overlay_text_3": "y asegurate de que tu configuración de docker-compose y .env está actualizada para evitar cualquier desconfiguración, especialmente si utiliza WatchTower o cualquier mecanismo que se encargue de actualizar su aplicación de servidor automáticamente.",
"version_announcement_overlay_title": "Nueva versión del servidor disponible \uD83C\uDF89"
}

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "JÄTETTY POIS",
"album_info_card_backup_album_included": "SISÄLLYTETTY",
"album_viewer_appbar_share_delete": "Poista albumi",
"album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui",
"album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui",
"album_viewer_appbar_share_err_remove": "Ongelmia kohteiden poistamisessa albumista",
"album_viewer_appbar_share_err_title": "Albumin nimen muuttaminen epäonnistui",
"album_viewer_appbar_share_leave": "Poistu albumista",
"album_viewer_appbar_share_remove": "Poista albumista",
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
"backup_album_selection_page_albums_device": "Laitteen albumit ({})",
"backup_album_selection_page_albums_tap": "Napauta sisällyttääksesi, kaksoisnapauta jättääksesi pois",
"backup_album_selection_page_assets_scatter": "Kohteet voivat olla hajaantuneina useisiin albumeihin. Albumeita voidaan sisällyttää varmuuskopiointiin tai jättää siitä pois.",
"backup_album_selection_page_select_albums": "Valitse albumit",
"backup_album_selection_page_selection_info": "Valintatiedot",
"backup_album_selection_page_total_assets": "Uniikkeja kohteita yhteensä",
"backup_all": "Kaikki",
"backup_controller_page_albums": "Varmuuskopioi albumit",
"backup_controller_page_backup": "Varmuuskopioitu",
"backup_controller_page_backup_selected": "Valittu:",
"backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot",
"backup_controller_page_cancel": "Peruuta",
"backup_controller_page_created": "Luotu: {}",
"backup_controller_page_desc_backup": "Kytke varmuuskopiointi päälle ladataksesi uudet kohteet palvelimelle automaattisesti.",
"backup_controller_page_excluded": "Jätetty pois:",
"backup_controller_page_failed": "Epäonnistui ({})",
"backup_controller_page_filename": "Tiedoston nimi: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Varmuuskopioinnin tiedot",
"backup_controller_page_none_selected": "Ei mitään",
"backup_controller_page_remainder": "Jäljellä",
"backup_controller_page_remainder_sub": "Varmuuskopiointia odottavat kuvat ja videot",
"backup_controller_page_select": "Valitse",
"backup_controller_page_server_storage": "Palvelimen tallennustila",
"backup_controller_page_start_backup": "Aloita varmuuskopiointi",
"backup_controller_page_status_off": "Varmuuskopiointi on pois päältä",
"backup_controller_page_status_on": "Varmuuskopiointi on päällä",
"backup_controller_page_storage_format": "{} / {} käytetty",
"backup_controller_page_to_backup": "Varmuuskopioitavat albumit",
"backup_controller_page_total": "Yhteensä",
"backup_controller_page_total_sub": "Kaikki uniikit kuvat ja videot valituista albumeista",
"backup_controller_page_turn_off": "Varmuuskopiointi pois päältä",
"backup_controller_page_turn_on": "Varmuuskopiointi päälle",
"backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot",
"backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna",
"backup_info_card_assets": "kohdetta",
"control_bottom_app_bar_delete": "Poista",
"create_shared_album_page_share": "Jaa",
"create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA",
"create_shared_album_page_share_select_photos": "Valitse kuvat",
"daily_title_text_date": "",
"daily_title_text_date_year": "",
"date_format": "",
"delete_dialog_alert": "Nämä kohteet poistetaan pysyvästi Immich:stä ja laitteeltasi",
"delete_dialog_cancel": "Peruuta",
"delete_dialog_ok": "Poista",
"delete_dialog_title": "Poista pysyvästi",
"exif_bottom_sheet_description": "Lisää kuvaus…",
"exif_bottom_sheet_details": "TIEDOT",
"exif_bottom_sheet_location": "SIJAINTI",
"login_form_button_text": "Kirjaudu",
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
"login_form_endpoint_hint": "http://palvelimesi-osoite:portti/api",
"login_form_endpoint_url": "Palvelimen URL",
"login_form_err_http": "Lisää http:// tai https://",
"login_form_err_invalid_email": "Virheellinen sähköpostiosoite",
"login_form_err_leading_whitespace": "Alussa välilyönti",
"login_form_err_trailing_whitespace": "Lopussa välilyönti",
"login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähköpostiosoite ja salasana.",
"login_form_label_email": "Sähköposti",
"login_form_label_password": "Salasana",
"login_form_password_hint": "salasana",
"login_form_save_login": "Pysy kirjautuneena",
"monthly_title_text_date_format": "",
"profile_drawer_client_server_up_to_date": "Asiakassovellus ja palvelin ovat ajan tasalla",
"profile_drawer_sign_out": "Kirjaudu ulos",
"search_bar_hint": "Etsi kuvia",
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
"search_page_no_places": "Paikkatietoja ei ole saatavilla",
"search_page_places": "Paikat",
"search_page_things": "Asiat",
"search_result_page_new_search_hint": "Uusi haku",
"select_additional_user_for_sharing_page_suggestions": "Ehdotukset",
"select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui",
"select_user_for_sharing_page_share_suggestions": "Ehdotukset",
"share_add": "Lisää",
"share_add_photos": "Lisää kuvia",
"share_add_title": "Lisää nimi",
"share_create_album": "Luo albumi",
"share_invite": "Kutsu albumiin",
"sharing_page_album": "Jaetut albumit",
"sharing_page_description": "Luo jaettuja albumeja jakaaksesi kuvia ja videoita läheisillesi.",
"sharing_page_empty_list": "TYHJÄ LISTA",
"sharing_silver_appbar_create_shared_album": "Luo jaettu albumi",
"sharing_silver_appbar_share_partner": "Jaa kumppanille",
"tab_controller_nav_photos": "Kuvat",
"tab_controller_nav_search": "Haku",
"tab_controller_nav_sharing": "Jakaminen",
"version_announcement_overlay_ack": "Tiedostan",
"version_announcement_overlay_release_notes": "julkaisutiedoissa",
"version_announcement_overlay_text_1": "Hei, kaveri! Uusi palvelinversio on saatavilla sovelluksesta",
"version_announcement_overlay_text_2": "Ota hetki aikaa vieraillaksesi",
"version_announcement_overlay_text_3": "ja varmista, että käyttämäsi docker-compose ja .env-asetukset ovat ajantasalla välttyäksesi asetusongelmilta. Varsinkin jos käytät WatchToweria tai jotain muuta mekanismia päivittääksesi palvelinsovellusta automaattisesti.",
"version_announcement_overlay_title": "Uusi palvelinversio saatavilla \uD83C\uDF89"
}

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "EXCLU",
"album_info_card_backup_album_included": "INCLUS",
"album_viewer_appbar_share_delete": "Supprimer l'album",
"album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album",
"album_viewer_appbar_share_err_leave": "Impossible de quitter l'album",
"album_viewer_appbar_share_err_remove": "Il y a des problèmes pour retirer les éléments de l'album",
"album_viewer_appbar_share_err_title": "Échec de la modification du titre de l'album",
"album_viewer_appbar_share_leave": "Quitter l'album",
"album_viewer_appbar_share_remove": "Retirer de l'album",
"album_viewer_page_share_add_users": "Ajouter des utilisateurs",
"backup_album_selection_page_albums_device": "Albums sur l'appareil ({})",
"backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure",
"backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.",
"backup_album_selection_page_select_albums": "Sélectionner les albums",
"backup_album_selection_page_selection_info": "Informations sur la sélection",
"backup_album_selection_page_total_assets": "Total des éléments uniques",
"backup_all": "Tout",
"backup_controller_page_albums": "Sauvegarder les albums",
"backup_controller_page_backup": "Sauvegardé",
"backup_controller_page_backup_selected": "Sélectionné : ",
"backup_controller_page_backup_sub": "Photos et vidéos sauvegardées",
"backup_controller_page_cancel": "Annuler",
"backup_controller_page_created": "Créé le : {}",
"backup_controller_page_desc_backup": "Activez la sauvegarde pour envoyer automatiquement les nouveaux éléments sur le serveur.",
"backup_controller_page_excluded": "Exclus : ",
"backup_controller_page_failed": "Échec de l'opération ({})",
"backup_controller_page_filename": "Nom du fichier : {} [{}]",
"backup_controller_page_id": "ID : {}",
"backup_controller_page_info": "Informations de sauvegarde",
"backup_controller_page_none_selected": "Aucune sélection",
"backup_controller_page_remainder": "Restant",
"backup_controller_page_remainder_sub": "Photos et albums restants à sauvegarder à partir de la sélection",
"backup_controller_page_select": "Sélectionner",
"backup_controller_page_server_storage": "Stockage du serveur",
"backup_controller_page_start_backup": "Démarrer la sauvegarde",
"backup_controller_page_status_off": "La sauvegarde est désactivée",
"backup_controller_page_status_on": "La sauvegarde est activée",
"backup_controller_page_storage_format": "{} de {} utilisé",
"backup_controller_page_to_backup": "Albums à sauvegarder",
"backup_controller_page_total": "Total",
"backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés",
"backup_controller_page_turn_off": "Désactiver la sauvegarde",
"backup_controller_page_turn_on": "Activer la sauvegarde",
"backup_controller_page_uploading_file_info": "Envoi d'informations sur le fichier",
"backup_err_only_album": "Impossible de retirer le seul album",
"backup_info_card_assets": "éléments",
"control_bottom_app_bar_delete": "Supprimer",
"create_shared_album_page_share": "Partager",
"create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS",
"create_shared_album_page_share_select_photos": "Sélectionner les photos",
"daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "Ces éléments seront définitivement supprimés de Immich et de votre appareil.",
"delete_dialog_cancel": "Annuler",
"delete_dialog_ok": "Supprimer",
"delete_dialog_title": "Supprimer définitivement",
"exif_bottom_sheet_description": "Ajouter une description...",
"exif_bottom_sheet_details": "DÉTAILS",
"exif_bottom_sheet_location": "LOCALISATION",
"login_form_button_text": "Connexion",
"login_form_email_hint": "votreemail@email.com",
"login_form_endpoint_hint": "http://adresse-ip-serveur:port/api",
"login_form_endpoint_url": "URL du point d'accès au serveur",
"login_form_err_http": "Veuillez préciser http:// ou https://",
"login_form_err_invalid_email": "Email invalide",
"login_form_err_leading_whitespace": "Espace en début de ligne",
"login_form_err_trailing_whitespace": "Espace de fin de ligne",
"login_form_failed_login": "Erreur de connexion, vérifiez l'url du serveur, l'email et le mot de passe",
"login_form_label_email": "Email",
"login_form_label_password": "Mot de passe",
"login_form_password_hint": "mot de passe",
"login_form_save_login": "Rester connecté",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour",
"profile_drawer_sign_out": "Se déconnecter",
"search_bar_hint": "Rechercher vos photos",
"search_page_no_objects": "Aucune information disponible sur les objets",
"search_page_no_places": "Aucune information disponible sur la localisation",
"search_page_places": "Lieux",
"search_page_things": "Objets",
"search_result_page_new_search_hint": "Nouvelle recherche",
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
"select_user_for_sharing_page_err_album": "Échec de la création de l'album",
"select_user_for_sharing_page_share_suggestions": "Suggestions",
"share_add": "Ajouter",
"share_add_photos": "Ajouter des photos",
"share_add_title": "Ajouter un titre",
"share_create_album": "Créer un album",
"share_invite": "Inviter à l'album",
"sharing_page_album": "Albums partagés",
"sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.",
"sharing_page_empty_list": "LISTE VIDE",
"sharing_silver_appbar_create_shared_album": "Créer un album partagé",
"sharing_silver_appbar_share_partner": "Partager avec un partenaire",
"tab_controller_nav_photos": "Photos",
"tab_controller_nav_search": "Recherche",
"tab_controller_nav_sharing": "Partage",
"version_announcement_overlay_ack": "Confirmer",
"version_announcement_overlay_release_notes": "notes de mise à jour",
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
"version_announcement_overlay_text_2": "veuillez prendre le temps de visiter le ",
"version_announcement_overlay_text_3": " et assurez-vous que votre configuration docker-compose et .env est à jour pour éviter toute erreur de configuration, en particulier si vous utilisez WatchTower ou tout autre mécanisme qui gère la mise à jour automatique de votre application serveur.",
"version_announcement_overlay_title": "Nouvelle version serveur disponible \uD83C\uDF89"
}

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "ESCLUSI",
"album_info_card_backup_album_included": "INCLUSI",
"album_viewer_appbar_share_delete": "Elimina album ",
"album_viewer_appbar_share_err_delete": "Fallito nel cancellare l'album ",
"album_viewer_appbar_share_err_leave": "Fallito nel lasciare l'album ",
"album_viewer_appbar_share_err_remove": "Ci sono problemi nel rimuovere oggetti dall'album ",
"album_viewer_appbar_share_err_title": "Fallito nel cambiare titolo dell'album ",
"album_viewer_appbar_share_leave": "Lascia l'album",
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
"album_viewer_page_share_add_users": "Aggiungi utenti",
"backup_album_selection_page_albums_device": "Albums nel device ({})",
"backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere.",
"backup_album_selection_page_assets_scatter": "Stesse immagini e video possono trovarsi tra più album, così gli album possono essere inclusi o esclusi dal backup.",
"backup_album_selection_page_select_albums": "Seleziona gli album",
"backup_album_selection_page_selection_info": "Informazioni sulla selezione ",
"backup_album_selection_page_total_assets": "Numero totale di oggetti unici",
"backup_all": "Tutti",
"backup_controller_page_albums": "Backup album",
"backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Selezionati:",
"backup_controller_page_backup_sub": "Photo e video salvati",
"backup_controller_page_cancel": "Cancella ",
"backup_controller_page_created": "Creato il: {}",
"backup_controller_page_desc_backup": "Attiva il backup automatico per eseguire upload sul server",
"backup_controller_page_excluded": "Esclusi:",
"backup_controller_page_failed": "Falliti: ({})",
"backup_controller_page_filename": "Nome del file: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Informazioni sul backup",
"backup_controller_page_none_selected": "Nessuna selezione",
"backup_controller_page_remainder": "Promemoria ",
"backup_controller_page_remainder_sub": "Photo e album selezionati che rimangono da salvare",
"backup_controller_page_select": "Seleziona ",
"backup_controller_page_server_storage": "Spazio nel server",
"backup_controller_page_start_backup": "Inizia backup ",
"backup_controller_page_status_off": "Backup è disattivato ",
"backup_controller_page_status_on": "Backup è attivato",
"backup_controller_page_storage_format": "{} di {} usati",
"backup_controller_page_to_backup": "Album da salvare",
"backup_controller_page_total": "Totale",
"backup_controller_page_total_sub": "Tutte le foto e i video unici salvati dagli album selezionati ",
"backup_controller_page_turn_off": "Disattiva backup",
"backup_controller_page_turn_on": "Attiva backup ",
"backup_controller_page_uploading_file_info": "Info sul file caricato",
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
"backup_info_card_assets": "Oggetti ",
"control_bottom_app_bar_delete": "Elimina",
"create_shared_album_page_share": "Condividi",
"create_shared_album_page_share_add_assets": "AGGIUNGI OGGETTI",
"create_shared_album_page_share_select_photos": "Seleziona foto",
"daily_title_text_date": "E, dd MMM",
"daily_title_text_date_year": "E, dd MMM, yyyy",
"date_format": "E, d LLL, y • hh:mm",
"delete_dialog_alert": "Questi oggetti saranno cancellati permanentemente da Immich e dal tuo device",
"delete_dialog_cancel": "Annulla",
"delete_dialog_ok": "Elimina",
"delete_dialog_title": "Cancella in modo permanente ",
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
"exif_bottom_sheet_details": "DETTAGLI",
"exif_bottom_sheet_location": "POSIZIONE",
"login_form_button_text": "Accedi",
"login_form_email_hint": "tuaemail@email.com",
"login_form_endpoint_hint": "http://tuo-ip-del-server:port/api",
"login_form_endpoint_url": "URL del Server Endpoint",
"login_form_err_http": "Per favore specificare http:// o https://",
"login_form_err_invalid_email": "Email non valida",
"login_form_err_leading_whitespace": "Spazio bianco all'inizio ",
"login_form_err_trailing_whitespace": "Spazio bianco alla fine",
"login_form_failed_login": "Errore nel login, controlla URL del server e le credenziali (email e password)",
"login_form_label_email": "Email",
"login_form_label_password": "Password",
"login_form_password_hint": "password ",
"login_form_save_login": "Rimani connesso ",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "Client e server sono aggiornati",
"profile_drawer_sign_out": "Esci",
"search_bar_hint": "Cerca le tue foto",
"search_page_no_objects": "Nessuna Informazione relativa all'Oggetto Disponibile",
"search_page_no_places": "Nessun informazione sulla posizione ",
"search_page_places": "Luoghi",
"search_page_things": "Oggetti",
"search_result_page_new_search_hint": "Nuova ricerca ",
"select_additional_user_for_sharing_page_suggestions": "Suggerimenti ",
"select_user_for_sharing_page_err_album": "Fallito nel creare l'album ",
"select_user_for_sharing_page_share_suggestions": "Suggerimenti",
"share_add": "Aggiungi",
"share_add_photos": "Aggiungi foto",
"share_add_title": "Aggiungi un titolo ",
"share_create_album": "Crea album",
"share_invite": "Invitare all'album ",
"sharing_page_album": "Album condivisi",
"sharing_page_description": "Crea un album condiviso per condividere foto e video con gente nel tuo network",
"sharing_page_empty_list": "LISTA VUOTA",
"sharing_silver_appbar_create_shared_album": "Crea album condiviso",
"sharing_silver_appbar_share_partner": "Condividi con il partner",
"tab_controller_nav_photos": "Foto",
"tab_controller_nav_search": "Cerca",
"tab_controller_nav_sharing": "Condividi",
"version_announcement_overlay_ack": "Riconosci ",
"version_announcement_overlay_release_notes": "le note di rilascio ",
"version_announcement_overlay_text_1": "Ciao amico, c'è una nuova versione di",
"version_announcement_overlay_text_2": "prova a controllare ",
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore nella configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico delle immagini docker.",
"version_announcement_overlay_title": "Nuova versione di server disponibile! \uD83C\uDF89"
}

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "除外",
"album_info_card_backup_album_included": "選択",
"album_viewer_appbar_share_delete": "アルバムを削除",
"album_viewer_appbar_share_err_delete": "削除に失敗...",
"album_viewer_appbar_share_err_leave": "退会に失敗...",
"album_viewer_appbar_share_err_remove": "アルバムから写真を除外する際にエラー発生",
"album_viewer_appbar_share_err_title": "タイトルの変更に失敗...",
"album_viewer_appbar_share_leave": "アルバムから退会",
"album_viewer_appbar_share_remove": "アルバムから除外",
"album_viewer_page_share_add_users": "ユーザーを追加",
"backup_album_selection_page_albums_device": "端末上のアルバム数は {} だよ",
"backup_album_selection_page_albums_tap": "タップで選択、ダブルタップで除外だよ",
"backup_album_selection_page_assets_scatter": "写真がいろんなアルバムに登録されてる事があるから、アルバムを含めたり除外したりしてどの写真を保存するか選択できるよ。",
"backup_album_selection_page_select_albums": "アルバムを選択",
"backup_album_selection_page_selection_info": "選択、又は除外されてるアルバム",
"backup_album_selection_page_total_assets": "選択されたアルバムの写真と動画の数",
"backup_all": "全て",
"backup_controller_page_albums": "アルバム",
"backup_controller_page_backup": "バックアップ",
"backup_controller_page_backup_selected": "選択されてる:",
"backup_controller_page_backup_sub": "バックアップされた写真と動画の数だよ",
"backup_controller_page_cancel": "キャンセルするよ",
"backup_controller_page_created": "{} に作成されたよ",
"backup_controller_page_desc_backup": "ONにすれば自動的に新しい写真などがバックアップされるようになるよ",
"backup_controller_page_excluded": "除外されてるアルバム:",
"backup_controller_page_failed": "失敗: ({})",
"backup_controller_page_filename": "ファイル名: {} [{}] ",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "バックアップ情報",
"backup_controller_page_none_selected": "何も選んでないよ",
"backup_controller_page_remainder": "リマインダー",
"backup_controller_page_remainder_sub": "残りの写真と動画の数だよ",
"backup_controller_page_select": "選択",
"backup_controller_page_server_storage": "サーバーの容量",
"backup_controller_page_start_backup": "バックアップを開始するよ",
"backup_controller_page_status_off": "バックアップがOFFになってるよ",
"backup_controller_page_status_on": "バックアップがONになってるよ",
"backup_controller_page_storage_format": "{}中、 {}を使用中だよ",
"backup_controller_page_to_backup": "バックアップされるアルバム",
"backup_controller_page_total": "トータル",
"backup_controller_page_total_sub": "選択されたアルバムの写真と動画の数だよ",
"backup_controller_page_turn_off": "バックアップOFF",
"backup_controller_page_turn_on": "バックアップON",
"backup_controller_page_uploading_file_info": "アップロードされてるファイルに関する情報",
"backup_err_only_album": "唯一のアルバムを除外する事はできないよ",
"backup_info_card_assets": "写真と動画",
"control_bottom_app_bar_delete": "削除",
"create_shared_album_page_share": "共有",
"create_shared_album_page_share_add_assets": "写真や動画を追加",
"create_shared_album_page_share_select_photos": "写真を選択",
"daily_title_text_date": "E, MM月 dd日",
"daily_title_text_date_year": "E, yyyy年 MM月 dd日",
"date_format": "E, MM月 dd日 • hh時mm分",
"delete_dialog_alert": "サーバーからも端末からも永久的に削除されるけど良いの?",
"delete_dialog_cancel": "キャンセル",
"delete_dialog_ok": "削除",
"delete_dialog_title": "永久的に削除",
"exif_bottom_sheet_description": "概要を追加",
"exif_bottom_sheet_details": "詳細な情報",
"exif_bottom_sheet_location": "撮影地",
"login_form_button_text": "ログイン",
"login_form_email_hint": "example@email.com",
"login_form_endpoint_hint": "https://example.com:port/api",
"login_form_endpoint_url": "サーバーエンドポイントURL",
"login_form_err_http": "http://かhttps://かを指定してね",
"login_form_err_invalid_email": "メールアドレスが有効じゃないよ",
"login_form_err_leading_whitespace": "最初に半角スペースが含まれてるよ",
"login_form_err_trailing_whitespace": "最後に半角スペースが含まれてるよ",
"login_form_failed_login": "ログインエラー。サーバーのURL、メールアドレスとパスワードを再確認してね",
"login_form_label_email": "メールアドレス",
"login_form_label_password": "パスワード",
"login_form_password_hint": "パスワード",
"login_form_save_login": "ログインしたままにする",
"monthly_title_text_date_format": "yyyy年 MM月",
"profile_drawer_client_server_up_to_date": "サーバーとクライアント、両方最新バージョンだよ",
"profile_drawer_sign_out": "サインアウト",
"search_bar_hint": "写真を検索",
"search_page_no_objects": "被写体に関するデータがないよ",
"search_page_no_places": "場所に関するデータがないよ",
"search_page_places": "撮影地",
"search_page_things": "カテゴリ",
"search_result_page_new_search_hint": "検索",
"select_additional_user_for_sharing_page_suggestions": "ユーザーリスト",
"select_user_for_sharing_page_err_album": "アルバム作成に失敗...",
"select_user_for_sharing_page_share_suggestions": "ユーザーの一覧だよ",
"share_add": "追加",
"share_add_photos": "写真を追加",
"share_add_title": "タイトルを追加",
"share_create_album": "アルバムを作成",
"share_invite": "アルバムに参加",
"sharing_page_album": "共有アルバム",
"sharing_page_description": "共有アルバムを作成して同じネットワークにいる仲間に写真を共有してみよう!",
"sharing_page_empty_list": "誰も居ないね ( T_T)(^-^ ) ドンマイ",
"sharing_silver_appbar_create_shared_album": "共有アルバムを作成",
"sharing_silver_appbar_share_partner": "パートナーと共有",
"tab_controller_nav_photos": "写真",
"tab_controller_nav_search": "検索",
"tab_controller_nav_sharing": "共有",
"version_announcement_overlay_ack": "了解",
"version_announcement_overlay_release_notes": "更新情報",
"version_announcement_overlay_text_1": "こんにちは、又はこんばんは!新しい",
"version_announcement_overlay_text_2": "のバージョンが公開中だよ。",
"version_announcement_overlay_text_3": "を確認してみてね。あと、docker-composeや.envファイルが最新の状態に更新されてか、特にWatchTowerなどのツールを使ってDockerイメージを自動アップデートしてる人は確認してね",
"version_announcement_overlay_title": "新しいバージョン、公開中\uD83C\uDF89"
}

View File

@@ -0,0 +1,106 @@
{
"album_info_card_backup_album_excluded": "WYKLUCZONE",
"album_info_card_backup_album_included": "WŁĄCZONE",
"album_viewer_appbar_share_delete": "Usuń album",
"album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu",
"album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu",
"album_viewer_appbar_share_err_remove": "Wystąpiły problemy z usunięciem plików z albumu",
"album_viewer_appbar_share_err_title": "Nie udało się zmienić tytułu albumu",
"album_viewer_appbar_share_leave": "Opuść album",
"album_viewer_appbar_share_remove": "Usuń z albumu",
"album_viewer_page_share_add_users": "Dodaj użytkowników",
"backup_album_selection_page_albums_device": "Albumy na urządzeniu ({})",
"backup_album_selection_page_albums_tap": "Stuknij, aby włączyć, stuknij dwukrotnie, aby wykluczyć",
"backup_album_selection_page_assets_scatter": "Pliki mogą być rozproszone w wielu albumach. Dzięki temu albumy mogą być włączane lub wyłączane podczas procesu tworzenia kopii zapasowej.",
"backup_album_selection_page_select_albums": "Zaznacz albumy",
"backup_album_selection_page_selection_info": "Info o wyborze",
"backup_album_selection_page_total_assets": "Łącznie unikalnych plików",
"backup_all": "Wszystkie",
"backup_controller_page_albums": "Backup Albumów",
"backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Zaznaczone: ",
"backup_controller_page_backup_sub": "Tworzenie kopii zapasowych zdjęć i filmów",
"backup_controller_page_cancel": "Anuluj",
"backup_controller_page_created": "Utworzony na: {}",
"backup_controller_page_desc_backup": "Włącz backup, aby automatycznie przesyłać nowe zasoby na serwer.",
"backup_controller_page_excluded": "Wykluczone: ",
"backup_controller_page_failed": "Nieudane ({})",
"backup_controller_page_filename": "Nazwa pliku: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Informacje o kopii zapasowej",
"backup_controller_page_none_selected": "Brak wybranych",
"backup_controller_page_remainder": "Reszta",
"backup_controller_page_remainder_sub": "Pozostałe zdjęcia i albumy do wykonania kopii zapasowej z wyboru",
"backup_controller_page_select": "Zaznacz",
"backup_controller_page_server_storage": "Pamięć Serwera",
"backup_controller_page_start_backup": "Rozpocznij Backup",
"backup_controller_page_status_off": "Backup jest wyłączony",
"backup_controller_page_status_on": "Backup jest włączony",
"backup_controller_page_storage_format": "{} z {} wykorzystanych",
"backup_controller_page_to_backup": "Albumy do backupu",
"backup_controller_page_total": "Łącznie",
"backup_controller_page_total_sub": "Wszystkie unikalne zdjęcia i filmy z wybranych albumów",
"backup_controller_page_turn_off": "Wyłącz Backup",
"backup_controller_page_turn_on": "Włącz Backup",
"backup_controller_page_uploading_file_info": "Przesyłanie informacji o pliku",
"backup_err_only_album": "Nie można usunąć tylko i wyłącznie albumu",
"backup_info_card_assets": "pliki",
"control_bottom_app_bar_delete": "Usuń",
"create_shared_album_page_share": "Udostępnij",
"create_shared_album_page_share_add_assets": "DODAJ PLIKI",
"create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia",
"daily_title_text_date": "E, MMM dd",
"daily_title_text_date_year": "E, MMM dd, yyyy",
"date_format": "E, LLL d, y • h:mm a",
"delete_dialog_alert": "Te elementy zostaną trwale usunięte z Immich i z Twojego urządzenia",
"delete_dialog_cancel": "Anuluj",
"delete_dialog_ok": "Usuń",
"delete_dialog_title": "Usuń trwale",
"exif_bottom_sheet_description": "Dodaj opis...",
"exif_bottom_sheet_details": "SZCZEGÓŁY",
"exif_bottom_sheet_location": "LOKALIZACJA",
"login_form_button_text": "Login",
"login_form_email_hint": "twojmail@email.com",
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",
"login_form_endpoint_url": "URL Serwera",
"login_form_err_http": "Proszę określić http:// lub https://",
"login_form_err_invalid_email": "Niepoprawny emaill",
"login_form_err_leading_whitespace": "Białe znaki",
"login_form_err_trailing_whitespace": "Białe znaki po przecinku",
"login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło.",
"login_form_label_email": "Email",
"login_form_label_password": "Hasło",
"login_form_password_hint": "hasło",
"login_form_save_login": "Pozostań zalogowany",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne",
"profile_drawer_sign_out": "Wyloguj się",
"search_bar_hint": "Szukaj swoich zdjęć",
"search_page_no_objects": "Brak informacji o obiektach",
"search_page_no_places": "Brak informacji o miejscu",
"search_page_places": "Miejsca",
"search_page_things": "Rzeczy",
"search_result_page_new_search_hint": "Nowe wyszukiwanie",
"select_additional_user_for_sharing_page_suggestions": "Propozycje",
"select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu",
"select_user_for_sharing_page_share_suggestions": "Propozycje",
"share_add": "Dodaj",
"share_add_photos": "Dodaj zdjęcia",
"share_add_title": "Dodaj tytuł",
"share_create_album": "Utwórz album",
"share_invite": "Zaproś do albumu",
"sharing_page_album": "Udostępnione albumy",
"sharing_page_description": "Twórz wspóldzielone albumy, aby udostępniać zdjęcia i filmy osobom w sieci.",
"sharing_page_empty_list": "PUSTA LISTA",
"sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album",
"sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi",
"tab_controller_nav_photos": "Zdjęcia",
"tab_controller_nav_search": "Szukaj",
"tab_controller_nav_sharing": "Udostępnianie",
"version_announcement_overlay_ack": "Potwierdzenie",
"version_announcement_overlay_release_notes": "informacje o wydaniu",
"version_announcement_overlay_text_1": "Cześć przyjacielu, jest nowe wydanie",
"version_announcement_overlay_text_2": "prosimy o poświęcenie czasu na odwiedzenie ",
"version_announcement_overlay_text_3": " i upewnij się, że twoja konfiguracja docker-compose i .env jest aktualna, aby zapobiec błędnym konfiguracjom, zwłaszcza jeśli używasz WatchTower lub dowolnego mechanizmu, który obsługuje automatyczną aktualizację aplikacji serwera.",
"version_announcement_overlay_title": "Nowa wersja serwera dostępna \uD83C\uDF89"
}

View File

@@ -19,6 +19,8 @@ PODS:
- Flutter
- FlutterMacOS
- SAMKeychain (1.5.3)
- shared_preferences_ios (0.0.1):
- Flutter
- sqflite (0.0.2):
- Flutter
- FMDB (>= 2.7.5)
@@ -38,6 +40,7 @@ DEPENDENCIES:
- 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`)
- shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/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`)
@@ -64,6 +67,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_ios/ios"
photo_manager:
:path: ".symlinks/plugins/photo_manager/ios"
shared_preferences_ios:
:path: ".symlinks/plugins/shared_preferences_ios/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios:
@@ -83,6 +88,7 @@ SPEC CHECKSUMS:
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de

View File

@@ -360,7 +360,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 14;
CURRENT_PROJECT_VERSION = 38;
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 = 14;
CURRENT_PROJECT_VERSION = 38;
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 = 14;
CURRENT_PROJECT_VERSION = 38;
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>1.10.0</string>
<string>1.20.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>14</string>
<string>38</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
@@ -87,6 +87,13 @@
<array>
<string>en</string>
<string>de</string>
<string>da</string>
<string>es</string>
<string>fr</string>
<string>it</string>
<string>fi</string>
<string>ja</string>
<string>pl</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.18.0"
version_number: "1.20.0"
)
increment_build_number(
build_number: latest_testflight_build_number + 1,

View File

@@ -5,12 +5,34 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000946">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000213">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="16.3225">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.088407">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="22.635867">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.376681">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="91.762747">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="49.149884">
<failure message="/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:229:in `chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:229:in `execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing&apos;&#10;Fastfile:30:in `block (2 levels) in parsing_binding&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/lane.rb:33:in `call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:49:in `block in execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:45:in `chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/runner.rb:45:in `execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/commands_generator.rb:354:in `run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/commands_generator.rb:43:in `start&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/gems/fastlane-2.207.0/bin/fastlane:23:in `&lt;top (required)&gt;&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/bin/fastlane:25:in `load&apos;&#10;/opt/homebrew/Cellar/fastlane/2.207.0/libexec/bin/fastlane:25:in `&lt;main&gt;&apos;&#10;&#10;Error uploading ipa file: &#10; [Transporter Error Output]: ERROR ITMS-90186: Invalid Pre-Release Train. The train version &apos;1.19.0&apos; is closed for new build submissions
&#10;[Transporter Error Output]: ERROR ITMS-90062: This bundle is invalid. The value for key CFBundleShortVersionString [1.19.0] in the Info.plist file must contain a higher version than that of the previously approved version [1.19.0]. Please find more information about CFBundleShortVersionString at https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring
&#10;[Transporter Error Output]: Return status of iTunes Transporter was 1: ERROR ITMS-90186: Invalid Pre-Release Train. The train version &apos;1.19.0&apos; is closed for new build submissions
\nERROR ITMS-90062: This bundle is invalid. The value for key CFBundleShortVersionString [1.19.0] in the Info.plist file must contain a higher version than that of the previously approved version [1.19.0]. Please find more information about CFBundleShortVersionString at https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring

View File

@@ -17,7 +17,6 @@ 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 {
@@ -43,7 +42,11 @@ void main() async {
// Default locale
Locale('en', 'US'),
// Additional locales
Locale('de', 'DE')
Locale('da', 'DK'),
Locale('de', 'DE'),
Locale('es', 'ES'),
Locale('fr', 'FR'),
Locale('it', 'IT'),
];
runApp(

View File

@@ -0,0 +1,40 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:openapi/api.dart';
class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
AlbumNotifier(this._albumService) : super([]);
final AlbumService _albumService;
getAllAlbums() async {
List<AlbumResponseDto>? albums =
await _albumService.getAlbums(isShared: false);
if (albums != null) {
state = albums;
}
}
deleteAlbum(String albumId) {
state = state.where((album) => album.id != albumId).toList();
}
Future<AlbumResponseDto?> createAlbum(
String albumTitle,
Set<AssetResponseDto> assets,
) async {
AlbumResponseDto? album =
await _albumService.createAlbum(albumTitle, assets, []);
if (album != null) {
state = [...state, album];
return album;
}
return null;
}
}
final albumProvider =
StateNotifierProvider<AlbumNotifier, List<AlbumResponseDto>>((ref) {
return AlbumNotifier(ref.watch(albumServiceProvider));
});

View File

@@ -1,7 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/models/album_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
import 'package:immich_mobile/modules/album/models/album_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
AlbumViewerNotifier(this.ref)
@@ -34,7 +34,7 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
String ownerId,
String newAlbumTitle,
) async {
SharedAlbumService service = ref.watch(sharedAlbumServiceProvider);
AlbumService service = ref.watch(albumServiceProvider);
bool isSuccess =
await service.changeTitleAlbum(albumId, ownerId, newAlbumTitle);

View File

@@ -1,5 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/models/asset_selection_state.model.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_state.model.dart';
import 'package:openapi/api.dart';

View File

@@ -1,30 +1,48 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:openapi/api.dart';
class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
SharedAlbumNotifier(this._sharedAlbumService) : super([]);
final SharedAlbumService _sharedAlbumService;
final AlbumService _sharedAlbumService;
Future<AlbumResponseDto?> createSharedAlbum(
String albumName,
Set<AssetResponseDto> assets,
List<String> sharedUserIds,
) async {
try {
var newAlbum = await _sharedAlbumService.createAlbum(
albumName,
assets,
sharedUserIds,
);
if (newAlbum != null) {
state = [...state, newAlbum];
}
return newAlbum;
} catch (e) {
debugPrint("Error createSharedAlbum ${e.toString()}");
return null;
}
}
getAllSharedAlbums() async {
List<AlbumResponseDto>? sharedAlbums =
await _sharedAlbumService.getAllSharedAlbum();
await _sharedAlbumService.getAlbums(isShared: true);
if (sharedAlbums != null) {
state = sharedAlbums;
}
}
Future<bool> deleteAlbum(String albumId) async {
var res = await _sharedAlbumService.deleteAlbum(albumId);
if (res) {
state = state.where((album) => album.id != albumId).toList();
return true;
} else {
return false;
}
deleteAlbum(String albumId) async {
state = state.where((album) => album.id != albumId).toList();
}
Future<bool> leaveAlbum(String albumId) async {
@@ -54,13 +72,12 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
final sharedAlbumProvider =
StateNotifierProvider<SharedAlbumNotifier, List<AlbumResponseDto>>((ref) {
return SharedAlbumNotifier(ref.watch(sharedAlbumServiceProvider));
return SharedAlbumNotifier(ref.watch(albumServiceProvider));
});
final sharedAlbumDetailProvider = FutureProvider.autoDispose
.family<AlbumResponseDto?, String>((ref, albumId) async {
final SharedAlbumService sharedAlbumService =
ref.watch(sharedAlbumServiceProvider);
final AlbumService sharedAlbumService = ref.watch(albumServiceProvider);
return await sharedAlbumService.getAlbumDetail(albumId);
});

View File

@@ -2,46 +2,47 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
final sharedAlbumServiceProvider = Provider(
(ref) => SharedAlbumService(
final albumServiceProvider = Provider(
(ref) => AlbumService(
ref.watch(apiServiceProvider),
),
);
class SharedAlbumService {
class AlbumService {
final ApiService _apiService;
SharedAlbumService(this._apiService);
Future<List<AlbumResponseDto>?> getAllSharedAlbum() async {
AlbumService(this._apiService);
Future<List<AlbumResponseDto>?> getAlbums({required bool isShared}) async {
try {
return await _apiService.albumApi.getAllAlbums(shared: true);
return await _apiService.albumApi
.getAllAlbums(shared: isShared ? isShared : null);
} catch (e) {
debugPrint("Error getAllSharedAlbum ${e.toString()}");
return null;
}
}
Future<bool> createSharedAlbum(
Future<AlbumResponseDto?> createAlbum(
String albumName,
Set<AssetResponseDto> assets,
List<String> sharedUserIds,
) async {
try {
_apiService.albumApi.createAlbum(
return await _apiService.albumApi.createAlbum(
CreateAlbumDto(
albumName: albumName,
assetIds: assets.map((asset) => asset.id).toList(),
sharedWithUserIds: sharedUserIds,
),
);
return true;
} catch (e) {
debugPrint("Error createSharedAlbum ${e.toString()}");
return false;
return null;
}
}
@@ -134,7 +135,6 @@ class SharedAlbumService {
await _apiService.albumApi.updateAlbumInfo(
albumId,
UpdateAlbumDto(
ownerId: ownerId,
albumName: newAlbumTitle,
),
);

View File

@@ -0,0 +1,77 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:openapi/api.dart';
import 'package:transparent_image/transparent_image.dart';
class AlbumThumbnailCard extends StatelessWidget {
const AlbumThumbnailCard({Key? key, required this.album}) : super(key: key);
final AlbumResponseDto album;
@override
Widget build(BuildContext context) {
var box = Hive.box(userInfoBox);
return GestureDetector(
onTap: () {
AutoRouter.of(context).push(AlbumViewerRoute(albumId: album.id));
},
child: Padding(
padding: const EdgeInsets.only(bottom: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8),
child: FadeInImage(
width: MediaQuery.of(context).size.width / 2 - 18,
height: MediaQuery.of(context).size.width / 2 - 18,
fit: BoxFit.cover,
placeholder: MemoryImage(kTransparentImage),
image: NetworkImage(
'${box.get(serverEndpointKey)}/asset/thumbnail/${album.albumThumbnailAssetId}?format=JPEG',
headers: {
"Authorization": "Bearer ${box.get(accessTokenKey)}"
},
),
fadeInDuration: const Duration(milliseconds: 200),
fadeOutDuration: const Duration(milliseconds: 200),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
album.albumName,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${album.assets.length} item${album.assets.length > 1 ? 's' : ''}',
style: const TextStyle(
fontSize: 10,
),
),
if (album.shared)
const Text(
' · Shared',
style: TextStyle(
fontSize: 10,
),
)
],
)
],
),
),
);
}
}

View File

@@ -1,7 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_title.provider.dart';
class AlbumTitleTextField extends ConsumerWidget {
const AlbumTitleTextField({

View File

@@ -4,9 +4,11 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/sharing/providers/album_viewer.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
@@ -15,13 +17,12 @@ import 'package:openapi/api.dart';
class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
const AlbumViewerAppbar({
Key? key,
required AsyncValue<AlbumResponseDto?> albumInfo,
required this.albumInfo,
required this.userId,
required this.albumId,
}) : _albumInfo = albumInfo,
super(key: key);
}) : super(key: key);
final AsyncValue<AlbumResponseDto?> _albumInfo;
final AlbumResponseDto albumInfo;
final String userId;
final String albumId;
@@ -38,11 +39,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
ImmichLoadingOverlayController.appLoader.show();
bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId);
await ref.watch(albumServiceProvider).deleteAlbum(albumId);
if (isSuccess) {
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
if (albumInfo.shared) {
ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId);
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
} else {
ref.watch(albumProvider.notifier).deleteAlbum(albumId);
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [LibraryRoute()]));
}
} else {
ImmichToast.show(
context: context,
@@ -105,7 +113,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
_buildBottomSheetActionButton() {
if (isMultiSelectionEnable) {
if (_albumInfo.asData?.value?.ownerId == userId) {
if (albumInfo.ownerId == userId) {
return ListTile(
leading: const Icon(Icons.delete_sweep_rounded),
title: const Text(
@@ -118,7 +126,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
return const SizedBox();
}
} else {
if (_albumInfo.asData?.value?.ownerId == userId) {
if (albumInfo.ownerId == userId) {
return ListTile(
leading: const Icon(Icons.delete_forever_rounded),
title: const Text(

View File

@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/providers/album_viewer.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_viewer.provider.dart';
import 'package:openapi/api.dart';
class AlbumViewerEditableTitle extends HookConsumerWidget {

View File

@@ -6,14 +6,19 @@ import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:openapi/api.dart';
class AlbumViewerThumbnail extends HookConsumerWidget {
final AssetResponseDto asset;
final List<AssetResponseDto> assetList;
const AlbumViewerThumbnail({Key? key, required this.asset}) : super(key: key);
const AlbumViewerThumbnail({
Key? key,
required this.asset,
required this.assetList,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -28,25 +33,13 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
ref.watch(assetSelectionProvider).isMultiselectEnable;
_viewAsset() {
if (asset.type == AssetTypeEnum.IMAGE) {
AutoRouter.of(context).push(
ImageViewerRoute(
imageUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false',
heroTag: asset.id,
thumbnailUrl: thumbnailRequestUrl,
asset: asset,
),
);
} else {
AutoRouter.of(context).push(
VideoViewerRoute(
videoUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
asset: asset,
),
);
}
AutoRouter.of(context).push(
GalleryViewerRoute(
asset: asset,
assetList: assetList,
thumbnailRequestUrl: thumbnailRequestUrl,
),
);
}
BoxBorder drawBorderColor() {

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/ui/selection_thumbnail_image.dart';
import 'package:immich_mobile/modules/album/ui/selection_thumbnail_image.dart';
import 'package:openapi/api.dart';
class AssetGridByMonth extends HookConsumerWidget {

View File

@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:openapi/api.dart';
class MonthGroupTitle extends HookConsumerWidget {
@@ -96,13 +96,16 @@ class MonthGroupTitle extends HookConsumerWidget {
color: Colors.grey,
),
),
Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
_getSimplifiedMonth(),
style: TextStyle(
fontSize: 24,
color: Theme.of(context).primaryColor,
GestureDetector(
onTap: _handleTitleIconClick,
child: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
_getSimplifiedMonth(),
style: TextStyle(
fontSize: 24,
color: Theme.of(context).primaryColor,
),
),
),
),

View File

@@ -4,7 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:openapi/api.dart';
class SelectionThumbnailImage extends HookConsumerWidget {
@@ -26,17 +26,21 @@ class SelectionThumbnailImage extends HookConsumerWidget {
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
Widget _buildSelectionIcon(AssetResponseDto asset) {
if (selectedAsset.contains(asset) && !isAlbumExist) {
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
var isNewlySelected =
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
if (isSelected && !isAlbumExist) {
return Icon(
Icons.check_circle,
color: Theme.of(context).primaryColor,
);
} else if (selectedAsset.contains(asset) && isAlbumExist) {
} else if (isSelected && isAlbumExist) {
return const Icon(
Icons.check_circle,
color: Color.fromARGB(255, 233, 233, 233),
);
} else if (newAssetsForAlbum.contains(asset) && isAlbumExist) {
} else if (isNewlySelected && isAlbumExist) {
return Icon(
Icons.check_circle,
color: Theme.of(context).primaryColor,
@@ -50,17 +54,21 @@ class SelectionThumbnailImage extends HookConsumerWidget {
}
BoxBorder drawBorderColor() {
if (selectedAsset.contains(asset) && !isAlbumExist) {
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
var isNewlySelected =
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
if (isSelected && !isAlbumExist) {
return Border.all(
color: Theme.of(context).primaryColorLight,
width: 10,
);
} else if (selectedAsset.contains(asset) && isAlbumExist) {
} else if (isSelected && isAlbumExist) {
return Border.all(
color: const Color.fromARGB(255, 190, 190, 190),
width: 10,
);
} else if (newAssetsForAlbum.contains(asset) && isAlbumExist) {
} else if (isNewlySelected && isAlbumExist) {
return Border.all(
color: Theme.of(context).primaryColorLight,
width: 10,
@@ -71,10 +79,15 @@ class SelectionThumbnailImage extends HookConsumerWidget {
return GestureDetector(
onTap: () {
var isSelected =
selectedAsset.map((item) => item.id).contains(asset.id);
var isNewlySelected =
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
if (isAlbumExist) {
// Operation for existing album
if (!selectedAsset.contains(asset)) {
if (newAssetsForAlbum.contains(asset)) {
if (!isSelected) {
if (isNewlySelected) {
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedAdditionalAssets([asset]);
@@ -86,7 +99,7 @@ class SelectionThumbnailImage extends HookConsumerWidget {
}
} else {
// Operation for new album
if (selectedAsset.contains(asset)) {
if (isSelected) {
ref
.watch(assetSelectionProvider.notifier)
.removeSelectedNewAssets([asset]);

View File

@@ -16,8 +16,6 @@ class SharingSliverAppBar extends StatelessWidget {
pinned: true,
snap: false,
automaticallyImplyLeading: false,
// leading: Container(),
// elevation: 0,
title: Text(
'IMMICH',
style: TextStyle(
@@ -46,7 +44,7 @@ class SharingSliverAppBar extends StatelessWidget {
),
onPressed: () {
AutoRouter.of(context)
.push(const CreateSharedAlbumRoute());
.push(CreateAlbumRoute(isSharedAlbum: true));
},
icon: const Icon(
Icons.photo_album_outlined,

View File

@@ -6,14 +6,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
import 'package:immich_mobile/modules/sharing/ui/album_action_outlined_button.dart';
import 'package:immich_mobile/modules/sharing/ui/album_viewer_appbar.dart';
import 'package:immich_mobile/modules/sharing/ui/album_viewer_editable_title.dart';
import 'package:immich_mobile/modules/sharing/ui/album_viewer_thumbnail.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/ui/album_action_outlined_button.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_appbar.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_editable_title.dart';
import 'package:immich_mobile/modules/album/ui/album_viewer_thumbnail.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
@@ -29,8 +29,7 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
FocusNode titleFocusNode = useFocusNode();
ScrollController scrollController = useScrollController();
AsyncValue<AlbumResponseDto?> albumInfo =
ref.watch(sharedAlbumDetailProvider(albumId));
var albumInfo = ref.watch(sharedAlbumDetailProvider(albumId));
final userId = ref.watch(authenticationProvider).userId;
@@ -53,12 +52,11 @@ class AlbumViewerPage extends HookConsumerWidget {
if (returnPayload.selectedAdditionalAsset.isNotEmpty) {
ImmichLoadingOverlayController.appLoader.show();
var isSuccess = await ref
.watch(sharedAlbumServiceProvider)
.addAdditionalAssetToAlbum(
returnPayload.selectedAdditionalAsset,
albumId,
);
var isSuccess =
await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum(
returnPayload.selectedAdditionalAsset,
albumId,
);
if (isSuccess) {
ref.refresh(sharedAlbumDetailProvider(albumId));
@@ -83,7 +81,7 @@ class AlbumViewerPage extends HookConsumerWidget {
ImmichLoadingOverlayController.appLoader.show();
var isSuccess = await ref
.watch(sharedAlbumServiceProvider)
.watch(albumServiceProvider)
.addAdditionalUserToAlbum(sharedUserIds, albumId);
if (isSuccess) {
@@ -132,7 +130,11 @@ class AlbumViewerPage extends HookConsumerWidget {
String endDate = DateFormat('LLL d, y').format(parsedEndDate);
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8),
padding: EdgeInsets.only(
left: 16.0,
top: 8.0,
bottom: albumInfo.shared ? 0.0 : 8.0,
),
child: Text(
"$startDate-$endDate",
style: const TextStyle(
@@ -152,31 +154,33 @@ class AlbumViewerPage extends HookConsumerWidget {
_buildTitle(albumInfo),
if (albumInfo.assets.isNotEmpty == true)
_buildAlbumDateRange(albumInfo),
SizedBox(
height: 60,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16),
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[300],
radius: 18,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child:
Image.asset('assets/immich-logo-no-outline.png'),
if (albumInfo.shared)
SizedBox(
height: 60,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16),
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[300],
radius: 18,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Image.asset(
'assets/immich-logo-no-outline.png',
),
),
),
),
),
);
}),
itemCount: albumInfo.sharedUsers.length,
),
)
);
}),
itemCount: albumInfo.sharedUsers.length,
),
)
],
),
);
@@ -194,7 +198,10 @@ class AlbumViewerPage extends HookConsumerWidget {
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return AlbumViewerThumbnail(asset: albumInfo.assets[index]);
return AlbumViewerThumbnail(
asset: albumInfo.assets[index],
assetList: albumInfo.assets,
);
},
childCount: albumInfo.assets.length,
),
@@ -261,10 +268,19 @@ class AlbumViewerPage extends HookConsumerWidget {
}
return Scaffold(
appBar: AlbumViewerAppbar(
albumInfo: albumInfo,
userId: userId,
albumId: albumId,
appBar: albumInfo.when(
data: (AlbumResponseDto? data) {
if (data != null) {
return AlbumViewerAppbar(
albumInfo: data,
userId: userId,
albumId: albumId,
);
}
return null;
},
error: (e, _) => null,
loading: () => null,
),
body: albumInfo.when(
data: (albumInfo) => albumInfo != null

View File

@@ -3,15 +3,16 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/sharing/ui/asset_grid_by_month.dart';
import 'package:immich_mobile/modules/sharing/ui/month_group_title.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/ui/asset_grid_by_month.dart';
import 'package:immich_mobile/modules/album/ui/month_group_title.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/modules/home/ui/draggable_scrollbar.dart';
class AssetSelectionPage extends HookConsumerWidget {
const AssetSelectionPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
ScrollController scrollController = useScrollController();

View File

@@ -3,16 +3,20 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/sharing/ui/album_action_outlined_button.dart';
import 'package:immich_mobile/modules/sharing/ui/album_title_text_field.dart';
import 'package:immich_mobile/modules/sharing/ui/shared_album_thumbnail_image.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_action_outlined_button.dart';
import 'package:immich_mobile/modules/album/ui/album_title_text_field.dart';
import 'package:immich_mobile/modules/album/ui/shared_album_thumbnail_image.dart';
import 'package:immich_mobile/routing/router.dart';
class CreateSharedAlbumPage extends HookConsumerWidget {
const CreateSharedAlbumPage({Key? key}) : super(key: key);
// ignore: must_be_immutable
class CreateAlbumPage extends HookConsumerWidget {
bool isSharedAlbum;
CreateAlbumPage({Key? key, required this.isSharedAlbum}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -165,6 +169,21 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
return const SliverToBoxAdapter();
}
_createNonSharedAlbum() async {
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
ref.watch(albumTitleProvider),
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
);
if (newAlbum != null) {
ref.watch(albumProvider.notifier).getAllAlbums();
ref.watch(assetSelectionProvider.notifier).removeAll();
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();
AutoRouter.of(context).replace(AlbumViewerRoute(albumId: newAlbum.id));
}
}
return Scaffold(
appBar: AppBar(
elevation: 0,
@@ -181,17 +200,31 @@ class CreateSharedAlbumPage extends HookConsumerWidget {
style: TextStyle(color: Colors.black),
).tr(),
actions: [
TextButton(
onPressed: albumTitleController.text.isNotEmpty
? _showSelectUserPage
: null,
child: Text(
'create_shared_album_page_share'.tr(),
style: const TextStyle(
fontWeight: FontWeight.bold,
if (isSharedAlbum)
TextButton(
onPressed: albumTitleController.text.isNotEmpty
? _showSelectUserPage
: null,
child: Text(
'create_shared_album_page_share'.tr(),
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
if (!isSharedAlbum)
TextButton(
onPressed: albumTitleController.text.isNotEmpty &&
selectedAssets.isNotEmpty
? _createNonSharedAlbum
: null,
child: Text(
'create_shared_album_page_create'.tr(),
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
),
],
),
body: GestureDetector(

View File

@@ -0,0 +1,116 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_card.dart';
import 'package:immich_mobile/routing/router.dart';
class LibraryPage extends HookConsumerWidget {
const LibraryPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final albums = ref.watch(albumProvider);
useEffect(
() {
ref.read(albumProvider.notifier).getAllAlbums();
return null;
},
[],
);
Widget _buildAppBar() {
return SliverAppBar(
centerTitle: true,
floating: true,
pinned: false,
snap: false,
automaticallyImplyLeading: false,
title: Text(
'IMMICH',
style: TextStyle(
fontFamily: 'SnowburstOne',
fontWeight: FontWeight.bold,
fontSize: 22,
color: Theme.of(context).primaryColor,
),
),
);
}
Widget _buildCreateAlbumButton() {
return GestureDetector(
onTap: () {
AutoRouter.of(context).push(CreateAlbumRoute(isSharedAlbum: false));
},
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width / 2 - 18,
height: MediaQuery.of(context).size.width / 2 - 18,
decoration: BoxDecoration(
border: Border.all(
color: Colors.grey,
),
borderRadius: BorderRadius.circular(8),
),
child: Center(
child: Icon(
Icons.add_rounded,
size: 28,
color: Theme.of(context).primaryColor,
),
),
),
const Padding(
padding: EdgeInsets.only(top: 8.0),
child: Text(
"New album",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
)
],
),
);
}
return Scaffold(
body: CustomScrollView(
slivers: [
_buildAppBar(),
const SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(12.0),
child: Text(
"Albums",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
),
SliverPadding(
padding: const EdgeInsets.only(left: 12.0, right: 12, bottom: 50),
sliver: SliverToBoxAdapter(
child: Wrap(
spacing: 12,
children: [
_buildCreateAlbumButton(),
for (var album in albums)
AlbumThumbnailCard(
album: album,
),
],
),
),
)
],
),
);
}
}

View File

@@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart';

View File

@@ -3,11 +3,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/sharing/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/modules/sharing/services/shared_album.service.dart';
import 'package:immich_mobile/modules/album/providers/album_title.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart';
@@ -22,14 +21,14 @@ class SelectUserForSharingPage extends HookConsumerWidget {
ref.watch(suggestedSharedUsersProvider);
_createSharedAlbum() async {
var isSuccess =
await ref.watch(sharedAlbumServiceProvider).createSharedAlbum(
var newAlbum =
await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum(
ref.watch(albumTitleProvider),
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
sharedUsersList.value.map((userInfo) => userInfo.id).toList(),
);
if (isSuccess) {
if (newAlbum != null) {
await ref.watch(sharedAlbumProvider.notifier).getAllSharedAlbums();
ref.watch(assetSelectionProvider.notifier).removeAll();
ref.watch(albumTitleProvider.notifier).clearAlbumTitle();

View File

@@ -5,8 +5,8 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/sharing/ui/sharing_sliver_appbar.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/ui/sharing_sliver_appbar.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:openapi/api.dart';
import 'package:transparent_image/transparent_image.dart';
@@ -23,7 +23,6 @@ class SharingPage extends HookConsumerWidget {
useEffect(
() {
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
return null;
},
[],

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
import 'package:path/path.dart' as p;
@@ -14,6 +15,7 @@ final imageViewerServiceProvider =
class ImageViewerService {
final ApiService _apiService;
ImageViewerService(this._apiService);
Future<bool> downloadAssetToDevice(AssetResponseDto asset) async {

View File

@@ -15,7 +15,6 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
@override
Widget build(BuildContext context) {
bool allowMoving = _status == _RemoteImageStatus.full;
return PhotoView(
imageProvider: _imageProvider,
minScale: PhotoViewComputedScale.contained,
@@ -32,8 +31,9 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
PhotoViewControllerValue controllerValue,
) {
// Disable swipe events when zoomed in
if (_zoomedIn) return;
if (_zoomedIn) {
return;
}
if (controllerValue.position.dy > swipeThreshold) {
widget.onSwipeDown();
} else if (controllerValue.position.dy < -swipeThreshold) {
@@ -42,7 +42,14 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
}
void _scaleStateChanged(PhotoViewScaleState state) {
_zoomedIn = state == PhotoViewScaleState.zoomedIn;
// _onScaleListener;
_zoomedIn = state != PhotoViewScaleState.initial;
if (_zoomedIn) {
widget.isZoomedListener.value = true;
} else {
widget.isZoomedListener.value = false;
}
widget.isZoomedFunction();
}
CachedNetworkImageProvider _authorizedImageProvider(String url) {
@@ -107,6 +114,8 @@ class RemotePhotoView extends StatefulWidget {
required this.thumbnailUrl,
required this.imageUrl,
required this.authToken,
required this.isZoomedFunction,
required this.isZoomedListener,
required this.onSwipeDown,
required this.onSwipeUp,
}) : super(key: key);
@@ -117,6 +126,9 @@ class RemotePhotoView extends StatefulWidget {
final void Function() onSwipeDown;
final void Function() onSwipeUp;
final void Function() isZoomedFunction;
final ValueNotifier<bool> isZoomedListener;
@override
State<StatefulWidget> createState() {

View File

@@ -0,0 +1,134 @@
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';
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
import 'package:immich_mobile/modules/asset_viewer/ui/top_control_app_bar.dart';
import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart';
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
import 'package:immich_mobile/modules/home/services/asset.service.dart';
import 'package:openapi/api.dart';
// ignore: must_be_immutable
class GalleryViewerPage extends HookConsumerWidget {
late List<AssetResponseDto> assetList;
final AssetResponseDto asset;
final String thumbnailRequestUrl;
GalleryViewerPage({
Key? key,
required this.assetList,
required this.asset,
required this.thumbnailRequestUrl,
}) : super(key: key);
AssetResponseDto? assetDetail;
@override
Widget build(BuildContext context, WidgetRef ref) {
final Box<dynamic> box = Hive.box(userInfoBox);
int indexOfAsset = assetList.indexOf(asset);
@override
void initState(int index) {
indexOfAsset = index;
}
PageController controller =
PageController(initialPage: assetList.indexOf(asset));
getAssetExif() async {
assetDetail = await ref
.watch(assetServiceProvider)
.getAssetById(assetList[indexOfAsset].id);
}
void showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
},
);
}
final isZoomed = useState<bool>(false);
ValueNotifier<bool> isZoomedListener = ValueNotifier<bool>(false);
//make isZoomed listener call instead
void isZoomedMethod() {
if (isZoomedListener.value) {
isZoomed.value = true;
} else {
isZoomed.value = false;
}
}
return Scaffold(
backgroundColor: Colors.black,
appBar: TopControlAppBar(
asset: assetList[indexOfAsset],
onMoreInfoPressed: () {
showInfo();
},
onDownloadPressed: () {
ref
.watch(imageViewerStateProvider.notifier)
.downloadAsset(assetList[indexOfAsset], context);
},
),
body: SafeArea(
child: PageView.builder(
controller: controller,
pageSnapping: true,
physics: isZoomed.value
? const NeverScrollableScrollPhysics()
: const BouncingScrollPhysics(),
itemCount: assetList.length,
scrollDirection: Axis.horizontal,
itemBuilder: (context, index) {
initState(index);
getAssetExif();
if (assetList[index].type == AssetTypeEnum.IMAGE) {
return ImageViewerPage(
thumbnailUrl:
'${box.get(serverEndpointKey)}/asset/thumbnail/${assetList[index].id}',
imageUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${assetList[index].deviceAssetId}&did=${assetList[index].deviceId}&isThumb=false',
authToken: 'Bearer ${box.get(accessTokenKey)}',
isZoomedFunction: isZoomedMethod,
isZoomedListener: isZoomedListener,
asset: assetList[index],
heroTag: assetList[index].id,
);
} else {
return SwipeDetector(
onSwipeDown: (_) {
AutoRouter.of(context).pop();
},
onSwipeUp: (_) {
showInfo();
},
child: Hero(
tag: assetList[index].id,
child: VideoViewerPage(
asset: assetList[index],
videoUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${assetList[index].deviceAssetId}&did=${assetList[index].deviceId}',
),
),
);
}
},
),
),
);
}
}

View File

@@ -1,15 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.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/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/home/services/asset.service.dart';
import 'package:openapi/api.dart';
@@ -19,8 +16,9 @@ class ImageViewerPage extends HookConsumerWidget {
final String heroTag;
final String thumbnailUrl;
final AssetResponseDto asset;
AssetResponseDto? assetDetail;
final String authToken;
final ValueNotifier<bool> isZoomedListener;
final void Function() isZoomedFunction;
ImageViewerPage({
Key? key,
@@ -28,31 +26,22 @@ class ImageViewerPage extends HookConsumerWidget {
required this.heroTag,
required this.thumbnailUrl,
required this.asset,
required this.authToken,
required this.isZoomedFunction,
required this.isZoomedListener,
}) : super(key: key);
AssetResponseDto? assetDetail;
@override
Widget build(BuildContext context, WidgetRef ref) {
final downloadAssetStatus =
ref.watch(imageViewerStateProvider).downloadAssetStatus;
var box = Hive.box(userInfoBox);
getAssetExif() async {
assetDetail =
await ref.watch(assetServiceProvider).getAssetById(asset.id);
}
showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
},
);
}
useEffect(
() {
getAssetExif();
@@ -61,39 +50,39 @@ class ImageViewerPage extends HookConsumerWidget {
[],
);
return Scaffold(
backgroundColor: Colors.black,
appBar: TopControlAppBar(
asset: asset,
onMoreInfoPressed: showInfo,
onDownloadPressed: () {
ref
.watch(imageViewerStateProvider.notifier)
.downloadAsset(asset, context);
showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail ?? asset);
},
),
body: SafeArea(
child: Stack(
children: [
Center(
child: Hero(
tag: heroTag,
child: RemotePhotoView(
thumbnailUrl: thumbnailUrl,
imageUrl: imageUrl,
authToken: "Bearer ${box.get(accessTokenKey)}",
onSwipeDown: () => AutoRouter.of(context).pop(),
onSwipeUp: () => showInfo(),
),
),
);
}
return Stack(
children: [
Center(
child: Hero(
tag: heroTag,
child: RemotePhotoView(
thumbnailUrl: thumbnailUrl,
imageUrl: imageUrl,
authToken: authToken,
isZoomedFunction: isZoomedFunction,
isZoomedListener: isZoomedListener,
onSwipeDown: () => AutoRouter.of(context).pop(),
onSwipeUp: () => showInfo(),
),
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(
child: DownloadLoadingIndicator(),
),
],
),
),
),
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(
child: DownloadLoadingIndicator(),
),
],
);
}
}

View File

@@ -1,7 +1,4 @@
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';
@@ -9,9 +6,6 @@ import 'package:chewie/chewie.dart';
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.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/exif_bottom_sheet.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:openapi/api.dart';
import 'package:video_player/video_player.dart';
@@ -31,66 +25,17 @@ 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 ref.watch(assetServiceProvider).getAssetById(asset.id);
}
useEffect(
() {
getAssetExif();
return null;
},
[],
);
return Scaffold(
backgroundColor: Colors.black,
appBar: TopControlAppBar(
asset: asset,
onMoreInfoPressed: () {
showInfo();
},
onDownloadPressed: () {
ref
.watch(imageViewerStateProvider.notifier)
.downloadAsset(asset, context);
},
),
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(),
),
],
),
return Stack(
children: [
VideoThumbnailPlayer(
url: videoUrl,
jwtToken: jwtToken,
),
),
if (downloadAssetStatus == DownloadAssetStatus.loading)
const Center(
child: DownloadLoadingIndicator(),
),
],
);
}
}
@@ -134,10 +79,13 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
_createChewieController() {
chewieController = ChewieController(
showOptions: true,
showControlsOnInitialize: false,
showControlsOnInitialize: true,
videoPlayerController: videoPlayerController,
autoPlay: true,
autoInitialize: false,
autoInitialize: true,
allowFullScreen: true,
showControls: true,
hideControlsTimer: const Duration(seconds: 5),
);
}
@@ -157,11 +105,13 @@ class _VideoThumbnailPlayerState extends State<VideoThumbnailPlayer> {
controller: chewieController!,
),
)
: const SizedBox(
width: 75,
height: 75,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
: const Center(
child: SizedBox(
width: 75,
height: 75,
child: CircularProgressIndicator.adaptive(
strokeWidth: 2,
),
),
);
}

View File

@@ -162,6 +162,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
onlyAll: true,
type: RequestType.common,
);
if (list.isEmpty) {
return;
}
AssetPathEntity albumHasAllAssets = list.first;
backupAlbumInfoBox.put(

View File

@@ -8,6 +8,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/utils/files_helper.dart';
import 'package:openapi/api.dart';
@@ -24,6 +25,7 @@ final backupServiceProvider = Provider(
class BackupService {
final ApiService _apiService;
BackupService(this._apiService);
Future<List<String>?> getDeviceBackupAsset() async {

View File

@@ -102,10 +102,12 @@ class AlbumInfoCard extends HookConsumerWidget {
HapticFeedback.selectionClick();
if (isExcluded) {
// Remove from exclude album list
ref
.watch(backupProvider.notifier)
.removeExcludedAlbumForBackup(albumInfo);
} else {
// Add to exclude album list
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 &&
ref
.watch(backupProvider)
@@ -120,6 +122,16 @@ class AlbumInfoCard extends HookConsumerWidget {
return;
}
if (albumInfo.id == 'isAll') {
ImmichToast.show(
context: context,
msg: 'Cannot exclude album contains all assets',
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
return;
}
ref
.watch(backupProvider.notifier)
.addExcludedAlbumForBackup(albumInfo);

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';

View File

@@ -3,10 +3,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
import 'package:openapi/api.dart';
// ignore: must_be_immutable
class ImageGrid extends ConsumerWidget {
final List<AssetResponseDto> assetGroup;
final List<AssetResponseDto> sortedAssetGroup;
const ImageGrid({Key? key, required this.assetGroup}) : super(key: key);
ImageGrid({
Key? key,
required this.assetGroup,
required this.sortedAssetGroup,
}) : super(key: key);
List<AssetResponseDto> imageSortedList = [];
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -19,12 +27,14 @@ class ImageGrid extends ConsumerWidget {
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
var assetType = assetGroup[index].type;
return GestureDetector(
onTap: () {},
child: Stack(
children: [
ThumbnailImage(asset: assetGroup[index]),
ThumbnailImage(
asset: assetGroup[index],
assetList: sortedAssetGroup,
),
if (assetType != AssetTypeEnum.IMAGE)
Positioned(
top: 5,

View File

@@ -13,8 +13,10 @@ import 'package:openapi/api.dart';
class ThumbnailImage extends HookConsumerWidget {
final AssetResponseDto asset;
final List<AssetResponseDto> assetList;
const ThumbnailImage({Key? key, required this.asset}) : super(key: key);
const ThumbnailImage({Key? key, required this.asset, required this.assetList})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -60,29 +62,17 @@ class ThumbnailImage extends HookConsumerWidget {
.watch(homePageStateProvider.notifier)
.addSingleSelectedItem(asset);
} else {
if (asset.type == AssetTypeEnum.IMAGE) {
AutoRouter.of(context).push(
ImageViewerRoute(
imageUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}&isThumb=false',
heroTag: asset.id,
thumbnailUrl: thumbnailRequestUrl,
asset: asset,
),
);
} else {
AutoRouter.of(context).push(
VideoViewerRoute(
videoUrl:
'${box.get(serverEndpointKey)}/asset/file?aid=${asset.deviceAssetId}&did=${asset.deviceId}',
asset: asset,
),
);
}
AutoRouter.of(context).push(
GalleryViewerRoute(
assetList: assetList,
thumbnailRequestUrl: thumbnailRequestUrl,
asset: asset,
),
);
}
},
onLongPress: () {
// Enable multi selecte function
// Enable multi select function
ref.watch(homePageStateProvider.notifier).enableMultiSelect({asset});
HapticFeedback.heavyImpact();
},

View File

@@ -10,9 +10,11 @@ import 'package:immich_mobile/modules/home/ui/image_grid.dart';
import 'package:immich_mobile/modules/home/ui/immich_sliver_appbar.dart';
import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart';
import 'package:immich_mobile/modules/home/ui/profile_drawer.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
import 'package:openapi/api.dart';
class HomePage extends HookConsumerWidget {
const HomePage({Key? key}) : super(key: key);
@@ -25,6 +27,13 @@ class HomePage extends HookConsumerWidget {
var isMultiSelectEnable =
ref.watch(homePageStateProvider).isMultiSelectEnable;
var homePageState = ref.watch(homePageStateProvider);
List<AssetResponseDto> sortedAssetList = [];
// set sorted List
for (var group in assetGroupByDateTime.values) {
for (var value in group) {
sortedAssetList.add(value);
}
}
useEffect(
() {
@@ -73,7 +82,10 @@ class HomePage extends HookConsumerWidget {
);
imageGridGroup.add(
ImageGrid(assetGroup: immichAssetList),
ImageGrid(
assetGroup: immichAssetList,
sortedAssetGroup: sortedAssetList,
),
);
lastMonth = currentMonth;

View File

@@ -5,6 +5,7 @@ import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/shared/services/device_info.service.dart';
import 'package:openapi/api.dart';

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
@@ -11,6 +12,7 @@ final searchServiceProvider = Provider(
class SearchService {
final ApiService _apiService;
SearchService(this._apiService);
Future<List<String>?> getUserSuggestedSearchTerms() async {

View File

@@ -140,7 +140,7 @@ class SearchPage extends HookConsumerWidget {
return ThumbnailWithInfo(
imageUrl:
'https://images.unsplash.com/photo-1612178537253-bccd437b730e?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxzZWFyY2h8NXx8Ymxhbmt8ZW58MHx8MHx8&auto=format&fit=crop&w=700&q=60',
textInfo: 'sarch_no_objects'.tr(),
textInfo: 'search_page_no_objects'.tr(),
onTap: () {},
);
}),

View File

@@ -11,6 +11,7 @@ import 'package:immich_mobile/modules/home/ui/monthly_title_text.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/search/providers/search_result_page.provider.dart';
import 'package:immich_mobile/modules/search/ui/search_suggestion_list.dart';
import 'package:openapi/api.dart';
class SearchResultPage extends HookConsumerWidget {
const SearchResultPage({Key? key, required this.searchTerm})
@@ -27,7 +28,9 @@ class SearchResultPage extends HookConsumerWidget {
final List<Widget> imageGridGroup = [];
late FocusNode searchFocusNode;
FocusNode? searchFocusNode;
List<AssetResponseDto> sortedAssetList = [];
useEffect(
() {
@@ -37,14 +40,14 @@ class SearchResultPage extends HookConsumerWidget {
Duration.zero,
() => ref.read(searchResultPageProvider.notifier).search(searchTerm),
);
return () => searchFocusNode.dispose();
return () => searchFocusNode?.dispose();
},
[],
);
_onSearchSubmitted(String newSearchTerm) {
debugPrint("Re-Search with $newSearchTerm");
searchFocusNode.unfocus();
searchFocusNode?.unfocus();
isNewSearch.value = false;
currentSearchTerm.value = newSearchTerm;
ref.watch(searchResultPageProvider.notifier).search(newSearchTerm);
@@ -58,7 +61,7 @@ class SearchResultPage extends HookConsumerWidget {
onTap: () {
searchTermController.clear();
ref.watch(searchPageStateProvider.notifier).setSearchTerm("");
searchFocusNode.requestFocus();
searchFocusNode?.requestFocus();
},
textInputAction: TextInputAction.search,
onSubmitted: (searchTerm) {
@@ -131,7 +134,12 @@ class SearchResultPage extends HookConsumerWidget {
if (searchResultPageState.isSuccess) {
if (searchResultPageState.searchResult.isNotEmpty) {
int? lastMonth;
// set sorted List
for (var group in assetGroupByDateTime.values) {
for (var value in group) {
sortedAssetList.add(value);
}
}
assetGroupByDateTime.forEach((dateGroup, immichAssetList) {
DateTime parseDateGroup = DateTime.parse(dateGroup);
int currentMonth = parseDateGroup.month;
@@ -154,7 +162,10 @@ class SearchResultPage extends HookConsumerWidget {
);
imageGridGroup.add(
ImageGrid(assetGroup: immichAssetList),
ImageGrid(
assetGroup: immichAssetList,
sortedAssetGroup: sortedAssetList,
),
);
lastMonth = currentMonth;
@@ -193,7 +204,7 @@ class SearchResultPage extends HookConsumerWidget {
title: GestureDetector(
onTap: () {
isNewSearch.value = true;
searchFocusNode.requestFocus();
searchFocusNode?.requestFocus();
},
child: isNewSearch.value ? _buildTextField() : _buildChip(),
),
@@ -201,7 +212,10 @@ class SearchResultPage extends HookConsumerWidget {
),
body: GestureDetector(
onTap: () {
searchFocusNode.unfocus();
if (searchFocusNode != null) {
searchFocusNode?.unfocus();
}
ref.watch(searchPageStateProvider.notifier).disableSearch();
},
child: Stack(

View File

@@ -1,6 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/views/library_page.dart';
import 'package:immich_mobile/modules/asset_viewer/views/gallery_viewer.dart';
import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
import 'package:immich_mobile/modules/backup/views/failed_backup_status_page.dart';
@@ -9,16 +11,17 @@ import 'package:immich_mobile/modules/login/views/login_page.dart';
import 'package:immich_mobile/modules/home/views/home_page.dart';
import 'package:immich_mobile/modules/search/views/search_page.dart';
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
import 'package:immich_mobile/modules/sharing/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/sharing/views/album_viewer_page.dart';
import 'package:immich_mobile/modules/sharing/views/asset_selection_page.dart';
import 'package:immich_mobile/modules/sharing/views/create_shared_album_page.dart';
import 'package:immich_mobile/modules/sharing/views/select_additional_user_for_sharing_page.dart';
import 'package:immich_mobile/modules/sharing/views/select_user_for_sharing_page.dart';
import 'package:immich_mobile/modules/sharing/views/sharing_page.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/views/album_viewer_page.dart';
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
import 'package:immich_mobile/modules/album/views/select_user_for_sharing_page.dart';
import 'package:immich_mobile/modules/album/views/sharing_page.dart';
import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/modules/backup/views/backup_controller_page.dart';
import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/shared/views/splash_screen.dart';
import 'package:immich_mobile/shared/views/tab_controller_page.dart';
@@ -40,15 +43,17 @@ part 'router.gr.dart';
children: [
AutoRoute(page: HomePage, guards: [AuthGuard]),
AutoRoute(page: SearchPage, guards: [AuthGuard]),
AutoRoute(page: SharingPage, guards: [AuthGuard])
AutoRoute(page: SharingPage, guards: [AuthGuard]),
AutoRoute(page: LibraryPage, guards: [AuthGuard])
],
transitionsBuilder: TransitionsBuilders.fadeIn,
),
AutoRoute(page: GalleryViewerPage, guards: [AuthGuard]),
AutoRoute(page: ImageViewerPage, guards: [AuthGuard]),
AutoRoute(page: VideoViewerPage, guards: [AuthGuard]),
AutoRoute(page: BackupControllerPage, guards: [AuthGuard]),
AutoRoute(page: SearchResultPage, guards: [AuthGuard]),
AutoRoute(page: CreateSharedAlbumPage, guards: [AuthGuard]),
AutoRoute(page: CreateAlbumPage, guards: [AuthGuard]),
CustomRoute<AssetSelectionPageResult?>(
page: AssetSelectionPage,
guards: [AuthGuard],
@@ -75,7 +80,9 @@ part 'router.gr.dart';
],
)
class AppRouter extends _$AppRouter {
// ignore: unused_field
final ApiService _apiService;
AppRouter(this._apiService) : super(authGuard: AuthGuard(_apiService));
}

View File

@@ -41,6 +41,16 @@ class _$AppRouter extends RootStackRouter {
opaque: true,
barrierDismissible: false);
},
GalleryViewerRoute.name: (routeData) {
final args = routeData.argsAs<GalleryViewerRouteArgs>();
return MaterialPageX<dynamic>(
routeData: routeData,
child: GalleryViewerPage(
key: args.key,
assetList: args.assetList,
asset: args.asset,
thumbnailRequestUrl: args.thumbnailRequestUrl));
},
ImageViewerRoute.name: (routeData) {
final args = routeData.argsAs<ImageViewerRouteArgs>();
return MaterialPageX<dynamic>(
@@ -50,7 +60,10 @@ class _$AppRouter extends RootStackRouter {
imageUrl: args.imageUrl,
heroTag: args.heroTag,
thumbnailUrl: args.thumbnailUrl,
asset: args.asset));
asset: args.asset,
authToken: args.authToken,
isZoomedFunction: args.isZoomedFunction,
isZoomedListener: args.isZoomedListener));
},
VideoViewerRoute.name: (routeData) {
final args = routeData.argsAs<VideoViewerRouteArgs>();
@@ -69,9 +82,12 @@ class _$AppRouter extends RootStackRouter {
routeData: routeData,
child: SearchResultPage(key: args.key, searchTerm: args.searchTerm));
},
CreateSharedAlbumRoute.name: (routeData) {
CreateAlbumRoute.name: (routeData) {
final args = routeData.argsAs<CreateAlbumRouteArgs>();
return MaterialPageX<dynamic>(
routeData: routeData, child: const CreateSharedAlbumPage());
routeData: routeData,
child: CreateAlbumPage(
key: args.key, isSharedAlbum: args.isSharedAlbum));
},
AssetSelectionRoute.name: (routeData) {
return CustomPage<AssetSelectionPageResult?>(
@@ -136,6 +152,10 @@ class _$AppRouter extends RootStackRouter {
SharingRoute.name: (routeData) {
return MaterialPageX<dynamic>(
routeData: routeData, child: const SharingPage());
},
LibraryRoute.name: (routeData) {
return MaterialPageX<dynamic>(
routeData: routeData, child: const LibraryPage());
}
};
@@ -161,8 +181,14 @@ class _$AppRouter extends RootStackRouter {
RouteConfig(SharingRoute.name,
path: 'sharing-page',
parent: TabControllerRoute.name,
guards: [authGuard]),
RouteConfig(LibraryRoute.name,
path: 'library-page',
parent: TabControllerRoute.name,
guards: [authGuard])
]),
RouteConfig(GalleryViewerRoute.name,
path: '/gallery-viewer-page', guards: [authGuard]),
RouteConfig(ImageViewerRoute.name,
path: '/image-viewer-page', guards: [authGuard]),
RouteConfig(VideoViewerRoute.name,
@@ -171,8 +197,8 @@ class _$AppRouter extends RootStackRouter {
path: '/backup-controller-page', guards: [authGuard]),
RouteConfig(SearchResultRoute.name,
path: '/search-result-page', guards: [authGuard]),
RouteConfig(CreateSharedAlbumRoute.name,
path: '/create-shared-album-page', guards: [authGuard]),
RouteConfig(CreateAlbumRoute.name,
path: '/create-album-page', guards: [authGuard]),
RouteConfig(AssetSelectionRoute.name,
path: '/asset-selection-page', guards: [authGuard]),
RouteConfig(SelectUserForSharingRoute.name,
@@ -226,6 +252,46 @@ class TabControllerRoute extends PageRouteInfo<void> {
static const String name = 'TabControllerRoute';
}
/// generated route for
/// [GalleryViewerPage]
class GalleryViewerRoute extends PageRouteInfo<GalleryViewerRouteArgs> {
GalleryViewerRoute(
{Key? key,
required List<AssetResponseDto> assetList,
required AssetResponseDto asset,
required String thumbnailRequestUrl})
: super(GalleryViewerRoute.name,
path: '/gallery-viewer-page',
args: GalleryViewerRouteArgs(
key: key,
assetList: assetList,
asset: asset,
thumbnailRequestUrl: thumbnailRequestUrl));
static const String name = 'GalleryViewerRoute';
}
class GalleryViewerRouteArgs {
const GalleryViewerRouteArgs(
{this.key,
required this.assetList,
required this.asset,
required this.thumbnailRequestUrl});
final Key? key;
final List<AssetResponseDto> assetList;
final AssetResponseDto asset;
final String thumbnailRequestUrl;
@override
String toString() {
return 'GalleryViewerRouteArgs{key: $key, assetList: $assetList, asset: $asset, thumbnailRequestUrl: $thumbnailRequestUrl}';
}
}
/// generated route for
/// [ImageViewerPage]
class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
@@ -234,7 +300,10 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
required String imageUrl,
required String heroTag,
required String thumbnailUrl,
required AssetResponseDto asset})
required AssetResponseDto asset,
required String authToken,
required void Function() isZoomedFunction,
required ValueNotifier<bool> isZoomedListener})
: super(ImageViewerRoute.name,
path: '/image-viewer-page',
args: ImageViewerRouteArgs(
@@ -242,7 +311,10 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
imageUrl: imageUrl,
heroTag: heroTag,
thumbnailUrl: thumbnailUrl,
asset: asset));
asset: asset,
authToken: authToken,
isZoomedFunction: isZoomedFunction,
isZoomedListener: isZoomedListener));
static const String name = 'ImageViewerRoute';
}
@@ -253,7 +325,10 @@ class ImageViewerRouteArgs {
required this.imageUrl,
required this.heroTag,
required this.thumbnailUrl,
required this.asset});
required this.asset,
required this.authToken,
required this.isZoomedFunction,
required this.isZoomedListener});
final Key? key;
@@ -265,9 +340,15 @@ class ImageViewerRouteArgs {
final AssetResponseDto asset;
final String authToken;
final void Function() isZoomedFunction;
final ValueNotifier<bool> isZoomedListener;
@override
String toString() {
return 'ImageViewerRouteArgs{key: $key, imageUrl: $imageUrl, heroTag: $heroTag, thumbnailUrl: $thumbnailUrl, asset: $asset}';
return 'ImageViewerRouteArgs{key: $key, imageUrl: $imageUrl, heroTag: $heroTag, thumbnailUrl: $thumbnailUrl, asset: $asset, authToken: $authToken, isZoomedFunction: $isZoomedFunction, isZoomedListener: $isZoomedListener}';
}
}
@@ -334,12 +415,27 @@ class SearchResultRouteArgs {
}
/// generated route for
/// [CreateSharedAlbumPage]
class CreateSharedAlbumRoute extends PageRouteInfo<void> {
const CreateSharedAlbumRoute()
: super(CreateSharedAlbumRoute.name, path: '/create-shared-album-page');
/// [CreateAlbumPage]
class CreateAlbumRoute extends PageRouteInfo<CreateAlbumRouteArgs> {
CreateAlbumRoute({Key? key, required bool isSharedAlbum})
: super(CreateAlbumRoute.name,
path: '/create-album-page',
args: CreateAlbumRouteArgs(key: key, isSharedAlbum: isSharedAlbum));
static const String name = 'CreateSharedAlbumRoute';
static const String name = 'CreateAlbumRoute';
}
class CreateAlbumRouteArgs {
const CreateAlbumRouteArgs({this.key, required this.isSharedAlbum});
final Key? key;
final bool isSharedAlbum;
@override
String toString() {
return 'CreateAlbumRouteArgs{key: $key, isSharedAlbum: $isSharedAlbum}';
}
}
/// generated route for
@@ -492,3 +588,11 @@ class SharingRoute extends PageRouteInfo<void> {
static const String name = 'SharingRoute';
}
/// generated route for
/// [LibraryPage]
class LibraryRoute extends PageRouteInfo<void> {
const LibraryRoute() : super(LibraryRoute.name, path: 'library-page');
static const String name = 'LibraryRoute';
}

View File

@@ -1,8 +1,9 @@
import 'package:auto_route/auto_route.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/search/providers/search_page_state.provider.dart';
import 'package:immich_mobile/modules/sharing/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
class TabNavigationObserver extends AutoRouterObserver {
@@ -37,6 +38,9 @@ class TabNavigationObserver extends AutoRouterObserver {
ref.read(sharedAlbumProvider.notifier).getAllSharedAlbums();
}
if (route.name == 'LibraryRoute') {
ref.read(albumProvider.notifier).getAllAlbums();
}
ref.watch(serverInfoProvider.notifier).getServerVersion();
}
}

View File

@@ -0,0 +1,4 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
final apiServiceProvider = Provider((ref) => ApiService());

View File

@@ -1,8 +1,5 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:openapi/api.dart';
final apiServiceProvider = Provider((ref) => ApiService());
class ApiService {
late ApiClient _apiClient;
@@ -15,7 +12,6 @@ class ApiService {
setEndpoint(String endpoint) {
_apiClient = ApiClient(basePath: endpoint);
userApi = UserApi(_apiClient);
authenticationApi = AuthenticationApi(_apiClient);
albumApi = AlbumApi(_apiClient);
@@ -25,6 +21,6 @@ class ApiService {
}
setAccessToken(String accessToken) {
_apiClient.addDefaultHeader('Authorization', 'bearer $accessToken');
_apiClient.addDefaultHeader('Authorization', 'Bearer $accessToken');
}
}

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
@@ -11,6 +12,7 @@ final serverInfoServiceProvider = Provider(
class ServerInfoService {
final ApiService _apiService;
ServerInfoService(this._apiService);
Future<ServerInfoResponseDto?> getServerInfo() async {

View File

@@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart';
import 'package:http_parser/http_parser.dart';
import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:immich_mobile/utils/files_helper.dart';
import 'package:openapi/api.dart';

View File

@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/home/providers/home_page_state.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -18,6 +19,7 @@ class TabControllerPage extends ConsumerWidget {
const HomeRoute(),
SearchRoute(),
const SharingRoute(),
const LibraryRoute()
],
builder: (context, child, animation) {
final tabsRouter = AutoTabsRouter.of(context);
@@ -34,12 +36,14 @@ class TabControllerPage extends ConsumerWidget {
bottomNavigationBar: isMultiSelectEnable
? null
: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
backgroundColor: immichBackgroundColor,
selectedLabelStyle: const TextStyle(
fontSize: 15,
fontSize: 13,
fontWeight: FontWeight.w600,
),
unselectedLabelStyle: const TextStyle(
fontSize: 15,
fontSize: 13,
fontWeight: FontWeight.w600,
),
currentIndex: tabsRouter.activeIndex,
@@ -59,6 +63,12 @@ class TabControllerPage extends ConsumerWidget {
label: 'tab_controller_nav_sharing'.tr(),
icon: const Icon(Icons.group_outlined),
),
BottomNavigationBarItem(
label: 'tab_controller_nav_library'.tr(),
icon: const Icon(
Icons.photo_album_outlined,
),
)
],
),
),

View File

@@ -29,6 +29,7 @@ doc/DeviceTypeEnum.md
doc/ExifResponseDto.md
doc/LoginCredentialDto.md
doc/LoginResponseDto.md
doc/LogoutResponseDto.md
doc/RemoveAssetsDto.md
doc/SearchAssetDto.md
doc/ServerInfoApi.md
@@ -37,6 +38,7 @@ doc/ServerPingResponse.md
doc/ServerVersionReponseDto.md
doc/SignUpDto.md
doc/SmartInfoResponseDto.md
doc/ThumbnailFormat.md
doc/UpdateAlbumDto.md
doc/UpdateDeviceInfoDto.md
doc/UpdateUserDto.md
@@ -83,6 +85,7 @@ lib/model/device_type_enum.dart
lib/model/exif_response_dto.dart
lib/model/login_credential_dto.dart
lib/model/login_response_dto.dart
lib/model/logout_response_dto.dart
lib/model/remove_assets_dto.dart
lib/model/search_asset_dto.dart
lib/model/server_info_response_dto.dart
@@ -90,6 +93,7 @@ lib/model/server_ping_response.dart
lib/model/server_version_reponse_dto.dart
lib/model/sign_up_dto.dart
lib/model/smart_info_response_dto.dart
lib/model/thumbnail_format.dart
lib/model/update_album_dto.dart
lib/model/update_device_info_dto.dart
lib/model/update_user_dto.dart
@@ -97,4 +101,3 @@ lib/model/user_count_response_dto.dart
lib/model/user_response_dto.dart
lib/model/validate_access_token_response_dto.dart
pubspec.yaml
test/validate_access_token_response_dto_test.dart

View File

@@ -89,6 +89,7 @@ Class | Method | HTTP request | Description
*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload |
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login |
*AuthenticationApi* | [**logout**](doc//AuthenticationApi.md#logout) | **POST** /auth/logout |
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
*DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
@@ -100,6 +101,7 @@ Class | Method | HTTP request | Description
*UserApi* | [**getAllUsers**](doc//UserApi.md#getallusers) | **GET** /user |
*UserApi* | [**getMyUserInfo**](doc//UserApi.md#getmyuserinfo) | **GET** /user/me |
*UserApi* | [**getProfileImage**](doc//UserApi.md#getprofileimage) | **GET** /user/profile-image/{userId} |
*UserApi* | [**getUserById**](doc//UserApi.md#getuserbyid) | **GET** /user/info/{userId} |
*UserApi* | [**getUserCount**](doc//UserApi.md#getusercount) | **GET** /user/count |
*UserApi* | [**updateUser**](doc//UserApi.md#updateuser) | **PUT** /user |
@@ -129,6 +131,7 @@ Class | Method | HTTP request | Description
- [ExifResponseDto](doc//ExifResponseDto.md)
- [LoginCredentialDto](doc//LoginCredentialDto.md)
- [LoginResponseDto](doc//LoginResponseDto.md)
- [LogoutResponseDto](doc//LogoutResponseDto.md)
- [RemoveAssetsDto](doc//RemoveAssetsDto.md)
- [SearchAssetDto](doc//SearchAssetDto.md)
- [ServerInfoResponseDto](doc//ServerInfoResponseDto.md)
@@ -136,6 +139,7 @@ Class | Method | HTTP request | Description
- [ServerVersionReponseDto](doc//ServerVersionReponseDto.md)
- [SignUpDto](doc//SignUpDto.md)
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
- [ThumbnailFormat](doc//ThumbnailFormat.md)
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
- [UpdateUserDto](doc//UpdateUserDto.md)

View File

@@ -306,7 +306,7 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **removeAssetFromAlbum**
> removeAssetFromAlbum(albumId, removeAssetsDto)
> AlbumResponseDto removeAssetFromAlbum(albumId, removeAssetsDto)
@@ -325,7 +325,8 @@ final albumId = albumId_example; // String |
final removeAssetsDto = RemoveAssetsDto(); // RemoveAssetsDto |
try {
api_instance.removeAssetFromAlbum(albumId, removeAssetsDto);
final result = api_instance.removeAssetFromAlbum(albumId, removeAssetsDto);
print(result);
} catch (e) {
print('Exception when calling AlbumApi->removeAssetFromAlbum: $e\n');
}
@@ -340,7 +341,7 @@ Name | Type | Description | Notes
### Return type
void (empty response body)
[**AlbumResponseDto**](AlbumResponseDto.md)
### Authorization
@@ -349,7 +350,7 @@ void (empty response body)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)

View File

@@ -311,7 +311,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAssetThumbnail**
> Object getAssetThumbnail(assetId)
> Object getAssetThumbnail(assetId, format)
@@ -327,9 +327,10 @@ import 'package:openapi/api.dart';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final format = ; // ThumbnailFormat |
try {
final result = api_instance.getAssetThumbnail(assetId);
final result = api_instance.getAssetThumbnail(assetId, format);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getAssetThumbnail: $e\n');
@@ -341,6 +342,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**format** | [**ThumbnailFormat**](.md)| | [optional]
### Return type

View File

@@ -11,6 +11,7 @@ Method | HTTP request | Description
------------- | ------------- | -------------
[**adminSignUp**](AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
[**login**](AuthenticationApi.md#login) | **POST** /auth/login |
[**logout**](AuthenticationApi.md#logout) | **POST** /auth/logout |
[**validateAccessToken**](AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
@@ -96,6 +97,43 @@ No authorization required
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **logout**
> LogoutResponseDto logout()
### Example
```dart
import 'package:openapi/api.dart';
final api_instance = AuthenticationApi();
try {
final result = api_instance.logout();
print(result);
} catch (e) {
print('Exception when calling AuthenticationApi->logout: $e\n');
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**LogoutResponseDto**](LogoutResponseDto.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **validateAccessToken**
> ValidateAccessTokenResponseDto validateAccessToken()

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