Compare commits
55 Commits
v1.36.2_56
...
v1.38.2_60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1efcac0946 | ||
|
|
415550f16d | ||
|
|
aa554a9e77 | ||
|
|
2876c7ff97 | ||
|
|
651f56370a | ||
|
|
950adeebbf | ||
|
|
4e33a52290 | ||
|
|
f2cc7c2873 | ||
|
|
9c01ca1080 | ||
|
|
09103dc981 | ||
|
|
f096910abc | ||
|
|
242165485d | ||
|
|
e6904ca884 | ||
|
|
5a792cc821 | ||
|
|
0633eaf68c | ||
|
|
40afa3695a | ||
|
|
14889e7d85 | ||
|
|
3bb103c6b6 | ||
|
|
5e680551b9 | ||
|
|
cefdd86b7f | ||
|
|
b8e26a2112 | ||
|
|
58a149990d | ||
|
|
c23b2479f7 | ||
|
|
a97b761eda | ||
|
|
1adf8ff6b6 | ||
|
|
b5a5363a6a | ||
|
|
f91bdc2785 | ||
|
|
db34f2f7fd | ||
|
|
5de8ea162d | ||
|
|
6e2763b72c | ||
|
|
966d99217a | ||
|
|
5d140145c1 | ||
|
|
fcf3b0b672 | ||
|
|
e8bbad6772 | ||
|
|
5f2b75997f | ||
|
|
426ce77f1c | ||
|
|
83c7434eb5 | ||
|
|
99854e90be | ||
|
|
424b11cf50 | ||
|
|
da87b1256c | ||
|
|
a3971543b5 | ||
|
|
a384798779 | ||
|
|
d31eddf32f | ||
|
|
1068c4ad23 | ||
|
|
cbc979263e | ||
|
|
765181bbc0 | ||
|
|
d82dec9773 | ||
|
|
024177515d | ||
|
|
fb3b36a569 | ||
|
|
614743c8f4 | ||
|
|
47f5e4134e | ||
|
|
efa7b3ba54 | ||
|
|
1e9d67ec39 | ||
|
|
80d0ddca9a | ||
|
|
976d347623 |
13
.gitattributes
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
mobile/openapi/**/*.md -diff -merge
|
||||
mobile/openapi/**/*.md linguist-generated=true
|
||||
mobile/openapi/**/*.dart -diff -merge
|
||||
mobile/openapi/**/*.dart linguist-generated=true
|
||||
|
||||
web/src/api/open-api/**/*.md -diff -merge
|
||||
web/src/api/open-api/**/*.md linguist-generated=true
|
||||
|
||||
web/src/api/open-api/**/*.ts -diff -merge
|
||||
web/src/api/open-api/**/*.ts linguist-generated=true
|
||||
|
||||
mobile/openapi/.openapi-generator/FILES -diff -merge
|
||||
mobile/openapi/.openapi-generator/FILES linguist-generated=true
|
||||
8
Makefile
@@ -4,6 +4,9 @@ dev:
|
||||
dev-new:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-new-update:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
dev-update:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
@@ -26,4 +29,7 @@ prod-scale:
|
||||
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
||||
|
||||
api:
|
||||
cd ./server && npm run api:generate
|
||||
cd ./server && npm run api:generate
|
||||
|
||||
attach-server:
|
||||
docker exec -it docker_immich-server_1 sh
|
||||
9
NOTES.md
@@ -1,9 +0,0 @@
|
||||
# TODO
|
||||
|
||||
Server scenario with web
|
||||
|
||||
[ ] 1 user exist without admin right -> make admin on first check
|
||||
|
||||
[ ] 2 users exist without admin right -> ask user to choose which account will be the admin
|
||||
|
||||
[ X ] No users exist -> prompt signup form for Admin
|
||||
@@ -17,6 +17,9 @@
|
||||
<img src="design/immich-screenshots.png" title="Main Screenshot">
|
||||
</a>
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
</p>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
||||
115
README_zh_CN.md
Normal file
@@ -0,0 +1,115 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
|
||||
<a href="https://discord.gg/D8JsnBEuKb">
|
||||
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="design/immich-logo.svg" width="150" title="Login With Custom URL">
|
||||
</p>
|
||||
<h3 align="center">Immich - 高性能的自托管照片和视频备份方案</h3>
|
||||
<p align="center">
|
||||
请注意: 此README不是由Immich团队维护, 这意味着它在某一时间点不会被更新,因为我们是依靠贡献者来更新的。感谢理解。
|
||||
</p>
|
||||
<br/>
|
||||
<a href="https://immich.app">
|
||||
<img src="design/immich-screenshots.png" title="Main Screenshot">
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a>
|
||||
</p>
|
||||
|
||||
|
||||
## 免责声明
|
||||
|
||||
- ⚠️ 本项目正在 **非常活跃** 的开发中。
|
||||
- ⚠️ 可能存在bug或者重大变更。
|
||||
- ⚠️ **不要把本软件作为你存储照片或视频的唯一方式!**
|
||||
|
||||
## 目录
|
||||
|
||||
- [官方文档](https://immich.app/docs/overview/introduction)
|
||||
- [示例](#示例)
|
||||
- [功能特性](#功能特性)
|
||||
- [介绍](https://immich.app/docs/overview/introduction)
|
||||
- [安装](https://immich.app/docs/installation/requirements)
|
||||
- [贡献指南](https://immich.app/docs/contribution-guidelines)
|
||||
- [支持本项目](#support-the-project)
|
||||
- [已知问题](#known-issues)
|
||||
|
||||
## 官方文档
|
||||
|
||||
你可以在 https://immich.app/ 找到包含安装手册的官方文档.
|
||||
## 示例
|
||||
|
||||
你可以在 https://demo.immich.app 访问示例.
|
||||
|
||||
在移动端, 你可以使用 `https://demo.immich.app/api`获取`服务终端链接`
|
||||
|
||||
```bash title="示例认证信息"
|
||||
认证信息
|
||||
邮箱: demo@immich.app
|
||||
密码: demo
|
||||
```
|
||||
|
||||
```
|
||||
规格: 甲骨文免费虚拟机套餐-阿姆斯特丹 4核 2.4Ghz ARM64 CPU, 24GB RAM。
|
||||
```
|
||||
|
||||
# 功能特性
|
||||
|
||||
| 功能特性 | 移动端 | 网页端 |
|
||||
| ------------------------------------------- | ------- | --- |
|
||||
| 上传并查看照片和视频 | 是 | 是 |
|
||||
| 软件运行时自动备份 | 是 | N/A |
|
||||
| 选择需要备份的相册 | 是 | N/A |
|
||||
| 下载照片和视频到本地 | 是 | 是 |
|
||||
| 多用户支持 | 是 | 是 |
|
||||
| 相册 | 是 | 是 |
|
||||
| 共享相册 | 是 | 是 |
|
||||
| 可拖动的快速导航栏 | 是 | 是 |
|
||||
| 支持RAW格式 (HEIC, HEIF, DNG, Apple ProRaw) | 是 | 是 |
|
||||
| 元数据视图 (EXIF, 地图) | 是 | 是 |
|
||||
| 通过元数据、对象和标签进行搜索 | 是 | No |
|
||||
| 管理功能 (用户管理) | N/A | 是 |
|
||||
| 后台备份 | Android | N/A |
|
||||
| 虚拟滚动 | 是 | 是 |
|
||||
| OAuth支持 | 是 | 是 |
|
||||
| 实时照片备份和查看 (仅iOS) | 是 | 是 |
|
||||
|
||||
# 支持本项目
|
||||
|
||||
我已经致力于本项目并且将我会持续更新文档、新增功能和修复问题。但是我不能一个人走下去,所以我需要你给予我走下去的动力。
|
||||
|
||||
就像我主页里面 [selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) 说的一样,这是我和团队的一项艰巨的任务。我希望某一天我能够全职开发本项目,在此我希望你们能够助我梦想成真。
|
||||
|
||||
如果你使用了本项目一段时间,并且觉得上面的话有道理,那么请你按照如下方式帮助我吧。
|
||||
|
||||
## 捐赠
|
||||
|
||||
- [按月捐赠](https://github.com/sponsors/alextran1502) via GitHub Sponsors
|
||||
- [一次捐赠](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via Github Sponsors
|
||||
|
||||
# 已知问题
|
||||
|
||||
## TensorFlow 构建问题
|
||||
|
||||
_这是一个针对于Proxmox的已知问题_
|
||||
|
||||
TensorFlow 不能运行在很旧的CPU架构上, 需要运行在AVX和AVX2指令集的CPU上。如果你在docker-compose的命令行中遇到了 `illegal instruction core dump`的错误, 通过如下命令检查你的CPU flag寄存器然后确保你能够看到`AVX`和`AVX2`的字样:
|
||||
|
||||
```bash
|
||||
more /proc/cpuinfo | grep flags
|
||||
```
|
||||
|
||||
如果你在Proxmox中运行虚拟机, 虚拟机中没有启用flag寄存器。
|
||||
|
||||
你需要在虚拟机的硬件面板中把CPU类型从`kvm64`改为`host`。
|
||||
|
||||
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
|
||||
@@ -13,7 +13,13 @@ sidebar_position: 6
|
||||
|  | Asset was uploaded from this device and is now backed up in the cloud/server and still available in original on the device |
|
||||
|
||||
### How can I sync an existing directory with Immich's server?
|
||||
|
||||
Immich doesn't have the mechanism to sync an existing directory with the server. There is however, a helper CLI tool to help you bulk upload the existing photos and videos to the server. You can find the guide to use the CLI tool [here](/docs/usage/bulk-upload.md).
|
||||
|
||||
### Why doesn't Immich watch an existing photo gallery directory?
|
||||
The initial approach of Immich is to become a backup tool, primarily for mobile device usage. Thus, all the assets must be uploaded from the mobile client. The app was architectured to perform that job well.
|
||||
|
||||
The initial approach of Immich is to become a backup tool, primarily for mobile device usage. Thus, all the assets must be uploaded from the mobile client. The app was architectured to perform that job well.
|
||||
|
||||
### How can I reset the admin password?
|
||||
|
||||
The admin password can be reset by running the [reset-admin-password](/docs/usage/server-commands) command on the immich-server.
|
||||
|
||||
BIN
docs/docs/installation/img/unraid01.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/docs/installation/img/unraid02.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
docs/docs/installation/img/unraid03.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/docs/installation/img/unraid04.png
Normal file
|
After Width: | Height: | Size: 910 B |
BIN
docs/docs/installation/img/unraid05.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/docs/installation/img/unraid06.webp
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/docs/installation/img/unraid07.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
docs/docs/installation/img/unraid08.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
@@ -4,12 +4,104 @@ sidebar_position: 5
|
||||
|
||||
# Unraid
|
||||
|
||||
Install Immich on Unraid.
|
||||
Install Immich on Unraid using the [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps.
|
||||
|
||||
:::info
|
||||
|
||||
- Guide was written using Unraid v6.11.1
|
||||
- Requires you to have installed the plugin: [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/)
|
||||
- An Unraid share created for your images
|
||||
- There has been a [report](https://forums.unraid.net/topic/130006-errortraps-traps-node27707-trap-invalid-opcode-ip14fcfc8d03c0-sp7fff32889dd8-more/#comment-1189395) of this not working if your Unraid server doesn't support AVX _(e.g. using a T610)_
|
||||
|
||||
:::info Community contribution
|
||||
Please follow [this community contributed article](https://mfaz.dev/posts/immich-unraid/) to install Immich on Unraid.
|
||||
:::
|
||||
|
||||
1. Go to "**Plugins**" and click on "**Compose.Manager**"
|
||||
2. Click "**Add New Stack**" and when prompted for a label enter "**Immich**"
|
||||
|
||||
<img
|
||||
src={require('./img/unraid01.webp').default}
|
||||
width="70%"
|
||||
alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
||||
/>
|
||||
|
||||
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
|
||||
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml) file into the Unraid editor
|
||||
<details >
|
||||
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 5.</summary>
|
||||
<ul>
|
||||
<li>Comment out the database service</li>
|
||||
<img
|
||||
src={require('./img/unraid02.png').default}
|
||||
width="50%"
|
||||
alt="Comment out database service in the compose file"
|
||||
/>
|
||||
<li>Comment out the database dependency for <b>each service</b> <i>(example in screenshot below only shows 2 of the services - ensure you do this for all services)</i></li>
|
||||
<img
|
||||
src={require('./img/unraid03.png').default}
|
||||
width="50%"
|
||||
alt="Comment out every reference to the database service in the compose file"
|
||||
/>
|
||||
<li>Comment out the volumes</li>
|
||||
<img
|
||||
src={require('./img/unraid04.png').default}
|
||||
width="20%"
|
||||
alt="Comment out database volume"
|
||||
/>
|
||||
</ul>
|
||||
</details>
|
||||
5. Click "**Save Changes**", you will be promoted to edit stack UI labels, just leave this blank and click "**Ok**"
|
||||
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
|
||||
7. Past the entire contents of the [Immich .env.example](https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example) file into the Unraid editor, then **before saving** edit the following:
|
||||
|
||||
- `JWT_SECRET`: Generate a unique secret and paste the value here > Can be generated by either typing `openssl rand -base64 128` in your terminal or copying from [uuidgenerator](https://www.uuidgenerator.net/version1)
|
||||
- `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION`
|
||||
|
||||
<img
|
||||
src={require('./img/unraid05.webp').default}
|
||||
width="70%"
|
||||
alt="Absolute location of where you want immich images stored"
|
||||
/>
|
||||
|
||||
<details >
|
||||
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 8.</summary>
|
||||
<p>Update the following database variables as relevant to your Postgres container:</p>
|
||||
<ul>
|
||||
<li><code>DB_HOSTNAME</code></li>
|
||||
<li><code>DB_USERNAME</code></li>
|
||||
<li><code>DB_PASSWORD</code></li>
|
||||
<li><code>DB_DATABASE_NAME</code></li>
|
||||
<li><code>DB_PORT</code></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
8. Click "**Save Changes**" followed by "**Compose Up**" and Unraid will begin to create the Immich containers in a popup window. Once complete you will see a message on the popup window stating _"Connection Closed"_. Click "**Done**" and go to the Unraid "**Docker**" page
|
||||
|
||||
> Note: This can take several minutes depending on your Internet speed and Unraid hardware
|
||||
|
||||
9. Once on the Docker page you will see several Immich containers, one of them will be labelled `immich_proxy` and will have a port mapping. Visit the `IP:PORT` displayed in your web browser and you should see the Immich admin setup page.
|
||||
|
||||
<img
|
||||
src={require('./img/unraid06.webp').default}
|
||||
width="80%"
|
||||
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
|
||||
/>
|
||||
|
||||
<details >
|
||||
<summary>Using the Unraid Docker Folders plugin? Click me! Otherwise you're complete!</summary>
|
||||
<p>If you are using the Docker Folders plugin go the Docker tab and select "<b>New Folder</b>".<br />Label it <i>"Immich"</i> and use the logo from the <a href="https://immich.app/">Immich homepage</a> <i>(right click the logo, "Save As", and reupload to Unraid)</i><br />Then simply select all the Immich related containers before clicking "<b>Submit</b>"</p>
|
||||
<img
|
||||
src={require('./img/unraid07.webp').default}
|
||||
width="80%"
|
||||
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
|
||||
/>
|
||||
<img
|
||||
src={require('./img/unraid08.webp').default}
|
||||
width="90%"
|
||||
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
|
||||
/>
|
||||
|
||||
</details>
|
||||
|
||||
:::tip
|
||||
For more information on how to use the application, please refer to the [Post Installation](/docs/usage/post-installation) guide.
|
||||
:::
|
||||
For more information on how to use the application once installed, please refer to the [Post Installation](/docs/usage/post-installation) guide.
|
||||
:::
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"label": "How to use the application",
|
||||
"label": "Usage",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
|
||||
@@ -28,13 +28,13 @@ Before enabling OAuth in Immich, a new client application needs to be configured
|
||||
|
||||
2. Configure Redirect URIs/Origins
|
||||
|
||||
The **Sign-in redirect URIs** should include:
|
||||
The **Sign-in redirect URIs** should include:
|
||||
|
||||
- All URLs that will be used to access the login page of the Immich web client (eg. `http://localhost:2283/auth/login`, `http://192.168.0.200:2283/auth/login`, `https://immich.example.com/auth/login`)
|
||||
- Mobile app redirect URL `app.immich:/`
|
||||
|
||||
* All URLs that will be used to access the login page of the Immich web client (eg. `http://localhost:2283/auth/login`, `http://192.168.0.200:2283/auth/login`, `https://immich.example.com/auth/login`)
|
||||
* Mobile app redirect URL `app.immich:/`
|
||||
|
||||
:::caution
|
||||
You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobile app to work properly.
|
||||
You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobile app to work properly.
|
||||
|
||||
**Authentik example**
|
||||
<img src={require('./img/authentik-redirect.png').default} title="Authentik Redirection URL" width="80%" />
|
||||
@@ -42,17 +42,17 @@ You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobi
|
||||
|
||||
## Enable OAuth
|
||||
|
||||
Once you have a new OAuth client application configured, Immich can be configured using the following environment variables:
|
||||
Once you have a new OAuth client application configured, Immich can be configured using the Administration Settings page, available on the web (Administration -> Settings).
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| Setting | Type | Default | Description |
|
||||
| ------------------- | ------- | -------------------- | ------------------------------------------------------------------------- |
|
||||
| OAUTH_ENABLED | boolean | false | Enable/disable OAuth2 |
|
||||
| OAUTH_ISSUER_URL | URL | (required) | Required. Self-discovery URL for client (from previous step) |
|
||||
| OAUTH_CLIENT_ID | string | (required) | Required. Client ID (from previous step) |
|
||||
| OAUTH_CLIENT_SECRET | string | (required) | Required. Client Secret (previous step) |
|
||||
| OAUTH_SCOPE | string | openid email profile | Full list of scopes to send with the request (space delimited) |
|
||||
| OAUTH_AUTO_REGISTER | boolean | true | When true, will automatically register a user the first time they sign in |
|
||||
| OAUTH_BUTTON_TEXT | string | Login with OAuth | Text for the OAuth button on the web |
|
||||
| OAuth enabled | boolean | false | Enable/disable OAuth2 |
|
||||
| OAuth issuer URL | URL | (required) | Required. Self-discovery URL for client (from previous step) |
|
||||
| OAuth client ID | string | (required) | Required. Client ID (from previous step) |
|
||||
| OAuth client secret | string | (required) | Required. Client Secret (previous step) |
|
||||
| OAuth scope | string | openid email profile | Full list of scopes to send with the request (space delimited) |
|
||||
| OAuth button text | string | Login with OAuth | Text for the OAuth button on the web |
|
||||
| OAuth auto register | boolean | true | When true, will automatically register a user the first time they sign in |
|
||||
|
||||
:::info
|
||||
The Issuer URL should look something like the following, and return a valid json document.
|
||||
@@ -63,14 +63,4 @@ The Issuer URL should look something like the following, and return a valid json
|
||||
The `.well-known/openid-configuration` part of the url is optional and will be automatically added during discovery.
|
||||
:::
|
||||
|
||||
Here is an example of a valid configuration for setting up Immich to use OAuth with Authentik:
|
||||
|
||||
```
|
||||
OAUTH_ENABLED=true
|
||||
OAUTH_ISSUER_URL=http://192.168.0.187:9000/application/o/immich
|
||||
OAUTH_CLIENT_ID=f08f9c5b4f77dcfd3916b1c032336b5544a7b368
|
||||
OAUTH_CLIENT_SECRET=6fe2e697644da6ff6aef73387a457d819018189086fa54b151a6067fbb884e75f7e5c90be16d3c688cf902c6974817a85eab93007d76675041eaead8c39cf5a2
|
||||
OAUTH_BUTTON_TEXT=Login with Authentik
|
||||
```
|
||||
|
||||
[oidc]: https://openid.net/connect/
|
||||
|
||||
25
docs/docs/usage/server-commands.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Server Commands
|
||||
|
||||
The `immich-server` docker image comes preinstalled with an administrative CLI that supports the following commands:
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------------- | ------------------------------------- |
|
||||
| `immich help` | Display help |
|
||||
| `immich reset-admin-password` | Reset the password for the admin user |
|
||||
|
||||
## How to run a command
|
||||
|
||||
To run a command, connect to the container and then execute it. For example:
|
||||
|
||||
```bash
|
||||
docker exec -it immich-server_1 sh
|
||||
|
||||
/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
|
||||
```
|
||||
@@ -30,8 +30,8 @@ const config = {
|
||||
|
||||
presets: [
|
||||
[
|
||||
"classic",
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
"docusaurus-preset-openapi",
|
||||
/** @type {import('docusaurus-preset-openapi').Options} */
|
||||
({
|
||||
docs: {
|
||||
showLastUpdateAuthor: true,
|
||||
@@ -42,6 +42,10 @@ const config = {
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
||||
},
|
||||
api: {
|
||||
path: "../server/immich-openapi-specs.json",
|
||||
routeBasePath: "/docs/api"
|
||||
},
|
||||
// blog: {
|
||||
// showReadingTime: true,
|
||||
// editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
||||
@@ -80,6 +84,11 @@ const config = {
|
||||
position: "right",
|
||||
label: "Documentation",
|
||||
},
|
||||
{
|
||||
to: "/docs/api",
|
||||
position: "right",
|
||||
label: "API"
|
||||
},
|
||||
{ to: "/blog", label: "Blog", position: "right" },
|
||||
{
|
||||
href: "https://github.com/immich-app/immich",
|
||||
|
||||
2742
docs/package-lock.json
generated
@@ -19,9 +19,11 @@
|
||||
"@docusaurus/preset-classic": "2.1.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-preset-openapi": "^0.6.3",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.1.0",
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
config_version: 1.0
|
||||
project_id: ead34689-ec52-41d9-b675-09bc85a6cbd7
|
||||
file_type: json
|
||||
branch: main
|
||||
upload:
|
||||
files:
|
||||
- file: mobile/assets/i18n/en-US.json
|
||||
locale_code: en-US
|
||||
- file: mobile/assets/i18n/de-DE.json
|
||||
locale_code: de-DE
|
||||
- file: mobile/assets/i18n/fr-FR.json
|
||||
locale_code: fr-FR
|
||||
- file: mobile/assets/i18n/it-IT.json
|
||||
locale_code: it-IT
|
||||
- file: mobile/assets/i18n/nl-NL.json
|
||||
locale_code: nl-NL
|
||||
- file: mobile/assets/i18n/ko-KR.json
|
||||
locale_code: ko-KR
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
download:
|
||||
params:
|
||||
export_empty_as: main
|
||||
files:
|
||||
- file: mobile/assets/i18n/en-US.json
|
||||
locale_code: en-US
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
- file: mobile/assets/i18n/de-DE.json
|
||||
locale_code: de-DE
|
||||
- file: mobile/assets/i18n/fr-FR.json
|
||||
@@ -31,5 +24,13 @@ download:
|
||||
locale_code: nl-NL
|
||||
- file: mobile/assets/i18n/ko-KR.json
|
||||
locale_code: ko-KR
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
- file: mobile/assets/i18n/es-ES.json
|
||||
locale_code: es-ES
|
||||
- file: mobile/assets/i18n/fi-FI.json
|
||||
locale_code: fi-FI
|
||||
- file: mobile/assets/i18n/ja-JP.json
|
||||
locale_code: ja-JP
|
||||
- file: mobile/assets/i18n/pt-BR.json
|
||||
locale_code: pt-BR
|
||||
- file: mobile/assets/i18n/pl-PL.json
|
||||
locale_code: pl-PL
|
||||
|
||||
@@ -52,7 +52,7 @@ android {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "app.alextran.immich"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- If you want to read images-->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- If you want to read videos-->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- If you want to read audio-->
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
||||
@@ -54,7 +54,9 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
||||
val requireCharging = args.get(1) as Boolean
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
|
||||
val triggerUpdateDelay = (args.get(2) as Number).toLong()
|
||||
val triggerMaxDelay = (args.get(3) as Number).toLong()
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging, triggerUpdateDelay, triggerMaxDelay)
|
||||
result.success(true)
|
||||
}
|
||||
"disable" -> {
|
||||
|
||||
@@ -37,6 +37,8 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
||||
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
||||
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
||||
const val SHARED_PREF_TRIGGER_UPDATE_DELAY = "triggerUpdateDelay"
|
||||
const val SHARED_PREF_TRIGGER_MAX_DELAY = "triggerMaxDelay"
|
||||
|
||||
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
||||
|
||||
@@ -62,12 +64,16 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
*/
|
||||
fun configureWork(context: Context,
|
||||
requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false) {
|
||||
requireCharging: Boolean = false,
|
||||
triggerUpdateDelay: Long = 5000,
|
||||
triggerMaxDelay: Long = 50000) {
|
||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
||||
.putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay)
|
||||
.putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay)
|
||||
.apply()
|
||||
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
||||
}
|
||||
@@ -106,12 +112,14 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
}
|
||||
|
||||
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
val constraints = Constraints.Builder()
|
||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.setTriggerContentUpdateDelay(5000, TimeUnit.MILLISECONDS)
|
||||
.setTriggerContentUpdateDelay(sp.getLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, 5000), TimeUnit.MILLISECONDS)
|
||||
.setTriggerContentMaxDelay(sp.getLong(SHARED_PREF_TRIGGER_MAX_DELAY, 50000), TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 56,
|
||||
"android.injected.version.name" => "1.36.1",
|
||||
"android.injected.version.code" => 60,
|
||||
"android.injected.version.name" => "1.38.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')
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
* Show human readable file size in detail view
|
||||
* Fix permission issue on Android 33
|
||||
@@ -0,0 +1,6 @@
|
||||
* Use binary prefixes for data sizes
|
||||
* Fix not able to show device asset on Android 13
|
||||
* Use cached asset info if unchanged instead of downloading all assets
|
||||
* Add in-app logging
|
||||
* Add search mechanism to album selection page
|
||||
* Improve UI
|
||||
@@ -0,0 +1 @@
|
||||
* Fixed foreground backup not triggered on app relaunch
|
||||
@@ -0,0 +1,2 @@
|
||||
* Improve data usage on loading asset
|
||||
* Add background backup delay
|
||||
@@ -5,17 +5,17 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000345">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000201">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="123.14891">
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="63.132489">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="39.270764">
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="38.15883">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "EKSKLUDERET",
|
||||
"album_info_card_backup_album_included": "INKLUDERET",
|
||||
"album_thumbnail_card_item": "1 genstand",
|
||||
"album_thumbnail_card_items": "{} genstande",
|
||||
"album_thumbnail_card_shared": ". Delt",
|
||||
"album_viewer_appbar_share_delete": "Slet album",
|
||||
"album_viewer_appbar_share_err_delete": "Fejlede sletning af album",
|
||||
"album_viewer_appbar_share_err_leave": "Fejlede i at forlade album",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Forlad album",
|
||||
"album_viewer_appbar_share_remove": "Fjern fra album",
|
||||
"album_viewer_page_share_add_users": "Tilføj brugere",
|
||||
"asset_list_settings_subtitle": "Indstillinger for billedgitterlayout",
|
||||
"asset_list_settings_title": "Billedgitter",
|
||||
"backup_album_selection_page_albums_device": "Albummer på enhed ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere",
|
||||
"backup_album_selection_page_assets_scatter": "Elementer kan være spredt på tværs af flere albummer. Albummer kan således inkluderes eller udelukkes under sikkerhedskopieringsprocessen.",
|
||||
@@ -16,7 +21,27 @@
|
||||
"backup_album_selection_page_selection_info": "Oplysninger om valgte",
|
||||
"backup_album_selection_page_total_assets": "Samlede unikke elementer",
|
||||
"backup_all": "Alt",
|
||||
"backup_controller_page_albums": "Sikkerhedskopier albummer",
|
||||
"backup_background_service_backup_failed_message": "Backup af billeder og videoer fejlede. Forsøger igen...",
|
||||
"backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igen...",
|
||||
"backup_background_service_current_upload_notification": "Uploader {}",
|
||||
"backup_background_service_default_notification": "Checking for new assets…",
|
||||
"backup_background_service_error_title": "Fejl med backup",
|
||||
"backup_background_service_in_progress_notification": "Tager backup af dine billeder og videoer...",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Sikkerhedskopiér albummer",
|
||||
"backup_controller_page_background_battery_info_link": "Vis mig hvordan",
|
||||
"backup_controller_page_background_battery_info_message": "For den bedste oplevelse med baggrundsbackup, bør du slå batterioptimering, der begrænder baggrundsaktivitet, fra.\n\nSiden dette er afhængigt af enheden, bør du undersøge denne information leveret af din enheds producent.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Batterioptimering",
|
||||
"backup_controller_page_background_charging": "Kun under opladning",
|
||||
"backup_controller_page_background_configure_error": "Fejlede konfigureringen af baggrundsbackup",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Slå baggrundsbackup til, for automatisk at tage backup af nye billeder og videoer, uden at skulle åbne appen",
|
||||
"backup_controller_page_background_is_off": "Automatisk baggrundsbackup er slået fra",
|
||||
"backup_controller_page_background_is_on": "Automatisk baggrundsbackup er slået til",
|
||||
"backup_controller_page_background_turn_off": "Slå baggrundsbackup fra",
|
||||
"backup_controller_page_background_turn_on": "Slå baggrundsbackup til",
|
||||
"backup_controller_page_background_wifi": "Kun med WiFi",
|
||||
"backup_controller_page_backup": "Sikkerhedskopier",
|
||||
"backup_controller_page_backup_selected": "Valgte: ",
|
||||
"backup_controller_page_backup_sub": "Sikkerhedskopierede billeder og videoer",
|
||||
@@ -45,7 +70,27 @@
|
||||
"backup_controller_page_uploading_file_info": "Uploader filinformation",
|
||||
"backup_err_only_album": "Kan ikke slette det eneste album",
|
||||
"backup_info_card_assets": "elementer",
|
||||
"cache_settings_album_thumbnails": "Biblioteksminiaturebilleder ({} billeder og videoer)",
|
||||
"cache_settings_clear_cache_button": "Fjern cache",
|
||||
"cache_settings_clear_cache_button_title": "Fjern appens cache. Dette vil i stor grad påvirke appens ydeevne indtil cachen er genopbygget.",
|
||||
"cache_settings_image_cache_size": "Størrelse af billedecache ({} billeder og videoer)",
|
||||
"cache_settings_statistics_album": "Biblioteksminiaturer",
|
||||
"cache_settings_statistics_assets": "{} billeder og videoer ({})",
|
||||
"cache_settings_statistics_full": "Fulde billeder",
|
||||
"cache_settings_statistics_shared": "Miniaturebilleder til delte albummer",
|
||||
"cache_settings_statistics_thumbnail": "Miniaturebilleder",
|
||||
"cache_settings_statistics_title": "Cacheforbrug",
|
||||
"cache_settings_subtitle": "Håndter cache-adfærden for Immich-appen.",
|
||||
"cache_settings_thumbnail_size": "Størrelse af miniaturebillede cache ({} billeder og videoer)",
|
||||
"cache_settings_title": "Cache-indstillinger",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Slet",
|
||||
"control_bottom_app_bar_share": "Del",
|
||||
"create_album_page_untitled": "Uden titel",
|
||||
"create_shared_album_page_create": "Opret",
|
||||
"create_shared_album_page_share": "Del",
|
||||
"create_shared_album_page_share_add_assets": "TILFØJ ELEMENT",
|
||||
"create_shared_album_page_share_select_photos": "Vælg billeder",
|
||||
@@ -59,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "LOKATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "Under udarbejdelse",
|
||||
"experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter",
|
||||
"experimental_settings_subtitle": "Brug på eget ansvar!",
|
||||
"experimental_settings_title": "Eksperimentelle",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albummer",
|
||||
"library_page_new_album": "Nyt album",
|
||||
"login_form_button_text": "Log ind",
|
||||
"login_form_email_hint": "din-email@email.com",
|
||||
"login_form_endpoint_hint": "http://din-server-ip:port/api",
|
||||
@@ -67,13 +120,17 @@
|
||||
"login_form_err_invalid_email": "Ugyldig email",
|
||||
"login_form_err_leading_whitespace": "Mellemrum før",
|
||||
"login_form_err_trailing_whitespace": "Mellemrum efter",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Der opstod en vejl ved at logge ind. Tjek server URL, email og kodeordet",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Kodeord",
|
||||
"login_form_password_hint": "kodeord",
|
||||
"login_form_save_login": "Forbliv logget ind",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Klient og server er ajour",
|
||||
"profile_drawer_settings": "Indstillinger",
|
||||
"profile_drawer_sign_out": "Log ud",
|
||||
"search_bar_hint": "Søg i dine billeder",
|
||||
"search_page_no_objects": "Ingen elementer er tilgængelige",
|
||||
@@ -84,19 +141,50 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Anbefalinger",
|
||||
"select_user_for_sharing_page_err_album": "Fejlede i at oprette et nyt album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Anbefalinger",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Giv besked om baggrundsbackupfejl: {}",
|
||||
"setting_notifications_notify_hours": "{} timer",
|
||||
"setting_notifications_notify_immediately": "med det samme",
|
||||
"setting_notifications_notify_minutes": "{} minutter",
|
||||
"setting_notifications_notify_never": "aldrig",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detaljeret uploadstatus pr. billed og video",
|
||||
"setting_notifications_single_progress_title": "Vis detaljeret baggrundsuploadstatus",
|
||||
"setting_notifications_subtitle": "Tilpas dine notifikationspræferencer",
|
||||
"setting_notifications_title": "Notifikationer",
|
||||
"setting_notifications_total_progress_subtitle": "Samlet uploadstatus (færdige/samlede billeder og videoer)",
|
||||
"setting_notifications_total_progress_title": "Vis samlet baggrundsuploadstatus",
|
||||
"setting_pages_app_bar_settings": "Indstillinger",
|
||||
"settings_require_restart": "Genstart venligst Immich for at anvende denne ændring",
|
||||
"share_add": "Tilføj",
|
||||
"share_add_photos": "Tilføj billeder",
|
||||
"share_add_title": "Tilføj en titel",
|
||||
"share_create_album": "Opret album",
|
||||
"share_dialog_preparing": "Forbereder...",
|
||||
"share_invite": "Inviter til album",
|
||||
"sharing_page_album": "Delt albums",
|
||||
"sharing_page_description": "Opret delte albummer for at dele billeder og video med personer på dit netværk.",
|
||||
"sharing_page_empty_list": "TOM LISTE",
|
||||
"sharing_silver_appbar_create_shared_album": "Opret delt album",
|
||||
"sharing_silver_appbar_share_partner": "Del med partner",
|
||||
"tab_controller_nav_library": "Bibliotek",
|
||||
"tab_controller_nav_photos": "Billeder",
|
||||
"tab_controller_nav_search": "Søg",
|
||||
"tab_controller_nav_sharing": "Deling",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Vis opbevaringsindikator på filer",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Antal billeder og videoer per række ({})",
|
||||
"theme_setting_dark_mode_switch": "Mørk tilstand",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten i billedfremviseren",
|
||||
"theme_setting_image_viewer_quality_title": "Billedfremviserkvalitet",
|
||||
"theme_setting_system_theme_switch": "Automatisk (Følg systemindstillinger)",
|
||||
"theme_setting_theme_subtitle": "Vælg appens temaindstilling",
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning",
|
||||
"theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til",
|
||||
"version_announcement_overlay_ack": "Vedkend",
|
||||
"version_announcement_overlay_release_notes": "udgivelsesnoter",
|
||||
"version_announcement_overlay_text_1": "Hej vej, der er en ny version af",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Album verlassen",
|
||||
"album_viewer_appbar_share_remove": "Entferne vom Album",
|
||||
"album_viewer_page_share_add_users": "Nutzer hinzufügen",
|
||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||
"asset_list_settings_title": "Photo Grid",
|
||||
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
||||
@@ -19,12 +21,31 @@
|
||||
"backup_album_selection_page_selection_info": "Auswahl",
|
||||
"backup_album_selection_page_total_assets": "Elemente",
|
||||
"backup_all": "Alle",
|
||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_default_notification": "Suche nach neuen assets…",
|
||||
"backup_background_service_error_title": "Backup error",
|
||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Gesicherte Alben",
|
||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_backup": "Sicherung",
|
||||
"backup_controller_page_backup_selected": "Ausgewählt: ",
|
||||
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
|
||||
"backup_controller_page_cancel": "Abbrechen",
|
||||
"backup_background_service_default_notification": "Suche nach neuen assets…",
|
||||
"backup_controller_page_created": "Erstellt: {}",
|
||||
"backup_controller_page_desc_backup": "Aktiviere die Sicherung um Elemente automatisch auf den Server zu laden.",
|
||||
"backup_controller_page_excluded": "Ausgeschlossen: ",
|
||||
@@ -49,6 +70,23 @@
|
||||
"backup_controller_page_uploading_file_info": "Informationen",
|
||||
"backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
|
||||
"backup_info_card_assets": "Elemente",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Vorschaubilder",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Löschen",
|
||||
"control_bottom_app_bar_share": "Teilen",
|
||||
"create_album_page_untitled": "Unbenannt",
|
||||
@@ -66,6 +104,12 @@
|
||||
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "STANDORT",
|
||||
"experimental_settings_new_asset_list_subtitle": "In Arbeit",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimentell",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Alben",
|
||||
"library_page_new_album": "Neues Album",
|
||||
"login_form_button_text": "Anmelden",
|
||||
@@ -76,12 +120,15 @@
|
||||
"login_form_err_invalid_email": "Ungültige E-Mail",
|
||||
"login_form_err_leading_whitespace": "Führendes Leerzichen",
|
||||
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Error logging you in, check server url, email and password",
|
||||
"login_form_label_email": "E-Mail",
|
||||
"login_form_label_password": "Passwort",
|
||||
"login_form_password_hint": "password",
|
||||
"login_form_save_login": "Angemeldet bleiben",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
||||
"profile_drawer_settings": "Einstellungen",
|
||||
"profile_drawer_sign_out": "Abmelden",
|
||||
@@ -94,7 +141,25 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
||||
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detaillierte Upload Informationen für jedes Element.",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Einstellungen",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"share_add": "Hinzufügen",
|
||||
"share_add_photos": "Fotos hinzufügen",
|
||||
"share_add_title": "Titel hinzufügen",
|
||||
@@ -110,6 +175,8 @@
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Suche",
|
||||
"tab_controller_nav_sharing": "Teilen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
||||
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
||||
@@ -124,4 +191,4 @@
|
||||
"version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
|
||||
"version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
|
||||
"version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89"
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
||||
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
||||
"backup_album_selection_page_select_albums": "Select Albums",
|
||||
"backup_album_selection_page_select_albums": "Select albums",
|
||||
"backup_album_selection_page_selection_info": "Selection Info",
|
||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
||||
"backup_all": "All",
|
||||
@@ -35,6 +35,7 @@
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
@@ -82,6 +83,10 @@
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Delete",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
@@ -99,6 +104,12 @@
|
||||
"exif_bottom_sheet_description": "Add Description...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "LOCATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_new_album": "New album",
|
||||
"login_form_button_text": "Login",
|
||||
@@ -109,14 +120,15 @@
|
||||
"login_form_err_invalid_email": "Invalid Email",
|
||||
"login_form_err_leading_whitespace": "Leading whitespace",
|
||||
"login_form_err_trailing_whitespace": "Trailing whitespace",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Password",
|
||||
"login_form_password_hint": "password",
|
||||
"login_form_save_login": "Stay logged in",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||
"profile_drawer_settings": "Settings",
|
||||
"profile_drawer_sign_out": "Sign Out",
|
||||
@@ -129,18 +141,25 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||
"select_user_for_sharing_page_err_album": "Failed to create album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
@@ -171,13 +190,5 @@
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album"
|
||||
}
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "EXCLUIDOS",
|
||||
"album_info_card_backup_album_included": "INCLUIDOS",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_shared": " · Shared",
|
||||
"album_viewer_appbar_share_delete": "Eliminar álbum ",
|
||||
"album_viewer_appbar_share_err_delete": "No ha podido eliminar el álbum",
|
||||
"album_viewer_appbar_share_err_leave": "No ha podido dejar el álbum",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Abandonar álbum ",
|
||||
"album_viewer_appbar_share_remove": "Eliminar del álbum ",
|
||||
"album_viewer_page_share_add_users": "Añadir usuarios",
|
||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||
"asset_list_settings_title": "Photo Grid",
|
||||
"backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({})",
|
||||
"backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir",
|
||||
"backup_album_selection_page_assets_scatter": "Los activos pueden dispersarse en varios álbumes. De este modo, los álbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.",
|
||||
@@ -16,13 +21,37 @@
|
||||
"backup_album_selection_page_selection_info": "Información sobre la Selección",
|
||||
"backup_album_selection_page_total_assets": "Total de activos únicos",
|
||||
"backup_all": "Todos",
|
||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_default_notification": "Checking for new assets…",
|
||||
"backup_background_service_error_title": "Backup error",
|
||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Álbumes de copia de seguridad",
|
||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_backup": "Copia de Seguridad",
|
||||
"backup_controller_page_backup_selected": "Seleccionado:",
|
||||
"backup_controller_page_backup_sub": "Copia de seguridad de fotos y vídeos",
|
||||
"backup_controller_page_cancel": "Cancelar",
|
||||
"backup_controller_page_created": "Created on: {}",
|
||||
"backup_controller_page_desc_backup": "Active la copia de seguridad para cargar automáticamente los nuevos activos al servidor.",
|
||||
"backup_controller_page_excluded": "Excluido:",
|
||||
"backup_controller_page_failed": "Failed ({})",
|
||||
"backup_controller_page_filename": "File name: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "Información de la Copia de Seguridad",
|
||||
"backup_controller_page_none_selected": "Ninguno seleccionado",
|
||||
"backup_controller_page_remainder": "Remanente",
|
||||
@@ -38,9 +67,30 @@
|
||||
"backup_controller_page_total_sub": "Todas las fotos y vídeos únicos de los álbumes seleccionados",
|
||||
"backup_controller_page_turn_off": "Apagar la copia de seguridad",
|
||||
"backup_controller_page_turn_on": "Activar la copia de seguridad",
|
||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||
"backup_err_only_album": "No se puede eliminar el único álbum",
|
||||
"backup_info_card_assets": "activos",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Thumbnails",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Eliminar",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
"create_shared_album_page_create": "Create",
|
||||
"create_shared_album_page_share": "Compartir",
|
||||
"create_shared_album_page_share_add_assets": "AÑADIR ACTIVOS",
|
||||
"create_shared_album_page_share_select_photos": "Seleccionar Fotos",
|
||||
@@ -54,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "Añadir Descripción...",
|
||||
"exif_bottom_sheet_details": "DETALLES",
|
||||
"exif_bottom_sheet_location": "LOCALZACIÓN",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_new_album": "New album",
|
||||
"login_form_button_text": "Iniciar Sesión",
|
||||
"login_form_email_hint": "tucorreo@correo.com",
|
||||
"login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto/api",
|
||||
@@ -62,33 +120,71 @@
|
||||
"login_form_err_invalid_email": "Correo electrónico no válido",
|
||||
"login_form_err_leading_whitespace": "Espacio en blanco inicial",
|
||||
"login_form_err_trailing_whitespace": "Espacio en blanco al final",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_label_email": "Correo",
|
||||
"login_form_label_password": "Contraseña",
|
||||
"login_form_password_hint": "contraseña",
|
||||
"login_form_save_login": "Mantener la sesión iniciada",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "El Cliente y el Servidor están actualizados",
|
||||
"profile_drawer_settings": "Settings",
|
||||
"profile_drawer_sign_out": "Cerrar Sesión",
|
||||
"search_bar_hint": "Busca tus fotos",
|
||||
"search_page_no_objects": "No Objects Info Available",
|
||||
"search_page_no_places": "No hay información de lugares disponibles",
|
||||
"search_page_places": "Lugares",
|
||||
"search_page_things": "Cosas",
|
||||
"search_result_page_new_search_hint": "Nueva Busqueda",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Sugerencias",
|
||||
"select_user_for_sharing_page_err_album": "Fallo al crear el álbum",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Añadir",
|
||||
"share_add_photos": "Añadir fotos",
|
||||
"share_add_title": "Añadir un título",
|
||||
"share_create_album": "Crear álbum",
|
||||
"share_dialog_preparing": "Preparing...",
|
||||
"share_invite": "Invitar al álbum",
|
||||
"sharing_page_album": "Álbumes compartidos",
|
||||
"sharing_page_description": "Crea álbumes compartidos para compartir fotos y vídeos con las personas de tu red.",
|
||||
"sharing_page_empty_list": "LISTA VACIA",
|
||||
"sharing_silver_appbar_create_shared_album": "Crear un álbum compartido",
|
||||
"sharing_silver_appbar_share_partner": "Compartir con el compañero",
|
||||
"tab_controller_nav_library": "Library",
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Buscar",
|
||||
"tab_controller_nav_sharing": "Compartiendo",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Dark mode",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||
"theme_setting_theme_title": "Theme",
|
||||
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
||||
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
||||
"version_announcement_overlay_ack": "Reconocer",
|
||||
"version_announcement_overlay_release_notes": "notas de versión",
|
||||
"version_announcement_overlay_text_1": "Hola amigo, hay una nueva versión de",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "JÄTETTY POIS",
|
||||
"album_info_card_backup_album_included": "SISÄLLYTETTY",
|
||||
"album_thumbnail_card_item": "1 kohde",
|
||||
"album_thumbnail_card_items": "{} kohdetta",
|
||||
"album_thumbnail_card_shared": "Jaettu",
|
||||
"album_viewer_appbar_share_delete": "Poista albumi",
|
||||
"album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui",
|
||||
"album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Poistu albumista",
|
||||
"album_viewer_appbar_share_remove": "Poista albumista",
|
||||
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
|
||||
"asset_list_settings_subtitle": "Kuvaruudukon asettelu",
|
||||
"asset_list_settings_title": "Kuvaruudukko",
|
||||
"backup_album_selection_page_albums_device": "Laitteen albumit ({})",
|
||||
"backup_album_selection_page_albums_tap": "Napauta sisällyttääksesi, kaksoisnapauta jättääksesi pois",
|
||||
"backup_album_selection_page_assets_scatter": "Kohteet voivat olla hajaantuneina useisiin albumeihin. Albumeita voidaan sisällyttää varmuuskopiointiin tai jättää siitä pois.",
|
||||
@@ -16,7 +21,27 @@
|
||||
"backup_album_selection_page_selection_info": "Valintatiedot",
|
||||
"backup_album_selection_page_total_assets": "Uniikkeja kohteita yhteensä",
|
||||
"backup_all": "Kaikki",
|
||||
"backup_background_service_backup_failed_message": "Kohteiden varmuuskopiointi epäonnistui. Yritetään uudelleen...",
|
||||
"backup_background_service_connection_failed_message": "Palvelimeen ei saatu yhteyttä. Yritetään uudelleen...",
|
||||
"backup_background_service_current_upload_notification": "Lähetetään {}",
|
||||
"backup_background_service_default_notification": "Tarkistetaan uusia kohteita...",
|
||||
"backup_background_service_error_title": "Virhe varmuuskopioinnissa",
|
||||
"backup_background_service_in_progress_notification": "Varmuuskopioidaan kohteita...",
|
||||
"backup_background_service_upload_failure_notification": "Lähetys palvelimelle epäonnistui {}",
|
||||
"backup_controller_page_albums": "Varmuuskopioi albumit",
|
||||
"backup_controller_page_background_battery_info_link": "Näytä minulle miten",
|
||||
"backup_controller_page_background_battery_info_message": "Kytke pois päältä kaikki Immichin taustatyöskentelyyn liittyvät akun optimoinnit, jotta varmistat taustavarmuuskopioinnin parhaan mahdollisen toiminnan.\n\nKoska tämä on laitekohtaista, tarkista tarvittavat toimet laitevalmistajan ohjeista.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Akun optimointi",
|
||||
"backup_controller_page_background_charging": "Vain laitteen ollessa kytkettynä laturiin",
|
||||
"backup_controller_page_background_configure_error": "Taustapalvelun asettaminen epäonnistui",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Kytke taustapalvelu päälle varmuuskopioidaksesi uudet kohteet automaattisesti, ilman sovelluksen avaamista",
|
||||
"backup_controller_page_background_is_off": "Automaattinen varmuuskopiointi taustalla on pois päältä",
|
||||
"backup_controller_page_background_is_on": "Automaattinen varmuuskopiointi taustalla on päällä",
|
||||
"backup_controller_page_background_turn_off": "Kytke taustapalvelu pois päältä",
|
||||
"backup_controller_page_background_turn_on": "Kytke taustapalvelu päälle",
|
||||
"backup_controller_page_background_wifi": "Vain WiFi-verkossa",
|
||||
"backup_controller_page_backup": "Varmuuskopioitu",
|
||||
"backup_controller_page_backup_selected": "Valittu:",
|
||||
"backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot",
|
||||
@@ -45,10 +70,33 @@
|
||||
"backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot",
|
||||
"backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna",
|
||||
"backup_info_card_assets": "kohdetta",
|
||||
"cache_settings_album_thumbnails": "Kirjastosivun esikatselukuvat ({} kohdetta)",
|
||||
"cache_settings_clear_cache_button": "Tyhjennä välimuisti",
|
||||
"cache_settings_clear_cache_button_title": "Tyhjennä sovelluksen välimuisti. Tämä vaikuttaa merkittävästi sovelluksen suorituskykyyn, kunnes välimuisti on rakennettu uudelleen.",
|
||||
"cache_settings_image_cache_size": "Kuvien välimuistin koko ({} kohdetta)",
|
||||
"cache_settings_statistics_album": "Kirjaston esikatselukuvat",
|
||||
"cache_settings_statistics_assets": "{} kohdetta ({})",
|
||||
"cache_settings_statistics_full": "Täysikokoiset kuvat",
|
||||
"cache_settings_statistics_shared": "Jaettujen albumien esikatselukuvat",
|
||||
"cache_settings_statistics_thumbnail": "Esikatselukuvat",
|
||||
"cache_settings_statistics_title": "Välimuistin käyttö",
|
||||
"cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttöä",
|
||||
"cache_settings_thumbnail_size": "Esikatselukuvien välimuistin koko ({} kohdetta)",
|
||||
"cache_settings_title": "Välimuistin asetukset",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Poista",
|
||||
"control_bottom_app_bar_share": "Jaa",
|
||||
"create_album_page_untitled": "Nimetön",
|
||||
"create_shared_album_page_create": "Luo",
|
||||
"create_shared_album_page_share": "Jaa",
|
||||
"create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA",
|
||||
"create_shared_album_page_share_select_photos": "Valitse kuvat",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"delete_dialog_alert": "Nämä kohteet poistetaan pysyvästi Immich:stä ja laitteeltasi",
|
||||
"delete_dialog_cancel": "Peruuta",
|
||||
"delete_dialog_ok": "Poista",
|
||||
@@ -56,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "Lisää kuvaus…",
|
||||
"exif_bottom_sheet_details": "TIEDOT",
|
||||
"exif_bottom_sheet_location": "SIJAINTI",
|
||||
"experimental_settings_new_asset_list_subtitle": "Työn alla",
|
||||
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
|
||||
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
|
||||
"experimental_settings_title": "Kokeellinen",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albumit",
|
||||
"library_page_new_album": "Uusi albumi",
|
||||
"login_form_button_text": "Kirjaudu",
|
||||
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
|
||||
"login_form_endpoint_hint": "http://palvelimesi-osoite:portti/api",
|
||||
@@ -64,12 +120,17 @@
|
||||
"login_form_err_invalid_email": "Virheellinen sähköpostiosoite",
|
||||
"login_form_err_leading_whitespace": "Alussa välilyönti",
|
||||
"login_form_err_trailing_whitespace": "Lopussa välilyönti",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähköpostiosoite ja salasana.",
|
||||
"login_form_label_email": "Sähköposti",
|
||||
"login_form_label_password": "Salasana",
|
||||
"login_form_password_hint": "salasana",
|
||||
"login_form_save_login": "Pysy kirjautuneena",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Asiakassovellus ja palvelin ovat ajan tasalla",
|
||||
"profile_drawer_settings": "Asetukset",
|
||||
"profile_drawer_sign_out": "Kirjaudu ulos",
|
||||
"search_bar_hint": "Etsi kuvia",
|
||||
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
|
||||
@@ -80,19 +141,50 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Ehdotukset",
|
||||
"select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui",
|
||||
"select_user_for_sharing_page_share_suggestions": "Ehdotukset",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Ilmoita taustavarmuuskopioinnin epäonnistumisista: {}",
|
||||
"setting_notifications_notify_hours": "{} tunnin välein",
|
||||
"setting_notifications_notify_immediately": "heti",
|
||||
"setting_notifications_notify_minutes": "{} minuutin välein",
|
||||
"setting_notifications_notify_never": "ei koskaan",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Yksityiskohtainen tieto palvelimelle lähettämisen edistymisestä kohteittain",
|
||||
"setting_notifications_single_progress_title": "Näytä taustavarmuuskopioinnin eidstminen",
|
||||
"setting_notifications_subtitle": "Ilmoitusasetusten määrittely",
|
||||
"setting_notifications_title": "Ilmoitukset",
|
||||
"setting_notifications_total_progress_subtitle": "Lähetyksen yleinen edistyminen (kohteita lähetetty/yhteensä)",
|
||||
"setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen",
|
||||
"setting_pages_app_bar_settings": "Asetukset",
|
||||
"settings_require_restart": "Käynnistä Immich uudelleen ottaaksesti tämän asetuksen käyttöön",
|
||||
"share_add": "Lisää",
|
||||
"share_add_photos": "Lisää kuvia",
|
||||
"share_add_title": "Lisää nimi",
|
||||
"share_create_album": "Luo albumi",
|
||||
"share_dialog_preparing": "Valmistellaan...",
|
||||
"share_invite": "Kutsu albumiin",
|
||||
"sharing_page_album": "Jaetut albumit",
|
||||
"sharing_page_description": "Luo jaettuja albumeja jakaaksesi kuvia ja videoita läheisillesi.",
|
||||
"sharing_page_empty_list": "TYHJÄ LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Luo jaettu albumi",
|
||||
"sharing_silver_appbar_share_partner": "Jaa kumppanille",
|
||||
"tab_controller_nav_library": "Kirjasto",
|
||||
"tab_controller_nav_photos": "Kuvat",
|
||||
"tab_controller_nav_search": "Haku",
|
||||
"tab_controller_nav_sharing": "Jakaminen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Näytä tallennustilainidikaattori kohteiden kuvakkeissa",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Kohteiden määrä rivillä ({})",
|
||||
"theme_setting_dark_mode_switch": "Tumma teema",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Säädä kuvien katselun laatua",
|
||||
"theme_setting_image_viewer_quality_title": "Kuvien katseluohjelman laatu",
|
||||
"theme_setting_system_theme_switch": "Automaattinen (seuraa järjestelmän asetusta)",
|
||||
"theme_setting_theme_subtitle": "Valitse sovelluksen teema-asetukset",
|
||||
"theme_setting_theme_title": "Teema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataaminen saattaa parantaa latauksen suorituskykyä, mutta lisää kaistankäyttöä huomattavasti.",
|
||||
"theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön",
|
||||
"version_announcement_overlay_ack": "Tiedostan",
|
||||
"version_announcement_overlay_release_notes": "julkaisutiedoissa",
|
||||
"version_announcement_overlay_text_1": "Hei, kaveri! Uusi palvelinversio on saatavilla sovelluksesta",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Quitter l'album",
|
||||
"album_viewer_appbar_share_remove": "Retirer de l'album",
|
||||
"album_viewer_page_share_add_users": "Ajouter des utilisateurs",
|
||||
"asset_list_settings_subtitle": "Paramètres de disposition de la grille de photos",
|
||||
"asset_list_settings_title": "Grille de photos",
|
||||
"backup_album_selection_page_albums_device": "Albums sur l'appareil ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure",
|
||||
"backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.",
|
||||
@@ -19,7 +21,27 @@
|
||||
"backup_album_selection_page_selection_info": "Informations sur la sélection",
|
||||
"backup_album_selection_page_total_assets": "Total des éléments uniques",
|
||||
"backup_all": "Tout",
|
||||
"backup_background_service_backup_failed_message": "Échec de la sauvegarde des éléments. Nouvelle tentative...",
|
||||
"backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentative...",
|
||||
"backup_background_service_current_upload_notification": "Transfert {}",
|
||||
"backup_background_service_default_notification": "Recherche de nouveaux éléments...",
|
||||
"backup_background_service_error_title": "Erreur de sauvegarde",
|
||||
"backup_background_service_in_progress_notification": "Sauvegarde de vos éléments...",
|
||||
"backup_background_service_upload_failure_notification": "Impossible de transférer {}",
|
||||
"backup_controller_page_albums": "Sauvegarder les albums",
|
||||
"backup_controller_page_background_battery_info_link": "Montrez-moi comment",
|
||||
"backup_controller_page_background_battery_info_message": "Pour une expérience optimale de la sauvegarde en arrière-plan, veuillez désactiver toute optimisation de la batterie limitant l'activité en arrière-plan pour Immich.\n\nÉtant donné que cela est spécifique à chaque appareil, veuillez consulter les informations requises pour le fabricant de votre appareil.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Optimisation de la batterie",
|
||||
"backup_controller_page_background_charging": "Seulement pendant la charge",
|
||||
"backup_controller_page_background_configure_error": "Échec de la configuration du service d'arrière-plan",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Activez le service d'arrière-plan pour sauvegarder automatiquement tous les nouveaux éléments sans avoir à ouvrir l'application.",
|
||||
"backup_controller_page_background_is_off": "La sauvegarde automatique en arrière-plan est désactivée",
|
||||
"backup_controller_page_background_is_on": "La sauvegarde automatique en arrière-plan est activée",
|
||||
"backup_controller_page_background_turn_off": "Désactiver le service d'arrière-plan",
|
||||
"backup_controller_page_background_turn_on": "Activer le service d'arrière-plan",
|
||||
"backup_controller_page_background_wifi": "Uniquement en WiFi",
|
||||
"backup_controller_page_backup": "Sauvegardé",
|
||||
"backup_controller_page_backup_selected": "Sélectionné : ",
|
||||
"backup_controller_page_backup_sub": "Photos et vidéos sauvegardées",
|
||||
@@ -45,9 +67,26 @@
|
||||
"backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés",
|
||||
"backup_controller_page_turn_off": "Désactiver la sauvegarde",
|
||||
"backup_controller_page_turn_on": "Activer la sauvegarde",
|
||||
"backup_controller_page_uploading_file_info": "Envoi d'informations sur le fichier",
|
||||
"backup_controller_page_uploading_file_info": "Transfert des informations du fichier",
|
||||
"backup_err_only_album": "Impossible de retirer le seul album",
|
||||
"backup_info_card_assets": "éléments",
|
||||
"cache_settings_album_thumbnails": "Miniatures de la page bibliothèque ({} éléments)",
|
||||
"cache_settings_clear_cache_button": "Effacer le cache",
|
||||
"cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.",
|
||||
"cache_settings_image_cache_size": "Taille du cache des images ({} éléments)",
|
||||
"cache_settings_statistics_album": "Miniatures de la bibliothèque",
|
||||
"cache_settings_statistics_assets": "{} éléments ({})",
|
||||
"cache_settings_statistics_full": "Images complètes",
|
||||
"cache_settings_statistics_shared": "Miniatures d'albums partagés",
|
||||
"cache_settings_statistics_thumbnail": "Miniatures",
|
||||
"cache_settings_statistics_title": "Utilisation du cache",
|
||||
"cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich",
|
||||
"cache_settings_thumbnail_size": "Taille du cache des miniatures ({} éléments)",
|
||||
"cache_settings_title": "Paramètres de mise en cache",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Supprimer",
|
||||
"control_bottom_app_bar_share": "Partager",
|
||||
"create_album_page_untitled": "Sans titre",
|
||||
@@ -65,6 +104,12 @@
|
||||
"exif_bottom_sheet_description": "Ajouter une description...",
|
||||
"exif_bottom_sheet_details": "DÉTAILS",
|
||||
"exif_bottom_sheet_location": "LOCALISATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "En cours de développement",
|
||||
"experimental_settings_new_asset_list_title": "Activez la grille de photos expérimentale",
|
||||
"experimental_settings_subtitle": "Utilisez à vos dépends !",
|
||||
"experimental_settings_title": "Expérimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_new_album": "Nouvel album",
|
||||
"login_form_button_text": "Connexion",
|
||||
@@ -75,12 +120,15 @@
|
||||
"login_form_err_invalid_email": "Email invalide",
|
||||
"login_form_err_leading_whitespace": "Espace en début de ligne",
|
||||
"login_form_err_trailing_whitespace": "Espace de fin de ligne",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Erreur de connexion, vérifiez l'url du serveur, l'email et le mot de passe",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Mot de passe",
|
||||
"login_form_password_hint": "mot de passe",
|
||||
"login_form_save_login": "Rester connecté",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour",
|
||||
"profile_drawer_settings": "Paramètres",
|
||||
"profile_drawer_sign_out": "Se déconnecter",
|
||||
@@ -93,6 +141,25 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||
"select_user_for_sharing_page_err_album": "Échec de la création de l'album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan : {}",
|
||||
"setting_notifications_notify_hours": "{} heures",
|
||||
"setting_notifications_notify_immediately": "immédiatement",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "jamais",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Informations détaillées sur la progression du transfert par élément",
|
||||
"setting_notifications_single_progress_title": "Afficher la progression du détail de la sauvegarde en arrière-plan",
|
||||
"setting_notifications_subtitle": "Ajustez vos préférences de notification",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Progrès global du transfert (effectué/total des éléments)",
|
||||
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
|
||||
"setting_pages_app_bar_settings": "Paramètres",
|
||||
"settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre",
|
||||
"share_add": "Ajouter",
|
||||
"share_add_photos": "Ajouter des photos",
|
||||
"share_add_title": "Ajouter un titre",
|
||||
@@ -108,6 +175,16 @@
|
||||
"tab_controller_nav_photos": "Photos",
|
||||
"tab_controller_nav_search": "Recherche",
|
||||
"tab_controller_nav_sharing": "Partage",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Afficher l'indicateur de stockage sur les tuiles des éléments",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Nombre d'éléments par ligne ({})",
|
||||
"theme_setting_dark_mode_switch": "Mode sombre",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Ajustez la qualité de la visionneuse d'images détaillées",
|
||||
"theme_setting_image_viewer_quality_title": "Qualité de la visualisation des images",
|
||||
"theme_setting_system_theme_switch": "Automatique (suivre les paramètres du système)",
|
||||
"theme_setting_theme_subtitle": "Choisissez le thème de l'application",
|
||||
"theme_setting_theme_title": "Thème",
|
||||
"theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.",
|
||||
"theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes",
|
||||
"version_announcement_overlay_ack": "Confirmer",
|
||||
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
||||
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"album_thumbnail_card_items": "{} elementi",
|
||||
"album_thumbnail_card_shared": "Condiviso",
|
||||
"album_viewer_appbar_share_delete": "Elimina album ",
|
||||
"album_viewer_appbar_share_err_delete": "Errore nel cancellare l'album ",
|
||||
"album_viewer_appbar_share_err_leave": "Errore nel lasciare l'album ",
|
||||
"album_viewer_appbar_share_err_delete": "Impossibile cancellare l'album ",
|
||||
"album_viewer_appbar_share_err_leave": "Impossibile lasciare l'album ",
|
||||
"album_viewer_appbar_share_err_remove": "Ci sono problemi nel rimuovere oggetti dall'album ",
|
||||
"album_viewer_appbar_share_err_title": "Errore nel cambiare il titolo dell'album ",
|
||||
"album_viewer_appbar_share_err_title": "Impossibile cambiare il titolo dell'album ",
|
||||
"album_viewer_appbar_share_leave": "Lascia album",
|
||||
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
||||
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
||||
@@ -21,22 +21,23 @@
|
||||
"backup_album_selection_page_selection_info": "Informazioni sulla selezione ",
|
||||
"backup_album_selection_page_total_assets": "Numero totale di oggetti unici",
|
||||
"backup_all": "Tutti",
|
||||
"backup_background_service_backup_failed_message": "Impossibile caricare contenuti. Nuovo tentativo…",
|
||||
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Nuovo tentativo…",
|
||||
"backup_background_service_backup_failed_message": "Impossibile caricare i contenuti. Riprovo…",
|
||||
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…",
|
||||
"backup_background_service_current_upload_notification": "Caricamento {}",
|
||||
"backup_background_service_default_notification": "Verifica di nuovi contenuti…",
|
||||
"backup_background_service_default_notification": "Ricerca di nuovi contenuti…",
|
||||
"backup_background_service_error_title": "Errore di Backup",
|
||||
"backup_background_service_in_progress_notification": "Backing dei tuoi contenuti…",
|
||||
"backup_background_service_upload_failure_notification": "Impossibile caricare {}",
|
||||
"backup_controller_page_albums": "Backup Album",
|
||||
"backup_controller_page_background_battery_info_link": "Mostrami come",
|
||||
"backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimisazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.",
|
||||
"backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimizzazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Ottimizzazioni batteria",
|
||||
"backup_controller_page_background_charging": "Solo durante la ricarica",
|
||||
"backup_controller_page_background_configure_error": "Impossibile configurare i servizi in background",
|
||||
"backup_controller_page_background_description": "Abilita i servizi in background per sincronizzare tutti i nuovi contenuti senza la necessità di aprire l'app",
|
||||
"backup_controller_page_background_is_off": "Backup automatico spento",
|
||||
"backup_controller_page_background_delay": "Ritarda il backup di nuovi elementi: {}",
|
||||
"backup_controller_page_background_description": "Abilita i servizi in background per fare il backup di tutti i nuovi contenuti senza la necessità di aprire l'app",
|
||||
"backup_controller_page_background_is_off": "Backup automatico disattivato",
|
||||
"backup_controller_page_background_is_on": "Backup automatico attivo",
|
||||
"backup_controller_page_background_turn_off": "Disabilita servizi in background",
|
||||
"backup_controller_page_background_turn_on": "Abilita servizi in background",
|
||||
@@ -66,12 +67,12 @@
|
||||
"backup_controller_page_total_sub": "Tutte le foto e i video unici caricati dagli album selezionati ",
|
||||
"backup_controller_page_turn_off": "Disattiva backup",
|
||||
"backup_controller_page_turn_on": "Attiva backup ",
|
||||
"backup_controller_page_uploading_file_info": "Info sul file caricato",
|
||||
"backup_controller_page_uploading_file_info": "Caricando informazioni sul file",
|
||||
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
||||
"backup_info_card_assets": "oggetti ",
|
||||
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Cancella cache",
|
||||
"cache_settings_clear_cache_button_title": "Cancella cache app. Questo impatterà sulle prestazioni applicative fino a quando la cache non sarà rigenerata.",
|
||||
"cache_settings_clear_cache_button_title": "Cancella la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.",
|
||||
"cache_settings_image_cache_size": "Dimensione cache foto ({} assets)",
|
||||
"cache_settings_statistics_album": "Anteprime librerie",
|
||||
"cache_settings_statistics_assets": "{} contenuti ({})",
|
||||
@@ -79,9 +80,13 @@
|
||||
"cache_settings_statistics_shared": "Anteprime album condivisi",
|
||||
"cache_settings_statistics_thumbnail": "Anteprime",
|
||||
"cache_settings_statistics_title": "Uso della cache",
|
||||
"cache_settings_subtitle": "Controlla il comportamento della cache",
|
||||
"cache_settings_subtitle": "Controlla il comportamento della cache dell'applicazione mobile immich",
|
||||
"cache_settings_thumbnail_size": "Dimensione cache anteprime ({} assets)",
|
||||
"cache_settings_title": "Impostazioni della Cache",
|
||||
"control_bottom_app_bar_add_to_album": "Aggiungi all'album",
|
||||
"control_bottom_app_bar_album_info": "{} elementi",
|
||||
"control_bottom_app_bar_album_info_shared": "{} elementi · Condivisi",
|
||||
"control_bottom_app_bar_create_new_album": "Crea nuovo album",
|
||||
"control_bottom_app_bar_delete": "Elimina",
|
||||
"control_bottom_app_bar_share": "Condividi",
|
||||
"create_album_page_untitled": "Senza titolo",
|
||||
@@ -99,22 +104,31 @@
|
||||
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
|
||||
"exif_bottom_sheet_details": "DETTAGLI",
|
||||
"exif_bottom_sheet_location": "POSIZIONE",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Attiva griglia di foto sperimentale",
|
||||
"experimental_settings_subtitle": "Usalo a tuo rischio!",
|
||||
"experimental_settings_title": "Sperimentale",
|
||||
"home_page_add_to_album_conflicts": "Aggiunti {added} elementi all'album {album}. {failed} elementi erano già presenti nell'album.",
|
||||
"home_page_add_to_album_success": "Aggiunti {added} elementi all'album {album}",
|
||||
"library_page_albums": "Album",
|
||||
"library_page_new_album": "Nuovo Album",
|
||||
"login_form_button_text": "Login",
|
||||
"login_form_email_hint": "tuaemail@email.com",
|
||||
"login_form_endpoint_hint": "http://tuo-ip-del-server:port/api",
|
||||
"login_form_endpoint_hint": "http://ip-del-tuo-server:port/api",
|
||||
"login_form_endpoint_url": "Server Endpoint URL",
|
||||
"login_form_err_http": "Per favore specificare http:// o https://",
|
||||
"login_form_err_invalid_email": "Email non valida",
|
||||
"login_form_err_leading_whitespace": "Spazio bianco all'inizio ",
|
||||
"login_form_err_trailing_whitespace": "Spazio bianco alla fine",
|
||||
"login_form_err_leading_whitespace": "Whitespace all'inizio ",
|
||||
"login_form_err_trailing_whitespace": "Whitespace alla fine",
|
||||
"login_form_failed_get_oauth_server_config": "Errore di login usando OAuth, controlla l'URL del server",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth non è disponibile su questo server",
|
||||
"login_form_failed_login": "Errore nel login, controlla URL del server e le credenziali (email e password)",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Password",
|
||||
"login_form_password_hint": "password ",
|
||||
"login_form_save_login": "Rimani connesso ",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Client e server sono aggiornati",
|
||||
"profile_drawer_settings": "Impostazioni ",
|
||||
"profile_drawer_sign_out": "Logout",
|
||||
@@ -125,24 +139,35 @@
|
||||
"search_page_things": "Oggetti",
|
||||
"search_result_page_new_search_hint": "Nuova ricerca ",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggerimenti ",
|
||||
"select_user_for_sharing_page_err_album": "Errore nel creare l'album ",
|
||||
"select_user_for_sharing_page_err_album": "Impossibile nel creare l'album ",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggerimenti",
|
||||
"setting_image_viewer_help": "Il visualizzatore dettagliato carica una piccola thumbnail per prima, per poi caricare un immagine di media grandezza (se abilitato). Ed infine carica l'originale (se abilitato).",
|
||||
"setting_image_viewer_original_subtitle": "Abilita per caricare l'immagine originale a risoluzione massima (grande!). Disabilita per ridurre l'utilizzo di banda (sia sul network che nella cache del dispositivo).",
|
||||
"setting_image_viewer_original_title": "Carica l'immagine originale",
|
||||
"setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media.\nDisabilita per caricare direttamente l'immagine originale o usare la thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Carica immagine di preview",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {}",
|
||||
"setting_notifications_notify_hours": "{} Ore",
|
||||
"setting_notifications_notify_immediately": "Immediatamente",
|
||||
"setting_notifications_notify_minutes": "{} Minuti",
|
||||
"setting_notifications_notify_never": "Mai",
|
||||
"setting_notifications_notify_hours": "{} ore",
|
||||
"setting_notifications_notify_immediately": "immediatamente",
|
||||
"setting_notifications_notify_minutes": "{} minuti",
|
||||
"setting_notifications_notify_never": "mai",
|
||||
"setting_notifications_notify_seconds": "{} secondi",
|
||||
"setting_notifications_single_progress_subtitle": "Informazioni dettagliate sul caricamento dell'immagine",
|
||||
"setting_notifications_single_progress_title": "Mostra dettagli del processo di backup in background",
|
||||
"setting_notifications_subtitle": "Cambia le impostazioni di notifica",
|
||||
"setting_notifications_title": "Notifiche",
|
||||
"setting_notifications_total_progress_subtitle": "Progresso di caricamento generale (eseguiti/totale)",
|
||||
"setting_notifications_total_progress_title": "Mostra il progresso di backup totale in background",
|
||||
"setting_pages_app_bar_settings": "Impostazioni",
|
||||
"settings_require_restart": "Si prega di riavviare Immich perché vengano applicate le impostazioni",
|
||||
"share_add": "Aggiungi",
|
||||
"share_add_photos": "Aggiungi foto",
|
||||
"share_add_title": "Aggiungi un titolo ",
|
||||
"share_create_album": "Crea album",
|
||||
"share_dialog_preparing": "Preparo…",
|
||||
"share_invite": "Invitare nell'album ",
|
||||
"share_invite": "Invita nell'album ",
|
||||
"sharing_page_album": "Album condivisi",
|
||||
"sharing_page_description": "Crea un album condiviso per condividere foto e video con persone nel tuo network",
|
||||
"sharing_page_description": "Crea un album condiviso per condividere foto e video con persone sul tuo network",
|
||||
"sharing_page_empty_list": "LISTA VUOTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Crea album condiviso",
|
||||
"sharing_silver_appbar_share_partner": "Condividi con il partner",
|
||||
@@ -155,15 +180,15 @@
|
||||
"theme_setting_dark_mode_switch": "Dark mode",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Cambia la qualità del dettaglio dell'immagine",
|
||||
"theme_setting_image_viewer_quality_title": "Qualità immagine",
|
||||
"theme_setting_system_theme_switch": "Automatico (Vai alle impostazioni di sistema)",
|
||||
"theme_setting_theme_subtitle": "Scegli un'impostazione per il tema",
|
||||
"theme_setting_system_theme_switch": "Automatico (Segue le impostazioni di sistema)",
|
||||
"theme_setting_theme_subtitle": "Scegli un'impostazione per il tema dell'app",
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Il caricamento in 3 stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
||||
"theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
||||
"theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage",
|
||||
"version_announcement_overlay_ack": "Riconosci ",
|
||||
"version_announcement_overlay_ack": "Presa visione",
|
||||
"version_announcement_overlay_release_notes": "note di rilascio ",
|
||||
"version_announcement_overlay_text_1": "Ciao amico, c'è una nuova versione di",
|
||||
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per controllare il",
|
||||
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore nella configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico delle immagini docker.",
|
||||
"version_announcement_overlay_title": "Nuova versione del server disponibile! \uD83C\uDF89"
|
||||
"version_announcement_overlay_text_1": "Ciao, c'è una nuova versione di",
|
||||
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per visitare il",
|
||||
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore di configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico dell'applicativo",
|
||||
"version_announcement_overlay_title": "Nuova versione del server disponibile \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "除外",
|
||||
"album_info_card_backup_album_included": "選択",
|
||||
"album_info_card_backup_album_excluded": "除外中",
|
||||
"album_info_card_backup_album_included": "選択中",
|
||||
"album_thumbnail_card_item": "項目数: 1",
|
||||
"album_thumbnail_card_items": "項目数: {}",
|
||||
"album_thumbnail_card_shared": "共有済み",
|
||||
"album_viewer_appbar_share_delete": "アルバムを削除",
|
||||
"album_viewer_appbar_share_err_delete": "削除に失敗...",
|
||||
"album_viewer_appbar_share_err_leave": "退会に失敗...",
|
||||
@@ -9,17 +12,39 @@
|
||||
"album_viewer_appbar_share_leave": "アルバムから退会",
|
||||
"album_viewer_appbar_share_remove": "アルバムから除外",
|
||||
"album_viewer_page_share_add_users": "ユーザーを追加",
|
||||
"asset_list_settings_subtitle": "グリッドに関する設定",
|
||||
"asset_list_settings_title": "グリッド",
|
||||
"backup_album_selection_page_albums_device": "端末上のアルバム数は {} だよ",
|
||||
"backup_album_selection_page_albums_tap": "タップで選択、ダブルタップで除外だよ",
|
||||
"backup_album_selection_page_assets_scatter": "写真がいろんなアルバムに登録されてる事があるから、アルバムを含めたり除外したりしてどの写真を保存するか選択できるよ。",
|
||||
"backup_album_selection_page_assets_scatter": "同じ写真がいろんなアルバムに登録されてる事があるから、アルバムを含めたり除外したりしてどの写真を保存するか選択できるよ。",
|
||||
"backup_album_selection_page_select_albums": "アルバムを選択",
|
||||
"backup_album_selection_page_selection_info": "選択、又は除外されてるアルバム",
|
||||
"backup_album_selection_page_selection_info": "選択、又は除外されているアルバム",
|
||||
"backup_album_selection_page_total_assets": "選択されたアルバムの写真と動画の数",
|
||||
"backup_all": "全て",
|
||||
"backup_background_service_backup_failed_message": "アップロードに失敗しました。リトライ中",
|
||||
"backup_background_service_connection_failed_message": "サーバーに接続できません。リトライ中",
|
||||
"backup_background_service_current_upload_notification": " {} をアップロード中",
|
||||
"backup_background_service_default_notification": "新しい写真をチェックしています",
|
||||
"backup_background_service_error_title": "バックアップエラー",
|
||||
"backup_background_service_in_progress_notification": "バックアップ中",
|
||||
"backup_background_service_upload_failure_notification": "{} のアップロードに失敗",
|
||||
"backup_controller_page_albums": "アルバム",
|
||||
"backup_controller_page_backup": "バックアップ",
|
||||
"backup_controller_page_backup_selected": "選択されてる:",
|
||||
"backup_controller_page_backup_sub": "バックアップされた写真と動画の数だよ",
|
||||
"backup_controller_page_background_battery_info_link": "方法を見る",
|
||||
"backup_controller_page_background_battery_info_message": "バックグラウンドバックアップが正常に動作するためにImmichに適用されてるバッテリーの最適化と自動調整をオフにしてね。\n\n端末によって方法が変わるから各々調べてね",
|
||||
"backup_controller_page_background_battery_info_ok": "了解",
|
||||
"backup_controller_page_background_battery_info_title": "バッテリーの最適化",
|
||||
"backup_controller_page_background_charging": "充電中のみに行う",
|
||||
"backup_controller_page_background_configure_error": "バックグラウンドサービスの構築に失敗しました",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "バックグラウンドバックアップをオンにしてアプリを開かなくても自動で画像をアップロードするようにします",
|
||||
"backup_controller_page_background_is_off": "自動バックグラウンドバックアップはオフになってます",
|
||||
"backup_controller_page_background_is_on": "自動バックグラウンドバックアップはオンになってます",
|
||||
"backup_controller_page_background_turn_off": "バックグラウンドサービスをオフにする",
|
||||
"backup_controller_page_background_turn_on": "バックグラウンドサービスをオンにする",
|
||||
"backup_controller_page_background_wifi": "WiFi接続中のみに行う",
|
||||
"backup_controller_page_backup": "バックアップ済み",
|
||||
"backup_controller_page_backup_selected": "選択済み:",
|
||||
"backup_controller_page_backup_sub": "バックアップされた写真と動画の数",
|
||||
"backup_controller_page_cancel": "キャンセルするよ",
|
||||
"backup_controller_page_created": "{} に作成されたよ",
|
||||
"backup_controller_page_desc_backup": "ONにすれば自動的に新しい写真などがバックアップされるようになるよ",
|
||||
@@ -29,29 +54,49 @@
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "バックアップ情報",
|
||||
"backup_controller_page_none_selected": "何も選んでないよ",
|
||||
"backup_controller_page_remainder": "リマインダー",
|
||||
"backup_controller_page_remainder_sub": "残りの写真と動画の数だよ",
|
||||
"backup_controller_page_remainder": "残り",
|
||||
"backup_controller_page_remainder_sub": "残りの写真と動画の数",
|
||||
"backup_controller_page_select": "選択",
|
||||
"backup_controller_page_server_storage": "サーバーの容量",
|
||||
"backup_controller_page_start_backup": "バックアップを開始するよ",
|
||||
"backup_controller_page_status_off": "バックアップがOFFになってるよ",
|
||||
"backup_controller_page_status_on": "バックアップがONになってるよ",
|
||||
"backup_controller_page_storage_format": "{}中、 {}を使用中だよ",
|
||||
"backup_controller_page_start_backup": "バックアップを開始",
|
||||
"backup_controller_page_status_off": "バックアップがOFFだよ",
|
||||
"backup_controller_page_status_on": "バックアップがONだよ",
|
||||
"backup_controller_page_storage_format": "使用済み: {}/{}",
|
||||
"backup_controller_page_to_backup": "バックアップされるアルバム",
|
||||
"backup_controller_page_total": "トータル",
|
||||
"backup_controller_page_total_sub": "選択されたアルバムの写真と動画の数だよ",
|
||||
"backup_controller_page_total_sub": "選択されたアルバムの写真と動画の数",
|
||||
"backup_controller_page_turn_off": "バックアップOFF",
|
||||
"backup_controller_page_turn_on": "バックアップON",
|
||||
"backup_controller_page_uploading_file_info": "アップロードされてるファイルに関する情報",
|
||||
"backup_err_only_album": "唯一のアルバムを除外する事はできないよ",
|
||||
"backup_err_only_album": "唯一のアルバムを削除する事はできないよ",
|
||||
"backup_info_card_assets": "写真と動画",
|
||||
"cache_settings_album_thumbnails": "ライブラリのサムネイル ({}枚)",
|
||||
"cache_settings_clear_cache_button": "キャッシュをクリア",
|
||||
"cache_settings_clear_cache_button_title": "キャッシュを削除するけど、キャッシュを作り直すまでアプリのパフォーマンスが著しく低下するよ",
|
||||
"cache_settings_image_cache_size": "キャッシュのサイズ ({}枚) ",
|
||||
"cache_settings_statistics_album": "ライブラリのサムネイル",
|
||||
"cache_settings_statistics_assets": "{} 枚 ({}枚中)",
|
||||
"cache_settings_statistics_full": "フル画像",
|
||||
"cache_settings_statistics_shared": "共有アルバムのサムネイル",
|
||||
"cache_settings_statistics_thumbnail": "サムネイル",
|
||||
"cache_settings_statistics_title": "キャッシュ",
|
||||
"cache_settings_subtitle": "キャッシュの動作を変更できるよ",
|
||||
"cache_settings_thumbnail_size": "サムネイルのキャッシュのサイズ ({}枚)",
|
||||
"cache_settings_title": "キャッシュの設定",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "削除",
|
||||
"control_bottom_app_bar_share": "共有",
|
||||
"create_album_page_untitled": "タイトル無し",
|
||||
"create_shared_album_page_create": "作成",
|
||||
"create_shared_album_page_share": "共有",
|
||||
"create_shared_album_page_share_add_assets": "写真や動画を追加",
|
||||
"create_shared_album_page_share_add_assets": "写真を追加",
|
||||
"create_shared_album_page_share_select_photos": "写真を選択",
|
||||
"daily_title_text_date": "E, MM月 dd日",
|
||||
"daily_title_text_date_year": "E, yyyy年 MM月 dd日",
|
||||
"date_format": "E, MM月 dd日 • hh時mm分",
|
||||
"daily_title_text_date": "MM月 DD日, EE",
|
||||
"daily_title_text_date_year": "yyyy年 MM月 DD日, EE",
|
||||
"date_format": "MM月 DD日, EE • hh時mm分",
|
||||
"delete_dialog_alert": "サーバーからも端末からも永久的に削除されるけど良いの?",
|
||||
"delete_dialog_cancel": "キャンセル",
|
||||
"delete_dialog_ok": "削除",
|
||||
@@ -59,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "概要を追加",
|
||||
"exif_bottom_sheet_details": "詳細な情報",
|
||||
"exif_bottom_sheet_location": "撮影地",
|
||||
"experimental_settings_new_asset_list_subtitle": "進行中",
|
||||
"experimental_settings_new_asset_list_title": "試験的なグリッドを有効",
|
||||
"experimental_settings_subtitle": "試験的だから自己責任でね",
|
||||
"experimental_settings_title": "試験的",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "アルバム",
|
||||
"library_page_new_album": "新しいアルバム",
|
||||
"login_form_button_text": "ログイン",
|
||||
"login_form_email_hint": "example@email.com",
|
||||
"login_form_endpoint_hint": "https://example.com:port/api",
|
||||
@@ -67,36 +120,71 @@
|
||||
"login_form_err_invalid_email": "メールアドレスが有効じゃないよ",
|
||||
"login_form_err_leading_whitespace": "最初に半角スペースが含まれてるよ",
|
||||
"login_form_err_trailing_whitespace": "最後に半角スペースが含まれてるよ",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "ログインエラー。サーバーのURL、メールアドレスとパスワードを再確認してね",
|
||||
"login_form_label_email": "メールアドレス",
|
||||
"login_form_label_password": "パスワード",
|
||||
"login_form_password_hint": "パスワード",
|
||||
"login_form_save_login": "ログインしたままにする",
|
||||
"monthly_title_text_date_format": "yyyy年 MM月",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "サーバーとクライアント、両方最新バージョンだよ",
|
||||
"profile_drawer_settings": "設定",
|
||||
"profile_drawer_sign_out": "サインアウト",
|
||||
"search_bar_hint": "写真を検索",
|
||||
"search_page_no_objects": "被写体に関するデータがないよ",
|
||||
"search_page_no_places": "場所に関するデータがないよ",
|
||||
"search_page_no_objects": "被写体に関するデータがなし",
|
||||
"search_page_no_places": "場所に関するデータがなし",
|
||||
"search_page_places": "撮影地",
|
||||
"search_page_things": "カテゴリ",
|
||||
"search_result_page_new_search_hint": "検索",
|
||||
"select_additional_user_for_sharing_page_suggestions": "ユーザーリスト",
|
||||
"select_user_for_sharing_page_err_album": "アルバム作成に失敗...",
|
||||
"select_user_for_sharing_page_share_suggestions": "ユーザーの一覧だよ",
|
||||
"select_user_for_sharing_page_share_suggestions": "ユーザーの一覧",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "バックアップ失敗の通知: {}",
|
||||
"setting_notifications_notify_hours": "{}時間後",
|
||||
"setting_notifications_notify_immediately": "すぐに行う",
|
||||
"setting_notifications_notify_minutes": "{}分後",
|
||||
"setting_notifications_notify_never": "行わない",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "アップロード中の写真の詳細",
|
||||
"setting_notifications_single_progress_title": "バックグランドバックアップの詳細を表示",
|
||||
"setting_notifications_subtitle": "通知設定を変更する",
|
||||
"setting_notifications_title": "通知",
|
||||
"setting_notifications_total_progress_subtitle": "アップロードの進行状況 (完了済み/全体) ",
|
||||
"setting_notifications_total_progress_title": "バックグラウンドバックアップの進行状況を表示",
|
||||
"setting_pages_app_bar_settings": "設定",
|
||||
"settings_require_restart": "設定の適用にImmichの再起動が必要だよ",
|
||||
"share_add": "追加",
|
||||
"share_add_photos": "写真を追加",
|
||||
"share_add_title": "タイトルを追加",
|
||||
"share_create_album": "アルバムを作成",
|
||||
"share_dialog_preparing": "準備中...ちょっと待ってね",
|
||||
"share_invite": "アルバムに参加",
|
||||
"sharing_page_album": "共有アルバム",
|
||||
"sharing_page_description": "共有アルバムを作成して同じネットワークにいる仲間に写真を共有してみよう!",
|
||||
"sharing_page_empty_list": "誰も居ないね ( T_T)\(^-^ ) ドンマイ",
|
||||
"sharing_page_description": "共有アルバムを作成して同じネットワークにいる人たちに写真を共有してみよう!",
|
||||
"sharing_page_empty_list": "共有アルバムが無いよ",
|
||||
"sharing_silver_appbar_create_shared_album": "共有アルバムを作成",
|
||||
"sharing_silver_appbar_share_partner": "パートナーと共有",
|
||||
"tab_controller_nav_library": "ライブラリ",
|
||||
"tab_controller_nav_photos": "写真",
|
||||
"tab_controller_nav_search": "検索",
|
||||
"tab_controller_nav_sharing": "共有",
|
||||
"theme_setting_asset_list_storage_indicator_title": "ストレージに関する情報を表示",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "一列のの写真の数: {} ",
|
||||
"theme_setting_dark_mode_switch": "ダークモード",
|
||||
"theme_setting_image_viewer_quality_subtitle": "画像ビューワの画質の設定",
|
||||
"theme_setting_image_viewer_quality_title": "画像ビューワ",
|
||||
"theme_setting_system_theme_switch": "自動 (端末の設定を反映) ",
|
||||
"theme_setting_theme_subtitle": "アプリの見た目の設定",
|
||||
"theme_setting_theme_title": "テーマ",
|
||||
"theme_setting_three_stage_loading_subtitle": "三段階読み込みを有効にするとパフォーマンスが改善する可能性があるけど、データ使用量が凄く増えるよ",
|
||||
"theme_setting_three_stage_loading_title": "三段階読み込みをオンにする",
|
||||
"version_announcement_overlay_ack": "了解",
|
||||
"version_announcement_overlay_release_notes": "更新情報",
|
||||
"version_announcement_overlay_text_1": "こんにちは、又はこんばんは!新しい",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "WYKLUCZONE",
|
||||
"album_info_card_backup_album_included": "WŁĄCZONE",
|
||||
"album_thumbnail_card_item": "1 pozycja",
|
||||
"album_thumbnail_card_items": "{} pozycje",
|
||||
"album_thumbnail_card_shared": "Udostępniony",
|
||||
"album_viewer_appbar_share_delete": "Usuń album",
|
||||
"album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu",
|
||||
"album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Opuść album",
|
||||
"album_viewer_appbar_share_remove": "Usuń z albumu",
|
||||
"album_viewer_page_share_add_users": "Dodaj użytkowników",
|
||||
"asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć",
|
||||
"asset_list_settings_title": "Siatka Zdjęć",
|
||||
"backup_album_selection_page_albums_device": "Albumy na urządzeniu ({})",
|
||||
"backup_album_selection_page_albums_tap": "Stuknij, aby włączyć, stuknij dwukrotnie, aby wykluczyć",
|
||||
"backup_album_selection_page_assets_scatter": "Pliki mogą być rozproszone w wielu albumach. Dzięki temu albumy mogą być włączane lub wyłączane podczas procesu tworzenia kopii zapasowej.",
|
||||
@@ -16,13 +21,33 @@
|
||||
"backup_album_selection_page_selection_info": "Info o wyborze",
|
||||
"backup_album_selection_page_total_assets": "Łącznie unikalnych plików",
|
||||
"backup_all": "Wszystkie",
|
||||
"backup_background_service_backup_failed_message": "Nie udało się wykonać kopii zapasowej zasobów. Próbuję ponownie...",
|
||||
"backup_background_service_connection_failed_message": "Nie udało się połączyć z serwerem. Próbuję ponownie...",
|
||||
"backup_background_service_current_upload_notification": "Wysyłanie {}",
|
||||
"backup_background_service_default_notification": "Sprawdzanie nowych zasobów...",
|
||||
"backup_background_service_error_title": "Błąd kopii zapasowej",
|
||||
"backup_background_service_in_progress_notification": "Tworzę kopię twoich zasobów...",
|
||||
"backup_background_service_upload_failure_notification": "Nie udało się przesłać {}",
|
||||
"backup_controller_page_albums": "Backup Albumów",
|
||||
"backup_controller_page_backup": "Backup",
|
||||
"backup_controller_page_background_battery_info_link": "Pokaż mi jak",
|
||||
"backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, należy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPonieważ jest to zależne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Optymalizacja Baterii",
|
||||
"backup_controller_page_background_charging": "Tylko podczas ładowania",
|
||||
"backup_controller_page_background_configure_error": "Nie udało się skonfigurować usługi w tle",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Włącz usługę w tle, aby automatycznie tworzyć kopie zapasowe wszelkich nowych zasobów bez konieczności otwierania aplikacji",
|
||||
"backup_controller_page_background_is_off": "Automatyczna kopia zapasowa w tle jest wyłączona",
|
||||
"backup_controller_page_background_is_on": "Automatyczna kopia zapasowa w tle jest włączona",
|
||||
"backup_controller_page_background_turn_off": "Wyłącz usługę w tle",
|
||||
"backup_controller_page_background_turn_on": "Włącz usługę w tle",
|
||||
"backup_controller_page_background_wifi": "Tylko na WiFi",
|
||||
"backup_controller_page_backup": "Kopia Zapasowa",
|
||||
"backup_controller_page_backup_selected": "Zaznaczone: ",
|
||||
"backup_controller_page_backup_sub": "Tworzenie kopii zapasowych zdjęć i filmów",
|
||||
"backup_controller_page_cancel": "Anuluj",
|
||||
"backup_controller_page_created": "Utworzony na: {}",
|
||||
"backup_controller_page_desc_backup": "Włącz backup, aby automatycznie przesyłać nowe zasoby na serwer.",
|
||||
"backup_controller_page_desc_backup": "Włącz kopię zapasową, aby automatycznie przesyłać nowe zasoby na serwer.",
|
||||
"backup_controller_page_excluded": "Wykluczone: ",
|
||||
"backup_controller_page_failed": "Nieudane ({})",
|
||||
"backup_controller_page_filename": "Nazwa pliku: {} [{}]",
|
||||
@@ -33,21 +58,41 @@
|
||||
"backup_controller_page_remainder_sub": "Pozostałe zdjęcia i albumy do wykonania kopii zapasowej z wyboru",
|
||||
"backup_controller_page_select": "Zaznacz",
|
||||
"backup_controller_page_server_storage": "Pamięć Serwera",
|
||||
"backup_controller_page_start_backup": "Rozpocznij Backup",
|
||||
"backup_controller_page_status_off": "Backup jest wyłączony",
|
||||
"backup_controller_page_status_on": "Backup jest włączony",
|
||||
"backup_controller_page_start_backup": "Rozpocznij Kopię Zapasową",
|
||||
"backup_controller_page_status_off": "Kopia Zapasowa jest wyłaczona",
|
||||
"backup_controller_page_status_on": "Kopia Zapasowa jest włączona",
|
||||
"backup_controller_page_storage_format": "{} z {} wykorzystanych",
|
||||
"backup_controller_page_to_backup": "Albumy do backupu",
|
||||
"backup_controller_page_total": "Łącznie",
|
||||
"backup_controller_page_total_sub": "Wszystkie unikalne zdjęcia i filmy z wybranych albumów",
|
||||
"backup_controller_page_turn_off": "Wyłącz Backup",
|
||||
"backup_controller_page_turn_on": "Włącz Backup",
|
||||
"backup_controller_page_turn_off": "Wyłącz Kopię Zapasową",
|
||||
"backup_controller_page_turn_on": "Włącz Kopię Zapasową",
|
||||
"backup_controller_page_uploading_file_info": "Przesyłanie informacji o pliku",
|
||||
"backup_err_only_album": "Nie można usunąć tylko i wyłącznie albumu",
|
||||
"backup_info_card_assets": "pliki",
|
||||
"backup_info_card_assets": "zasoby",
|
||||
"cache_settings_album_thumbnails": "Miniatury stron bibliotek ({} zasobów)",
|
||||
"cache_settings_clear_cache_button": "Wyczyść Cache",
|
||||
"cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopóki pamięć podręczna nie zostanie odbudowana.",
|
||||
"cache_settings_image_cache_size": "Rozmiar pamięci podręcznej obrazów ({} zasobów)",
|
||||
"cache_settings_statistics_album": "Biblioteka miniatur",
|
||||
"cache_settings_statistics_assets": "{} zasoby ({})",
|
||||
"cache_settings_statistics_full": "Pełne Zdjęcia",
|
||||
"cache_settings_statistics_shared": "Udostępnione miniatury albumów",
|
||||
"cache_settings_statistics_thumbnail": "Miniatury",
|
||||
"cache_settings_statistics_title": "Użycie Cache",
|
||||
"cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich",
|
||||
"cache_settings_thumbnail_size": "Rozmiar pamięci podręcznej miniatur ({} zasobów)",
|
||||
"cache_settings_title": "Ustawienia Buforowania",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Usuń",
|
||||
"control_bottom_app_bar_share": "Udostępnij",
|
||||
"create_album_page_untitled": "Bez tytułu",
|
||||
"create_shared_album_page_create": "Utwórz",
|
||||
"create_shared_album_page_share": "Udostępnij",
|
||||
"create_shared_album_page_share_add_assets": "DODAJ PLIKI",
|
||||
"create_shared_album_page_share_add_assets": "DODAJ ZASOBY",
|
||||
"create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
@@ -56,24 +101,36 @@
|
||||
"delete_dialog_cancel": "Anuluj",
|
||||
"delete_dialog_ok": "Usuń",
|
||||
"delete_dialog_title": "Usuń trwale",
|
||||
"exif_bottom_sheet_description": "Dodaj opis...",
|
||||
"exif_bottom_sheet_description": "Dodaj Opis...",
|
||||
"exif_bottom_sheet_details": "SZCZEGÓŁY",
|
||||
"exif_bottom_sheet_location": "LOKALIZACJA",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albumy",
|
||||
"library_page_new_album": "Nowy album",
|
||||
"login_form_button_text": "Login",
|
||||
"login_form_email_hint": "twojmail@email.com",
|
||||
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",
|
||||
"login_form_endpoint_url": "URL Serwera",
|
||||
"login_form_err_http": "Proszę określić http:// lub https://",
|
||||
"login_form_err_invalid_email": "Niepoprawny emaill",
|
||||
"login_form_err_invalid_email": "Niepoprawny Email",
|
||||
"login_form_err_leading_whitespace": "Białe znaki",
|
||||
"login_form_err_trailing_whitespace": "Białe znaki po przecinku",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło.",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Hasło",
|
||||
"login_form_password_hint": "hasło",
|
||||
"login_form_save_login": "Pozostań zalogowany",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne",
|
||||
"profile_drawer_settings": "Ustawienia",
|
||||
"profile_drawer_sign_out": "Wyloguj się",
|
||||
"search_bar_hint": "Szukaj swoich zdjęć",
|
||||
"search_page_no_objects": "Brak informacji o obiektach",
|
||||
@@ -84,20 +141,51 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Propozycje",
|
||||
"select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu",
|
||||
"select_user_for_sharing_page_share_suggestions": "Propozycje",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
||||
"setting_notifications_notify_hours": "{} godzin",
|
||||
"setting_notifications_notify_immediately": "natychmiast",
|
||||
"setting_notifications_notify_minutes": "{} minut",
|
||||
"setting_notifications_notify_never": "nigdy",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Dostosuj preferencje powiadomień",
|
||||
"setting_notifications_title": "Powiadomienia",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Ustawienia",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj zdjęcia",
|
||||
"share_add_title": "Dodaj tytuł",
|
||||
"share_create_album": "Utwórz album",
|
||||
"share_dialog_preparing": "Przygotowywanie...",
|
||||
"share_invite": "Zaproś do albumu",
|
||||
"sharing_page_album": "Udostępnione albumy",
|
||||
"sharing_page_description": "Twórz wspóldzielone albumy, aby udostępniać zdjęcia i filmy osobom w sieci.",
|
||||
"sharing_page_empty_list": "PUSTA LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album",
|
||||
"sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi",
|
||||
"tab_controller_nav_library": "Biblioteka",
|
||||
"tab_controller_nav_photos": "Zdjęcia",
|
||||
"tab_controller_nav_search": "Szukaj",
|
||||
"tab_controller_nav_sharing": "Udostępnianie",
|
||||
"version_announcement_overlay_ack": "Potwierdzenie",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Ciemny Motyw",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Dostosuj jakość podglądu szczegółowości",
|
||||
"theme_setting_image_viewer_quality_title": "Jakość przeglądania obrazów",
|
||||
"theme_setting_system_theme_switch": "Automatyczny (Postępuj zgodnie z ustawieniami systemu)",
|
||||
"theme_setting_theme_subtitle": "Wybierz ustawienia motywu aplikacji",
|
||||
"theme_setting_theme_title": "Motyw",
|
||||
"theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci",
|
||||
"theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania",
|
||||
"version_announcement_overlay_ack": "Potwierdzam",
|
||||
"version_announcement_overlay_release_notes": "informacje o wydaniu",
|
||||
"version_announcement_overlay_text_1": "Cześć przyjacielu, jest nowe wydanie",
|
||||
"version_announcement_overlay_text_2": "prosimy o poświęcenie czasu na odwiedzenie ",
|
||||
|
||||
BIN
mobile/fonts/Inconsolata-Regular.ttf
Normal file
2
mobile/ios/.gitignore
vendored
@@ -31,4 +31,4 @@ Runner/GeneratedPluginRegistrant.*
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
!default.perspectivev3
|
||||
@@ -360,7 +360,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 72;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
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 = 72;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
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 = 72;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.36.1</string>
|
||||
<string>1.37.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>72</string>
|
||||
<string>74</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true />
|
||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.36.1"
|
||||
version_number: "1.38.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -5,32 +5,29 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000358">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000334">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.721922">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="1.671363">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="6.015111">
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="7.167423">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.656945">
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.654653">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="75.686541">
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="29.319346">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="68.644406">
|
||||
<failure message="/usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:229:in `chdir' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:229:in `execute_action' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing' Fastfile:27:in `block (2 levels) in parsing_binding' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/lane.rb:33:in `call' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:49:in `block in execute' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:45:in `chdir' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:45:in `execute' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/commands_generator.rb:354:in `run' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/commands_generator.rb:43:in `start' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/bin/fastlane:23:in `<top (required)>' /usr/local/Cellar/fastlane/2.210.1/libexec/bin/fastlane:25:in `load' /usr/local/Cellar/fastlane/2.210.1/libexec/bin/fastlane:25:in `<main>' Error building the application - see the log above" />
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
|
||||
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
||||
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
||||
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
||||
const String assetEtagKey = 'immichAssetEtagKey'; // Key 5
|
||||
|
||||
// Login Info
|
||||
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
||||
@@ -25,7 +26,11 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box
|
||||
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
||||
const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2
|
||||
const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3
|
||||
const String backupTriggerDelay = "immichBackupTriggerDelay"; // Key 4
|
||||
|
||||
// Duplicate asset
|
||||
const String duplicatedAssetsBox = "immichDuplicatedAssetsBox"; // Box
|
||||
const String duplicatedAssetsKey = "immichDuplicatedAssetsKey"; // Key 1
|
||||
|
||||
// In app logger
|
||||
const String immichLoggerBox = "immichInAppLogger"; // Box
|
||||
@@ -16,11 +16,13 @@ import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.d
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
||||
@@ -31,8 +33,10 @@ void main() async {
|
||||
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
||||
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
||||
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
||||
Hive.registerAdapter(ImmichLoggerMessageAdapter());
|
||||
|
||||
await Future.wait([
|
||||
Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
|
||||
Hive.openBox(userInfoBox),
|
||||
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
|
||||
Hive.openBox(hiveGithubReleaseInfoBox),
|
||||
@@ -58,6 +62,9 @@ void main() async {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Immich Logger Service
|
||||
ImmichLogger().init();
|
||||
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
supportedLocales: locales,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -21,8 +19,38 @@ class AlbumThumbnailCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var box = Hive.box(userInfoBox);
|
||||
var cardSize = MediaQuery.of(context).size.width / 2 - 18;
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
final cardSize = MediaQuery.of(context).size.width / 2 - 18;
|
||||
buildEmptyThumbnail() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
|
||||
),
|
||||
child: SizedBox(
|
||||
height: cardSize,
|
||||
width: cardSize,
|
||||
child: const Center(
|
||||
child: Icon(Icons.no_photography),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buildAlbumThumbnail() {
|
||||
return CachedNetworkImage(
|
||||
width: cardSize,
|
||||
height: cardSize,
|
||||
fit: BoxFit.cover,
|
||||
fadeInDuration: const Duration(milliseconds: 200),
|
||||
imageUrl: getAlbumThumbnailUrl(
|
||||
album,
|
||||
type: ThumbnailFormat.JPEG,
|
||||
),
|
||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
||||
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
@@ -35,19 +63,9 @@ class AlbumThumbnailCard extends StatelessWidget {
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: CachedNetworkImage(
|
||||
memCacheHeight: max(400, cardSize.toInt() * 3),
|
||||
width: cardSize,
|
||||
height: cardSize,
|
||||
fit: BoxFit.cover,
|
||||
fadeInDuration: const Duration(milliseconds: 200),
|
||||
imageUrl:
|
||||
getAlbumThumbnailUrl(album, type: ThumbnailFormat.JPEG),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
cacheKey: "${album.albumThumbnailAssetId}",
|
||||
),
|
||||
child: album.albumThumbnailAssetId == null
|
||||
? buildEmptyThumbnail()
|
||||
: buildAlbumThumbnail(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
|
||||
@@ -42,10 +42,9 @@ class SharingPage extends HookConsumerWidget {
|
||||
child: CachedNetworkImage(
|
||||
width: 60,
|
||||
height: 60,
|
||||
memCacheHeight: 200,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: getAlbumThumbnailUrl(album),
|
||||
cacheKey: album.albumThumbnailAssetId,
|
||||
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:immich_mobile/shared/models/asset.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 {
|
||||
final Asset assetDetail;
|
||||
@@ -162,7 +163,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
),
|
||||
subtitle: exifInfo.exifImageHeight != null
|
||||
? Text(
|
||||
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${exifInfo.fileSizeInByte!}B ",
|
||||
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${formatBytes(exifInfo.fileSizeInByte!)} ",
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -178,7 +179,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(
|
||||
"ƒ/${exifInfo.fNumber} 1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)} ${exifInfo.focalLength}mm ISO${exifInfo.iso} ",
|
||||
"ƒ/${exifInfo.fNumber} 1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -20,10 +20,10 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool allowMoving = _status == _RemoteImageStatus.full;
|
||||
final bool forbidZoom = _status == _RemoteImageStatus.thumbnail;
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: !allowMoving,
|
||||
ignoring: forbidZoom,
|
||||
child: Listener(
|
||||
onPointerMove: handleSwipUpDown,
|
||||
child: PhotoView(
|
||||
@@ -115,7 +115,7 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
|
||||
_thumbnailProvider = _authorizedImageProvider(
|
||||
getThumbnailUrl(widget.asset.remote!),
|
||||
widget.asset.id,
|
||||
getThumbnailCacheKey(widget.asset.remote!),
|
||||
);
|
||||
_imageProvider = _thumbnailProvider;
|
||||
|
||||
@@ -128,10 +128,10 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
}),
|
||||
);
|
||||
|
||||
if (widget.threeStageLoading) {
|
||||
if (widget.loadPreview) {
|
||||
_previewProvider = _authorizedImageProvider(
|
||||
getThumbnailUrl(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
||||
"${widget.asset.id}_previewStage",
|
||||
getThumbnailCacheKey(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
||||
);
|
||||
_previewProvider.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||
@@ -140,15 +140,17 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
);
|
||||
}
|
||||
|
||||
_fullProvider = _authorizedImageProvider(
|
||||
getImageUrl(widget.asset.remote!),
|
||||
"${widget.asset.id}_fullStage",
|
||||
);
|
||||
_fullProvider.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||
_performStateTransition(_RemoteImageStatus.full, _fullProvider);
|
||||
}),
|
||||
);
|
||||
if (widget.loadOriginal) {
|
||||
_fullProvider = _authorizedImageProvider(
|
||||
getImageUrl(widget.asset.remote!),
|
||||
getImageCacheKey(widget.asset.remote!),
|
||||
);
|
||||
_fullProvider.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||
_performStateTransition(_RemoteImageStatus.full, _fullProvider);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -178,7 +180,8 @@ class RemotePhotoView extends StatefulWidget {
|
||||
Key? key,
|
||||
required this.asset,
|
||||
required this.authToken,
|
||||
required this.threeStageLoading,
|
||||
required this.loadPreview,
|
||||
required this.loadOriginal,
|
||||
required this.isZoomedFunction,
|
||||
required this.isZoomedListener,
|
||||
required this.onSwipeDown,
|
||||
@@ -187,7 +190,8 @@ class RemotePhotoView extends StatefulWidget {
|
||||
|
||||
final Asset asset;
|
||||
final String authToken;
|
||||
final bool threeStageLoading;
|
||||
final bool loadPreview;
|
||||
final bool loadOriginal;
|
||||
final void Function() onSwipeDown;
|
||||
final void Function() onSwipeUp;
|
||||
final void Function() isZoomedFunction;
|
||||
|
||||
@@ -31,8 +31,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final Box<dynamic> box = Hive.box(userInfoBox);
|
||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||
final threeStageLoading = useState(false);
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||
final isZoomed = useState<bool>(false);
|
||||
final indexOfAsset = useState(assetList.indexOf(asset));
|
||||
final isPlayingMotionVideo = useState(false);
|
||||
@@ -43,8 +44,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
threeStageLoading.value = appSettingService
|
||||
.getSetting<bool>(AppSettingsEnum.threeStageLoading);
|
||||
isLoadPreview.value =
|
||||
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
|
||||
isLoadOriginal.value =
|
||||
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
|
||||
isPlayingMotionVideo.value = false;
|
||||
return null;
|
||||
},
|
||||
@@ -140,7 +143,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
isZoomedListener: isZoomedListener,
|
||||
asset: assetList[index],
|
||||
heroTag: assetList[index].id,
|
||||
threeStageLoading: threeStageLoading.value,
|
||||
loadPreview: isLoadPreview.value,
|
||||
loadOriginal: isLoadOriginal.value,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
||||
final String authToken;
|
||||
final ValueNotifier<bool> isZoomedListener;
|
||||
final void Function() isZoomedFunction;
|
||||
final bool threeStageLoading;
|
||||
final bool loadPreview;
|
||||
final bool loadOriginal;
|
||||
|
||||
ImageViewerPage({
|
||||
Key? key,
|
||||
@@ -26,7 +27,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
||||
required this.authToken,
|
||||
required this.isZoomedFunction,
|
||||
required this.isZoomedListener,
|
||||
required this.threeStageLoading,
|
||||
required this.loadPreview,
|
||||
required this.loadOriginal,
|
||||
}) : super(key: key);
|
||||
|
||||
Asset? assetDetail;
|
||||
@@ -74,7 +76,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
||||
child: RemotePhotoView(
|
||||
asset: asset,
|
||||
authToken: authToken,
|
||||
threeStageLoading: threeStageLoading,
|
||||
loadPreview: loadPreview,
|
||||
loadOriginal: loadOriginal,
|
||||
isZoomedFunction: isZoomedFunction,
|
||||
isZoomedListener: isZoomedListener,
|
||||
onSwipeDown: () => AutoRouter.of(context).pop(),
|
||||
|
||||
@@ -86,6 +86,8 @@ class BackgroundService {
|
||||
Future<bool> configureService({
|
||||
bool requireUnmetered = true,
|
||||
bool requireCharging = false,
|
||||
int triggerUpdateDelay = 5000,
|
||||
int triggerMaxDelay = 50000,
|
||||
}) async {
|
||||
if (!Platform.isAndroid) {
|
||||
return true;
|
||||
@@ -93,7 +95,12 @@ class BackgroundService {
|
||||
try {
|
||||
final bool ok = await _foregroundChannel.invokeMethod(
|
||||
'configure',
|
||||
[requireUnmetered, requireCharging],
|
||||
[
|
||||
requireUnmetered,
|
||||
requireCharging,
|
||||
triggerUpdateDelay,
|
||||
triggerMaxDelay
|
||||
],
|
||||
);
|
||||
return ok;
|
||||
} catch (error) {
|
||||
@@ -349,7 +356,6 @@ class BackgroundService {
|
||||
Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
|
||||
Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
|
||||
]);
|
||||
|
||||
ApiService apiService = ApiService();
|
||||
apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey));
|
||||
apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey));
|
||||
|
||||
@@ -18,6 +18,7 @@ class BackUpState {
|
||||
final bool backgroundBackup;
|
||||
final bool backupRequireWifi;
|
||||
final bool backupRequireCharging;
|
||||
final int backupTriggerDelay;
|
||||
|
||||
/// All available albums on the device
|
||||
final List<AvailableAlbum> availableAlbums;
|
||||
@@ -42,6 +43,7 @@ class BackUpState {
|
||||
required this.backgroundBackup,
|
||||
required this.backupRequireWifi,
|
||||
required this.backupRequireCharging,
|
||||
required this.backupTriggerDelay,
|
||||
required this.availableAlbums,
|
||||
required this.selectedBackupAlbums,
|
||||
required this.excludedBackupAlbums,
|
||||
@@ -59,6 +61,7 @@ class BackUpState {
|
||||
bool? backgroundBackup,
|
||||
bool? backupRequireWifi,
|
||||
bool? backupRequireCharging,
|
||||
int? backupTriggerDelay,
|
||||
List<AvailableAlbum>? availableAlbums,
|
||||
Set<AvailableAlbum>? selectedBackupAlbums,
|
||||
Set<AvailableAlbum>? excludedBackupAlbums,
|
||||
@@ -76,6 +79,7 @@ class BackUpState {
|
||||
backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi,
|
||||
backupRequireCharging:
|
||||
backupRequireCharging ?? this.backupRequireCharging,
|
||||
backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay,
|
||||
availableAlbums: availableAlbums ?? this.availableAlbums,
|
||||
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
||||
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
||||
@@ -88,7 +92,7 @@ class BackUpState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -105,6 +109,7 @@ class BackUpState {
|
||||
other.backgroundBackup == backgroundBackup &&
|
||||
other.backupRequireWifi == backupRequireWifi &&
|
||||
other.backupRequireCharging == backupRequireCharging &&
|
||||
other.backupTriggerDelay == backupTriggerDelay &&
|
||||
collectionEquals(other.availableAlbums, availableAlbums) &&
|
||||
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
||||
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
||||
@@ -126,6 +131,7 @@ class BackUpState {
|
||||
backgroundBackup.hashCode ^
|
||||
backupRequireWifi.hashCode ^
|
||||
backupRequireCharging.hashCode ^
|
||||
backupTriggerDelay.hashCode ^
|
||||
availableAlbums.hashCode ^
|
||||
selectedBackupAlbums.hashCode ^
|
||||
excludedBackupAlbums.hashCode ^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cancellation_token_http/http.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
@@ -18,6 +18,7 @@ import 'package:immich_mobile/modules/login/models/authentication_state.model.da
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/server_info.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
@@ -37,6 +38,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
backgroundBackup: false,
|
||||
backupRequireWifi: true,
|
||||
backupRequireCharging: false,
|
||||
backupTriggerDelay: 5000,
|
||||
serverInfo: ServerInfoResponseDto(
|
||||
diskAvailable: "0",
|
||||
diskAvailableRaw: 0,
|
||||
@@ -62,6 +64,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
getBackupInfo();
|
||||
}
|
||||
|
||||
final log = Logger('BackupNotifier');
|
||||
final BackupService _backupService;
|
||||
final ServerInfoService _serverInfoService;
|
||||
final AuthenticationState _authState;
|
||||
@@ -117,18 +120,26 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
bool? enabled,
|
||||
bool? requireWifi,
|
||||
bool? requireCharging,
|
||||
int? triggerDelay,
|
||||
required void Function(String msg) onError,
|
||||
required void Function() onBatteryInfo,
|
||||
}) async {
|
||||
assert(enabled != null || requireWifi != null || requireCharging != null);
|
||||
assert(
|
||||
enabled != null ||
|
||||
requireWifi != null ||
|
||||
requireCharging != null ||
|
||||
triggerDelay != null,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
final bool wasEnabled = state.backgroundBackup;
|
||||
final bool wasWifi = state.backupRequireWifi;
|
||||
final bool wasCharing = state.backupRequireCharging;
|
||||
final bool wasCharging = state.backupRequireCharging;
|
||||
final int oldTriggerDelay = state.backupTriggerDelay;
|
||||
state = state.copyWith(
|
||||
backgroundBackup: enabled,
|
||||
backupRequireWifi: requireWifi,
|
||||
backupRequireCharging: requireCharging,
|
||||
backupTriggerDelay: triggerDelay,
|
||||
);
|
||||
|
||||
if (state.backgroundBackup) {
|
||||
@@ -143,17 +154,22 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await _backgroundService.configureService(
|
||||
requireUnmetered: state.backupRequireWifi,
|
||||
requireCharging: state.backupRequireCharging,
|
||||
triggerUpdateDelay: state.backupTriggerDelay,
|
||||
triggerMaxDelay: state.backupTriggerDelay * 10,
|
||||
);
|
||||
if (success) {
|
||||
await Hive.box(backgroundBackupInfoBox)
|
||||
.put(backupRequireWifi, state.backupRequireWifi);
|
||||
await Hive.box(backgroundBackupInfoBox)
|
||||
.put(backupRequireCharging, state.backupRequireCharging);
|
||||
final box = Hive.box(backgroundBackupInfoBox);
|
||||
await Future.wait([
|
||||
box.put(backupRequireWifi, state.backupRequireWifi),
|
||||
box.put(backupRequireCharging, state.backupRequireCharging),
|
||||
box.put(backupTriggerDelay, state.backupTriggerDelay),
|
||||
]);
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
backgroundBackup: wasEnabled,
|
||||
backupRequireWifi: wasWifi,
|
||||
backupRequireCharging: wasCharing,
|
||||
backupRequireCharging: wasCharging,
|
||||
backupTriggerDelay: oldTriggerDelay,
|
||||
);
|
||||
onError("backup_controller_page_background_configure_error");
|
||||
}
|
||||
@@ -171,9 +187,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
/// Get all album on the device
|
||||
/// Get all selected and excluded album from the user's persistent storage
|
||||
/// If this is the first time performing backup - set the default selected album to be
|
||||
/// the one that has all assets (Recent on Android, Recents on iOS)
|
||||
/// the one that has all assets (`Recent` on Android, `Recents` on iOS)
|
||||
///
|
||||
Future<void> _getBackupAlbumsInfo() async {
|
||||
Stopwatch stopwatch = Stopwatch()..start();
|
||||
// Get all albums on the device
|
||||
List<AvailableAlbum> availableAlbums = [];
|
||||
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
||||
@@ -181,6 +198,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
type: RequestType.common,
|
||||
);
|
||||
|
||||
log.info('Found ${albums.length} local albums');
|
||||
|
||||
for (AssetPathEntity album in albums) {
|
||||
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
||||
|
||||
@@ -218,13 +237,16 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
|
||||
if (backupAlbumInfo == null) {
|
||||
debugPrint("[ERROR] getting Hive backup album infomation");
|
||||
log.severe(
|
||||
"backupAlbumInfo == null",
|
||||
"Failed to get Hive backup album information",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// First time backup - set isAll album is the default one for backup.
|
||||
if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
|
||||
debugPrint("First time backup setup recent album as default");
|
||||
log.info("First time backup; setup 'Recent(s)' album as default");
|
||||
|
||||
// Get album that contains all assets
|
||||
var list = await PhotoManager.getAssetPathList(
|
||||
@@ -286,9 +308,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
selectedBackupAlbums: selectedAlbums,
|
||||
excludedBackupAlbums: excludedAlbums,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint("[ERROR] Failed to generate album from id $e");
|
||||
} catch (e, stackTrace) {
|
||||
log.severe("Failed to generate album from id", e, stackTrace);
|
||||
}
|
||||
|
||||
debugPrint("_getBackupAlbumsInfo takes ${stopwatch.elapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
///
|
||||
@@ -338,7 +362,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
|
||||
if (allUniqueAssets.isEmpty) {
|
||||
debugPrint("No Asset On Device");
|
||||
log.info("Not found albums or assets on the device to backup");
|
||||
state = state.copyWith(
|
||||
backupProgress: BackUpProgressEnum.idle,
|
||||
allAssetsInDatabase: allAssetsInDatabase,
|
||||
@@ -360,14 +384,14 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
return;
|
||||
}
|
||||
|
||||
///
|
||||
/// Get all necessary information for calculating the available albums,
|
||||
/// which albums are selected or excluded
|
||||
/// and then update the UI according to those information
|
||||
///
|
||||
Future<void> getBackupInfo() async {
|
||||
final bool isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
||||
var isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
||||
|
||||
state = state.copyWith(backgroundBackup: isEnabled);
|
||||
|
||||
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
||||
await _getBackupAlbumsInfo();
|
||||
await _updateServerInfo();
|
||||
@@ -375,10 +399,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Save user selection of selected albums and excluded albums to
|
||||
/// Hive database
|
||||
///
|
||||
void _updatePersistentAlbumsSelection() {
|
||||
final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
||||
Box<HiveBackupAlbums> backupAlbumInfoBox =
|
||||
@@ -398,10 +420,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Invoke backup process
|
||||
///
|
||||
Future<void> startBackupProcess() async {
|
||||
debugPrint("Start backup process");
|
||||
assert(state.backupProgress == BackUpProgressEnum.idle);
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
||||
|
||||
@@ -412,13 +433,12 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await PhotoManager.clearFileCache();
|
||||
|
||||
if (state.allUniqueAssets.isEmpty) {
|
||||
debugPrint("No Asset On Device - Abort Backup Process");
|
||||
log.info("No Asset On Device - Abort Backup Process");
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
||||
|
||||
// Remove item that has already been backed up
|
||||
for (var assetId in state.allAssetsInDatabase) {
|
||||
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
||||
@@ -530,7 +550,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
|
||||
// User has been logged out return
|
||||
if (accessKey == null || !_authState.isAuthenticated) {
|
||||
debugPrint("[resumeBackup] not authenticated - abort");
|
||||
log.info("[_resumeBackup] not authenticated - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -539,17 +559,17 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
_authState.deviceInfo.isAutoBackup) {
|
||||
// check if backup is alreayd in process - then return
|
||||
if (state.backupProgress == BackUpProgressEnum.inProgress) {
|
||||
debugPrint("[resumeBackup] Backup is already in progress - abort");
|
||||
log.info("[_resumeBackup] Backup is already in progress - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.backupProgress == BackUpProgressEnum.inBackground) {
|
||||
debugPrint("[resumeBackup] Background backup is running - abort");
|
||||
log.info("[_resumeBackup] Background backup is running - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// Run backup
|
||||
debugPrint("[resumeBackup] Start back up");
|
||||
log.info("[_resumeBackup] Start back up");
|
||||
await startBackupProcess();
|
||||
}
|
||||
|
||||
@@ -565,7 +585,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground);
|
||||
final bool hasLock = await _backgroundService.acquireLock();
|
||||
if (!hasLock) {
|
||||
debugPrint("WARNING [resumeBackup] failed to acquireLock");
|
||||
log.warning("WARNING [resumeBackup] failed to acquireLock");
|
||||
return;
|
||||
}
|
||||
await Future.wait([
|
||||
@@ -596,6 +616,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
excludedBackupAlbums: excludedAlbums,
|
||||
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
||||
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
||||
backupTriggerDelay: backgroundBox.get(backupTriggerDelay),
|
||||
);
|
||||
}
|
||||
return _resumeBackup();
|
||||
@@ -612,7 +633,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]);
|
||||
result.add(a.copyWith(lastBackup: times[i]));
|
||||
} on StateError {
|
||||
debugPrint("[_updateAlbumBackupTime] failed to find album in state");
|
||||
log.severe(
|
||||
"[_updateAlbumBackupTime] failed to find album in state",
|
||||
"State Error",
|
||||
StackTrace.current,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -631,21 +656,29 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).close();
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
log.info("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
}
|
||||
try {
|
||||
if (Hive.isBoxOpen(duplicatedAssetsBox)) {
|
||||
await Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox).close();
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
} catch (error, stackTrace) {
|
||||
log.severe(
|
||||
"[_notifyBackgroundServiceCanRun] failed to close box",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
|
||||
await Hive.box(backgroundBackupInfoBox).close();
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
} catch (error, stackTrace) {
|
||||
log.severe(
|
||||
"[_notifyBackgroundServiceCanRun] failed to close box",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
_backgroundService.releaseLock();
|
||||
}
|
||||
|
||||
@@ -376,8 +376,8 @@ class BackupService {
|
||||
DeviceTypeEnum deviceType,
|
||||
) async {
|
||||
try {
|
||||
var updatedDeviceInfo = await _apiService.deviceInfoApi.updateDeviceInfo(
|
||||
UpdateDeviceInfoDto(
|
||||
var updatedDeviceInfo = await _apiService.deviceInfoApi.upsertDeviceInfo(
|
||||
UpsertDeviceInfoDto(
|
||||
deviceId: deviceId,
|
||||
deviceType: deviceType,
|
||||
isAutoBackup: status,
|
||||
|
||||
@@ -36,7 +36,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
||||
title: Column(
|
||||
children: [
|
||||
Text(
|
||||
"${album.name} (${album.assetCountAsync})",
|
||||
album.name,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Padding(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
@@ -14,10 +15,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
const BackupAlbumSelectionPage({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final availableAlbums = ref.watch(backupProvider).availableAlbums;
|
||||
// final availableAlbums = ref.watch(backupProvider).availableAlbums;
|
||||
final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
final albums = useState<List<AvailableAlbum>>(
|
||||
ref.watch(backupProvider).availableAlbums,
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
@@ -28,7 +32,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
);
|
||||
|
||||
buildAlbumSelectionList() {
|
||||
if (availableAlbums.isEmpty) {
|
||||
if (albums.value.isEmpty) {
|
||||
return const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
);
|
||||
@@ -38,17 +42,17 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
height: 265,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: availableAlbums.length,
|
||||
itemCount: albums.value.length,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: ((context, index) {
|
||||
var thumbnailData = availableAlbums[index].thumbnailData;
|
||||
var thumbnailData = albums.value[index].thumbnailData;
|
||||
return Padding(
|
||||
padding: index == 0
|
||||
? const EdgeInsets.only(left: 16.00)
|
||||
: const EdgeInsets.all(0),
|
||||
child: AlbumInfoCard(
|
||||
imageData: thumbnailData,
|
||||
albumInfo: availableAlbums[index],
|
||||
albumInfo: albums.value[index],
|
||||
),
|
||||
);
|
||||
}),
|
||||
@@ -79,15 +83,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
label: Text(
|
||||
album.name,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
color: isDarkTheme ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -119,7 +121,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
label: Text(
|
||||
album.name,
|
||||
@@ -143,6 +145,46 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
buildSearchBar() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
|
||||
child: TextFormField(
|
||||
onChanged: (searchValue) {
|
||||
albums.value = ref
|
||||
.watch(backupProvider)
|
||||
.availableAlbums
|
||||
.where(
|
||||
(album) => album.name
|
||||
.toLowerCase()
|
||||
.contains(searchValue.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
hintText: "Search",
|
||||
hintStyle: TextStyle(
|
||||
color: isDarkTheme ? Colors.white : Colors.grey,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
prefixIcon: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.grey,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDarkTheme ? Colors.white30 : Colors.grey[200],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
@@ -188,7 +230,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(
|
||||
color: isDarkTheme
|
||||
? const Color.fromARGB(255, 0, 0, 0)
|
||||
@@ -225,8 +267,11 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
|
||||
ListTile(
|
||||
title: Text(
|
||||
"backup_album_selection_page_albums_device"
|
||||
.tr(args: [availableAlbums.length.toString()]),
|
||||
"backup_album_selection_page_albums_device".tr(
|
||||
args: [
|
||||
ref.watch(backupProvider).availableAlbums.length.toString()
|
||||
],
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
subtitle: Padding(
|
||||
@@ -254,7 +299,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 5,
|
||||
title: Text(
|
||||
@@ -284,6 +329,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
buildSearchBar(),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: buildAlbumSelectionList(),
|
||||
|
||||
@@ -198,6 +198,46 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
final bool isWifiRequired = backupState.backupRequireWifi;
|
||||
final bool isChargingRequired = backupState.backupRequireCharging;
|
||||
final Color activeColor = Theme.of(context).primaryColor;
|
||||
|
||||
String formatBackupDelaySliderValue(double v) {
|
||||
if (v == 0.0) {
|
||||
return 'setting_notifications_notify_seconds'.tr(args: const ['5']);
|
||||
} else if (v == 1.0) {
|
||||
return 'setting_notifications_notify_seconds'.tr(args: const ['30']);
|
||||
} else if (v == 2.0) {
|
||||
return 'setting_notifications_notify_minutes'.tr(args: const ['2']);
|
||||
} else {
|
||||
return 'setting_notifications_notify_minutes'.tr(args: const ['10']);
|
||||
}
|
||||
}
|
||||
|
||||
int backupDelayToMilliseconds(double v) {
|
||||
if (v == 0.0) {
|
||||
return 5000;
|
||||
} else if (v == 1.0) {
|
||||
return 30000;
|
||||
} else if (v == 2.0) {
|
||||
return 120000;
|
||||
} else {
|
||||
return 600000;
|
||||
}
|
||||
}
|
||||
|
||||
double backupDelayToSliderValue(int ms) {
|
||||
if (ms == 5000) {
|
||||
return 0.0;
|
||||
} else if (ms == 30000) {
|
||||
return 1.0;
|
||||
} else if (ms == 120000) {
|
||||
return 2.0;
|
||||
} else {
|
||||
return 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
final triggerDelay =
|
||||
useState(backupDelayToSliderValue(backupState.backupTriggerDelay));
|
||||
|
||||
return ListTile(
|
||||
isThreeLine: true,
|
||||
leading: isBackgroundEnabled
|
||||
@@ -264,6 +304,35 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (isBackgroundEnabled)
|
||||
ListTile(
|
||||
isThreeLine: false,
|
||||
dense: true,
|
||||
enabled: hasExclusiveAccess,
|
||||
title: const Text(
|
||||
'backup_controller_page_background_delay',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(args: [formatBackupDelaySliderValue(triggerDelay.value)]),
|
||||
subtitle: Slider(
|
||||
value: triggerDelay.value,
|
||||
onChanged: hasExclusiveAccess
|
||||
? (double v) => triggerDelay.value = v
|
||||
: null,
|
||||
onChangeEnd: (double v) => ref
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(
|
||||
triggerDelay: backupDelayToMilliseconds(v),
|
||||
onError: showErrorToUser,
|
||||
onBatteryInfo: showBatteryOptimizationInfoToUser,
|
||||
),
|
||||
max: 3.0,
|
||||
divisions: 3,
|
||||
label: formatBackupDelaySliderValue(triggerDelay.value),
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
||||
|
||||
@@ -10,8 +10,10 @@ import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/openapi_extensions.dart';
|
||||
import 'package:immich_mobile/utils/tuple.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
final assetServiceProvider = Provider(
|
||||
(ref) => AssetService(
|
||||
@@ -25,42 +27,31 @@ class AssetService {
|
||||
final ApiService _apiService;
|
||||
final BackupService _backupService;
|
||||
final BackgroundService _backgroundService;
|
||||
final log = Logger('AssetService');
|
||||
|
||||
AssetService(this._apiService, this._backupService, this._backgroundService);
|
||||
|
||||
/// Returns all local, remote assets in that order
|
||||
Future<List<Asset>> getAllAsset({bool urgent = false}) async {
|
||||
final List<Asset> assets = [];
|
||||
/// Returns `null` if the server state did not change, else list of assets
|
||||
Future<Pair<List<Asset>?, String?>> getRemoteAssets({String? etag}) async {
|
||||
try {
|
||||
// not using `await` here to fetch local & remote assets concurrently
|
||||
final Future<List<AssetResponseDto>?> remoteTask =
|
||||
_apiService.assetApi.getAllAssets();
|
||||
final Iterable<AssetEntity> newLocalAssets;
|
||||
final List<AssetEntity> localAssets = await _getLocalAssets(urgent);
|
||||
final List<AssetResponseDto> remoteAssets = await remoteTask ?? [];
|
||||
if (remoteAssets.isNotEmpty && localAssets.isNotEmpty) {
|
||||
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
||||
final Set<String> existingIds = remoteAssets
|
||||
.where((e) => e.deviceId == deviceId)
|
||||
.map((e) => e.deviceAssetId)
|
||||
.toSet();
|
||||
newLocalAssets = localAssets.where((e) => !existingIds.contains(e.id));
|
||||
} else {
|
||||
newLocalAssets = localAssets;
|
||||
final Pair<List<AssetResponseDto>, String?>? remote =
|
||||
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
||||
if (remote == null) {
|
||||
return const Pair(null, null);
|
||||
}
|
||||
|
||||
assets.addAll(newLocalAssets.map((e) => Asset.local(e)));
|
||||
// the order (first all local, then remote assets) is important!
|
||||
assets.addAll(remoteAssets.map((e) => Asset.remote(e)));
|
||||
} catch (e) {
|
||||
debugPrint("Error [getAllAsset] ${e.toString()}");
|
||||
return Pair(
|
||||
remote.first.map(Asset.remote).toList(growable: false),
|
||||
remote.second,
|
||||
);
|
||||
} catch (e, stack) {
|
||||
log.severe('Error while getting remote assets', e, stack);
|
||||
return const Pair(null, null);
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
/// if [urgent] is `true`, do not block by waiting on the background service
|
||||
/// to finish running. Returns an empty list instead after a timeout.
|
||||
Future<List<AssetEntity>> _getLocalAssets(bool urgent) async {
|
||||
/// to finish running. Returns `null` instead after a timeout.
|
||||
Future<List<Asset>?> getLocalAssets({bool urgent = false}) async {
|
||||
try {
|
||||
final Future<bool> hasAccess = urgent
|
||||
? _backgroundService.hasAccess
|
||||
@@ -71,15 +62,16 @@ class AssetService {
|
||||
}
|
||||
final box = await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
||||
final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey);
|
||||
|
||||
return backupAlbumInfo != null
|
||||
? await _backupService
|
||||
.buildUploadCandidates(backupAlbumInfo.deepCopy())
|
||||
: [];
|
||||
if (backupAlbumInfo != null) {
|
||||
return (await _backupService
|
||||
.buildUploadCandidates(backupAlbumInfo.deepCopy()))
|
||||
.map(Asset.local)
|
||||
.toList(growable: false);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Error [_getLocalAssets] ${e.toString()}");
|
||||
return [];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Asset?> getAssetById(String assetId) async {
|
||||
|
||||
@@ -75,14 +75,11 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: getAlbumThumbnailUrl(
|
||||
album,
|
||||
type: ThumbnailFormat.JPEG,
|
||||
),
|
||||
imageUrl: getAlbumThumbnailUrl(album),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
cacheKey: "${album.albumThumbnailAssetId}",
|
||||
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
||||
@@ -32,7 +32,9 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
||||
snap: false,
|
||||
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
),
|
||||
),
|
||||
leading: Builder(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -2,12 +2,12 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer_header.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/server_info_box.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
|
||||
class ProfileDrawer extends HookConsumerWidget {
|
||||
@@ -70,6 +70,30 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
buildAppLogButton() {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 0,
|
||||
leading: SizedBox(
|
||||
height: double.infinity,
|
||||
child: Icon(
|
||||
Icons.assignment_outlined,
|
||||
color: Theme.of(context).textTheme.labelMedium?.color,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
"profile_drawer_app_logs",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
onTap: () {
|
||||
AutoRouter.of(context).push(const AppLogRoute());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Drawer(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -80,6 +104,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||
children: [
|
||||
const ProfileDrawerHeader(),
|
||||
buildSettingButton(),
|
||||
buildAppLogButton(),
|
||||
buildSignoutButton(),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -20,6 +22,7 @@ import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/share.service.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
@@ -37,6 +40,8 @@ class HomePage extends HookConsumerWidget {
|
||||
final albums = ref.watch(albumProvider);
|
||||
final albumService = ref.watch(albumServiceProvider);
|
||||
|
||||
final tipOneOpacity = useState(0.0);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
ref.read(websocketProvider.notifier).connect();
|
||||
@@ -146,6 +151,49 @@ class HomePage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
buildLoadingIndicator() {
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
tipOneOpacity.value = 1;
|
||||
});
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const ImmichLoadingIndicator(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Text(
|
||||
'Building the timeline',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: tipOneOpacity.value,
|
||||
child: const SizedBox(
|
||||
width: 250,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).',
|
||||
textAlign: TextAlign.justify,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
bottom: !multiselectEnabled.state,
|
||||
top: true,
|
||||
@@ -164,15 +212,17 @@ class HomePage extends HookConsumerWidget {
|
||||
top: selectionEnabledHook.value ? 0 : 60,
|
||||
bottom: 0.0,
|
||||
),
|
||||
child: ImmichAssetGrid(
|
||||
renderList: renderList,
|
||||
assetsPerRow:
|
||||
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
showStorageIndicator: appSettingService
|
||||
.getSetting(AppSettingsEnum.storageIndicator),
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
),
|
||||
child: ref.watch(assetProvider).isEmpty
|
||||
? buildLoadingIndicator()
|
||||
: ImmichAssetGrid(
|
||||
renderList: renderList,
|
||||
assetsPerRow: appSettingService
|
||||
.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
showStorageIndicator: appSettingService
|
||||
.getSetting(AppSettingsEnum.storageIndicator),
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
),
|
||||
),
|
||||
if (selectionEnabledHook.value)
|
||||
ControlBottomAppBar(
|
||||
|
||||
@@ -101,11 +101,14 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
}
|
||||
|
||||
Future<bool> logout() async {
|
||||
Hive.box(userInfoBox).delete(accessTokenKey);
|
||||
state = state.copyWith(isAuthenticated: false);
|
||||
_assetCacheService.invalidate();
|
||||
_albumCacheService.invalidate();
|
||||
_sharedAlbumCacheService.invalidate();
|
||||
await Future.wait([
|
||||
Hive.box(userInfoBox).delete(accessTokenKey),
|
||||
Hive.box(userInfoBox).delete(assetEtagKey),
|
||||
_assetCacheService.invalidate(),
|
||||
_albumCacheService.invalidate(),
|
||||
_sharedAlbumCacheService.invalidate(),
|
||||
]);
|
||||
|
||||
// Remove login info from local storage
|
||||
var loginInfo =
|
||||
@@ -115,7 +118,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
loginInfo.password = "";
|
||||
loginInfo.isSaveLogin = false;
|
||||
|
||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
||||
await Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
||||
savedLoginInfoKey,
|
||||
loginInfo,
|
||||
);
|
||||
@@ -207,8 +210,8 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
// Register device info
|
||||
try {
|
||||
DeviceInfoResponseDto? deviceInfo =
|
||||
await _apiService.deviceInfoApi.createDeviceInfo(
|
||||
CreateDeviceInfoDto(
|
||||
await _apiService.deviceInfoApi.upsertDeviceInfo(
|
||||
UpsertDeviceInfoDto(
|
||||
deviceId: state.deviceId,
|
||||
deviceType: state.deviceType,
|
||||
),
|
||||
|
||||
@@ -83,6 +83,13 @@ class LoginForm extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
populateTestLoginInfo() {
|
||||
usernameController.text = 'testuser@email.com';
|
||||
passwordController.text = 'password';
|
||||
serverEndpointController.text = 'http://10.1.15.216:2283/api';
|
||||
isSaveLoginInfo.value = true;
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
@@ -92,10 +99,13 @@ class LoginForm extends HookConsumerWidget {
|
||||
runSpacing: 16,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
const Image(
|
||||
image: AssetImage('assets/immich-logo-no-outline.png'),
|
||||
width: 100,
|
||||
filterQuality: FilterQuality.high,
|
||||
GestureDetector(
|
||||
onDoubleTap: () => populateTestLoginInfo(),
|
||||
child: const Image(
|
||||
image: AssetImage('assets/immich-logo-no-outline.png'),
|
||||
width: 100,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'IMMICH',
|
||||
|
||||
@@ -1,14 +1,65 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/login/ui/login_form.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class LoginPage extends HookConsumerWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const Scaffold(
|
||||
body: LoginForm(),
|
||||
final appVersion = useState('0.0.0');
|
||||
|
||||
getAppInfo() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
appVersion.value = packageInfo.version;
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
getAppInfo();
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: const LoginForm(),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'v${appVersion.value}',
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
),
|
||||
const Text(' '),
|
||||
GestureDetector(
|
||||
child: Text(
|
||||
'Logs',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
AutoRouter.of(context).push(const AppLogRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
|
||||
enum AppSettingsEnum<T> {
|
||||
threeStageLoading<bool>("threeStageLoading", false),
|
||||
loadPreview<bool>("loadPreview", true),
|
||||
loadOriginal<bool>("loadOriginal", false),
|
||||
themeMode<String>("themeMode", "system"), // "light","dark","system"
|
||||
tilesPerRow<int>("tilesPerRow", 4),
|
||||
uploadErrorNotificationGracePeriod<int>(
|
||||
|
||||
24
mobile/lib/modules/settings/ui/common.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
|
||||
SwitchListTile buildSwitchListTile(
|
||||
BuildContext context,
|
||||
AppSettingsService appSettingService,
|
||||
ValueNotifier<bool> valueNotifier,
|
||||
AppSettingsEnum settingsEnum, {
|
||||
required String title,
|
||||
String? subtitle,
|
||||
}) {
|
||||
return SwitchListTile.adaptive(
|
||||
key: Key(settingsEnum.name),
|
||||
value: valueNotifier.value,
|
||||
onChanged: (value) {
|
||||
valueNotifier.value = value;
|
||||
appSettingService.setSetting(settingsEnum, value);
|
||||
},
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
dense: true,
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,30 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/image_viewer_quality_setting/three_stage_loading.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/modules/settings/ui/common.dart';
|
||||
|
||||
class ImageViewerQualitySetting extends StatelessWidget {
|
||||
class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||
const ImageViewerQualitySetting({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final isPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||
final isOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
isPreview.value = settings.getSetting(AppSettingsEnum.loadPreview);
|
||||
isOriginal.value = settings.getSetting(AppSettingsEnum.loadOriginal);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
return ExpansionTile(
|
||||
textColor: Theme.of(context).primaryColor,
|
||||
title: const Text(
|
||||
@@ -23,8 +39,27 @@ class ImageViewerQualitySetting extends StatelessWidget {
|
||||
fontSize: 13,
|
||||
),
|
||||
).tr(),
|
||||
children: const [
|
||||
ThreeStageLoading(),
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('setting_image_viewer_help').tr(),
|
||||
dense: true,
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
settings,
|
||||
isPreview,
|
||||
AppSettingsEnum.loadPreview,
|
||||
title: "setting_image_viewer_preview_title".tr(),
|
||||
subtitle: "setting_image_viewer_preview_subtitle".tr(),
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
settings,
|
||||
isOriginal,
|
||||
AppSettingsEnum.loadOriginal,
|
||||
title: "setting_image_viewer_original_title".tr(),
|
||||
subtitle: "setting_image_viewer_original_subtitle".tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
|
||||
class ThreeStageLoading extends HookConsumerWidget {
|
||||
const ThreeStageLoading({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||
|
||||
final isEnable = useState(false);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
var isThreeStageLoadingEnable =
|
||||
appSettingService.getSetting(AppSettingsEnum.threeStageLoading);
|
||||
|
||||
isEnable.value = isThreeStageLoadingEnable;
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
void onSwitchChanged(bool switchValue) {
|
||||
appSettingService.setSetting(
|
||||
AppSettingsEnum.threeStageLoading,
|
||||
switchValue,
|
||||
);
|
||||
isEnable.value = switchValue;
|
||||
}
|
||||
|
||||
return SwitchListTile.adaptive(
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
title: const Text(
|
||||
"theme_setting_three_stage_loading_title",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(),
|
||||
subtitle: const Text(
|
||||
"theme_setting_three_stage_loading_subtitle",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
).tr(),
|
||||
value: isEnable.value,
|
||||
onChanged: onSwitchChanged,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/modules/settings/ui/common.dart';
|
||||
|
||||
class NotificationSetting extends HookConsumerWidget {
|
||||
const NotificationSetting({
|
||||
@@ -50,7 +51,7 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
),
|
||||
).tr(),
|
||||
children: [
|
||||
_buildSwitchListTile(
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
appSettingService,
|
||||
totalProgressValue,
|
||||
@@ -58,7 +59,7 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
title: 'setting_notifications_total_progress_title'.tr(),
|
||||
subtitle: 'setting_notifications_total_progress_subtitle'.tr(),
|
||||
),
|
||||
_buildSwitchListTile(
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
appSettingService,
|
||||
singleProgressValue,
|
||||
@@ -91,28 +92,6 @@ class NotificationSetting extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
SwitchListTile _buildSwitchListTile(
|
||||
BuildContext context,
|
||||
AppSettingsService appSettingService,
|
||||
ValueNotifier<bool> valueNotifier,
|
||||
AppSettingsEnum settingsEnum, {
|
||||
required String title,
|
||||
String? subtitle,
|
||||
}) {
|
||||
return SwitchListTile(
|
||||
key: Key(settingsEnum.name),
|
||||
value: valueNotifier.value,
|
||||
onChanged: (value) {
|
||||
valueNotifier.value = value;
|
||||
appSettingService.setSetting(settingsEnum, value);
|
||||
},
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
dense: true,
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||
);
|
||||
}
|
||||
|
||||
String _formatSliderValue(double v) {
|
||||
if (v == 0.0) {
|
||||
return 'setting_notifications_notify_immediately'.tr();
|
||||
|
||||
@@ -1,33 +1,34 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/gallery_viewer.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/failed_backup_status_page.dart';
|
||||
import 'package:immich_mobile/modules/login/views/change_password_page.dart';
|
||||
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
||||
import 'package:immich_mobile/modules/home/views/home_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/search_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
|
||||
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
|
||||
import 'package:immich_mobile/modules/album/views/album_viewer_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/library_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/select_additional_user_for_sharing_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/select_user_for_sharing_page.dart';
|
||||
import 'package:immich_mobile/modules/album/views/sharing_page.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/gallery_viewer.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/album_preview_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/backup_album_selection_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/backup_controller_page.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/failed_backup_status_page.dart';
|
||||
import 'package:immich_mobile/modules/home/views/home_page.dart';
|
||||
import 'package:immich_mobile/modules/login/views/change_password_page.dart';
|
||||
import 'package:immich_mobile/modules/login/views/login_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/search_page.dart';
|
||||
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
|
||||
import 'package:immich_mobile/modules/settings/views/settings_page.dart';
|
||||
import 'package:immich_mobile/routing/auth_guard.dart';
|
||||
import 'package:immich_mobile/modules/backup/views/backup_controller_page.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/image_viewer_page.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:immich_mobile/shared/views/app_log_page.dart';
|
||||
import 'package:immich_mobile/shared/views/splash_screen.dart';
|
||||
import 'package:immich_mobile/shared/views/tab_controller_page.dart';
|
||||
import 'package:immich_mobile/modules/asset_viewer/views/video_viewer_page.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
@@ -80,6 +81,10 @@ part 'router.gr.dart';
|
||||
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||
),
|
||||
AutoRoute(page: SettingsPage, guards: [AuthGuard]),
|
||||
CustomRoute(
|
||||
page: AppLogPage,
|
||||
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||
),
|
||||
],
|
||||
)
|
||||
class AppRouter extends _$AppRouter {
|
||||
|
||||
@@ -59,7 +59,8 @@ class _$AppRouter extends RootStackRouter {
|
||||
authToken: args.authToken,
|
||||
isZoomedFunction: args.isZoomedFunction,
|
||||
isZoomedListener: args.isZoomedListener,
|
||||
threeStageLoading: args.threeStageLoading));
|
||||
loadPreview: args.loadPreview,
|
||||
loadOriginal: args.loadOriginal));
|
||||
},
|
||||
VideoViewerRoute.name: (routeData) {
|
||||
final args = routeData.argsAs<VideoViewerRouteArgs>();
|
||||
@@ -142,6 +143,14 @@ class _$AppRouter extends RootStackRouter {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData, child: const SettingsPage());
|
||||
},
|
||||
AppLogRoute.name: (routeData) {
|
||||
return CustomPage<dynamic>(
|
||||
routeData: routeData,
|
||||
child: const AppLogPage(),
|
||||
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||
opaque: true,
|
||||
barrierDismissible: false);
|
||||
},
|
||||
HomeRoute.name: (routeData) {
|
||||
return MaterialPageX<dynamic>(
|
||||
routeData: routeData, child: const HomePage());
|
||||
@@ -218,7 +227,8 @@ class _$AppRouter extends RootStackRouter {
|
||||
RouteConfig(FailedBackupStatusRoute.name,
|
||||
path: '/failed-backup-status-page', guards: [authGuard]),
|
||||
RouteConfig(SettingsRoute.name,
|
||||
path: '/settings-page', guards: [authGuard])
|
||||
path: '/settings-page', guards: [authGuard]),
|
||||
RouteConfig(AppLogRoute.name, path: '/app-log-page')
|
||||
];
|
||||
}
|
||||
|
||||
@@ -296,7 +306,8 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
|
||||
required String authToken,
|
||||
required void Function() isZoomedFunction,
|
||||
required ValueNotifier<bool> isZoomedListener,
|
||||
required bool threeStageLoading})
|
||||
required bool loadPreview,
|
||||
required bool loadOriginal})
|
||||
: super(ImageViewerRoute.name,
|
||||
path: '/image-viewer-page',
|
||||
args: ImageViewerRouteArgs(
|
||||
@@ -306,7 +317,8 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
|
||||
authToken: authToken,
|
||||
isZoomedFunction: isZoomedFunction,
|
||||
isZoomedListener: isZoomedListener,
|
||||
threeStageLoading: threeStageLoading));
|
||||
loadPreview: loadPreview,
|
||||
loadOriginal: loadOriginal));
|
||||
|
||||
static const String name = 'ImageViewerRoute';
|
||||
}
|
||||
@@ -319,7 +331,8 @@ class ImageViewerRouteArgs {
|
||||
required this.authToken,
|
||||
required this.isZoomedFunction,
|
||||
required this.isZoomedListener,
|
||||
required this.threeStageLoading});
|
||||
required this.loadPreview,
|
||||
required this.loadOriginal});
|
||||
|
||||
final Key? key;
|
||||
|
||||
@@ -333,11 +346,13 @@ class ImageViewerRouteArgs {
|
||||
|
||||
final ValueNotifier<bool> isZoomedListener;
|
||||
|
||||
final bool threeStageLoading;
|
||||
final bool loadPreview;
|
||||
|
||||
final bool loadOriginal;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ImageViewerRouteArgs{key: $key, heroTag: $heroTag, asset: $asset, authToken: $authToken, isZoomedFunction: $isZoomedFunction, isZoomedListener: $isZoomedListener, threeStageLoading: $threeStageLoading}';
|
||||
return 'ImageViewerRouteArgs{key: $key, heroTag: $heroTag, asset: $asset, authToken: $authToken, isZoomedFunction: $isZoomedFunction, isZoomedListener: $isZoomedListener, loadPreview: $loadPreview, loadOriginal: $loadOriginal}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -560,6 +575,14 @@ class SettingsRoute extends PageRouteInfo<void> {
|
||||
static const String name = 'SettingsRoute';
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [AppLogPage]
|
||||
class AppLogRoute extends PageRouteInfo<void> {
|
||||
const AppLogRoute() : super(AppLogRoute.name, path: '/app-log-page');
|
||||
|
||||
static const String name = 'AppLogRoute';
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [HomePage]
|
||||
class HomeRoute extends PageRouteInfo<void> {
|
||||
|
||||
34
mobile/lib/shared/models/immich_logger_message.model.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'immich_logger_message.model.g.dart';
|
||||
|
||||
@HiveType(typeId: 3)
|
||||
class ImmichLoggerMessage {
|
||||
@HiveField(0)
|
||||
String message;
|
||||
|
||||
@HiveField(1, defaultValue: "INFO")
|
||||
String level;
|
||||
|
||||
@HiveField(2)
|
||||
DateTime createdAt;
|
||||
|
||||
@HiveField(3)
|
||||
String? context1;
|
||||
|
||||
@HiveField(4)
|
||||
String? context2;
|
||||
|
||||
ImmichLoggerMessage({
|
||||
required this.message,
|
||||
required this.level,
|
||||
required this.createdAt,
|
||||
required this.context1,
|
||||
required this.context2,
|
||||
});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'InAppLoggerMessage(message: $message, level: $level, createdAt: $createdAt)';
|
||||
}
|
||||
}
|
||||
53
mobile/lib/shared/models/immich_logger_message.model.g.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'immich_logger_message.model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class ImmichLoggerMessageAdapter extends TypeAdapter<ImmichLoggerMessage> {
|
||||
@override
|
||||
final int typeId = 3;
|
||||
|
||||
@override
|
||||
ImmichLoggerMessage read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return ImmichLoggerMessage(
|
||||
message: fields[0] as String,
|
||||
level: fields[1] == null ? 'INFO' : fields[1] as String,
|
||||
createdAt: fields[2] as DateTime,
|
||||
context1: fields[3] as String?,
|
||||
context2: fields[4] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, ImmichLoggerMessage obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.message)
|
||||
..writeByte(1)
|
||||
..write(obj.level)
|
||||
..writeByte(2)
|
||||
..write(obj.createdAt)
|
||||
..writeByte(3)
|
||||
..write(obj.context1)
|
||||
..writeByte(4)
|
||||
..write(obj.context2);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ImmichLoggerMessageAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
||||
@@ -1,20 +1,23 @@
|
||||
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/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/services/device_info.service.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:immich_mobile/utils/tuple.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
class AssetNotifier extends StateNotifier<List<Asset>> {
|
||||
final AssetService _assetService;
|
||||
final AssetCacheService _assetCacheService;
|
||||
|
||||
final log = Logger('AssetNotifier');
|
||||
final DeviceInfoService _deviceInfoService = DeviceInfoService();
|
||||
bool _getAllAssetInProgress = false;
|
||||
bool _deleteInProgress = false;
|
||||
@@ -33,32 +36,65 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
|
||||
final stopwatch = Stopwatch();
|
||||
try {
|
||||
_getAllAssetInProgress = true;
|
||||
|
||||
final bool isCacheValid = await _assetCacheService.isValid();
|
||||
stopwatch.start();
|
||||
final Box box = Hive.box(userInfoBox);
|
||||
final localTask = _assetService.getLocalAssets(urgent: !isCacheValid);
|
||||
final remoteTask = _assetService.getRemoteAssets(
|
||||
etag: isCacheValid ? box.get(assetEtagKey) : null,
|
||||
);
|
||||
if (isCacheValid && state.isEmpty) {
|
||||
stopwatch.start();
|
||||
state = await _assetCacheService.get();
|
||||
debugPrint(
|
||||
log.info(
|
||||
"Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms",
|
||||
);
|
||||
stopwatch.reset();
|
||||
}
|
||||
|
||||
stopwatch.start();
|
||||
var allAssets = await _assetService.getAllAsset(urgent: !isCacheValid);
|
||||
debugPrint("Query assets from API: ${stopwatch.elapsedMilliseconds}ms");
|
||||
int remoteBegin = state.indexWhere((a) => a.isRemote);
|
||||
remoteBegin = remoteBegin == -1 ? state.length : remoteBegin;
|
||||
final List<Asset> currentLocal = state.slice(0, remoteBegin);
|
||||
final Pair<List<Asset>?, String?> remoteResult = await remoteTask;
|
||||
List<Asset>? newRemote = remoteResult.first;
|
||||
List<Asset>? newLocal = await localTask;
|
||||
log.info("Load assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||
stopwatch.reset();
|
||||
if (newRemote == null &&
|
||||
(newLocal == null || currentLocal.equals(newLocal))) {
|
||||
log.info("state is already up-to-date");
|
||||
return;
|
||||
}
|
||||
newRemote ??= state.slice(remoteBegin);
|
||||
newLocal ??= [];
|
||||
state = _combineLocalAndRemoteAssets(local: newLocal, remote: newRemote);
|
||||
log.info("Combining assets: ${stopwatch.elapsedMilliseconds}ms");
|
||||
|
||||
state = allAssets;
|
||||
stopwatch.reset();
|
||||
_cacheState();
|
||||
box.put(assetEtagKey, remoteResult.second);
|
||||
log.info("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
|
||||
} finally {
|
||||
_getAllAssetInProgress = false;
|
||||
}
|
||||
debugPrint("[getAllAsset] setting new asset state");
|
||||
}
|
||||
|
||||
stopwatch.start();
|
||||
_cacheState();
|
||||
debugPrint("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
|
||||
stopwatch.reset();
|
||||
List<Asset> _combineLocalAndRemoteAssets({
|
||||
required Iterable<Asset> local,
|
||||
required List<Asset> remote,
|
||||
}) {
|
||||
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)
|
||||
.toSet();
|
||||
local = local.where((e) => !existingIds.contains(e.id));
|
||||
}
|
||||
assets.addAll(local);
|
||||
// the order (first all local, then remote assets) is important!
|
||||
assets.addAll(remote);
|
||||
return assets;
|
||||
}
|
||||
|
||||
clearAllAsset() {
|
||||
@@ -124,8 +160,8 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
|
||||
if (local.isNotEmpty) {
|
||||
try {
|
||||
return await PhotoManager.editor.deleteWithIds(local);
|
||||
} catch (e) {
|
||||
debugPrint("Delete asset from device failed: $e");
|
||||
} catch (e, stack) {
|
||||
log.severe("Failed to delete asset from device", e, stack);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
|
||||
@@ -6,10 +6,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class ReleaseInfoNotifier extends StateNotifier<String> {
|
||||
ReleaseInfoNotifier() : super("");
|
||||
|
||||
final log = Logger('ReleaseInfoNotifier');
|
||||
void checkGithubReleaseInfo() async {
|
||||
final Client client = Client();
|
||||
var box = Hive.box(hiveGithubReleaseInfoBox);
|
||||
@@ -28,9 +29,6 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
|
||||
String latestTagVersion = data["tag_name"];
|
||||
state = latestTagVersion;
|
||||
|
||||
debugPrint("Local release version $localReleaseVersion");
|
||||
debugPrint("Remote release veresion $latestTagVersion");
|
||||
|
||||
if (localReleaseVersion == null && latestTagVersion.isNotEmpty) {
|
||||
VersionAnnouncementOverlayController.appLoader.show();
|
||||
return;
|
||||
|
||||
@@ -6,23 +6,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:socket_io_client/socket_io_client.dart';
|
||||
|
||||
class WebscoketState {
|
||||
class WebsocketState {
|
||||
final Socket? socket;
|
||||
final bool isConnected;
|
||||
|
||||
WebscoketState({
|
||||
WebsocketState({
|
||||
this.socket,
|
||||
required this.isConnected,
|
||||
});
|
||||
|
||||
WebscoketState copyWith({
|
||||
WebsocketState copyWith({
|
||||
Socket? socket,
|
||||
bool? isConnected,
|
||||
}) {
|
||||
return WebscoketState(
|
||||
return WebsocketState(
|
||||
socket: socket ?? this.socket,
|
||||
isConnected: isConnected ?? this.isConnected,
|
||||
);
|
||||
@@ -30,13 +31,13 @@ class WebscoketState {
|
||||
|
||||
@override
|
||||
String toString() =>
|
||||
'WebscoketState(socket: $socket, isConnected: $isConnected)';
|
||||
'WebsocketState(socket: $socket, isConnected: $isConnected)';
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
if (identical(this, other)) return true;
|
||||
|
||||
return other is WebscoketState &&
|
||||
return other is WebsocketState &&
|
||||
other.socket == socket &&
|
||||
other.isConnected == isConnected;
|
||||
}
|
||||
@@ -45,12 +46,11 @@ class WebscoketState {
|
||||
int get hashCode => socket.hashCode ^ isConnected.hashCode;
|
||||
}
|
||||
|
||||
class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
||||
class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||
WebsocketNotifier(this.ref)
|
||||
: super(WebscoketState(socket: null, isConnected: false)) {
|
||||
debugPrint("Init websocket instance");
|
||||
}
|
||||
: super(WebsocketState(socket: null, isConnected: false));
|
||||
|
||||
final log = Logger('WebsocketNotifier');
|
||||
final Ref ref;
|
||||
|
||||
connect() {
|
||||
@@ -60,8 +60,8 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
||||
var accessToken = Hive.box(userInfoBox).get(accessTokenKey);
|
||||
var endpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
||||
try {
|
||||
debugPrint("[WEBSOCKET] Attempting to connect to ws");
|
||||
// Configure socket transports must be sepecified
|
||||
debugPrint("Attempting to connect to websocket");
|
||||
// Configure socket transports must be specified
|
||||
Socket socket = io(
|
||||
endpoint.toString().replaceAll('/api', ''),
|
||||
OptionBuilder()
|
||||
@@ -76,18 +76,18 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
||||
);
|
||||
|
||||
socket.onConnect((_) {
|
||||
debugPrint("[WEBSOCKET] Established Websocket Connection");
|
||||
state = WebscoketState(isConnected: true, socket: socket);
|
||||
debugPrint("Established Websocket Connection");
|
||||
state = WebsocketState(isConnected: true, socket: socket);
|
||||
});
|
||||
|
||||
socket.onDisconnect((_) {
|
||||
debugPrint("[WEBSOCKET] Disconnect to Websocket Connection");
|
||||
state = WebscoketState(isConnected: false, socket: null);
|
||||
debugPrint("Disconnect to Websocket Connection");
|
||||
state = WebsocketState(isConnected: false, socket: null);
|
||||
});
|
||||
|
||||
socket.on('error', (errorMessage) {
|
||||
debugPrint("Webcoket Error - $errorMessage");
|
||||
state = WebscoketState(isConnected: false, socket: null);
|
||||
log.severe("Websocket Error - $errorMessage");
|
||||
state = WebsocketState(isConnected: false, socket: null);
|
||||
});
|
||||
|
||||
socket.on('on_upload_success', (data) {
|
||||
@@ -105,21 +105,22 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
debugPrint("[WEBSOCKET] Attempting to disconnect");
|
||||
debugPrint("Attempting to disconnect from websocket");
|
||||
|
||||
var socket = state.socket?.disconnect();
|
||||
|
||||
if (socket?.disconnected == true) {
|
||||
state = WebscoketState(isConnected: false, socket: null);
|
||||
state = WebsocketState(isConnected: false, socket: null);
|
||||
}
|
||||
}
|
||||
|
||||
stopListenToEvent(String eventName) {
|
||||
debugPrint("[Websocket] Stop listening to event $eventName");
|
||||
debugPrint("Stop listening to event $eventName");
|
||||
state.socket?.off(eventName);
|
||||
}
|
||||
|
||||
listenUploadEvent() {
|
||||
debugPrint("[Websocket] Start listening to event on_upload_success");
|
||||
debugPrint("Start listening to event on_upload_success");
|
||||
state.socket?.on('on_upload_success', (data) {
|
||||
var jsonString = jsonDecode(data.toString());
|
||||
AssetResponseDto? newAsset = AssetResponseDto.fromJson(jsonString);
|
||||
@@ -132,6 +133,6 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
||||
}
|
||||
|
||||
final websocketProvider =
|
||||
StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
|
||||
StateNotifierProvider<WebsocketNotifier, WebsocketState>((ref) {
|
||||
return WebsocketNotifier(ref);
|
||||
});
|
||||
|
||||
95
mobile/lib/shared/services/immich_logger.service.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
/// [ImmichLogger] is a custom logger that is built on top of the [logging] package.
|
||||
/// The logs are written to a Hive box and onto console, using `debugPrint` method.
|
||||
///
|
||||
/// The logs are deleted when exceeding the `maxLogEntries` (default 200) property
|
||||
/// in the class.
|
||||
///
|
||||
/// Logs can be shared by calling the `shareLogs` method, which will open a share dialog
|
||||
/// and generate a csv file.
|
||||
class ImmichLogger {
|
||||
final maxLogEntries = 200;
|
||||
final Box<ImmichLoggerMessage> _box = Hive.box(immichLoggerBox);
|
||||
|
||||
List<ImmichLoggerMessage> get messages =>
|
||||
_box.values.toList().reversed.toList();
|
||||
|
||||
ImmichLogger() {
|
||||
_removeOverflowMessages();
|
||||
}
|
||||
|
||||
init() {
|
||||
Logger.root.level = Level.INFO;
|
||||
Logger.root.onRecord.listen(_writeLogToHiveBox);
|
||||
}
|
||||
|
||||
_removeOverflowMessages() {
|
||||
if (_box.length > maxLogEntries) {
|
||||
var numberOfEntryToBeDeleted = _box.length - maxLogEntries;
|
||||
for (var i = 0; i < numberOfEntryToBeDeleted; i++) {
|
||||
_box.deleteAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_writeLogToHiveBox(LogRecord record) {
|
||||
final Box<ImmichLoggerMessage> box = Hive.box(immichLoggerBox);
|
||||
var formattedMessage = record.message;
|
||||
|
||||
debugPrint('[${record.level.name}] [${record.time}] ${record.message}');
|
||||
box.add(
|
||||
ImmichLoggerMessage(
|
||||
message: formattedMessage,
|
||||
level: record.level.name,
|
||||
createdAt: record.time,
|
||||
context1: record.loggerName,
|
||||
context2: record.stackTrace?.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void clearLogs() {
|
||||
_box.clear();
|
||||
}
|
||||
|
||||
Future<void> shareLogs() async {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final dateTime = DateTime.now().toIso8601String();
|
||||
final filePath = '${tempDir.path}/Immich_log_$dateTime.csv';
|
||||
final logFile = await File(filePath).create();
|
||||
final io = logFile.openWrite();
|
||||
try {
|
||||
// Write header
|
||||
io.write("created_at,level,context,message,stacktrace\n");
|
||||
|
||||
// Write messages
|
||||
for (final m in messages) {
|
||||
io.write(
|
||||
'${m.createdAt},${m.level},"${m.context1 ?? ""}","${m.message}","${m.context2 ?? ""}"\n',
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await io.flush();
|
||||
await io.close();
|
||||
}
|
||||
|
||||
// Share file
|
||||
await Share.shareFiles(
|
||||
[filePath],
|
||||
subject: "Immich logs $dateTime",
|
||||
sharePositionOrigin: Rect.zero,
|
||||
);
|
||||
|
||||
// Clean up temp file
|
||||
await logFile.delete();
|
||||
}
|
||||
}
|
||||
@@ -23,8 +23,12 @@ abstract class JsonCache<T> {
|
||||
}
|
||||
|
||||
Future<void> invalidate() async {
|
||||
final file = await _getCacheFile();
|
||||
await file.delete();
|
||||
try {
|
||||
final file = await _getCacheFile();
|
||||
await file.delete();
|
||||
} on FileSystemException {
|
||||
// file is already deleted
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> putRawData(dynamic data) async {
|
||||
@@ -46,4 +50,4 @@ abstract class JsonCache<T> {
|
||||
|
||||
void put(T data);
|
||||
Future<T> get();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ class ImmichImage extends StatelessWidget {
|
||||
return CachedNetworkImage(
|
||||
imageUrl: thumbnailRequestUrl,
|
||||
httpHeaders: {"Authorization": "Bearer $token"},
|
||||
cacheKey: 'thumbnail-image-${asset.id}',
|
||||
cacheKey: getThumbnailCacheKey(asset.remote!),
|
||||
width: width,
|
||||
height: height,
|
||||
// keeping memCacheWidth, memCacheHeight, maxWidthDiskCache and
|
||||
|
||||
@@ -15,7 +15,10 @@ class ImmichLoadingIndicator extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: const CircularProgressIndicator(color: Colors.white),
|
||||
child: const CircularProgressIndicator(
|
||||
color: Colors.white,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
165
mobile/lib/shared/views/app_log_page.dart
Normal file
@@ -0,0 +1,165 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class AppLogPage extends HookConsumerWidget {
|
||||
const AppLogPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final immichLogger = ImmichLogger();
|
||||
final logMessages = useState(immichLogger.messages);
|
||||
|
||||
Widget colorStatusIndicator(Color color) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
color: color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildLeadingIcon(String level) {
|
||||
switch (level) {
|
||||
case "INFO":
|
||||
return colorStatusIndicator(Theme.of(context).primaryColor);
|
||||
case "SEVERE":
|
||||
return colorStatusIndicator(Colors.redAccent);
|
||||
|
||||
case "WARNING":
|
||||
return colorStatusIndicator(Colors.orangeAccent);
|
||||
default:
|
||||
return colorStatusIndicator(Colors.grey);
|
||||
}
|
||||
}
|
||||
|
||||
getTileColor(String level) {
|
||||
switch (level) {
|
||||
case "INFO":
|
||||
return Colors.transparent;
|
||||
case "SEVERE":
|
||||
return Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.redAccent.withOpacity(0.25)
|
||||
: Colors.redAccent.withOpacity(0.075);
|
||||
case "WARNING":
|
||||
return Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.orangeAccent.withOpacity(0.25)
|
||||
: Colors.orangeAccent.withOpacity(0.075);
|
||||
default:
|
||||
return Theme.of(context).primaryColor.withOpacity(0.1);
|
||||
}
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
"Logs - ${logMessages.value.length}",
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16.0,
|
||||
),
|
||||
),
|
||||
scrolledUnderElevation: 1,
|
||||
elevation: 2,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete_outline_rounded,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticLabel: "Clear logs",
|
||||
size: 20.0,
|
||||
),
|
||||
onPressed: () {
|
||||
immichLogger.clearLogs();
|
||||
logMessages.value = [];
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.share_rounded,
|
||||
color: Theme.of(context).primaryColor,
|
||||
semanticLabel: "Share logs",
|
||||
size: 20.0,
|
||||
),
|
||||
onPressed: () {
|
||||
immichLogger.shareLogs();
|
||||
},
|
||||
),
|
||||
],
|
||||
leading: IconButton(
|
||||
onPressed: () {
|
||||
AutoRouter.of(context).pop();
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios_new_rounded,
|
||||
size: 20.0,
|
||||
),
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: ListView.separated(
|
||||
separatorBuilder: (context, index) {
|
||||
return Divider(
|
||||
height: 0,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Colors.grey[600],
|
||||
);
|
||||
},
|
||||
itemCount: logMessages.value.length,
|
||||
itemBuilder: (context, index) {
|
||||
var logMessage = logMessages.value[index];
|
||||
return ListTile(
|
||||
visualDensity: VisualDensity.compact,
|
||||
dense: true,
|
||||
tileColor: getTileColor(logMessage.level),
|
||||
minLeadingWidth: 10,
|
||||
title: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: "#$index ",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white70
|
||||
: Colors.grey[600],
|
||||
fontSize: 14.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: logMessage.message,
|
||||
style: const TextStyle(
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
style: const TextStyle(fontSize: 14.0, fontFamily: "Inconsolata"),
|
||||
),
|
||||
subtitle: Text(
|
||||
"[${logMessage.context1}] Logged on ${DateFormat("HH:mm:ss.SSS").format(logMessage.createdAt)}",
|
||||
style: TextStyle(
|
||||
fontSize: 12.0,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
leading: buildLeadingIcon(logMessage.level),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,17 @@
|
||||
|
||||
String formatBytes(int bytes) {
|
||||
if (bytes < 1000) {
|
||||
return "$bytes B";
|
||||
} else if (bytes < 1000000) {
|
||||
final kb = (bytes / 1000).toStringAsFixed(1);
|
||||
return "$kb kB";
|
||||
} else if (bytes < 1000000000) {
|
||||
final mb = (bytes / 1000000).toStringAsFixed(1);
|
||||
return "$mb MB";
|
||||
} else {
|
||||
final gb = (bytes / 1000000000).toStringAsFixed(1);
|
||||
return "$gb GB";
|
||||
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
|
||||
|
||||
int magnitude = 0;
|
||||
double remainder = bytes.toDouble();
|
||||
while (remainder >= 1024) {
|
||||
if (magnitude + 1 < units.length) {
|
||||
magnitude++;
|
||||
remainder /= 1024;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "${remainder.toStringAsFixed(magnitude == 0 ? 0 : 1)} ${units[magnitude]}";
|
||||
}
|
||||
|
||||
@@ -10,6 +10,19 @@ String getThumbnailUrl(
|
||||
return _getThumbnailUrl(asset.id, type: type);
|
||||
}
|
||||
|
||||
String getThumbnailCacheKey(final AssetResponseDto asset,
|
||||
{ThumbnailFormat type = ThumbnailFormat.WEBP}) {
|
||||
return _getThumbnailCacheKey(asset.id, type);
|
||||
}
|
||||
|
||||
String _getThumbnailCacheKey(final String id, final ThumbnailFormat type) {
|
||||
if (type == ThumbnailFormat.WEBP) {
|
||||
return 'thumbnail-image-$id';
|
||||
} else {
|
||||
return '${id}_previewStage';
|
||||
}
|
||||
}
|
||||
|
||||
String getAlbumThumbnailUrl(
|
||||
final AlbumResponseDto album, {
|
||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
||||
@@ -20,11 +33,25 @@ String getAlbumThumbnailUrl(
|
||||
return _getThumbnailUrl(album.albumThumbnailAssetId!, type: type);
|
||||
}
|
||||
|
||||
String getAlbumThumbNailCacheKey(
|
||||
final AlbumResponseDto album, {
|
||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
||||
}) {
|
||||
if (album.albumThumbnailAssetId == null) {
|
||||
return '';
|
||||
}
|
||||
return _getThumbnailCacheKey(album.albumThumbnailAssetId!, type);
|
||||
}
|
||||
|
||||
String getImageUrl(final AssetResponseDto asset) {
|
||||
final box = Hive.box(userInfoBox);
|
||||
return '${box.get(serverEndpointKey)}/asset/file/${asset.id}?isThumb=false';
|
||||
}
|
||||
|
||||
String getImageCacheKey(final AssetResponseDto asset) {
|
||||
return '${asset.id}_fullStage';
|
||||
}
|
||||
|
||||
String _getThumbnailUrl(
|
||||
final String id, {
|
||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
||||
|
||||
53
mobile/lib/utils/openapi_extensions.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:http/http.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
import 'tuple.dart';
|
||||
|
||||
/// Extension methods to retrieve ETag together with the API call
|
||||
extension WithETag on AssetApi {
|
||||
/// Get all AssetEntity belong to the user
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [String] eTag:
|
||||
/// ETag of data already cached on the client
|
||||
Future<Pair<List<AssetResponseDto>, String?>?> getAllAssetsWithETag({
|
||||
String? eTag,
|
||||
}) async {
|
||||
final response = await getAllAssetsWithHttpInfo(
|
||||
ifNoneMatch: eTag,
|
||||
);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
final etag = response.headers[HttpHeaders.etagHeader];
|
||||
final data = (await apiClient.deserializeAsync(
|
||||
responseBody, 'List<AssetResponseDto>') as List)
|
||||
.cast<AssetResponseDto>()
|
||||
.toList();
|
||||
return Pair(data, etag);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the decoded body as UTF-8 if the given headers indicate an 'application/json'
|
||||
/// content type. Otherwise, returns the decoded body as decoded by dart:http package.
|
||||
Future<String> _decodeBodyBytes(Response response) async {
|
||||
final contentType = response.headers['content-type'];
|
||||
return contentType != null &&
|
||||
contentType.toLowerCase().startsWith('application/json')
|
||||
? response.bodyBytes.isEmpty
|
||||
? ''
|
||||
: utf8.decode(response.bodyBytes)
|
||||
: response.body;
|
||||
}
|
||||
8
mobile/lib/utils/tuple.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
/// An immutable pair or 2-tuple
|
||||
/// TODO replace with Record once Dart 2.19 is available
|
||||
class Pair<T1, T2> {
|
||||
final T1 first;
|
||||
final T2 second;
|
||||
|
||||
const Pair(this.first, this.second);
|
||||
}
|
||||
105
mobile/openapi/.openapi-generator/FILES
generated
@@ -1,4 +1,5 @@
|
||||
.gitignore
|
||||
.openapi-generator-ignore
|
||||
.travis.yml
|
||||
README.md
|
||||
analysis_options.yaml
|
||||
@@ -23,8 +24,8 @@ doc/CheckDuplicateAssetResponseDto.md
|
||||
doc/CheckExistingAssetsDto.md
|
||||
doc/CheckExistingAssetsResponseDto.md
|
||||
doc/CreateAlbumDto.md
|
||||
doc/CreateDeviceInfoDto.md
|
||||
doc/CreateProfileImageResponseDto.md
|
||||
doc/CreateTagDto.md
|
||||
doc/CreateUserDto.md
|
||||
doc/CuratedLocationsResponseDto.md
|
||||
doc/CuratedObjectsResponseDto.md
|
||||
@@ -60,15 +61,19 @@ doc/ServerVersionReponseDto.md
|
||||
doc/SignUpDto.md
|
||||
doc/SmartInfoResponseDto.md
|
||||
doc/SystemConfigApi.md
|
||||
doc/SystemConfigKey.md
|
||||
doc/SystemConfigResponseDto.md
|
||||
doc/SystemConfigResponseItem.md
|
||||
doc/SystemConfigDto.md
|
||||
doc/SystemConfigFFmpegDto.md
|
||||
doc/SystemConfigOAuthDto.md
|
||||
doc/TagApi.md
|
||||
doc/TagResponseDto.md
|
||||
doc/TagTypeEnum.md
|
||||
doc/ThumbnailFormat.md
|
||||
doc/TimeGroupEnum.md
|
||||
doc/UpdateAlbumDto.md
|
||||
doc/UpdateAssetDto.md
|
||||
doc/UpdateDeviceInfoDto.md
|
||||
doc/UpdateTagDto.md
|
||||
doc/UpdateUserDto.md
|
||||
doc/UpsertDeviceInfoDto.md
|
||||
doc/UsageByUserDto.md
|
||||
doc/UserApi.md
|
||||
doc/UserCountResponseDto.md
|
||||
@@ -84,6 +89,7 @@ lib/api/job_api.dart
|
||||
lib/api/o_auth_api.dart
|
||||
lib/api/server_info_api.dart
|
||||
lib/api/system_config_api.dart
|
||||
lib/api/tag_api.dart
|
||||
lib/api/user_api.dart
|
||||
lib/api_client.dart
|
||||
lib/api_exception.dart
|
||||
@@ -111,8 +117,8 @@ lib/model/check_duplicate_asset_response_dto.dart
|
||||
lib/model/check_existing_assets_dto.dart
|
||||
lib/model/check_existing_assets_response_dto.dart
|
||||
lib/model/create_album_dto.dart
|
||||
lib/model/create_device_info_dto.dart
|
||||
lib/model/create_profile_image_response_dto.dart
|
||||
lib/model/create_tag_dto.dart
|
||||
lib/model/create_user_dto.dart
|
||||
lib/model/curated_locations_response_dto.dart
|
||||
lib/model/curated_objects_response_dto.dart
|
||||
@@ -143,17 +149,96 @@ lib/model/server_stats_response_dto.dart
|
||||
lib/model/server_version_reponse_dto.dart
|
||||
lib/model/sign_up_dto.dart
|
||||
lib/model/smart_info_response_dto.dart
|
||||
lib/model/system_config_key.dart
|
||||
lib/model/system_config_response_dto.dart
|
||||
lib/model/system_config_response_item.dart
|
||||
lib/model/system_config_dto.dart
|
||||
lib/model/system_config_f_fmpeg_dto.dart
|
||||
lib/model/system_config_o_auth_dto.dart
|
||||
lib/model/tag_response_dto.dart
|
||||
lib/model/tag_type_enum.dart
|
||||
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_device_info_dto.dart
|
||||
lib/model/update_tag_dto.dart
|
||||
lib/model/update_user_dto.dart
|
||||
lib/model/upsert_device_info_dto.dart
|
||||
lib/model/usage_by_user_dto.dart
|
||||
lib/model/user_count_response_dto.dart
|
||||
lib/model/user_response_dto.dart
|
||||
lib/model/validate_access_token_response_dto.dart
|
||||
pubspec.yaml
|
||||
test/add_assets_dto_test.dart
|
||||
test/add_assets_response_dto_test.dart
|
||||
test/add_users_dto_test.dart
|
||||
test/admin_signup_response_dto_test.dart
|
||||
test/album_api_test.dart
|
||||
test/album_count_response_dto_test.dart
|
||||
test/album_response_dto_test.dart
|
||||
test/all_job_status_response_dto_test.dart
|
||||
test/asset_api_test.dart
|
||||
test/asset_count_by_time_bucket_response_dto_test.dart
|
||||
test/asset_count_by_time_bucket_test.dart
|
||||
test/asset_count_by_user_id_response_dto_test.dart
|
||||
test/asset_file_upload_response_dto_test.dart
|
||||
test/asset_response_dto_test.dart
|
||||
test/asset_type_enum_test.dart
|
||||
test/authentication_api_test.dart
|
||||
test/check_duplicate_asset_dto_test.dart
|
||||
test/check_duplicate_asset_response_dto_test.dart
|
||||
test/check_existing_assets_dto_test.dart
|
||||
test/check_existing_assets_response_dto_test.dart
|
||||
test/create_album_dto_test.dart
|
||||
test/create_profile_image_response_dto_test.dart
|
||||
test/create_tag_dto_test.dart
|
||||
test/create_user_dto_test.dart
|
||||
test/curated_locations_response_dto_test.dart
|
||||
test/curated_objects_response_dto_test.dart
|
||||
test/delete_asset_dto_test.dart
|
||||
test/delete_asset_response_dto_test.dart
|
||||
test/delete_asset_status_test.dart
|
||||
test/device_info_api_test.dart
|
||||
test/device_info_response_dto_test.dart
|
||||
test/device_type_enum_test.dart
|
||||
test/exif_response_dto_test.dart
|
||||
test/get_asset_by_time_bucket_dto_test.dart
|
||||
test/get_asset_count_by_time_bucket_dto_test.dart
|
||||
test/job_api_test.dart
|
||||
test/job_command_dto_test.dart
|
||||
test/job_command_test.dart
|
||||
test/job_counts_test.dart
|
||||
test/job_id_test.dart
|
||||
test/job_status_response_dto_test.dart
|
||||
test/login_credential_dto_test.dart
|
||||
test/login_response_dto_test.dart
|
||||
test/logout_response_dto_test.dart
|
||||
test/o_auth_api_test.dart
|
||||
test/o_auth_callback_dto_test.dart
|
||||
test/o_auth_config_dto_test.dart
|
||||
test/o_auth_config_response_dto_test.dart
|
||||
test/remove_assets_dto_test.dart
|
||||
test/search_asset_dto_test.dart
|
||||
test/server_info_api_test.dart
|
||||
test/server_info_response_dto_test.dart
|
||||
test/server_ping_response_test.dart
|
||||
test/server_stats_response_dto_test.dart
|
||||
test/server_version_reponse_dto_test.dart
|
||||
test/sign_up_dto_test.dart
|
||||
test/smart_info_response_dto_test.dart
|
||||
test/system_config_api_test.dart
|
||||
test/system_config_dto_test.dart
|
||||
test/system_config_f_fmpeg_dto_test.dart
|
||||
test/system_config_o_auth_dto_test.dart
|
||||
test/tag_api_test.dart
|
||||
test/tag_response_dto_test.dart
|
||||
test/tag_type_enum_test.dart
|
||||
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_tag_dto_test.dart
|
||||
test/update_user_dto_test.dart
|
||||
test/upsert_device_info_dto_test.dart
|
||||
test/usage_by_user_dto_test.dart
|
||||
test/user_api_test.dart
|
||||
test/user_count_response_dto_test.dart
|
||||
test/user_response_dto_test.dart
|
||||
test/validate_access_token_response_dto_test.dart
|
||||
|
||||
24
mobile/openapi/README.md
generated
@@ -3,7 +3,7 @@ Immich API
|
||||
|
||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||
|
||||
- API version: 1.17.0
|
||||
- API version: 1.38.0
|
||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||
|
||||
## Requirements
|
||||
@@ -93,7 +93,7 @@ Class | Method | HTTP request | Description
|
||||
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
||||
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
|
||||
*AssetApi* | [**updateAssetById**](doc//AssetApi.md#updateassetbyid) | **PUT** /asset/assetById/{assetId} |
|
||||
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{assetId} |
|
||||
*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload |
|
||||
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
|
||||
*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login |
|
||||
@@ -101,6 +101,7 @@ Class | Method | HTTP request | Description
|
||||
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||
*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
||||
*DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
|
||||
*DeviceInfoApi* | [**upsertDeviceInfo**](doc//DeviceInfoApi.md#upsertdeviceinfo) | **PUT** /device-info |
|
||||
*JobApi* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
|
||||
*JobApi* | [**getJobStatus**](doc//JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |
|
||||
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /jobs/{jobId} |
|
||||
@@ -111,7 +112,13 @@ Class | Method | HTTP request | Description
|
||||
*ServerInfoApi* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats |
|
||||
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
|
||||
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |
|
||||
*SystemConfigApi* | [**getDefaults**](doc//SystemConfigApi.md#getdefaults) | **GET** /system-config/defaults |
|
||||
*SystemConfigApi* | [**updateConfig**](doc//SystemConfigApi.md#updateconfig) | **PUT** /system-config |
|
||||
*TagApi* | [**create**](doc//TagApi.md#create) | **POST** /tag |
|
||||
*TagApi* | [**delete**](doc//TagApi.md#delete) | **DELETE** /tag/{id} |
|
||||
*TagApi* | [**findAll**](doc//TagApi.md#findall) | **GET** /tag |
|
||||
*TagApi* | [**findOne**](doc//TagApi.md#findone) | **GET** /tag/{id} |
|
||||
*TagApi* | [**update**](doc//TagApi.md#update) | **PATCH** /tag/{id} |
|
||||
*UserApi* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |
|
||||
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |
|
||||
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{userId} |
|
||||
@@ -144,8 +151,8 @@ Class | Method | HTTP request | Description
|
||||
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
||||
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
|
||||
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
||||
- [CreateDeviceInfoDto](doc//CreateDeviceInfoDto.md)
|
||||
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
||||
- [CreateTagDto](doc//CreateTagDto.md)
|
||||
- [CreateUserDto](doc//CreateUserDto.md)
|
||||
- [CuratedLocationsResponseDto](doc//CuratedLocationsResponseDto.md)
|
||||
- [CuratedObjectsResponseDto](doc//CuratedObjectsResponseDto.md)
|
||||
@@ -176,15 +183,18 @@ Class | Method | HTTP request | Description
|
||||
- [ServerVersionReponseDto](doc//ServerVersionReponseDto.md)
|
||||
- [SignUpDto](doc//SignUpDto.md)
|
||||
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
||||
- [SystemConfigKey](doc//SystemConfigKey.md)
|
||||
- [SystemConfigResponseDto](doc//SystemConfigResponseDto.md)
|
||||
- [SystemConfigResponseItem](doc//SystemConfigResponseItem.md)
|
||||
- [SystemConfigDto](doc//SystemConfigDto.md)
|
||||
- [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md)
|
||||
- [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md)
|
||||
- [TagResponseDto](doc//TagResponseDto.md)
|
||||
- [TagTypeEnum](doc//TagTypeEnum.md)
|
||||
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
||||
- [TimeGroupEnum](doc//TimeGroupEnum.md)
|
||||
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
||||
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
||||
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
|
||||
- [UpdateTagDto](doc//UpdateTagDto.md)
|
||||
- [UpdateUserDto](doc//UpdateUserDto.md)
|
||||
- [UpsertDeviceInfoDto](doc//UpsertDeviceInfoDto.md)
|
||||
- [UsageByUserDto](doc//UsageByUserDto.md)
|
||||
- [UserCountResponseDto](doc//UserCountResponseDto.md)
|
||||
- [UserResponseDto](doc//UserResponseDto.md)
|
||||
|
||||
20
mobile/openapi/doc/AssetApi.md
generated
@@ -26,7 +26,7 @@ Method | HTTP request | Description
|
||||
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
||||
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
|
||||
[**updateAssetById**](AssetApi.md#updateassetbyid) | **PUT** /asset/assetById/{assetId} |
|
||||
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{assetId} |
|
||||
[**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload |
|
||||
|
||||
|
||||
@@ -274,7 +274,7 @@ Name | Type | Description | Notes
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **getAllAssets**
|
||||
> List<AssetResponseDto> getAllAssets()
|
||||
> List<AssetResponseDto> getAllAssets(ifNoneMatch)
|
||||
|
||||
|
||||
|
||||
@@ -291,9 +291,10 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = AssetApi();
|
||||
final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
|
||||
|
||||
try {
|
||||
final result = api_instance.getAllAssets();
|
||||
final result = api_instance.getAllAssets(ifNoneMatch);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->getAllAssets: $e\n');
|
||||
@@ -301,7 +302,10 @@ try {
|
||||
```
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**ifNoneMatch** | **String**| ETag of data already cached on the client | [optional]
|
||||
|
||||
### Return type
|
||||
|
||||
@@ -829,8 +833,8 @@ 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)
|
||||
|
||||
# **updateAssetById**
|
||||
> AssetResponseDto updateAssetById(assetId, updateAssetDto)
|
||||
# **updateAsset**
|
||||
> AssetResponseDto updateAsset(assetId, updateAssetDto)
|
||||
|
||||
|
||||
|
||||
@@ -851,10 +855,10 @@ final assetId = assetId_example; // String |
|
||||
final updateAssetDto = UpdateAssetDto(); // UpdateAssetDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateAssetById(assetId, updateAssetDto);
|
||||
final result = api_instance.updateAsset(assetId, updateAssetDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling AssetApi->updateAssetById: $e\n');
|
||||
print('Exception when calling AssetApi->updateAsset: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
5
mobile/openapi/doc/AssetResponseDto.md
generated
@@ -21,10 +21,11 @@ Name | Type | Description | Notes
|
||||
**mimeType** | **String** | |
|
||||
**duration** | **String** | |
|
||||
**webpPath** | **String** | |
|
||||
**encodedVideoPath** | **String** | |
|
||||
**encodedVideoPath** | **String** | | [optional]
|
||||
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional]
|
||||
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.md) | | [optional]
|
||||
**livePhotoVideoId** | **String** | |
|
||||
**livePhotoVideoId** | **String** | | [optional]
|
||||
**tags** | [**List<TagResponseDto>**](TagResponseDto.md) | | [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)
|
||||
|
||||
|
||||
105
mobile/openapi/doc/ConfigApi.md
generated
@@ -1,105 +0,0 @@
|
||||
# openapi.api.ConfigApi
|
||||
|
||||
## Load the API package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
All URIs are relative to */api*
|
||||
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**getSystemConfig**](ConfigApi.md#getsystemconfig) | **GET** /config/system |
|
||||
[**updateSystemConfig**](ConfigApi.md#updatesystemconfig) | **PUT** /config/system |
|
||||
|
||||
|
||||
# **getSystemConfig**
|
||||
> SystemConfigResponseDto getSystemConfig()
|
||||
|
||||
|
||||
|
||||
### 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 = ConfigApi();
|
||||
|
||||
try {
|
||||
final result = api_instance.getSystemConfig();
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling ConfigApi->getSystemConfig: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**SystemConfigResponseDto**](SystemConfigResponseDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
[bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **updateSystemConfig**
|
||||
> SystemConfigResponseDto updateSystemConfig(body)
|
||||
|
||||
|
||||
|
||||
### 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 = ConfigApi();
|
||||
final body = Object(); // Object |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateSystemConfig(body);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling ConfigApi->updateSystemConfig: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**body** | **Object**| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**SystemConfigResponseDto**](SystemConfigResponseDto.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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# openapi.model.AssetCountResponseDto
|
||||
# openapi.model.CreateTagDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
@@ -8,8 +8,8 @@ import 'package:openapi/api.dart';
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**photos** | **int** | |
|
||||
**videos** | **int** | |
|
||||
**type** | [**TagTypeEnum**](TagTypeEnum.md) | |
|
||||
**name** | **String** | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
68
mobile/openapi/doc/DeviceInfoApi.md
generated
@@ -11,13 +11,16 @@ Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**createDeviceInfo**](DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
||||
[**updateDeviceInfo**](DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
|
||||
[**upsertDeviceInfo**](DeviceInfoApi.md#upsertdeviceinfo) | **PUT** /device-info |
|
||||
|
||||
|
||||
# **createDeviceInfo**
|
||||
> DeviceInfoResponseDto createDeviceInfo(createDeviceInfoDto)
|
||||
> DeviceInfoResponseDto createDeviceInfo(upsertDeviceInfoDto)
|
||||
|
||||
|
||||
|
||||
@deprecated
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -29,10 +32,10 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = DeviceInfoApi();
|
||||
final createDeviceInfoDto = CreateDeviceInfoDto(); // CreateDeviceInfoDto |
|
||||
final upsertDeviceInfoDto = UpsertDeviceInfoDto(); // UpsertDeviceInfoDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.createDeviceInfo(createDeviceInfoDto);
|
||||
final result = api_instance.createDeviceInfo(upsertDeviceInfoDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling DeviceInfoApi->createDeviceInfo: $e\n');
|
||||
@@ -43,7 +46,7 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**createDeviceInfoDto** | [**CreateDeviceInfoDto**](CreateDeviceInfoDto.md)| |
|
||||
**upsertDeviceInfoDto** | [**UpsertDeviceInfoDto**](UpsertDeviceInfoDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
@@ -61,10 +64,12 @@ 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)
|
||||
|
||||
# **updateDeviceInfo**
|
||||
> DeviceInfoResponseDto updateDeviceInfo(updateDeviceInfoDto)
|
||||
> DeviceInfoResponseDto updateDeviceInfo(upsertDeviceInfoDto)
|
||||
|
||||
|
||||
|
||||
@deprecated
|
||||
|
||||
### Example
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
@@ -76,10 +81,10 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = DeviceInfoApi();
|
||||
final updateDeviceInfoDto = UpdateDeviceInfoDto(); // UpdateDeviceInfoDto |
|
||||
final upsertDeviceInfoDto = UpsertDeviceInfoDto(); // UpsertDeviceInfoDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateDeviceInfo(updateDeviceInfoDto);
|
||||
final result = api_instance.updateDeviceInfo(upsertDeviceInfoDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling DeviceInfoApi->updateDeviceInfo: $e\n');
|
||||
@@ -90,7 +95,54 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**updateDeviceInfoDto** | [**UpdateDeviceInfoDto**](UpdateDeviceInfoDto.md)| |
|
||||
**upsertDeviceInfoDto** | [**UpsertDeviceInfoDto**](UpsertDeviceInfoDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**DeviceInfoResponseDto**](DeviceInfoResponseDto.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)
|
||||
|
||||
# **upsertDeviceInfo**
|
||||
> DeviceInfoResponseDto upsertDeviceInfo(upsertDeviceInfoDto)
|
||||
|
||||
|
||||
|
||||
### 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 = DeviceInfoApi();
|
||||
final upsertDeviceInfoDto = UpsertDeviceInfoDto(); // UpsertDeviceInfoDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.upsertDeviceInfo(upsertDeviceInfoDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling DeviceInfoApi->upsertDeviceInfo: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**upsertDeviceInfoDto** | [**UpsertDeviceInfoDto**](UpsertDeviceInfoDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
|
||||
15
mobile/openapi/doc/GetAssetCountByTimeGroupDto.md
generated
@@ -1,15 +0,0 @@
|
||||
# openapi.model.GetAssetCountByTimeGroupDto
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
60
mobile/openapi/doc/SystemConfigApi.md
generated
@@ -10,11 +10,12 @@ All URIs are relative to */api*
|
||||
Method | HTTP request | Description
|
||||
------------- | ------------- | -------------
|
||||
[**getConfig**](SystemConfigApi.md#getconfig) | **GET** /system-config |
|
||||
[**getDefaults**](SystemConfigApi.md#getdefaults) | **GET** /system-config/defaults |
|
||||
[**updateConfig**](SystemConfigApi.md#updateconfig) | **PUT** /system-config |
|
||||
|
||||
|
||||
# **getConfig**
|
||||
> SystemConfigResponseDto getConfig()
|
||||
> SystemConfigDto getConfig()
|
||||
|
||||
|
||||
|
||||
@@ -43,7 +44,7 @@ This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**SystemConfigResponseDto**](SystemConfigResponseDto.md)
|
||||
[**SystemConfigDto**](SystemConfigDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
@@ -56,8 +57,8 @@ This endpoint does not need any parameter.
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **updateConfig**
|
||||
> SystemConfigResponseDto updateConfig(body)
|
||||
# **getDefaults**
|
||||
> SystemConfigDto getDefaults()
|
||||
|
||||
|
||||
|
||||
@@ -72,10 +73,53 @@ import 'package:openapi/api.dart';
|
||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||
|
||||
final api_instance = SystemConfigApi();
|
||||
final body = Object(); // Object |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateConfig(body);
|
||||
final result = api_instance.getDefaults();
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling SystemConfigApi->getDefaults: $e\n');
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
This endpoint does not need any parameter.
|
||||
|
||||
### Return type
|
||||
|
||||
[**SystemConfigDto**](SystemConfigDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
[bearer](../README.md#bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: application/json
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||
|
||||
# **updateConfig**
|
||||
> SystemConfigDto updateConfig(systemConfigDto)
|
||||
|
||||
|
||||
|
||||
### 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 = SystemConfigApi();
|
||||
final systemConfigDto = SystemConfigDto(); // SystemConfigDto |
|
||||
|
||||
try {
|
||||
final result = api_instance.updateConfig(systemConfigDto);
|
||||
print(result);
|
||||
} catch (e) {
|
||||
print('Exception when calling SystemConfigApi->updateConfig: $e\n');
|
||||
@@ -86,11 +130,11 @@ try {
|
||||
|
||||
Name | Type | Description | Notes
|
||||
------------- | ------------- | ------------- | -------------
|
||||
**body** | **Object**| |
|
||||
**systemConfigDto** | [**SystemConfigDto**](SystemConfigDto.md)| |
|
||||
|
||||
### Return type
|
||||
|
||||
[**SystemConfigResponseDto**](SystemConfigResponseDto.md)
|
||||
[**SystemConfigDto**](SystemConfigDto.md)
|
||||
|
||||
### Authorization
|
||||
|
||||
|
||||