Compare commits

...

80 Commits

Author SHA1 Message Date
Immich Release Bot
b37162099e Version v1.46.1 2023-02-10 04:24:05 +00:00
Zack Pollard
dab74662e9 fix(server): fk constraint violation when updating to 1.46 with deleted users and albums (#1716)
Fixes #1715
2023-02-10 04:11:04 +00:00
bo0tzz
3d103046bc fix(ci): Add missing checkout step to prepare-release workflow (#1709) 2023-02-09 13:02:30 -06:00
Alex Tran
3ca62d9c55 Add changelogs 2023-02-09 13:00:28 -06:00
Immich Release Bot
2cd45ed1de Version v1.46.0 2023-02-09 17:59:47 +00:00
Alex
263598f2cf fix(server): get shared link album info doesn't contain owner property (#1708) 2023-02-09 11:55:24 -06:00
Alex
1d1d71c779 [Localizely] Translations update (#1707) 2023-02-09 11:36:46 -06:00
Matthias Rupp
fd13265131 feat(mobile): Home screen customization options (#1563)
* Try staggered layout for home page

* Introduce setting for dynamic layout

* Fix some provider related bugs

* Make asset grouping configurable

* Add translation keys, refactor group title

* Rename enum values

* Fix enum names

* Reformat long if statement

* Fix timezone related bug

* Minor clean up

* Fix unit test

* Add second assets check back to home screen
2023-02-09 11:35:44 -06:00
Fynn Petersen-Frey
911c35a7f1 refactor(mobile): add Isar DB & Store class (#1574)
* refactor(mobile): add Isar DB & Store class

new Store: globally accessible key-value store like Hive (but based on Isar)

replace first few places of Hive usage with the new Store

* reduce max. DB size to prevent errors on older iOS devices

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-02-09 11:32:08 -06:00
Michel Heusschen
adb265794c feat(web): allow uploading more file types (#1570)
* feat(web): allow uploading more file types

* fix(web): make filename extension lowercase
2023-02-09 10:08:19 -06:00
Alex
8c20d8cb3d fix(server): Create album response doesn't have owner property as required (#1704) 2023-02-08 17:15:32 -06:00
Skyler Mäntysaari
43359f1d26 feat(proxy): Initial IPv6 support (#1577) 2023-02-08 13:53:48 -06:00
martyfuhry
dc9da7480c feat(mobile): Responsive layout improvements with a navigation rail and album grid (#1583) 2023-02-08 13:42:45 -06:00
Alex Tran
18647203cc fix(GitHub): feature request form has wrong type for textarea 2023-02-07 22:19:50 -06:00
Alex Tran
be8f2c01a2 fix(GitHub): feature request template 2023-02-07 22:18:42 -06:00
Alex
c8fffe4ade feat(GitHub): update bug and feature request template (#1584)
* dev: Reusing template from Home Assistant

* dev: add bug report template

* fix: template

* dev: change type

* dev:

* dev: add default labels

* dev: Add default title

* dev: add feature request template

* remove feature request from markdown

* dev: frontmatter
2023-02-07 22:09:15 -06:00
Alex
43fd7737f1 chore(server): make owner as required response for AlbumResponseDto (#1579) 2023-02-07 15:11:14 -06:00
dependabot[bot]
fb4969d5d1 chore(deps): bump docker/setup-buildx-action from 2.4.0 to 2.4.1 (#1575)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.4.0...v2.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-07 08:31:30 -06:00
Alex
3cc4af5947 chore(server) Add user FK to album entity (#1569) 2023-02-06 20:47:06 -06:00
Zack Pollard
ac39ebddc0 chore(server): update package-lock.json to match package.json (#1573) 2023-02-06 22:40:31 +00:00
Alex
29bb1f7ef2 feat(server): add updatedAt to Asset, Album and User (#1566)
* feat: add updatedAt info to DTO and generate api

* chore: remove unsued file

* chore: Add update statement to add/remove asset/user to album

* fix: test
2023-02-06 10:24:58 -06:00
martyfuhry
b8d2f5b373 feat(mobile): Multiselect add to favorite from the timeline (#1558)
* multiselect add to favorites
2023-02-06 07:59:56 -05:00
Fynn Petersen-Frey
2139853dd9 refactor(mobile): introduce Album & User classes (#1561)
replace usages of AlbumResponseDto with Album
replace usages of UserResponseDto with User
2023-02-06 01:13:32 -06:00
martyfuhry
527aa61a87 fix(mobile): Added flutter native splash and splash screens (#1520)
* rebasing

* added launch background image to repository

---------

Co-authored-by: Marty Fuhry <marty@fuhry.farm>
2023-02-06 00:41:07 -06:00
James
4261fc8a04 feat(deployment): support docker secrets (#1254)
* Support secrets

* Rewrite to support sh

* Remove JWT_SECRET
2023-02-05 23:38:06 -06:00
Alex
7dbddba757 chore(server): remove token when logged out (#1560)
* chore(mobile): invoke logout() on mobile app

* feat: add mechanism to delete token from logging out endpoint

* fix: set state after login sequence success

* fix: not removing token when logging out from OAuth

* fix: prettier

* refactor: using accessTokenId to delete

* chore: pr comments

* fix: test

* fix: test threshold
2023-02-05 23:31:16 -06:00
martyfuhry
16183791f3 feat(mobile): Removed stay logged in checkbox and made it enabled by default (#1550)
* removed stay logged in checkbox and made it enabled by default

* adds padding to login button

* removed all isSaveLogin

* fix: logout would re-login with previous credential upon app restart

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-02-05 08:07:02 -06:00
martyfuhry
f38c7a4b7e feat(mobile): Tap to enter immersive mode on gallery viewer (#1546) 2023-02-05 06:57:07 -06:00
Matthias Rupp
bb0b2a1f53 feat(mobile): Library page rework (album sorting, favorites) (#1501)
* Add album sorting

* Change AppBar to match photos page behaviour

* Add buttons

* First crude implementation of the favorites page

* Clean up

* Add favorite button

* i18n

* Add star indicator to thumbnail

* Add favorite logic to separate provider and fix favorite behavior in album

* Review feedback (Add isFavorite variable)

* dev: style buttons

* dev: styled drop down button

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-02-04 21:25:11 -06:00
Fynn Petersen-Frey
0048662182 refactor(mobile): reworked Asset, store all required fields from local & remote (#1539)
replace usage of AssetResponseDto with Asset

Add new class ExifInfo to store data from ExifResponseDto
2023-02-04 14:42:42 -06:00
Devin Buhl
7bd2455175 ci(docker): pass labels to push (#1545) 2023-02-03 14:22:00 -06:00
Alex
43e49f36b7 chore(release) post release tasks (#1543) 2023-02-03 14:21:49 -06:00
bo0tzz
e4b3479779 fix(ci): Run release version bump before mobile build (#1542)
* fix(ci): Run release version bump before mobile build

* feat(ci): Add ref input to build-mobile

* feat(ci): Pass new tag ref to build_mobile flow in prepare-release

* chore(ci): Naming
2023-02-03 12:39:56 -06:00
Immich Release Bot
6bac9c7e8f Version v1.45.0 2023-02-03 16:41:11 +00:00
martyfuhry
ff3cde4dfb feat(mobile): Fullscreen image effects (#1529)
* fullscreen image effects

* toggles app bar instead of hides on tap

* edgeToEdge mode to render beneath navbar on android

* fixed appbar size

* fixed safearea for video and added opacity to appbar in gallery

* wrapped in black container to fix artifact on iOS

* changed to black

* added scaffold back woops
2023-02-03 10:26:05 -06:00
Jason Rasmussen
7aab84f2d9 chore(server): remove unused code (#1513)
* chore(server): remove unused code

* chore(server): unused imports
2023-02-03 09:18:33 -06:00
Jason Rasmussen
3a940711eb fix(server): duplicate asset (#1540) 2023-02-03 09:17:36 -06:00
Jason Rasmussen
2b0b2bb1ae refactor(server): download file (#1512)
* refactor(server): download file

* chore: generate open-api and remove unused refs

* chore(server): tests

* chore: remove unused code
2023-02-03 09:16:25 -06:00
Jason Rasmussen
e39507552f build: pump open-api specification (#1533) 2023-02-02 22:53:52 -06:00
Alex
b019ab79f9 fix(server): id of the deleted asset wasn't passed to the response (#1532) 2023-02-02 22:37:39 -06:00
Zack Pollard
43da8c2a72 chore: reduce docker image size (#1523)
* chore: remove @tensorflow/tfjs-node-gpu as it is unused

* chore: remove ffmpeg from machine-learning docker image

* chore: remove unneeded dependencies + move dev dependencies in server

* chore: reduce server image size

* chore: machine-learning remove extraneous dependencies

* chore: web remove extraneous dependencies

* chore: web Dockerfile reduce production image size

* chore: add exiftool-vendored.pl as a dependency
2023-02-02 21:28:34 -06:00
martyfuhry
0b65cea6fd fixed local asset thumbnail size and eliminated fade in duration of loading assets (#1525) 2023-02-02 13:29:52 -06:00
martyfuhry
a1806390b0 fixes dark mode color for invite to album app bar (#1524) 2023-02-02 12:31:44 -06:00
martyfuhry
5d6559e839 fix(mobile): back button while multiselecting showing the last selected image (#1521) 2023-02-02 12:20:26 -06:00
Alex
29c79ad1d8 chore(release) mobile release related changes 1.44 (#1522)
* relase note

* Add changelogs
2023-02-02 09:56:16 -06:00
Immich Release Bot
d77a1aba7a Version v1.44.0 2023-02-02 07:13:46 +00:00
Alex Tran
9e21b16553 Revert "Version v1.44.1"
This reverts commit ab2c019a7a.
2023-02-02 01:03:01 -06:00
Alex
dcb56ae775 fix(ci) pump script reset minor and patch based on major and minor pump (#1515) 2023-02-02 00:53:22 -06:00
Immich Release Bot
ab2c019a7a Version v1.44.1 2023-02-02 06:43:35 +00:00
Alex
eb408d4858 Inherit secrect in workflow (#1514) 2023-02-02 00:15:27 -06:00
Alex
4f38851880 fix(CI): fix mobile build artifact with proper signing (#1504) 2023-02-01 21:50:07 -06:00
martyfuhry
2c356ec87f fix(mobile) uses clamping scroll physics on android (#1503) 2023-02-01 16:29:32 -06:00
Jason Rasmussen
bb84464216 refactor(server): device info (#1490)
* refactor(server): device info

* fix: export device service

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-02-01 14:55:06 -06:00
Jason Rasmussen
32b9e0bad4 chore: editorconfig (#1505) 2023-02-01 14:38:47 -06:00
martyfuhry
02f5a86ee9 (fix)mobile: Improve the gallery to improve scale, double tap, and swipe gesture detection (#1502)
* photoviewgallery

* stiffer scrolling to react more like google photos

* adds a dx threshhold for the swipe/up down from the original dropped point

* stopped wrapping imageview in gallery viewer to avoid the double photoview issue. breaks imageview page pinch-to-zoom, so i need to fix that for other callers

* refactors gallery view to use remoteimage directly and breaks imageviewpage

* removed image_viewer_page

* adds minscale

* adds photo_view to repository

* double tap to zoom out with hacked commit

* double tapping!

* got up and down swipe gestures working

* fixed wrong cache and headers in image providers

* fixed image quality and added videos back in

* local loading asset image fix

* precaches images

* fixes lint errors

* deleted remote_photo_view and more linters

* fixes scale

* load preview and load original

* precache does original / preview as well

* refactored image providers to nice functions and added JPEG thumbnail format to remote image thumbnail lookup

* moved photo_view to shared/ui/

* three stage loading with webp and fixes some thumbnail fits

* fixed local thumbnail

* fixed paging in iOS
2023-02-01 10:59:34 -06:00
Alex
391bf052e4 Revert "fix(mobile): Generate 1 splash screen on Android (#1443)" (#1498)
This reverts commit 00630bd4a3.
2023-01-31 22:05:54 -06:00
Jason Rasmussen
d2a9363fc5 refactor(server): auth guard (#1472)
* refactor: auth guard

* chore: move auth guard to middleware

* chore: tests

* chore: remove unused code

* fix: migration to uuid without dataloss

* chore: e2e tests

* chore: removed unused guards
2023-01-31 12:11:49 -06:00
dependabot[bot]
68af4cd5ba chore(deps): bump docker/build-push-action from 3.3.0 to 4.0.0 (#1492)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.3.0 to 4.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.3.0...v4.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-31 10:01:22 -06:00
dependabot[bot]
c82dcb11e1 chore(deps): bump docker/setup-buildx-action from 2.3.0 to 2.4.0 (#1493)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.3.0 to 2.4.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.3.0...v2.4.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-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>
2023-01-31 10:00:20 -06:00
Fynn Petersen-Frey
d0f8d8d1f9 fix(server): re-enable Redis unix socket support (#1494) 2023-01-31 09:59:59 -06:00
bo0tzz
6a852332de feat(build): Include apk in prepare-release flow (#1496) 2023-01-31 09:59:37 -06:00
Alex
830fec0c29 feat(mobile) reused HTTP client for uploading assets (#1487)
* feat(mobile) reused HTTP client for uploading assets

* remove unused comments
2023-01-30 16:00:03 -06:00
dependabot[bot]
aa68d35f42 chore(deps): bump docker/setup-buildx-action from 2.2.1 to 2.3.0 (#1483)
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v2.2.1...v2.3.0)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-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>
2023-01-30 10:15:33 -06:00
dependabot[bot]
6e6fe9bc87 chore(deps): bump actions/upload-artifact from 1 to 3 (#1482)
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 1 to 3.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v1...v3)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 10:15:18 -06:00
dependabot[bot]
29f68e6dbb chore(deps): bump actions/checkout from 2 to 3 (#1481)
Bumps [actions/checkout](https://github.com/actions/checkout) from 2 to 3.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-30 10:14:58 -06:00
Jason Rasmussen
9428b2576b refactor(server): asset service - upload asset (#1438)
* refactor: asset upload

* refactor: background service

* chore: tests

* Regenerate api

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-01-30 10:14:13 -06:00
martyfuhry
3210302ecd fix(mobile): Fix back button closing the app from multiselection in Android (#1477)
* fixes back button multiselection on android in main timeline

* back button on multiselect in album clears selection

* fixed homepage back and refactor future

* not a futureOr
2023-01-29 22:36:26 -06:00
martyfuhry
f23979024a chore(mobile): Update share_file to latest version and migrate to using cross platform shareXFile (#1476)
* update share_plus and use sharexfile

* rename variable
2023-01-29 20:46:30 -06:00
Alex
870a65fa6d fix(server): Cannot remove album with shared links (#1479) 2023-01-29 20:13:34 -06:00
hydazz
199eb20b66 Update unraid.md (#1475) 2023-01-29 09:44:27 -06:00
Alex
91114e5aa0 fix(mobile) add and delete asset in album doesn' (#1474)
t update count in list
2023-01-28 21:57:13 -06:00
Alex
dfbc831525 fix(web) long album name break styling for shared album card (#1473) 2023-01-28 21:10:05 -06:00
Alex
1a640609c7 chore(mobile): Build and sign APK in GitHub Action (#1471)
* chore(mobile): Build and sign APK in GitHUb Action

* fix-1: working directory

* fix-2: working directory

* fix-3: key ALIAS

* fix-4: build apk

* fix-5: naming
2023-01-28 17:24:42 -06:00
martyfuhry
00630bd4a3 fix(mobile): Generate 1 splash screen on Android (#1443) 2023-01-28 16:51:38 -06:00
Skyler Mäntysaari
fb408d7aa3 chore(server): cookie changes to SameSite=Lax (#1467)
* fix(server/cookie): cookie should have SameSite=Lax.

* Forgot to update tests.
2023-01-28 16:33:03 -06:00
martyfuhry
6b5d6e4091 fix(mobile): top padding missing from drag handle in add to album bottom sheet (#1462) 2023-01-28 15:05:28 -06:00
Alex
a287be1f0c chore: Update .gitignore and openapi version (#1461)
* chore: Update gitignore to ignore mobile/openapi/pubspec.lock

* Update openapi version
2023-01-28 09:19:22 -06:00
Jason Rasmussen
42a3149fe3 refactor(server): move asset upload job to domain (#1434)
* refactor: move to domain

* refactor: rename method

* Update comments

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-01-27 23:57:37 -06:00
Kiel Hurley
5aee5c0fb8 feat(web): More localisation (#1441)
* File size localisation

* Localisation for sidebar tooltips

* Localisation for active/waiting jobs

* Localisation for selected item counts

* Prettier

* Ignore Jest coverage directory for Prettier
2023-01-27 23:57:25 -06:00
spiralham
12ecf366b0 chore(mobile): Add adaptive launcher icon for Android (#1457) 2023-01-27 23:18:42 -06:00
317 changed files with 10426 additions and 4306 deletions

19
.editorconfig Normal file
View File

@@ -0,0 +1,19 @@
# Editor configuration, see https://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.{ts,js}]
quote_type = single
[*.{md,mdx}]
max_line_length = off
trim_trailing_whitespace = false
[*.{yml,yaml}]
quote_type = double

View File

@@ -0,0 +1,24 @@
title: "[Feature] <feature-name-goes-here>"
labels: ["feature"]
body:
- type: markdown
attributes:
value: |
Please use this form to request new feature for Immich
- type: textarea
id: feature
attributes:
label: The feature
validations:
required: true
- type: checkboxes
validations:
required: true
attributes:
label: Platform
options:
- label: Server
- label: Web
- label: Mobile

View File

@@ -1,46 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: '[BUG] <title>'
labels: bug, need triage
assignees: ''
---
<!--
Note: Please search to see if an issue already exists for the bug you encountered.
-->
**Describe the bug**
A clear and concise description of what the bug is.
**Task List**
*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.
- [ ] I have included my `docker-compose` file.
- [ ] I have included my redacted `.env` file.
- [ ] I have included information on my machine, and environment.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**System**
- Phone OS [iOS, Android]: `<version>`
- Server Version: `<version>`
- Mobile App Version: `<version>`
**Additional context**
Add any other context about the problem here.

100
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@@ -0,0 +1,100 @@
name: Report an issue with Immich
description: Report an issue with Immich
labels: ["bug", "need triage"]
title: "[BUG] <title>"
body:
- type: markdown
attributes:
value: |
This issue form is for reporting bugs only!
If you have a feature or enhancement request, please use the [feature request][fr] section of our [GitHub Discussions][fr].
[fr]: https://github.com/immich-app/immich/discussions/new?category=feature-request
- type: textarea
validations:
required: true
attributes:
label: The bug
description: >-
Describe the issue you are experiencing here, to communicate to the
maintainers. Tell us what you were trying to do and what happened.
Provide a clear and concise description of what the problem is.
- type: markdown
attributes:
value: |
## Environment
- type: input
validations:
required: true
attributes:
label: The OS that Immich Server is running on
placeholder: Ubuntu 22.10, Debian, Arch...etc
- type: input
id: version
validations:
required: true
attributes:
label: Version of Immich Server
placeholder: v1.0.0
- type: input
validations:
required: true
attributes:
label: Version of Immich Mobile App
placeholder: v1.0.0
- type: checkboxes
validations:
required: true
attributes:
label: Platform with the issue
options:
- label: Server
- label: Web
- label: Mobile
- type: textarea
validations:
required: true
attributes:
label: Your docker-compose.yml content
render: YAML
- type: textarea
validations:
required: true
attributes:
label: Your .env content
description: Please provide the redacted .env content of your setup
render: Shell
- type: textarea
id: repro
attributes:
label: Reproduction steps
description: "How do you trigger this bug? Please walk us through it step by step."
value: |
1.
2.
3.
...
render: bash
validations:
required: true
- type: textarea
attributes:
label: Additional information
description: >
If you have any additional information for us, use the field below.
- type: markdown
attributes:
value: Thank you for submitting the form

View File

@@ -1 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: I have a question or need support
url: https://discord.gg/D8JsnBEuKb
about: We use GitHub for tracking bugs, please check out our Discord channel for freaky fast support.
- name: Feature Request
url: https://github.com/immich-app/immich/discussions/new?category=feature-request
about: Please use our GitHub Discussion for making feature requests.
- name: I'm unsure where to go
url: https://discord.gg/D8JsnBEuKb
about: If you are unsure where to go, then joining our Discord is recommended; Just ask!

View File

@@ -1,32 +0,0 @@
name: Feature Request
description: Request a feature that you would like for the app
title: "[Feature]: "
labels: ["feature", "need triage"]
assignees:
- ""
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this feature request!
- type: textarea
id: feature-detail
attributes:
label: Feature detail
placeholder: Describe the feature you would like to see for the app
validations:
required: true
- type: dropdown
id: platform
attributes:
label: Platform
description: Choose the platform for the feature
options:
- Web
- Mobile App
- Server
validations:
required: true

67
.github/workflows/build-mobile.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: Build Mobile
on:
workflow_dispatch:
workflow_call:
inputs:
ref:
required: false
type: string
pull_request:
push:
branches: [main]
jobs:
build-sign-android:
name: Build and sign Android
runs-on: macos-12
steps:
- name: Determine ref
id: get-ref
run: |
input_ref="${{ inputs.ref }}"
github_ref="${{ github.sha }}"
ref="${input_ref:-$github_ref}"
echo "ref=$ref" >> $GITHUB_OUTPUT
- uses: actions/checkout@v3
with:
ref: ${{ steps.get-ref.outputs.ref }}
- uses: actions/setup-java@v3
with:
distribution: "zulu"
java-version: "12.x"
cache: "gradle"
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.3.10"
cache: true
- name: Create the Keystore
env:
KEY_JKS: ${{ secrets.KEY_JKS }}
working-directory: ./mobile
run: echo $KEY_JKS | base64 -d > android/key.jks
- name: Get Packages
working-directory: ./mobile
run: flutter pub get
- name: Build Android App Bundle
working-directory: ./mobile
env:
ALIAS: ${{ secrets.ALIAS }}
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
ANDROID_STORE_PASSWORD: ${{ secrets.ANDROID_STORE_PASSWORD }}
run: flutter build apk --release
- name: Publish Android Artifact
uses: actions/upload-artifact@v3
with:
name: release-apk-signed
path: mobile/build/app/outputs/flutter-apk/app-release.apk

View File

@@ -34,7 +34,7 @@ jobs:
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.2.1
uses: docker/setup-buildx-action@v2.4.1
# Workaround to fix error:
# failed to push: failed to copy: io: read/write on closed pipe
# See https://github.com/docker/build-push-action/issues/761
@@ -89,7 +89,7 @@ jobs:
fi
- name: Build and push image
uses: docker/build-push-action@v3.3.0
uses: docker/build-push-action@v4.0.0
with:
context: ${{ matrix.context }}
platforms: linux/arm/v7,linux/amd64,linux/arm64
@@ -98,3 +98,4 @@ jobs:
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/immich-build-cache:${{matrix.image}}
cache-to: ${{ steps.cache-target.outputs.cache-to }}
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}

View File

@@ -4,23 +4,26 @@ on:
workflow_dispatch:
inputs:
serverBump:
description: 'Bump server version'
description: "Bump server version"
required: true
default: 'false'
default: "false"
type: choice
options:
- false
- minor
- patch
- "false"
- minor
- patch
mobileBump:
description: 'Bump mobile build number'
description: "Bump mobile build number"
required: false
type: boolean
jobs:
tag_release:
bump_version:
runs-on: ubuntu-latest
outputs:
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
steps:
- name: Checkout
uses: actions/checkout@v3
@@ -29,8 +32,9 @@ jobs:
- name: Bump version
run: misc/release/pump-version.sh -s "${{ inputs.serverBump }}" -m "${{ inputs.mobileBump }}"
- name: Commit and tag
id: push-tag
uses: EndBug/add-and-commit@v9
with:
author_name: Immich Release Bot
@@ -39,6 +43,28 @@ jobs:
tag: ${{ env.IMMICH_VERSION }}
push: true
build_mobile:
uses: ./.github/workflows/build-mobile.yml
needs: bump_version
secrets: inherit
with:
ref: ${{ needs.bump_version.outputs.ref }}
prepare_release:
runs-on: ubuntu-latest
needs: build_mobile
steps:
- name: Checkout
uses: actions/checkout@v3
with:
token: ${{ secrets.ORG_RELEASE_TOKEN }}
- name: Download APK
uses: actions/download-artifact@v3
with:
name: release-apk-signed
- name: Create draft release
uses: softprops/action-gh-release@v1
with:
@@ -49,3 +75,4 @@ jobs:
files: |
docker/docker-compose.yml
docker/example.env
*.apk

View File

@@ -44,7 +44,7 @@ jobs:
name: Run mobile unit tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
with:
@@ -58,7 +58,7 @@ jobs:
name: Run mobile end-to-end integration tests
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- uses: actions/setup-java@v3
with:
distribution: 'adopt'

2
.gitignore vendored
View File

@@ -8,3 +8,5 @@ uploads
coverage
mobile/gradle.properties
mobile/openapi/pubspec.lock
mobile/*.jks

View File

@@ -4,7 +4,25 @@ sidebar_position: 60
# Unraid
Immich can easily be installed and updated on Unraid using the [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps.
Immich can easily be installed and updated on Unraid via:
1. [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps
2. Community made template on the Unraid Community Apps
## Community Applications Template
:::info
- The Unraid template uses a community made image and is not officially supported by Immich
:::
In order to install Immich from the Unraid CA, you will need an existing Redis and PostgreSQL 14 container, If you do not already have Redis or PostgreSQL you can install them from the Unraid CA, just make sure you choose PostgreSQL **14**.
Once you have Redis and PostgreSQL running, search for Immich on the Unraid CA, Choose either of the templates listed and fill out the example variables.
For more information about setting up the community image see [here](https://github.com/imagegenius/docker-immich#application-setup)
## Docker-Compose Method (Official)
:::info

View File

@@ -1,4 +1,3 @@
FROM node:16-bullseye-slim as builder
ARG DEBIAN_FRONTEND=noninteractive
@@ -6,7 +5,7 @@ ARG DEBIAN_FRONTEND=noninteractive
WORKDIR /usr/src/app
RUN apt-get update
RUN apt-get install gcc g++ make cmake python3 python3-pip ffmpeg -y
RUN apt-get install gcc g++ make cmake python3 python3-pip -y
COPY package.json package-lock.json ./
@@ -15,24 +14,17 @@ RUN npm rebuild @tensorflow/tfjs-node --build-from-source
COPY . .
FROM builder as prod
RUN npm run build
RUN npm prune --omit=dev
FROM node:16-bullseye-slim
ARG DEBIAN_FRONTEND=noninteractive
WORKDIR /usr/src/app
RUN apt-get update \
&& apt-get install -y ffmpeg \
&& rm -rf /var/cache/apt/lists
COPY --from=prod /usr/src/app/node_modules ./node_modules
COPY --from=prod /usr/src/app/dist ./dist

File diff suppressed because it is too large Load Diff

View File

@@ -23,19 +23,9 @@
"dependencies": {
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/mapped-types": "^1.0.1",
"@nestjs/platform-express": "^8.0.0",
"@tensorflow-models/coco-ssd": "^2.2.2",
"@tensorflow-models/mobilenet": "^2.1.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",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0"
"@tensorflow/tfjs-node": "^3.19.0"
},
"devDependencies": {
"@nestjs/cli": "^8.2.4",
@@ -52,6 +42,7 @@
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.2.5",
"prettier": "^2.3.2",
"rimraf": "^3.0.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "^27.0.3",

View File

@@ -6,7 +6,7 @@
# usage: './scripts/pump-version.sh -s <major|minor|patch> <-m>
#
# examples:
# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50
# ./scripts/pump-version.sh -s major # 1.0.0+50 => 2.0.0+50
# ./scripts/pump-version.sh -s minor -m # 1.0.0+50 => 1.1.0+51
# ./scripts/pump-version.sh -m # 1.0.0+50 => 1.0.0+51
#
@@ -16,10 +16,12 @@ MOBILE_PUMP="false"
while getopts 's:m:' flag; do
case "${flag}" in
s) SERVER_PUMP=${OPTARG} ;;
m) MOBILE_PUMP=${OPTARG} ;;
*) echo "Invalid args"
exit 1 ;;
s) SERVER_PUMP=${OPTARG} ;;
m) MOBILE_PUMP=${OPTARG} ;;
*)
echo "Invalid args"
exit 1
;;
esac
done
@@ -30,8 +32,11 @@ PATCH=$(echo $CURRENT_SERVER | cut -d '.' -f3)
if [[ $SERVER_PUMP == "major" ]]; then
MAJOR=$((MAJOR + 1))
MINOR=0
PATCH=0
elif [[ $SERVER_PUMP == "minor" ]]; then
MINOR=$((MINOR + 1))
PATCH=0
elif [[ $SERVER_PUMP == "patch" ]]; then
PATCH=$((PATCH + 1))
elif [[ $SERVER_PUMP == "false" ]]; then
@@ -54,20 +59,17 @@ else
exit 1
fi
if [ "$CURRENT_SERVER" != "$NEXT_SERVER" ]; then
echo "Pumping Server: $CURRENT_SERVER => $NEXT_SERVER"
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package.json
sed -i "s/^ \"version\": \"$CURRENT_SERVER\",$/ \"version\": \"$NEXT_SERVER\",/" server/package-lock.json
sed -i "s/\"version\": \"$CURRENT_SERVER\",$/\"version\": \"$NEXT_SERVER\",/" server/immich-openapi-specs.json
sed -i "s/\"android\.injected\.version\.name\" => \"$CURRENT_SERVER\",/\"android\.injected\.version\.name\" => \"$NEXT_SERVER\",/" mobile/android/fastlane/Fastfile
sed -i "s/version_number: \"$CURRENT_SERVER\"$/version_number: \"$NEXT_SERVER\"/" mobile/ios/fastlane/Fastfile
fi
if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then
echo "Pumping Mobile: $CURRENT_MOBILE => $NEXT_MOBILE"
@@ -76,4 +78,4 @@ if [ "$CURRENT_MOBILE" != "$NEXT_MOBILE" ]; then
sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml
fi
echo "IMMICH_VERSION=v$NEXT_SERVER" >> $GITHUB_ENV
echo "IMMICH_VERSION=v$NEXT_SERVER" >>$GITHUB_ENV

View File

@@ -59,17 +59,19 @@ android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
def keyAliasVal = System.getenv("ALIAS")
def keyPasswordVal = System.getenv("ANDROID_KEY_PASSWORD")
def storePasswordVal = System.getenv("ANDROID_STORE_PASSWORD")
keyAlias keyAliasVal ? keyAliasVal : keystoreProperties['keyAlias']
keyPassword keyPasswordVal ? keyPasswordVal : keystoreProperties['keyPassword']
storeFile file("../key.jks") ? file("../key.jks") : file(keystoreProperties['storeFile'])
storePassword storePasswordVal ? storePasswordVal : keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@@ -0,0 +1,70 @@
<vector android:height="200dp"
android:viewportHeight="1300"
android:viewportWidth="1300"
android:width="200dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#4081ef"
android:fillType="nonZero"
android:pathData="M578,922.6c-2.2,-0.2 -5.5,-1 -9.7,-2.2c-52.4,-15.7 -99,-46.5 -133.8,-88.5c-8.8,-10.7 -17.2,-22.4 -19.4,-27.5c-8.1,-18.1 -6.3,-38.7 4.8,-55.4c5,-7.5 13.2,-15 20.5,-18.7c1.2,-0.6 54.1,-20 55.8,-20.4c0.5,-0.1 0.5,0.2 -0.3,2.1c-0.7,1.7 -1,3.1 -1.1,5.5l-0.1,3.2l2.8,5.8c8.7,17.9 19.2,32.7 33.2,46.4c6.3,6.2 7.8,7.6 13.8,12.3c22.7,18.1 52,30.7 79.9,34.3c2.5,0.3 5,0.8 5.7,1c2.8,0.9 7.7,-0.8 11,-3.7l1.8,-1.6l-0.2,4.8c-0.1,2.7 -0.6,15.4 -1,28.3c-0.6,20.3 -0.8,24 -1.5,27.5c-3.9,20.7 -18.6,37.5 -38.4,44.1c-4.6,1.5 -8,2.2 -13.1,2.7c-4.6,0.5 -5.9,0.4 -10.7,0Z" />
<path
android:fillColor="#31a452"
android:fillType="nonZero"
android:pathData="M707.3,922.4c-4,-0.4 -9.4,-1.6 -13.2,-2.9c-3.4,-1.2 -10,-4.4 -12.5,-6.1c-10.9,-7.4 -19,-17.9 -23.1,-30c-2.2,-6.7 -2.3,-7.5 -3.3,-36.9c-0.5,-14.9 -0.9,-27.9 -0.9,-28.9l-0,-1.9l2.3,1.8c2.6,2 6.6,3.4 8.5,3.1c0.6,-0.1 3,-0.5 5.3,-0.8c37.7,-5.3 71.2,-22.2 97.4,-49.1c12.2,-12.5 21.4,-25.5 29.9,-42.4l3.5,-7l0,-3.6c0,-3.1 -0.1,-3.8 -1,-5.7c-0.5,-1.2 -0.9,-2.1 -0.9,-2.2c0.2,-0.2 55.3,20.1 56.9,20.9c2.6,1.3 6.6,4.1 9.9,7c9.2,7.7 16.1,19.4 18.8,31.8c0.7,3.1 0.8,4.8 0.8,11.3c0,8.6 -0.5,11.7 -2.9,18.7c-1.7,5 -2.9,7.2 -7.1,13.1c-7.6,11 -15.3,20.5 -25.2,31.2c-32.8,35.4 -76.5,62.5 -123.4,76.3c-8,2.5 -12.4,3 -19.8,2.3Z" />
<path
android:fillColor="#de7fb3"
android:fillType="nonZero"
android:pathData="M623.1,811c-25.9,-4.2 -50.7,-14.9 -71.7,-31c-5.2,-4 -8.7,-7.1 -14.1,-12.4c-12.7,-12.5 -21.9,-24.9 -30.5,-41.4c-2.3,-4.4 -2.4,-4.7 -2.4,-7.1c0,-8.8 8.5,-15.2 16.9,-12.7c5.6,1.7 9.6,6.8 9.7,12.2c0,2.6 -0.8,4.6 -2.6,6.2c-1.2,1.1 -3.2,1.9 -4.6,1.9c-1.2,0 -3.3,-0.8 -4.3,-1.6c-2.1,-1.8 -2,-1 0.4,3.2c19.3,33.8 52.3,59.1 90,69.1c5.7,1.5 11.5,2.7 11.8,2.4c0.1,-0.1 -0.4,-0.8 -1.3,-1.6c-5.1,-4.5 -2.3,-11.7 5,-12.8c5.4,-0.8 11.4,2.7 13.9,8c0.8,1.7 1,2.5 1,5.3c0,2.8 -0.1,3.5 -1,5.3c-2,4.3 -6.8,7.9 -10.3,7.8c-0.9,-0.1 -3.6,-0.5 -5.9,-0.8Z" />
<path
android:fillColor="#4081ef"
android:fillType="nonZero"
android:pathData="M665.1,811.2c-3.4,-1.3 -6.4,-4.3 -7.8,-8.1c-1.1,-2.9 -0.9,-7.3 0.5,-10.2c2.6,-5.3 8.7,-8.5 14.4,-7.5c2.9,0.5 4.7,1.9 6,4.3c0.8,1.6 1,2.2 0.8,3.6c-0.3,2.2 -0.9,3.3 -2.7,4.8c-0.8,0.7 -1.4,1.4 -1.3,1.5c0.5,0.5 13.4,-2.7 21.3,-5.4c33.6,-11.3 62.5,-35.1 80.4,-66.1c2.5,-4.4 2.6,-5 0.5,-3.2c-2.8,2.4 -7,1.9 -9.6,-1c-4,-4.6 -0.7,-13.8 6.1,-16.9c2,-0.9 2.7,-1 5.5,-1c2.9,0 3.5,0.1 5.6,1.1c4.4,2.1 7.4,6.4 7.8,11c0.2,2.2 0.1,2.3 -2.2,6.9c-23,45.9 -67,78.1 -117.2,85.9c-5.5,0.9 -6.3,1 -8.1,0.3Z" />
<path
android:fillColor="#31a452"
android:fillType="nonZero"
android:pathData="M578.6,771.5c-4.7,-0.9 -8.7,-2.7 -12.9,-5.9c-10.8,-8.1 -13.5,-22.3 -6.6,-33.7c0.7,-1.2 1.1,-2.2 1,-2.4c-0.2,-0.2 -1.2,-0.6 -2.3,-1.1c-7.6,-3 -13,-10.6 -13.5,-19.1c-0.5,-7.4 3.1,-15 9,-19.4c1,-0.7 2.2,-1.5 2.6,-1.8c0.8,-0.4 68.9,-22.7 69.4,-22.7c0.2,0 0.7,0.7 1.2,1.5c0.5,0.8 1.6,2.3 2.4,3.3c1.2,1.4 1.5,1.9 1.2,2.3c-0.2,0.3 -6.9,9.5 -14.8,20.5c-15.9,21.9 -15.5,21.3 -13.4,23.4c1.3,1.3 2.9,1.4 4.4,0.3c0.6,-0.4 7.5,-9.7 15.5,-20.7c11.2,-15.4 14.6,-19.9 15,-19.7c0.9,0.4 5.5,1.9 6.6,2.1l1,0.2l-0,35.3c-0,39.7 -0,38.8 -2.5,44c-2.6,5.3 -7.2,9.3 -12.7,11.2c-3.7,1.3 -6.8,1.6 -10.2,1c-5.5,-0.9 -9.8,-3.2 -13.7,-7.4l-2.2,-2.4l-0.6,0.9c-3,4.3 -8.6,8.1 -14,9.5c-2.8,0.9 -7.8,1.2 -9.9,0.8Z" />
<path
android:fillColor="#ffb800"
android:fillType="nonZero"
android:pathData="M710.4,771.5c-5.5,-0.9 -9.9,-3.2 -14.3,-7.6l-3.2,-3.2l-0.7,1c-2.3,3.3 -6.8,6.5 -11.1,7.9c-3.7,1.2 -9.2,1.4 -12.6,0.3c-7.1,-2.1 -12.7,-7.4 -15.2,-14.3l-0.9,-2.6l0,-74.2l1.8,-0.4c1,-0.2 2.7,-0.8 3.9,-1.2c1.1,-0.5 2.1,-0.8 2.2,-0.7c0.1,0.1 6.5,9 14.4,19.9c7.8,10.9 14.7,20.1 15.2,20.5c2.2,1.9 5.4,0.4 5.4,-2.6c0,-1.4 -1,-2.9 -13.8,-20.5c-7.6,-10.5 -14.2,-19.6 -14.7,-20.4l-0.9,-1.3l1.4,-1.7c0.8,-0.9 1.9,-2.5 2.5,-3.4l1,-1.6l34.4,11.2c18.9,6.2 35.1,11.6 35.9,12.1c6.8,4 11.1,11.3 11.1,19.1c0,4.1 -0.5,6.4 -2.4,10.2c-2,4.1 -5.5,7.6 -9.6,9.7c-1.6,0.8 -3.2,1.5 -3.4,1.5c-1,0 -0.9,0.7 0.3,2.6c2.8,4.3 4,8.5 3.9,13.7c0,8.1 -3.7,15.2 -10.6,20.3c-6.5,4.8 -13.4,6.7 -20,5.7Z" />
<path
android:fillColor="#de7fb3"
android:fillType="nonZero"
android:pathData="M421.4,714.9c-0.5,-0.1 -2.3,-0.4 -3.9,-0.7c-15.6,-2.6 -30.4,-12.6 -38.8,-26.2c-3.5,-5.7 -6.4,-13.2 -7.8,-19.9c-1.2,-6.1 -0.8,-28.1 0.8,-43.1c4.5,-43 19,-84.3 42.2,-120.7c6.5,-10.2 14.9,-21.5 18.2,-24.6c17.8,-16.6 43.1,-20.5 64.8,-10c4.3,2.1 8.8,5.1 12.7,8.6c2.8,2.4 5.8,6.1 20.9,25.5c9.7,12.5 17.8,22.8 17.9,23c0.2,0.2 -0.9,0.4 -3.2,0.4c-2.5,0 -4.1,0.2 -5.7,0.7c-2.1,0.7 -2.6,1.1 -7.9,6.3c-8.2,8.1 -14.4,15.3 -20.3,23.9c-15.5,22.2 -25.4,47.7 -28.8,74.8c-2.2,16.9 -1.6,37.5 1.6,52.3c0.3,1.4 0.5,2.8 0.4,3c-0.1,0.2 0.2,1.3 0.8,2.4c1.1,2.4 4.3,5.7 6.5,6.8l1.5,0.8l-1.2,0.4c-0.7,0.2 -13.1,3.8 -27.6,8c-16.4,4.7 -27.7,7.8 -29.8,8.1c-3.1,0.4 -11.1,0.6 -13.3,0.2Z" />
<path
android:fillColor="#ffb800"
android:fillType="nonZero"
android:pathData="M862.2,714.7c-2.1,-0.3 -33.8,-9.1 -56.5,-15.8l-2.5,-0.7l1.6,-0.8c3.4,-1.7 7.2,-6.6 7.3,-9.6c0,-0.7 0.4,-3.3 0.8,-5.8c3.9,-22.7 3.1,-46.1 -2.5,-68.4c-6.4,-25.5 -18.6,-49.2 -35.8,-69.1c-4.6,-5.3 -14.8,-15.4 -16.4,-16.1c-2.4,-1.1 -5.1,-1.6 -8,-1.4l-2.7,0.2l1.2,-1.5c0.7,-0.8 8.5,-10.8 17.5,-22.3c8.9,-11.5 17.2,-21.8 18.5,-23.1c2.6,-2.7 7,-6.2 10.3,-8.2c19.3,-11.6 43,-11.1 61.6,1.2c5.4,3.6 8.2,6.2 12.3,11.7c26.4,34.5 44,73.7 52.3,116.2c3.4,17.6 4.9,33.3 5,52.4c0,13 -0.2,14.8 -2.5,21.8c-8.4,26.2 -34.5,42.8 -61.5,39.3Z" />
<path
android:fillColor="#e64132"
android:fillType="nonZero"
android:pathData="M501.4,691.5c-2,-0.5 -4.6,-1.9 -6,-3.3c-2.5,-2.4 -3.1,-3.5 -3.7,-7.3c-4.4,-27.3 -2.2,-54 6.7,-79.3c5.3,-15.1 13.5,-30.5 23,-43.1c5.8,-7.8 16.6,-19.5 19,-20.7c4.7,-2.4 11.3,-1.2 15.2,2.7c5.4,5.4 5.2,13.9 -0.3,19.1c-4.3,4 -9.4,4.4 -12.6,0.9c-1.7,-1.9 -2.2,-3.9 -1.7,-6.4c0.2,-1.1 0.3,-2 0.2,-2.2c-0.3,-0.3 -3.6,3.3 -8.3,9.1c-17.6,21.8 -28.5,48 -31.9,76.5c-1.1,9.3 -1,26.4 0.1,34.6c0.3,1.8 0.8,1.9 1.4,0.1c0.9,-2.6 4,-4.7 6.8,-4.7c3,-0 5.9,2.2 7.5,5.7c0.6,1.3 0.8,2.3 0.8,5.2c-0,3.3 -0.1,3.8 -1.1,5.7c-1.4,2.7 -4.6,5.7 -7.1,6.6c-2.5,0.9 -6.1,1.2 -8,0.8Z" />
<path
android:fillColor="#31a452"
android:fillType="nonZero"
android:pathData="M790.1,691.5c-3.7,-0.6 -7.7,-3.6 -9.4,-7.1c-3.8,-7.5 0.1,-16.9 6.9,-16.9c3.1,0 5.8,2 6.9,5.2c0.4,1.2 0.5,1.3 0.7,0.7c1.3,-3.7 1.7,-26.4 0.6,-35.7c-3.6,-29.6 -14.5,-55.3 -33,-77.9c-5.5,-6.7 -8.4,-9.4 -7.1,-6.6c0.7,1.4 0.5,4.3 -0.3,5.9c-0.9,1.7 -3.2,3.5 -5,3.8c-3.2,0.6 -7.9,-1.6 -10.2,-4.8c-6.5,-8.8 -0.5,-21.2 10.4,-21.4c4.6,-0.1 5.2,0.3 11.2,6.4c12.1,12.3 21.1,24.9 28.8,40.3c13.2,26.3 18.6,54.9 16.1,84.5c-0.5,5.6 -2,15.7 -2.6,17.1c-1.3,2.8 -4.8,5.5 -8.4,6.5c-2.3,0.4 -3.1,0.4 -5.6,0Z" />
<path
android:fillColor="#4081ef"
android:fillType="nonZero"
android:pathData="M545.7,680.2c-6,-1.3 -12.2,-6.2 -14.9,-11.7c-3.4,-7 -3.1,-15.1 0.9,-21.6c0.7,-1.2 1.2,-2.3 1.1,-2.4c-0.1,-0.1 -1.1,-0.6 -2.1,-1c-3.9,-1.5 -8.1,-4.8 -10.7,-8.3c-4.6,-6.2 -6.1,-14.6 -3.9,-22.1c2.9,-10.3 9.4,-16.8 19.1,-19.3c2.8,-0.7 9,-0.8 11.7,-0c1.1,0.3 2.2,0.5 2.4,0.5c0.2,-0 0.3,-0.7 0.3,-1.5c0,-2.9 0.8,-5.8 2.4,-9.2c5.2,-10.8 18.1,-15.5 29,-10.5c2.7,1.2 6.2,3.8 7.8,5.8c0.7,0.8 10.3,14 21.5,29.4l20.3,27.9l-1.5,1.8c-0.8,1 -1.9,2.6 -2.5,3.5c-0.6,1 -1.2,1.7 -1.5,1.6c-4.5,-1.7 -46.7,-15 -47.7,-15c-1.9,0 -3.1,1.3 -3.1,3.2c0,1 0.2,1.7 0.8,2.3c0.6,0.6 7.8,3.1 24.5,8.5l23.7,7.7l-0.2,8.6l-32.6,10.5c-18,5.9 -33.9,10.9 -35.2,11.2c-3.1,0.7 -6.6,0.7 -9.6,0.1Z" />
<path
android:fillColor="#e64132"
android:fillType="nonZero"
android:pathData="M740,679.8c-1.8,-0.5 -17.5,-5.6 -35,-11.3l-31.8,-10.4l1,-4.3l0,-4.3l22.6,-7.7c15,-4.9 24,-8 24.6,-8.5c0.7,-0.6 0.9,-1.1 0.9,-2.2c-0,-2 -1.2,-3.3 -3.1,-3.3c-0.9,-0 -10.5,2.9 -24.7,7.5c-12.8,4.1 -23.4,7.5 -23.6,7.5c-0.1,-0 -0.7,-0.8 -1.3,-1.9c-0.6,-1 -1.6,-2.5 -2.2,-3.2c-0.7,-0.7 -1.2,-1.5 -1.2,-1.6c0,-0.2 9.6,-13.5 21.4,-29.6c18.9,-26 21.6,-29.6 23.6,-31.1c5.7,-4.4 13.1,-5.8 19.7,-3.9c9,2.7 16.1,11.6 16.1,20.3c0,2.3 -0.1,2.3 3.1,1.5c4.7,-1.1 11.5,-0.5 16,1.5c4.6,2 9,6 11.5,10.2c2.1,3.6 3.9,9.4 4.2,13.2c0.3,5.2 -1.1,10.7 -4,15.3c-2.6,4.1 -7.8,8.3 -12.1,9.8c-0.9,0.3 -1.7,0.8 -1.7,1c0,0.2 0.4,1 0.9,1.7c2.4,3.6 3.6,7.7 3.5,12.7c0,5.8 -2.1,10.7 -6.4,15.1c-4,4.1 -8.9,6.3 -14.9,6.5c-3.3,0.4 -4.3,0.3 -7.1,-0.5Z" />
<path
android:fillColor="#f2f5fb"
android:fillType="nonZero"
android:pathData="M643.7,671.9c-6.1,-1.6 -11.4,-6.8 -13.2,-12.9c-0.7,-2.4 -0.7,-7.5 0,-9.9c1.7,-5.8 6.6,-10.8 12.3,-12.5c2.7,-0.8 7.2,-0.9 10,-0.2c6.2,1.6 11.6,7.1 13.2,13.3c1.6,6 -0.3,12.6 -5,17.3c-4.6,4.6 -11.3,6.5 -17.3,4.9Z" />
<path
android:fillColor="#de7fb3"
android:fillType="nonZero"
android:pathData="M615.8,602.8c-13.3,-18.3 -21.2,-29.6 -22,-31.1c-1.4,-3 -1.9,-5.5 -1.9,-9.4c0,-14.1 13.1,-24.4 27.1,-21.4c1.4,0.3 2.6,0.5 2.7,0.5c0.1,0 0.3,-1.3 0.4,-2.8c0.8,-10.7 8.4,-19.6 18.9,-22.4c3.9,-1 10.6,-1 14.5,-0c8.9,2.3 15.9,9.3 18.2,18.2c0.4,1.5 0.7,3.7 0.7,4.9c-0,1.2 0.1,2.1 0.3,2.1c0.2,-0 1.5,-0.3 3,-0.6c7.4,-1.6 15.2,0.7 20.5,6c4.3,4.3 6.6,9.6 6.6,15.6c-0,4 -0.6,6.5 -2.4,10c-0.6,1.2 -10.4,15 -21.7,30.7c-17.8,24.5 -20.8,28.5 -21.4,28.3c-0.4,-0.1 -1.9,-0.6 -3.4,-1.1c-1.5,-0.5 -2.9,-0.9 -3.3,-0.9c-0.7,-0 -0.7,-0.8 -0.3,-25.5l-0,-25.5l-1.4,-0.9c-1,-1.1 -2.5,-1.5 -3.8,-0.9c-2,0.8 -2,-0.5 -1.8,27.2l-0,25.8l-1.2,-0c-0.5,-0.2 -2.4,0.3 -4,0.9c-1.6,0.6 -3.1,1.1 -3.2,1.1c-0.2,-0.1 -9.6,-13 -21.1,-28.8Z" />
<path
android:fillColor="#ffb800"
android:fillType="nonZero"
android:pathData="M578.4,537.8c-4.1,-0.9 -7.7,-3.6 -9.6,-7.4c-1.4,-2.8 -1.7,-7.3 -0.5,-10.3c1.7,-4.5 3.9,-6.1 15.6,-11.2c15.8,-7 31.4,-11.1 49.2,-12.9c7.3,-0.8 23.2,-0.8 30.6,0c17.4,1.8 33.3,6 49.1,13c7.3,3.2 12.5,6.1 13.6,7.5c4.3,5.6 3.8,12.7 -1.1,17.6c-5.1,5.1 -12.9,5.4 -18.1,0.7c-2,-1.8 -3,-3.5 -3.4,-5.6c-0.7,-4 2.9,-8.1 7.3,-8.2c1.4,0 1.5,-0.1 1.1,-0.5c-0.3,-0.3 -2.2,-1.2 -4.3,-2.1c-33.2,-14.5 -70.5,-16.4 -105,-5.4c-7.5,2.4 -19,7.2 -18.6,7.7c0.1,0.2 0.8,0.3 1.6,0.3c5.6,0 9.1,6.2 6.1,10.8c-2.9,4.5 -8.6,7.1 -13.6,6Z" />
<path
android:fillColor="#e64132"
android:fillType="nonZero"
android:pathData="M542.2,496.4c-8.9,-13.1 -16.8,-25.1 -17.5,-26.6c-1.6,-3.3 -3.6,-9.2 -4.4,-13c-2.6,-12.5 -0.9,-25.8 5,-37.5c4.2,-8.3 11.2,-16.3 18.6,-21.3c5,-3.4 6.1,-3.9 12.8,-6.3c23.1,-8.2 47.2,-13.1 73.4,-15c7.5,-0.6 28.5,-0.6 36.3,-0c25.5,1.8 50.6,6.9 73,14.8c6.4,2.2 8.2,3.1 13.1,6.5c9.8,6.6 18.1,17.5 22,29.2c2.2,6.5 2.7,10 2.7,17.9c0,7.9 -0.5,11.3 -2.7,17.9c-2.3,6.8 -3.7,9.1 -20.3,33.6l-16.1,23.8l-0.4,-2.2c-0.2,-1.2 -0.9,-3 -1.4,-4c-1,-1.8 -4.4,-5.6 -4.7,-5.2c-0.1,0.1 -1.2,-0.4 -2.4,-1.1c-9.1,-5.2 -21.9,-10.5 -33.2,-13.9c-37,-11 -77.2,-8.8 -113,6.1c-4.9,2.1 -17.7,8.4 -19.2,9.5c-2.2,1.6 -5.1,6.8 -5.1,9c0,0.4 -0.1,1 -0.3,1.2c0.1,0.2 -6.2,-8.8 -16.2,-23.4Z" />
</vector>

View File

@@ -0,0 +1,70 @@
<vector android:height="200dp"
android:viewportHeight="1300"
android:viewportWidth="1300"
android:width="200dp"
xmlns:android="http://schemas.android.com/apk/res/android">
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M578,922.6c-2.2,-0.2 -5.5,-1 -9.7,-2.2c-52.4,-15.7 -99,-46.5 -133.8,-88.5c-8.8,-10.7 -17.2,-22.4 -19.4,-27.5c-8.1,-18.1 -6.3,-38.7 4.8,-55.4c5,-7.5 13.2,-15 20.5,-18.7c1.2,-0.6 54.1,-20 55.8,-20.4c0.5,-0.1 0.5,0.2 -0.3,2.1c-0.7,1.7 -1,3.1 -1.1,5.5l-0.1,3.2l2.8,5.8c8.7,17.9 19.2,32.7 33.2,46.4c6.3,6.2 7.8,7.6 13.8,12.3c22.7,18.1 52,30.7 79.9,34.3c2.5,0.3 5,0.8 5.7,1c2.8,0.9 7.7,-0.8 11,-3.7l1.8,-1.6l-0.2,4.8c-0.1,2.7 -0.6,15.4 -1,28.3c-0.6,20.3 -0.8,24 -1.5,27.5c-3.9,20.7 -18.6,37.5 -38.4,44.1c-4.6,1.5 -8,2.2 -13.1,2.7c-4.6,0.5 -5.9,0.4 -10.7,0Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M707.3,922.4c-4,-0.4 -9.4,-1.6 -13.2,-2.9c-3.4,-1.2 -10,-4.4 -12.5,-6.1c-10.9,-7.4 -19,-17.9 -23.1,-30c-2.2,-6.7 -2.3,-7.5 -3.3,-36.9c-0.5,-14.9 -0.9,-27.9 -0.9,-28.9l-0,-1.9l2.3,1.8c2.6,2 6.6,3.4 8.5,3.1c0.6,-0.1 3,-0.5 5.3,-0.8c37.7,-5.3 71.2,-22.2 97.4,-49.1c12.2,-12.5 21.4,-25.5 29.9,-42.4l3.5,-7l0,-3.6c0,-3.1 -0.1,-3.8 -1,-5.7c-0.5,-1.2 -0.9,-2.1 -0.9,-2.2c0.2,-0.2 55.3,20.1 56.9,20.9c2.6,1.3 6.6,4.1 9.9,7c9.2,7.7 16.1,19.4 18.8,31.8c0.7,3.1 0.8,4.8 0.8,11.3c0,8.6 -0.5,11.7 -2.9,18.7c-1.7,5 -2.9,7.2 -7.1,13.1c-7.6,11 -15.3,20.5 -25.2,31.2c-32.8,35.4 -76.5,62.5 -123.4,76.3c-8,2.5 -12.4,3 -19.8,2.3Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M623.1,811c-25.9,-4.2 -50.7,-14.9 -71.7,-31c-5.2,-4 -8.7,-7.1 -14.1,-12.4c-12.7,-12.5 -21.9,-24.9 -30.5,-41.4c-2.3,-4.4 -2.4,-4.7 -2.4,-7.1c0,-8.8 8.5,-15.2 16.9,-12.7c5.6,1.7 9.6,6.8 9.7,12.2c0,2.6 -0.8,4.6 -2.6,6.2c-1.2,1.1 -3.2,1.9 -4.6,1.9c-1.2,0 -3.3,-0.8 -4.3,-1.6c-2.1,-1.8 -2,-1 0.4,3.2c19.3,33.8 52.3,59.1 90,69.1c5.7,1.5 11.5,2.7 11.8,2.4c0.1,-0.1 -0.4,-0.8 -1.3,-1.6c-5.1,-4.5 -2.3,-11.7 5,-12.8c5.4,-0.8 11.4,2.7 13.9,8c0.8,1.7 1,2.5 1,5.3c0,2.8 -0.1,3.5 -1,5.3c-2,4.3 -6.8,7.9 -10.3,7.8c-0.9,-0.1 -3.6,-0.5 -5.9,-0.8Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M665.1,811.2c-3.4,-1.3 -6.4,-4.3 -7.8,-8.1c-1.1,-2.9 -0.9,-7.3 0.5,-10.2c2.6,-5.3 8.7,-8.5 14.4,-7.5c2.9,0.5 4.7,1.9 6,4.3c0.8,1.6 1,2.2 0.8,3.6c-0.3,2.2 -0.9,3.3 -2.7,4.8c-0.8,0.7 -1.4,1.4 -1.3,1.5c0.5,0.5 13.4,-2.7 21.3,-5.4c33.6,-11.3 62.5,-35.1 80.4,-66.1c2.5,-4.4 2.6,-5 0.5,-3.2c-2.8,2.4 -7,1.9 -9.6,-1c-4,-4.6 -0.7,-13.8 6.1,-16.9c2,-0.9 2.7,-1 5.5,-1c2.9,0 3.5,0.1 5.6,1.1c4.4,2.1 7.4,6.4 7.8,11c0.2,2.2 0.1,2.3 -2.2,6.9c-23,45.9 -67,78.1 -117.2,85.9c-5.5,0.9 -6.3,1 -8.1,0.3Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M578.6,771.5c-4.7,-0.9 -8.7,-2.7 -12.9,-5.9c-10.8,-8.1 -13.5,-22.3 -6.6,-33.7c0.7,-1.2 1.1,-2.2 1,-2.4c-0.2,-0.2 -1.2,-0.6 -2.3,-1.1c-7.6,-3 -13,-10.6 -13.5,-19.1c-0.5,-7.4 3.1,-15 9,-19.4c1,-0.7 2.2,-1.5 2.6,-1.8c0.8,-0.4 68.9,-22.7 69.4,-22.7c0.2,0 0.7,0.7 1.2,1.5c0.5,0.8 1.6,2.3 2.4,3.3c1.2,1.4 1.5,1.9 1.2,2.3c-0.2,0.3 -6.9,9.5 -14.8,20.5c-15.9,21.9 -15.5,21.3 -13.4,23.4c1.3,1.3 2.9,1.4 4.4,0.3c0.6,-0.4 7.5,-9.7 15.5,-20.7c11.2,-15.4 14.6,-19.9 15,-19.7c0.9,0.4 5.5,1.9 6.6,2.1l1,0.2l-0,35.3c-0,39.7 -0,38.8 -2.5,44c-2.6,5.3 -7.2,9.3 -12.7,11.2c-3.7,1.3 -6.8,1.6 -10.2,1c-5.5,-0.9 -9.8,-3.2 -13.7,-7.4l-2.2,-2.4l-0.6,0.9c-3,4.3 -8.6,8.1 -14,9.5c-2.8,0.9 -7.8,1.2 -9.9,0.8Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M710.4,771.5c-5.5,-0.9 -9.9,-3.2 -14.3,-7.6l-3.2,-3.2l-0.7,1c-2.3,3.3 -6.8,6.5 -11.1,7.9c-3.7,1.2 -9.2,1.4 -12.6,0.3c-7.1,-2.1 -12.7,-7.4 -15.2,-14.3l-0.9,-2.6l0,-74.2l1.8,-0.4c1,-0.2 2.7,-0.8 3.9,-1.2c1.1,-0.5 2.1,-0.8 2.2,-0.7c0.1,0.1 6.5,9 14.4,19.9c7.8,10.9 14.7,20.1 15.2,20.5c2.2,1.9 5.4,0.4 5.4,-2.6c0,-1.4 -1,-2.9 -13.8,-20.5c-7.6,-10.5 -14.2,-19.6 -14.7,-20.4l-0.9,-1.3l1.4,-1.7c0.8,-0.9 1.9,-2.5 2.5,-3.4l1,-1.6l34.4,11.2c18.9,6.2 35.1,11.6 35.9,12.1c6.8,4 11.1,11.3 11.1,19.1c0,4.1 -0.5,6.4 -2.4,10.2c-2,4.1 -5.5,7.6 -9.6,9.7c-1.6,0.8 -3.2,1.5 -3.4,1.5c-1,0 -0.9,0.7 0.3,2.6c2.8,4.3 4,8.5 3.9,13.7c0,8.1 -3.7,15.2 -10.6,20.3c-6.5,4.8 -13.4,6.7 -20,5.7Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M421.4,714.9c-0.5,-0.1 -2.3,-0.4 -3.9,-0.7c-15.6,-2.6 -30.4,-12.6 -38.8,-26.2c-3.5,-5.7 -6.4,-13.2 -7.8,-19.9c-1.2,-6.1 -0.8,-28.1 0.8,-43.1c4.5,-43 19,-84.3 42.2,-120.7c6.5,-10.2 14.9,-21.5 18.2,-24.6c17.8,-16.6 43.1,-20.5 64.8,-10c4.3,2.1 8.8,5.1 12.7,8.6c2.8,2.4 5.8,6.1 20.9,25.5c9.7,12.5 17.8,22.8 17.9,23c0.2,0.2 -0.9,0.4 -3.2,0.4c-2.5,0 -4.1,0.2 -5.7,0.7c-2.1,0.7 -2.6,1.1 -7.9,6.3c-8.2,8.1 -14.4,15.3 -20.3,23.9c-15.5,22.2 -25.4,47.7 -28.8,74.8c-2.2,16.9 -1.6,37.5 1.6,52.3c0.3,1.4 0.5,2.8 0.4,3c-0.1,0.2 0.2,1.3 0.8,2.4c1.1,2.4 4.3,5.7 6.5,6.8l1.5,0.8l-1.2,0.4c-0.7,0.2 -13.1,3.8 -27.6,8c-16.4,4.7 -27.7,7.8 -29.8,8.1c-3.1,0.4 -11.1,0.6 -13.3,0.2Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M862.2,714.7c-2.1,-0.3 -33.8,-9.1 -56.5,-15.8l-2.5,-0.7l1.6,-0.8c3.4,-1.7 7.2,-6.6 7.3,-9.6c0,-0.7 0.4,-3.3 0.8,-5.8c3.9,-22.7 3.1,-46.1 -2.5,-68.4c-6.4,-25.5 -18.6,-49.2 -35.8,-69.1c-4.6,-5.3 -14.8,-15.4 -16.4,-16.1c-2.4,-1.1 -5.1,-1.6 -8,-1.4l-2.7,0.2l1.2,-1.5c0.7,-0.8 8.5,-10.8 17.5,-22.3c8.9,-11.5 17.2,-21.8 18.5,-23.1c2.6,-2.7 7,-6.2 10.3,-8.2c19.3,-11.6 43,-11.1 61.6,1.2c5.4,3.6 8.2,6.2 12.3,11.7c26.4,34.5 44,73.7 52.3,116.2c3.4,17.6 4.9,33.3 5,52.4c0,13 -0.2,14.8 -2.5,21.8c-8.4,26.2 -34.5,42.8 -61.5,39.3Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M501.4,691.5c-2,-0.5 -4.6,-1.9 -6,-3.3c-2.5,-2.4 -3.1,-3.5 -3.7,-7.3c-4.4,-27.3 -2.2,-54 6.7,-79.3c5.3,-15.1 13.5,-30.5 23,-43.1c5.8,-7.8 16.6,-19.5 19,-20.7c4.7,-2.4 11.3,-1.2 15.2,2.7c5.4,5.4 5.2,13.9 -0.3,19.1c-4.3,4 -9.4,4.4 -12.6,0.9c-1.7,-1.9 -2.2,-3.9 -1.7,-6.4c0.2,-1.1 0.3,-2 0.2,-2.2c-0.3,-0.3 -3.6,3.3 -8.3,9.1c-17.6,21.8 -28.5,48 -31.9,76.5c-1.1,9.3 -1,26.4 0.1,34.6c0.3,1.8 0.8,1.9 1.4,0.1c0.9,-2.6 4,-4.7 6.8,-4.7c3,-0 5.9,2.2 7.5,5.7c0.6,1.3 0.8,2.3 0.8,5.2c-0,3.3 -0.1,3.8 -1.1,5.7c-1.4,2.7 -4.6,5.7 -7.1,6.6c-2.5,0.9 -6.1,1.2 -8,0.8Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M790.1,691.5c-3.7,-0.6 -7.7,-3.6 -9.4,-7.1c-3.8,-7.5 0.1,-16.9 6.9,-16.9c3.1,0 5.8,2 6.9,5.2c0.4,1.2 0.5,1.3 0.7,0.7c1.3,-3.7 1.7,-26.4 0.6,-35.7c-3.6,-29.6 -14.5,-55.3 -33,-77.9c-5.5,-6.7 -8.4,-9.4 -7.1,-6.6c0.7,1.4 0.5,4.3 -0.3,5.9c-0.9,1.7 -3.2,3.5 -5,3.8c-3.2,0.6 -7.9,-1.6 -10.2,-4.8c-6.5,-8.8 -0.5,-21.2 10.4,-21.4c4.6,-0.1 5.2,0.3 11.2,6.4c12.1,12.3 21.1,24.9 28.8,40.3c13.2,26.3 18.6,54.9 16.1,84.5c-0.5,5.6 -2,15.7 -2.6,17.1c-1.3,2.8 -4.8,5.5 -8.4,6.5c-2.3,0.4 -3.1,0.4 -5.6,0Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M545.7,680.2c-6,-1.3 -12.2,-6.2 -14.9,-11.7c-3.4,-7 -3.1,-15.1 0.9,-21.6c0.7,-1.2 1.2,-2.3 1.1,-2.4c-0.1,-0.1 -1.1,-0.6 -2.1,-1c-3.9,-1.5 -8.1,-4.8 -10.7,-8.3c-4.6,-6.2 -6.1,-14.6 -3.9,-22.1c2.9,-10.3 9.4,-16.8 19.1,-19.3c2.8,-0.7 9,-0.8 11.7,-0c1.1,0.3 2.2,0.5 2.4,0.5c0.2,-0 0.3,-0.7 0.3,-1.5c0,-2.9 0.8,-5.8 2.4,-9.2c5.2,-10.8 18.1,-15.5 29,-10.5c2.7,1.2 6.2,3.8 7.8,5.8c0.7,0.8 10.3,14 21.5,29.4l20.3,27.9l-1.5,1.8c-0.8,1 -1.9,2.6 -2.5,3.5c-0.6,1 -1.2,1.7 -1.5,1.6c-4.5,-1.7 -46.7,-15 -47.7,-15c-1.9,0 -3.1,1.3 -3.1,3.2c0,1 0.2,1.7 0.8,2.3c0.6,0.6 7.8,3.1 24.5,8.5l23.7,7.7l-0.2,8.6l-32.6,10.5c-18,5.9 -33.9,10.9 -35.2,11.2c-3.1,0.7 -6.6,0.7 -9.6,0.1Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M740,679.8c-1.8,-0.5 -17.5,-5.6 -35,-11.3l-31.8,-10.4l1,-4.3l0,-4.3l22.6,-7.7c15,-4.9 24,-8 24.6,-8.5c0.7,-0.6 0.9,-1.1 0.9,-2.2c-0,-2 -1.2,-3.3 -3.1,-3.3c-0.9,-0 -10.5,2.9 -24.7,7.5c-12.8,4.1 -23.4,7.5 -23.6,7.5c-0.1,-0 -0.7,-0.8 -1.3,-1.9c-0.6,-1 -1.6,-2.5 -2.2,-3.2c-0.7,-0.7 -1.2,-1.5 -1.2,-1.6c0,-0.2 9.6,-13.5 21.4,-29.6c18.9,-26 21.6,-29.6 23.6,-31.1c5.7,-4.4 13.1,-5.8 19.7,-3.9c9,2.7 16.1,11.6 16.1,20.3c0,2.3 -0.1,2.3 3.1,1.5c4.7,-1.1 11.5,-0.5 16,1.5c4.6,2 9,6 11.5,10.2c2.1,3.6 3.9,9.4 4.2,13.2c0.3,5.2 -1.1,10.7 -4,15.3c-2.6,4.1 -7.8,8.3 -12.1,9.8c-0.9,0.3 -1.7,0.8 -1.7,1c0,0.2 0.4,1 0.9,1.7c2.4,3.6 3.6,7.7 3.5,12.7c0,5.8 -2.1,10.7 -6.4,15.1c-4,4.1 -8.9,6.3 -14.9,6.5c-3.3,0.4 -4.3,0.3 -7.1,-0.5Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M643.7,671.9c-6.1,-1.6 -11.4,-6.8 -13.2,-12.9c-0.7,-2.4 -0.7,-7.5 0,-9.9c1.7,-5.8 6.6,-10.8 12.3,-12.5c2.7,-0.8 7.2,-0.9 10,-0.2c6.2,1.6 11.6,7.1 13.2,13.3c1.6,6 -0.3,12.6 -5,17.3c-4.6,4.6 -11.3,6.5 -17.3,4.9Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M615.8,602.8c-13.3,-18.3 -21.2,-29.6 -22,-31.1c-1.4,-3 -1.9,-5.5 -1.9,-9.4c0,-14.1 13.1,-24.4 27.1,-21.4c1.4,0.3 2.6,0.5 2.7,0.5c0.1,0 0.3,-1.3 0.4,-2.8c0.8,-10.7 8.4,-19.6 18.9,-22.4c3.9,-1 10.6,-1 14.5,-0c8.9,2.3 15.9,9.3 18.2,18.2c0.4,1.5 0.7,3.7 0.7,4.9c-0,1.2 0.1,2.1 0.3,2.1c0.2,-0 1.5,-0.3 3,-0.6c7.4,-1.6 15.2,0.7 20.5,6c4.3,4.3 6.6,9.6 6.6,15.6c-0,4 -0.6,6.5 -2.4,10c-0.6,1.2 -10.4,15 -21.7,30.7c-17.8,24.5 -20.8,28.5 -21.4,28.3c-0.4,-0.1 -1.9,-0.6 -3.4,-1.1c-1.5,-0.5 -2.9,-0.9 -3.3,-0.9c-0.7,-0 -0.7,-0.8 -0.3,-25.5l-0,-25.5l-1.4,-0.9c-1,-1.1 -2.5,-1.5 -3.8,-0.9c-2,0.8 -2,-0.5 -1.8,27.2l-0,25.8l-1.2,-0c-0.5,-0.2 -2.4,0.3 -4,0.9c-1.6,0.6 -3.1,1.1 -3.2,1.1c-0.2,-0.1 -9.6,-13 -21.1,-28.8Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M578.4,537.8c-4.1,-0.9 -7.7,-3.6 -9.6,-7.4c-1.4,-2.8 -1.7,-7.3 -0.5,-10.3c1.7,-4.5 3.9,-6.1 15.6,-11.2c15.8,-7 31.4,-11.1 49.2,-12.9c7.3,-0.8 23.2,-0.8 30.6,0c17.4,1.8 33.3,6 49.1,13c7.3,3.2 12.5,6.1 13.6,7.5c4.3,5.6 3.8,12.7 -1.1,17.6c-5.1,5.1 -12.9,5.4 -18.1,0.7c-2,-1.8 -3,-3.5 -3.4,-5.6c-0.7,-4 2.9,-8.1 7.3,-8.2c1.4,0 1.5,-0.1 1.1,-0.5c-0.3,-0.3 -2.2,-1.2 -4.3,-2.1c-33.2,-14.5 -70.5,-16.4 -105,-5.4c-7.5,2.4 -19,7.2 -18.6,7.7c0.1,0.2 0.8,0.3 1.6,0.3c5.6,0 9.1,6.2 6.1,10.8c-2.9,4.5 -8.6,7.1 -13.6,6Z" />
<path
android:fillColor="#fff"
android:fillType="nonZero"
android:pathData="M542.2,496.4c-8.9,-13.1 -16.8,-25.1 -17.5,-26.6c-1.6,-3.3 -3.6,-9.2 -4.4,-13c-2.6,-12.5 -0.9,-25.8 5,-37.5c4.2,-8.3 11.2,-16.3 18.6,-21.3c5,-3.4 6.1,-3.9 12.8,-6.3c23.1,-8.2 47.2,-13.1 73.4,-15c7.5,-0.6 28.5,-0.6 36.3,-0c25.5,1.8 50.6,6.9 73,14.8c6.4,2.2 8.2,3.1 13.1,6.5c9.8,6.6 18.1,17.5 22,29.2c2.2,6.5 2.7,10 2.7,17.9c0,7.9 -0.5,11.3 -2.7,17.9c-2.3,6.8 -3.7,9.1 -20.3,33.6l-16.1,23.8l-0.4,-2.2c-0.2,-1.2 -0.9,-3 -1.4,-4c-1,-1.8 -4.4,-5.6 -4.7,-5.2c-0.1,0.1 -1.2,-0.4 -2.4,-1.1c-9.1,-5.2 -21.9,-10.5 -33.2,-13.9c-37,-11 -77.2,-8.8 -113,6.1c-4.9,2.1 -17.7,8.4 -19.2,9.5c-2.2,1.6 -5.1,6.8 -5.1,9c0,0.4 -0.1,1 -0.3,1.2c0.1,0.2 -6.2,-8.8 -16.2,-23.4Z" />
</vector>

View File

@@ -1,12 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
<item>
<bitmap android:gravity="center" android:src="@drawable/splash"/>
</item>
</layer-list>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#000000</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -5,6 +5,9 @@
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#f6f8fe</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@@ -5,6 +5,9 @@
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your

View File

@@ -35,8 +35,8 @@ platform :android do
task: 'bundle',
build_type: 'Release',
properties: {
"android.injected.version.code" => 66,
"android.injected.version.name" => "1.43.1",
"android.injected.version.code" => 69,
"android.injected.version.name" => "1.46.1",
}
)
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')

View File

@@ -0,0 +1,7 @@
* reused HTTP client for uploading assets
* top padding missing from drag handle in add to album bottom sheet
* Improve the gallery to improve scale, double tap, and swipe gesture detection
* Uses clamping scroll physics on android
* Fix back button closing the app from multi selection in Android
* add and delete operation asset in album viewer doesn't update count in album list
* Update share_file to latest version and migrate to using cross platform shareXFile

View File

@@ -0,0 +1,4 @@
fixes back button while multiselecting showing the last selected image
fixes dark mode color for invite to album app bar
fixed local asset thumbnail size and eliminated fade in
Fullscreen image effects

View File

@@ -0,0 +1,6 @@
* Library page rework (album sorting, favorites).
* Tap to enter immersive mode on the gallery viewer.
* Removed stay logged in the checkbox and made it enabled by default.
* Multiselect add to favorites from the timeline.
* Responsive layout improvements with a navigation rail and album grid.
* Home screen customization options.

View File

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000213">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000281">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="61.218233">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="142.850758">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="41.974053">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="39.589103">
</testcase>

View File

@@ -108,23 +108,30 @@
"experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter",
"experimental_settings_subtitle": "Brug på eget ansvar!",
"experimental_settings_title": "Eksperimentelle",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Tilføjede {added} billeder og videoer til album {album}. {failed} billeder og videoer er allerede i albummet.",
"home_page_add_to_album_success": "Tilføjede {added} billeder og videoer til album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albummer",
"library_page_favorites": "Favorites",
"library_page_new_album": "Nyt album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Log ind",
"login_form_email_hint": "din-email@email.com",
"login_form_email_hint": "din-e-mail@e-mail.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_invalid_url": "Invalid URL",
"login_form_err_invalid_email": "Ugyldig e-mail",
"login_form_err_invalid_url": "Ugyldig webadresse",
"login_form_err_leading_whitespace": "Mellemrum før",
"login_form_err_trailing_whitespace": "Mellemrum efter",
"login_form_failed_get_oauth_server_config": "Fejl med at logge på med OAuth. Tjek serveres URL",
"login_form_failed_get_oauth_server_config": "Fejl med at logge på med OAuth. Tjek serveres webadresse",
"login_form_failed_get_oauth_server_disable": "OAuth er ikke tilgængelig på denne server",
"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_failed_login": "Der opstod en vejl ved at logge ind. Tjek server webadressen, e-mailen og kodeordet",
"login_form_label_email": "E-mail",
"login_form_label_password": "Kodeord",
"login_form_password_hint": "kodeord",
"login_form_save_login": "Forbliv logget ind",

View File

@@ -108,10 +108,17 @@
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimentell",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Alben",
"library_page_favorites": "Favorites",
"library_page_new_album": "Neues Album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Anmelden",
"login_form_email_hint": "deine@email.de",
"login_form_endpoint_hint": "http://deine-server-ip:port/api",

View File

@@ -12,6 +12,10 @@
"album_viewer_appbar_share_leave": "Leave album",
"album_viewer_appbar_share_remove": "Remove from album",
"album_viewer_page_share_add_users": "Add users",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_settings_group_by_month": "Month",
"asset_list_settings_subtitle": "Photo grid layout settings",
"asset_list_settings_title": "Photo Grid",
"backup_album_selection_page_albums_device": "Albums on device ({})",
@@ -108,12 +112,17 @@
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albums",
"library_page_favorites": "Favorites",
"library_page_new_album": "New album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Login",
"login_form_email_hint": "youremail@email.com",
"login_form_endpoint_hint": "http://your-server-ip:port/api",
@@ -194,4 +203,4 @@
"version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
}
}

View File

@@ -108,10 +108,17 @@
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Añadidos {added} elementos al álbum {album}. {failed} elementos ya están añadidos.",
"home_page_add_to_album_success": "Añadidos {added} elementos al álbum {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albums",
"library_page_favorites": "Favorites",
"library_page_new_album": "New album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"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",

View File

@@ -108,10 +108,17 @@
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
"experimental_settings_title": "Kokeellinen",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Lisätty {added} kohdetta albumiin {album}. {failed} kohdetta on jo albumissa.",
"home_page_add_to_album_success": "Lisätty {added} kohdetta albumiin {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albumit",
"library_page_favorites": "Favorites",
"library_page_new_album": "Uusi albumi",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Kirjaudu",
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
"login_form_endpoint_hint": "http://palvelimesi-osoite:portti/api",

View File

@@ -35,7 +35,7 @@
"backup_controller_page_background_battery_info_title": "Optimisation de la batterie",
"backup_controller_page_background_charging": "Seulement pendant la charge",
"backup_controller_page_background_configure_error": "Échec de la configuration du service d'arrière-plan",
"backup_controller_page_background_delay": "Delay new assets backup: {}",
"backup_controller_page_background_delay": "Retarder la sauvegarde des nouveaux éléments d'actif : {}",
"backup_controller_page_background_description": "Activez le service d'arrière-plan pour sauvegarder automatiquement tous les nouveaux éléments sans avoir à ouvrir l'application.",
"backup_controller_page_background_is_off": "La sauvegarde automatique en arrière-plan est désactivée",
"backup_controller_page_background_is_on": "La sauvegarde automatique en arrière-plan est activée",
@@ -83,10 +83,10 @@
"cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich",
"cache_settings_thumbnail_size": "Taille du cache des miniatures ({} éléments)",
"cache_settings_title": "Paramètres de mise en cache",
"control_bottom_app_bar_add_to_album": "Add to album",
"control_bottom_app_bar_album_info": "{} items",
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
"control_bottom_app_bar_create_new_album": "Create new album",
"control_bottom_app_bar_add_to_album": "Ajouter à l'album",
"control_bottom_app_bar_album_info": "{} éléments",
"control_bottom_app_bar_album_info_shared": "{} éléments - Partagés",
"control_bottom_app_bar_create_new_album": "Créer un nouvel album",
"control_bottom_app_bar_delete": "Supprimer",
"control_bottom_app_bar_share": "Partager",
"create_album_page_untitled": "Sans titre",
@@ -108,28 +108,35 @@
"experimental_settings_new_asset_list_title": "Activez la grille de photos expérimentale",
"experimental_settings_subtitle": "Utilisez à vos dépends !",
"experimental_settings_title": "Expérimental",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "{added} éléments ajoutés à l'album {album}. Les éléments {failed} sont déjà dans l'album.",
"home_page_add_to_album_success": "{added} éléments ajoutés à l'album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albums",
"library_page_favorites": "Favorites",
"library_page_new_album": "Nouvel album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"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_invalid_url": "Invalid URL",
"login_form_err_invalid_url": "URL 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_get_oauth_server_config": "Error logging using OAuth, check server URL",
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
"login_form_failed_get_oauth_server_config": "Erreur de connexion par OAuth, vérifiez l\"URL du serveur",
"login_form_failed_get_oauth_server_disable": "La fonctionnalité OAuth n'est pas disponible sur ce serveur",
"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_app_logs": "Logs",
"profile_drawer_app_logs": "Journaux",
"profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour",
"profile_drawer_settings": "Paramètres",
"profile_drawer_sign_out": "Se déconnecter",
@@ -142,17 +149,17 @@
"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",
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
"setting_image_viewer_original_title": "Load original image",
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
"setting_image_viewer_preview_title": "Load preview image",
"setting_image_viewer_help": "Le visualiseur de détails charge d'abord la petite miniature, puis l'aperçu de taille moyenne (s'il est activé), enfin l'original (s'il est activé).",
"setting_image_viewer_original_subtitle": "Activez cette option pour charger l'image originale en résolution originale (volumineux !). Désactiver pour réduire l'utilisation des données (réseau et cache de l'appareil).",
"setting_image_viewer_original_title": "Charger l'image originale",
"setting_image_viewer_preview_subtitle": "Activer pour charger une image de résolution moyenne. Désactiver pour charger directement l'original ou utiliser uniquement la miniature.",
"setting_image_viewer_preview_title": "Charger l'image d'aperçu",
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan : {}",
"setting_notifications_notify_hours": "{} heures",
"setting_notifications_notify_immediately": "immédiatement",
"setting_notifications_notify_minutes": "{} minutes",
"setting_notifications_notify_never": "jamais",
"setting_notifications_notify_seconds": "{} seconds",
"setting_notifications_notify_seconds": "{} secondes",
"setting_notifications_single_progress_subtitle": "Informations détaillées sur la progression du transfert par élément",
"setting_notifications_single_progress_title": "Afficher la progression du détail de la sauvegarde en arrière-plan",
"setting_notifications_subtitle": "Ajustez vos préférences de notification",

View File

@@ -108,10 +108,17 @@
"experimental_settings_new_asset_list_title": "Attiva griglia di foto sperimentale",
"experimental_settings_subtitle": "Usalo a tuo rischio!",
"experimental_settings_title": "Sperimentale",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Aggiunti {added} elementi all'album {album}. {failed} elementi erano già presenti nell'album.",
"home_page_add_to_album_success": "Aggiunti {added} elementi all'album {album}",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Album",
"library_page_favorites": "Favorites",
"library_page_new_album": "Nuovo Album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Login",
"login_form_email_hint": "tuaemail@email.com",
"login_form_endpoint_hint": "http://ip-del-tuo-server:port/api",

View File

@@ -108,10 +108,17 @@
"experimental_settings_new_asset_list_title": "試験的なグリッドを有効",
"experimental_settings_subtitle": "試験的だから自己責任でね",
"experimental_settings_title": "試験的",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "アルバム",
"library_page_favorites": "Favorites",
"library_page_new_album": "新しいアルバム",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "ログイン",
"login_form_email_hint": "example@email.com",
"login_form_endpoint_hint": "https://example.com:port/api",

View File

@@ -1,6 +1,9 @@
{
"album_info_card_backup_album_excluded": "UITGESLOTEN",
"album_info_card_backup_album_included": "INGESLOTEN",
"album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} items",
"album_thumbnail_card_shared": " · Gedeeld",
"album_viewer_appbar_share_delete": "Verwijder album",
"album_viewer_appbar_share_err_delete": "Fout bij verwijderen album",
"album_viewer_appbar_share_err_leave": "Fout bij verlaten album",
@@ -9,6 +12,8 @@
"album_viewer_appbar_share_leave": "Verlaat album",
"album_viewer_appbar_share_remove": "Verwijder uit album",
"album_viewer_page_share_add_users": "Voeg gebruiker toe",
"asset_list_settings_subtitle": "Foto grid layout instellingen",
"asset_list_settings_title": "Foto Grid",
"backup_album_selection_page_albums_device": "Albums op apparaat ({})",
"backup_album_selection_page_albums_tap": "Tik om in te voegen, dubbel tik om uit te sluiten",
"backup_album_selection_page_assets_scatter": "Items kunnen over verschillende albums verdeeld zijn, dus albums kunnen ingesloten of uitgesloten zijn van het backup proces.",
@@ -16,137 +21,182 @@
"backup_album_selection_page_selection_info": "Selectie info",
"backup_album_selection_page_total_assets": "Totaal unieke items",
"backup_all": "Alle",
"backup_background_service_default_notification": "Controleren op nieuw items…",
"backup_background_service_upload_failure_notification": "Fout bij upload {}",
"backup_background_service_in_progress_notification": "Backuppen van items…",
"backup_background_service_current_upload_notification": "Uploaden {}",
"backup_background_service_error_title": "Backup fout",
"backup_background_service_connection_failed_message": "Fout bij verbinden server. Opnieuw proberen…",
"backup_background_service_backup_failed_message": "Fout bij backuppen items. Opnieuw proberen…",
"backup_controller_page_albums": "Backup Albums",
"backup_controller_page_backup": "Backup",
"backup_controller_page_backup_selected": "Geselecteerd: ",
"backup_controller_page_backup_sub": "Foto's en video's gebackupped",
"backup_controller_page_background_description": "Gebruik achtergrondservice om automatisch nieuwe items te uploaden naar server zonder de app te openen",
"backup_controller_page_background_wifi": "Alleen op WiFi",
"backup_background_service_connection_failed_message": "Fout bij verbinden server. Opnieuw proberen…",
"backup_background_service_current_upload_notification": "Uploaden {}",
"backup_background_service_default_notification": "Controleren op nieuw items…",
"backup_background_service_error_title": "Backup fout",
"backup_background_service_in_progress_notification": "Back-up maken van items…",
"backup_background_service_upload_failure_notification": "Fout bij upload {}",
"backup_controller_page_albums": "Back-up albums",
"backup_controller_page_background_battery_info_link": "Toon me hoe",
"backup_controller_page_background_battery_info_message": "Schakel voor de beste back-up ervaring op de achtergrond alle batterij optimalisaties uit, die de achtergrondactiviteit van Immich beperkt.\n\nAangezien dit apparaatspecifiek is, zoek de vereiste informatie op voor de fabrikant van je apparaat.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Batterij optimalisaties",
"backup_controller_page_background_charging": "Alleen tijdens opladen",
"backup_controller_page_background_is_on": "Automatische achtergrond backup staat aan",
"backup_controller_page_background_is_off": "Automatische achtergrond backup staat uit",
"backup_controller_page_background_turn_on": "Zet achtergrondservice aan",
"backup_controller_page_background_turn_off": "Zet achtergrondservice uit",
"backup_controller_page_background_configure_error": "Achtergrondservice configuratie mislukt",
"backup_controller_page_background_delay": "Back-up vertraging nieuwe items: {}",
"backup_controller_page_background_description": "Schakel de achtergrondservice in om automatisch een back-up te maken van nieuwe items zonder de app te hoeven openen",
"backup_controller_page_background_is_off": "Automatische achtergrond back-up staat uit",
"backup_controller_page_background_is_on": "Automatische achtergrond back-up staat aan",
"backup_controller_page_background_turn_off": "Zet achtergrondservice uit",
"backup_controller_page_background_turn_on": "Zet achtergrondservice aan",
"backup_controller_page_background_wifi": "Alleen op WiFi",
"backup_controller_page_backup": "Back-up",
"backup_controller_page_backup_selected": "Geselecteerd: ",
"backup_controller_page_backup_sub": "Geback-upte foto's en video's",
"backup_controller_page_cancel": "Annuleren",
"backup_controller_page_created": "Gemaakt op: {}",
"backup_controller_page_desc_backup": "Configureer backup om automatisch nieuwe items te uploaden naar server.",
"backup_controller_page_desc_backup": "Schakel back-up op de voorgrond in om automatisch nieuwe items naar de server te uploaden bij het openen van de app.",
"backup_controller_page_excluded": "Uitgezonderd: ",
"backup_controller_page_failed": "Mislukt ({})",
"backup_controller_page_filename": "Bestandsnaam: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Backup informatie",
"backup_controller_page_info": "Back-up informatie",
"backup_controller_page_none_selected": "Geen geselecteerd",
"backup_controller_page_remainder": "Rest",
"backup_controller_page_remainder_sub": "Overgebleven foto's en video's om te backuppen uit selectie",
"backup_controller_page_remainder": "Resterend",
"backup_controller_page_remainder_sub": "Resterende foto's en video's om een back-up van te maken uit selectie",
"backup_controller_page_select": "Selecteer",
"backup_controller_page_server_storage": "Server Opslag",
"backup_controller_page_start_backup": "Start Backup",
"backup_controller_page_status_off": "Backup staat uit",
"backup_controller_page_status_on": "Backup staat aan",
"backup_controller_page_server_storage": "Server opslag",
"backup_controller_page_start_backup": "Back-up uitvoeren",
"backup_controller_page_status_off": "Automatische back-up op de voorgrond staat uit",
"backup_controller_page_status_on": "Automatische back-up op de voorgrond staat aan",
"backup_controller_page_storage_format": "{} van {} gebruikt",
"backup_controller_page_to_backup": "Albums om te backuppen",
"backup_controller_page_to_backup": "Albums om een back-up van te maken",
"backup_controller_page_total": "Totaal",
"backup_controller_page_total_sub": "Alle unieke foto's en video's uit geselecteerde albums",
"backup_controller_page_turn_off": "Backup uitzetten",
"backup_controller_page_turn_on": "Backup aanzetten",
"backup_controller_page_turn_off": "Zet back-up op de voorgrond uit",
"backup_controller_page_turn_on": "Zet back-up op de voorgrond aan",
"backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden",
"backup_err_only_album": "Kan niet alleen het album verwijderen",
"backup_err_only_album": "Kan het enige album niet verwijderen",
"backup_info_card_assets": "items",
"cache_settings_album_thumbnails": "Thumbnails bibliotheekpagina ({} items)",
"cache_settings_clear_cache_button": "Cache wissen",
"cache_settings_clear_cache_button_title": "Wist de cache van de app. Dit zal de presentaties van de app aanzienlijk beïnvloeden totdat de cache opnieuw is opgebouwd.",
"cache_settings_image_cache_size": "Grootte afbeeldingscache ({} items)",
"cache_settings_statistics_album": "Bibliotheek thumbnails",
"cache_settings_statistics_assets": "{} items ({})",
"cache_settings_statistics_full": "Volledige afbeeldingen",
"cache_settings_statistics_shared": "Gedeeld album thumbnails",
"cache_settings_statistics_thumbnail": "Thumbnails",
"cache_settings_statistics_title": "Cachegebruik",
"cache_settings_subtitle": "Beheer het cachegedrag van de Immich app",
"cache_settings_thumbnail_size": "Thumbnail cachegrootte ({} items)",
"cache_settings_title": "Cache instellingen",
"control_bottom_app_bar_add_to_album": "Toevoegen aan album",
"control_bottom_app_bar_album_info": "{} items",
"control_bottom_app_bar_album_info_shared": "{} items · Gedeeld",
"control_bottom_app_bar_create_new_album": "Maak nieuw album",
"control_bottom_app_bar_delete": "Verwijderen",
"create_shared_album_page_share": "Delen",
"control_bottom_app_bar_share": "Delen",
"create_album_page_untitled": "Naamloos",
"create_shared_album_page_create": "Aanmaken",
"create_shared_album_page_share_add_assets": "VOEG FOTO'S TOE",
"create_shared_album_page_share_select_photos": "Selecteer Foto's",
"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",
"create_shared_album_page_share": "Delen",
"create_shared_album_page_share_add_assets": "ITEMS TOEVOEGEN",
"create_shared_album_page_share_select_photos": "Selecteer foto's",
"daily_title_text_date": "E dd MMM",
"daily_title_text_date_year": "E dd MMM yyyy",
"date_format": "E d LLL y • H:mm",
"delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat",
"delete_dialog_cancel": "Annuleren",
"delete_dialog_ok": "Verwijderen",
"delete_dialog_title": "Verwijder permanent",
"exif_bottom_sheet_description": "Voeg beschrijving toe...",
"delete_dialog_title": "Permanent verwijderen",
"exif_bottom_sheet_description": "Beschrijving toevoegen...",
"exif_bottom_sheet_details": "DETAILS",
"exif_bottom_sheet_location": "LOCATIE",
"login_form_button_text": "Login",
"experimental_settings_new_asset_list_subtitle": "Werk in uitvoering",
"experimental_settings_new_asset_list_title": "Experimenteel foto grid inschakelen",
"experimental_settings_subtitle": "Gebruik op eigen risico!",
"experimental_settings_title": "Experimenteel",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "{added} items toegevoegd aan album {album}. {failed} items staan al in het album.",
"home_page_add_to_album_success": "{added} items toegevoegd aan album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albums",
"library_page_favorites": "Favorites",
"library_page_new_album": "Nieuw album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Inloggen",
"login_form_email_hint": "jouwemail@email.com",
"login_form_endpoint_hint": "http://jouw-server-ip:port/api",
"login_form_endpoint_url": "Server URL",
"login_form_err_http": "Voer http:// of https:// in",
"login_form_err_invalid_email": "Ongeldige Email",
"login_form_err_invalid_email": "Ongeldig e-mailadres",
"login_form_err_invalid_url": "Ongeldige URL",
"login_form_err_leading_whitespace": "Spatie aan het begin",
"login_form_err_trailing_whitespace": "Spatie aan het eind",
"login_form_failed_login": "Fout bij inloggen, controleer server url, email en wachtwoord",
"login_form_label_email": "Email",
"login_form_failed_get_oauth_server_config": "Fout bij inloggen met OAuth, controleer server URL",
"login_form_failed_get_oauth_server_disable": "OAuth functie is niet beschikbaar op deze server",
"login_form_failed_login": "Fout bij inloggen, controleer server URL, e-mailadres en wachtwoord",
"login_form_label_email": "E-mailadres",
"login_form_label_password": "Wachtwoord",
"login_form_password_hint": "wachtwoord",
"login_form_save_login": "Ingelogd blijven",
"monthly_title_text_date_format": "MMMM y",
"profile_drawer_client_server_up_to_date": "Client en Server zijn up-to-date",
"profile_drawer_sign_out": "Uitloggen",
"profile_drawer_app_logs": "Logboek",
"profile_drawer_client_server_up_to_date": "Client en server zijn up-to-date",
"profile_drawer_settings": "Instellingen",
"search_bar_hint": "Zoek je foto's",
"profile_drawer_sign_out": "Uitloggen",
"search_bar_hint": "Zoeken naar foto's",
"search_page_no_objects": "Geen object gegevens beschikbaar",
"search_page_no_places": "Geen locatie gegevens beschikbaar",
"search_page_places": "Plaatsen",
"search_page_things": "Dingen",
"search_result_page_new_search_hint": "Nieuw resultaat",
"search_result_page_new_search_hint": "Nieuwe zoekopdracht",
"select_additional_user_for_sharing_page_suggestions": "Suggesties",
"select_user_for_sharing_page_err_album": "Album aanmaken mislukt",
"select_user_for_sharing_page_share_suggestions": "Suggesties",
"setting_image_viewer_help": "De gedetailleerde weergave laadt eerst de kleine thumbnail, vervolgens het middelgrote voorbeeld (indien ingeschakeld) en ten slotte het origineel (indien ingeschakeld).",
"setting_image_viewer_original_subtitle": "Inschakelen om de originele afbeelding met volledige resolutie (groot!) te laden. Uitschakelen om datagebruik te verminderen (zowel netwerk- als apparaatcache).",
"setting_image_viewer_original_title": "Originele afbeelding laden",
"setting_image_viewer_preview_subtitle": "Schakel in om een afbeelding met middelgrote resolutie te laden. Schakel uit om alleen het origineel direct te laden of alleen de thumbnail te gebruiken.",
"setting_image_viewer_preview_title": "Voorbeeldafbeelding laden",
"setting_notifications_notify_failures_grace_period": "Meld back-upfouten op de achtergrond: {}",
"setting_notifications_notify_hours": "{} uur",
"setting_notifications_notify_immediately": "meteen",
"setting_notifications_notify_minutes": "{} minuten",
"setting_notifications_notify_never": "nooit",
"setting_notifications_notify_seconds": "{} seconden",
"setting_notifications_single_progress_subtitle": "Gedetaileerde informatie over de uploadvoortgang per item",
"setting_notifications_single_progress_title": "Toon gedetailleerde informatie over back-ups op de achtergrond",
"setting_notifications_subtitle": "Werk je notificatievoorkeuren bij",
"setting_notifications_title": "Notificaties",
"setting_notifications_total_progress_subtitle": "Algehele uploadvoortgang (voltooid/totaal aantal items)",
"setting_notifications_total_progress_title": "Toon de totale voortgang van achtergrond back-up",
"setting_pages_app_bar_settings": "Instellingen",
"settings_require_restart": "Start Immich opnieuw op om deze instelling toe te passen",
"share_add": "Toevoegen",
"share_add_photos": "Foto's toevoegen",
"share_add_title": "Titel toevoegen",
"share_create_album": "Album aanmaken",
"share_dialog_preparing": "Voorbereiden...",
"share_invite": "Uitnodigen voor album",
"sharing_page_album": "Gedeelde albums",
"sharing_page_description": "Maak gedeelde albums om foto's en video's te delen met mensen in je netwerk.",
"sharing_page_empty_list": "LEGE LIJST",
"sharing_silver_appbar_create_shared_album": "Maak gedeeld album",
"sharing_silver_appbar_share_partner": "Delen met partner",
"tab_controller_nav_library": "Bibliotheek",
"tab_controller_nav_photos": "Foto's",
"tab_controller_nav_search": "Zoeken",
"tab_controller_nav_sharing": "Delen",
"tab_controller_nav_library": "Bibliotheek",
"theme_setting_asset_list_storage_indicator_title": "Laat ruimte indicator zien bij item tegels",
"theme_setting_asset_list_tiles_per_row_title": "Aantal items per rij ({})",
"theme_setting_dark_mode_switch": "Donkere modus",
"theme_setting_image_viewer_quality_subtitle": "Pas de kwaliteit aan van de gedetailleerde foto weergave",
"theme_setting_image_viewer_quality_title": "Foto weergave kwaliteit",
"theme_setting_system_theme_switch": "Automatisch (volg systeeminstelling)",
"theme_setting_theme_subtitle": "Kies de thema instelling van de app",
"theme_setting_theme_title": "Thema",
"theme_setting_three_stage_loading_subtitle": "Laden in drie fasen kan de laadprestaties verbeteren, maar veroorzaakt een aanzienlijk hogere netwerkbelasting",
"theme_setting_three_stage_loading_title": "Schakel laden in drie fasen in",
"version_announcement_overlay_ack": "Bevestig",
"version_announcement_overlay_release_notes": "release opmerkingen",
"version_announcement_overlay_text_1": "Er is een nieuwe versie beschikbaar van",
"version_announcement_overlay_text_2": "neem je tijd en bezoek de ",
"version_announcement_overlay_text_3": " controleer of je docker-compose en .env up-to-date zijn om te voorkomen dat er misconfiguraties zijn, in het bijzonder als je gebruik maakt van WatchTower of een ander mechanisme dat je server automatisch configureert.",
"version_announcement_overlay_title": "Nieuwe server versie beschikbaar \uD83C\uDF89",
"album_thumbnail_card_item": "1 item",
"album_thumbnail_card_items": "{} items",
"album_thumbnail_card_shared": " · Gedeeld",
"library_page_albums": "Albums",
"library_page_new_album": "Nieuw album",
"create_album_page_untitled": "Naamloos",
"share_dialog_preparing": "Voorbereiden...",
"control_bottom_app_bar_share": "Delen",
"setting_pages_app_bar_settings": "Instellingen",
"theme_setting_theme_title": "Thema",
"theme_setting_theme_subtitle": "Kies de thema instelling van de app",
"theme_setting_system_theme_switch": "Automatisch (volg systeeminstelling)",
"theme_setting_dark_mode_switch": "Donkere modus",
"theme_setting_image_viewer_quality_title": "Foto weergave kwaliteit",
"theme_setting_image_viewer_quality_subtitle": "Pas de kwaliteit aan van de gedetailleerde foto weergave",
"theme_setting_three_stage_loading_title": "Drie-laags laden inschakelen",
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
"asset_list_settings_title": "Foto Grid",
"asset_list_settings_subtitle": "Foto grid layout instellingen",
"theme_setting_asset_list_storage_indicator_title": "Laat ruimte indicator zien bij item tegels",
"theme_setting_asset_list_tiles_per_row_title": "Aantal items per rij ({})",
"setting_notifications_title": "Notificaties",
"setting_notifications_subtitle": "Werk je notificatievoorkeuren bij",
"setting_notifications_notify_failures_grace_period": "Melding achtergrond backup fouten: {}",
"setting_notifications_notify_immediately": "meteen",
"setting_notifications_notify_minutes": "{} minuten",
"setting_notifications_notify_hours": "{} uur",
"setting_notifications_notify_never": "nooit"
}
"version_announcement_overlay_text_3": " controleer of je docker-compose en .env up-to-date zijn om te voorkomen dat er misconfiguraties zijn, in het bijzonder als je gebruik maakt van WatchTower of een ander mechanisme dat je serverapplicatie automatisch bijwerkt.",
"version_announcement_overlay_title": "Nieuwe serverversie beschikbaar \uD83C\uDF89"
}

View File

@@ -108,10 +108,17 @@
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
"experimental_settings_subtitle": "Use at your own risk!",
"experimental_settings_title": "Experimental",
"favorites_page_title": "Favorites",
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
"home_page_building_timeline": "Building the timeline",
"home_page_first_time_notice": "If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).",
"library_page_albums": "Albumy",
"library_page_favorites": "Favorites",
"library_page_new_album": "Nowy album",
"library_page_sharing": "Sharing",
"library_page_sort_created": "Most recently created",
"library_page_sort_title": "Album title",
"login_form_button_text": "Login",
"login_form_email_hint": "twojmail@email.com",
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@@ -0,0 +1,138 @@
flutter_native_splash:
# This package generates native code to customize Flutter's default white native splash screen
# with background color and splash image.
# Customize the parameters below, and run the following command in the terminal:
# flutter pub run flutter_native_splash:create
# To restore Flutter's default white splash screen, run the following command in the terminal:
# flutter pub run flutter_native_splash:remove
# color or background_image is the only required parameter. Use color to set the background
# of your splash screen to a solid color. Use background_image to set the background of your
# splash screen to a png image. This is useful for gradients. The image will be stretch to the
# size of the app. Only one parameter can be used, color and background_image cannot both be set.
#background_image: "assets/immich-logo-no-outline.png"
color: "#f6f8fe"
# Optional parameters are listed below. To enable a parameter, uncomment the line by removing
# the leading # character.
# The image parameter allows you to specify an image used in the splash screen. It must be a
# png file and should be sized for 4x pixel density.
image: assets/immich-splash.png
# The branding property allows you to specify an image used as branding in the splash screen.
# It must be a png file. It is supported for Android, iOS and the Web. For Android 12,
# see the Android 12 section below.
#branding: assets/dart.png
# To position the branding image at the bottom of the screen you can use bottom, bottomRight,
# and bottomLeft. The default values is bottom if not specified or specified something else.
#branding_mode: bottom
# The color_dark, background_image_dark, image_dark, branding_dark are parameters that set the background
# and image when the device is in dark mode. If they are not specified, the app will use the
# parameters from above. If the image_dark parameter is specified, color_dark or
# background_image_dark must be specified. color_dark and background_image_dark cannot both be
# set.
color_dark: "#000000"
#background_image_dark: "assets/dark-background.png"
#image_dark: assets/splash-invert.png
#branding_dark: assets/dart_dark.png
# Android 12 handles the splash screen differently than previous versions. Please visit
# https://developer.android.com/guide/topics/ui/splash-screen
# Following are Android 12 specific parameter.
android_12:
# The image parameter sets the splash screen icon image. If this parameter is not specified,
# the app's launcher icon will be used instead.
# Please note that the splash screen will be clipped to a circle on the center of the screen.
# App icon with an icon background: This should be 960×960 pixels, and fit within a circle
# 640 pixels in diameter.
# App icon without an icon background: This should be 1152×1152 pixels, and fit within a circle
# 768 pixels in diameter.
image: assets/immich-splash-android12.png
# Splash screen background color.
#color: "#42a5f5"
# App icon background color.
#icon_background_color: "#111111"
# The branding property allows you to specify an image used as branding in the splash screen.
#branding: assets/dart.png
# The image_dark, color_dark, icon_background_color_dark, and branding_dark set values that
# apply when the device is in dark mode. If they are not specified, the app will use the
# parameters from above.
#image_dark: assets/android12splash-invert.png
#color_dark: "#042a49"
#icon_background_color_dark: "#eeeeee"
# The android, ios and web parameters can be used to disable generating a splash screen on a given
# platform.
#android: false
#ios: false
#web: false
# Platform specific images can be specified with the following parameters, which will override
# the respective parameter. You may specify all, selected, or none of these parameters:
#color_android: "#42a5f5"
#color_dark_android: "#042a49"
#color_ios: "#42a5f5"
#color_dark_ios: "#042a49"
#color_web: "#42a5f5"
#color_dark_web: "#042a49"
#image_android: assets/splash-android.png
#image_dark_android: assets/splash-invert-android.png
#image_ios: assets/splash-ios.png
#image_dark_ios: assets/splash-invert-ios.png
#image_web: assets/splash-web.png
#image_dark_web: assets/splash-invert-web.png
#background_image_android: "assets/background-android.png"
#background_image_dark_android: "assets/dark-background-android.png"
#background_image_ios: "assets/background-ios.png"
#background_image_dark_ios: "assets/dark-background-ios.png"
#background_image_web: "assets/background-web.png"
#background_image_dark_web: "assets/dark-background-web.png"
#branding_android: assets/brand-android.png
#branding_dark_android: assets/dart_dark-android.png
#branding_ios: assets/brand-ios.png
#branding_dark_ios: assets/dart_dark-ios.png
# The position of the splash image can be set with android_gravity, ios_content_mode, and
# web_image_mode parameters. All default to center.
#
# android_gravity can be one of the following Android Gravity (see
# https://developer.android.com/reference/android/view/Gravity): bottom, center,
# center_horizontal, center_vertical, clip_horizontal, clip_vertical, end, fill, fill_horizontal,
# fill_vertical, left, right, start, or top.
#android_gravity: center
#
# ios_content_mode can be one of the following iOS UIView.ContentMode (see
# https://developer.apple.com/documentation/uikit/uiview/contentmode): scaleToFill,
# scaleAspectFit, scaleAspectFill, center, top, bottom, left, right, topLeft, topRight,
# bottomLeft, or bottomRight.
#ios_content_mode: center
#
# web_image_mode can be one of the following modes: center, contain, stretch, and cover.
#web_image_mode: center
# The screen orientation can be set in Android with the android_screen_orientation parameter.
# Valid parameters can be found here:
# https://developer.android.com/guide/topics/manifest/activity-element#screen
#android_screen_orientation: sensorLandscape
# To hide the notification bar, use the fullscreen parameter. Has no effect in web since web
# has no notification bar. Defaults to false.
# NOTE: Unlike Android, iOS will not automatically show the notification bar when the app loads.
# To show the notification bar, add the following code to your Flutter app:
# WidgetsFlutterBinding.ensureInitialized();
# SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom, SystemUiOverlay.top]);
#fullscreen: true
# If you have changed the name(s) of your info.plist file(s), you can specify the filename(s)
# with the info_plist_files parameter. Remove only the # characters in the three lines below,
# do not remove any spaces:
#info_plist_files:
# - 'ios/Runner/Info-Debug.plist'
# - 'ios/Runner/Info-Release.plist'

View File

@@ -1,7 +1,9 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hive/hive.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:integration_test/integration_test.dart';
import 'package:isar/isar.dart';
// ignore: depend_on_referenced_packages
import 'package:meta/meta.dart';
import 'package:immich_mobile/main.dart' as app;
@@ -34,8 +36,12 @@ class ImmichTestHelper {
// Clear all data from Hive
await Hive.deleteFromDisk();
await app.openBoxes();
// Clear all data from Isar (reuse existing instance if available)
final db = Isar.getInstance() ?? await app.loadDb();
await Store.clear();
await db.writeTxn(() => db.clear());
// Load main Widget
await tester.pumpWidget(app.getMainWidget());
await tester.pumpWidget(app.getMainWidget(db));
// Post run tasks
await tester.pumpAndSettle();
await EasyLocalization.ensureInitialized();

View File

@@ -1,5 +1,7 @@
PODS:
- Flutter (1.0.0)
- flutter_native_splash (0.0.1):
- Flutter
- flutter_udid (0.0.1):
- Flutter
- SAMKeychain
@@ -15,6 +17,8 @@ PODS:
- Flutter
- integration_test (0.0.1):
- Flutter
- isar_flutter_libs (1.0.0):
- Flutter
- package_info_plus (0.4.5):
- Flutter
- path_provider_ios (0.0.1):
@@ -40,11 +44,13 @@ PODS:
DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
- flutter_web_auth (from `.symlinks/plugins/flutter_web_auth/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- integration_test (from `.symlinks/plugins/integration_test/ios`)
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
@@ -64,6 +70,8 @@ SPEC REPOS:
EXTERNAL SOURCES:
Flutter:
:path: Flutter
flutter_native_splash:
:path: ".symlinks/plugins/flutter_native_splash/ios"
flutter_udid:
:path: ".symlinks/plugins/flutter_udid/ios"
flutter_web_auth:
@@ -74,6 +82,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_picker_ios/ios"
integration_test:
:path: ".symlinks/plugins/integration_test/ios"
isar_flutter_libs:
:path: ".symlinks/plugins/isar_flutter_libs/ios"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_ios:
@@ -95,12 +105,14 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604

View File

@@ -360,7 +360,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 82;
CURRENT_PROJECT_VERSION = 85;
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 = 82;
CURRENT_PROJECT_VERSION = 85;
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 = 82;
CURRENT_PROJECT_VERSION = 85;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -0,0 +1,22 @@
{
"images" : [
{
"filename" : "background.png",
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"filename" : "darkbackground.png",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 B

View File

@@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints>
</view>
</viewController>
@@ -32,6 +38,7 @@
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchImage" width="320" height="320"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>

View File

@@ -1,105 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Immich</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>immich_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.43.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>82</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true />
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
<key>NSLocationAlwaysUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true />
<key>io.flutter.embedded_views_preview</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>CFBundleLocalizations</key>
<array>
<string>cs</string>
<string>da</string>
<string>de</string>
<string>en</string>
<string>es</string>
<string>fi</string>
<string>fr</string>
<string>it</string>
<string>ja</string>
<string>ko</string>
<string>nl</string>
<string>pl</string>
<string>pt</string>
<string>ru</string>
<string>sk</string>
<string>zh</string>
</array>
</dict>
</plist>
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Immich</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>immich_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.46.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>85</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSLocationAlwaysUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>CFBundleLocalizations</key>
<array>
<string>cs</string>
<string>da</string>
<string>de</string>
<string>en</string>
<string>es</string>
<string>fi</string>
<string>fr</string>
<string>it</string>
<string>ja</string>
<string>ko</string>
<string>nl</string>
<string>pl</string>
<string>pt</string>
<string>ru</string>
<string>sk</string>
<string>zh</string>
</array>
<key>UIStatusBarHidden</key>
<false/>
</dict>
</plist>

View File

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

View File

@@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000396">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000283">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.478301">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.755864">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="3.846552">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="7.319767">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.367554">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.376562">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="75.618447">
<testcase classname="fastlane.lanes" name="4: build_app" time="105.396514">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="47.502114">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="86.092896">
</testcase>

View File

@@ -5,6 +5,7 @@ const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
const String assetEtagKey = 'immichAssetEtagKey'; // Key 5
const String userIdKey = 'immichUserIdKey'; // Key 6
// Login Info
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box

View File

@@ -3,6 +3,7 @@ import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_displaymode/flutter_displaymode.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@@ -16,8 +17,10 @@ import 'package:immich_mobile/modules/login/providers/authentication.provider.da
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
@@ -25,11 +28,16 @@ import 'package:immich_mobile/shared/services/immich_logger.service.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
import 'package:immich_mobile/utils/immich_app_theme.dart';
import 'package:immich_mobile/utils/migration.dart';
import 'package:isar/isar.dart';
import 'package:path_provider/path_provider.dart';
import 'constants/hive_box.dart';
void main() async {
await initApp();
runApp(getMainWidget());
final db = await loadDb();
await migrateHiveToStoreIfNecessary();
runApp(getMainWidget(db));
}
Future<void> openBoxes() async {
@@ -69,13 +77,27 @@ Future<void> initApp() async {
ImmichLogger().init();
}
Widget getMainWidget() {
Future<Isar> loadDb() async {
final dir = await getApplicationDocumentsDirectory();
Isar db = await Isar.open(
[StoreValueSchema],
directory: dir.path,
maxSizeMiB: 256,
);
Store.init(db);
return db;
}
Widget getMainWidget(Isar db) {
return EasyLocalization(
supportedLocales: locales,
path: translationsPath,
useFallbackTranslations: true,
fallbackLocale: locales.first,
child: const ProviderScope(child: ImmichApp()),
child: ProviderScope(
overrides: [dbProvider.overrideWithValue(db)],
child: const ImmichApp(),
),
);
}
@@ -155,6 +177,8 @@ class ImmichAppState extends ConsumerState<ImmichApp>
var router = ref.watch(appRouterProvider);
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
return MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,

View File

@@ -2,24 +2,26 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/album.dart';
class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
class AlbumNotifier extends StateNotifier<List<Album>> {
AlbumNotifier(this._albumService, this._albumCacheService) : super([]);
final AlbumService _albumService;
final AlbumCacheService _albumCacheService;
_cacheState() {
void _cacheState() {
_albumCacheService.put(state);
}
getAllAlbums() async {
Future<void> getAllAlbums() async {
if (await _albumCacheService.isValid() && state.isEmpty) {
state = await _albumCacheService.get();
final albums = await _albumCacheService.get();
if (albums != null) {
state = albums;
}
}
List<AlbumResponseDto>? albums =
await _albumService.getAlbums(isShared: false);
final albums = await _albumService.getAlbums(isShared: false);
if (albums != null) {
state = albums;
@@ -27,17 +29,16 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}
}
deleteAlbum(String albumId) {
state = state.where((album) => album.id != albumId).toList();
void deleteAlbum(Album album) {
state = state.where((a) => a.id != album.id).toList();
_cacheState();
}
Future<AlbumResponseDto?> createAlbum(
Future<Album?> createAlbum(
String albumTitle,
Set<Asset> assets,
) async {
AlbumResponseDto? album =
await _albumService.createAlbum(albumTitle, assets, []);
Album? album = await _albumService.createAlbum(albumTitle, assets, []);
if (album != null) {
state = [...state, album];
@@ -49,8 +50,7 @@ class AlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}
}
final albumProvider =
StateNotifierProvider<AlbumNotifier, List<AlbumResponseDto>>((ref) {
final albumProvider = StateNotifierProvider<AlbumNotifier, List<Album>>((ref) {
return AlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(albumCacheServiceProvider),

View File

@@ -2,6 +2,7 @@ import 'package:hooks_riverpod/hooks_riverpod.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';
import 'package:immich_mobile/shared/models/album.dart';
class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
AlbumViewerNotifier(this.ref)
@@ -30,14 +31,12 @@ class AlbumViewerNotifier extends StateNotifier<AlbumViewerPageState> {
}
Future<bool> changeAlbumTitle(
String albumId,
String ownerId,
Album album,
String newAlbumTitle,
) async {
AlbumService service = ref.watch(albumServiceProvider);
bool isSuccess =
await service.changeTitleAlbum(albumId, ownerId, newAlbumTitle);
bool isSuccess = await service.changeTitleAlbum(album, newAlbumTitle);
if (isSuccess) {
state = state.copyWith(editTitleText: "", isEditAlbum: false);

View File

@@ -2,30 +2,31 @@ import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/album/services/album_cache.service.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/user.dart';
class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
SharedAlbumNotifier(this._sharedAlbumService, this._sharedAlbumCacheService)
class SharedAlbumNotifier extends StateNotifier<List<Album>> {
SharedAlbumNotifier(this._albumService, this._sharedAlbumCacheService)
: super([]);
final AlbumService _sharedAlbumService;
final AlbumService _albumService;
final SharedAlbumCacheService _sharedAlbumCacheService;
_cacheState() {
void _cacheState() {
_sharedAlbumCacheService.put(state);
}
Future<AlbumResponseDto?> createSharedAlbum(
Future<Album?> createSharedAlbum(
String albumName,
Set<Asset> assets,
List<String> sharedUserIds,
Iterable<Asset> assets,
Iterable<User> sharedUsers,
) async {
try {
var newAlbum = await _sharedAlbumService.createAlbum(
var newAlbum = await _albumService.createAlbum(
albumName,
assets,
sharedUserIds,
sharedUsers,
);
if (newAlbum != null) {
@@ -41,13 +42,15 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}
}
getAllSharedAlbums() async {
Future<void> getAllSharedAlbums() async {
if (await _sharedAlbumCacheService.isValid() && state.isEmpty) {
state = await _sharedAlbumCacheService.get();
final albums = await _sharedAlbumCacheService.get();
if (albums != null) {
state = albums;
}
}
List<AlbumResponseDto>? sharedAlbums =
await _sharedAlbumService.getAlbums(isShared: true);
List<Album>? sharedAlbums = await _albumService.getAlbums(isShared: true);
if (sharedAlbums != null) {
state = sharedAlbums;
@@ -55,16 +58,16 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}
}
deleteAlbum(String albumId) async {
state = state.where((album) => album.id != albumId).toList();
void deleteAlbum(Album album) {
state = state.where((a) => a.id != album.id).toList();
_cacheState();
}
Future<bool> leaveAlbum(String albumId) async {
var res = await _sharedAlbumService.leaveAlbum(albumId);
Future<bool> leaveAlbum(Album album) async {
var res = await _albumService.leaveAlbum(album);
if (res) {
state = state.where((album) => album.id != albumId).toList();
state = state.where((a) => a.id != album.id).toList();
_cacheState();
return true;
} else {
@@ -73,10 +76,10 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}
Future<bool> removeAssetFromAlbum(
String albumId,
List<String> assetIds,
Album album,
Iterable<Asset> assets,
) async {
var res = await _sharedAlbumService.removeAssetFromAlbum(albumId, assetIds);
var res = await _albumService.removeAssetFromAlbum(album, assets);
if (res) {
return true;
@@ -87,15 +90,15 @@ class SharedAlbumNotifier extends StateNotifier<List<AlbumResponseDto>> {
}
final sharedAlbumProvider =
StateNotifierProvider<SharedAlbumNotifier, List<AlbumResponseDto>>((ref) {
StateNotifierProvider<SharedAlbumNotifier, List<Album>>((ref) {
return SharedAlbumNotifier(
ref.watch(albumServiceProvider),
ref.watch(sharedAlbumCacheServiceProvider),
);
});
final sharedAlbumDetailProvider = FutureProvider.autoDispose
.family<AlbumResponseDto?, String>((ref, albumId) async {
final sharedAlbumDetailProvider =
FutureProvider.autoDispose.family<Album?, String>((ref, albumId) async {
final AlbumService sharedAlbumService = ref.watch(albumServiceProvider);
return await sharedAlbumService.getAlbumDetail(albumId);

View File

@@ -1,10 +1,10 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/services/user.service.dart';
import 'package:openapi/api.dart';
final suggestedSharedUsersProvider =
FutureProvider.autoDispose<List<UserResponseDto>>((ref) async {
FutureProvider.autoDispose<List<User>>((ref) async {
UserService userService = ref.watch(userServiceProvider);
return await userService.getAllUsersInfo(isAll: false) ?? [];
return await userService.getAllUsers(isAll: false) ?? [];
});

View File

@@ -2,7 +2,9 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/services/api.service.dart';
import 'package:openapi/api.dart';
@@ -18,29 +20,31 @@ class AlbumService {
AlbumService(this._apiService);
Future<List<AlbumResponseDto>?> getAlbums({required bool isShared}) async {
Future<List<Album>?> getAlbums({required bool isShared}) async {
try {
return await _apiService.albumApi
final dto = await _apiService.albumApi
.getAllAlbums(shared: isShared ? isShared : null);
return dto?.map(Album.remote).toList();
} catch (e) {
debugPrint("Error getAllSharedAlbum ${e.toString()}");
return null;
}
}
Future<AlbumResponseDto?> createAlbum(
Future<Album?> createAlbum(
String albumName,
Iterable<Asset> assets,
List<String> sharedUserIds,
) async {
Iterable<Asset> assets, [
Iterable<User> sharedUsers = const [],
]) async {
try {
return await _apiService.albumApi.createAlbum(
final dto = await _apiService.albumApi.createAlbum(
CreateAlbumDto(
albumName: albumName,
assetIds: assets.map((asset) => asset.id).toList(),
sharedWithUserIds: sharedUserIds,
assetIds: assets.map((asset) => asset.remoteId!).toList(),
sharedWithUserIds: sharedUsers.map((e) => e.id).toList(),
),
);
return dto != null ? Album.remote(dto) : null;
} catch (e) {
debugPrint("Error createSharedAlbum ${e.toString()}");
return null;
@@ -50,14 +54,14 @@ class AlbumService {
/*
* Creates names like Untitled, Untitled (1), Untitled (2), ...
*/
String _getNextAlbumName(List<AlbumResponseDto>? albums) {
String _getNextAlbumName(List<Album>? albums) {
const baseName = "Untitled";
if (albums != null) {
for (int round = 0; round < albums.length; round++) {
final proposedName = "$baseName${round == 0 ? "" : " ($round)"}";
if (albums.where((a) => a.albumName == proposedName).isEmpty) {
if (albums.where((a) => a.name == proposedName).isEmpty) {
return proposedName;
}
}
@@ -65,7 +69,7 @@ class AlbumService {
return baseName;
}
Future<AlbumResponseDto?> createAlbumWithGeneratedName(
Future<Album?> createAlbumWithGeneratedName(
Iterable<Asset> assets,
) async {
return createAlbum(
@@ -75,9 +79,10 @@ class AlbumService {
);
}
Future<AlbumResponseDto?> getAlbumDetail(String albumId) async {
Future<Album?> getAlbumDetail(String albumId) async {
try {
return await _apiService.albumApi.getAlbumInfo(albumId);
final dto = await _apiService.albumApi.getAlbumInfo(albumId);
return dto != null ? Album.remote(dto) : null;
} catch (e) {
debugPrint('Error [getAlbumDetail] ${e.toString()}');
return null;
@@ -86,12 +91,12 @@ class AlbumService {
Future<AddAssetsResponseDto?> addAdditionalAssetToAlbum(
Iterable<Asset> assets,
String albumId,
Album album,
) async {
try {
var result = await _apiService.albumApi.addAssetsToAlbum(
albumId,
AddAssetsDto(assetIds: assets.map((asset) => asset.id).toList()),
album.remoteId!,
AddAssetsDto(assetIds: assets.map((asset) => asset.remoteId!).toList()),
);
return result;
} catch (e) {
@@ -102,11 +107,11 @@ class AlbumService {
Future<bool> addAdditionalUserToAlbum(
List<String> sharedUserIds,
String albumId,
Album album,
) async {
try {
var result = await _apiService.albumApi.addUsersToAlbum(
albumId,
album.remoteId!,
AddUsersDto(sharedUserIds: sharedUserIds),
);
@@ -117,9 +122,9 @@ class AlbumService {
}
}
Future<bool> deleteAlbum(String albumId) async {
Future<bool> deleteAlbum(Album album) async {
try {
await _apiService.albumApi.deleteAlbum(albumId);
await _apiService.albumApi.deleteAlbum(album.remoteId!);
return true;
} catch (e) {
debugPrint("Error deleteAlbum ${e.toString()}");
@@ -127,10 +132,9 @@ class AlbumService {
}
}
Future<bool> leaveAlbum(String albumId) async {
Future<bool> leaveAlbum(Album album) async {
try {
await _apiService.albumApi.removeUserFromAlbum(albumId, "me");
await _apiService.albumApi.removeUserFromAlbum(album.remoteId!, "me");
return true;
} catch (e) {
debugPrint("Error deleteAlbum ${e.toString()}");
@@ -139,13 +143,15 @@ class AlbumService {
}
Future<bool> removeAssetFromAlbum(
String albumId,
List<String> assetIds,
Album album,
Iterable<Asset> assets,
) async {
try {
await _apiService.albumApi.removeAssetFromAlbum(
albumId,
RemoveAssetsDto(assetIds: assetIds),
album.remoteId!,
RemoveAssetsDto(
assetIds: assets.map((e) => e.remoteId!).toList(growable: false),
),
);
return true;
@@ -156,17 +162,17 @@ class AlbumService {
}
Future<bool> changeTitleAlbum(
String albumId,
String ownerId,
Album album,
String newAlbumTitle,
) async {
try {
await _apiService.albumApi.updateAlbumInfo(
albumId,
album.remoteId!,
UpdateAlbumDto(
albumName: newAlbumTitle,
),
);
album.name = newAlbumTitle;
return true;
} catch (e) {

View File

@@ -1,32 +1,30 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/services/json_cache.dart';
import 'package:openapi/api.dart';
class BaseAlbumCacheService extends JsonCache<List<AlbumResponseDto>> {
class BaseAlbumCacheService extends JsonCache<List<Album>> {
BaseAlbumCacheService(super.cacheFileName);
@override
void put(List<AlbumResponseDto> data) {
void put(List<Album> data) {
putRawData(data.map((e) => e.toJson()).toList());
}
@override
Future<List<AlbumResponseDto>> get() async {
Future<List<Album>?> get() async {
try {
final mapList = await readRawData() as List<dynamic>;
final responseData = mapList
.map((e) => AlbumResponseDto.fromJson(e))
.whereNotNull()
.toList();
final responseData =
mapList.map((e) => Album.fromJson(e)).whereNotNull().toList();
return responseData;
} catch (e) {
await invalidate();
debugPrint(e.toString());
return [];
return null;
}
}
}
@@ -40,10 +38,9 @@ class SharedAlbumCacheService extends BaseAlbumCacheService {
}
final albumCacheServiceProvider = Provider(
(ref) => AlbumCacheService(),
(ref) => AlbumCacheService(),
);
final sharedAlbumCacheServiceProvider = Provider(
(ref) => SharedAlbumCacheService(),
(ref) => SharedAlbumCacheService(),
);

View File

@@ -8,10 +8,10 @@ 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/add_to_album_sliverlist.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:openapi/api.dart';
class AddToAlbumBottomSheet extends HookConsumerWidget {
/// The asset to add to an album
@@ -39,22 +39,22 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
[],
);
void addToAlbum(AlbumResponseDto album) async {
void addToAlbum(Album album) async {
final result = await albumService.addAdditionalAssetToAlbum(
assets,
album.id,
album,
);
if (result != null) {
if (result.alreadyInAlbum.isNotEmpty) {
ImmichToast.show(
context: context,
msg: 'Already in ${album.albumName}',
msg: 'Already in ${album.name}',
);
} else {
ImmichToast.show(
context: context,
msg: 'Added to ${album.albumName}',
msg: 'Added to ${album.name}',
);
}
}
@@ -80,6 +80,7 @@ class AddToAlbumBottomSheet extends HookConsumerWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
const Align(
alignment: Alignment.center,
child: CustomDraggingHandle(),

View File

@@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/ui/album_thumbnail_listtile.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/album.dart';
class AddToAlbumSliverList extends HookConsumerWidget {
/// The asset to add to an album
final List<AlbumResponseDto> albums;
final List<AlbumResponseDto> sharedAlbums;
final void Function(AlbumResponseDto) onAddToAlbum;
final List<Album> albums;
final List<Album> sharedAlbums;
final void Function(Album) onAddToAlbum;
const AddToAlbumSliverList({
Key? key,
@@ -21,36 +20,36 @@ class AddToAlbumSliverList extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
return SliverList(
delegate: SliverChildBuilderDelegate(
childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1),
(context, index) {
// Build shared expander
if (index == 0 && sharedAlbums.isNotEmpty) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ExpansionTile(
title: const Text('Shared'),
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
leading: const Icon(Icons.group),
children: sharedAlbums.map((album) =>
AlbumThumbnailListTile(
childCount: albums.length + (sharedAlbums.isEmpty ? 0 : 1),
(context, index) {
// Build shared expander
if (index == 0 && sharedAlbums.isNotEmpty) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ExpansionTile(
title: const Text('Shared'),
tilePadding: const EdgeInsets.symmetric(horizontal: 10.0),
leading: const Icon(Icons.group),
children: sharedAlbums
.map(
(album) => AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
),
).toList(),
),
);
}
// Build albums list
final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0);
final album = albums[offset];
return AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
)
.toList(),
),
);
}
),
// Build albums list
final offset = index - (sharedAlbums.isNotEmpty ? 1 : 0);
final album = albums[offset];
return AlbumThumbnailListTile(
album: album,
onTap: () => onAddToAlbum(album),
);
}),
);
}
}

View File

@@ -1,107 +1,114 @@
import 'package:auto_route/auto_route.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:easy_localization/easy_localization.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:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
class AlbumThumbnailCard extends StatelessWidget {
final Function()? onTap;
const AlbumThumbnailCard({
Key? key,
required this.album,
this.onTap,
}) : super(key: key);
final AlbumResponseDto album;
final Album album;
@override
Widget build(BuildContext context) {
var box = Hive.box(userInfoBox);
var cardSize = MediaQuery.of(context).size.width / 2 - 18;
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
return LayoutBuilder(
builder: (context, constraints) {
var cardSize = constraints.maxWidth;
buildEmptyThumbnail() {
return Container(
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
),
child: SizedBox(
buildEmptyThumbnail() {
return Container(
height: cardSize,
width: cardSize,
child: const Center(
child: Icon(Icons.no_photography),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
),
),
);
}
buildAlbumThumbnail() {
return CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
),
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
);
}
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: album.albumThumbnailAssetId == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
child: Center(
child: Icon(
Icons.no_photography,
size: cardSize * .15,
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: SizedBox(
width: cardSize,
child: Text(
album.albumName,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
);
}
buildAlbumThumbnail() {
return CachedNetworkImage(
width: cardSize,
height: cardSize,
fit: BoxFit.cover,
fadeInDuration: const Duration(milliseconds: 200),
imageUrl: getAlbumThumbnailUrl(
album,
type: ThumbnailFormat.JPEG,
),
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
);
}
return GestureDetector(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.only(bottom: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: album.albumThumbnailAssetId == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: SizedBox(
width: cardSize,
child: Text(
album.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
),
),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
album.assetCount == 1
? 'album_thumbnail_card_item'
: 'album_thumbnail_card_items',
style: const TextStyle(
fontSize: 12,
),
).tr(args: ['${album.assetCount}']),
if (album.shared)
const Text(
'album_thumbnail_card_shared',
style: TextStyle(
Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
album.assetCount == 1
? 'album_thumbnail_card_item'
: 'album_thumbnail_card_items',
style: const TextStyle(
fontSize: 12,
),
).tr()
],
)
],
).tr(args: ['${album.assetCount}']),
if (album.shared)
const Text(
'album_thumbnail_card_shared',
style: TextStyle(
fontSize: 12,
),
).tr()
],
)
],
),
),
),
);
},
);
}
}

View File

@@ -5,6 +5,7 @@ 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:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
@@ -15,7 +16,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
this.onTap,
}) : super(key: key);
final AlbumResponseDto album;
final Album album;
final void Function()? onTap;
@override
@@ -80,7 +81,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
album.albumName,
album.name,
style: const TextStyle(
fontWeight: FontWeight.bold,
),

View File

@@ -9,21 +9,19 @@ import 'package:immich_mobile/modules/album/providers/asset_selection.provider.d
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:openapi/api.dart';
class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
const AlbumViewerAppbar({
Key? key,
required this.albumInfo,
required this.album,
required this.userId,
required this.albumId,
}) : super(key: key);
final AlbumResponseDto albumInfo;
final Album album;
final String userId;
final String albumId;
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -34,19 +32,18 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
void onDeleteAlbumPressed(String albumId) async {
void onDeleteAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show();
bool isSuccess =
await ref.watch(albumServiceProvider).deleteAlbum(albumId);
bool isSuccess = await ref.watch(albumServiceProvider).deleteAlbum(album);
if (isSuccess) {
if (albumInfo.shared) {
ref.watch(sharedAlbumProvider.notifier).deleteAlbum(albumId);
if (album.shared) {
ref.watch(sharedAlbumProvider.notifier).deleteAlbum(album);
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
} else {
ref.watch(albumProvider.notifier).deleteAlbum(albumId);
ref.watch(albumProvider.notifier).deleteAlbum(album);
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [LibraryRoute()]));
}
@@ -62,11 +59,11 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
ImmichLoadingOverlayController.appLoader.hide();
}
void onLeaveAlbumPressed(String albumId) async {
void onLeaveAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show();
bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(albumId);
await ref.watch(sharedAlbumProvider.notifier).leaveAlbum(album);
if (isSuccess) {
AutoRouter.of(context)
@@ -84,19 +81,20 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
ImmichLoadingOverlayController.appLoader.hide();
}
void onRemoveFromAlbumPressed(String albumId) async {
void onRemoveFromAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show();
bool isSuccess =
await ref.watch(sharedAlbumProvider.notifier).removeAssetFromAlbum(
albumId,
selectedAssetsInAlbum.map((a) => a.id).toList(),
album,
selectedAssetsInAlbum,
);
if (isSuccess) {
Navigator.pop(context);
ref.watch(assetSelectionProvider.notifier).disableMultiselection();
ref.invalidate(sharedAlbumDetailProvider(albumId));
ref.watch(albumProvider.notifier).getAllAlbums();
ref.invalidate(sharedAlbumDetailProvider(album.id));
} else {
Navigator.pop(context);
ImmichToast.show(
@@ -112,27 +110,27 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
buildBottomSheetActionButton() {
if (isMultiSelectionEnable) {
if (albumInfo.ownerId == userId) {
if (album.ownerId == userId) {
return ListTile(
leading: const Icon(Icons.delete_sweep_rounded),
title: const Text(
'album_viewer_appbar_share_remove',
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
onTap: () => onRemoveFromAlbumPressed(albumId),
onTap: () => onRemoveFromAlbumPressed(),
);
} else {
return const SizedBox();
}
} else {
if (albumInfo.ownerId == userId) {
if (album.ownerId == userId) {
return ListTile(
leading: const Icon(Icons.delete_forever_rounded),
title: const Text(
'album_viewer_appbar_share_delete',
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
onTap: () => onDeleteAlbumPressed(albumId),
onTap: () => onDeleteAlbumPressed(),
);
} else {
return ListTile(
@@ -141,7 +139,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
'album_viewer_appbar_share_leave',
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
onTap: () => onLeaveAlbumPressed(albumId),
onTap: () => onLeaveAlbumPressed(),
);
}
}
@@ -179,7 +177,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
onPressed: () async {
bool isSuccess = await ref
.watch(albumViewerProvider.notifier)
.changeAlbumTitle(albumId, userId, newAlbumTitle);
.changeAlbumTitle(album, newAlbumTitle);
if (!isSuccess) {
ImmichToast.show(

View File

@@ -3,21 +3,20 @@ 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_viewer.provider.dart';
import 'package:openapi/api.dart';
import 'package:immich_mobile/shared/models/album.dart';
class AlbumViewerEditableTitle extends HookConsumerWidget {
final AlbumResponseDto albumInfo;
final Album album;
final FocusNode titleFocusNode;
const AlbumViewerEditableTitle({
Key? key,
required this.albumInfo,
required this.album,
required this.titleFocusNode,
}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final titleTextEditController =
useTextEditingController(text: albumInfo.albumName);
final titleTextEditController = useTextEditingController(text: album.name);
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
void onFocusModeChange() {
@@ -50,9 +49,7 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
onTap: () {
FocusScope.of(context).requestFocus(titleFocusNode);
ref
.watch(albumViewerProvider.notifier)
.setEditTitleText(albumInfo.albumName);
ref.watch(albumViewerProvider.notifier).setEditTitleText(album.name);
ref.watch(albumViewerProvider.notifier).enableEditAlbum();
if (titleTextEditController.text == 'Untitled') {

View File

@@ -1,6 +1,7 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
import 'package:immich_mobile/routing/router.dart';
@@ -26,6 +27,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
ref.watch(assetSelectionProvider).selectedAssetsInAlbumViewer;
final isMultiSelectionEnable =
ref.watch(assetSelectionProvider).isMultiselectEnable;
final isFavorite = ref.watch(favoriteProvider).contains(asset.id);
viewAsset() {
AutoRouter.of(context).push(
@@ -85,9 +87,23 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
right: 10,
bottom: 5,
child: Icon(
(deviceId != asset.deviceId)
? Icons.cloud_done_outlined
: Icons.photo_library_rounded,
asset.isRemote
? (deviceId == asset.deviceId
? Icons.cloud_done_outlined
: Icons.cloud_outlined)
: Icons.cloud_off_outlined,
color: Colors.white,
size: 18,
),
);
}
buildAssetFavoriteIcon() {
return const Positioned(
left: 10,
bottom: 5,
child: Icon(
Icons.star,
color: Colors.white,
size: 18,
),
@@ -141,6 +157,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
child: Stack(
children: [
buildThumbnailImage(),
if (isFavorite) buildAssetFavoriteIcon(),
if (showStorageIndicator) buildAssetStoreLocationIcon(),
if (!asset.isImage) buildVideoLabel(),
if (isMultiSelectionEnable) buildAssetSelectionIcon(),

View File

@@ -121,7 +121,7 @@ class SelectionThumbnailImage extends HookConsumerWidget {
child: Row(
children: [
Text(
asset.duration.substring(0, 7),
asset.duration.toString().substring(0, 7),
style: const TextStyle(
color: Colors.white,
fontSize: 10,

View File

@@ -1,8 +1,11 @@
import 'dart:async';
import 'package:auto_route/auto_route.dart';
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/album/providers/album.provider.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/album/models/asset_selection_page_result.model.dart';
@@ -16,11 +19,10 @@ import 'package:immich_mobile/modules/album/ui/album_viewer_thumbnail.dart';
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
import 'package:openapi/api.dart';
class AlbumViewerPage extends HookConsumerWidget {
final String albumId;
@@ -31,16 +33,16 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
FocusNode titleFocusNode = useFocusNode();
ScrollController scrollController = useScrollController();
var albumInfo = ref.watch(sharedAlbumDetailProvider(albumId));
final album = ref.watch(sharedAlbumDetailProvider(albumId));
final userId = ref.watch(authenticationProvider).userId;
/// Find out if the assets in album exist on the device
/// If they exist, add to selected asset state to show they are already selected.
void onAddPhotosPressed(AlbumResponseDto albumInfo) async {
void onAddPhotosPressed(Album albumInfo) async {
if (albumInfo.assets.isNotEmpty == true) {
ref.watch(assetSelectionProvider.notifier).addNewAssets(
albumInfo.assets.map((e) => Asset.remote(e)).toList(),
albumInfo.assets,
);
}
@@ -57,11 +59,12 @@ class AlbumViewerPage extends HookConsumerWidget {
var addAssetsResult =
await ref.watch(albumServiceProvider).addAdditionalAssetToAlbum(
returnPayload.selectedAdditionalAsset,
albumId,
albumInfo,
);
if (addAssetsResult != null &&
addAssetsResult.successfullyAdded > 0) {
ref.watch(albumProvider.notifier).getAllAlbums();
ref.invalidate(sharedAlbumDetailProvider(albumId));
}
@@ -74,10 +77,10 @@ class AlbumViewerPage extends HookConsumerWidget {
}
}
void onAddUsersPressed(AlbumResponseDto albumInfo) async {
void onAddUsersPressed(Album album) async {
List<String>? sharedUserIds =
await AutoRouter.of(context).push<List<String>?>(
SelectAdditionalUserForSharingRoute(albumInfo: albumInfo),
SelectAdditionalUserForSharingRoute(album: album),
);
if (sharedUserIds != null) {
@@ -85,7 +88,7 @@ class AlbumViewerPage extends HookConsumerWidget {
var isSuccess = await ref
.watch(albumServiceProvider)
.addAdditionalUserToAlbum(sharedUserIds, albumId);
.addAdditionalUserToAlbum(sharedUserIds, album);
if (isSuccess) {
ref.invalidate(sharedAlbumDetailProvider(albumId));
@@ -95,18 +98,18 @@ class AlbumViewerPage extends HookConsumerWidget {
}
}
Widget buildTitle(AlbumResponseDto albumInfo) {
Widget buildTitle(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
child: userId == albumInfo.ownerId
child: userId == album.ownerId
? AlbumViewerEditableTitle(
albumInfo: albumInfo,
album: album,
titleFocusNode: titleFocusNode,
)
: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
albumInfo.albumName,
album.name,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
@@ -116,30 +119,22 @@ class AlbumViewerPage extends HookConsumerWidget {
);
}
Widget buildAlbumDateRange(AlbumResponseDto albumInfo) {
String startDate = "";
DateTime parsedStartDate =
DateTime.parse(albumInfo.assets.first.createdAt);
DateTime parsedEndDate = DateTime.parse(
albumInfo.assets.last.createdAt,
); //Need default.
if (parsedStartDate.year == parsedEndDate.year) {
startDate = DateFormat('LLL d').format(parsedStartDate);
} else {
startDate = DateFormat('LLL d, y').format(parsedStartDate);
}
String endDate = DateFormat('LLL d, y').format(parsedEndDate);
Widget buildAlbumDateRange(Album album) {
final DateTime startDate = album.assets.first.createdAt;
final DateTime endDate = album.assets.last.createdAt; //Need default.
final String startDateText =
DateFormat(startDate.year == endDate.year ? 'LLL d' : 'LLL d, y')
.format(startDate);
final String endDateText = DateFormat('LLL d, y').format(endDate);
return Padding(
padding: EdgeInsets.only(
left: 16.0,
top: 8.0,
bottom: albumInfo.shared ? 0.0 : 8.0,
bottom: album.shared ? 0.0 : 8.0,
),
child: Text(
"$startDate-$endDate",
"$startDateText-$endDateText",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
@@ -149,15 +144,14 @@ class AlbumViewerPage extends HookConsumerWidget {
);
}
Widget buildHeader(AlbumResponseDto albumInfo) {
Widget buildHeader(Album album) {
return SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildTitle(albumInfo),
if (albumInfo.assets.isNotEmpty == true)
buildAlbumDateRange(albumInfo),
if (albumInfo.shared)
buildTitle(album),
if (album.assets.isNotEmpty == true) buildAlbumDateRange(album),
if (album.shared)
SizedBox(
height: 60,
child: ListView.builder(
@@ -181,7 +175,7 @@ class AlbumViewerPage extends HookConsumerWidget {
),
);
}),
itemCount: albumInfo.sharedUsers.length,
itemCount: album.sharedUsers.length,
),
)
],
@@ -189,12 +183,12 @@ class AlbumViewerPage extends HookConsumerWidget {
);
}
Widget buildImageGrid(AlbumResponseDto albumInfo) {
Widget buildImageGrid(Album album) {
final appSettingService = ref.watch(appSettingsServiceProvider);
final bool showStorageIndicator =
appSettingService.getSetting(AppSettingsEnum.storageIndicator);
if (albumInfo.assets.isNotEmpty) {
if (album.assets.isNotEmpty) {
return SliverPadding(
padding: const EdgeInsets.only(top: 10.0),
sliver: SliverGrid(
@@ -207,13 +201,12 @@ class AlbumViewerPage extends HookConsumerWidget {
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return AlbumViewerThumbnail(
asset: Asset.remote(albumInfo.assets[index]),
assetList:
albumInfo.assets.map((e) => Asset.remote(e)).toList(),
asset: album.assets[index],
assetList: album.assets,
showStorageIndicator: showStorageIndicator,
);
},
childCount: albumInfo.assetCount,
childCount: album.assetCount,
),
),
);
@@ -221,7 +214,7 @@ class AlbumViewerPage extends HookConsumerWidget {
return const SliverToBoxAdapter();
}
Widget buildControlButton(AlbumResponseDto albumInfo) {
Widget buildControlButton(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
child: SizedBox(
@@ -231,13 +224,13 @@ class AlbumViewerPage extends HookConsumerWidget {
children: [
AlbumActionOutlinedButton(
iconData: Icons.add_photo_alternate_outlined,
onPressed: () => onAddPhotosPressed(albumInfo),
onPressed: () => onAddPhotosPressed(album),
labelText: "share_add_photos".tr(),
),
if (userId == albumInfo.ownerId)
if (userId == album.ownerId)
AlbumActionOutlinedButton(
iconData: Icons.person_add_alt_rounded,
onPressed: () => onAddUsersPressed(albumInfo),
onPressed: () => onAddUsersPressed(album),
labelText: "album_viewer_page_share_add_users".tr(),
),
],
@@ -246,45 +239,60 @@ class AlbumViewerPage extends HookConsumerWidget {
);
}
Widget buildBody(AlbumResponseDto albumInfo) {
return GestureDetector(
onTap: () {
titleFocusNode.unfocus();
},
child: DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).hintColor,
controller: scrollController,
heightScrollThumb: 48.0,
child: CustomScrollView(
Future<bool> onWillPop() async {
final isMultiselectEnable = ref
.read(assetSelectionProvider)
.selectedAssetsInAlbumViewer
.isNotEmpty;
if (isMultiselectEnable) {
ref.watch(assetSelectionProvider.notifier).removeAll();
return false;
}
return true;
}
Widget buildBody(Album album) {
return WillPopScope(
onWillPop: onWillPop,
child: GestureDetector(
onTap: () {
titleFocusNode.unfocus();
},
child: DraggableScrollbar.semicircle(
backgroundColor: Theme.of(context).hintColor,
controller: scrollController,
slivers: [
buildHeader(albumInfo),
SliverPersistentHeader(
pinned: true,
delegate: ImmichSliverPersistentAppBarDelegate(
minHeight: 50,
maxHeight: 50,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: buildControlButton(albumInfo),
heightScrollThumb: 48.0,
child: CustomScrollView(
controller: scrollController,
slivers: [
buildHeader(album),
SliverPersistentHeader(
pinned: true,
delegate: ImmichSliverPersistentAppBarDelegate(
minHeight: 50,
maxHeight: 50,
child: Container(
color: Theme.of(context).scaffoldBackgroundColor,
child: buildControlButton(album),
),
),
),
),
buildImageGrid(albumInfo)
],
buildImageGrid(album)
],
),
),
),
);
}
return Scaffold(
appBar: albumInfo.when(
data: (AlbumResponseDto? data) {
appBar: album.when(
data: (Album? data) {
if (data != null) {
return AlbumViewerAppbar(
albumInfo: data,
album: data,
userId: userId,
albumId: albumId,
);
}
return null;
@@ -292,7 +300,7 @@ class AlbumViewerPage extends HookConsumerWidget {
error: (e, _) => null,
loading: () => null,
),
body: albumInfo.when(
body: album.when(
data: (albumInfo) => albumInfo != null
? buildBody(albumInfo)
: const Center(

View File

@@ -1,4 +1,5 @@
import 'package:auto_route/auto_route.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
@@ -6,6 +7,7 @@ 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';
import 'package:immich_mobile/shared/models/album.dart';
class LibraryPage extends HookConsumerWidget {
const LibraryPage({Key? key}) : super(key: key);
@@ -22,14 +24,11 @@ class LibraryPage extends HookConsumerWidget {
[],
);
Widget buildAppBar() {
return const SliverAppBar(
AppBar buildAppBar() {
return AppBar(
centerTitle: true,
floating: true,
pinned: false,
snap: false,
automaticallyImplyLeading: false,
title: Text(
title: const Text(
'IMMICH',
style: TextStyle(
fontFamily: 'SnowburstOne',
@@ -40,74 +39,236 @@ class LibraryPage extends HookConsumerWidget {
);
}
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),
final selectedAlbumSortOrder = useState(0);
List<Album> sortedAlbums() {
if (selectedAlbumSortOrder.value == 0) {
return albums.sortedBy((album) => album.createdAt).reversed.toList();
}
return albums.sortedBy((album) => album.name);
}
Widget buildSortButton() {
final options = [
"library_page_sort_created".tr(),
"library_page_sort_title".tr()
];
return PopupMenuButton(
position: PopupMenuPosition.over,
itemBuilder: (BuildContext context) {
return options.mapIndexed<PopupMenuEntry<int>>((index, option) {
final selected = selectedAlbumSortOrder.value == index;
return PopupMenuItem(
value: index,
child: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: Icon(
Icons.check,
color: selected
? Theme.of(context).primaryColor
: Colors.transparent,
),
),
Text(
option,
style: TextStyle(
color: selected ? Theme.of(context).primaryColor : null,
fontSize: 12.0,
),
)
],
),
child: Center(
child: Icon(
Icons.add_rounded,
size: 28,
color: Theme.of(context).primaryColor,
),
);
}).toList();
},
onSelected: (int value) {
selectedAlbumSortOrder.value = value;
},
child: Row(
children: [
Icon(
Icons.swap_vert_rounded,
size: 18,
color: Theme.of(context).primaryColor,
),
Text(
options[selectedAlbumSortOrder.value],
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
fontSize: 12.0,
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
child: const Text(
'library_page_new_album',
style: TextStyle(
fontWeight: FontWeight.bold,
),
).tr(),
)
],
),
);
}
return Scaffold(
body: CustomScrollView(
slivers: [
buildAppBar(),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.all(12.0),
child: const Text(
'library_page_albums',
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
Widget buildCreateAlbumButton() {
return GestureDetector(
onTap: () {
AutoRouter.of(context).push(CreateAlbumRoute(isSharedAlbum: false));
},
child: Padding(
padding: const EdgeInsets.only(bottom: 32),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Container(
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,
),
),
),
),
Padding(
padding: const EdgeInsets.only(
top: 8.0,
bottom: 16,
),
child: const Text(
'library_page_new_album',
style: TextStyle(
fontWeight: FontWeight.bold,
),
).tr(),
),
],
),
),
);
}
Widget buildLibraryNavButton(
String label,
IconData icon,
Function() onClick,
) {
return Expanded(
child: OutlinedButton.icon(
onPressed: onClick,
label: Padding(
padding: const EdgeInsets.only(left: 8.0),
child: Text(
label,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12.0,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white
: Colors.black,
),
),
),
SliverPadding(
padding: const EdgeInsets.only(left: 12.0, right: 12, bottom: 50),
sliver: SliverToBoxAdapter(
child: Wrap(
spacing: 12,
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.all(12),
side: BorderSide(
color: Theme.of(context).brightness == Brightness.dark
? Colors.grey[600]!
: Colors.grey[300]!,
),
alignment: Alignment.centerLeft,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0),
),
),
icon: Icon(icon, color: Theme.of(context).primaryColor),
),
);
}
final sorted = sortedAlbums();
return Scaffold(
appBar: buildAppBar(),
body: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
left: 12.0,
right: 12.0,
top: 24.0,
bottom: 12.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
buildCreateAlbumButton(),
for (var album in albums)
AlbumThumbnailCard(
album: album,
),
buildLibraryNavButton(
"library_page_favorites".tr(), Icons.star_border, () {
AutoRouter.of(context).navigate(const FavoritesRoute());
}),
const SizedBox(width: 12.0),
buildLibraryNavButton(
"library_page_sharing".tr(), Icons.group_outlined, () {
AutoRouter.of(context).navigate(const SharingRoute());
}),
],
),
),
)
),
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.only(
top: 12.0,
left: 12.0,
right: 12.0,
bottom: 20.0,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
'library_page_albums',
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
buildSortButton(),
],
),
),
),
SliverPadding(
padding: const EdgeInsets.all(12.0),
sliver: SliverGrid(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 250,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
childAspectRatio: .7,
),
delegate: SliverChildBuilderDelegate(
childCount: sorted.length + 1,
(context, index) {
if (index == 0) {
return buildCreateAlbumButton();
}
return AlbumThumbnailCard(
album: sorted[index - 1],
onTap: () => AutoRouter.of(context).push(
AlbumViewerRoute(
albumId: sorted[index - 1].id,
),
),
);
},
),
),
),
],
),
);

View File

@@ -4,27 +4,28 @@ 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/suggested_shared_users.provider.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart';
class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
final AlbumResponseDto albumInfo;
final Album album;
const SelectAdditionalUserForSharingPage({Key? key, required this.albumInfo})
const SelectAdditionalUserForSharingPage({Key? key, required this.album})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
AsyncValue<List<UserResponseDto>> suggestedShareUsers =
final AsyncValue<List<User>> suggestedShareUsers =
ref.watch(suggestedSharedUsersProvider);
final sharedUsersList = useState<Set<UserResponseDto>>({});
final sharedUsersList = useState<Set<User>>({});
addNewUsersHandler() {
AutoRouter.of(context)
.pop(sharedUsersList.value.map((e) => e.id).toList());
}
buildTileIcon(UserResponseDto user) {
buildTileIcon(User user) {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
@@ -42,7 +43,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
}
}
buildUserList(List<UserResponseDto> users) {
buildUserList(List<User> users) {
List<Widget> usersChip = [];
for (var user in sharedUsersList.value) {
@@ -118,7 +119,6 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
appBar: AppBar(
title: const Text(
'share_invite',
style: TextStyle(color: Colors.black),
).tr(),
elevation: 0,
centerTitle: false,
@@ -141,9 +141,9 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
),
body: suggestedShareUsers.when(
data: (users) {
for (var sharedUsers in albumInfo.sharedUsers) {
for (var sharedUsers in album.sharedUsers) {
users.removeWhere(
(u) => u.id == sharedUsers.id || u.id == albumInfo.ownerId,
(u) => u.id == sharedUsers.id || u.id == album.ownerId,
);
}

View File

@@ -8,16 +8,16 @@ import 'package:immich_mobile/modules/album/providers/asset_selection.provider.d
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/album/providers/suggested_shared_users.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:openapi/api.dart';
class SelectUserForSharingPage extends HookConsumerWidget {
const SelectUserForSharingPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final sharedUsersList = useState<Set<UserResponseDto>>({});
AsyncValue<List<UserResponseDto>> suggestedShareUsers =
final sharedUsersList = useState<Set<User>>({});
AsyncValue<List<User>> suggestedShareUsers =
ref.watch(suggestedSharedUsersProvider);
createSharedAlbum() async {
@@ -25,7 +25,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum(
ref.watch(albumTitleProvider),
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
sharedUsersList.value.map((userInfo) => userInfo.id).toList(),
sharedUsersList.value,
);
if (newAlbum != null) {
@@ -44,7 +44,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
);
}
buildTileIcon(UserResponseDto user) {
buildTileIcon(User user) {
if (sharedUsersList.value.contains(user)) {
return CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
@@ -62,7 +62,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
}
}
buildUserList(List<UserResponseDto> users) {
buildUserList(List<User> users) {
List<Widget> usersChip = [];
for (var user in sharedUsersList.value) {

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