Compare commits
47 Commits
v1.36.2_56
...
v1.38.0_60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
||||||
6
Makefile
@@ -4,6 +4,9 @@ dev:
|
|||||||
dev-new:
|
dev-new:
|
||||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
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:
|
dev-update:
|
||||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||||
|
|
||||||
@@ -27,3 +30,6 @@ prod-scale:
|
|||||||
|
|
||||||
api:
|
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">
|
<img src="design/immich-screenshots.png" title="Main Screenshot">
|
||||||
</a>
|
</a>
|
||||||
<br/>
|
<br/>
|
||||||
|
<p align="center">
|
||||||
|
<a href="README_zh_CN.md">中文</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
## Disclaimer
|
## 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 |
|
|  | 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?
|
### 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).
|
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?
|
### 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
|
# 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
|
:::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,
|
"position": 3,
|
||||||
"link": {
|
"link": {
|
||||||
"type": "generated-index",
|
"type": "generated-index",
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ Before enabling OAuth in Immich, a new client application needs to be configured
|
|||||||
|
|
||||||
2. Configure Redirect URIs/Origins
|
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`)
|
- 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:/`
|
- Mobile app redirect URL `app.immich:/`
|
||||||
|
|
||||||
:::caution
|
:::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.
|
||||||
@@ -42,17 +42,17 @@ You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobi
|
|||||||
|
|
||||||
## Enable OAuth
|
## 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 enabled | boolean | false | Enable/disable OAuth2 |
|
||||||
| OAUTH_ISSUER_URL | URL | (required) | Required. Self-discovery URL for client (from previous step) |
|
| 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 ID | string | (required) | Required. Client ID (from previous step) |
|
||||||
| OAUTH_CLIENT_SECRET | string | (required) | Required. Client Secret (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 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_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
|
:::info
|
||||||
The Issuer URL should look something like the following, and return a valid json document.
|
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.
|
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/
|
[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: [
|
presets: [
|
||||||
[
|
[
|
||||||
"classic",
|
"docusaurus-preset-openapi",
|
||||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
/** @type {import('docusaurus-preset-openapi').Options} */
|
||||||
({
|
({
|
||||||
docs: {
|
docs: {
|
||||||
showLastUpdateAuthor: true,
|
showLastUpdateAuthor: true,
|
||||||
@@ -42,6 +42,10 @@ const config = {
|
|||||||
// Remove this to remove the "edit this page" links.
|
// Remove this to remove the "edit this page" links.
|
||||||
editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
||||||
},
|
},
|
||||||
|
api: {
|
||||||
|
path: "../server/immich-openapi-specs.json",
|
||||||
|
routeBasePath: "/docs/api"
|
||||||
|
},
|
||||||
// blog: {
|
// blog: {
|
||||||
// showReadingTime: true,
|
// showReadingTime: true,
|
||||||
// editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
// editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
||||||
@@ -80,6 +84,11 @@ const config = {
|
|||||||
position: "right",
|
position: "right",
|
||||||
label: "Documentation",
|
label: "Documentation",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
to: "/docs/api",
|
||||||
|
position: "right",
|
||||||
|
label: "API"
|
||||||
|
},
|
||||||
{ to: "/blog", label: "Blog", position: "right" },
|
{ to: "/blog", label: "Blog", position: "right" },
|
||||||
{
|
{
|
||||||
href: "https://github.com/immich-app/immich",
|
href: "https://github.com/immich-app/immich",
|
||||||
|
|||||||
2742
docs/package-lock.json
generated
@@ -19,9 +19,11 @@
|
|||||||
"@docusaurus/preset-classic": "2.1.0",
|
"@docusaurus/preset-classic": "2.1.0",
|
||||||
"@mdx-js/react": "^1.6.22",
|
"@mdx-js/react": "^1.6.22",
|
||||||
"clsx": "^1.2.1",
|
"clsx": "^1.2.1",
|
||||||
|
"docusaurus-preset-openapi": "^0.6.3",
|
||||||
"prism-react-renderer": "^1.3.5",
|
"prism-react-renderer": "^1.3.5",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2"
|
"react-dom": "^17.0.2",
|
||||||
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@docusaurus/module-type-aliases": "2.1.0",
|
"@docusaurus/module-type-aliases": "2.1.0",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ android {
|
|||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "app.alextran.immich"
|
applicationId "app.alextran.immich"
|
||||||
minSdkVersion 23
|
minSdkVersion 23
|
||||||
targetSdkVersion flutter.targetSdkVersion
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_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.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>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
val args = call.arguments<ArrayList<*>>()!!
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
val requireUnmeteredNetwork = args.get(0) as Boolean
|
||||||
val requireCharging = args.get(1) 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)
|
result.success(true)
|
||||||
}
|
}
|
||||||
"disable" -> {
|
"disable" -> {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
|||||||
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
||||||
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
||||||
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
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"
|
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
||||||
|
|
||||||
@@ -62,12 +64,16 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
|||||||
*/
|
*/
|
||||||
fun configureWork(context: Context,
|
fun configureWork(context: Context,
|
||||||
requireWifi: Boolean = false,
|
requireWifi: Boolean = false,
|
||||||
requireCharging: Boolean = false) {
|
requireCharging: Boolean = false,
|
||||||
|
triggerUpdateDelay: Long = 5000,
|
||||||
|
triggerMaxDelay: Long = 50000) {
|
||||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
.edit()
|
.edit()
|
||||||
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
||||||
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
||||||
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
||||||
|
.putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay)
|
||||||
|
.putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay)
|
||||||
.apply()
|
.apply()
|
||||||
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
||||||
}
|
}
|
||||||
@@ -106,12 +112,14 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
||||||
|
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||||
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||||||
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||||||
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_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()
|
.build()
|
||||||
|
|
||||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 56,
|
"android.injected.version.code" => 60,
|
||||||
"android.injected.version.name" => "1.36.1",
|
"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')
|
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>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="123.14891">
|
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="63.132489">
|
||||||
|
|
||||||
</testcase>
|
</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>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
"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_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_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_selection_info": "Selection Info",
|
||||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
"backup_album_selection_page_total_assets": "Total unique assets",
|
||||||
"backup_all": "All",
|
"backup_all": "All",
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||||
|
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||||
"backup_controller_page_backup": "Backup",
|
"backup_controller_page_backup": "Backup",
|
||||||
"backup_controller_page_backup_selected": "Selected: ",
|
"backup_controller_page_backup_selected": "Selected: ",
|
||||||
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
||||||
@@ -120,6 +121,7 @@
|
|||||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||||
"profile_drawer_settings": "Settings",
|
"profile_drawer_settings": "Settings",
|
||||||
"profile_drawer_sign_out": "Sign Out",
|
"profile_drawer_sign_out": "Sign Out",
|
||||||
|
"profile_drawer_app_logs": "Logs",
|
||||||
"search_bar_hint": "Search your photos",
|
"search_bar_hint": "Search your photos",
|
||||||
"search_page_no_objects": "No Objects Info Available",
|
"search_page_no_objects": "No Objects Info Available",
|
||||||
"search_page_no_places": "No Places Info Available",
|
"search_page_no_places": "No Places Info Available",
|
||||||
@@ -133,6 +135,7 @@
|
|||||||
"setting_notifications_notify_hours": "{} hours",
|
"setting_notifications_notify_hours": "{} hours",
|
||||||
"setting_notifications_notify_immediately": "immediately",
|
"setting_notifications_notify_immediately": "immediately",
|
||||||
"setting_notifications_notify_minutes": "{} minutes",
|
"setting_notifications_notify_minutes": "{} minutes",
|
||||||
|
"setting_notifications_notify_seconds": "{} seconds",
|
||||||
"setting_notifications_notify_never": "never",
|
"setting_notifications_notify_never": "never",
|
||||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||||
"setting_notifications_title": "Notifications",
|
"setting_notifications_title": "Notifications",
|
||||||
@@ -140,6 +143,11 @@
|
|||||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
"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_title": "Show background backup detail progress",
|
||||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||||
|
"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_preview_title": "Load preview 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_original_title": "Load original image",
|
||||||
|
"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_pages_app_bar_settings": "Settings",
|
"setting_pages_app_bar_settings": "Settings",
|
||||||
"share_add": "Add",
|
"share_add": "Add",
|
||||||
"share_add_photos": "Add photos",
|
"share_add_photos": "Add photos",
|
||||||
@@ -164,8 +172,6 @@
|
|||||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||||
"theme_setting_theme_title": "Theme",
|
"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": "Acknowledge",
|
"version_announcement_overlay_ack": "Acknowledge",
|
||||||
"version_announcement_overlay_release_notes": "release notes",
|
"version_announcement_overlay_release_notes": "release notes",
|
||||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||||
|
|||||||
BIN
mobile/fonts/Inconsolata-Regular.ttf
Normal file
@@ -360,7 +360,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 72;
|
CURRENT_PROJECT_VERSION = 74;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 72;
|
CURRENT_PROJECT_VERSION = 74;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 72;
|
CURRENT_PROJECT_VERSION = 74;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.36.1</string>
|
<string>1.37.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>72</string>
|
<string>74</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true />
|
<true />
|
||||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.36.1"
|
version_number: "1.38.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
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>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: build_app" time="75.686541">
|
<testcase classname="fastlane.lanes" name="4: build_app" time="29.319346">
|
||||||
|
|
||||||
</testcase>
|
<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 classname="fastlane.lanes" name="5: upload_to_testflight" time="68.644406">
|
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
|
|||||||
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
||||||
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
||||||
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
||||||
|
const String assetEtagKey = 'immichAssetEtagKey'; // Key 5
|
||||||
|
|
||||||
// Login Info
|
// Login Info
|
||||||
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
||||||
@@ -25,7 +26,11 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box
|
|||||||
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
||||||
const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2
|
const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2
|
||||||
const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3
|
const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3
|
||||||
|
const String backupTriggerDelay = "immichBackupTriggerDelay"; // Key 4
|
||||||
|
|
||||||
// Duplicate asset
|
// Duplicate asset
|
||||||
const String duplicatedAssetsBox = "immichDuplicatedAssetsBox"; // Box
|
const String duplicatedAssetsBox = "immichDuplicatedAssetsBox"; // Box
|
||||||
const String duplicatedAssetsKey = "immichDuplicatedAssetsKey"; // Key 1
|
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/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/routing/tab_navigation_observer.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/app_state.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.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/release_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_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/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/immich_loading_overlay.dart';
|
||||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||||
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
||||||
@@ -31,8 +33,10 @@ void main() async {
|
|||||||
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
||||||
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
||||||
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
||||||
|
Hive.registerAdapter(ImmichLoggerMessageAdapter());
|
||||||
|
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
|
||||||
Hive.openBox(userInfoBox),
|
Hive.openBox(userInfoBox),
|
||||||
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
|
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
|
||||||
Hive.openBox(hiveGithubReleaseInfoBox),
|
Hive.openBox(hiveGithubReleaseInfoBox),
|
||||||
@@ -58,6 +62,9 @@ void main() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize Immich Logger Service
|
||||||
|
ImmichLogger().init();
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
EasyLocalization(
|
EasyLocalization(
|
||||||
supportedLocales: locales,
|
supportedLocales: locales,
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:math';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@@ -21,8 +19,38 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var box = Hive.box(userInfoBox);
|
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(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@@ -35,19 +63,9 @@ class AlbumThumbnailCard extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
ClipRRect(
|
ClipRRect(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
child: CachedNetworkImage(
|
child: album.albumThumbnailAssetId == null
|
||||||
memCacheHeight: max(400, cardSize.toInt() * 3),
|
? buildEmptyThumbnail()
|
||||||
width: cardSize,
|
: buildAlbumThumbnail(),
|
||||||
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}",
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8.0),
|
padding: const EdgeInsets.only(top: 8.0),
|
||||||
|
|||||||
@@ -42,10 +42,9 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
width: 60,
|
width: 60,
|
||||||
height: 60,
|
height: 60,
|
||||||
memCacheHeight: 200,
|
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageUrl: getAlbumThumbnailUrl(album),
|
imageUrl: getAlbumThumbnailUrl(album),
|
||||||
cacheKey: album.albumThumbnailAssetId,
|
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||||
httpHeaders: {
|
httpHeaders: {
|
||||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:immich_mobile/shared/models/asset.dart';
|
|||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
|
|
||||||
class ExifBottomSheet extends ConsumerWidget {
|
class ExifBottomSheet extends ConsumerWidget {
|
||||||
final Asset assetDetail;
|
final Asset assetDetail;
|
||||||
@@ -162,7 +163,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
|||||||
),
|
),
|
||||||
subtitle: exifInfo.exifImageHeight != null
|
subtitle: exifInfo.exifImageHeight != null
|
||||||
? Text(
|
? Text(
|
||||||
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${exifInfo.fileSizeInByte!}B ",
|
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${formatBytes(exifInfo.fileSizeInByte!)} ",
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -178,7 +179,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
|||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
bool allowMoving = _status == _RemoteImageStatus.full;
|
final bool forbidZoom = _status == _RemoteImageStatus.thumbnail;
|
||||||
|
|
||||||
return IgnorePointer(
|
return IgnorePointer(
|
||||||
ignoring: !allowMoving,
|
ignoring: forbidZoom,
|
||||||
child: Listener(
|
child: Listener(
|
||||||
onPointerMove: handleSwipUpDown,
|
onPointerMove: handleSwipUpDown,
|
||||||
child: PhotoView(
|
child: PhotoView(
|
||||||
@@ -115,7 +115,7 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
|
|
||||||
_thumbnailProvider = _authorizedImageProvider(
|
_thumbnailProvider = _authorizedImageProvider(
|
||||||
getThumbnailUrl(widget.asset.remote!),
|
getThumbnailUrl(widget.asset.remote!),
|
||||||
widget.asset.id,
|
getThumbnailCacheKey(widget.asset.remote!),
|
||||||
);
|
);
|
||||||
_imageProvider = _thumbnailProvider;
|
_imageProvider = _thumbnailProvider;
|
||||||
|
|
||||||
@@ -128,10 +128,10 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.threeStageLoading) {
|
if (widget.loadPreview) {
|
||||||
_previewProvider = _authorizedImageProvider(
|
_previewProvider = _authorizedImageProvider(
|
||||||
getThumbnailUrl(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
getThumbnailUrl(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
||||||
"${widget.asset.id}_previewStage",
|
getThumbnailCacheKey(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
||||||
);
|
);
|
||||||
_previewProvider.resolve(const ImageConfiguration()).addListener(
|
_previewProvider.resolve(const ImageConfiguration()).addListener(
|
||||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||||
@@ -140,15 +140,17 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
_fullProvider = _authorizedImageProvider(
|
if (widget.loadOriginal) {
|
||||||
getImageUrl(widget.asset.remote!),
|
_fullProvider = _authorizedImageProvider(
|
||||||
"${widget.asset.id}_fullStage",
|
getImageUrl(widget.asset.remote!),
|
||||||
);
|
getImageCacheKey(widget.asset.remote!),
|
||||||
_fullProvider.resolve(const ImageConfiguration()).addListener(
|
);
|
||||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
_fullProvider.resolve(const ImageConfiguration()).addListener(
|
||||||
_performStateTransition(_RemoteImageStatus.full, _fullProvider);
|
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||||
}),
|
_performStateTransition(_RemoteImageStatus.full, _fullProvider);
|
||||||
);
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -178,7 +180,8 @@ class RemotePhotoView extends StatefulWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.authToken,
|
required this.authToken,
|
||||||
required this.threeStageLoading,
|
required this.loadPreview,
|
||||||
|
required this.loadOriginal,
|
||||||
required this.isZoomedFunction,
|
required this.isZoomedFunction,
|
||||||
required this.isZoomedListener,
|
required this.isZoomedListener,
|
||||||
required this.onSwipeDown,
|
required this.onSwipeDown,
|
||||||
@@ -187,7 +190,8 @@ class RemotePhotoView extends StatefulWidget {
|
|||||||
|
|
||||||
final Asset asset;
|
final Asset asset;
|
||||||
final String authToken;
|
final String authToken;
|
||||||
final bool threeStageLoading;
|
final bool loadPreview;
|
||||||
|
final bool loadOriginal;
|
||||||
final void Function() onSwipeDown;
|
final void Function() onSwipeDown;
|
||||||
final void Function() onSwipeUp;
|
final void Function() onSwipeUp;
|
||||||
final void Function() isZoomedFunction;
|
final void Function() isZoomedFunction;
|
||||||
|
|||||||
@@ -31,8 +31,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final Box<dynamic> box = Hive.box(userInfoBox);
|
final Box<dynamic> box = Hive.box(userInfoBox);
|
||||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
final settings = ref.watch(appSettingsServiceProvider);
|
||||||
final threeStageLoading = useState(false);
|
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||||
|
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||||
final isZoomed = useState<bool>(false);
|
final isZoomed = useState<bool>(false);
|
||||||
final indexOfAsset = useState(assetList.indexOf(asset));
|
final indexOfAsset = useState(assetList.indexOf(asset));
|
||||||
final isPlayingMotionVideo = useState(false);
|
final isPlayingMotionVideo = useState(false);
|
||||||
@@ -43,8 +44,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
threeStageLoading.value = appSettingService
|
isLoadPreview.value =
|
||||||
.getSetting<bool>(AppSettingsEnum.threeStageLoading);
|
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
|
||||||
|
isLoadOriginal.value =
|
||||||
|
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
|
||||||
isPlayingMotionVideo.value = false;
|
isPlayingMotionVideo.value = false;
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
@@ -140,7 +143,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
isZoomedListener: isZoomedListener,
|
isZoomedListener: isZoomedListener,
|
||||||
asset: assetList[index],
|
asset: assetList[index],
|
||||||
heroTag: assetList[index].id,
|
heroTag: assetList[index].id,
|
||||||
threeStageLoading: threeStageLoading.value,
|
loadPreview: isLoadPreview.value,
|
||||||
|
loadOriginal: isLoadOriginal.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
final String authToken;
|
final String authToken;
|
||||||
final ValueNotifier<bool> isZoomedListener;
|
final ValueNotifier<bool> isZoomedListener;
|
||||||
final void Function() isZoomedFunction;
|
final void Function() isZoomedFunction;
|
||||||
final bool threeStageLoading;
|
final bool loadPreview;
|
||||||
|
final bool loadOriginal;
|
||||||
|
|
||||||
ImageViewerPage({
|
ImageViewerPage({
|
||||||
Key? key,
|
Key? key,
|
||||||
@@ -26,7 +27,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
required this.authToken,
|
required this.authToken,
|
||||||
required this.isZoomedFunction,
|
required this.isZoomedFunction,
|
||||||
required this.isZoomedListener,
|
required this.isZoomedListener,
|
||||||
required this.threeStageLoading,
|
required this.loadPreview,
|
||||||
|
required this.loadOriginal,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
Asset? assetDetail;
|
Asset? assetDetail;
|
||||||
@@ -74,7 +76,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
child: RemotePhotoView(
|
child: RemotePhotoView(
|
||||||
asset: asset,
|
asset: asset,
|
||||||
authToken: authToken,
|
authToken: authToken,
|
||||||
threeStageLoading: threeStageLoading,
|
loadPreview: loadPreview,
|
||||||
|
loadOriginal: loadOriginal,
|
||||||
isZoomedFunction: isZoomedFunction,
|
isZoomedFunction: isZoomedFunction,
|
||||||
isZoomedListener: isZoomedListener,
|
isZoomedListener: isZoomedListener,
|
||||||
onSwipeDown: () => AutoRouter.of(context).pop(),
|
onSwipeDown: () => AutoRouter.of(context).pop(),
|
||||||
|
|||||||
@@ -86,6 +86,8 @@ class BackgroundService {
|
|||||||
Future<bool> configureService({
|
Future<bool> configureService({
|
||||||
bool requireUnmetered = true,
|
bool requireUnmetered = true,
|
||||||
bool requireCharging = false,
|
bool requireCharging = false,
|
||||||
|
int triggerUpdateDelay = 5000,
|
||||||
|
int triggerMaxDelay = 50000,
|
||||||
}) async {
|
}) async {
|
||||||
if (!Platform.isAndroid) {
|
if (!Platform.isAndroid) {
|
||||||
return true;
|
return true;
|
||||||
@@ -93,7 +95,12 @@ class BackgroundService {
|
|||||||
try {
|
try {
|
||||||
final bool ok = await _foregroundChannel.invokeMethod(
|
final bool ok = await _foregroundChannel.invokeMethod(
|
||||||
'configure',
|
'configure',
|
||||||
[requireUnmetered, requireCharging],
|
[
|
||||||
|
requireUnmetered,
|
||||||
|
requireCharging,
|
||||||
|
triggerUpdateDelay,
|
||||||
|
triggerMaxDelay
|
||||||
|
],
|
||||||
);
|
);
|
||||||
return ok;
|
return ok;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -349,7 +356,6 @@ class BackgroundService {
|
|||||||
Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
|
Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
|
||||||
Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
|
Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
ApiService apiService = ApiService();
|
ApiService apiService = ApiService();
|
||||||
apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey));
|
apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey));
|
||||||
apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey));
|
apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey));
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class BackUpState {
|
|||||||
final bool backgroundBackup;
|
final bool backgroundBackup;
|
||||||
final bool backupRequireWifi;
|
final bool backupRequireWifi;
|
||||||
final bool backupRequireCharging;
|
final bool backupRequireCharging;
|
||||||
|
final int backupTriggerDelay;
|
||||||
|
|
||||||
/// All available albums on the device
|
/// All available albums on the device
|
||||||
final List<AvailableAlbum> availableAlbums;
|
final List<AvailableAlbum> availableAlbums;
|
||||||
@@ -42,6 +43,7 @@ class BackUpState {
|
|||||||
required this.backgroundBackup,
|
required this.backgroundBackup,
|
||||||
required this.backupRequireWifi,
|
required this.backupRequireWifi,
|
||||||
required this.backupRequireCharging,
|
required this.backupRequireCharging,
|
||||||
|
required this.backupTriggerDelay,
|
||||||
required this.availableAlbums,
|
required this.availableAlbums,
|
||||||
required this.selectedBackupAlbums,
|
required this.selectedBackupAlbums,
|
||||||
required this.excludedBackupAlbums,
|
required this.excludedBackupAlbums,
|
||||||
@@ -59,6 +61,7 @@ class BackUpState {
|
|||||||
bool? backgroundBackup,
|
bool? backgroundBackup,
|
||||||
bool? backupRequireWifi,
|
bool? backupRequireWifi,
|
||||||
bool? backupRequireCharging,
|
bool? backupRequireCharging,
|
||||||
|
int? backupTriggerDelay,
|
||||||
List<AvailableAlbum>? availableAlbums,
|
List<AvailableAlbum>? availableAlbums,
|
||||||
Set<AvailableAlbum>? selectedBackupAlbums,
|
Set<AvailableAlbum>? selectedBackupAlbums,
|
||||||
Set<AvailableAlbum>? excludedBackupAlbums,
|
Set<AvailableAlbum>? excludedBackupAlbums,
|
||||||
@@ -76,6 +79,7 @@ class BackUpState {
|
|||||||
backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi,
|
backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi,
|
||||||
backupRequireCharging:
|
backupRequireCharging:
|
||||||
backupRequireCharging ?? this.backupRequireCharging,
|
backupRequireCharging ?? this.backupRequireCharging,
|
||||||
|
backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay,
|
||||||
availableAlbums: availableAlbums ?? this.availableAlbums,
|
availableAlbums: availableAlbums ?? this.availableAlbums,
|
||||||
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
||||||
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
||||||
@@ -88,7 +92,7 @@ class BackUpState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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
|
@override
|
||||||
@@ -105,6 +109,7 @@ class BackUpState {
|
|||||||
other.backgroundBackup == backgroundBackup &&
|
other.backgroundBackup == backgroundBackup &&
|
||||||
other.backupRequireWifi == backupRequireWifi &&
|
other.backupRequireWifi == backupRequireWifi &&
|
||||||
other.backupRequireCharging == backupRequireCharging &&
|
other.backupRequireCharging == backupRequireCharging &&
|
||||||
|
other.backupTriggerDelay == backupTriggerDelay &&
|
||||||
collectionEquals(other.availableAlbums, availableAlbums) &&
|
collectionEquals(other.availableAlbums, availableAlbums) &&
|
||||||
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
||||||
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
||||||
@@ -126,6 +131,7 @@ class BackUpState {
|
|||||||
backgroundBackup.hashCode ^
|
backgroundBackup.hashCode ^
|
||||||
backupRequireWifi.hashCode ^
|
backupRequireWifi.hashCode ^
|
||||||
backupRequireCharging.hashCode ^
|
backupRequireCharging.hashCode ^
|
||||||
|
backupTriggerDelay.hashCode ^
|
||||||
availableAlbums.hashCode ^
|
availableAlbums.hashCode ^
|
||||||
selectedBackupAlbums.hashCode ^
|
selectedBackupAlbums.hashCode ^
|
||||||
excludedBackupAlbums.hashCode ^
|
excludedBackupAlbums.hashCode ^
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cancellation_token_http/http.dart';
|
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:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.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/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/server_info.service.dart';
|
import 'package:immich_mobile/shared/services/server_info.service.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
backgroundBackup: false,
|
backgroundBackup: false,
|
||||||
backupRequireWifi: true,
|
backupRequireWifi: true,
|
||||||
backupRequireCharging: false,
|
backupRequireCharging: false,
|
||||||
|
backupTriggerDelay: 5000,
|
||||||
serverInfo: ServerInfoResponseDto(
|
serverInfo: ServerInfoResponseDto(
|
||||||
diskAvailable: "0",
|
diskAvailable: "0",
|
||||||
diskAvailableRaw: 0,
|
diskAvailableRaw: 0,
|
||||||
@@ -62,6 +64,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
getBackupInfo();
|
getBackupInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final log = Logger('BackupNotifier');
|
||||||
final BackupService _backupService;
|
final BackupService _backupService;
|
||||||
final ServerInfoService _serverInfoService;
|
final ServerInfoService _serverInfoService;
|
||||||
final AuthenticationState _authState;
|
final AuthenticationState _authState;
|
||||||
@@ -117,18 +120,26 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
bool? enabled,
|
bool? enabled,
|
||||||
bool? requireWifi,
|
bool? requireWifi,
|
||||||
bool? requireCharging,
|
bool? requireCharging,
|
||||||
|
int? triggerDelay,
|
||||||
required void Function(String msg) onError,
|
required void Function(String msg) onError,
|
||||||
required void Function() onBatteryInfo,
|
required void Function() onBatteryInfo,
|
||||||
}) async {
|
}) async {
|
||||||
assert(enabled != null || requireWifi != null || requireCharging != null);
|
assert(
|
||||||
|
enabled != null ||
|
||||||
|
requireWifi != null ||
|
||||||
|
requireCharging != null ||
|
||||||
|
triggerDelay != null,
|
||||||
|
);
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
final bool wasEnabled = state.backgroundBackup;
|
final bool wasEnabled = state.backgroundBackup;
|
||||||
final bool wasWifi = state.backupRequireWifi;
|
final bool wasWifi = state.backupRequireWifi;
|
||||||
final bool wasCharing = state.backupRequireCharging;
|
final bool wasCharging = state.backupRequireCharging;
|
||||||
|
final int oldTriggerDelay = state.backupTriggerDelay;
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backgroundBackup: enabled,
|
backgroundBackup: enabled,
|
||||||
backupRequireWifi: requireWifi,
|
backupRequireWifi: requireWifi,
|
||||||
backupRequireCharging: requireCharging,
|
backupRequireCharging: requireCharging,
|
||||||
|
backupTriggerDelay: triggerDelay,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (state.backgroundBackup) {
|
if (state.backgroundBackup) {
|
||||||
@@ -143,17 +154,22 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
await _backgroundService.configureService(
|
await _backgroundService.configureService(
|
||||||
requireUnmetered: state.backupRequireWifi,
|
requireUnmetered: state.backupRequireWifi,
|
||||||
requireCharging: state.backupRequireCharging,
|
requireCharging: state.backupRequireCharging,
|
||||||
|
triggerUpdateDelay: state.backupTriggerDelay,
|
||||||
|
triggerMaxDelay: state.backupTriggerDelay * 10,
|
||||||
);
|
);
|
||||||
if (success) {
|
if (success) {
|
||||||
await Hive.box(backgroundBackupInfoBox)
|
final box = Hive.box(backgroundBackupInfoBox);
|
||||||
.put(backupRequireWifi, state.backupRequireWifi);
|
await Future.wait([
|
||||||
await Hive.box(backgroundBackupInfoBox)
|
box.put(backupRequireWifi, state.backupRequireWifi),
|
||||||
.put(backupRequireCharging, state.backupRequireCharging);
|
box.put(backupRequireCharging, state.backupRequireCharging),
|
||||||
|
box.put(backupTriggerDelay, state.backupTriggerDelay),
|
||||||
|
]);
|
||||||
} else {
|
} else {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backgroundBackup: wasEnabled,
|
backgroundBackup: wasEnabled,
|
||||||
backupRequireWifi: wasWifi,
|
backupRequireWifi: wasWifi,
|
||||||
backupRequireCharging: wasCharing,
|
backupRequireCharging: wasCharging,
|
||||||
|
backupTriggerDelay: oldTriggerDelay,
|
||||||
);
|
);
|
||||||
onError("backup_controller_page_background_configure_error");
|
onError("backup_controller_page_background_configure_error");
|
||||||
}
|
}
|
||||||
@@ -171,9 +187,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
/// Get all album on the device
|
/// Get all album on the device
|
||||||
/// Get all selected and excluded album from the user's persistent storage
|
/// 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
|
/// 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 {
|
Future<void> _getBackupAlbumsInfo() async {
|
||||||
|
Stopwatch stopwatch = Stopwatch()..start();
|
||||||
// Get all albums on the device
|
// Get all albums on the device
|
||||||
List<AvailableAlbum> availableAlbums = [];
|
List<AvailableAlbum> availableAlbums = [];
|
||||||
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
||||||
@@ -181,6 +198,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
type: RequestType.common,
|
type: RequestType.common,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
log.info('Found ${albums.length} local albums');
|
||||||
|
|
||||||
for (AssetPathEntity album in albums) {
|
for (AssetPathEntity album in albums) {
|
||||||
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
||||||
|
|
||||||
@@ -218,13 +237,16 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (backupAlbumInfo == null) {
|
if (backupAlbumInfo == null) {
|
||||||
debugPrint("[ERROR] getting Hive backup album infomation");
|
log.severe(
|
||||||
|
"backupAlbumInfo == null",
|
||||||
|
"Failed to get Hive backup album information",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// First time backup - set isAll album is the default one for backup.
|
// First time backup - set isAll album is the default one for backup.
|
||||||
if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
|
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
|
// Get album that contains all assets
|
||||||
var list = await PhotoManager.getAssetPathList(
|
var list = await PhotoManager.getAssetPathList(
|
||||||
@@ -286,9 +308,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
selectedBackupAlbums: selectedAlbums,
|
selectedBackupAlbums: selectedAlbums,
|
||||||
excludedBackupAlbums: excludedAlbums,
|
excludedBackupAlbums: excludedAlbums,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e, stackTrace) {
|
||||||
debugPrint("[ERROR] Failed to generate album from id $e");
|
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) {
|
if (allUniqueAssets.isEmpty) {
|
||||||
debugPrint("No Asset On Device");
|
log.info("Not found albums or assets on the device to backup");
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backupProgress: BackUpProgressEnum.idle,
|
backupProgress: BackUpProgressEnum.idle,
|
||||||
allAssetsInDatabase: allAssetsInDatabase,
|
allAssetsInDatabase: allAssetsInDatabase,
|
||||||
@@ -360,14 +384,14 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Get all necessary information for calculating the available albums,
|
/// Get all necessary information for calculating the available albums,
|
||||||
/// which albums are selected or excluded
|
/// which albums are selected or excluded
|
||||||
/// and then update the UI according to those information
|
/// and then update the UI according to those information
|
||||||
///
|
|
||||||
Future<void> getBackupInfo() async {
|
Future<void> getBackupInfo() async {
|
||||||
final bool isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
var isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
||||||
|
|
||||||
state = state.copyWith(backgroundBackup: isEnabled);
|
state = state.copyWith(backgroundBackup: isEnabled);
|
||||||
|
|
||||||
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
||||||
await _getBackupAlbumsInfo();
|
await _getBackupAlbumsInfo();
|
||||||
await _updateServerInfo();
|
await _updateServerInfo();
|
||||||
@@ -375,10 +399,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Save user selection of selected albums and excluded albums to
|
/// Save user selection of selected albums and excluded albums to
|
||||||
/// Hive database
|
/// Hive database
|
||||||
///
|
|
||||||
void _updatePersistentAlbumsSelection() {
|
void _updatePersistentAlbumsSelection() {
|
||||||
final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
||||||
Box<HiveBackupAlbums> backupAlbumInfoBox =
|
Box<HiveBackupAlbums> backupAlbumInfoBox =
|
||||||
@@ -398,10 +420,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Invoke backup process
|
/// Invoke backup process
|
||||||
///
|
|
||||||
Future<void> startBackupProcess() async {
|
Future<void> startBackupProcess() async {
|
||||||
|
debugPrint("Start backup process");
|
||||||
assert(state.backupProgress == BackUpProgressEnum.idle);
|
assert(state.backupProgress == BackUpProgressEnum.idle);
|
||||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
||||||
|
|
||||||
@@ -412,13 +433,12 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
await PhotoManager.clearFileCache();
|
await PhotoManager.clearFileCache();
|
||||||
|
|
||||||
if (state.allUniqueAssets.isEmpty) {
|
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);
|
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
||||||
|
|
||||||
// Remove item that has already been backed up
|
// Remove item that has already been backed up
|
||||||
for (var assetId in state.allAssetsInDatabase) {
|
for (var assetId in state.allAssetsInDatabase) {
|
||||||
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
||||||
@@ -530,7 +550,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
|
|
||||||
// User has been logged out return
|
// User has been logged out return
|
||||||
if (accessKey == null || !_authState.isAuthenticated) {
|
if (accessKey == null || !_authState.isAuthenticated) {
|
||||||
debugPrint("[resumeBackup] not authenticated - abort");
|
log.info("[_resumeBackup] not authenticated - abort");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -539,17 +559,17 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
_authState.deviceInfo.isAutoBackup) {
|
_authState.deviceInfo.isAutoBackup) {
|
||||||
// check if backup is alreayd in process - then return
|
// check if backup is alreayd in process - then return
|
||||||
if (state.backupProgress == BackUpProgressEnum.inProgress) {
|
if (state.backupProgress == BackUpProgressEnum.inProgress) {
|
||||||
debugPrint("[resumeBackup] Backup is already in progress - abort");
|
log.info("[_resumeBackup] Backup is already in progress - abort");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state.backupProgress == BackUpProgressEnum.inBackground) {
|
if (state.backupProgress == BackUpProgressEnum.inBackground) {
|
||||||
debugPrint("[resumeBackup] Background backup is running - abort");
|
log.info("[_resumeBackup] Background backup is running - abort");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run backup
|
// Run backup
|
||||||
debugPrint("[resumeBackup] Start back up");
|
log.info("[_resumeBackup] Start back up");
|
||||||
await startBackupProcess();
|
await startBackupProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,7 +585,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground);
|
state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground);
|
||||||
final bool hasLock = await _backgroundService.acquireLock();
|
final bool hasLock = await _backgroundService.acquireLock();
|
||||||
if (!hasLock) {
|
if (!hasLock) {
|
||||||
debugPrint("WARNING [resumeBackup] failed to acquireLock");
|
log.warning("WARNING [resumeBackup] failed to acquireLock");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
@@ -596,6 +616,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
excludedBackupAlbums: excludedAlbums,
|
excludedBackupAlbums: excludedAlbums,
|
||||||
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
||||||
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
||||||
|
backupTriggerDelay: backgroundBox.get(backupTriggerDelay),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return _resumeBackup();
|
return _resumeBackup();
|
||||||
@@ -612,7 +633,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]);
|
AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]);
|
||||||
result.add(a.copyWith(lastBackup: times[i]));
|
result.add(a.copyWith(lastBackup: times[i]));
|
||||||
} on StateError {
|
} 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;
|
return result;
|
||||||
@@ -631,21 +656,29 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
await Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).close();
|
await Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).close();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
log.info("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (Hive.isBoxOpen(duplicatedAssetsBox)) {
|
if (Hive.isBoxOpen(duplicatedAssetsBox)) {
|
||||||
await Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox).close();
|
await Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox).close();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error, stackTrace) {
|
||||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
log.severe(
|
||||||
|
"[_notifyBackgroundServiceCanRun] failed to close box",
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
|
if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
|
||||||
await Hive.box(backgroundBackupInfoBox).close();
|
await Hive.box(backgroundBackupInfoBox).close();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error, stackTrace) {
|
||||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
log.severe(
|
||||||
|
"[_notifyBackgroundServiceCanRun] failed to close box",
|
||||||
|
error,
|
||||||
|
stackTrace,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_backgroundService.releaseLock();
|
_backgroundService.releaseLock();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -376,8 +376,8 @@ class BackupService {
|
|||||||
DeviceTypeEnum deviceType,
|
DeviceTypeEnum deviceType,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
var updatedDeviceInfo = await _apiService.deviceInfoApi.updateDeviceInfo(
|
var updatedDeviceInfo = await _apiService.deviceInfoApi.upsertDeviceInfo(
|
||||||
UpdateDeviceInfoDto(
|
UpsertDeviceInfoDto(
|
||||||
deviceId: deviceId,
|
deviceId: deviceId,
|
||||||
deviceType: deviceType,
|
deviceType: deviceType,
|
||||||
isAutoBackup: status,
|
isAutoBackup: status,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
|||||||
title: Column(
|
title: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
"${album.name} (${album.assetCountAsync})",
|
album.name,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/immich_colors.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/providers/backup.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
|
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.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);
|
const BackupAlbumSelectionPage({Key? key}) : super(key: key);
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
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 selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||||
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||||
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||||
|
final albums = useState<List<AvailableAlbum>>(
|
||||||
|
ref.watch(backupProvider).availableAlbums,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
@@ -28,7 +32,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
buildAlbumSelectionList() {
|
buildAlbumSelectionList() {
|
||||||
if (availableAlbums.isEmpty) {
|
if (albums.value.isEmpty) {
|
||||||
return const Center(
|
return const Center(
|
||||||
child: ImmichLoadingIndicator(),
|
child: ImmichLoadingIndicator(),
|
||||||
);
|
);
|
||||||
@@ -38,17 +42,17 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
height: 265,
|
height: 265,
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: availableAlbums.length,
|
itemCount: albums.value.length,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
itemBuilder: ((context, index) {
|
itemBuilder: ((context, index) {
|
||||||
var thumbnailData = availableAlbums[index].thumbnailData;
|
var thumbnailData = albums.value[index].thumbnailData;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: index == 0
|
padding: index == 0
|
||||||
? const EdgeInsets.only(left: 16.00)
|
? const EdgeInsets.only(left: 16.00)
|
||||||
: const EdgeInsets.all(0),
|
: const EdgeInsets.all(0),
|
||||||
child: AlbumInfoCard(
|
child: AlbumInfoCard(
|
||||||
imageData: thumbnailData,
|
imageData: thumbnailData,
|
||||||
albumInfo: availableAlbums[index],
|
albumInfo: albums.value[index],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
@@ -79,15 +83,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
child: Chip(
|
child: Chip(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
album.name,
|
album.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 10,
|
fontSize: 10,
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
color: isDarkTheme ? Colors.black : Colors.white,
|
||||||
? Colors.black
|
|
||||||
: Colors.white,
|
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -119,7 +121,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
child: Chip(
|
child: Chip(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
album.name,
|
album.name,
|
||||||
@@ -143,6 +145,46 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
}).toSet();
|
}).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(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
@@ -188,7 +230,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
child: Card(
|
child: Card(
|
||||||
margin: const EdgeInsets.all(0),
|
margin: const EdgeInsets.all(0),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(5),
|
borderRadius: BorderRadius.circular(10),
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: isDarkTheme
|
color: isDarkTheme
|
||||||
? const Color.fromARGB(255, 0, 0, 0)
|
? const Color.fromARGB(255, 0, 0, 0)
|
||||||
@@ -225,8 +267,11 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
"backup_album_selection_page_albums_device"
|
"backup_album_selection_page_albums_device".tr(
|
||||||
.tr(args: [availableAlbums.length.toString()]),
|
args: [
|
||||||
|
ref.watch(backupProvider).availableAlbums.length.toString()
|
||||||
|
],
|
||||||
|
),
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||||
),
|
),
|
||||||
subtitle: Padding(
|
subtitle: Padding(
|
||||||
@@ -254,7 +299,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
elevation: 5,
|
elevation: 5,
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -284,6 +329,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
buildSearchBar(),
|
||||||
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
padding: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: buildAlbumSelectionList(),
|
child: buildAlbumSelectionList(),
|
||||||
|
|||||||
@@ -198,6 +198,46 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
final bool isWifiRequired = backupState.backupRequireWifi;
|
final bool isWifiRequired = backupState.backupRequireWifi;
|
||||||
final bool isChargingRequired = backupState.backupRequireCharging;
|
final bool isChargingRequired = backupState.backupRequireCharging;
|
||||||
final Color activeColor = Theme.of(context).primaryColor;
|
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(
|
return ListTile(
|
||||||
isThreeLine: true,
|
isThreeLine: true,
|
||||||
leading: isBackgroundEnabled
|
leading: isBackgroundEnabled
|
||||||
@@ -264,6 +304,35 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
)
|
)
|
||||||
: null,
|
: 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(
|
ElevatedButton(
|
||||||
onPressed: () =>
|
onPressed: () =>
|
||||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
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/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/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:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
|
||||||
|
|
||||||
final assetServiceProvider = Provider(
|
final assetServiceProvider = Provider(
|
||||||
(ref) => AssetService(
|
(ref) => AssetService(
|
||||||
@@ -25,42 +27,31 @@ class AssetService {
|
|||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
final BackupService _backupService;
|
final BackupService _backupService;
|
||||||
final BackgroundService _backgroundService;
|
final BackgroundService _backgroundService;
|
||||||
|
final log = Logger('AssetService');
|
||||||
|
|
||||||
AssetService(this._apiService, this._backupService, this._backgroundService);
|
AssetService(this._apiService, this._backupService, this._backgroundService);
|
||||||
|
|
||||||
/// Returns all local, remote assets in that order
|
/// Returns `null` if the server state did not change, else list of assets
|
||||||
Future<List<Asset>> getAllAsset({bool urgent = false}) async {
|
Future<Pair<List<Asset>?, String?>> getRemoteAssets({String? etag}) async {
|
||||||
final List<Asset> assets = [];
|
|
||||||
try {
|
try {
|
||||||
// not using `await` here to fetch local & remote assets concurrently
|
final Pair<List<AssetResponseDto>, String?>? remote =
|
||||||
final Future<List<AssetResponseDto>?> remoteTask =
|
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
||||||
_apiService.assetApi.getAllAssets();
|
if (remote == null) {
|
||||||
final Iterable<AssetEntity> newLocalAssets;
|
return const Pair(null, null);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
return Pair(
|
||||||
assets.addAll(newLocalAssets.map((e) => Asset.local(e)));
|
remote.first.map(Asset.remote).toList(growable: false),
|
||||||
// the order (first all local, then remote assets) is important!
|
remote.second,
|
||||||
assets.addAll(remoteAssets.map((e) => Asset.remote(e)));
|
);
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
debugPrint("Error [getAllAsset] ${e.toString()}");
|
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
|
/// if [urgent] is `true`, do not block by waiting on the background service
|
||||||
/// to finish running. Returns an empty list instead after a timeout.
|
/// to finish running. Returns `null` instead after a timeout.
|
||||||
Future<List<AssetEntity>> _getLocalAssets(bool urgent) async {
|
Future<List<Asset>?> getLocalAssets({bool urgent = false}) async {
|
||||||
try {
|
try {
|
||||||
final Future<bool> hasAccess = urgent
|
final Future<bool> hasAccess = urgent
|
||||||
? _backgroundService.hasAccess
|
? _backgroundService.hasAccess
|
||||||
@@ -71,15 +62,16 @@ class AssetService {
|
|||||||
}
|
}
|
||||||
final box = await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
final box = await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
||||||
final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey);
|
final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey);
|
||||||
|
if (backupAlbumInfo != null) {
|
||||||
return backupAlbumInfo != null
|
return (await _backupService
|
||||||
? await _backupService
|
.buildUploadCandidates(backupAlbumInfo.deepCopy()))
|
||||||
.buildUploadCandidates(backupAlbumInfo.deepCopy())
|
.map(Asset.local)
|
||||||
: [];
|
.toList(growable: false);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Error [_getLocalAssets] ${e.toString()}");
|
debugPrint("Error [_getLocalAssets] ${e.toString()}");
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Asset?> getAssetById(String assetId) async {
|
Future<Asset?> getAssetById(String assetId) async {
|
||||||
|
|||||||
@@ -75,14 +75,11 @@ class ControlBottomAppBar extends ConsumerWidget {
|
|||||||
width: 100,
|
width: 100,
|
||||||
height: 100,
|
height: 100,
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
imageUrl: getAlbumThumbnailUrl(
|
imageUrl: getAlbumThumbnailUrl(album),
|
||||||
album,
|
|
||||||
type: ThumbnailFormat.JPEG,
|
|
||||||
),
|
|
||||||
httpHeaders: {
|
httpHeaders: {
|
||||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||||
},
|
},
|
||||||
cacheKey: "${album.albumThumbnailAssetId}",
|
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
snap: false,
|
snap: false,
|
||||||
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(5),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
leading: Builder(
|
leading: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/profile_drawer_header.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/server_info_box.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/routing/router.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.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';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
|
|
||||||
class ProfileDrawer extends HookConsumerWidget {
|
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(
|
return Drawer(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
@@ -80,6 +104,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
|||||||
children: [
|
children: [
|
||||||
const ProfileDrawerHeader(),
|
const ProfileDrawerHeader(),
|
||||||
buildSettingButton(),
|
buildSettingButton(),
|
||||||
|
buildAppLogButton(),
|
||||||
buildSignoutButton(),
|
buildSignoutButton(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.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/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/websocket.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/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:immich_mobile/shared/ui/immich_toast.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@@ -37,6 +40,8 @@ class HomePage extends HookConsumerWidget {
|
|||||||
final albums = ref.watch(albumProvider);
|
final albums = ref.watch(albumProvider);
|
||||||
final albumService = ref.watch(albumServiceProvider);
|
final albumService = ref.watch(albumServiceProvider);
|
||||||
|
|
||||||
|
final tipOneOpacity = useState(0.0);
|
||||||
|
|
||||||
useEffect(
|
useEffect(
|
||||||
() {
|
() {
|
||||||
ref.read(websocketProvider.notifier).connect();
|
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(
|
return SafeArea(
|
||||||
bottom: !multiselectEnabled.state,
|
bottom: !multiselectEnabled.state,
|
||||||
top: true,
|
top: true,
|
||||||
@@ -164,15 +212,17 @@ class HomePage extends HookConsumerWidget {
|
|||||||
top: selectionEnabledHook.value ? 0 : 60,
|
top: selectionEnabledHook.value ? 0 : 60,
|
||||||
bottom: 0.0,
|
bottom: 0.0,
|
||||||
),
|
),
|
||||||
child: ImmichAssetGrid(
|
child: ref.watch(assetProvider).isEmpty
|
||||||
renderList: renderList,
|
? buildLoadingIndicator()
|
||||||
assetsPerRow:
|
: ImmichAssetGrid(
|
||||||
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
renderList: renderList,
|
||||||
showStorageIndicator: appSettingService
|
assetsPerRow: appSettingService
|
||||||
.getSetting(AppSettingsEnum.storageIndicator),
|
.getSetting(AppSettingsEnum.tilesPerRow),
|
||||||
listener: selectionListener,
|
showStorageIndicator: appSettingService
|
||||||
selectionActive: selectionEnabledHook.value,
|
.getSetting(AppSettingsEnum.storageIndicator),
|
||||||
),
|
listener: selectionListener,
|
||||||
|
selectionActive: selectionEnabledHook.value,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
if (selectionEnabledHook.value)
|
if (selectionEnabledHook.value)
|
||||||
ControlBottomAppBar(
|
ControlBottomAppBar(
|
||||||
|
|||||||
@@ -101,11 +101,14 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> logout() async {
|
Future<bool> logout() async {
|
||||||
Hive.box(userInfoBox).delete(accessTokenKey);
|
|
||||||
state = state.copyWith(isAuthenticated: false);
|
state = state.copyWith(isAuthenticated: false);
|
||||||
_assetCacheService.invalidate();
|
await Future.wait([
|
||||||
_albumCacheService.invalidate();
|
Hive.box(userInfoBox).delete(accessTokenKey),
|
||||||
_sharedAlbumCacheService.invalidate();
|
Hive.box(userInfoBox).delete(assetEtagKey),
|
||||||
|
_assetCacheService.invalidate(),
|
||||||
|
_albumCacheService.invalidate(),
|
||||||
|
_sharedAlbumCacheService.invalidate(),
|
||||||
|
]);
|
||||||
|
|
||||||
// Remove login info from local storage
|
// Remove login info from local storage
|
||||||
var loginInfo =
|
var loginInfo =
|
||||||
@@ -115,7 +118,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
loginInfo.password = "";
|
loginInfo.password = "";
|
||||||
loginInfo.isSaveLogin = false;
|
loginInfo.isSaveLogin = false;
|
||||||
|
|
||||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
await Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
||||||
savedLoginInfoKey,
|
savedLoginInfoKey,
|
||||||
loginInfo,
|
loginInfo,
|
||||||
);
|
);
|
||||||
@@ -207,8 +210,8 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
|||||||
// Register device info
|
// Register device info
|
||||||
try {
|
try {
|
||||||
DeviceInfoResponseDto? deviceInfo =
|
DeviceInfoResponseDto? deviceInfo =
|
||||||
await _apiService.deviceInfoApi.createDeviceInfo(
|
await _apiService.deviceInfoApi.upsertDeviceInfo(
|
||||||
CreateDeviceInfoDto(
|
UpsertDeviceInfoDto(
|
||||||
deviceId: state.deviceId,
|
deviceId: state.deviceId,
|
||||||
deviceType: state.deviceType,
|
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(
|
return Center(
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: const BoxConstraints(maxWidth: 300),
|
constraints: const BoxConstraints(maxWidth: 300),
|
||||||
@@ -92,10 +99,13 @@ class LoginForm extends HookConsumerWidget {
|
|||||||
runSpacing: 16,
|
runSpacing: 16,
|
||||||
alignment: WrapAlignment.center,
|
alignment: WrapAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const Image(
|
GestureDetector(
|
||||||
image: AssetImage('assets/immich-logo-no-outline.png'),
|
onDoubleTap: () => populateTestLoginInfo(),
|
||||||
width: 100,
|
child: const Image(
|
||||||
filterQuality: FilterQuality.high,
|
image: AssetImage('assets/immich-logo-no-outline.png'),
|
||||||
|
width: 100,
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'IMMICH',
|
'IMMICH',
|
||||||
|
|||||||
@@ -1,14 +1,65 @@
|
|||||||
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/login/ui/login_form.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 {
|
class LoginPage extends HookConsumerWidget {
|
||||||
const LoginPage({Key? key}) : super(key: key);
|
const LoginPage({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return const Scaffold(
|
final appVersion = useState('0.0.0');
|
||||||
body: LoginForm(),
|
|
||||||
|
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';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
|
|
||||||
enum AppSettingsEnum<T> {
|
enum AppSettingsEnum<T> {
|
||||||
threeStageLoading<bool>("threeStageLoading", false),
|
loadPreview<bool>("loadPreview", true),
|
||||||
|
loadOriginal<bool>("loadOriginal", false),
|
||||||
themeMode<String>("themeMode", "system"), // "light","dark","system"
|
themeMode<String>("themeMode", "system"), // "light","dark","system"
|
||||||
tilesPerRow<int>("tilesPerRow", 4),
|
tilesPerRow<int>("tilesPerRow", 4),
|
||||||
uploadErrorNotificationGracePeriod<int>(
|
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:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.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({
|
const ImageViewerQualitySetting({
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@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(
|
return ExpansionTile(
|
||||||
textColor: Theme.of(context).primaryColor,
|
textColor: Theme.of(context).primaryColor,
|
||||||
title: const Text(
|
title: const Text(
|
||||||
@@ -23,8 +39,27 @@ class ImageViewerQualitySetting extends StatelessWidget {
|
|||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
children: const [
|
children: [
|
||||||
ThreeStageLoading(),
|
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:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.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/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/ui/common.dart';
|
||||||
|
|
||||||
class NotificationSetting extends HookConsumerWidget {
|
class NotificationSetting extends HookConsumerWidget {
|
||||||
const NotificationSetting({
|
const NotificationSetting({
|
||||||
@@ -50,7 +51,7 @@ class NotificationSetting extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
).tr(),
|
).tr(),
|
||||||
children: [
|
children: [
|
||||||
_buildSwitchListTile(
|
buildSwitchListTile(
|
||||||
context,
|
context,
|
||||||
appSettingService,
|
appSettingService,
|
||||||
totalProgressValue,
|
totalProgressValue,
|
||||||
@@ -58,7 +59,7 @@ class NotificationSetting extends HookConsumerWidget {
|
|||||||
title: 'setting_notifications_total_progress_title'.tr(),
|
title: 'setting_notifications_total_progress_title'.tr(),
|
||||||
subtitle: 'setting_notifications_total_progress_subtitle'.tr(),
|
subtitle: 'setting_notifications_total_progress_subtitle'.tr(),
|
||||||
),
|
),
|
||||||
_buildSwitchListTile(
|
buildSwitchListTile(
|
||||||
context,
|
context,
|
||||||
appSettingService,
|
appSettingService,
|
||||||
singleProgressValue,
|
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) {
|
String _formatSliderValue(double v) {
|
||||||
if (v == 0.0) {
|
if (v == 0.0) {
|
||||||
return 'setting_notifications_notify_immediately'.tr();
|
return 'setting_notifications_notify_immediately'.tr();
|
||||||
|
|||||||
@@ -1,33 +1,34 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/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/album_viewer_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/asset_selection_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/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_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/select_user_for_sharing_page.dart';
|
||||||
import 'package:immich_mobile/modules/album/views/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/modules/settings/views/settings_page.dart';
|
||||||
import 'package:immich_mobile/routing/auth_guard.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/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
import 'package:immich_mobile/shared/services/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/splash_screen.dart';
|
||||||
import 'package:immich_mobile/shared/views/tab_controller_page.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:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
@@ -80,6 +81,10 @@ part 'router.gr.dart';
|
|||||||
transitionsBuilder: TransitionsBuilders.slideBottom,
|
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||||
),
|
),
|
||||||
AutoRoute(page: SettingsPage, guards: [AuthGuard]),
|
AutoRoute(page: SettingsPage, guards: [AuthGuard]),
|
||||||
|
CustomRoute(
|
||||||
|
page: AppLogPage,
|
||||||
|
transitionsBuilder: TransitionsBuilders.slideBottom,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class AppRouter extends _$AppRouter {
|
class AppRouter extends _$AppRouter {
|
||||||
|
|||||||
@@ -59,7 +59,8 @@ class _$AppRouter extends RootStackRouter {
|
|||||||
authToken: args.authToken,
|
authToken: args.authToken,
|
||||||
isZoomedFunction: args.isZoomedFunction,
|
isZoomedFunction: args.isZoomedFunction,
|
||||||
isZoomedListener: args.isZoomedListener,
|
isZoomedListener: args.isZoomedListener,
|
||||||
threeStageLoading: args.threeStageLoading));
|
loadPreview: args.loadPreview,
|
||||||
|
loadOriginal: args.loadOriginal));
|
||||||
},
|
},
|
||||||
VideoViewerRoute.name: (routeData) {
|
VideoViewerRoute.name: (routeData) {
|
||||||
final args = routeData.argsAs<VideoViewerRouteArgs>();
|
final args = routeData.argsAs<VideoViewerRouteArgs>();
|
||||||
@@ -142,6 +143,14 @@ class _$AppRouter extends RootStackRouter {
|
|||||||
return MaterialPageX<dynamic>(
|
return MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: const SettingsPage());
|
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) {
|
HomeRoute.name: (routeData) {
|
||||||
return MaterialPageX<dynamic>(
|
return MaterialPageX<dynamic>(
|
||||||
routeData: routeData, child: const HomePage());
|
routeData: routeData, child: const HomePage());
|
||||||
@@ -218,7 +227,8 @@ class _$AppRouter extends RootStackRouter {
|
|||||||
RouteConfig(FailedBackupStatusRoute.name,
|
RouteConfig(FailedBackupStatusRoute.name,
|
||||||
path: '/failed-backup-status-page', guards: [authGuard]),
|
path: '/failed-backup-status-page', guards: [authGuard]),
|
||||||
RouteConfig(SettingsRoute.name,
|
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 String authToken,
|
||||||
required void Function() isZoomedFunction,
|
required void Function() isZoomedFunction,
|
||||||
required ValueNotifier<bool> isZoomedListener,
|
required ValueNotifier<bool> isZoomedListener,
|
||||||
required bool threeStageLoading})
|
required bool loadPreview,
|
||||||
|
required bool loadOriginal})
|
||||||
: super(ImageViewerRoute.name,
|
: super(ImageViewerRoute.name,
|
||||||
path: '/image-viewer-page',
|
path: '/image-viewer-page',
|
||||||
args: ImageViewerRouteArgs(
|
args: ImageViewerRouteArgs(
|
||||||
@@ -306,7 +317,8 @@ class ImageViewerRoute extends PageRouteInfo<ImageViewerRouteArgs> {
|
|||||||
authToken: authToken,
|
authToken: authToken,
|
||||||
isZoomedFunction: isZoomedFunction,
|
isZoomedFunction: isZoomedFunction,
|
||||||
isZoomedListener: isZoomedListener,
|
isZoomedListener: isZoomedListener,
|
||||||
threeStageLoading: threeStageLoading));
|
loadPreview: loadPreview,
|
||||||
|
loadOriginal: loadOriginal));
|
||||||
|
|
||||||
static const String name = 'ImageViewerRoute';
|
static const String name = 'ImageViewerRoute';
|
||||||
}
|
}
|
||||||
@@ -319,7 +331,8 @@ class ImageViewerRouteArgs {
|
|||||||
required this.authToken,
|
required this.authToken,
|
||||||
required this.isZoomedFunction,
|
required this.isZoomedFunction,
|
||||||
required this.isZoomedListener,
|
required this.isZoomedListener,
|
||||||
required this.threeStageLoading});
|
required this.loadPreview,
|
||||||
|
required this.loadOriginal});
|
||||||
|
|
||||||
final Key? key;
|
final Key? key;
|
||||||
|
|
||||||
@@ -333,11 +346,13 @@ class ImageViewerRouteArgs {
|
|||||||
|
|
||||||
final ValueNotifier<bool> isZoomedListener;
|
final ValueNotifier<bool> isZoomedListener;
|
||||||
|
|
||||||
final bool threeStageLoading;
|
final bool loadPreview;
|
||||||
|
|
||||||
|
final bool loadOriginal;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
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';
|
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
|
/// generated route for
|
||||||
/// [HomePage]
|
/// [HomePage]
|
||||||
class HomeRoute extends PageRouteInfo<void> {
|
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 'dart:collection';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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.service.dart';
|
||||||
import 'package:immich_mobile/modules/home/services/asset_cache.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/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/services/device_info.service.dart';
|
import 'package:immich_mobile/shared/services/device_info.service.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:immich_mobile/utils/tuple.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
|
|
||||||
class AssetNotifier extends StateNotifier<List<Asset>> {
|
class AssetNotifier extends StateNotifier<List<Asset>> {
|
||||||
final AssetService _assetService;
|
final AssetService _assetService;
|
||||||
final AssetCacheService _assetCacheService;
|
final AssetCacheService _assetCacheService;
|
||||||
|
final log = Logger('AssetNotifier');
|
||||||
final DeviceInfoService _deviceInfoService = DeviceInfoService();
|
final DeviceInfoService _deviceInfoService = DeviceInfoService();
|
||||||
bool _getAllAssetInProgress = false;
|
bool _getAllAssetInProgress = false;
|
||||||
bool _deleteInProgress = false;
|
bool _deleteInProgress = false;
|
||||||
@@ -33,32 +36,65 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
|
|||||||
final stopwatch = Stopwatch();
|
final stopwatch = Stopwatch();
|
||||||
try {
|
try {
|
||||||
_getAllAssetInProgress = true;
|
_getAllAssetInProgress = true;
|
||||||
|
|
||||||
final bool isCacheValid = await _assetCacheService.isValid();
|
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) {
|
if (isCacheValid && state.isEmpty) {
|
||||||
stopwatch.start();
|
|
||||||
state = await _assetCacheService.get();
|
state = await _assetCacheService.get();
|
||||||
debugPrint(
|
log.info(
|
||||||
"Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms",
|
"Reading assets from cache: ${stopwatch.elapsedMilliseconds}ms",
|
||||||
);
|
);
|
||||||
stopwatch.reset();
|
stopwatch.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
stopwatch.start();
|
int remoteBegin = state.indexWhere((a) => a.isRemote);
|
||||||
var allAssets = await _assetService.getAllAsset(urgent: !isCacheValid);
|
remoteBegin = remoteBegin == -1 ? state.length : remoteBegin;
|
||||||
debugPrint("Query assets from API: ${stopwatch.elapsedMilliseconds}ms");
|
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();
|
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 {
|
} finally {
|
||||||
_getAllAssetInProgress = false;
|
_getAllAssetInProgress = false;
|
||||||
}
|
}
|
||||||
debugPrint("[getAllAsset] setting new asset state");
|
}
|
||||||
|
|
||||||
stopwatch.start();
|
List<Asset> _combineLocalAndRemoteAssets({
|
||||||
_cacheState();
|
required Iterable<Asset> local,
|
||||||
debugPrint("Store assets in cache: ${stopwatch.elapsedMilliseconds}ms");
|
required List<Asset> remote,
|
||||||
stopwatch.reset();
|
}) {
|
||||||
|
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() {
|
clearAllAsset() {
|
||||||
@@ -124,8 +160,8 @@ class AssetNotifier extends StateNotifier<List<Asset>> {
|
|||||||
if (local.isNotEmpty) {
|
if (local.isNotEmpty) {
|
||||||
try {
|
try {
|
||||||
return await PhotoManager.editor.deleteWithIds(local);
|
return await PhotoManager.editor.deleteWithIds(local);
|
||||||
} catch (e) {
|
} catch (e, stack) {
|
||||||
debugPrint("Delete asset from device failed: $e");
|
log.severe("Failed to delete asset from device", e, stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
class ReleaseInfoNotifier extends StateNotifier<String> {
|
class ReleaseInfoNotifier extends StateNotifier<String> {
|
||||||
ReleaseInfoNotifier() : super("");
|
ReleaseInfoNotifier() : super("");
|
||||||
|
final log = Logger('ReleaseInfoNotifier');
|
||||||
void checkGithubReleaseInfo() async {
|
void checkGithubReleaseInfo() async {
|
||||||
final Client client = Client();
|
final Client client = Client();
|
||||||
var box = Hive.box(hiveGithubReleaseInfoBox);
|
var box = Hive.box(hiveGithubReleaseInfoBox);
|
||||||
@@ -28,9 +29,6 @@ class ReleaseInfoNotifier extends StateNotifier<String> {
|
|||||||
String latestTagVersion = data["tag_name"];
|
String latestTagVersion = data["tag_name"];
|
||||||
state = latestTagVersion;
|
state = latestTagVersion;
|
||||||
|
|
||||||
debugPrint("Local release version $localReleaseVersion");
|
|
||||||
debugPrint("Remote release veresion $latestTagVersion");
|
|
||||||
|
|
||||||
if (localReleaseVersion == null && latestTagVersion.isNotEmpty) {
|
if (localReleaseVersion == null && latestTagVersion.isNotEmpty) {
|
||||||
VersionAnnouncementOverlayController.appLoader.show();
|
VersionAnnouncementOverlayController.appLoader.show();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -6,23 +6,24 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
import 'package:socket_io_client/socket_io_client.dart';
|
import 'package:socket_io_client/socket_io_client.dart';
|
||||||
|
|
||||||
class WebscoketState {
|
class WebsocketState {
|
||||||
final Socket? socket;
|
final Socket? socket;
|
||||||
final bool isConnected;
|
final bool isConnected;
|
||||||
|
|
||||||
WebscoketState({
|
WebsocketState({
|
||||||
this.socket,
|
this.socket,
|
||||||
required this.isConnected,
|
required this.isConnected,
|
||||||
});
|
});
|
||||||
|
|
||||||
WebscoketState copyWith({
|
WebsocketState copyWith({
|
||||||
Socket? socket,
|
Socket? socket,
|
||||||
bool? isConnected,
|
bool? isConnected,
|
||||||
}) {
|
}) {
|
||||||
return WebscoketState(
|
return WebsocketState(
|
||||||
socket: socket ?? this.socket,
|
socket: socket ?? this.socket,
|
||||||
isConnected: isConnected ?? this.isConnected,
|
isConnected: isConnected ?? this.isConnected,
|
||||||
);
|
);
|
||||||
@@ -30,13 +31,13 @@ class WebscoketState {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'WebscoketState(socket: $socket, isConnected: $isConnected)';
|
'WebsocketState(socket: $socket, isConnected: $isConnected)';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
if (identical(this, other)) return true;
|
if (identical(this, other)) return true;
|
||||||
|
|
||||||
return other is WebscoketState &&
|
return other is WebsocketState &&
|
||||||
other.socket == socket &&
|
other.socket == socket &&
|
||||||
other.isConnected == isConnected;
|
other.isConnected == isConnected;
|
||||||
}
|
}
|
||||||
@@ -45,12 +46,11 @@ class WebscoketState {
|
|||||||
int get hashCode => socket.hashCode ^ isConnected.hashCode;
|
int get hashCode => socket.hashCode ^ isConnected.hashCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
||||||
WebsocketNotifier(this.ref)
|
WebsocketNotifier(this.ref)
|
||||||
: super(WebscoketState(socket: null, isConnected: false)) {
|
: super(WebsocketState(socket: null, isConnected: false));
|
||||||
debugPrint("Init websocket instance");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
final log = Logger('WebsocketNotifier');
|
||||||
final Ref ref;
|
final Ref ref;
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
@@ -60,8 +60,8 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
|||||||
var accessToken = Hive.box(userInfoBox).get(accessTokenKey);
|
var accessToken = Hive.box(userInfoBox).get(accessTokenKey);
|
||||||
var endpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
var endpoint = Hive.box(userInfoBox).get(serverEndpointKey);
|
||||||
try {
|
try {
|
||||||
debugPrint("[WEBSOCKET] Attempting to connect to ws");
|
debugPrint("Attempting to connect to websocket");
|
||||||
// Configure socket transports must be sepecified
|
// Configure socket transports must be specified
|
||||||
Socket socket = io(
|
Socket socket = io(
|
||||||
endpoint.toString().replaceAll('/api', ''),
|
endpoint.toString().replaceAll('/api', ''),
|
||||||
OptionBuilder()
|
OptionBuilder()
|
||||||
@@ -76,18 +76,18 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
socket.onConnect((_) {
|
socket.onConnect((_) {
|
||||||
debugPrint("[WEBSOCKET] Established Websocket Connection");
|
debugPrint("Established Websocket Connection");
|
||||||
state = WebscoketState(isConnected: true, socket: socket);
|
state = WebsocketState(isConnected: true, socket: socket);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.onDisconnect((_) {
|
socket.onDisconnect((_) {
|
||||||
debugPrint("[WEBSOCKET] Disconnect to Websocket Connection");
|
debugPrint("Disconnect to Websocket Connection");
|
||||||
state = WebscoketState(isConnected: false, socket: null);
|
state = WebsocketState(isConnected: false, socket: null);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('error', (errorMessage) {
|
socket.on('error', (errorMessage) {
|
||||||
debugPrint("Webcoket Error - $errorMessage");
|
log.severe("Websocket Error - $errorMessage");
|
||||||
state = WebscoketState(isConnected: false, socket: null);
|
state = WebsocketState(isConnected: false, socket: null);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('on_upload_success', (data) {
|
socket.on('on_upload_success', (data) {
|
||||||
@@ -105,21 +105,22 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
disconnect() {
|
disconnect() {
|
||||||
debugPrint("[WEBSOCKET] Attempting to disconnect");
|
debugPrint("Attempting to disconnect from websocket");
|
||||||
|
|
||||||
var socket = state.socket?.disconnect();
|
var socket = state.socket?.disconnect();
|
||||||
|
|
||||||
if (socket?.disconnected == true) {
|
if (socket?.disconnected == true) {
|
||||||
state = WebscoketState(isConnected: false, socket: null);
|
state = WebsocketState(isConnected: false, socket: null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stopListenToEvent(String eventName) {
|
stopListenToEvent(String eventName) {
|
||||||
debugPrint("[Websocket] Stop listening to event $eventName");
|
debugPrint("Stop listening to event $eventName");
|
||||||
state.socket?.off(eventName);
|
state.socket?.off(eventName);
|
||||||
}
|
}
|
||||||
|
|
||||||
listenUploadEvent() {
|
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) {
|
state.socket?.on('on_upload_success', (data) {
|
||||||
var jsonString = jsonDecode(data.toString());
|
var jsonString = jsonDecode(data.toString());
|
||||||
AssetResponseDto? newAsset = AssetResponseDto.fromJson(jsonString);
|
AssetResponseDto? newAsset = AssetResponseDto.fromJson(jsonString);
|
||||||
@@ -132,6 +133,6 @@ class WebsocketNotifier extends StateNotifier<WebscoketState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final websocketProvider =
|
final websocketProvider =
|
||||||
StateNotifierProvider<WebsocketNotifier, WebscoketState>((ref) {
|
StateNotifierProvider<WebsocketNotifier, WebsocketState>((ref) {
|
||||||
return WebsocketNotifier(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 {
|
Future<void> invalidate() async {
|
||||||
final file = await _getCacheFile();
|
try {
|
||||||
await file.delete();
|
final file = await _getCacheFile();
|
||||||
|
await file.delete();
|
||||||
|
} on FileSystemException {
|
||||||
|
// file is already deleted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> putRawData(dynamic data) async {
|
Future<void> putRawData(dynamic data) async {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ class ImmichImage extends StatelessWidget {
|
|||||||
return CachedNetworkImage(
|
return CachedNetworkImage(
|
||||||
imageUrl: thumbnailRequestUrl,
|
imageUrl: thumbnailRequestUrl,
|
||||||
httpHeaders: {"Authorization": "Bearer $token"},
|
httpHeaders: {"Authorization": "Bearer $token"},
|
||||||
cacheKey: 'thumbnail-image-${asset.id}',
|
cacheKey: getThumbnailCacheKey(asset.remote!),
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
// keeping memCacheWidth, memCacheHeight, maxWidthDiskCache and
|
// keeping memCacheWidth, memCacheHeight, maxWidthDiskCache and
|
||||||
|
|||||||
@@ -15,7 +15,10 @@ class ImmichLoadingIndicator extends StatelessWidget {
|
|||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
padding: const EdgeInsets.all(15),
|
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) {
|
String formatBytes(int bytes) {
|
||||||
if (bytes < 1000) {
|
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB'];
|
||||||
return "$bytes B";
|
|
||||||
} else if (bytes < 1000000) {
|
int magnitude = 0;
|
||||||
final kb = (bytes / 1000).toStringAsFixed(1);
|
double remainder = bytes.toDouble();
|
||||||
return "$kb kB";
|
while (remainder >= 1024) {
|
||||||
} else if (bytes < 1000000000) {
|
if (magnitude + 1 < units.length) {
|
||||||
final mb = (bytes / 1000000).toStringAsFixed(1);
|
magnitude++;
|
||||||
return "$mb MB";
|
remainder /= 1024;
|
||||||
} else {
|
}
|
||||||
final gb = (bytes / 1000000000).toStringAsFixed(1);
|
else {
|
||||||
return "$gb GB";
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return "${remainder.toStringAsFixed(magnitude == 0 ? 0 : 1)} ${units[magnitude]}";
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,19 @@ String getThumbnailUrl(
|
|||||||
return _getThumbnailUrl(asset.id, type: type);
|
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(
|
String getAlbumThumbnailUrl(
|
||||||
final AlbumResponseDto album, {
|
final AlbumResponseDto album, {
|
||||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
||||||
@@ -20,11 +33,25 @@ String getAlbumThumbnailUrl(
|
|||||||
return _getThumbnailUrl(album.albumThumbnailAssetId!, type: type);
|
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) {
|
String getImageUrl(final AssetResponseDto asset) {
|
||||||
final box = Hive.box(userInfoBox);
|
final box = Hive.box(userInfoBox);
|
||||||
return '${box.get(serverEndpointKey)}/asset/file/${asset.id}?isThumb=false';
|
return '${box.get(serverEndpointKey)}/asset/file/${asset.id}?isThumb=false';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getImageCacheKey(final AssetResponseDto asset) {
|
||||||
|
return '${asset.id}_fullStage';
|
||||||
|
}
|
||||||
|
|
||||||
String _getThumbnailUrl(
|
String _getThumbnailUrl(
|
||||||
final String id, {
|
final String id, {
|
||||||
ThumbnailFormat type = ThumbnailFormat.WEBP,
|
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
|
.gitignore
|
||||||
|
.openapi-generator-ignore
|
||||||
.travis.yml
|
.travis.yml
|
||||||
README.md
|
README.md
|
||||||
analysis_options.yaml
|
analysis_options.yaml
|
||||||
@@ -23,8 +24,8 @@ doc/CheckDuplicateAssetResponseDto.md
|
|||||||
doc/CheckExistingAssetsDto.md
|
doc/CheckExistingAssetsDto.md
|
||||||
doc/CheckExistingAssetsResponseDto.md
|
doc/CheckExistingAssetsResponseDto.md
|
||||||
doc/CreateAlbumDto.md
|
doc/CreateAlbumDto.md
|
||||||
doc/CreateDeviceInfoDto.md
|
|
||||||
doc/CreateProfileImageResponseDto.md
|
doc/CreateProfileImageResponseDto.md
|
||||||
|
doc/CreateTagDto.md
|
||||||
doc/CreateUserDto.md
|
doc/CreateUserDto.md
|
||||||
doc/CuratedLocationsResponseDto.md
|
doc/CuratedLocationsResponseDto.md
|
||||||
doc/CuratedObjectsResponseDto.md
|
doc/CuratedObjectsResponseDto.md
|
||||||
@@ -60,15 +61,19 @@ doc/ServerVersionReponseDto.md
|
|||||||
doc/SignUpDto.md
|
doc/SignUpDto.md
|
||||||
doc/SmartInfoResponseDto.md
|
doc/SmartInfoResponseDto.md
|
||||||
doc/SystemConfigApi.md
|
doc/SystemConfigApi.md
|
||||||
doc/SystemConfigKey.md
|
doc/SystemConfigDto.md
|
||||||
doc/SystemConfigResponseDto.md
|
doc/SystemConfigFFmpegDto.md
|
||||||
doc/SystemConfigResponseItem.md
|
doc/SystemConfigOAuthDto.md
|
||||||
|
doc/TagApi.md
|
||||||
|
doc/TagResponseDto.md
|
||||||
|
doc/TagTypeEnum.md
|
||||||
doc/ThumbnailFormat.md
|
doc/ThumbnailFormat.md
|
||||||
doc/TimeGroupEnum.md
|
doc/TimeGroupEnum.md
|
||||||
doc/UpdateAlbumDto.md
|
doc/UpdateAlbumDto.md
|
||||||
doc/UpdateAssetDto.md
|
doc/UpdateAssetDto.md
|
||||||
doc/UpdateDeviceInfoDto.md
|
doc/UpdateTagDto.md
|
||||||
doc/UpdateUserDto.md
|
doc/UpdateUserDto.md
|
||||||
|
doc/UpsertDeviceInfoDto.md
|
||||||
doc/UsageByUserDto.md
|
doc/UsageByUserDto.md
|
||||||
doc/UserApi.md
|
doc/UserApi.md
|
||||||
doc/UserCountResponseDto.md
|
doc/UserCountResponseDto.md
|
||||||
@@ -84,6 +89,7 @@ lib/api/job_api.dart
|
|||||||
lib/api/o_auth_api.dart
|
lib/api/o_auth_api.dart
|
||||||
lib/api/server_info_api.dart
|
lib/api/server_info_api.dart
|
||||||
lib/api/system_config_api.dart
|
lib/api/system_config_api.dart
|
||||||
|
lib/api/tag_api.dart
|
||||||
lib/api/user_api.dart
|
lib/api/user_api.dart
|
||||||
lib/api_client.dart
|
lib/api_client.dart
|
||||||
lib/api_exception.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_dto.dart
|
||||||
lib/model/check_existing_assets_response_dto.dart
|
lib/model/check_existing_assets_response_dto.dart
|
||||||
lib/model/create_album_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_profile_image_response_dto.dart
|
||||||
|
lib/model/create_tag_dto.dart
|
||||||
lib/model/create_user_dto.dart
|
lib/model/create_user_dto.dart
|
||||||
lib/model/curated_locations_response_dto.dart
|
lib/model/curated_locations_response_dto.dart
|
||||||
lib/model/curated_objects_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/server_version_reponse_dto.dart
|
||||||
lib/model/sign_up_dto.dart
|
lib/model/sign_up_dto.dart
|
||||||
lib/model/smart_info_response_dto.dart
|
lib/model/smart_info_response_dto.dart
|
||||||
lib/model/system_config_key.dart
|
lib/model/system_config_dto.dart
|
||||||
lib/model/system_config_response_dto.dart
|
lib/model/system_config_f_fmpeg_dto.dart
|
||||||
lib/model/system_config_response_item.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/thumbnail_format.dart
|
||||||
lib/model/time_group_enum.dart
|
lib/model/time_group_enum.dart
|
||||||
lib/model/update_album_dto.dart
|
lib/model/update_album_dto.dart
|
||||||
lib/model/update_asset_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/update_user_dto.dart
|
||||||
|
lib/model/upsert_device_info_dto.dart
|
||||||
lib/model/usage_by_user_dto.dart
|
lib/model/usage_by_user_dto.dart
|
||||||
lib/model/user_count_response_dto.dart
|
lib/model/user_count_response_dto.dart
|
||||||
lib/model/user_response_dto.dart
|
lib/model/user_response_dto.dart
|
||||||
lib/model/validate_access_token_response_dto.dart
|
lib/model/validate_access_token_response_dto.dart
|
||||||
pubspec.yaml
|
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:
|
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
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
@@ -93,7 +93,7 @@ Class | Method | HTTP request | Description
|
|||||||
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
||||||
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
|
*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 |
|
*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload |
|
||||||
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
|
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
|
||||||
*AuthenticationApi* | [**login**](doc//AuthenticationApi.md#login) | **POST** /auth/login |
|
*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 |
|
*AuthenticationApi* | [**validateAccessToken**](doc//AuthenticationApi.md#validateaccesstoken) | **POST** /auth/validateToken |
|
||||||
*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
*DeviceInfoApi* | [**createDeviceInfo**](doc//DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
||||||
*DeviceInfoApi* | [**updateDeviceInfo**](doc//DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /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* | [**getAllJobsStatus**](doc//JobApi.md#getalljobsstatus) | **GET** /jobs |
|
||||||
*JobApi* | [**getJobStatus**](doc//JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |
|
*JobApi* | [**getJobStatus**](doc//JobApi.md#getjobstatus) | **GET** /jobs/{jobId} |
|
||||||
*JobApi* | [**sendJobCommand**](doc//JobApi.md#sendjobcommand) | **PUT** /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* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats |
|
||||||
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
|
*ServerInfoApi* | [**pingServer**](doc//ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
|
||||||
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |
|
*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 |
|
*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* | [**createProfileImage**](doc//UserApi.md#createprofileimage) | **POST** /user/profile-image |
|
||||||
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |
|
*UserApi* | [**createUser**](doc//UserApi.md#createuser) | **POST** /user |
|
||||||
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{userId} |
|
*UserApi* | [**deleteUser**](doc//UserApi.md#deleteuser) | **DELETE** /user/{userId} |
|
||||||
@@ -144,8 +151,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
- [CheckExistingAssetsDto](doc//CheckExistingAssetsDto.md)
|
||||||
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
|
- [CheckExistingAssetsResponseDto](doc//CheckExistingAssetsResponseDto.md)
|
||||||
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
- [CreateAlbumDto](doc//CreateAlbumDto.md)
|
||||||
- [CreateDeviceInfoDto](doc//CreateDeviceInfoDto.md)
|
|
||||||
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
- [CreateProfileImageResponseDto](doc//CreateProfileImageResponseDto.md)
|
||||||
|
- [CreateTagDto](doc//CreateTagDto.md)
|
||||||
- [CreateUserDto](doc//CreateUserDto.md)
|
- [CreateUserDto](doc//CreateUserDto.md)
|
||||||
- [CuratedLocationsResponseDto](doc//CuratedLocationsResponseDto.md)
|
- [CuratedLocationsResponseDto](doc//CuratedLocationsResponseDto.md)
|
||||||
- [CuratedObjectsResponseDto](doc//CuratedObjectsResponseDto.md)
|
- [CuratedObjectsResponseDto](doc//CuratedObjectsResponseDto.md)
|
||||||
@@ -176,15 +183,18 @@ Class | Method | HTTP request | Description
|
|||||||
- [ServerVersionReponseDto](doc//ServerVersionReponseDto.md)
|
- [ServerVersionReponseDto](doc//ServerVersionReponseDto.md)
|
||||||
- [SignUpDto](doc//SignUpDto.md)
|
- [SignUpDto](doc//SignUpDto.md)
|
||||||
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
||||||
- [SystemConfigKey](doc//SystemConfigKey.md)
|
- [SystemConfigDto](doc//SystemConfigDto.md)
|
||||||
- [SystemConfigResponseDto](doc//SystemConfigResponseDto.md)
|
- [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md)
|
||||||
- [SystemConfigResponseItem](doc//SystemConfigResponseItem.md)
|
- [SystemConfigOAuthDto](doc//SystemConfigOAuthDto.md)
|
||||||
|
- [TagResponseDto](doc//TagResponseDto.md)
|
||||||
|
- [TagTypeEnum](doc//TagTypeEnum.md)
|
||||||
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
||||||
- [TimeGroupEnum](doc//TimeGroupEnum.md)
|
- [TimeGroupEnum](doc//TimeGroupEnum.md)
|
||||||
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
||||||
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
- [UpdateAssetDto](doc//UpdateAssetDto.md)
|
||||||
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
|
- [UpdateTagDto](doc//UpdateTagDto.md)
|
||||||
- [UpdateUserDto](doc//UpdateUserDto.md)
|
- [UpdateUserDto](doc//UpdateUserDto.md)
|
||||||
|
- [UpsertDeviceInfoDto](doc//UpsertDeviceInfoDto.md)
|
||||||
- [UsageByUserDto](doc//UsageByUserDto.md)
|
- [UsageByUserDto](doc//UsageByUserDto.md)
|
||||||
- [UserCountResponseDto](doc//UserCountResponseDto.md)
|
- [UserCountResponseDto](doc//UserCountResponseDto.md)
|
||||||
- [UserResponseDto](doc//UserResponseDto.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} |
|
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
||||||
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{assetId} |
|
[**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 |
|
[**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)
|
[[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**
|
# **getAllAssets**
|
||||||
> List<AssetResponseDto> getAllAssets()
|
> List<AssetResponseDto> getAllAssets(ifNoneMatch)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -291,9 +291,10 @@ import 'package:openapi/api.dart';
|
|||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
final api_instance = AssetApi();
|
final api_instance = AssetApi();
|
||||||
|
final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.getAllAssets();
|
final result = api_instance.getAllAssets(ifNoneMatch);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling AssetApi->getAllAssets: $e\n');
|
print('Exception when calling AssetApi->getAllAssets: $e\n');
|
||||||
@@ -301,7 +302,10 @@ try {
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Parameters
|
### 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
|
### 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)
|
[[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**
|
# **updateAsset**
|
||||||
> AssetResponseDto updateAssetById(assetId, updateAssetDto)
|
> AssetResponseDto updateAsset(assetId, updateAssetDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -851,10 +855,10 @@ final assetId = assetId_example; // String |
|
|||||||
final updateAssetDto = UpdateAssetDto(); // UpdateAssetDto |
|
final updateAssetDto = UpdateAssetDto(); // UpdateAssetDto |
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.updateAssetById(assetId, updateAssetDto);
|
final result = api_instance.updateAsset(assetId, updateAssetDto);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} 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** | |
|
**mimeType** | **String** | |
|
||||||
**duration** | **String** | |
|
**duration** | **String** | |
|
||||||
**webpPath** | **String** | |
|
**webpPath** | **String** | |
|
||||||
**encodedVideoPath** | **String** | |
|
**encodedVideoPath** | **String** | | [optional]
|
||||||
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional]
|
**exifInfo** | [**ExifResponseDto**](ExifResponseDto.md) | | [optional]
|
||||||
**smartInfo** | [**SmartInfoResponseDto**](SmartInfoResponseDto.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)
|
[[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
|
## Load the model package
|
||||||
```dart
|
```dart
|
||||||
@@ -8,8 +8,8 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**photos** | **int** | |
|
**type** | [**TagTypeEnum**](TagTypeEnum.md) | |
|
||||||
**videos** | **int** | |
|
**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)
|
[[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 |
|
[**createDeviceInfo**](DeviceInfoApi.md#createdeviceinfo) | **POST** /device-info |
|
||||||
[**updateDeviceInfo**](DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
|
[**updateDeviceInfo**](DeviceInfoApi.md#updatedeviceinfo) | **PATCH** /device-info |
|
||||||
|
[**upsertDeviceInfo**](DeviceInfoApi.md#upsertdeviceinfo) | **PUT** /device-info |
|
||||||
|
|
||||||
|
|
||||||
# **createDeviceInfo**
|
# **createDeviceInfo**
|
||||||
> DeviceInfoResponseDto createDeviceInfo(createDeviceInfoDto)
|
> DeviceInfoResponseDto createDeviceInfo(upsertDeviceInfoDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
```dart
|
```dart
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -29,10 +32,10 @@ import 'package:openapi/api.dart';
|
|||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
final api_instance = DeviceInfoApi();
|
final api_instance = DeviceInfoApi();
|
||||||
final createDeviceInfoDto = CreateDeviceInfoDto(); // CreateDeviceInfoDto |
|
final upsertDeviceInfoDto = UpsertDeviceInfoDto(); // UpsertDeviceInfoDto |
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.createDeviceInfo(createDeviceInfoDto);
|
final result = api_instance.createDeviceInfo(upsertDeviceInfoDto);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling DeviceInfoApi->createDeviceInfo: $e\n');
|
print('Exception when calling DeviceInfoApi->createDeviceInfo: $e\n');
|
||||||
@@ -43,7 +46,7 @@ try {
|
|||||||
|
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------- | ------------- | ------------- | -------------
|
------------- | ------------- | ------------- | -------------
|
||||||
**createDeviceInfoDto** | [**CreateDeviceInfoDto**](CreateDeviceInfoDto.md)| |
|
**upsertDeviceInfoDto** | [**UpsertDeviceInfoDto**](UpsertDeviceInfoDto.md)| |
|
||||||
|
|
||||||
### Return type
|
### 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)
|
[[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**
|
# **updateDeviceInfo**
|
||||||
> DeviceInfoResponseDto updateDeviceInfo(updateDeviceInfoDto)
|
> DeviceInfoResponseDto updateDeviceInfo(upsertDeviceInfoDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@deprecated
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
```dart
|
```dart
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -76,10 +81,10 @@ import 'package:openapi/api.dart';
|
|||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
final api_instance = DeviceInfoApi();
|
final api_instance = DeviceInfoApi();
|
||||||
final updateDeviceInfoDto = UpdateDeviceInfoDto(); // UpdateDeviceInfoDto |
|
final upsertDeviceInfoDto = UpsertDeviceInfoDto(); // UpsertDeviceInfoDto |
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.updateDeviceInfo(updateDeviceInfoDto);
|
final result = api_instance.updateDeviceInfo(upsertDeviceInfoDto);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling DeviceInfoApi->updateDeviceInfo: $e\n');
|
print('Exception when calling DeviceInfoApi->updateDeviceInfo: $e\n');
|
||||||
@@ -90,7 +95,54 @@ try {
|
|||||||
|
|
||||||
Name | Type | Description | Notes
|
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
|
### 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
|
Method | HTTP request | Description
|
||||||
------------- | ------------- | -------------
|
------------- | ------------- | -------------
|
||||||
[**getConfig**](SystemConfigApi.md#getconfig) | **GET** /system-config |
|
[**getConfig**](SystemConfigApi.md#getconfig) | **GET** /system-config |
|
||||||
|
[**getDefaults**](SystemConfigApi.md#getdefaults) | **GET** /system-config/defaults |
|
||||||
[**updateConfig**](SystemConfigApi.md#updateconfig) | **PUT** /system-config |
|
[**updateConfig**](SystemConfigApi.md#updateconfig) | **PUT** /system-config |
|
||||||
|
|
||||||
|
|
||||||
# **getConfig**
|
# **getConfig**
|
||||||
> SystemConfigResponseDto getConfig()
|
> SystemConfigDto getConfig()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ This endpoint does not need any parameter.
|
|||||||
|
|
||||||
### Return type
|
### Return type
|
||||||
|
|
||||||
[**SystemConfigResponseDto**](SystemConfigResponseDto.md)
|
[**SystemConfigDto**](SystemConfigDto.md)
|
||||||
|
|
||||||
### Authorization
|
### 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)
|
[[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**
|
# **getDefaults**
|
||||||
> SystemConfigResponseDto updateConfig(body)
|
> SystemConfigDto getDefaults()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -72,10 +73,53 @@ import 'package:openapi/api.dart';
|
|||||||
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
final api_instance = SystemConfigApi();
|
final api_instance = SystemConfigApi();
|
||||||
final body = Object(); // Object |
|
|
||||||
|
|
||||||
try {
|
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);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling SystemConfigApi->updateConfig: $e\n');
|
print('Exception when calling SystemConfigApi->updateConfig: $e\n');
|
||||||
@@ -86,11 +130,11 @@ try {
|
|||||||
|
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------- | ------------- | ------------- | -------------
|
------------- | ------------- | ------------- | -------------
|
||||||
**body** | **Object**| |
|
**systemConfigDto** | [**SystemConfigDto**](SystemConfigDto.md)| |
|
||||||
|
|
||||||
### Return type
|
### Return type
|
||||||
|
|
||||||
[**SystemConfigResponseDto**](SystemConfigResponseDto.md)
|
[**SystemConfigDto**](SystemConfigDto.md)
|
||||||
|
|
||||||
### Authorization
|
### Authorization
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# openapi.model.AdminConfigResponseDto
|
# openapi.model.SystemConfigDto
|
||||||
|
|
||||||
## Load the model package
|
## Load the model package
|
||||||
```dart
|
```dart
|
||||||
@@ -8,7 +8,8 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**config** | [**Object**](.md) | |
|
**ffmpeg** | [**SystemConfigFFmpegDto**](SystemConfigFFmpegDto.md) | |
|
||||||
|
**oauth** | [**SystemConfigOAuthDto**](SystemConfigOAuthDto.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)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
16
mobile/openapi/doc/SystemConfigEntity.md
generated
@@ -1,16 +0,0 @@
|
|||||||
# openapi.model.SystemConfigEntity
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**key** | **String** | |
|
|
||||||
**value** | [**Object**](.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)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# openapi.model.AssetCountByTimeGroupResponseDto
|
# openapi.model.SystemConfigFFmpegDto
|
||||||
|
|
||||||
## Load the model package
|
## Load the model package
|
||||||
```dart
|
```dart
|
||||||
@@ -8,8 +8,11 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**count** | **int** | |
|
**crf** | **String** | |
|
||||||
**buckets** | [**List<AssetCountByTimeBucketResponseDto>**](AssetCountByTimeBucketResponseDto.md) | | [default to const []]
|
**preset** | **String** | |
|
||||||
|
**targetVideoCodec** | **String** | |
|
||||||
|
**targetAudioCodec** | **String** | |
|
||||||
|
**targetScaling** | **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)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
14
mobile/openapi/doc/SystemConfigKey.md
generated
@@ -1,14 +0,0 @@
|
|||||||
# openapi.model.SystemConfigKey
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# openapi.model.AssetCountByTimeGroupDto
|
# openapi.model.SystemConfigOAuthDto
|
||||||
|
|
||||||
## Load the model package
|
## Load the model package
|
||||||
```dart
|
```dart
|
||||||
@@ -8,8 +8,13 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**timeGroup** | **String** | |
|
**enabled** | **bool** | |
|
||||||
**count** | **int** | |
|
**issuerUrl** | **String** | |
|
||||||
|
**clientId** | **String** | |
|
||||||
|
**clientSecret** | **String** | |
|
||||||
|
**scope** | **String** | |
|
||||||
|
**buttonText** | **String** | |
|
||||||
|
**autoRegister** | **bool** | |
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.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)
|
||||||
|
|
||||||
15
mobile/openapi/doc/SystemConfigResponseDto.md
generated
@@ -1,15 +0,0 @@
|
|||||||
# openapi.model.SystemConfigResponseDto
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**config** | [**List<SystemConfigResponseItem>**](SystemConfigResponseItem.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)
|
|
||||||
|
|
||||||
|
|
||||||
220
mobile/openapi/doc/TagApi.md
generated
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
# openapi.api.TagApi
|
||||||
|
|
||||||
|
## Load the API package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
All URIs are relative to */api*
|
||||||
|
|
||||||
|
Method | HTTP request | Description
|
||||||
|
------------- | ------------- | -------------
|
||||||
|
[**create**](TagApi.md#create) | **POST** /tag |
|
||||||
|
[**delete**](TagApi.md#delete) | **DELETE** /tag/{id} |
|
||||||
|
[**findAll**](TagApi.md#findall) | **GET** /tag |
|
||||||
|
[**findOne**](TagApi.md#findone) | **GET** /tag/{id} |
|
||||||
|
[**update**](TagApi.md#update) | **PATCH** /tag/{id} |
|
||||||
|
|
||||||
|
|
||||||
|
# **create**
|
||||||
|
> TagResponseDto create(createTagDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
final api_instance = TagApi();
|
||||||
|
final createTagDto = CreateTagDto(); // CreateTagDto |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.create(createTagDto);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling TagApi->create: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**createTagDto** | [**CreateTagDto**](CreateTagDto.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**TagResponseDto**](TagResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
No authorization required
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|
||||||
|
# **delete**
|
||||||
|
> delete(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
final api_instance = TagApi();
|
||||||
|
final id = id_example; // String |
|
||||||
|
|
||||||
|
try {
|
||||||
|
api_instance.delete(id);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling TagApi->delete: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**id** | **String**| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
void (empty response body)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
No authorization required
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: Not defined
|
||||||
|
- **Accept**: Not defined
|
||||||
|
|
||||||
|
[[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)
|
||||||
|
|
||||||
|
# **findAll**
|
||||||
|
> List<TagResponseDto> findAll()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
final api_instance = TagApi();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.findAll();
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling TagApi->findAll: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
This endpoint does not need any parameter.
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**List<TagResponseDto>**](TagResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
No authorization required
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: Not defined
|
||||||
|
- **Accept**: application/json
|
||||||
|
|
||||||
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
# **findOne**
|
||||||
|
> TagResponseDto findOne(id)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
final api_instance = TagApi();
|
||||||
|
final id = id_example; // String |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.findOne(id);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling TagApi->findOne: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**id** | **String**| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**TagResponseDto**](TagResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
No authorization required
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: Not defined
|
||||||
|
- **Accept**: application/json
|
||||||
|
|
||||||
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
# **update**
|
||||||
|
> TagResponseDto update(id, updateTagDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
|
final api_instance = TagApi();
|
||||||
|
final id = id_example; // String |
|
||||||
|
final updateTagDto = UpdateTagDto(); // UpdateTagDto |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.update(id, updateTagDto);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling TagApi->update: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**id** | **String**| |
|
||||||
|
**updateTagDto** | [**UpdateTagDto**](UpdateTagDto.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**TagResponseDto**](TagResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
No authorization required
|
||||||
|
|
||||||
|
### 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.SystemConfigResponseItem
|
# openapi.model.TagResponseDto
|
||||||
|
|
||||||
## Load the model package
|
## Load the model package
|
||||||
```dart
|
```dart
|
||||||
@@ -8,10 +8,11 @@ import 'package:openapi/api.dart';
|
|||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**id** | **String** | |
|
||||||
|
**type** | [**TagTypeEnum**](TagTypeEnum.md) | |
|
||||||
**name** | **String** | |
|
**name** | **String** | |
|
||||||
**key** | [**SystemConfigKey**](SystemConfigKey.md) | |
|
**userId** | **String** | |
|
||||||
**value** | **String** | |
|
**renameTagId** | **String** | | [optional]
|
||||||
**defaultValue** | **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)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# openapi.model.JobType
|
# openapi.model.TagTypeEnum
|
||||||
|
|
||||||
## Load the model package
|
## Load the model package
|
||||||
```dart
|
```dart
|
||||||
14
mobile/openapi/doc/TimeBucketEnum.md
generated
@@ -1,14 +0,0 @@
|
|||||||
# openapi.model.TimeBucketEnum
|
|
||||||
|
|
||||||
## Load the model package
|
|
||||||
```dart
|
|
||||||
import 'package:openapi/api.dart';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||