Compare commits

...

40 Commits

Author SHA1 Message Date
Alex Tran
0c258f0506 bump OpenAPI Version 2023-01-18 16:25:21 -06:00
Jason Rasmussen
912d5a3069 fix(server): build (#1354) 2023-01-18 15:48:20 -05:00
Alex Tran
1b6dd9241f Added release note for Android 2023-01-18 11:52:22 -06:00
Alex Tran
ecb4ee2e3e Pump version 2023-01-18 10:15:25 -06:00
Matthias Rupp
7a1ae8691e feat(mobile): Various minor performance improvements (#1176)
* Improve scroll performance by introducing repaint boundaries and moving more calculations to providers.

* Add error handing for malformed dates.

* Remove unused method

* Use compute in different places to improve app performance during heavy tasks

* Fix test

* Refactor `List<RenderAssetGridElement>` to separate `RenderList` class and make `fromAssetGroups` a static method of this class.

* Fix loading indicator bug

* Use provider directly

* `RenderList` refactoring

* `AssetNotifier` refactoring

* Move `combine` to static private method

* Extract compute methods in cache services to static private methods.

* Use `tryParse` instead of `parse` with try/catch for dates.

* Fix bug in caching mechanism.

* Fixed state not being used to trigger conditional rendering

* styling

* Corrected state

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-01-18 09:59:23 -06:00
Jason Rasmussen
92972ac776 refactor(server): api keys (#1339)
* refactor: api keys

* refactor: test module

* chore: tests

* chore: fix provider

* refactor: test mock repos
2023-01-18 08:40:15 -06:00
bo0tzz
0c469cc712 feat(ci): Clean up the actions cache on PR close (#1350)
* feat(ci): Clean up the actions cache on PR close

The cache entries that are generated on a workflow run for a PR cannot be used by any other contexts [1]. As such, they are useless and just wasting valuable cache space. This commit adds a workflow (copied from [2]) that deletes the cache entries when a PR is closed.

[1] https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache
[2] https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#force-deleting-cache-entries

* feat(ci): List as many cache entries as possible
2023-01-18 08:28:28 -06:00
Alex
3e4a14b299 chore(server) harden EXIF extraction (#1347)
* chore(server) Harden EXIF extraction

* Remove unused function in timeutil

* Remove deadcode
2023-01-17 13:41:00 -06:00
Skyler Mäntysaari
dff10e89fe feat(server): Fix exif data parsing (#1326)
* Trying to get exifdata working with different lib.

* Got the new library working.

* Addressing PR comments.

* Removed not used vars and proper place for the eslint disable.

* Fix time-utils to use the exiftool-vendored lib.

Fixed also one test, as that would be valid.

* Using filename for timestamp as well if possible.

* Add new tests for time-utils.

* Remember to gracefully terminate the exiftool instance when not needed.

* eslint ignore...

* Apperantly Dockerfile changes were not pushed.

* feat(dockerfile): Tweak the Server Dockerfile

* feat(server): getTimestampFromFilename should return string or undefined.

* feat(server): If we don't have exifData or timestamp from filename, raise an error.

* Apparently test was already right, but my local system disagrees.

* More utilities for parsing and fix the timestampFromFilename.

It was returning an incorrect date as the regex doesn't seem to be the best for this as files named `IMG_0115.HEIC` will want to get parsed incorrectly due to it.

* feat(server/docker): Install perl as it seems to be required.

* feat(server): remember to include exposureTime and focalLength in new exif data.

* feat(server): Remove the parsing from filename as requested.

* feat(server): Import exiftool differently in time-utils.

* feat(server): Error handling when there is no exifData.

* feat(server): Fixes for the error handling when there is no exifData.

* feat(server): Remember to include modifyDate despite no exif.

* feat(server): Remember to include model of Camera.

* feat(server): Fixing up Exiftool usage.

Including proper logging for it, which had to be done in wrapped fashion due to it expecting all the logging levels which NextJS logger doesn't implement.

* feat(server): Do not use a wrapper for ExifTool logging.

* fix merge conflicts in metadata-extractor
2023-01-17 09:29:49 -06:00
Jason Rasmussen
693adf8488 refactor: job names (#1343)
* refactor: job names

* refactor: remove jobId
2023-01-17 08:43:45 -06:00
Jason Rasmussen
adacfb1110 feat(cli): list users (#1341) 2023-01-16 18:31:46 -06:00
Jason Rasmussen
177cc3d7f9 chore(docs): watchtower warning (#1342) 2023-01-16 18:30:50 -06:00
Alex
0c582df962 feat(server) Add filetype variable to storage template (#1337)
* feat(server) Add filetype variable to storage template

* Remove console.log

* Added additional variable for full file type
2023-01-16 15:54:52 -06:00
Alex
1e1fd97b38 fix(web) fix cannot add uploaded asset to a shared album (#1338) 2023-01-16 14:37:18 -06:00
Jason Rasmussen
1e2f02613f refactor: reset admin password (#1335)
* refactor: reset-admin-password

* chore: docs
2023-01-16 12:09:04 -06:00
dependabot[bot]
5a6a726014 chore(deps): bump docker/build-push-action from 3.2.0 to 3.3.0 (#1332)
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 3.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](https://github.com/docker/build-push-action/compare/v3.2.0...v3.3.0)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 11:56:14 -06:00
Alex
eace0af7a5 fix(web) Disable draggable on common usage element to avoid trigger drag-n-drop layer (#1330) 2023-01-15 14:01:10 -06:00
Jaime Baez
036d0556a4 Fix e2e tests (#1321)
* Fix e2e tests

* Enable e2e tests in CI

* Remove unnecessary TypeOrmModule from e2e tests
2023-01-15 13:08:24 -06:00
Alex
e9fda40b2b feat(web) Individual assets shared mechanism (#1317)
* Create shared link modal for individual asset

* Added API to create asset shared link

* Added viewer for individual shared link

* Added multiselection app bar

* Refactor gallery viewer to its own component

* Refactor

* Refactor

* Add and remove asset from shared link

* Fixed test

* Fixed notification card doesn't wrap

* Add check asset access when created asset shared link

* pr feedback
2023-01-14 23:49:47 -06:00
bo0tzz
b9b2b559a1 fix(database): Set connection timeout (#1324) 2023-01-14 09:06:59 -06:00
Jason Rasmussen
5fb3ea465f fix(web): login error handling (#1322) 2023-01-13 16:04:59 -06:00
Jason Rasmussen
ba04b753de refactor: logging (#1318) 2023-01-13 08:23:12 -06:00
Jason Rasmussen
92ca447f33 refactor(server): use UserService (#1309)
* refactor: communication gateway

* refactor: share strategy

* refactor: communication module
2023-01-12 20:15:45 -06:00
Jason Rasmussen
755a1331da chore(web,server): run code coverage reports (#1313)
* chore(web,server): run code coverage reports

* chore(tests): fail test check if coverage drops

* chore: disable e2e until they are fixed

* chore(web): coverage threshold
2023-01-12 16:07:57 -06:00
Alex
6db541c89b chore(server) Update NestJs to V9 (#1312)
* chore(server) update nestjs to v9

* remove deadcode

* downgrade local-reverse-geocoder

* Added ignore script

* remove ignore script

* Fixed local-reverse-geocoder to a working version

* Fixed issue with eslint mismatch typescript version

* chore: remove unused package

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-01-12 16:07:27 -06:00
Jason Rasmussen
67c52c3764 chore(docs): contributing (#1311)
* chore(server): linting

* chore: contributing pr checklist
2023-01-12 08:44:11 -06:00
Jason Rasmussen
131caa20eb refactor(server): domain/infra (#1298)
* refactor: user repository

* refactor: user module

* refactor: move database into infra

* refactor(cli): use user core

* chore: import path

* chore: tests
2023-01-11 21:34:36 -05:00
Matthias Rupp
89a6ed2a5b feat(mobile): Rework of the exif sheet (#1213)
* Draggable sheet in asset viewer page

* Minor improvements

* Fix display bug

* Fix some styling

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-01-11 14:54:12 -06:00
Jason Rasmussen
b597cd891b docs: community all in one (#1301) 2023-01-11 09:49:39 -06:00
Jason Rasmussen
fa31a6e441 feat(web): meta tags for share links (#1290)
* feat(web): meta tags for share links

* refactor: svelte head tags

* chore: clean up

* chore: linting
2023-01-10 21:36:50 -06:00
Jason Rasmussen
a3688fe642 chore(web): modal max-width to 95 viewport width (#1297) 2023-01-10 21:06:27 -06:00
Alex Tran
96e786d480 Pump 2023-01-10 16:04:14 -06:00
Alex Tran
3c09482a93 Pump 2023-01-10 16:04:06 -06:00
Alex
a648da021f fix(server) fix order of asset in shared album to be similar to the actual album (#1293) 2023-01-10 15:57:03 -06:00
Alex Tran
d1d69bfaf4 chore(doc) update api key usage for CLI tool 2023-01-10 13:06:36 -06:00
Alex Tran
221e03488e Fixed scrolling overflow 2023-01-10 11:26:37 -06:00
Alex
2ffb7cab2e fix(web) add disable property to generated shared link (#1287) 2023-01-10 10:34:16 -06:00
otbutz
acffabf9de fix(nginx): fix entrypoint (#1284) 2023-01-10 10:10:15 -06:00
otbutz
0a464f9d28 fix(nginx): Switch to sh (#1282)
* Switch to sh

* Fix entrypoint
2023-01-10 10:04:35 -06:00
Alex
7add754fc3 fix(web) show exif info in public shared (#1283)
* fix(web) show exif in public share page

* Added exif info to return payload'
2023-01-10 10:03:15 -06:00
400 changed files with 7557 additions and 4369 deletions

View File

@@ -33,7 +33,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Immich Mono Repo
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./server
file: ./server/Dockerfile
@@ -70,7 +70,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
@@ -106,7 +106,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Web
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./web
file: ./web/Dockerfile
@@ -141,7 +141,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./nginx
file: ./nginx/Dockerfile

View File

@@ -35,7 +35,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Immich Mono Repo
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./server
file: ./server/Dockerfile
@@ -76,7 +76,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
@@ -116,7 +116,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Web
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./web
file: ./web/Dockerfile
@@ -155,7 +155,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Proxy
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./nginx
file: ./nginx/Dockerfile

View File

@@ -42,7 +42,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push immich-server release
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./server
file: ./server/Dockerfile
@@ -85,7 +85,7 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and Push Machine Learning
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./machine-learning
file: ./machine-learning/Dockerfile
@@ -135,7 +135,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push immich-web release
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./web
file: ./web/Dockerfile
@@ -184,7 +184,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push immich-proxy release
uses: docker/build-push-action@v3.2.0
uses: docker/build-push-action@v3.3.0
with:
context: ./nginx
file: ./nginx/Dockerfile

33
.github/workflows/cache-cleanup.yml vendored Normal file
View File

@@ -0,0 +1,33 @@
name: Clean up actions cache on PR close
on:
pull_request:
types:
- closed
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v3
- name: Cleanup
run: |
gh extension install actions/gh-actions-cache
REPO=${{ github.repository }}
BRANCH=${{ github.ref }}
echo "Fetching list of cache keys"
cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH -L 100 | cut -f 1 )
## Setting this to not fail the workflow while deleting cache keys.
set +e
echo "Deleting caches..."
for cacheKey in $cacheKeysForPR
do
gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm
done
echo "Done"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@
.idea
docker/upload
coverage

View File

@@ -0,0 +1,43 @@
---
sidebar_position: 3
---
# Contributing
Contributions are welcome!
## PR Checklist
When contributing code through a pull request, please check the following:
### Web Checks
- [ ] `npm run lint` (linting via ESLint)
- [ ] `npm run format` (formatting via Prettier)
- [ ] `npm run check` (Type checking via SvelteKit)
- [ ] `npm test` (Tests via Jest)
:::tip
Run all web checks with `npm run check:all`
:::
### Server Checks
- [ ] `npm run lint` (linting via ESLint)
- [ ] `npm run format` (formatting via Prettier)
- [ ] `npm run check` (Type checking via `tsc`)
- [ ] `npm test` (Tests via Jest)
:::tip
Run all server checks with `npm run check:all`
:::
### Open API
The Open API client libraries need to be regenerated whenever there are changes to the `immich-openapi-specs.json` file.
- [ ] `npm run api:generate`
:::tip
This can also be run via `make api` from the project root directory (not in the `server` folder)
:::

View File

@@ -20,7 +20,7 @@ npm i -g immich
Specify user's credentials, Immich's server address and port, and the directory you would like to upload videos/photos from.
```bash
immich upload --email testuser@email.com --password password --server http://192.168.1.216:2283/api -d your/target/directory
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api -d your/target/directory
```
---
@@ -31,26 +31,32 @@ immich upload --email testuser@email.com --password password --server http://192
| ---------------- | ------------------------------------------------------------------- |
| --yes / -y | Assume yes on all interactive prompts |
| --delete / -da | Delete local assets after upload |
| --email / -e | User's email |
| --password / -pw | User's password |
| --key / -k | User's API key |
| --server / -s | Immich's server address |
| --directory / -d | Directory to upload from |
| --threads / -t | Number of threads to use (Default 5) |
| --album/ -al | Create albums for assets based on the parent folder or a given name |
### Obtain the API Key
The API key can be obtained in the user setting panel on the web interface.
![Obtain Api Key](./img/obtain-api-key.png)
### Run via Docker
Be aware that as this runs inside a container it mounts your current directory as a volume, and for the -d flag you need to use the path inside the container.
```bash
docker run -it --rm -v $(pwd):/import ghcr.io/immich-app/immich-cli:latest upload --email testuser@email.com --password password --server http://192.168.1.216:2283/api -d /import
docker run -it --rm -v $(pwd):/import ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api -d /import
```
Optionally, you can create an alias:
```bash
alias immich="docker run -it --rm -v $(pwd):/import ghcr.io/immich-app/immich-cli:latest"
immich upload --email testuser@email.com --password password --server http://192.168.1.216:2283/api -d /import
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api -d /import
```
### Run from source
@@ -68,5 +74,5 @@ npm run build
```
```bash title="Run the command"
node bin/index.js upload --email testuser@email.com --password password --server http://192.168.1.216:2283/api -d your/target/directory
node bin/index.js upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api -d your/target/directory
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -8,32 +8,26 @@ The `immich-server` docker image comes preinstalled with an administrative CLI (
| `reset-admin-password` | Reset the password for the admin user |
| `disable-password-login` | Disable password login |
| `enable-password-login` | Enable password login |
| `list-users` | List Immich users |
## How to run a command
To run a command, connect to the container and then execute it by running `immich <command>`.
To run a command, [connect](/docs/guides/docker-help.md#attach-to-a-container) to the `immich_server` container and then execute the command via `immich <command>`.
## Examples
```bash title="Reset Admin Password"
docker exec -it immich_server sh
Reset Admin Password
/usr/src/app$ immich reset-admin-password
? Please choose a new password (optional) immich-is-awesome-unlike-this-password
New password:
immich-is-awesome-unlike-this-password
```
![Reset Admin Password](./img/reset-admin-password.png)
```bash title="Disable Password Login"
docker exec -it immich_server sh
Disable Password Login
/usr/src/app$ immich disable-password-login
Password login has been disabled.
```
![Disable Password Login](./img/disable-password-login.png)
```bash title="Enable Password Login"
docker exec -it immich_server sh
Enabled Password Login
/usr/src/app$ immich enable-password-login
Password login has been enabled.
```
![Enable Password Login](./img/enable-password-login.png)
List Users
![List Users](./img/list-users.png)

View File

@@ -4,11 +4,27 @@ sidebar_position: 1
# Docker Help
## Logs
## Containers
```bash title="Log Examples"
```bash
docker ps # see a list of running containers
docker ps -a # see a list of running and stopped containers
```
## Attach to a Container
```bash
docker exec -it <id or name> <command> # attach to a container with a command
docker exec -it immich_server sh
docker exec -it immich_microservices sh
docker exec -it immich_machine_learning sh
docker exec -it immich_web sh
docker exec -it immich_proxy sh
```
## Logs
```bash
docker logs <id or name> # see the logs for a specific container (by id or name)
docker logs immich_server

View File

@@ -0,0 +1,21 @@
---
sidebar_position: 99
---
# All-In-One [Community]
:::note
This is a community contribution and not officially supported by the Immich team, but included here for convenience.
**Please report issues to the corresponding [Github Repository][github].**
:::
## Installation
For installation instructions, refer to the [Github Repository][github].
## Issues
For issues, open an issue on the associated [GitHub Repository][github].
[github]: https://github.com/martabal/docker-immich

View File

@@ -126,5 +126,10 @@ When a new version of Immich is [released](https://github.com/immich-app/immich/
docker-compose pull && docker-compose up -d # Or `docker compose`
```
:::caution Automatic Updates
Immich is currently under heavy development, which means you can expect breaking changes and bugs. Therefore, we recommend reading the release notes prior to updating and to take special care when using automated tools like [Watchtower][watchtower].
:::
[compose-file]: https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml
[env-file]: https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example
[watchtower]: https://containrrr.dev/watchtower/

View File

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

View File

@@ -0,0 +1,2 @@
* Various performance improvements
* UI improvement on metadata sheet

BIN
mobile/flutter_01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

View File

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

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.39.0</string>
<string>1.42.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>76</string>
<string>79</string>
<key>LSRequiresIPhoneOS</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>

View File

@@ -19,7 +19,7 @@ platform :ios do
desc "iOS Beta"
lane :beta do
increment_version_number(
version_number: "1.41.0"
version_number: "1.42.0"
)
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.000212">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000301">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="1.00785">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.73906">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.724004">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.857767">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.670744">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.648708">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="85.266784">
<testcase classname="fastlane.lanes" name="4: build_app" time="88.88212">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="59.733923">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="162.957763">
</testcase>

View File

@@ -57,6 +57,7 @@ void main() async {
if (kReleaseMode && Platform.isAndroid) {
try {
await FlutterDisplayMode.setHighRefreshRate();
debugPrint("Enabled high refresh mode");
} catch (e) {
debugPrint("Error setting high refresh rate: $e");
}

View File

@@ -3,12 +3,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
import 'package:openapi/api.dart';
import 'package:path/path.dart' as p;
import 'package:latlong2/latlong.dart';
import 'package:immich_mobile/utils/bytes_units.dart';
class ExifBottomSheet extends ConsumerWidget {
class ExifBottomSheet extends HookConsumerWidget {
final Asset assetDetail;
const ExifBottomSheet({Key? key, required this.assetDetail})
@@ -65,6 +66,8 @@ class ExifBottomSheet extends ConsumerWidget {
);
}
final textColor = Theme.of(context).primaryColor;
ExifResponseDto? exifInfo = assetDetail.remote?.exifInfo;
buildLocationText() {
@@ -72,120 +75,131 @@ class ExifBottomSheet extends ConsumerWidget {
"${exifInfo?.city}, ${exifInfo?.state}",
style: TextStyle(
fontSize: 12,
color: Colors.grey[200],
fontWeight: FontWeight.bold,
color: textColor,
),
);
}
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 8),
child: ListView(
children: [
if (exifInfo?.dateTimeOriginal != null)
Text(
DateFormat('date_format'.tr()).format(
exifInfo!.dateTimeOriginal!.toLocal(),
),
style: TextStyle(
color: Colors.grey[400],
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: Text(
"exif_bottom_sheet_description",
style: TextStyle(
color: Colors.grey[500],
fontSize: 11,
),
).tr(),
return SingleChildScrollView(
child: Card(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(15),
topRight: Radius.circular(15),
),
),
margin: const EdgeInsets.all(0),
child: Container(
margin: const EdgeInsets.symmetric(horizontal: 8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
const Align(
alignment: Alignment.center,
child: CustomDraggingHandle(),
),
const SizedBox(height: 12),
if (exifInfo?.dateTimeOriginal != null)
Text(
DateFormat('date_format'.tr()).format(
exifInfo!.dateTimeOriginal!.toLocal(),
),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
// Location
if (assetDetail.latitude != null)
Padding(
padding: const EdgeInsets.only(top: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(
thickness: 1,
color: Colors.grey[600],
),
Text(
"exif_bottom_sheet_location",
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
).tr(),
if (assetDetail.latitude != null &&
assetDetail.longitude != null)
buildMap(),
if (exifInfo != null &&
exifInfo.city != null &&
exifInfo.state != null)
buildLocationText(),
Text(
"${assetDetail.latitude?.toStringAsFixed(4)}, ${assetDetail.longitude?.toStringAsFixed(4)}",
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
)
],
),
),
// Detail
if (exifInfo != null)
Padding(
padding: const EdgeInsets.only(top: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(
thickness: 1,
color: Colors.grey[600],
),
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"exif_bottom_sheet_details",
style: TextStyle(fontSize: 11, color: Colors.grey[400]),
).tr(),
),
ListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
textColor: Colors.grey[300],
iconColor: Colors.grey[300],
leading: const Icon(Icons.image),
title: Text(
"${exifInfo.imageName!}${p.extension(assetDetail.remote!.originalPath)}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
subtitle: exifInfo.exifImageHeight != null
? Text(
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${formatBytes(exifInfo.fileSizeInByte!)} ",
)
: null,
),
if (exifInfo.make != null)
ListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
textColor: Colors.grey[300],
iconColor: Colors.grey[300],
leading: const Icon(Icons.camera),
title: Text(
"${exifInfo.make} ${exifInfo.model}",
style: const TextStyle(fontWeight: FontWeight.bold),
// Location
if (assetDetail.latitude != null)
Padding(
padding: const EdgeInsets.only(top: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider(
thickness: 1,
),
subtitle: Text(
"ƒ/${exifInfo.fNumber} 1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
Text(
"exif_bottom_sheet_location",
style: TextStyle(fontSize: 11, color: textColor),
).tr(),
if (assetDetail.latitude != null &&
assetDetail.longitude != null)
buildMap(),
if (exifInfo != null &&
exifInfo.city != null &&
exifInfo.state != null)
buildLocationText(),
Text(
"${assetDetail.latitude?.toStringAsFixed(4)}, ${assetDetail.longitude?.toStringAsFixed(4)}",
style: const TextStyle(fontSize: 12),
)
],
),
),
// Detail
if (exifInfo != null)
Padding(
padding: const EdgeInsets.only(top: 32.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Divider(
thickness: 1,
color: Colors.grey[600],
),
),
],
Padding(
padding: const EdgeInsets.only(bottom: 8.0),
child: Text(
"exif_bottom_sheet_details",
style: TextStyle(fontSize: 11, color: textColor),
).tr(),
),
ListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
leading: const Icon(Icons.image),
title: Text(
"${exifInfo.imageName!}${p.extension(assetDetail.remote!.originalPath)}",
style: TextStyle(
fontWeight: FontWeight.bold,
color: textColor,
),
),
subtitle: exifInfo.exifImageHeight != null
? Text(
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${formatBytes(exifInfo.fileSizeInByte ?? 0)} ",
)
: null,
),
if (exifInfo.make != null)
ListTile(
contentPadding: const EdgeInsets.all(0),
dense: true,
leading: const Icon(Icons.camera),
title: Text(
"${exifInfo.make} ${exifInfo.model}",
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
"ƒ/${exifInfo.fNumber} 1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
),
),
],
),
),
const SizedBox(
height: 50,
),
),
],
],
),
),
),
);
}

View File

@@ -69,9 +69,12 @@ class GalleryViewerPage extends HookConsumerWidget {
void showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0),
),
barrierColor: Colors.transparent,
isScrollControlled: false,
backgroundColor: Colors.transparent,
isScrollControlled: true,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail!);
@@ -162,6 +165,7 @@ class GalleryViewerPage extends HookConsumerWidget {
heroTag: assetList[index].id,
loadPreview: isLoadPreview.value,
loadOriginal: isLoadOriginal.value,
showExifSheet: showInfo,
);
}
} else {

View File

@@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/asset_viewer/models/image_viewer_page_state.model.dart';
import 'package:immich_mobile/modules/asset_viewer/providers/image_viewer_page_state.provider.dart';
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart';
import 'package:immich_mobile/modules/home/services/asset.service.dart';
import 'package:immich_mobile/shared/models/asset.dart';
@@ -17,6 +16,7 @@ class ImageViewerPage extends HookConsumerWidget {
final String authToken;
final ValueNotifier<bool> isZoomedListener;
final void Function() isZoomedFunction;
final void Function()? showExifSheet;
final bool loadPreview;
final bool loadOriginal;
@@ -29,6 +29,7 @@ class ImageViewerPage extends HookConsumerWidget {
required this.isZoomedListener,
required this.loadPreview,
required this.loadOriginal,
this.showExifSheet,
}) : super(key: key);
Asset? assetDetail;
@@ -56,18 +57,6 @@ class ImageViewerPage extends HookConsumerWidget {
[],
);
showInfo() {
showModalBottomSheet(
backgroundColor: Colors.black,
barrierColor: Colors.transparent,
isScrollControlled: false,
context: context,
builder: (context) {
return ExifBottomSheet(assetDetail: assetDetail ?? asset);
},
);
}
return Stack(
children: [
Center(
@@ -81,7 +70,7 @@ class ImageViewerPage extends HookConsumerWidget {
isZoomedFunction: isZoomedFunction,
isZoomedListener: isZoomedListener,
onSwipeDown: () => AutoRouter.of(context).pop(),
onSwipeUp: asset.isRemote ? showInfo : () {},
onSwipeUp: (asset.isRemote && showExifSheet != null) ? showExifSheet! : () {},
),
),
),

View File

@@ -1,14 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.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/shared/providers/asset.provider.dart';
final renderListProvider = StateProvider((ref) {
var assetGroups = ref.watch(assetGroupByDateTimeProvider);
var settings = ref.watch(appSettingsServiceProvider);
final assetsPerRow = settings.getSetting(AppSettingsEnum.tilesPerRow);
return assetGroupsToRenderList(assetGroups, assetsPerRow);
});

View File

@@ -7,9 +7,18 @@ import 'package:immich_mobile/shared/services/json_cache.dart';
class AssetCacheService extends JsonCache<List<Asset>> {
AssetCacheService() : super("asset_cache");
static Future<List<Map<String, dynamic>>> _computeSerialize(
List<Asset> assets) async {
return assets.map((e) => e.toJson()).toList();
}
@override
void put(List<Asset> data) {
putRawData(data.map((e) => e.toJson()).toList());
void put(List<Asset> data) async {
putRawData(await compute(_computeSerialize, data));
}
static Future<List<Asset>> _computeEncode(List<dynamic> data) async {
return data.map((e) => Asset.fromJson(e)).whereNotNull().toList();
}
@override
@@ -17,8 +26,7 @@ class AssetCacheService extends JsonCache<List<Asset>> {
try {
final mapList = await readRawData() as List<dynamic>;
final responseData =
mapList.map((e) => Asset.fromJson(e)).whereNotNull().toList();
final responseData = await compute(_computeEncode, mapList);
return responseData;
} catch (e) {

View File

@@ -1,5 +1,7 @@
import 'dart:math';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:logging/logging.dart';
@@ -33,85 +35,122 @@ class RenderAssetGridElement {
});
}
List<RenderAssetGridElement> assetsToRenderList(
List<Asset> assets,
int assetsPerRow,
) {
List<RenderAssetGridElement> elements = [];
class _AssetGroupsToRenderListComputeParameters {
final String monthFormat;
final String dayFormat;
final String dayFormatYear;
final Map<String, List<Asset>> groups;
final int perRow;
int cursor = 0;
while (cursor < assets.length) {
int rowElements = min(assets.length - cursor, assetsPerRow);
final date = assets[cursor].createdAt;
final rowElement = RenderAssetGridElement(
RenderAssetGridElementType.assetRow,
date: date,
assetRow: RenderAssetGridRow(
assets.sublist(cursor, cursor + rowElements),
),
);
elements.add(rowElement);
cursor += rowElements;
}
return elements;
_AssetGroupsToRenderListComputeParameters(this.monthFormat, this.dayFormat,
this.dayFormatYear, this.groups, this.perRow);
}
List<RenderAssetGridElement> assetGroupsToRenderList(
Map<String, List<Asset>> assetGroups,
int assetsPerRow,
) {
List<RenderAssetGridElement> elements = [];
DateTime? lastDate;
class RenderList {
final List<RenderAssetGridElement> elements;
assetGroups.forEach((groupName, assets) {
try {
final date = DateTime.parse(groupName);
RenderList(this.elements);
static Future<RenderList> _processAssetGroupData(
_AssetGroupsToRenderListComputeParameters data) async {
final monthFormat = DateFormat(data.monthFormat);
final dayFormatSameYear = DateFormat(data.dayFormat);
final dayFormatOtherYear = DateFormat(data.dayFormatYear);
final groups = data.groups;
final perRow = data.perRow;
List<RenderAssetGridElement> elements = [];
DateTime? lastDate;
groups.forEach((groupName, assets) {
try {
final date = DateTime.parse(groupName);
if (lastDate == null || lastDate!.month != date.month) {
// Month title
var monthTitleText = groupName;
var groupDate = DateTime.tryParse(groupName);
if (groupDate != null) {
monthTitleText = monthFormat.format(groupDate);
} else {
log.severe("Failed to format date for day title: $groupName");
}
elements.add(
RenderAssetGridElement(
RenderAssetGridElementType.monthTitle,
title: monthTitleText,
date: date,
),
);
}
// Add group title
var currentYear = DateTime.now().year;
var groupYear = DateTime.parse(groupName).year;
var formatDate =
currentYear == groupYear ? dayFormatSameYear : dayFormatOtherYear;
var dateText = groupName;
var groupDate = DateTime.tryParse(groupName);
if (groupDate != null) {
dateText = formatDate.format(groupDate);
} else {
log.severe("Failed to format date for day title: $groupName");
}
if (lastDate == null || lastDate!.month != date.month) {
elements.add(
RenderAssetGridElement(
RenderAssetGridElementType.monthTitle,
title: groupName,
RenderAssetGridElementType.dayTitle,
title: dateText,
date: date,
),
);
}
// Add group title
elements.add(
RenderAssetGridElement(
RenderAssetGridElementType.dayTitle,
title: groupName,
date: date,
relatedAssetList: assets,
),
);
// Add rows
int cursor = 0;
while (cursor < assets.length) {
int rowElements = min(assets.length - cursor, assetsPerRow);
final rowElement = RenderAssetGridElement(
RenderAssetGridElementType.assetRow,
date: date,
assetRow: RenderAssetGridRow(
assets.sublist(cursor, cursor + rowElements),
relatedAssetList: assets,
),
);
elements.add(rowElement);
cursor += rowElements;
// Add rows
int cursor = 0;
while (cursor < assets.length) {
int rowElements = min(assets.length - cursor, perRow);
final rowElement = RenderAssetGridElement(
RenderAssetGridElementType.assetRow,
date: date,
assetRow: RenderAssetGridRow(
assets.sublist(cursor, cursor + rowElements),
),
);
elements.add(rowElement);
cursor += rowElements;
}
lastDate = date;
} catch (e, stackTrace) {
log.severe(e, stackTrace);
}
});
lastDate = date;
} catch (e, stackTrace) {
log.severe(e, stackTrace);
}
});
return RenderList(elements);
}
return elements;
static Future<RenderList> fromAssetGroups(
Map<String, List<Asset>> assetGroups,
int assetsPerRow,
) async {
// Compute only allows for one parameter. Therefore we pass all parameters in a map
return compute(
_processAssetGroupData,
_AssetGroupsToRenderListComputeParameters(
"monthly_title_text_date_format".tr(),
"daily_title_text_date".tr(),
"daily_title_text_date_year".tr(),
assetGroups,
assetsPerRow,
),
);
}
}

View File

@@ -5,14 +5,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
class DailyTitleText extends ConsumerWidget {
const DailyTitleText({
Key? key,
required this.isoDate,
required this.text,
required this.multiselectEnabled,
required this.onSelect,
required this.onDeselect,
required this.selected,
}) : super(key: key);
final String isoDate;
final String text;
final bool multiselectEnabled;
final Function onSelect;
final Function onDeselect;
@@ -20,13 +20,7 @@ class DailyTitleText extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
var currentYear = DateTime.now().year;
var groupYear = DateTime.parse(isoDate).year;
var formatDateTemplate = currentYear == groupYear
? "daily_title_text_date".tr()
: "daily_title_text_date_year".tr();
var dateText =
DateFormat(formatDateTemplate).format(DateTime.parse(isoDate));
void handleTitleIconClick() {
if (selected) {
@@ -46,7 +40,7 @@ class DailyTitleText extends ConsumerWidget {
child: Row(
children: [
Text(
dateText,
text,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,

View File

@@ -24,22 +24,10 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
bool _scrolling = false;
final Set<String> _selectedAssets = HashSet();
List<Asset> get _assets {
return widget.renderList
.map((e) {
if (e.type == RenderAssetGridElementType.assetRow) {
return e.assetRow!.assets;
} else {
return List<Asset>.empty();
}
})
.flattened
.toList();
}
Set<Asset> _getSelectedAssets() {
return _selectedAssets
.map((e) => _assets.firstWhereOrNull((a) => a.id == e))
.map((e) => widget.allAssets.firstWhereOrNull((a) => a.id == e))
.whereNotNull()
.toSet();
}
@@ -95,9 +83,9 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
}
return ThumbnailImage(
asset: asset,
assetList: _assets,
assetList: widget.allAssets,
multiselectEnabled: widget.selectionActive,
isSelected: _selectedAssets.contains(asset.id),
isSelected: widget.selectionActive && _selectedAssets.contains(asset.id),
onSelect: () => _selectAssets([asset]),
onDeselect: () => _deselectAssets([asset]),
useGrayBoxPlaceholder: true,
@@ -137,7 +125,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
List<Asset> assets,
) {
return DailyTitleText(
isoDate: title,
text: title,
multiselectEnabled: widget.selectionActive,
onSelect: () => _selectAssets(assets),
onDeselect: () => _deselectAssets(assets),
@@ -146,14 +134,11 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
}
Widget _buildMonthTitle(BuildContext context, String title) {
var monthTitleText = DateFormat("monthly_title_text_date_format".tr())
.format(DateTime.parse(title));
return Padding(
key: Key("month-$title"),
padding: const EdgeInsets.only(left: 12.0, top: 32),
child: Text(
monthTitleText,
title,
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
@@ -164,7 +149,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
}
Widget _itemBuilder(BuildContext c, int position) {
final item = widget.renderList[position];
final item = widget.renderList.elements[position];
if (item.type == RenderAssetGridElementType.dayTitle) {
return _buildTitle(c, item.title!, item.relatedAssetList!);
@@ -178,7 +163,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
}
Text _labelBuilder(int pos) {
final date = widget.renderList[pos].date;
final date = widget.renderList.elements[pos].date;
return Text(
DateFormat.yMMMd().format(date),
style: const TextStyle(
@@ -196,7 +181,7 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
}
Widget _buildAssetGrid() {
final useDragScrolling = _assets.length >= 20;
final useDragScrolling = widget.allAssets.length >= 20;
void dragScrolling(bool active) {
setState(() {
@@ -208,7 +193,8 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
itemBuilder: _itemBuilder,
itemPositionsListener: _itemPositionsListener,
itemScrollController: _itemScrollController,
itemCount: widget.renderList.length,
itemCount: widget.renderList.elements.length,
addRepaintBoundaries: true,
);
if (!useDragScrolling) {
@@ -250,16 +236,18 @@ class ImmichAssetGridState extends State<ImmichAssetGrid> {
}
class ImmichAssetGrid extends StatefulWidget {
final List<RenderAssetGridElement> renderList;
final RenderList renderList;
final int assetsPerRow;
final double margin;
final bool showStorageIndicator;
final ImmichAssetGridSelectionListener? listener;
final bool selectionActive;
final List<Asset> allAssets;
const ImmichAssetGrid({
super.key,
required this.renderList,
required this.allAssets,
required this.assetsPerRow,
required this.showStorageIndicator,
this.listener,

View File

@@ -5,6 +5,7 @@ import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/home/ui/delete_diaglog.dart';
import 'package:immich_mobile/shared/ui/drag_sheet.dart';
import 'package:immich_mobile/utils/image_url_builder.dart';
import 'package:openapi/api.dart';
@@ -200,53 +201,3 @@ class AddToAlbumTitleRow extends StatelessWidget {
);
}
}
class CustomDraggingHandle extends StatelessWidget {
const CustomDraggingHandle({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 5,
width: 30,
decoration: BoxDecoration(
color: Colors.grey[500],
borderRadius: BorderRadius.circular(16),
),
);
}
}
class ControlBoxButton extends StatelessWidget {
const ControlBoxButton({
Key? key,
required this.label,
required this.iconData,
required this.onPressed,
}) : super(key: key);
final String label;
final IconData iconData;
final Function onPressed;
@override
Widget build(BuildContext context) {
return MaterialButton(
padding: const EdgeInsets.all(10),
shape: const CircleBorder(),
onPressed: () => onPressed(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(iconData, size: 24),
const SizedBox(height: 6),
Text(
label,
style: const TextStyle(fontSize: 12.0),
),
],
),
);
}
}

View File

@@ -8,7 +8,6 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/album.provider.dart';
import 'package:immich_mobile/modules/album/services/album.service.dart';
import 'package:immich_mobile/modules/home/providers/home_page_render_list_provider.dart';
import 'package:immich_mobile/modules/home/providers/multiselect.provider.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/immich_asset_grid.dart';
import 'package:immich_mobile/modules/home/ui/control_bottom_app_bar.dart';
@@ -32,7 +31,6 @@ class HomePage extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final appSettingService = ref.watch(appSettingsServiceProvider);
var renderList = ref.watch(renderListProvider);
final multiselectEnabled = ref.watch(multiselectProvider.notifier);
final selectionEnabledHook = useState(false);
@@ -212,10 +210,12 @@ class HomePage extends HookConsumerWidget {
top: selectionEnabledHook.value ? 0 : 60,
bottom: 0.0,
),
child: ref.watch(assetProvider).isEmpty
child: ref.watch(assetProvider).renderList == null ||
ref.watch(assetProvider).allAssets.isEmpty
? buildLoadingIndicator()
: ImmichAssetGrid(
renderList: renderList,
renderList: ref.watch(assetProvider).renderList!,
allAssets: ref.watch(assetProvider).allAssets,
assetsPerRow: appSettingService
.getSetting(AppSettingsEnum.tilesPerRow),
showStorageIndicator: appSettingService

View File

@@ -70,11 +70,11 @@ final searchResultGroupByDateTimeProvider = StateProvider((ref) {
);
});
final searchRenderListProvider = StateProvider((ref) {
final searchRenderListProvider = FutureProvider((ref) {
var assetGroups = ref.watch(searchResultGroupByDateTimeProvider);
var settings = ref.watch(appSettingsServiceProvider);
final assetsPerRow = settings.getSetting(AppSettingsEnum.tilesPerRow);
return assetGroupsToRenderList(assetGroups, assetsPerRow);
return RenderList.fromAssetGroups(assetGroups, assetsPerRow);
});

View File

@@ -111,6 +111,7 @@ class SearchResultPage extends HookConsumerWidget {
buildSearchResult() {
var searchResultPageState = ref.watch(searchResultPageProvider);
var searchResultRenderList = ref.watch(searchRenderListProvider);
var allSearchAssets = ref.watch(searchResultPageProvider).searchResult;
var settings = ref.watch(appSettingsServiceProvider);
final assetsPerRow = settings.getSetting(AppSettingsEnum.tilesPerRow);
@@ -126,10 +127,21 @@ class SearchResultPage extends HookConsumerWidget {
}
if (searchResultPageState.isSuccess) {
return ImmichAssetGrid(
renderList: searchResultRenderList,
assetsPerRow: assetsPerRow,
showStorageIndicator: showStorageIndicator,
return searchResultRenderList.when(
data: (result) {
return ImmichAssetGrid(
allAssets: allSearchAssets,
renderList: result,
assetsPerRow: assetsPerRow,
showStorageIndicator: showStorageIndicator,
);
},
error: (err, stack) {
return Text("$err");
},
loading: () {
return const CircularProgressIndicator();
},
);
}

View File

@@ -21,7 +21,7 @@ class StorageIndicator extends HookConsumerWidget {
appSettingService.setSetting(AppSettingsEnum.storageIndicator, value);
showStorageIndicator.value = value;
ref.invalidate(assetGroupByDateTimeProvider);
ref.invalidate(assetProvider);
}
useEffect(

View File

@@ -23,7 +23,7 @@ class TilesPerRow extends HookConsumerWidget {
}
void sliderChangedEnd(double _) {
ref.invalidate(assetGroupByDateTimeProvider);
ref.invalidate(assetProvider);
}
useEffect(

View File

@@ -1,10 +1,14 @@
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/home/services/asset.service.dart';
import 'package:immich_mobile/modules/home/services/asset_cache.service.dart';
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.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/shared/models/asset.dart';
import 'package:immich_mobile/shared/services/device_info.service.dart';
import 'package:collection/collection.dart';
@@ -14,18 +18,79 @@ import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:photo_manager/photo_manager.dart';
class AssetNotifier extends StateNotifier<List<Asset>> {
class AssetsState {
final List<Asset> allAssets;
final RenderList? renderList;
AssetsState(this.allAssets, {this.renderList});
Future<AssetsState> withRenderDataStructure(int groupSize) async {
return AssetsState(
allAssets,
renderList:
await RenderList.fromAssetGroups(await _groupByDate(), groupSize),
);
}
AssetsState withAdditionalAssets(List<Asset> toAdd) {
return AssetsState([...allAssets, ...toAdd]);
}
_groupByDate() async {
sortCompare(List<Asset> assets) {
assets.sortByCompare<DateTime>(
(e) => e.createdAt,
(a, b) => b.compareTo(a),
);
return assets.groupListsBy(
(element) => DateFormat('y-MM-dd').format(element.createdAt.toLocal()),
);
}
return await compute(sortCompare, allAssets.toList());
}
static fromAssetList(List<Asset> assets) {
return AssetsState(assets);
}
static empty() {
return AssetsState([]);
}
}
class _CombineAssetsComputeParameters {
final Iterable<Asset> local;
final Iterable<Asset> remote;
final String deviceId;
_CombineAssetsComputeParameters(this.local, this.remote, this.deviceId);
}
class AssetNotifier extends StateNotifier<AssetsState> {
final AssetService _assetService;
final AssetCacheService _assetCacheService;
final AppSettingsService _settingsService;
final log = Logger('AssetNotifier');
final DeviceInfoService _deviceInfoService = DeviceInfoService();
bool _getAllAssetInProgress = false;
bool _deleteInProgress = false;
AssetNotifier(this._assetService, this._assetCacheService) : super([]);
AssetNotifier(
this._assetService,
this._assetCacheService,
this._settingsService,
) : super(AssetsState.fromAssetList([]));
_cacheState() {
_assetCacheService.put(state);
_updateAssetsState(List<Asset> newAssetList, {bool cache = true}) async {
if (cache) {
_assetCacheService.put(newAssetList);
}
state =
await AssetsState.fromAssetList(newAssetList).withRenderDataStructure(
_settingsService.getSetting(AppSettingsEnum.tilesPerRow),
);
}
getAllAsset() async {
@@ -43,17 +108,19 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
final remoteTask = _assetService.getRemoteAssets(
etag: isCacheValid ? box.get(assetEtagKey) : null,
);
if (isCacheValid && state.isEmpty) {
state = await _assetCacheService.get();
if (isCacheValid && state.allAssets.isEmpty) {
await _updateAssetsState(await _assetCacheService.get(), cache: false);
log.info(
"Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms",
"Reading assets ${state.allAssets.length} from cache: ${stopwatch.elapsedMilliseconds}ms",
);
stopwatch.reset();
}
int remoteBegin = state.indexWhere((a) => a.isRemote);
remoteBegin = remoteBegin == -1 ? state.length : remoteBegin;
final List<Asset> currentLocal = state.slice(0, remoteBegin);
int remoteBegin = state.allAssets.indexWhere((a) => a.isRemote);
remoteBegin = remoteBegin == -1 ? state.allAssets.length : remoteBegin;
final List<Asset> currentLocal = state.allAssets.slice(0, remoteBegin);
final Pair<List<Asset>?, String?> remoteResult = await remoteTask;
List<Asset>? newRemote = remoteResult.first;
List<Asset>? newLocal = await localTask;
@@ -64,27 +131,32 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
log.info("state is already up-to-date");
return;
}
newRemote ??= state.slice(remoteBegin);
newRemote ??= state.allAssets.slice(remoteBegin);
newLocal ??= [];
state = _combineLocalAndRemoteAssets(local: newLocal, remote: newRemote);
final combinedAssets = await _combineLocalAndRemoteAssets(
local: newLocal,
remote: newRemote,
);
await _updateAssetsState(combinedAssets);
log.info("Combining assets: ${stopwatch.elapsedMilliseconds}ms");
stopwatch.reset();
_cacheState();
box.put(assetEtagKey, remoteResult.second);
log.info("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
} finally {
_getAllAssetInProgress = false;
}
}
List<Asset> _combineLocalAndRemoteAssets({
required Iterable<Asset> local,
required List<Asset> remote,
}) {
static Future<List<Asset>> _computeCombine(
_CombineAssetsComputeParameters data,
) async {
var local = data.local;
var remote = data.remote;
final deviceId = data.deviceId;
final List<Asset> assets = [];
if (remote.isNotEmpty && local.isNotEmpty) {
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
final Set<String> existingIds = remote
.where((e) => e.deviceId == deviceId)
.map((e) => e.deviceAssetId)
@@ -97,31 +169,40 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
return assets;
}
Future<List<Asset>> _combineLocalAndRemoteAssets({
required Iterable<Asset> local,
required List<Asset> remote,
}) async {
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
return await compute(
_computeCombine,
_CombineAssetsComputeParameters(local, remote, deviceId),
);
}
clearAllAsset() {
state = [];
_cacheState();
_updateAssetsState([]);
}
onNewAssetUploaded(AssetResponseDto newAsset) {
final int i = state.indexWhere(
final int i = state.allAssets.indexWhere(
(a) =>
a.isRemote ||
(a.id == newAsset.deviceAssetId && a.deviceId == newAsset.deviceId),
);
if (i == -1 || state[i].deviceAssetId != newAsset.deviceAssetId) {
state = [...state, Asset.remote(newAsset)];
if (i == -1 || state.allAssets[i].deviceAssetId != newAsset.deviceAssetId) {
_updateAssetsState([...state.allAssets, Asset.remote(newAsset)]);
} else {
// order is important to keep all local-only assets at the beginning!
state = [
...state.slice(0, i),
...state.slice(i + 1),
_updateAssetsState([
...state.allAssets.slice(0, i),
...state.allAssets.slice(i + 1),
Asset.remote(newAsset),
];
]);
// TODO here is a place to unify local/remote assets by replacing the
// local-only asset in the state with a local&remote asset
}
_cacheState();
}
deleteAssets(Set<Asset> deleteAssets) async {
@@ -133,8 +214,9 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
deleted.addAll(localDeleted);
deleted.addAll(remoteDeleted);
if (deleted.isNotEmpty) {
state = state.where((a) => !deleted.contains(a.id)).toList();
_cacheState();
_updateAssetsState(
state.allAssets.where((a) => !deleted.contains(a.id)).toList(),
);
}
} finally {
_deleteInProgress = false;
@@ -180,23 +262,11 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
}
}
final assetProvider = StateNotifierProvider<AssetNotifier, List<Asset>>((ref) {
final assetProvider = StateNotifierProvider<AssetNotifier, AssetsState>((ref) {
return AssetNotifier(
ref.watch(assetServiceProvider),
ref.watch(assetCacheServiceProvider),
);
});
final assetGroupByDateTimeProvider = StateProvider((ref) {
final assets = ref.watch(assetProvider).toList();
// `toList()` ist needed to make a copy as to NOT sort the original list/state
assets.sortByCompare<DateTime>(
(e) => e.createdAt,
(a, b) => b.compareTo(a),
);
return assets.groupListsBy(
(element) => DateFormat('y-MM-dd').format(element.createdAt.toLocal()),
ref.watch(appSettingsServiceProvider),
);
});
@@ -204,7 +274,8 @@ final assetGroupByMonthYearProvider = StateProvider((ref) {
// TODO: remove `where` once temporary workaround is no longer needed (to only
// allow remote assets to be added to album). Keep `toList()` as to NOT sort
// the original list/state
final assets = ref.watch(assetProvider).where((e) => e.isRemote).toList();
final assets =
ref.watch(assetProvider).allAssets.where((e) => e.isRemote).toList();
assets.sortByCompare<DateTime>(
(e) => e.createdAt,

View File

@@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
abstract class JsonCache<T> {
@@ -31,8 +32,13 @@ abstract class JsonCache<T> {
}
}
static Future<String> _computeEncodeJson(dynamic toEncode) async {
return json.encode(toEncode);
}
Future<void> putRawData(dynamic data) async {
final jsonString = json.encode(data);
final jsonString = await compute(_computeEncodeJson, data);
final file = await _getCacheFile();
if (!await file.exists()) {
@@ -42,10 +48,15 @@ abstract class JsonCache<T> {
await file.writeAsString(jsonString);
}
dynamic readRawData() async {
static Future<dynamic> _computeDecodeJson(String jsonString) async {
return json.decode(jsonString);
}
Future<dynamic> readRawData() async {
final file = await _getCacheFile();
final data = await file.readAsString();
return json.decode(data);
return await compute(_computeDecodeJson, data);
}
void put(T data);

View File

@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
class CustomDraggingHandle extends StatelessWidget {
const CustomDraggingHandle({super.key});
@override
Widget build(BuildContext context) {
return Container(
height: 5,
width: 30,
decoration: BoxDecoration(
color: Colors.grey[500],
borderRadius: BorderRadius.circular(16),
),
);
}
}
class ControlBoxButton extends StatelessWidget {
const ControlBoxButton({
Key? key,
required this.label,
required this.iconData,
required this.onPressed,
}) : super(key: key);
final String label;
final IconData iconData;
final Function onPressed;
@override
Widget build(BuildContext context) {
return MaterialButton(
padding: const EdgeInsets.all(10),
shape: const CircleBorder(),
onPressed: () => onPressed(),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(iconData, size: 24),
const SizedBox(height: 6),
Text(
label,
style: const TextStyle(fontSize: 12.0),
),
],
),
);
}
}

View File

@@ -31,6 +31,7 @@ doc/CheckExistingAssetsDto.md
doc/CheckExistingAssetsResponseDto.md
doc/CreateAlbumDto.md
doc/CreateAlbumShareLinkDto.md
doc/CreateAssetsShareLinkDto.md
doc/CreateProfileImageResponseDto.md
doc/CreateTagDto.md
doc/CreateUserDto.md
@@ -86,6 +87,7 @@ doc/ThumbnailFormat.md
doc/TimeGroupEnum.md
doc/UpdateAlbumDto.md
doc/UpdateAssetDto.md
doc/UpdateAssetsToSharedLinkDto.md
doc/UpdateTagDto.md
doc/UpdateUserDto.md
doc/UpsertDeviceInfoDto.md
@@ -140,6 +142,7 @@ lib/model/check_existing_assets_dto.dart
lib/model/check_existing_assets_response_dto.dart
lib/model/create_album_dto.dart
lib/model/create_album_share_link_dto.dart
lib/model/create_assets_share_link_dto.dart
lib/model/create_profile_image_response_dto.dart
lib/model/create_tag_dto.dart
lib/model/create_user_dto.dart
@@ -188,6 +191,7 @@ lib/model/thumbnail_format.dart
lib/model/time_group_enum.dart
lib/model/update_album_dto.dart
lib/model/update_asset_dto.dart
lib/model/update_assets_to_shared_link_dto.dart
lib/model/update_tag_dto.dart
lib/model/update_user_dto.dart
lib/model/upsert_device_info_dto.dart
@@ -224,6 +228,7 @@ test/check_existing_assets_dto_test.dart
test/check_existing_assets_response_dto_test.dart
test/create_album_dto_test.dart
test/create_album_share_link_dto_test.dart
test/create_assets_share_link_dto_test.dart
test/create_profile_image_response_dto_test.dart
test/create_tag_dto_test.dart
test/create_user_dto_test.dart
@@ -279,6 +284,7 @@ test/thumbnail_format_test.dart
test/time_group_enum_test.dart
test/update_album_dto_test.dart
test/update_asset_dto_test.dart
test/update_assets_to_shared_link_dto_test.dart
test/update_tag_dto_test.dart
test/update_user_dto_test.dart
test/upsert_device_info_dto_test.dart

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.40.0
- API version: 1.41.1
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -77,6 +77,7 @@ Class | Method | HTTP request | Description
*AlbumApi* | [**updateAlbumInfo**](doc//AlbumApi.md#updatealbuminfo) | **PATCH** /album/{albumId} |
*AssetApi* | [**checkDuplicateAsset**](doc//AssetApi.md#checkduplicateasset) | **POST** /asset/check |
*AssetApi* | [**checkExistingAssets**](doc//AssetApi.md#checkexistingassets) | **POST** /asset/exist |
*AssetApi* | [**createAssetsSharedLink**](doc//AssetApi.md#createassetssharedlink) | **POST** /asset/shared-link |
*AssetApi* | [**deleteAsset**](doc//AssetApi.md#deleteasset) | **DELETE** /asset |
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download/{assetId} |
*AssetApi* | [**downloadFiles**](doc//AssetApi.md#downloadfiles) | **POST** /asset/download-files |
@@ -94,6 +95,7 @@ Class | Method | HTTP request | Description
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{assetId} |
*AssetApi* | [**updateAssetsInSharedLink**](doc//AssetApi.md#updateassetsinsharedlink) | **PATCH** /asset/shared-link |
*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload |
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password |
@@ -167,6 +169,7 @@ Class | Method | HTTP request | Description
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
- [CreateAlbumDto](doc//CreateAlbumDto.md)
- [CreateAlbumShareLinkDto](doc//CreateAlbumShareLinkDto.md)
- [CreateAssetsShareLinkDto](doc//CreateAssetsShareLinkDto.md)
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
- [CreateTagDto](doc//CreateTagDto.md)
- [CreateUserDto](doc//CreateUserDto.md)
@@ -215,6 +218,7 @@ Class | Method | HTTP request | Description
- [TimeGroupEnum](doc//TimeGroupEnum.md)
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
- [UpdateAssetDto](doc//UpdateAssetDto.md)
- [UpdateAssetsToSharedLinkDto](doc//UpdateAssetsToSharedLinkDto.md)
- [UpdateTagDto](doc//UpdateTagDto.md)
- [UpdateUserDto](doc//UpdateUserDto.md)
- [UpsertDeviceInfoDto](doc//UpsertDeviceInfoDto.md)

View File

@@ -21,6 +21,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -62,6 +64,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -102,6 +106,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -143,6 +149,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -180,6 +188,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -28,6 +28,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -77,6 +79,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -126,6 +130,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -173,6 +179,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -220,6 +228,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -266,6 +276,8 @@ void (empty response body)
### Example
```dart
import 'package:openapi/api.dart';
@@ -315,6 +327,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -358,6 +372,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -405,6 +421,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -454,6 +472,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -503,6 +523,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -551,6 +573,8 @@ void (empty response body)
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -11,6 +11,7 @@ Method | HTTP request | Description
------------- | ------------- | -------------
[**checkDuplicateAsset**](AssetApi.md#checkduplicateasset) | **POST** /asset/check |
[**checkExistingAssets**](AssetApi.md#checkexistingassets) | **POST** /asset/exist |
[**createAssetsSharedLink**](AssetApi.md#createassetssharedlink) | **POST** /asset/shared-link |
[**deleteAsset**](AssetApi.md#deleteasset) | **DELETE** /asset |
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download/{assetId} |
[**downloadFiles**](AssetApi.md#downloadfiles) | **POST** /asset/download-files |
@@ -28,6 +29,7 @@ Method | HTTP request | Description
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{assetId} |
[**updateAssetsInSharedLink**](AssetApi.md#updateassetsinsharedlink) | **PATCH** /asset/shared-link |
[**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload |
@@ -129,11 +131,62 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **createAssetsSharedLink**
> SharedLinkResponseDto createAssetsSharedLink(createAssetsShareLinkDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final createAssetsShareLinkDto = CreateAssetsShareLinkDto(); // CreateAssetsShareLinkDto |
try {
final result = api_instance.createAssetsSharedLink(createAssetsShareLinkDto);
print(result);
} catch (e) {
print('Exception when calling AssetApi->createAssetsSharedLink: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**createAssetsShareLinkDto** | [**CreateAssetsShareLinkDto**](CreateAssetsShareLinkDto.md)| |
### Return type
[**SharedLinkResponseDto**](SharedLinkResponseDto.md)
### Authorization
[bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **deleteAsset**
> List<DeleteAssetResponseDto> deleteAsset(deleteAssetDto)
### Example
```dart
import 'package:openapi/api.dart';
@@ -181,6 +234,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -232,6 +287,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -279,6 +336,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -424,6 +483,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -471,6 +532,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -518,6 +581,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -561,6 +626,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -604,6 +671,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -653,6 +722,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -696,6 +767,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -788,6 +861,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -835,6 +910,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -932,11 +1009,62 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **updateAssetsInSharedLink**
> SharedLinkResponseDto updateAssetsInSharedLink(updateAssetsToSharedLinkDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final updateAssetsToSharedLinkDto = UpdateAssetsToSharedLinkDto(); // UpdateAssetsToSharedLinkDto |
try {
final result = api_instance.updateAssetsInSharedLink(updateAssetsToSharedLinkDto);
print(result);
} catch (e) {
print('Exception when calling AssetApi->updateAssetsInSharedLink: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**updateAssetsToSharedLinkDto** | [**UpdateAssetsToSharedLinkDto**](UpdateAssetsToSharedLinkDto.md)| |
### Return type
[**SharedLinkResponseDto**](SharedLinkResponseDto.md)
### Authorization
[bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **uploadFile**
> AssetFileUploadResponseDto uploadFile(assetData)
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -21,6 +21,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -62,6 +64,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -109,6 +113,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -150,6 +156,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -187,6 +195,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -0,0 +1,18 @@
# openapi.model.CreateAssetsShareLinkDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetIds** | **List<String>** | | [default to const []]
**expiredAt** | **String** | | [optional]
**allowUpload** | **bool** | | [optional]
**description** | **String** | | [optional]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -117,6 +117,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -19,6 +19,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -62,6 +64,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -109,6 +113,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -21,6 +21,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -62,6 +64,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -103,6 +107,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -144,6 +150,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -180,6 +188,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -20,6 +20,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -57,6 +59,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -94,6 +98,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -131,6 +137,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -21,6 +21,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -64,6 +66,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -101,6 +105,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -138,6 +144,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -179,6 +187,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -20,6 +20,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -63,6 +65,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -106,6 +110,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -149,6 +155,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -21,6 +21,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -62,6 +64,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -102,6 +106,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -139,6 +145,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -180,6 +188,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -0,0 +1,15 @@
# openapi.model.UpdateAssetsToSharedLinkDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**assetIds** | **List<String>** | | [default to const []]
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -26,6 +26,8 @@ Method | HTTP request | Description
### Example
```dart
import 'package:openapi/api.dart';
@@ -73,6 +75,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -120,6 +124,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -167,6 +173,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -214,6 +222,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';
@@ -257,6 +267,8 @@ This endpoint does not need any parameter.
### Example
```dart
import 'package:openapi/api.dart';
@@ -298,6 +310,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -339,6 +353,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -380,6 +396,8 @@ No authorization required
### Example
```dart
import 'package:openapi/api.dart';
@@ -427,6 +445,8 @@ Name | Type | Description | Notes
### Example
```dart
import 'package:openapi/api.dart';

View File

@@ -64,6 +64,7 @@ part 'model/check_existing_assets_dto.dart';
part 'model/check_existing_assets_response_dto.dart';
part 'model/create_album_dto.dart';
part 'model/create_album_share_link_dto.dart';
part 'model/create_assets_share_link_dto.dart';
part 'model/create_profile_image_response_dto.dart';
part 'model/create_tag_dto.dart';
part 'model/create_user_dto.dart';
@@ -112,6 +113,7 @@ part 'model/thumbnail_format.dart';
part 'model/time_group_enum.dart';
part 'model/update_album_dto.dart';
part 'model/update_asset_dto.dart';
part 'model/update_assets_to_shared_link_dto.dart';
part 'model/update_tag_dto.dart';
part 'model/update_user_dto.dart';
part 'model/upsert_device_info_dto.dart';

View File

@@ -16,7 +16,10 @@ class AlbumApi {
final ApiClient apiClient;
/// Performs an HTTP 'PUT /album/{albumId}/assets' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -48,6 +51,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -68,7 +73,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'PUT /album/{albumId}/users' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -100,6 +108,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -120,7 +130,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'POST /album' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [CreateAlbumDto] createAlbumDto (required):
@@ -149,6 +162,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [CreateAlbumDto] createAlbumDto (required):
@@ -167,7 +182,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'POST /album/create-shared-link' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [CreateAlbumShareLinkDto] createAlbumShareLinkDto (required):
@@ -196,6 +214,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [CreateAlbumShareLinkDto] createAlbumShareLinkDto (required):
@@ -214,7 +234,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'DELETE /album/{albumId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -244,6 +267,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -254,7 +279,10 @@ class AlbumApi {
}
}
/// Performs an HTTP 'GET /album/{albumId}/download' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -290,6 +318,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -310,7 +340,9 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'GET /album/count-by-user-id' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAlbumCountByUserIdWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/album/count-by-user-id';
@@ -336,6 +368,7 @@ class AlbumApi {
);
}
///
Future<AlbumCountResponseDto?> getAlbumCountByUserId() async {
final response = await getAlbumCountByUserIdWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -351,7 +384,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'GET /album/{albumId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -381,6 +417,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -399,7 +437,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'GET /album' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [bool] shared:
@@ -438,6 +479,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [bool] shared:
@@ -462,7 +505,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'DELETE /album/{albumId}/assets' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -494,6 +540,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -514,7 +562,10 @@ class AlbumApi {
return null;
}
/// Performs an HTTP 'DELETE /album/{albumId}/user/{userId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -547,6 +598,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -559,7 +612,10 @@ class AlbumApi {
}
}
/// Performs an HTTP 'PATCH /album/{albumId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] albumId (required):
@@ -591,6 +647,8 @@ class AlbumApi {
);
}
///
///
/// Parameters:
///
/// * [String] albumId (required):

View File

@@ -16,7 +16,10 @@ class APIKeyApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /api-key' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [APIKeyCreateDto] aPIKeyCreateDto (required):
@@ -45,6 +48,8 @@ class APIKeyApi {
);
}
///
///
/// Parameters:
///
/// * [APIKeyCreateDto] aPIKeyCreateDto (required):
@@ -63,7 +68,10 @@ class APIKeyApi {
return null;
}
/// Performs an HTTP 'DELETE /api-key/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [num] id (required):
@@ -93,6 +101,8 @@ class APIKeyApi {
);
}
///
///
/// Parameters:
///
/// * [num] id (required):
@@ -103,7 +113,10 @@ class APIKeyApi {
}
}
/// Performs an HTTP 'GET /api-key/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [num] id (required):
@@ -133,6 +146,8 @@ class APIKeyApi {
);
}
///
///
/// Parameters:
///
/// * [num] id (required):
@@ -151,7 +166,9 @@ class APIKeyApi {
return null;
}
/// Performs an HTTP 'GET /api-key' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getKeysWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/api-key';
@@ -177,6 +194,7 @@ class APIKeyApi {
);
}
///
Future<List<APIKeyResponseDto>?> getKeys() async {
final response = await getKeysWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -195,7 +213,10 @@ class APIKeyApi {
return null;
}
/// Performs an HTTP 'PUT /api-key/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [num] id (required):
@@ -227,6 +248,8 @@ class APIKeyApi {
);
}
///
///
/// Parameters:
///
/// * [num] id (required):

View File

@@ -120,7 +120,62 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'DELETE /asset' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [CreateAssetsShareLinkDto] createAssetsShareLinkDto (required):
Future<Response> createAssetsSharedLinkWithHttpInfo(CreateAssetsShareLinkDto createAssetsShareLinkDto,) async {
// ignore: prefer_const_declarations
final path = r'/asset/shared-link';
// ignore: prefer_final_locals
Object? postBody = createAssetsShareLinkDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'POST',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
///
///
/// Parameters:
///
/// * [CreateAssetsShareLinkDto] createAssetsShareLinkDto (required):
Future<SharedLinkResponseDto?> createAssetsSharedLink(CreateAssetsShareLinkDto createAssetsShareLinkDto,) async {
final response = await createAssetsSharedLinkWithHttpInfo(createAssetsShareLinkDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto;
}
return null;
}
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [DeleteAssetDto] deleteAssetDto (required):
@@ -149,6 +204,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [DeleteAssetDto] deleteAssetDto (required):
@@ -170,7 +227,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/download/{assetId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] assetId (required):
@@ -211,6 +271,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [String] assetId (required):
@@ -233,7 +295,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'POST /asset/download-files' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [DownloadFilesDto] downloadFilesDto (required):
@@ -262,6 +327,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [DownloadFilesDto] downloadFilesDto (required):
@@ -280,7 +347,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/download-library' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [num] skip:
@@ -313,6 +383,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [num] skip:
@@ -445,7 +517,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'POST /asset/time-bucket' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [GetAssetByTimeBucketDto] getAssetByTimeBucketDto (required):
@@ -474,6 +549,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [GetAssetByTimeBucketDto] getAssetByTimeBucketDto (required):
@@ -495,7 +572,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'POST /asset/count-by-time-bucket' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [GetAssetCountByTimeBucketDto] getAssetCountByTimeBucketDto (required):
@@ -524,6 +604,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [GetAssetCountByTimeBucketDto] getAssetCountByTimeBucketDto (required):
@@ -542,7 +624,9 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/count-by-user-id' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAssetCountByUserIdWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/asset/count-by-user-id';
@@ -568,6 +652,7 @@ class AssetApi {
);
}
///
Future<AssetCountByUserIdResponseDto?> getAssetCountByUserId() async {
final response = await getAssetCountByUserIdWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -583,7 +668,9 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/search-terms' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAssetSearchTermsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/asset/search-terms';
@@ -609,6 +696,7 @@ class AssetApi {
);
}
///
Future<List<String>?> getAssetSearchTerms() async {
final response = await getAssetSearchTermsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -627,7 +715,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/thumbnail/{assetId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] assetId (required):
@@ -663,6 +754,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [String] assetId (required):
@@ -683,7 +776,9 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/curated-locations' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getCuratedLocationsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/asset/curated-locations';
@@ -709,6 +804,7 @@ class AssetApi {
);
}
///
Future<List<CuratedLocationsResponseDto>?> getCuratedLocations() async {
final response = await getCuratedLocationsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -727,7 +823,9 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/curated-objects' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getCuratedObjectsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/asset/curated-objects';
@@ -753,6 +851,7 @@ class AssetApi {
);
}
///
Future<List<CuratedObjectsResponseDto>?> getCuratedObjects() async {
final response = await getCuratedObjectsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -827,7 +926,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'POST /asset/search' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [SearchAssetDto] searchAssetDto (required):
@@ -856,6 +958,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [SearchAssetDto] searchAssetDto (required):
@@ -877,7 +981,10 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'GET /asset/file/{assetId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] assetId (required):
@@ -918,6 +1025,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [String] assetId (required):
@@ -997,7 +1106,62 @@ class AssetApi {
return null;
}
/// Performs an HTTP 'POST /asset/upload' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [UpdateAssetsToSharedLinkDto] updateAssetsToSharedLinkDto (required):
Future<Response> updateAssetsInSharedLinkWithHttpInfo(UpdateAssetsToSharedLinkDto updateAssetsToSharedLinkDto,) async {
// ignore: prefer_const_declarations
final path = r'/asset/shared-link';
// ignore: prefer_final_locals
Object? postBody = updateAssetsToSharedLinkDto;
final queryParams = <QueryParam>[];
final headerParams = <String, String>{};
final formParams = <String, String>{};
const contentTypes = <String>['application/json'];
return apiClient.invokeAPI(
path,
'PATCH',
queryParams,
postBody,
headerParams,
formParams,
contentTypes.isEmpty ? null : contentTypes.first,
);
}
///
///
/// Parameters:
///
/// * [UpdateAssetsToSharedLinkDto] updateAssetsToSharedLinkDto (required):
Future<SharedLinkResponseDto?> updateAssetsInSharedLink(UpdateAssetsToSharedLinkDto updateAssetsToSharedLinkDto,) async {
final response = await updateAssetsInSharedLinkWithHttpInfo(updateAssetsToSharedLinkDto,);
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
// When a remote server returns no body with a status of 204, we shall not decode it.
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
// FormatException when trying to decode an empty string.
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'SharedLinkResponseDto',) as SharedLinkResponseDto;
}
return null;
}
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [MultipartFile] assetData (required):
@@ -1036,6 +1200,8 @@ class AssetApi {
);
}
///
///
/// Parameters:
///
/// * [MultipartFile] assetData (required):

View File

@@ -16,7 +16,10 @@ class AuthenticationApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /auth/admin-sign-up' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [SignUpDto] signUpDto (required):
@@ -45,6 +48,8 @@ class AuthenticationApi {
);
}
///
///
/// Parameters:
///
/// * [SignUpDto] signUpDto (required):
@@ -63,7 +68,10 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/change-password' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [ChangePasswordDto] changePasswordDto (required):
@@ -92,6 +100,8 @@ class AuthenticationApi {
);
}
///
///
/// Parameters:
///
/// * [ChangePasswordDto] changePasswordDto (required):
@@ -110,7 +120,10 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/login' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [LoginCredentialDto] loginCredentialDto (required):
@@ -139,6 +152,8 @@ class AuthenticationApi {
);
}
///
///
/// Parameters:
///
/// * [LoginCredentialDto] loginCredentialDto (required):
@@ -157,7 +172,9 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/logout' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> logoutWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/auth/logout';
@@ -183,6 +200,7 @@ class AuthenticationApi {
);
}
///
Future<LogoutResponseDto?> logout() async {
final response = await logoutWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -198,7 +216,9 @@ class AuthenticationApi {
return null;
}
/// Performs an HTTP 'POST /auth/validateToken' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> validateAccessTokenWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/auth/validateToken';
@@ -224,6 +244,7 @@ class AuthenticationApi {
);
}
///
Future<ValidateAccessTokenResponseDto?> validateAccessToken() async {
final response = await validateAccessTokenWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {

View File

@@ -120,7 +120,10 @@ class DeviceInfoApi {
return null;
}
/// Performs an HTTP 'PUT /device-info' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [UpsertDeviceInfoDto] upsertDeviceInfoDto (required):
@@ -149,6 +152,8 @@ class DeviceInfoApi {
);
}
///
///
/// Parameters:
///
/// * [UpsertDeviceInfoDto] upsertDeviceInfoDto (required):

View File

@@ -16,7 +16,9 @@ class JobApi {
final ApiClient apiClient;
/// Performs an HTTP 'GET /jobs' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAllJobsStatusWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/jobs';
@@ -42,6 +44,7 @@ class JobApi {
);
}
///
Future<AllJobStatusResponseDto?> getAllJobsStatus() async {
final response = await getAllJobsStatusWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -57,7 +60,10 @@ class JobApi {
return null;
}
/// Performs an HTTP 'GET /jobs/{jobId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [JobId] jobId (required):
@@ -87,6 +93,8 @@ class JobApi {
);
}
///
///
/// Parameters:
///
/// * [JobId] jobId (required):
@@ -105,7 +113,10 @@ class JobApi {
return null;
}
/// Performs an HTTP 'PUT /jobs/{jobId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [JobId] jobId (required):
@@ -137,6 +148,8 @@ class JobApi {
);
}
///
///
/// Parameters:
///
/// * [JobId] jobId (required):

View File

@@ -16,7 +16,10 @@ class OAuthApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /oauth/callback' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
@@ -45,6 +48,8 @@ class OAuthApi {
);
}
///
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
@@ -63,7 +68,10 @@ class OAuthApi {
return null;
}
/// Performs an HTTP 'POST /oauth/config' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [OAuthConfigDto] oAuthConfigDto (required):
@@ -92,6 +100,8 @@ class OAuthApi {
);
}
///
///
/// Parameters:
///
/// * [OAuthConfigDto] oAuthConfigDto (required):
@@ -110,7 +120,10 @@ class OAuthApi {
return null;
}
/// Performs an HTTP 'POST /oauth/link' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
@@ -139,6 +152,8 @@ class OAuthApi {
);
}
///
///
/// Parameters:
///
/// * [OAuthCallbackDto] oAuthCallbackDto (required):
@@ -157,7 +172,9 @@ class OAuthApi {
return null;
}
/// Performs an HTTP 'GET /oauth/mobile-redirect' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> mobileRedirectWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/oauth/mobile-redirect';
@@ -183,6 +200,7 @@ class OAuthApi {
);
}
///
Future<void> mobileRedirect() async {
final response = await mobileRedirectWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -190,7 +208,9 @@ class OAuthApi {
}
}
/// Performs an HTTP 'POST /oauth/unlink' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> unlinkWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/oauth/unlink';
@@ -216,6 +236,7 @@ class OAuthApi {
);
}
///
Future<UserResponseDto?> unlink() async {
final response = await unlinkWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {

View File

@@ -16,7 +16,9 @@ class ServerInfoApi {
final ApiClient apiClient;
/// Performs an HTTP 'GET /server-info' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerInfoWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/server-info';
@@ -42,6 +44,7 @@ class ServerInfoApi {
);
}
///
Future<ServerInfoResponseDto?> getServerInfo() async {
final response = await getServerInfoWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -57,7 +60,9 @@ class ServerInfoApi {
return null;
}
/// Performs an HTTP 'GET /server-info/version' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getServerVersionWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/server-info/version';
@@ -83,6 +88,7 @@ class ServerInfoApi {
);
}
///
Future<ServerVersionReponseDto?> getServerVersion() async {
final response = await getServerVersionWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -98,7 +104,9 @@ class ServerInfoApi {
return null;
}
/// Performs an HTTP 'GET /server-info/stats' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getStatsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/server-info/stats';
@@ -124,6 +132,7 @@ class ServerInfoApi {
);
}
///
Future<ServerStatsResponseDto?> getStats() async {
final response = await getStatsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -139,7 +148,9 @@ class ServerInfoApi {
return null;
}
/// Performs an HTTP 'GET /server-info/ping' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> pingServerWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/server-info/ping';
@@ -165,6 +176,7 @@ class ServerInfoApi {
);
}
///
Future<ServerPingResponse?> pingServer() async {
final response = await pingServerWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {

View File

@@ -16,7 +16,10 @@ class ShareApi {
final ApiClient apiClient;
/// Performs an HTTP 'PATCH /share/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -48,6 +51,8 @@ class ShareApi {
);
}
///
///
/// Parameters:
///
/// * [String] id (required):
@@ -68,7 +73,9 @@ class ShareApi {
return null;
}
/// Performs an HTTP 'GET /share' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getAllSharedLinksWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/share';
@@ -94,6 +101,7 @@ class ShareApi {
);
}
///
Future<List<SharedLinkResponseDto>?> getAllSharedLinks() async {
final response = await getAllSharedLinksWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -112,7 +120,9 @@ class ShareApi {
return null;
}
/// Performs an HTTP 'GET /share/me' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getMySharedLinkWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/share/me';
@@ -138,6 +148,7 @@ class ShareApi {
);
}
///
Future<SharedLinkResponseDto?> getMySharedLink() async {
final response = await getMySharedLinkWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -153,7 +164,10 @@ class ShareApi {
return null;
}
/// Performs an HTTP 'GET /share/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -183,6 +197,8 @@ class ShareApi {
);
}
///
///
/// Parameters:
///
/// * [String] id (required):
@@ -201,7 +217,10 @@ class ShareApi {
return null;
}
/// Performs an HTTP 'DELETE /share/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -231,6 +250,8 @@ class ShareApi {
);
}
///
///
/// Parameters:
///
/// * [String] id (required):

View File

@@ -16,7 +16,9 @@ class SystemConfigApi {
final ApiClient apiClient;
/// Performs an HTTP 'GET /system-config' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getConfigWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/system-config';
@@ -42,6 +44,7 @@ class SystemConfigApi {
);
}
///
Future<SystemConfigDto?> getConfig() async {
final response = await getConfigWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -57,7 +60,9 @@ class SystemConfigApi {
return null;
}
/// Performs an HTTP 'GET /system-config/defaults' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getDefaultsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/system-config/defaults';
@@ -83,6 +88,7 @@ class SystemConfigApi {
);
}
///
Future<SystemConfigDto?> getDefaults() async {
final response = await getDefaultsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -98,7 +104,9 @@ class SystemConfigApi {
return null;
}
/// Performs an HTTP 'GET /system-config/storage-template-options' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getStorageTemplateOptionsWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/system-config/storage-template-options';
@@ -124,6 +132,7 @@ class SystemConfigApi {
);
}
///
Future<SystemConfigTemplateStorageOptionDto?> getStorageTemplateOptions() async {
final response = await getStorageTemplateOptionsWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -139,7 +148,10 @@ class SystemConfigApi {
return null;
}
/// Performs an HTTP 'PUT /system-config' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [SystemConfigDto] systemConfigDto (required):
@@ -168,6 +180,8 @@ class SystemConfigApi {
);
}
///
///
/// Parameters:
///
/// * [SystemConfigDto] systemConfigDto (required):

View File

@@ -16,7 +16,10 @@ class TagApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /tag' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [CreateTagDto] createTagDto (required):
@@ -45,6 +48,8 @@ class TagApi {
);
}
///
///
/// Parameters:
///
/// * [CreateTagDto] createTagDto (required):
@@ -63,7 +68,10 @@ class TagApi {
return null;
}
/// Performs an HTTP 'DELETE /tag/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -93,6 +101,8 @@ class TagApi {
);
}
///
///
/// Parameters:
///
/// * [String] id (required):
@@ -103,7 +113,9 @@ class TagApi {
}
}
/// Performs an HTTP 'GET /tag' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> findAllWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/tag';
@@ -129,6 +141,7 @@ class TagApi {
);
}
///
Future<List<TagResponseDto>?> findAll() async {
final response = await findAllWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -147,7 +160,10 @@ class TagApi {
return null;
}
/// Performs an HTTP 'GET /tag/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -177,6 +193,8 @@ class TagApi {
);
}
///
///
/// Parameters:
///
/// * [String] id (required):
@@ -195,7 +213,10 @@ class TagApi {
return null;
}
/// Performs an HTTP 'PATCH /tag/{id}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] id (required):
@@ -227,6 +248,8 @@ class TagApi {
);
}
///
///
/// Parameters:
///
/// * [String] id (required):

View File

@@ -16,7 +16,10 @@ class UserApi {
final ApiClient apiClient;
/// Performs an HTTP 'POST /user/profile-image' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [MultipartFile] file (required):
@@ -55,6 +58,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [MultipartFile] file (required):
@@ -73,7 +78,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'POST /user' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [CreateUserDto] createUserDto (required):
@@ -102,6 +110,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [CreateUserDto] createUserDto (required):
@@ -120,7 +130,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'DELETE /user/{userId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] userId (required):
@@ -150,6 +163,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [String] userId (required):
@@ -168,7 +183,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [bool] isAll (required):
@@ -199,6 +217,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [bool] isAll (required):
@@ -220,7 +240,9 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/me' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getMyUserInfoWithHttpInfo() async {
// ignore: prefer_const_declarations
final path = r'/user/me';
@@ -246,6 +268,7 @@ class UserApi {
);
}
///
Future<UserResponseDto?> getMyUserInfo() async {
final response = await getMyUserInfoWithHttpInfo();
if (response.statusCode >= HttpStatus.badRequest) {
@@ -261,7 +284,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/profile-image/{userId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] userId (required):
@@ -291,6 +317,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [String] userId (required):
@@ -309,7 +337,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/info/{userId}' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] userId (required):
@@ -339,6 +370,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [String] userId (required):
@@ -357,7 +390,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'GET /user/count' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [bool] admin:
@@ -390,6 +426,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [bool] admin:
@@ -408,7 +446,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'POST /user/{userId}/restore' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [String] userId (required):
@@ -438,6 +479,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [String] userId (required):
@@ -456,7 +499,10 @@ class UserApi {
return null;
}
/// Performs an HTTP 'PUT /user' operation and returns the [Response].
///
///
/// Note: This method returns the HTTP [Response].
///
/// Parameters:
///
/// * [UpdateUserDto] updateUserDto (required):
@@ -485,6 +531,8 @@ class UserApi {
);
}
///
///
/// Parameters:
///
/// * [UpdateUserDto] updateUserDto (required):

View File

@@ -240,6 +240,8 @@ class ApiClient {
return CreateAlbumDto.fromJson(value);
case 'CreateAlbumShareLinkDto':
return CreateAlbumShareLinkDto.fromJson(value);
case 'CreateAssetsShareLinkDto':
return CreateAssetsShareLinkDto.fromJson(value);
case 'CreateProfileImageResponseDto':
return CreateProfileImageResponseDto.fromJson(value);
case 'CreateTagDto':
@@ -336,6 +338,8 @@ class ApiClient {
return UpdateAlbumDto.fromJson(value);
case 'UpdateAssetDto':
return UpdateAssetDto.fromJson(value);
case 'UpdateAssetsToSharedLinkDto':
return UpdateAssetsToSharedLinkDto.fromJson(value);
case 'UpdateTagDto':
return UpdateTagDto.fromJson(value);
case 'UpdateUserDto':

View File

@@ -0,0 +1,164 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class CreateAssetsShareLinkDto {
/// Returns a new [CreateAssetsShareLinkDto] instance.
CreateAssetsShareLinkDto({
this.assetIds = const [],
this.expiredAt,
this.allowUpload,
this.description,
});
List<String> assetIds;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? expiredAt;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
bool? allowUpload;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? description;
@override
bool operator ==(Object other) => identical(this, other) || other is CreateAssetsShareLinkDto &&
other.assetIds == assetIds &&
other.expiredAt == expiredAt &&
other.allowUpload == allowUpload &&
other.description == description;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetIds.hashCode) +
(expiredAt == null ? 0 : expiredAt!.hashCode) +
(allowUpload == null ? 0 : allowUpload!.hashCode) +
(description == null ? 0 : description!.hashCode);
@override
String toString() => 'CreateAssetsShareLinkDto[assetIds=$assetIds, expiredAt=$expiredAt, allowUpload=$allowUpload, description=$description]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetIds'] = this.assetIds;
if (this.expiredAt != null) {
json[r'expiredAt'] = this.expiredAt;
} else {
// json[r'expiredAt'] = null;
}
if (this.allowUpload != null) {
json[r'allowUpload'] = this.allowUpload;
} else {
// json[r'allowUpload'] = null;
}
if (this.description != null) {
json[r'description'] = this.description;
} else {
// json[r'description'] = null;
}
return json;
}
/// Returns a new [CreateAssetsShareLinkDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static CreateAssetsShareLinkDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "CreateAssetsShareLinkDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "CreateAssetsShareLinkDto[$key]" has a null value in JSON.');
});
return true;
}());
return CreateAssetsShareLinkDto(
assetIds: json[r'assetIds'] is List
? (json[r'assetIds'] as List).cast<String>()
: const [],
expiredAt: mapValueOfType<String>(json, r'expiredAt'),
allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
description: mapValueOfType<String>(json, r'description'),
);
}
return null;
}
static List<CreateAssetsShareLinkDto>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <CreateAssetsShareLinkDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = CreateAssetsShareLinkDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, CreateAssetsShareLinkDto> mapFromJson(dynamic json) {
final map = <String, CreateAssetsShareLinkDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = CreateAssetsShareLinkDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of CreateAssetsShareLinkDto-objects as value to a dart map
static Map<String, List<CreateAssetsShareLinkDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<CreateAssetsShareLinkDto>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = CreateAssetsShareLinkDto.listFromJson(entry.value, growable: growable,);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetIds',
};
}

View File

@@ -0,0 +1,113 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
part of openapi.api;
class UpdateAssetsToSharedLinkDto {
/// Returns a new [UpdateAssetsToSharedLinkDto] instance.
UpdateAssetsToSharedLinkDto({
this.assetIds = const [],
});
List<String> assetIds;
@override
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetsToSharedLinkDto &&
other.assetIds == assetIds;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(assetIds.hashCode);
@override
String toString() => 'UpdateAssetsToSharedLinkDto[assetIds=$assetIds]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'assetIds'] = this.assetIds;
return json;
}
/// Returns a new [UpdateAssetsToSharedLinkDto] instance and imports its values from
/// [value] if it's a [Map], null otherwise.
// ignore: prefer_constructors_over_static_methods
static UpdateAssetsToSharedLinkDto? fromJson(dynamic value) {
if (value is Map) {
final json = value.cast<String, dynamic>();
// Ensure that the map contains the required keys.
// Note 1: the values aren't checked for validity beyond being non-null.
// Note 2: this code is stripped in release mode!
assert(() {
requiredKeys.forEach((key) {
assert(json.containsKey(key), 'Required key "UpdateAssetsToSharedLinkDto[$key]" is missing from JSON.');
assert(json[key] != null, 'Required key "UpdateAssetsToSharedLinkDto[$key]" has a null value in JSON.');
});
return true;
}());
return UpdateAssetsToSharedLinkDto(
assetIds: json[r'assetIds'] is List
? (json[r'assetIds'] as List).cast<String>()
: const [],
);
}
return null;
}
static List<UpdateAssetsToSharedLinkDto>? listFromJson(dynamic json, {bool growable = false,}) {
final result = <UpdateAssetsToSharedLinkDto>[];
if (json is List && json.isNotEmpty) {
for (final row in json) {
final value = UpdateAssetsToSharedLinkDto.fromJson(row);
if (value != null) {
result.add(value);
}
}
}
return result.toList(growable: growable);
}
static Map<String, UpdateAssetsToSharedLinkDto> mapFromJson(dynamic json) {
final map = <String, UpdateAssetsToSharedLinkDto>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = UpdateAssetsToSharedLinkDto.fromJson(entry.value);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
// maps a json object with a list of UpdateAssetsToSharedLinkDto-objects as value to a dart map
static Map<String, List<UpdateAssetsToSharedLinkDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
final map = <String, List<UpdateAssetsToSharedLinkDto>>{};
if (json is Map && json.isNotEmpty) {
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
for (final entry in json.entries) {
final value = UpdateAssetsToSharedLinkDto.listFromJson(entry.value, growable: growable,);
if (value != null) {
map[entry.key] = value;
}
}
}
return map;
}
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'assetIds',
};
}

View File

@@ -17,61 +17,85 @@ void main() {
// final instance = AlbumApi();
group('tests for AlbumApi', () {
//
//
//Future<AddAssetsResponseDto> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto) async
test('test addAssetsToAlbum', () async {
// TODO
});
//
//
//Future<AlbumResponseDto> addUsersToAlbum(String albumId, AddUsersDto addUsersDto) async
test('test addUsersToAlbum', () async {
// TODO
});
//
//
//Future<AlbumResponseDto> createAlbum(CreateAlbumDto createAlbumDto) async
test('test createAlbum', () async {
// TODO
});
//
//
//Future<SharedLinkResponseDto> createAlbumSharedLink(CreateAlbumShareLinkDto createAlbumShareLinkDto) async
test('test createAlbumSharedLink', () async {
// TODO
});
//
//
//Future deleteAlbum(String albumId) async
test('test deleteAlbum', () async {
// TODO
});
//
//
//Future<Object> downloadArchive(String albumId, { num skip }) async
test('test downloadArchive', () async {
// TODO
});
//
//
//Future<AlbumCountResponseDto> getAlbumCountByUserId() async
test('test getAlbumCountByUserId', () async {
// TODO
});
//
//
//Future<AlbumResponseDto> getAlbumInfo(String albumId) async
test('test getAlbumInfo', () async {
// TODO
});
//
//
//Future<List<AlbumResponseDto>> getAllAlbums({ bool shared, String assetId }) async
test('test getAllAlbums', () async {
// TODO
});
//
//
//Future<AlbumResponseDto> removeAssetFromAlbum(String albumId, RemoveAssetsDto removeAssetsDto) async
test('test removeAssetFromAlbum', () async {
// TODO
});
//
//
//Future removeUserFromAlbum(String albumId, String userId) async
test('test removeUserFromAlbum', () async {
// TODO
});
//
//
//Future<AlbumResponseDto> updateAlbumInfo(String albumId, UpdateAlbumDto updateAlbumDto) async
test('test updateAlbumInfo', () async {
// TODO

View File

@@ -17,26 +17,36 @@ void main() {
// final instance = APIKeyApi();
group('tests for APIKeyApi', () {
//
//
//Future<APIKeyCreateResponseDto> createKey(APIKeyCreateDto aPIKeyCreateDto) async
test('test createKey', () async {
// TODO
});
//
//
//Future deleteKey(num id) async
test('test deleteKey', () async {
// TODO
});
//
//
//Future<APIKeyResponseDto> getKey(num id) async
test('test getKey', () async {
// TODO
});
//
//
//Future<List<APIKeyResponseDto>> getKeys() async
test('test getKeys', () async {
// TODO
});
//
//
//Future<APIKeyResponseDto> updateKey(num id, APIKeyUpdateDto aPIKeyUpdateDto) async
test('test updateKey', () async {
// TODO

View File

@@ -31,21 +31,36 @@ void main() {
// TODO
});
//
//
//Future<SharedLinkResponseDto> createAssetsSharedLink(CreateAssetsShareLinkDto createAssetsShareLinkDto) async
test('test createAssetsSharedLink', () async {
// TODO
});
//
//
//Future<List<DeleteAssetResponseDto>> deleteAsset(DeleteAssetDto deleteAssetDto) async
test('test deleteAsset', () async {
// TODO
});
//
//
//Future<Object> downloadFile(String assetId, { bool isThumb, bool isWeb }) async
test('test downloadFile', () async {
// TODO
});
//
//
//Future<Object> downloadFiles(DownloadFilesDto downloadFilesDto) async
test('test downloadFiles', () async {
// TODO
});
//
//
//Future<Object> downloadLibrary({ num skip }) async
test('test downloadLibrary', () async {
// TODO
@@ -65,36 +80,50 @@ void main() {
// TODO
});
//
//
//Future<List<AssetResponseDto>> getAssetByTimeBucket(GetAssetByTimeBucketDto getAssetByTimeBucketDto) async
test('test getAssetByTimeBucket', () async {
// TODO
});
//
//
//Future<AssetCountByTimeBucketResponseDto> getAssetCountByTimeBucket(GetAssetCountByTimeBucketDto getAssetCountByTimeBucketDto) async
test('test getAssetCountByTimeBucket', () async {
// TODO
});
//
//
//Future<AssetCountByUserIdResponseDto> getAssetCountByUserId() async
test('test getAssetCountByUserId', () async {
// TODO
});
//
//
//Future<List<String>> getAssetSearchTerms() async
test('test getAssetSearchTerms', () async {
// TODO
});
//
//
//Future<Object> getAssetThumbnail(String assetId, { ThumbnailFormat format }) async
test('test getAssetThumbnail', () async {
// TODO
});
//
//
//Future<List<CuratedLocationsResponseDto>> getCuratedLocations() async
test('test getCuratedLocations', () async {
// TODO
});
//
//
//Future<List<CuratedObjectsResponseDto>> getCuratedObjects() async
test('test getCuratedObjects', () async {
// TODO
@@ -107,11 +136,15 @@ void main() {
// TODO
});
//
//
//Future<List<AssetResponseDto>> searchAsset(SearchAssetDto searchAssetDto) async
test('test searchAsset', () async {
// TODO
});
//
//
//Future<Object> serveFile(String assetId, { bool isThumb, bool isWeb }) async
test('test serveFile', () async {
// TODO
@@ -124,6 +157,15 @@ void main() {
// TODO
});
//
//
//Future<SharedLinkResponseDto> updateAssetsInSharedLink(UpdateAssetsToSharedLinkDto updateAssetsToSharedLinkDto) async
test('test updateAssetsInSharedLink', () async {
// TODO
});
//
//
//Future<AssetFileUploadResponseDto> uploadFile(MultipartFile assetData) async
test('test uploadFile', () async {
// TODO

View File

@@ -17,26 +17,36 @@ void main() {
// final instance = AuthenticationApi();
group('tests for AuthenticationApi', () {
//
//
//Future<AdminSignupResponseDto> adminSignUp(SignUpDto signUpDto) async
test('test adminSignUp', () async {
// TODO
});
//
//
//Future<UserResponseDto> changePassword(ChangePasswordDto changePasswordDto) async
test('test changePassword', () async {
// TODO
});
//
//
//Future<LoginResponseDto> login(LoginCredentialDto loginCredentialDto) async
test('test login', () async {
// TODO
});
//
//
//Future<LogoutResponseDto> logout() async
test('test logout', () async {
// TODO
});
//
//
//Future<ValidateAccessTokenResponseDto> validateAccessToken() async
test('test validateAccessToken', () async {
// TODO

View File

@@ -0,0 +1,42 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for CreateAssetsShareLinkDto
void main() {
// final instance = CreateAssetsShareLinkDto();
group('test CreateAssetsShareLinkDto', () {
// List<String> assetIds (default value: const [])
test('to test the property `assetIds`', () async {
// TODO
});
// String expiredAt
test('to test the property `expiredAt`', () async {
// TODO
});
// bool allowUpload
test('to test the property `allowUpload`', () async {
// TODO
});
// String description
test('to test the property `description`', () async {
// TODO
});
});
}

View File

@@ -31,6 +31,8 @@ void main() {
// TODO
});
//
//
//Future<DeviceInfoResponseDto> upsertDeviceInfo(UpsertDeviceInfoDto upsertDeviceInfoDto) async
test('test upsertDeviceInfo', () async {
// TODO

View File

@@ -17,16 +17,22 @@ void main() {
// final instance = JobApi();
group('tests for JobApi', () {
//
//
//Future<AllJobStatusResponseDto> getAllJobsStatus() async
test('test getAllJobsStatus', () async {
// TODO
});
//
//
//Future<JobStatusResponseDto> getJobStatus(JobId jobId) async
test('test getJobStatus', () async {
// TODO
});
//
//
//Future<num> sendJobCommand(JobId jobId, JobCommandDto jobCommandDto) async
test('test sendJobCommand', () async {
// TODO

View File

@@ -17,26 +17,36 @@ void main() {
// final instance = OAuthApi();
group('tests for OAuthApi', () {
//
//
//Future<LoginResponseDto> callback(OAuthCallbackDto oAuthCallbackDto) async
test('test callback', () async {
// TODO
});
//
//
//Future<OAuthConfigResponseDto> generateConfig(OAuthConfigDto oAuthConfigDto) async
test('test generateConfig', () async {
// TODO
});
//
//
//Future<UserResponseDto> link(OAuthCallbackDto oAuthCallbackDto) async
test('test link', () async {
// TODO
});
//
//
//Future mobileRedirect() async
test('test mobileRedirect', () async {
// TODO
});
//
//
//Future<UserResponseDto> unlink() async
test('test unlink', () async {
// TODO

View File

@@ -17,21 +17,29 @@ void main() {
// final instance = ServerInfoApi();
group('tests for ServerInfoApi', () {
//
//
//Future<ServerInfoResponseDto> getServerInfo() async
test('test getServerInfo', () async {
// TODO
});
//
//
//Future<ServerVersionReponseDto> getServerVersion() async
test('test getServerVersion', () async {
// TODO
});
//
//
//Future<ServerStatsResponseDto> getStats() async
test('test getStats', () async {
// TODO
});
//
//
//Future<ServerPingResponse> pingServer() async
test('test pingServer', () async {
// TODO

View File

@@ -17,26 +17,36 @@ void main() {
// final instance = ShareApi();
group('tests for ShareApi', () {
//
//
//Future<SharedLinkResponseDto> editSharedLink(String id, EditSharedLinkDto editSharedLinkDto) async
test('test editSharedLink', () async {
// TODO
});
//
//
//Future<List<SharedLinkResponseDto>> getAllSharedLinks() async
test('test getAllSharedLinks', () async {
// TODO
});
//
//
//Future<SharedLinkResponseDto> getMySharedLink() async
test('test getMySharedLink', () async {
// TODO
});
//
//
//Future<SharedLinkResponseDto> getSharedLinkById(String id) async
test('test getSharedLinkById', () async {
// TODO
});
//
//
//Future<String> removeSharedLink(String id) async
test('test removeSharedLink', () async {
// TODO

View File

@@ -17,21 +17,29 @@ void main() {
// final instance = SystemConfigApi();
group('tests for SystemConfigApi', () {
//
//
//Future<SystemConfigDto> getConfig() async
test('test getConfig', () async {
// TODO
});
//
//
//Future<SystemConfigDto> getDefaults() async
test('test getDefaults', () async {
// TODO
});
//
//
//Future<SystemConfigTemplateStorageOptionDto> getStorageTemplateOptions() async
test('test getStorageTemplateOptions', () async {
// TODO
});
//
//
//Future<SystemConfigDto> updateConfig(SystemConfigDto systemConfigDto) async
test('test updateConfig', () async {
// TODO

View File

@@ -17,26 +17,36 @@ void main() {
// final instance = TagApi();
group('tests for TagApi', () {
//
//
//Future<TagResponseDto> create(CreateTagDto createTagDto) async
test('test create', () async {
// TODO
});
//
//
//Future delete(String id) async
test('test delete', () async {
// TODO
});
//
//
//Future<List<TagResponseDto>> findAll() async
test('test findAll', () async {
// TODO
});
//
//
//Future<TagResponseDto> findOne(String id) async
test('test findOne', () async {
// TODO
});
//
//
//Future<TagResponseDto> update(String id, UpdateTagDto updateTagDto) async
test('test update', () async {
// TODO

View File

@@ -0,0 +1,27 @@
//
// AUTO-GENERATED FILE, DO NOT MODIFY!
//
// @dart=2.12
// ignore_for_file: unused_element, unused_import
// ignore_for_file: always_put_required_named_parameters_first
// ignore_for_file: constant_identifier_names
// ignore_for_file: lines_longer_than_80_chars
import 'package:openapi/api.dart';
import 'package:test/test.dart';
// tests for UpdateAssetsToSharedLinkDto
void main() {
// final instance = UpdateAssetsToSharedLinkDto();
group('test UpdateAssetsToSharedLinkDto', () {
// List<String> assetIds (default value: const [])
test('to test the property `assetIds`', () async {
// TODO
});
});
}

View File

@@ -17,51 +17,71 @@ void main() {
// final instance = UserApi();
group('tests for UserApi', () {
//
//
//Future<CreateProfileImageResponseDto> createProfileImage(MultipartFile file) async
test('test createProfileImage', () async {
// TODO
});
//
//
//Future<UserResponseDto> createUser(CreateUserDto createUserDto) async
test('test createUser', () async {
// TODO
});
//
//
//Future<UserResponseDto> deleteUser(String userId) async
test('test deleteUser', () async {
// TODO
});
//
//
//Future<List<UserResponseDto>> getAllUsers(bool isAll) async
test('test getAllUsers', () async {
// TODO
});
//
//
//Future<UserResponseDto> getMyUserInfo() async
test('test getMyUserInfo', () async {
// TODO
});
//
//
//Future<Object> getProfileImage(String userId) async
test('test getProfileImage', () async {
// TODO
});
//
//
//Future<UserResponseDto> getUserById(String userId) async
test('test getUserById', () async {
// TODO
});
//
//
//Future<UserCountResponseDto> getUserCount({ bool admin }) async
test('test getUserCount', () async {
// TODO
});
//
//
//Future<UserResponseDto> restoreUser(String userId) async
test('test restoreUser', () async {
// TODO
});
//
//
//Future<UserResponseDto> updateUser(UpdateUserDto updateUserDto) async
test('test updateUser', () async {
// TODO

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: "none"
version: 1.41.0+64
version: 1.42.0+65
environment:
sdk: ">=2.17.0 <3.0.0"

View File

@@ -54,55 +54,9 @@ void main() {
}).toList()
};
group('Asset only list', () {
test('items < itemsPerRow', () {
final assets = testAssets.sublist(0, 2);
final renderList = assetsToRenderList(assets, 3);
expect(renderList.length, 1);
expect(renderList[0].assetRow!.assets.length, 2);
});
test('items = itemsPerRow', () {
final assets = testAssets.sublist(0, 3);
final renderList = assetsToRenderList(assets, 3);
expect(renderList.length, 1);
expect(renderList[0].assetRow!.assets.length, 3);
});
test('items > itemsPerRow', () {
final assets = testAssets.sublist(0, 20);
final renderList = assetsToRenderList(assets, 3);
expect(renderList.length, 7);
expect(renderList[6].assetRow!.assets.length, 2);
});
test('items > itemsPerRow partition 4', () {
final assets = testAssets.sublist(0, 21);
final renderList = assetsToRenderList(assets, 4);
expect(renderList.length, 6);
expect(renderList[5].assetRow!.assets.length, 1);
});
test('items > itemsPerRow check ids', () {
final assets = testAssets.sublist(0, 21);
final renderList = assetsToRenderList(assets, 3);
expect(renderList.length, 7);
expect(renderList[6].assetRow!.assets.length, 3);
expect(renderList[0].assetRow!.assets[0].id, '0');
expect(renderList[1].assetRow!.assets[1].id, '4');
expect(renderList[3].assetRow!.assets[2].id, '11');
expect(renderList[6].assetRow!.assets[2].id, '20');
});
});
group('Test grouped', () {
test('test grouped check months', () {
final renderList = assetGroupsToRenderList(groups, 3);
test('test grouped check months', () async {
final renderList = await RenderList.fromAssetGroups(groups, 3);
// Jan
// Day 1
@@ -115,17 +69,17 @@ void main() {
// Oct
// Day 1
// 15 Assets => 5 Rows
expect(renderList.length, 18);
expect(renderList[0].type, RenderAssetGridElementType.monthTitle);
expect(renderList[0].date.month, 1);
expect(renderList[7].type, RenderAssetGridElementType.monthTitle);
expect(renderList[7].date.month, 2);
expect(renderList[11].type, RenderAssetGridElementType.monthTitle);
expect(renderList[11].date.month, 10);
expect(renderList.elements.length, 18);
expect(renderList.elements[0].type, RenderAssetGridElementType.monthTitle);
expect(renderList.elements[0].date.month, 1);
expect(renderList.elements[7].type, RenderAssetGridElementType.monthTitle);
expect(renderList.elements[7].date.month, 2);
expect(renderList.elements[11].type, RenderAssetGridElementType.monthTitle);
expect(renderList.elements[11].date.month, 10);
});
test('test grouped check types', () {
final renderList = assetGroupsToRenderList(groups, 5);
test('test grouped check types', () async {
final renderList = await RenderList.fromAssetGroups(groups, 5);
// Jan
// Day 1
@@ -155,10 +109,10 @@ void main() {
RenderAssetGridElementType.assetRow
];
expect(renderList.length, types.length);
expect(renderList.elements.length, types.length);
for (int i = 0; i < renderList.length; i++) {
expect(renderList[i].type, types[i]);
for (int i = 0; i < renderList.elements.length; i++) {
expect(renderList.elements[i].type, types[i]);
}
});
});

View File

@@ -8,4 +8,4 @@ COPY start.sh /start.sh
STOPSIGNAL SIGQUIT
CMD /start.sh
ENTRYPOINT ["/start.sh"]

View File

@@ -1,12 +1,12 @@
#! /bin/bash
#! /bin/sh
set -e
export IMMICH_WEB_URL=${IMMICH_WEB_URL:-http://immich-web:3000}
export IMMICH_WEB_URL="${IMMICH_WEB_URL:-http://immich-web:3000}"
IMMICH_WEB_SCHEME=$(echo "$IMMICH_WEB_URL" | grep -Eo '^https?://' || echo "http://")
export IMMICH_WEB_SCHEME
IMMICH_WEB_HOST=$(echo "$IMMICH_WEB_URL" | cut -d '/' -f 3)
export IMMICH_WEB_HOST
export IMMICH_SERVER_URL=${IMMICH_SERVER_URL:-http://immich-server:3001}
export IMMICH_SERVER_URL="${IMMICH_SERVER_URL:-http://immich-server:3001}"
IMMICH_SERVER_SCHEME=$(echo "$IMMICH_WEB_URL" | grep -Eo '^https?://' || echo "http://")
export IMMICH_SERVER_SCHEME
IMMICH_SERVER_HOST=$(echo "$IMMICH_SERVER_URL" | cut -d '/' -f 3)

View File

@@ -6,10 +6,7 @@ module.exports = {
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
extends: ['plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended'],
root: true,
env: {
node: true,

18
server/.prettierignore Normal file
View File

@@ -0,0 +1,18 @@
.DS_Store
node_modules
/build
/package
.env
.env.*
!.env.example
src/api/open-api
*.md
*.json
coverage
dist
**/migrations/**
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

View File

@@ -2,7 +2,7 @@ FROM node:16-alpine3.14 as builder
WORKDIR /usr/src/app
RUN apk add --update-cache build-base python3 libheif vips-dev ffmpeg
RUN apk add --update-cache build-base python3 libheif vips-dev ffmpeg exiftool perl
COPY package.json package-lock.json ./
@@ -21,7 +21,7 @@ FROM node:16-alpine3.14
WORKDIR /usr/src/app
RUN apk add --no-cache libheif vips ffmpeg
RUN apk add --no-cache libheif vips ffmpeg exiftool perl
COPY --from=prod /usr/src/app/node_modules ./node_modules
COPY --from=prod /usr/src/app/dist ./dist

View File

@@ -1,16 +1,24 @@
import { DatabaseModule, SystemConfigEntity, UserEntity } from '@app/database';
import { DomainModule } from '@app/domain';
import { InfraModule, SystemConfigEntity } from '@app/infra';
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ListUsersCommand } from './commands/list-users.command';
import { DisablePasswordLoginCommand, EnablePasswordLoginCommand } from './commands/password-login';
import { PromptPasswordQuestions, ResetAdminPasswordCommand } from './commands/reset-admin-password.command';
@Module({
imports: [DatabaseModule, TypeOrmModule.forFeature([UserEntity, SystemConfigEntity])],
imports: [
DomainModule.register({
imports: [InfraModule],
}),
TypeOrmModule.forFeature([SystemConfigEntity]),
],
providers: [
ResetAdminPasswordCommand,
PromptPasswordQuestions,
EnablePasswordLoginCommand,
DisablePasswordLoginCommand,
ListUsersCommand,
],
})
export class AppModule {}

View File

@@ -0,0 +1,23 @@
import { UserService } from '@app/domain';
import { Command, CommandRunner } from 'nest-commander';
import { CLI_USER } from '../constants';
@Command({
name: 'list-users',
description: 'List Immich users',
})
export class ListUsersCommand extends CommandRunner {
constructor(private userService: UserService) {
super();
}
async run(): Promise<void> {
try {
const users = await this.userService.getAllUsers(CLI_USER, true);
console.dir(users);
} catch (error) {
console.error(error);
console.error('Unable to load users');
}
}
}

View File

@@ -1,4 +1,4 @@
import { SystemConfigEntity, SystemConfigKey } from '@app/database';
import { SystemConfigEntity, SystemConfigKey } from '@app/infra';
import { InjectRepository } from '@nestjs/typeorm';
import axios from 'axios';
import { Command, CommandRunner } from 'nest-commander';
@@ -9,9 +9,7 @@ import { Repository } from 'typeorm';
description: 'Enable password login',
})
export class EnablePasswordLoginCommand extends CommandRunner {
constructor(
@InjectRepository(SystemConfigEntity) private repository: Repository<SystemConfigEntity>, //
) {
constructor(@InjectRepository(SystemConfigEntity) private repository: Repository<SystemConfigEntity>) {
super();
}

View File

@@ -1,40 +1,39 @@
import { UserEntity } from '@app/database';
import { InjectRepository } from '@nestjs/typeorm';
import bcrypt from 'bcrypt';
import { UserResponseDto, UserService } from '@app/domain';
import { Command, CommandRunner, InquirerService, Question, QuestionSet } from 'nest-commander';
import { randomBytes } from 'node:crypto';
import { Repository } from 'typeorm';
@Command({
name: 'reset-admin-password',
description: 'Reset the admin password',
})
export class ResetAdminPasswordCommand extends CommandRunner {
constructor(
private readonly inquirer: InquirerService,
@InjectRepository(UserEntity) private userRepository: Repository<UserEntity>,
) {
constructor(private userService: UserService, private readonly inquirer: InquirerService) {
super();
}
async run(): Promise<void> {
let { password } = await this.inquirer.ask<{ password: string }>('prompt-password', undefined);
password = password || randomBytes(24).toString('base64').replace(/\W/g, '');
const ask = (admin: UserResponseDto) => {
const { id, oauthId, email, firstName, lastName } = admin;
console.log(`Found Admin:
- ID=${id}
- OAuth ID=${oauthId}
- Email=${email}
- Name=${firstName} ${lastName}`);
const hashedPassword = await bcrypt.hash(password, 10);
return this.inquirer.ask<{ password: string }>('prompt-password', undefined).then(({ password }) => password);
};
const user = await this.userRepository.findOne({ where: { isAdmin: true } });
if (!user) {
console.log('Unable to reset password: no admin user.');
return;
try {
const { password, provided } = await this.userService.resetAdminPassword(ask);
if (provided) {
console.log(`The admin password has been updated.`);
} else {
console.log(`The admin password has been updated to:\n${password}`);
}
} catch (error) {
console.error(error);
console.error('Unable to reset admin password');
}
user.password = hashedPassword;
user.shouldChangePassword = true;
await this.userRepository.save(user);
console.log(`New password:\n${password}`);
}
}

View File

@@ -0,0 +1,9 @@
import { AuthUserDto } from '@app/domain';
export const CLI_USER: AuthUserDto = {
id: 'cli',
email: 'cli@immich.app',
isAdmin: true,
isPublicUser: false,
isAllowUpload: true,
};

View File

@@ -1,4 +1,4 @@
import { AlbumEntity, AssetAlbumEntity, UserAlbumEntity } from '@app/database';
import { AlbumEntity, AssetAlbumEntity, UserAlbumEntity } from '@app/infra';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository, SelectQueryBuilder, DataSource, Brackets, Not, IsNull } from 'typeorm';

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