Compare commits
62 Commits
v1.36.1_55
...
v1.38.2_60
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1efcac0946 | ||
|
|
415550f16d | ||
|
|
aa554a9e77 | ||
|
|
2876c7ff97 | ||
|
|
651f56370a | ||
|
|
950adeebbf | ||
|
|
4e33a52290 | ||
|
|
f2cc7c2873 | ||
|
|
9c01ca1080 | ||
|
|
09103dc981 | ||
|
|
f096910abc | ||
|
|
242165485d | ||
|
|
e6904ca884 | ||
|
|
5a792cc821 | ||
|
|
0633eaf68c | ||
|
|
40afa3695a | ||
|
|
14889e7d85 | ||
|
|
3bb103c6b6 | ||
|
|
5e680551b9 | ||
|
|
cefdd86b7f | ||
|
|
b8e26a2112 | ||
|
|
58a149990d | ||
|
|
c23b2479f7 | ||
|
|
a97b761eda | ||
|
|
1adf8ff6b6 | ||
|
|
b5a5363a6a | ||
|
|
f91bdc2785 | ||
|
|
db34f2f7fd | ||
|
|
5de8ea162d | ||
|
|
6e2763b72c | ||
|
|
966d99217a | ||
|
|
5d140145c1 | ||
|
|
fcf3b0b672 | ||
|
|
e8bbad6772 | ||
|
|
5f2b75997f | ||
|
|
426ce77f1c | ||
|
|
83c7434eb5 | ||
|
|
99854e90be | ||
|
|
424b11cf50 | ||
|
|
da87b1256c | ||
|
|
a3971543b5 | ||
|
|
a384798779 | ||
|
|
d31eddf32f | ||
|
|
1068c4ad23 | ||
|
|
cbc979263e | ||
|
|
765181bbc0 | ||
|
|
d82dec9773 | ||
|
|
024177515d | ||
|
|
fb3b36a569 | ||
|
|
614743c8f4 | ||
|
|
47f5e4134e | ||
|
|
efa7b3ba54 | ||
|
|
1e9d67ec39 | ||
|
|
80d0ddca9a | ||
|
|
976d347623 | ||
|
|
df0a059a02 | ||
|
|
cc697486fc | ||
|
|
2227a6f5f3 | ||
|
|
a9320f06e8 | ||
|
|
39b7ab66d4 | ||
|
|
bc9ee1d611 | ||
|
|
56ce747ffc |
13
.gitattributes
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
mobile/openapi/**/*.md -diff -merge
|
||||
mobile/openapi/**/*.md linguist-generated=true
|
||||
mobile/openapi/**/*.dart -diff -merge
|
||||
mobile/openapi/**/*.dart linguist-generated=true
|
||||
|
||||
web/src/api/open-api/**/*.md -diff -merge
|
||||
web/src/api/open-api/**/*.md linguist-generated=true
|
||||
|
||||
web/src/api/open-api/**/*.ts -diff -merge
|
||||
web/src/api/open-api/**/*.ts linguist-generated=true
|
||||
|
||||
mobile/openapi/.openapi-generator/FILES -diff -merge
|
||||
mobile/openapi/.openapi-generator/FILES linguist-generated=true
|
||||
8
Makefile
@@ -4,6 +4,9 @@ dev:
|
||||
dev-new:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --remove-orphans
|
||||
|
||||
dev-new-update:
|
||||
rm -rf ./server/dist && docker compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
dev-update:
|
||||
rm -rf ./server/dist && docker-compose -f ./docker/docker-compose.dev.yml up --build -V --remove-orphans
|
||||
|
||||
@@ -26,4 +29,7 @@ prod-scale:
|
||||
docker-compose -f ./docker/docker-compose.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
||||
|
||||
api:
|
||||
cd ./server && npm run api:generate
|
||||
cd ./server && npm run api:generate
|
||||
|
||||
attach-server:
|
||||
docker exec -it docker_immich-server_1 sh
|
||||
9
NOTES.md
@@ -1,9 +0,0 @@
|
||||
# TODO
|
||||
|
||||
Server scenario with web
|
||||
|
||||
[ ] 1 user exist without admin right -> make admin on first check
|
||||
|
||||
[ ] 2 users exist without admin right -> ask user to choose which account will be the admin
|
||||
|
||||
[ X ] No users exist -> prompt signup form for Admin
|
||||
@@ -17,6 +17,9 @@
|
||||
<img src="design/immich-screenshots.png" title="Main Screenshot">
|
||||
</a>
|
||||
<br/>
|
||||
<p align="center">
|
||||
<a href="README_zh_CN.md">中文</a>
|
||||
</p>
|
||||
|
||||
## Disclaimer
|
||||
|
||||
|
||||
115
README_zh_CN.md
Normal file
@@ -0,0 +1,115 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-green.svg?color=3F51B5&style=for-the-badge&label=License&logoColor=000000&labelColor=ececec" alt="License: MIT"></a>
|
||||
<a href="https://discord.gg/D8JsnBEuKb">
|
||||
<img src="https://img.shields.io/discord/979116623879368755.svg?label=Discord&logo=Discord&style=for-the-badge&logoColor=000000&labelColor=ececec" atl="Discord"/>
|
||||
</a>
|
||||
<br/>
|
||||
<br/>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="design/immich-logo.svg" width="150" title="Login With Custom URL">
|
||||
</p>
|
||||
<h3 align="center">Immich - 高性能的自托管照片和视频备份方案</h3>
|
||||
<p align="center">
|
||||
请注意: 此README不是由Immich团队维护, 这意味着它在某一时间点不会被更新,因为我们是依靠贡献者来更新的。感谢理解。
|
||||
</p>
|
||||
<br/>
|
||||
<a href="https://immich.app">
|
||||
<img src="design/immich-screenshots.png" title="Main Screenshot">
|
||||
</a>
|
||||
<br/>
|
||||
|
||||
<p align="center">
|
||||
<a href="README.md">English</a>
|
||||
</p>
|
||||
|
||||
|
||||
## 免责声明
|
||||
|
||||
- ⚠️ 本项目正在 **非常活跃** 的开发中。
|
||||
- ⚠️ 可能存在bug或者重大变更。
|
||||
- ⚠️ **不要把本软件作为你存储照片或视频的唯一方式!**
|
||||
|
||||
## 目录
|
||||
|
||||
- [官方文档](https://immich.app/docs/overview/introduction)
|
||||
- [示例](#示例)
|
||||
- [功能特性](#功能特性)
|
||||
- [介绍](https://immich.app/docs/overview/introduction)
|
||||
- [安装](https://immich.app/docs/installation/requirements)
|
||||
- [贡献指南](https://immich.app/docs/contribution-guidelines)
|
||||
- [支持本项目](#support-the-project)
|
||||
- [已知问题](#known-issues)
|
||||
|
||||
## 官方文档
|
||||
|
||||
你可以在 https://immich.app/ 找到包含安装手册的官方文档.
|
||||
## 示例
|
||||
|
||||
你可以在 https://demo.immich.app 访问示例.
|
||||
|
||||
在移动端, 你可以使用 `https://demo.immich.app/api`获取`服务终端链接`
|
||||
|
||||
```bash title="示例认证信息"
|
||||
认证信息
|
||||
邮箱: demo@immich.app
|
||||
密码: demo
|
||||
```
|
||||
|
||||
```
|
||||
规格: 甲骨文免费虚拟机套餐-阿姆斯特丹 4核 2.4Ghz ARM64 CPU, 24GB RAM。
|
||||
```
|
||||
|
||||
# 功能特性
|
||||
|
||||
| 功能特性 | 移动端 | 网页端 |
|
||||
| ------------------------------------------- | ------- | --- |
|
||||
| 上传并查看照片和视频 | 是 | 是 |
|
||||
| 软件运行时自动备份 | 是 | N/A |
|
||||
| 选择需要备份的相册 | 是 | N/A |
|
||||
| 下载照片和视频到本地 | 是 | 是 |
|
||||
| 多用户支持 | 是 | 是 |
|
||||
| 相册 | 是 | 是 |
|
||||
| 共享相册 | 是 | 是 |
|
||||
| 可拖动的快速导航栏 | 是 | 是 |
|
||||
| 支持RAW格式 (HEIC, HEIF, DNG, Apple ProRaw) | 是 | 是 |
|
||||
| 元数据视图 (EXIF, 地图) | 是 | 是 |
|
||||
| 通过元数据、对象和标签进行搜索 | 是 | No |
|
||||
| 管理功能 (用户管理) | N/A | 是 |
|
||||
| 后台备份 | Android | N/A |
|
||||
| 虚拟滚动 | 是 | 是 |
|
||||
| OAuth支持 | 是 | 是 |
|
||||
| 实时照片备份和查看 (仅iOS) | 是 | 是 |
|
||||
|
||||
# 支持本项目
|
||||
|
||||
我已经致力于本项目并且将我会持续更新文档、新增功能和修复问题。但是我不能一个人走下去,所以我需要你给予我走下去的动力。
|
||||
|
||||
就像我主页里面 [selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) 说的一样,这是我和团队的一项艰巨的任务。我希望某一天我能够全职开发本项目,在此我希望你们能够助我梦想成真。
|
||||
|
||||
如果你使用了本项目一段时间,并且觉得上面的话有道理,那么请你按照如下方式帮助我吧。
|
||||
|
||||
## 捐赠
|
||||
|
||||
- [按月捐赠](https://github.com/sponsors/alextran1502) via GitHub Sponsors
|
||||
- [一次捐赠](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via Github Sponsors
|
||||
|
||||
# 已知问题
|
||||
|
||||
## TensorFlow 构建问题
|
||||
|
||||
_这是一个针对于Proxmox的已知问题_
|
||||
|
||||
TensorFlow 不能运行在很旧的CPU架构上, 需要运行在AVX和AVX2指令集的CPU上。如果你在docker-compose的命令行中遇到了 `illegal instruction core dump`的错误, 通过如下命令检查你的CPU flag寄存器然后确保你能够看到`AVX`和`AVX2`的字样:
|
||||
|
||||
```bash
|
||||
more /proc/cpuinfo | grep flags
|
||||
```
|
||||
|
||||
如果你在Proxmox中运行虚拟机, 虚拟机中没有启用flag寄存器。
|
||||
|
||||
你需要在虚拟机的硬件面板中把CPU类型从`kvm64`改为`host`。
|
||||
|
||||
`Hardware > Processors > Edit > Advanced > Type (dropdown menu) > host`
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
slug: first-blog-post
|
||||
title: First Blog Post
|
||||
authors:
|
||||
name: Gao Wei
|
||||
title: Docusaurus Core Team
|
||||
url: https://github.com/wgao19
|
||||
image_url: https://github.com/wgao19.png
|
||||
tags: [hola, docusaurus]
|
||||
---
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
@@ -1,44 +0,0 @@
|
||||
---
|
||||
slug: long-blog-post
|
||||
title: Long Blog Post
|
||||
authors: endi
|
||||
tags: [hello, docusaurus]
|
||||
---
|
||||
|
||||
This is the summary of a very long blog post,
|
||||
|
||||
Use a `<!--` `truncate` `-->` comment to limit blog post size in the list view.
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
slug: mdx-blog-post
|
||||
title: MDX Blog Post
|
||||
authors: [slorber]
|
||||
tags: [docusaurus]
|
||||
---
|
||||
|
||||
Blog posts support [Docusaurus Markdown features](https://docusaurus.io/docs/markdown-features), such as [MDX](https://mdxjs.com/).
|
||||
|
||||
:::tip
|
||||
|
||||
Use the power of React to create interactive blog posts.
|
||||
|
||||
```js
|
||||
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||
```
|
||||
|
||||
<button onClick={() => alert('button clicked!')}>Click me!</button>
|
||||
|
||||
:::
|
||||
|
Before Width: | Height: | Size: 94 KiB |
@@ -1,25 +0,0 @@
|
||||
---
|
||||
slug: welcome
|
||||
title: Welcome
|
||||
authors: [slorber, yangshun]
|
||||
tags: [facebook, hello, docusaurus]
|
||||
---
|
||||
|
||||
[Docusaurus blogging features](https://docusaurus.io/docs/blog) are powered by the [blog plugin](https://docusaurus.io/docs/api/plugins/@docusaurus/plugin-content-blog).
|
||||
|
||||
Simply add Markdown files (or folders) to the `blog` directory.
|
||||
|
||||
Regular blog authors can be added to `authors.yml`.
|
||||
|
||||
The blog post date can be extracted from filenames, such as:
|
||||
|
||||
- `2019-05-30-welcome.md`
|
||||
- `2019-05-30-welcome/index.md`
|
||||
|
||||
A blog post folder can be convenient to co-locate blog post images:
|
||||
|
||||

|
||||
|
||||
The blog supports tags as well!
|
||||
|
||||
**And if you don't want a blog**: just delete this directory, and use `blog: false` in your Docusaurus config.
|
||||
@@ -1,17 +1,5 @@
|
||||
endi:
|
||||
name: Endilie Yacop Sucipto
|
||||
title: Maintainer of Docusaurus
|
||||
url: https://github.com/endiliey
|
||||
image_url: https://github.com/endiliey.png
|
||||
|
||||
yangshun:
|
||||
name: Yangshun Tay
|
||||
title: Front End Engineer @ Facebook
|
||||
url: https://github.com/yangshun
|
||||
image_url: https://github.com/yangshun.png
|
||||
|
||||
slorber:
|
||||
name: Sébastien Lorber
|
||||
title: Docusaurus maintainer
|
||||
url: https://sebastienlorber.com
|
||||
image_url: https://github.com/slorber.png
|
||||
alextran:
|
||||
name: Alex Tran
|
||||
title: Maintainer of Immich
|
||||
url: https://github.com/alextran1502
|
||||
image_url: https://github.com/alextran1502.png
|
||||
|
||||
114
docs/blog/release-1.36/index.mdx
Normal file
@@ -0,0 +1,114 @@
|
||||
---
|
||||
slug: release-1-36
|
||||
title: Release v1.36.0
|
||||
authors: [alextran]
|
||||
tags: [release]
|
||||
date: 2022-11-10
|
||||
---
|
||||
|
||||
Hello everyone, it is my pleasure to deliver the new release of Immich to you. The team has been working hard to bring you the new features and improvements. This release includes some big features that the community has been asking since the beginning of Immich. We hope you will enjoy it.
|
||||
|
||||
Some notable features are:
|
||||
|
||||
- [OAuth integration](#livephoto-ios-support-)
|
||||
- [LivePhoto support on iOS](#oauth-integration-)
|
||||
- User config system
|
||||
|
||||
<!--truncate-->
|
||||
|
||||
## LivePhoto iOS Support 🎉
|
||||
|
||||
LivePhoto on iOS is now supported in Immich.
|
||||
|
||||
The motion part will now be uploaded and can be played on the mobile app and the web.
|
||||
|
||||
:::caution
|
||||
|
||||
- The server and the app has to be on version **1.36.x** for the application to work correctly.
|
||||
- Previous uploaded photos will not be updated automatically, you will have to remove and reupload them if you want to keep the LivePhoto functionality.
|
||||
|
||||
:::
|
||||
|
||||
<img
|
||||
src="https://media.giphy.com/media/fTrGceZd7t1ewi8ESc/giphy.gif"
|
||||
width="100%"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
}}
|
||||
title="LivePhoto playback on the web"
|
||||
/>
|
||||
|
||||
## OAuth Integration 🎉
|
||||
|
||||
I want to borrow this chance to express my gratitude to [@EnricoBilla](https://github.com/EnricoBilla), who has been the trailblazer for this feature since the beginning days of Immich. His PR has sparked ideas, suggestions, and discussion among the team member on how to integrate this feature successfully into the app. Thank you so much for your work and your time.
|
||||
|
||||
OAuth is now integrated into the system. Please follow the guide [here](https://immich.app/docs/usage/oauth) to set up your OAuth integration
|
||||
|
||||
After setting up the correct environment variables in the `.env` file, as shown below
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| ------------------- | ------- | -------------------- | ------------------------------------------------------------------------- |
|
||||
| OAUTH_ENABLED | boolean | false | Enable/disable OAuth2 |
|
||||
| OAUTH_ISSUER_URL | URL | (required) | Required. Self-discovery URL for client |
|
||||
| OAUTH_CLIENT_ID | string | (required) | Required. Client ID |
|
||||
| OAUTH_CLIENT_SECRET | string | (required) | Required. Client Secret |
|
||||
| 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 |
|
||||
|
||||
```bash title="Authentik Example"
|
||||
OAUTH_ENABLED=true
|
||||
OAUTH_ISSUER_URL=http://10.1.15.216:9000/application/o/immich-test/
|
||||
OAUTH_CLIENT_ID=30596v8f78a4b6a97d5985c3076b6b4c4d12ddc33
|
||||
OAUTH_CLIENT_SECRET=50f1eafdec353b95b1c638db390db4ab67ef035a51212dbec2f56175e2eb272b5d572c099176e6fe116ecf47ffdd544bgdb9e2edc588307ee0339d25eeccd88
|
||||
OAUTH_BUTTON_TEXT=Login with Authentik
|
||||
```
|
||||
|
||||
The web will have the option to sign in with OAuth.
|
||||
|
||||
<img
|
||||
src="https://user-images.githubusercontent.com/27055614/202923726-f43fa148-47f5-4182-8f29-b0b87e4586fa.png"
|
||||
width="50%"
|
||||
title="Web Sign in with OAuth"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
}}
|
||||
/>
|
||||
|
||||
The mobile app will check if the server has OAuth enabled before displaying the OAuth
|
||||
sign-in button.
|
||||
|
||||
<img
|
||||
src="https://media.giphy.com/media/3iy3SaNkVYtlkEiw06/giphy.gif"
|
||||
title="Mobile sign in with OAuth"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
}}
|
||||
/>
|
||||
|
||||
## Support
|
||||
|
||||
<img
|
||||
src="https://media.giphy.com/media/LStqgGESXW8XnuCv5y/giphy.gif"
|
||||
width="300"
|
||||
style={{
|
||||
borderRadius: "10px",
|
||||
boxShadow:
|
||||
"rgba(9, 30, 66, 0.25) 0px 1px 1px, rgba(9, 30, 66, 0.13) 0px 0px 1px 1px",
|
||||
}}
|
||||
title="Support the project"
|
||||
/>
|
||||
|
||||
If you find the project helpful and it helps you in some ways, you can support the project [one time](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) or [monthly](https://github.com/sponsors/alextran1502) from GitHub Sponsor
|
||||
|
||||
It is a great way to let me know that you want me to continue developing and working on this project for years to come.
|
||||
|
||||
## Details
|
||||
|
||||
For more details, please check out the [release note](https://github.com/immich-app/immich/releases/tag/v1.36.0_55-dev)
|
||||
@@ -13,7 +13,13 @@ sidebar_position: 6
|
||||
|  | Asset was uploaded from this device and is now backed up in the cloud/server and still available in original on the device |
|
||||
|
||||
### How can I sync an existing directory with Immich's server?
|
||||
|
||||
Immich doesn't have the mechanism to sync an existing directory with the server. There is however, a helper CLI tool to help you bulk upload the existing photos and videos to the server. You can find the guide to use the CLI tool [here](/docs/usage/bulk-upload.md).
|
||||
|
||||
### Why doesn't Immich watch an existing photo gallery directory?
|
||||
The initial approach of Immich is to become a backup tool, primarily for mobile device usage. Thus, all the assets must be uploaded from the mobile client. The app was architectured to perform that job well.
|
||||
|
||||
The initial approach of Immich is to become a backup tool, primarily for mobile device usage. Thus, all the assets must be uploaded from the mobile client. The app was architectured to perform that job well.
|
||||
|
||||
### How can I reset the admin password?
|
||||
|
||||
The admin password can be reset by running the [reset-admin-password](/docs/usage/server-commands) command on the immich-server.
|
||||
|
||||
BIN
docs/docs/installation/img/unraid01.webp
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
docs/docs/installation/img/unraid02.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
docs/docs/installation/img/unraid03.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/docs/installation/img/unraid04.png
Normal file
|
After Width: | Height: | Size: 910 B |
BIN
docs/docs/installation/img/unraid05.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/docs/installation/img/unraid06.webp
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
docs/docs/installation/img/unraid07.webp
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
docs/docs/installation/img/unraid08.webp
Normal file
|
After Width: | Height: | Size: 39 KiB |
@@ -4,12 +4,104 @@ sidebar_position: 5
|
||||
|
||||
# Unraid
|
||||
|
||||
Install Immich on Unraid.
|
||||
Install Immich on Unraid using the [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/) plugin from the Unraid Community Apps.
|
||||
|
||||
:::info
|
||||
|
||||
- Guide was written using Unraid v6.11.1
|
||||
- Requires you to have installed the plugin: [Docker Compose Manager](https://forums.unraid.net/topic/114415-plugin-docker-compose-manager/)
|
||||
- An Unraid share created for your images
|
||||
- There has been a [report](https://forums.unraid.net/topic/130006-errortraps-traps-node27707-trap-invalid-opcode-ip14fcfc8d03c0-sp7fff32889dd8-more/#comment-1189395) of this not working if your Unraid server doesn't support AVX _(e.g. using a T610)_
|
||||
|
||||
:::info Community contribution
|
||||
Please follow [this community contributed article](https://mfaz.dev/posts/immich-unraid/) to install Immich on Unraid.
|
||||
:::
|
||||
|
||||
1. Go to "**Plugins**" and click on "**Compose.Manager**"
|
||||
2. Click "**Add New Stack**" and when prompted for a label enter "**Immich**"
|
||||
|
||||
<img
|
||||
src={require('./img/unraid01.webp').default}
|
||||
width="70%"
|
||||
alt="Select Plugins > Compose.Manager > Add New Stack > Label it Immich"
|
||||
/>
|
||||
|
||||
3. Select the cog ⚙️ next to Immich then click "**Edit Stack**"
|
||||
4. Click "**Compose File**" and then paste the entire contents of the [Immich Docker Compose](https://raw.githubusercontent.com/immich-app/immich/main/docker/docker-compose.yml) file into the Unraid editor
|
||||
<details >
|
||||
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 5.</summary>
|
||||
<ul>
|
||||
<li>Comment out the database service</li>
|
||||
<img
|
||||
src={require('./img/unraid02.png').default}
|
||||
width="50%"
|
||||
alt="Comment out database service in the compose file"
|
||||
/>
|
||||
<li>Comment out the database dependency for <b>each service</b> <i>(example in screenshot below only shows 2 of the services - ensure you do this for all services)</i></li>
|
||||
<img
|
||||
src={require('./img/unraid03.png').default}
|
||||
width="50%"
|
||||
alt="Comment out every reference to the database service in the compose file"
|
||||
/>
|
||||
<li>Comment out the volumes</li>
|
||||
<img
|
||||
src={require('./img/unraid04.png').default}
|
||||
width="20%"
|
||||
alt="Comment out database volume"
|
||||
/>
|
||||
</ul>
|
||||
</details>
|
||||
5. Click "**Save Changes**", you will be promoted to edit stack UI labels, just leave this blank and click "**Ok**"
|
||||
6. Select the cog ⚙️ next to Immich, click "**Edit Stack**", then click "**Env File**"
|
||||
7. Past the entire contents of the [Immich .env.example](https://raw.githubusercontent.com/immich-app/immich/main/docker/.env.example) file into the Unraid editor, then **before saving** edit the following:
|
||||
|
||||
- `JWT_SECRET`: Generate a unique secret and paste the value here > Can be generated by either typing `openssl rand -base64 128` in your terminal or copying from [uuidgenerator](https://www.uuidgenerator.net/version1)
|
||||
- `UPLOAD_LOCATION`: Create a folder in your Images Unraid share and place the **absolute** location here > For example my _"images"_ share has a folder within it called _"immich"_. If I browse to this directory in the terminal and type `pwd` the output is `/mnt/user/images/immich`. This is the exact value I need to enter as my `UPLOAD_LOCATION`
|
||||
|
||||
<img
|
||||
src={require('./img/unraid05.webp').default}
|
||||
width="70%"
|
||||
alt="Absolute location of where you want immich images stored"
|
||||
/>
|
||||
|
||||
<details >
|
||||
<summary>Using an existing Postgres container? Click me! Otherwise proceed to step 8.</summary>
|
||||
<p>Update the following database variables as relevant to your Postgres container:</p>
|
||||
<ul>
|
||||
<li><code>DB_HOSTNAME</code></li>
|
||||
<li><code>DB_USERNAME</code></li>
|
||||
<li><code>DB_PASSWORD</code></li>
|
||||
<li><code>DB_DATABASE_NAME</code></li>
|
||||
<li><code>DB_PORT</code></li>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
8. Click "**Save Changes**" followed by "**Compose Up**" and Unraid will begin to create the Immich containers in a popup window. Once complete you will see a message on the popup window stating _"Connection Closed"_. Click "**Done**" and go to the Unraid "**Docker**" page
|
||||
|
||||
> Note: This can take several minutes depending on your Internet speed and Unraid hardware
|
||||
|
||||
9. Once on the Docker page you will see several Immich containers, one of them will be labelled `immich_proxy` and will have a port mapping. Visit the `IP:PORT` displayed in your web browser and you should see the Immich admin setup page.
|
||||
|
||||
<img
|
||||
src={require('./img/unraid06.webp').default}
|
||||
width="80%"
|
||||
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
|
||||
/>
|
||||
|
||||
<details >
|
||||
<summary>Using the Unraid Docker Folders plugin? Click me! Otherwise you're complete!</summary>
|
||||
<p>If you are using the Docker Folders plugin go the Docker tab and select "<b>New Folder</b>".<br />Label it <i>"Immich"</i> and use the logo from the <a href="https://immich.app/">Immich homepage</a> <i>(right click the logo, "Save As", and reupload to Unraid)</i><br />Then simply select all the Immich related containers before clicking "<b>Submit</b>"</p>
|
||||
<img
|
||||
src={require('./img/unraid07.webp').default}
|
||||
width="80%"
|
||||
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
|
||||
/>
|
||||
<img
|
||||
src={require('./img/unraid08.webp').default}
|
||||
width="90%"
|
||||
alt="Go to Docker Tab and visit the address listed next to immich-proxy"
|
||||
/>
|
||||
|
||||
</details>
|
||||
|
||||
:::tip
|
||||
For more information on how to use the application, please refer to the [Post Installation](/docs/usage/post-installation) guide.
|
||||
:::
|
||||
For more information on how to use the application once installed, please refer to the [Post Installation](/docs/usage/post-installation) guide.
|
||||
:::
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"label": "How to use the application",
|
||||
"label": "Usage",
|
||||
"position": 3,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
|
||||
@@ -28,13 +28,13 @@ Before enabling OAuth in Immich, a new client application needs to be configured
|
||||
|
||||
2. Configure Redirect URIs/Origins
|
||||
|
||||
The **Sign-in redirect URIs** should include:
|
||||
The **Sign-in redirect URIs** should include:
|
||||
|
||||
- All URLs that will be used to access the login page of the Immich web client (eg. `http://localhost:2283/auth/login`, `http://192.168.0.200:2283/auth/login`, `https://immich.example.com/auth/login`)
|
||||
- Mobile app redirect URL `app.immich:/`
|
||||
|
||||
* All URLs that will be used to access the login page of the Immich web client (eg. `http://localhost:2283/auth/login`, `http://192.168.0.200:2283/auth/login`, `https://immich.example.com/auth/login`)
|
||||
* Mobile app redirect URL `app.immich:/`
|
||||
|
||||
:::caution
|
||||
You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobile app to work properly.
|
||||
You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobile app to work properly.
|
||||
|
||||
**Authentik example**
|
||||
<img src={require('./img/authentik-redirect.png').default} title="Authentik Redirection URL" width="80%" />
|
||||
@@ -42,17 +42,17 @@ You **MUST** include `app.immich:/` as the redirect URI for iOS and Android mobi
|
||||
|
||||
## Enable OAuth
|
||||
|
||||
Once you have a new OAuth client application configured, Immich can be configured using the following environment variables:
|
||||
Once you have a new OAuth client application configured, Immich can be configured using the Administration Settings page, available on the web (Administration -> Settings).
|
||||
|
||||
| Key | Type | Default | Description |
|
||||
| Setting | Type | Default | Description |
|
||||
| ------------------- | ------- | -------------------- | ------------------------------------------------------------------------- |
|
||||
| OAUTH_ENABLED | boolean | false | Enable/disable OAuth2 |
|
||||
| OAUTH_ISSUER_URL | URL | (required) | Required. Self-discovery URL for client (from previous step) |
|
||||
| OAUTH_CLIENT_ID | string | (required) | Required. Client ID (from previous step) |
|
||||
| OAUTH_CLIENT_SECRET | string | (required) | Required. Client Secret (previous step |
|
||||
| OAUTH_SCOPE | string | openid email profile | Full list of scopes to send with the request (space delimited) |
|
||||
| OAUTH_AUTO_REGISTER | boolean | true | When true, will automatically register a user the first time they sign in |
|
||||
| OAUTH_BUTTON_TEXT | string | Login with OAuth | Text for the OAuth button on the web |
|
||||
| OAuth enabled | boolean | false | Enable/disable OAuth2 |
|
||||
| OAuth issuer URL | URL | (required) | Required. Self-discovery URL for client (from previous step) |
|
||||
| OAuth client ID | string | (required) | Required. Client ID (from previous step) |
|
||||
| OAuth client secret | string | (required) | Required. Client Secret (previous step) |
|
||||
| OAuth scope | string | openid email profile | Full list of scopes to send with the request (space delimited) |
|
||||
| OAuth button text | string | Login with OAuth | Text for the OAuth button on the web |
|
||||
| OAuth auto register | boolean | true | When true, will automatically register a user the first time they sign in |
|
||||
|
||||
:::info
|
||||
The Issuer URL should look something like the following, and return a valid json document.
|
||||
@@ -63,14 +63,4 @@ The Issuer URL should look something like the following, and return a valid json
|
||||
The `.well-known/openid-configuration` part of the url is optional and will be automatically added during discovery.
|
||||
:::
|
||||
|
||||
Here is an example of a valid configuration for setting up Immich to use OAuth with Authentik:
|
||||
|
||||
```
|
||||
OAUTH_ENABLED=true
|
||||
OAUTH_ISSUER_URL=http://192.168.0.187:9000/application/o/immich
|
||||
OAUTH_CLIENT_ID=f08f9c5b4f77dcfd3916b1c032336b5544a7b368
|
||||
OAUTH_CLIENT_SECRET=6fe2e697644da6ff6aef73387a457d819018189086fa54b151a6067fbb884e75f7e5c90be16d3c688cf902c6974817a85eab93007d76675041eaead8c39cf5a2
|
||||
OAUTH_BUTTON_TEXT=Login with Authentik
|
||||
```
|
||||
|
||||
[oidc]: https://openid.net/connect/
|
||||
|
||||
25
docs/docs/usage/server-commands.md
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# Server Commands
|
||||
|
||||
The `immich-server` docker image comes preinstalled with an administrative CLI that supports the following commands:
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------------- | ------------------------------------- |
|
||||
| `immich help` | Display help |
|
||||
| `immich reset-admin-password` | Reset the password for the admin user |
|
||||
|
||||
## How to run a command
|
||||
|
||||
To run a command, connect to the container and then execute it. For example:
|
||||
|
||||
```bash
|
||||
docker exec -it immich-server_1 sh
|
||||
|
||||
/usr/src/app$ immich reset-admin-password
|
||||
? Please choose a new password (optional) immich-is-awesome-unlike-this-password
|
||||
New password:
|
||||
immich-is-awesome-unlike-this-password
|
||||
```
|
||||
@@ -30,8 +30,8 @@ const config = {
|
||||
|
||||
presets: [
|
||||
[
|
||||
"classic",
|
||||
/** @type {import('@docusaurus/preset-classic').Options} */
|
||||
"docusaurus-preset-openapi",
|
||||
/** @type {import('docusaurus-preset-openapi').Options} */
|
||||
({
|
||||
docs: {
|
||||
showLastUpdateAuthor: true,
|
||||
@@ -42,6 +42,10 @@ const config = {
|
||||
// Remove this to remove the "edit this page" links.
|
||||
editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
||||
},
|
||||
api: {
|
||||
path: "../server/immich-openapi-specs.json",
|
||||
routeBasePath: "/docs/api"
|
||||
},
|
||||
// blog: {
|
||||
// showReadingTime: true,
|
||||
// editUrl: "https://github.com/immich-app/immich/tree/main/docs/",
|
||||
@@ -80,7 +84,12 @@ const config = {
|
||||
position: "right",
|
||||
label: "Documentation",
|
||||
},
|
||||
// { to: "/blog", label: "Blog", position: "right" },
|
||||
{
|
||||
to: "/docs/api",
|
||||
position: "right",
|
||||
label: "API"
|
||||
},
|
||||
{ to: "/blog", label: "Blog", position: "right" },
|
||||
{
|
||||
href: "https://github.com/immich-app/immich",
|
||||
label: "GitHub",
|
||||
|
||||
2742
docs/package-lock.json
generated
@@ -19,9 +19,11 @@
|
||||
"@docusaurus/preset-classic": "2.1.0",
|
||||
"@mdx-js/react": "^1.6.22",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-preset-openapi": "^0.6.3",
|
||||
"prism-react-renderer": "^1.3.5",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2"
|
||||
"react-dom": "^17.0.2",
|
||||
"url": "^0.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "2.1.0",
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
config_version: 1.0
|
||||
project_id: ead34689-ec52-41d9-b675-09bc85a6cbd7
|
||||
file_type: json
|
||||
branch: main
|
||||
upload:
|
||||
files:
|
||||
- file: mobile/assets/i18n/en-US.json
|
||||
locale_code: en-US
|
||||
- file: mobile/assets/i18n/de-DE.json
|
||||
locale_code: de-DE
|
||||
- file: mobile/assets/i18n/fr-FR.json
|
||||
locale_code: fr-FR
|
||||
- file: mobile/assets/i18n/it-IT.json
|
||||
locale_code: it-IT
|
||||
- file: mobile/assets/i18n/nl-NL.json
|
||||
locale_code: nl-NL
|
||||
- file: mobile/assets/i18n/ko-KR.json
|
||||
locale_code: ko-KR
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
download:
|
||||
params:
|
||||
export_empty_as: main
|
||||
files:
|
||||
- file: mobile/assets/i18n/en-US.json
|
||||
locale_code: en-US
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
- file: mobile/assets/i18n/de-DE.json
|
||||
locale_code: de-DE
|
||||
- file: mobile/assets/i18n/fr-FR.json
|
||||
@@ -31,5 +24,13 @@ download:
|
||||
locale_code: nl-NL
|
||||
- file: mobile/assets/i18n/ko-KR.json
|
||||
locale_code: ko-KR
|
||||
- file: mobile/assets/i18n/da-DK.json
|
||||
locale_code: da-DK
|
||||
- file: mobile/assets/i18n/es-ES.json
|
||||
locale_code: es-ES
|
||||
- file: mobile/assets/i18n/fi-FI.json
|
||||
locale_code: fi-FI
|
||||
- file: mobile/assets/i18n/ja-JP.json
|
||||
locale_code: ja-JP
|
||||
- file: mobile/assets/i18n/pt-BR.json
|
||||
locale_code: pt-BR
|
||||
- file: mobile/assets/i18n/pl-PL.json
|
||||
locale_code: pl-PL
|
||||
|
||||
@@ -52,7 +52,7 @@ android {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId "app.alextran.immich"
|
||||
minSdkVersion 23
|
||||
targetSdkVersion flutter.targetSdkVersion
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
}
|
||||
|
||||
@@ -38,6 +38,9 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- If you want to read images-->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- If you want to read videos-->
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <!-- If you want to read audio-->
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
|
||||
@@ -54,7 +54,9 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
||||
val args = call.arguments<ArrayList<*>>()!!
|
||||
val requireUnmeteredNetwork = args.get(0) as Boolean
|
||||
val requireCharging = args.get(1) as Boolean
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging)
|
||||
val triggerUpdateDelay = (args.get(2) as Number).toLong()
|
||||
val triggerMaxDelay = (args.get(3) as Number).toLong()
|
||||
ContentObserverWorker.configureWork(ctx, requireUnmeteredNetwork, requireCharging, triggerUpdateDelay, triggerMaxDelay)
|
||||
result.success(true)
|
||||
}
|
||||
"disable" -> {
|
||||
|
||||
@@ -37,6 +37,8 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
const val SHARED_PREF_SERVICE_ENABLED = "serviceEnabled"
|
||||
const val SHARED_PREF_REQUIRE_WIFI = "requireWifi"
|
||||
const val SHARED_PREF_REQUIRE_CHARGING = "requireCharging"
|
||||
const val SHARED_PREF_TRIGGER_UPDATE_DELAY = "triggerUpdateDelay"
|
||||
const val SHARED_PREF_TRIGGER_MAX_DELAY = "triggerMaxDelay"
|
||||
|
||||
private const val TASK_NAME_OBSERVER = "immich/ContentObserver"
|
||||
|
||||
@@ -62,12 +64,16 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
*/
|
||||
fun configureWork(context: Context,
|
||||
requireWifi: Boolean = false,
|
||||
requireCharging: Boolean = false) {
|
||||
requireCharging: Boolean = false,
|
||||
triggerUpdateDelay: Long = 5000,
|
||||
triggerMaxDelay: Long = 50000) {
|
||||
context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
.edit()
|
||||
.putBoolean(SHARED_PREF_SERVICE_ENABLED, true)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_WIFI, requireWifi)
|
||||
.putBoolean(SHARED_PREF_REQUIRE_CHARGING, requireCharging)
|
||||
.putLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, triggerUpdateDelay)
|
||||
.putLong(SHARED_PREF_TRIGGER_MAX_DELAY, triggerMaxDelay)
|
||||
.apply()
|
||||
BackupWorker.updateBackupWorker(context, requireWifi, requireCharging)
|
||||
}
|
||||
@@ -106,12 +112,14 @@ class ContentObserverWorker(ctx: Context, params: WorkerParameters) : Worker(ctx
|
||||
}
|
||||
|
||||
private fun enqueueObserverWorker(context: Context, policy: ExistingWorkPolicy) {
|
||||
val sp = context.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
||||
val constraints = Constraints.Builder()
|
||||
.addContentUriTrigger(MediaStore.Images.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.INTERNAL_CONTENT_URI, true)
|
||||
.addContentUriTrigger(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, true)
|
||||
.setTriggerContentUpdateDelay(5000, TimeUnit.MILLISECONDS)
|
||||
.setTriggerContentUpdateDelay(sp.getLong(SHARED_PREF_TRIGGER_UPDATE_DELAY, 5000), TimeUnit.MILLISECONDS)
|
||||
.setTriggerContentMaxDelay(sp.getLong(SHARED_PREF_TRIGGER_MAX_DELAY, 50000), TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
val work = OneTimeWorkRequest.Builder(ContentObserverWorker::class.java)
|
||||
|
||||
@@ -35,8 +35,8 @@ platform :android do
|
||||
task: 'bundle',
|
||||
build_type: 'Release',
|
||||
properties: {
|
||||
"android.injected.version.code" => 55,
|
||||
"android.injected.version.name" => "1.36.0",
|
||||
"android.injected.version.code" => 60,
|
||||
"android.injected.version.name" => "1.38.0",
|
||||
}
|
||||
)
|
||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
* Fixed freezed splash screen
|
||||
* Fixed OIDC redirect but not logging in
|
||||
@@ -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.000315">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000201">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="185.624188">
|
||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="63.132489">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="39.180655">
|
||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="38.15883">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "EKSKLUDERET",
|
||||
"album_info_card_backup_album_included": "INKLUDERET",
|
||||
"album_thumbnail_card_item": "1 genstand",
|
||||
"album_thumbnail_card_items": "{} genstande",
|
||||
"album_thumbnail_card_shared": ". Delt",
|
||||
"album_viewer_appbar_share_delete": "Slet album",
|
||||
"album_viewer_appbar_share_err_delete": "Fejlede sletning af album",
|
||||
"album_viewer_appbar_share_err_leave": "Fejlede i at forlade album",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Forlad album",
|
||||
"album_viewer_appbar_share_remove": "Fjern fra album",
|
||||
"album_viewer_page_share_add_users": "Tilføj brugere",
|
||||
"asset_list_settings_subtitle": "Indstillinger for billedgitterlayout",
|
||||
"asset_list_settings_title": "Billedgitter",
|
||||
"backup_album_selection_page_albums_device": "Albummer på enhed ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tryk en gang for at inkludere, tryk to gange for at ekskludere",
|
||||
"backup_album_selection_page_assets_scatter": "Elementer kan være spredt på tværs af flere albummer. Albummer kan således inkluderes eller udelukkes under sikkerhedskopieringsprocessen.",
|
||||
@@ -16,7 +21,27 @@
|
||||
"backup_album_selection_page_selection_info": "Oplysninger om valgte",
|
||||
"backup_album_selection_page_total_assets": "Samlede unikke elementer",
|
||||
"backup_all": "Alt",
|
||||
"backup_controller_page_albums": "Sikkerhedskopier albummer",
|
||||
"backup_background_service_backup_failed_message": "Backup af billeder og videoer fejlede. Forsøger igen...",
|
||||
"backup_background_service_connection_failed_message": "Forbindelsen til serveren blev tabt. Forsøger igen...",
|
||||
"backup_background_service_current_upload_notification": "Uploader {}",
|
||||
"backup_background_service_default_notification": "Checking for new assets…",
|
||||
"backup_background_service_error_title": "Fejl med backup",
|
||||
"backup_background_service_in_progress_notification": "Tager backup af dine billeder og videoer...",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Sikkerhedskopiér albummer",
|
||||
"backup_controller_page_background_battery_info_link": "Vis mig hvordan",
|
||||
"backup_controller_page_background_battery_info_message": "For den bedste oplevelse med baggrundsbackup, bør du slå batterioptimering, der begrænder baggrundsaktivitet, fra.\n\nSiden dette er afhængigt af enheden, bør du undersøge denne information leveret af din enheds producent.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Batterioptimering",
|
||||
"backup_controller_page_background_charging": "Kun under opladning",
|
||||
"backup_controller_page_background_configure_error": "Fejlede konfigureringen af baggrundsbackup",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Slå baggrundsbackup til, for automatisk at tage backup af nye billeder og videoer, uden at skulle åbne appen",
|
||||
"backup_controller_page_background_is_off": "Automatisk baggrundsbackup er slået fra",
|
||||
"backup_controller_page_background_is_on": "Automatisk baggrundsbackup er slået til",
|
||||
"backup_controller_page_background_turn_off": "Slå baggrundsbackup fra",
|
||||
"backup_controller_page_background_turn_on": "Slå baggrundsbackup til",
|
||||
"backup_controller_page_background_wifi": "Kun med WiFi",
|
||||
"backup_controller_page_backup": "Sikkerhedskopier",
|
||||
"backup_controller_page_backup_selected": "Valgte: ",
|
||||
"backup_controller_page_backup_sub": "Sikkerhedskopierede billeder og videoer",
|
||||
@@ -45,7 +70,27 @@
|
||||
"backup_controller_page_uploading_file_info": "Uploader filinformation",
|
||||
"backup_err_only_album": "Kan ikke slette det eneste album",
|
||||
"backup_info_card_assets": "elementer",
|
||||
"cache_settings_album_thumbnails": "Biblioteksminiaturebilleder ({} billeder og videoer)",
|
||||
"cache_settings_clear_cache_button": "Fjern cache",
|
||||
"cache_settings_clear_cache_button_title": "Fjern appens cache. Dette vil i stor grad påvirke appens ydeevne indtil cachen er genopbygget.",
|
||||
"cache_settings_image_cache_size": "Størrelse af billedecache ({} billeder og videoer)",
|
||||
"cache_settings_statistics_album": "Biblioteksminiaturer",
|
||||
"cache_settings_statistics_assets": "{} billeder og videoer ({})",
|
||||
"cache_settings_statistics_full": "Fulde billeder",
|
||||
"cache_settings_statistics_shared": "Miniaturebilleder til delte albummer",
|
||||
"cache_settings_statistics_thumbnail": "Miniaturebilleder",
|
||||
"cache_settings_statistics_title": "Cacheforbrug",
|
||||
"cache_settings_subtitle": "Håndter cache-adfærden for Immich-appen.",
|
||||
"cache_settings_thumbnail_size": "Størrelse af miniaturebillede cache ({} billeder og videoer)",
|
||||
"cache_settings_title": "Cache-indstillinger",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Slet",
|
||||
"control_bottom_app_bar_share": "Del",
|
||||
"create_album_page_untitled": "Uden titel",
|
||||
"create_shared_album_page_create": "Opret",
|
||||
"create_shared_album_page_share": "Del",
|
||||
"create_shared_album_page_share_add_assets": "TILFØJ ELEMENT",
|
||||
"create_shared_album_page_share_select_photos": "Vælg billeder",
|
||||
@@ -59,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "Tilføj beskrivelse...",
|
||||
"exif_bottom_sheet_details": "DETALJER",
|
||||
"exif_bottom_sheet_location": "LOKATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "Under udarbejdelse",
|
||||
"experimental_settings_new_asset_list_title": "Aktiver eksperimentelt fotogitter",
|
||||
"experimental_settings_subtitle": "Brug på eget ansvar!",
|
||||
"experimental_settings_title": "Eksperimentelle",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albummer",
|
||||
"library_page_new_album": "Nyt album",
|
||||
"login_form_button_text": "Log ind",
|
||||
"login_form_email_hint": "din-email@email.com",
|
||||
"login_form_endpoint_hint": "http://din-server-ip:port/api",
|
||||
@@ -67,13 +120,17 @@
|
||||
"login_form_err_invalid_email": "Ugyldig email",
|
||||
"login_form_err_leading_whitespace": "Mellemrum før",
|
||||
"login_form_err_trailing_whitespace": "Mellemrum efter",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Der opstod en vejl ved at logge ind. Tjek server URL, email og kodeordet",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Kodeord",
|
||||
"login_form_password_hint": "kodeord",
|
||||
"login_form_save_login": "Forbliv logget ind",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Klient og server er ajour",
|
||||
"profile_drawer_settings": "Indstillinger",
|
||||
"profile_drawer_sign_out": "Log ud",
|
||||
"search_bar_hint": "Søg i dine billeder",
|
||||
"search_page_no_objects": "Ingen elementer er tilgængelige",
|
||||
@@ -84,19 +141,50 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Anbefalinger",
|
||||
"select_user_for_sharing_page_err_album": "Fejlede i at oprette et nyt album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Anbefalinger",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Giv besked om baggrundsbackupfejl: {}",
|
||||
"setting_notifications_notify_hours": "{} timer",
|
||||
"setting_notifications_notify_immediately": "med det samme",
|
||||
"setting_notifications_notify_minutes": "{} minutter",
|
||||
"setting_notifications_notify_never": "aldrig",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detaljeret uploadstatus pr. billed og video",
|
||||
"setting_notifications_single_progress_title": "Vis detaljeret baggrundsuploadstatus",
|
||||
"setting_notifications_subtitle": "Tilpas dine notifikationspræferencer",
|
||||
"setting_notifications_title": "Notifikationer",
|
||||
"setting_notifications_total_progress_subtitle": "Samlet uploadstatus (færdige/samlede billeder og videoer)",
|
||||
"setting_notifications_total_progress_title": "Vis samlet baggrundsuploadstatus",
|
||||
"setting_pages_app_bar_settings": "Indstillinger",
|
||||
"settings_require_restart": "Genstart venligst Immich for at anvende denne ændring",
|
||||
"share_add": "Tilføj",
|
||||
"share_add_photos": "Tilføj billeder",
|
||||
"share_add_title": "Tilføj en titel",
|
||||
"share_create_album": "Opret album",
|
||||
"share_dialog_preparing": "Forbereder...",
|
||||
"share_invite": "Inviter til album",
|
||||
"sharing_page_album": "Delt albums",
|
||||
"sharing_page_description": "Opret delte albummer for at dele billeder og video med personer på dit netværk.",
|
||||
"sharing_page_empty_list": "TOM LISTE",
|
||||
"sharing_silver_appbar_create_shared_album": "Opret delt album",
|
||||
"sharing_silver_appbar_share_partner": "Del med partner",
|
||||
"tab_controller_nav_library": "Bibliotek",
|
||||
"tab_controller_nav_photos": "Billeder",
|
||||
"tab_controller_nav_search": "Søg",
|
||||
"tab_controller_nav_sharing": "Deling",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Vis opbevaringsindikator på filer",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Antal billeder og videoer per række ({})",
|
||||
"theme_setting_dark_mode_switch": "Mørk tilstand",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten i billedfremviseren",
|
||||
"theme_setting_image_viewer_quality_title": "Billedfremviserkvalitet",
|
||||
"theme_setting_system_theme_switch": "Automatisk (Følg systemindstillinger)",
|
||||
"theme_setting_theme_subtitle": "Vælg appens temaindstilling",
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Tre-trins indlæsning kan øge ydeevnen, men kan ligeledes føre til højere netværksbelastning",
|
||||
"theme_setting_three_stage_loading_title": "Slå tre-trins indlæsning til",
|
||||
"version_announcement_overlay_ack": "Vedkend",
|
||||
"version_announcement_overlay_release_notes": "udgivelsesnoter",
|
||||
"version_announcement_overlay_text_1": "Hej vej, der er en ny version af",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Album verlassen",
|
||||
"album_viewer_appbar_share_remove": "Entferne vom Album",
|
||||
"album_viewer_page_share_add_users": "Nutzer hinzufügen",
|
||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||
"asset_list_settings_title": "Photo Grid",
|
||||
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
|
||||
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
||||
@@ -19,12 +21,31 @@
|
||||
"backup_album_selection_page_selection_info": "Auswahl",
|
||||
"backup_album_selection_page_total_assets": "Elemente",
|
||||
"backup_all": "Alle",
|
||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_default_notification": "Suche nach neuen assets…",
|
||||
"backup_background_service_error_title": "Backup error",
|
||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Gesicherte Alben",
|
||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_backup": "Sicherung",
|
||||
"backup_controller_page_backup_selected": "Ausgewählt: ",
|
||||
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
|
||||
"backup_controller_page_cancel": "Abbrechen",
|
||||
"backup_background_service_default_notification": "Suche nach neuen assets…",
|
||||
"backup_controller_page_created": "Erstellt: {}",
|
||||
"backup_controller_page_desc_backup": "Aktiviere die Sicherung um Elemente automatisch auf den Server zu laden.",
|
||||
"backup_controller_page_excluded": "Ausgeschlossen: ",
|
||||
@@ -49,6 +70,23 @@
|
||||
"backup_controller_page_uploading_file_info": "Informationen",
|
||||
"backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
|
||||
"backup_info_card_assets": "Elemente",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Vorschaubilder",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Löschen",
|
||||
"control_bottom_app_bar_share": "Teilen",
|
||||
"create_album_page_untitled": "Unbenannt",
|
||||
@@ -66,6 +104,12 @@
|
||||
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "STANDORT",
|
||||
"experimental_settings_new_asset_list_subtitle": "In Arbeit",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimentell",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Alben",
|
||||
"library_page_new_album": "Neues Album",
|
||||
"login_form_button_text": "Anmelden",
|
||||
@@ -76,12 +120,15 @@
|
||||
"login_form_err_invalid_email": "Ungültige E-Mail",
|
||||
"login_form_err_leading_whitespace": "Führendes Leerzichen",
|
||||
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Error logging you in, check server url, email and password",
|
||||
"login_form_label_email": "E-Mail",
|
||||
"login_form_label_password": "Passwort",
|
||||
"login_form_password_hint": "password",
|
||||
"login_form_save_login": "Angemeldet bleiben",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
||||
"profile_drawer_settings": "Einstellungen",
|
||||
"profile_drawer_sign_out": "Abmelden",
|
||||
@@ -94,7 +141,25 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
||||
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detaillierte Upload Informationen für jedes Element.",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Einstellungen",
|
||||
"settings_require_restart": "Bitte starte Immich neu, um diese Einstellung anzuwenden.",
|
||||
"share_add": "Hinzufügen",
|
||||
"share_add_photos": "Fotos hinzufügen",
|
||||
"share_add_title": "Titel hinzufügen",
|
||||
@@ -110,6 +175,8 @@
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Suche",
|
||||
"tab_controller_nav_sharing": "Teilen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
||||
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
||||
@@ -124,4 +191,4 @@
|
||||
"version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
|
||||
"version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
|
||||
"version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89"
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
||||
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
||||
"backup_album_selection_page_select_albums": "Select Albums",
|
||||
"backup_album_selection_page_select_albums": "Select albums",
|
||||
"backup_album_selection_page_selection_info": "Selection Info",
|
||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
||||
"backup_all": "All",
|
||||
@@ -35,6 +35,7 @@
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
@@ -82,6 +83,10 @@
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Delete",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
@@ -99,6 +104,12 @@
|
||||
"exif_bottom_sheet_description": "Add Description...",
|
||||
"exif_bottom_sheet_details": "DETAILS",
|
||||
"exif_bottom_sheet_location": "LOCATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_new_album": "New album",
|
||||
"login_form_button_text": "Login",
|
||||
@@ -109,14 +120,15 @@
|
||||
"login_form_err_invalid_email": "Invalid Email",
|
||||
"login_form_err_leading_whitespace": "Leading whitespace",
|
||||
"login_form_err_trailing_whitespace": "Trailing whitespace",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Password",
|
||||
"login_form_password_hint": "password",
|
||||
"login_form_save_login": "Stay logged in",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||
"profile_drawer_settings": "Settings",
|
||||
"profile_drawer_sign_out": "Sign Out",
|
||||
@@ -129,18 +141,25 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||
"select_user_for_sharing_page_err_album": "Failed to create album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Add",
|
||||
"share_add_photos": "Add photos",
|
||||
"share_add_title": "Add a title",
|
||||
@@ -171,13 +190,5 @@
|
||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
||||
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album"
|
||||
}
|
||||
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "EXCLUIDOS",
|
||||
"album_info_card_backup_album_included": "INCLUIDOS",
|
||||
"album_thumbnail_card_item": "1 item",
|
||||
"album_thumbnail_card_items": "{} items",
|
||||
"album_thumbnail_card_shared": " · Shared",
|
||||
"album_viewer_appbar_share_delete": "Eliminar álbum ",
|
||||
"album_viewer_appbar_share_err_delete": "No ha podido eliminar el álbum",
|
||||
"album_viewer_appbar_share_err_leave": "No ha podido dejar el álbum",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Abandonar álbum ",
|
||||
"album_viewer_appbar_share_remove": "Eliminar del álbum ",
|
||||
"album_viewer_page_share_add_users": "Añadir usuarios",
|
||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||
"asset_list_settings_title": "Photo Grid",
|
||||
"backup_album_selection_page_albums_device": "Álbumes en el dispositivo ({})",
|
||||
"backup_album_selection_page_albums_tap": "Toque para incluir, doble toque para excluir",
|
||||
"backup_album_selection_page_assets_scatter": "Los activos pueden dispersarse en varios álbumes. De este modo, los álbumes pueden ser incluidos o excluidos durante el proceso de copia de seguridad.",
|
||||
@@ -16,13 +21,37 @@
|
||||
"backup_album_selection_page_selection_info": "Información sobre la Selección",
|
||||
"backup_album_selection_page_total_assets": "Total de activos únicos",
|
||||
"backup_all": "Todos",
|
||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||
"backup_background_service_default_notification": "Checking for new assets…",
|
||||
"backup_background_service_error_title": "Backup error",
|
||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||
"backup_controller_page_albums": "Álbumes de copia de seguridad",
|
||||
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||
"backup_controller_page_background_charging": "Only while charging",
|
||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||
"backup_controller_page_backup": "Copia de Seguridad",
|
||||
"backup_controller_page_backup_selected": "Seleccionado:",
|
||||
"backup_controller_page_backup_sub": "Copia de seguridad de fotos y vídeos",
|
||||
"backup_controller_page_cancel": "Cancelar",
|
||||
"backup_controller_page_created": "Created on: {}",
|
||||
"backup_controller_page_desc_backup": "Active la copia de seguridad para cargar automáticamente los nuevos activos al servidor.",
|
||||
"backup_controller_page_excluded": "Excluido:",
|
||||
"backup_controller_page_failed": "Failed ({})",
|
||||
"backup_controller_page_filename": "File name: {} [{}]",
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "Información de la Copia de Seguridad",
|
||||
"backup_controller_page_none_selected": "Ninguno seleccionado",
|
||||
"backup_controller_page_remainder": "Remanente",
|
||||
@@ -38,9 +67,30 @@
|
||||
"backup_controller_page_total_sub": "Todas las fotos y vídeos únicos de los álbumes seleccionados",
|
||||
"backup_controller_page_turn_off": "Apagar la copia de seguridad",
|
||||
"backup_controller_page_turn_on": "Activar la copia de seguridad",
|
||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||
"backup_err_only_album": "No se puede eliminar el único álbum",
|
||||
"backup_info_card_assets": "activos",
|
||||
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Clear cache",
|
||||
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||
"cache_settings_statistics_album": "Library thumbnails",
|
||||
"cache_settings_statistics_assets": "{} assets ({})",
|
||||
"cache_settings_statistics_full": "Full images",
|
||||
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||
"cache_settings_statistics_thumbnail": "Thumbnails",
|
||||
"cache_settings_statistics_title": "Cache usage",
|
||||
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||
"cache_settings_title": "Caching Settings",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Eliminar",
|
||||
"control_bottom_app_bar_share": "Share",
|
||||
"create_album_page_untitled": "Untitled",
|
||||
"create_shared_album_page_create": "Create",
|
||||
"create_shared_album_page_share": "Compartir",
|
||||
"create_shared_album_page_share_add_assets": "AÑADIR ACTIVOS",
|
||||
"create_shared_album_page_share_select_photos": "Seleccionar Fotos",
|
||||
@@ -54,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "Añadir Descripción...",
|
||||
"exif_bottom_sheet_details": "DETALLES",
|
||||
"exif_bottom_sheet_location": "LOCALZACIÓN",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_new_album": "New album",
|
||||
"login_form_button_text": "Iniciar Sesión",
|
||||
"login_form_email_hint": "tucorreo@correo.com",
|
||||
"login_form_endpoint_hint": "http://tu-ip-de-servidor:puerto/api",
|
||||
@@ -62,33 +120,71 @@
|
||||
"login_form_err_invalid_email": "Correo electrónico no válido",
|
||||
"login_form_err_leading_whitespace": "Espacio en blanco inicial",
|
||||
"login_form_err_trailing_whitespace": "Espacio en blanco al final",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Error logging you in, check server URL, email and password",
|
||||
"login_form_label_email": "Correo",
|
||||
"login_form_label_password": "Contraseña",
|
||||
"login_form_password_hint": "contraseña",
|
||||
"login_form_save_login": "Mantener la sesión iniciada",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "El Cliente y el Servidor están actualizados",
|
||||
"profile_drawer_settings": "Settings",
|
||||
"profile_drawer_sign_out": "Cerrar Sesión",
|
||||
"search_bar_hint": "Busca tus fotos",
|
||||
"search_page_no_objects": "No Objects Info Available",
|
||||
"search_page_no_places": "No hay información de lugares disponibles",
|
||||
"search_page_places": "Lugares",
|
||||
"search_page_things": "Cosas",
|
||||
"search_result_page_new_search_hint": "Nueva Busqueda",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Sugerencias",
|
||||
"select_user_for_sharing_page_err_album": "Fallo al crear el álbum",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||
"setting_notifications_notify_hours": "{} hours",
|
||||
"setting_notifications_notify_immediately": "immediately",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "never",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Settings",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Añadir",
|
||||
"share_add_photos": "Añadir fotos",
|
||||
"share_add_title": "Añadir un título",
|
||||
"share_create_album": "Crear álbum",
|
||||
"share_dialog_preparing": "Preparing...",
|
||||
"share_invite": "Invitar al álbum",
|
||||
"sharing_page_album": "Álbumes compartidos",
|
||||
"sharing_page_description": "Crea álbumes compartidos para compartir fotos y vídeos con las personas de tu red.",
|
||||
"sharing_page_empty_list": "LISTA VACIA",
|
||||
"sharing_silver_appbar_create_shared_album": "Crear un álbum compartido",
|
||||
"sharing_silver_appbar_share_partner": "Compartir con el compañero",
|
||||
"tab_controller_nav_library": "Library",
|
||||
"tab_controller_nav_photos": "Fotos",
|
||||
"tab_controller_nav_search": "Buscar",
|
||||
"tab_controller_nav_sharing": "Compartiendo",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Dark mode",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||
"theme_setting_theme_title": "Theme",
|
||||
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
||||
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
||||
"version_announcement_overlay_ack": "Reconocer",
|
||||
"version_announcement_overlay_release_notes": "notas de versión",
|
||||
"version_announcement_overlay_text_1": "Hola amigo, hay una nueva versión de",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "JÄTETTY POIS",
|
||||
"album_info_card_backup_album_included": "SISÄLLYTETTY",
|
||||
"album_thumbnail_card_item": "1 kohde",
|
||||
"album_thumbnail_card_items": "{} kohdetta",
|
||||
"album_thumbnail_card_shared": "Jaettu",
|
||||
"album_viewer_appbar_share_delete": "Poista albumi",
|
||||
"album_viewer_appbar_share_err_delete": "Albumin poistaminen epäonnistui",
|
||||
"album_viewer_appbar_share_err_leave": "Albumista poistuminen epäonnistui",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Poistu albumista",
|
||||
"album_viewer_appbar_share_remove": "Poista albumista",
|
||||
"album_viewer_page_share_add_users": "Lisää käyttäjiä",
|
||||
"asset_list_settings_subtitle": "Kuvaruudukon asettelu",
|
||||
"asset_list_settings_title": "Kuvaruudukko",
|
||||
"backup_album_selection_page_albums_device": "Laitteen albumit ({})",
|
||||
"backup_album_selection_page_albums_tap": "Napauta sisällyttääksesi, kaksoisnapauta jättääksesi pois",
|
||||
"backup_album_selection_page_assets_scatter": "Kohteet voivat olla hajaantuneina useisiin albumeihin. Albumeita voidaan sisällyttää varmuuskopiointiin tai jättää siitä pois.",
|
||||
@@ -16,7 +21,27 @@
|
||||
"backup_album_selection_page_selection_info": "Valintatiedot",
|
||||
"backup_album_selection_page_total_assets": "Uniikkeja kohteita yhteensä",
|
||||
"backup_all": "Kaikki",
|
||||
"backup_background_service_backup_failed_message": "Kohteiden varmuuskopiointi epäonnistui. Yritetään uudelleen...",
|
||||
"backup_background_service_connection_failed_message": "Palvelimeen ei saatu yhteyttä. Yritetään uudelleen...",
|
||||
"backup_background_service_current_upload_notification": "Lähetetään {}",
|
||||
"backup_background_service_default_notification": "Tarkistetaan uusia kohteita...",
|
||||
"backup_background_service_error_title": "Virhe varmuuskopioinnissa",
|
||||
"backup_background_service_in_progress_notification": "Varmuuskopioidaan kohteita...",
|
||||
"backup_background_service_upload_failure_notification": "Lähetys palvelimelle epäonnistui {}",
|
||||
"backup_controller_page_albums": "Varmuuskopioi albumit",
|
||||
"backup_controller_page_background_battery_info_link": "Näytä minulle miten",
|
||||
"backup_controller_page_background_battery_info_message": "Kytke pois päältä kaikki Immichin taustatyöskentelyyn liittyvät akun optimoinnit, jotta varmistat taustavarmuuskopioinnin parhaan mahdollisen toiminnan.\n\nKoska tämä on laitekohtaista, tarkista tarvittavat toimet laitevalmistajan ohjeista.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Akun optimointi",
|
||||
"backup_controller_page_background_charging": "Vain laitteen ollessa kytkettynä laturiin",
|
||||
"backup_controller_page_background_configure_error": "Taustapalvelun asettaminen epäonnistui",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Kytke taustapalvelu päälle varmuuskopioidaksesi uudet kohteet automaattisesti, ilman sovelluksen avaamista",
|
||||
"backup_controller_page_background_is_off": "Automaattinen varmuuskopiointi taustalla on pois päältä",
|
||||
"backup_controller_page_background_is_on": "Automaattinen varmuuskopiointi taustalla on päällä",
|
||||
"backup_controller_page_background_turn_off": "Kytke taustapalvelu pois päältä",
|
||||
"backup_controller_page_background_turn_on": "Kytke taustapalvelu päälle",
|
||||
"backup_controller_page_background_wifi": "Vain WiFi-verkossa",
|
||||
"backup_controller_page_backup": "Varmuuskopioitu",
|
||||
"backup_controller_page_backup_selected": "Valittu:",
|
||||
"backup_controller_page_backup_sub": "Varmuuskopioidut kuvat ja videot",
|
||||
@@ -45,10 +70,33 @@
|
||||
"backup_controller_page_uploading_file_info": "Tiedostojen lähetystiedot",
|
||||
"backup_err_only_album": "Vähintään yhden albumin tulee olla valittuna",
|
||||
"backup_info_card_assets": "kohdetta",
|
||||
"cache_settings_album_thumbnails": "Kirjastosivun esikatselukuvat ({} kohdetta)",
|
||||
"cache_settings_clear_cache_button": "Tyhjennä välimuisti",
|
||||
"cache_settings_clear_cache_button_title": "Tyhjennä sovelluksen välimuisti. Tämä vaikuttaa merkittävästi sovelluksen suorituskykyyn, kunnes välimuisti on rakennettu uudelleen.",
|
||||
"cache_settings_image_cache_size": "Kuvien välimuistin koko ({} kohdetta)",
|
||||
"cache_settings_statistics_album": "Kirjaston esikatselukuvat",
|
||||
"cache_settings_statistics_assets": "{} kohdetta ({})",
|
||||
"cache_settings_statistics_full": "Täysikokoiset kuvat",
|
||||
"cache_settings_statistics_shared": "Jaettujen albumien esikatselukuvat",
|
||||
"cache_settings_statistics_thumbnail": "Esikatselukuvat",
|
||||
"cache_settings_statistics_title": "Välimuistin käyttö",
|
||||
"cache_settings_subtitle": "Hallitse Immich-mobiilisovelluksen välimuistin käyttöä",
|
||||
"cache_settings_thumbnail_size": "Esikatselukuvien välimuistin koko ({} kohdetta)",
|
||||
"cache_settings_title": "Välimuistin asetukset",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Poista",
|
||||
"control_bottom_app_bar_share": "Jaa",
|
||||
"create_album_page_untitled": "Nimetön",
|
||||
"create_shared_album_page_create": "Luo",
|
||||
"create_shared_album_page_share": "Jaa",
|
||||
"create_shared_album_page_share_add_assets": "LISÄÄ KOHTEITA",
|
||||
"create_shared_album_page_share_select_photos": "Valitse kuvat",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
"date_format": "E, LLL d, y • h:mm a",
|
||||
"delete_dialog_alert": "Nämä kohteet poistetaan pysyvästi Immich:stä ja laitteeltasi",
|
||||
"delete_dialog_cancel": "Peruuta",
|
||||
"delete_dialog_ok": "Poista",
|
||||
@@ -56,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "Lisää kuvaus…",
|
||||
"exif_bottom_sheet_details": "TIEDOT",
|
||||
"exif_bottom_sheet_location": "SIJAINTI",
|
||||
"experimental_settings_new_asset_list_subtitle": "Työn alla",
|
||||
"experimental_settings_new_asset_list_title": "Ota käyttöön kokeellinen kuvaruudukko",
|
||||
"experimental_settings_subtitle": "Käyttö omalla vastuulla!",
|
||||
"experimental_settings_title": "Kokeellinen",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albumit",
|
||||
"library_page_new_album": "Uusi albumi",
|
||||
"login_form_button_text": "Kirjaudu",
|
||||
"login_form_email_hint": "sahkopostisi@esimerkki.fi",
|
||||
"login_form_endpoint_hint": "http://palvelimesi-osoite:portti/api",
|
||||
@@ -64,12 +120,17 @@
|
||||
"login_form_err_invalid_email": "Virheellinen sähköpostiosoite",
|
||||
"login_form_err_leading_whitespace": "Alussa välilyönti",
|
||||
"login_form_err_trailing_whitespace": "Lopussa välilyönti",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Virhe kirjautumisessa. Tarkista palvelimen URL, sähköpostiosoite ja salasana.",
|
||||
"login_form_label_email": "Sähköposti",
|
||||
"login_form_label_password": "Salasana",
|
||||
"login_form_password_hint": "salasana",
|
||||
"login_form_save_login": "Pysy kirjautuneena",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Asiakassovellus ja palvelin ovat ajan tasalla",
|
||||
"profile_drawer_settings": "Asetukset",
|
||||
"profile_drawer_sign_out": "Kirjaudu ulos",
|
||||
"search_bar_hint": "Etsi kuvia",
|
||||
"search_page_no_objects": "Objektitietoja ei ole saatavilla",
|
||||
@@ -80,19 +141,50 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Ehdotukset",
|
||||
"select_user_for_sharing_page_err_album": "Albumin luonti epäonnistui",
|
||||
"select_user_for_sharing_page_share_suggestions": "Ehdotukset",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Ilmoita taustavarmuuskopioinnin epäonnistumisista: {}",
|
||||
"setting_notifications_notify_hours": "{} tunnin välein",
|
||||
"setting_notifications_notify_immediately": "heti",
|
||||
"setting_notifications_notify_minutes": "{} minuutin välein",
|
||||
"setting_notifications_notify_never": "ei koskaan",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Yksityiskohtainen tieto palvelimelle lähettämisen edistymisestä kohteittain",
|
||||
"setting_notifications_single_progress_title": "Näytä taustavarmuuskopioinnin eidstminen",
|
||||
"setting_notifications_subtitle": "Ilmoitusasetusten määrittely",
|
||||
"setting_notifications_title": "Ilmoitukset",
|
||||
"setting_notifications_total_progress_subtitle": "Lähetyksen yleinen edistyminen (kohteita lähetetty/yhteensä)",
|
||||
"setting_notifications_total_progress_title": "Näytä taustavarmuuskopioinnin kokonaisedistyminen",
|
||||
"setting_pages_app_bar_settings": "Asetukset",
|
||||
"settings_require_restart": "Käynnistä Immich uudelleen ottaaksesti tämän asetuksen käyttöön",
|
||||
"share_add": "Lisää",
|
||||
"share_add_photos": "Lisää kuvia",
|
||||
"share_add_title": "Lisää nimi",
|
||||
"share_create_album": "Luo albumi",
|
||||
"share_dialog_preparing": "Valmistellaan...",
|
||||
"share_invite": "Kutsu albumiin",
|
||||
"sharing_page_album": "Jaetut albumit",
|
||||
"sharing_page_description": "Luo jaettuja albumeja jakaaksesi kuvia ja videoita läheisillesi.",
|
||||
"sharing_page_empty_list": "TYHJÄ LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Luo jaettu albumi",
|
||||
"sharing_silver_appbar_share_partner": "Jaa kumppanille",
|
||||
"tab_controller_nav_library": "Kirjasto",
|
||||
"tab_controller_nav_photos": "Kuvat",
|
||||
"tab_controller_nav_search": "Haku",
|
||||
"tab_controller_nav_sharing": "Jakaminen",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Näytä tallennustilainidikaattori kohteiden kuvakkeissa",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Kohteiden määrä rivillä ({})",
|
||||
"theme_setting_dark_mode_switch": "Tumma teema",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Säädä kuvien katselun laatua",
|
||||
"theme_setting_image_viewer_quality_title": "Kuvien katseluohjelman laatu",
|
||||
"theme_setting_system_theme_switch": "Automaattinen (seuraa järjestelmän asetusta)",
|
||||
"theme_setting_theme_subtitle": "Valitse sovelluksen teema-asetukset",
|
||||
"theme_setting_theme_title": "Teema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Kolmivaiheinen lataaminen saattaa parantaa latauksen suorituskykyä, mutta lisää kaistankäyttöä huomattavasti.",
|
||||
"theme_setting_three_stage_loading_title": "Ota kolmivaiheinen lataus käyttöön",
|
||||
"version_announcement_overlay_ack": "Tiedostan",
|
||||
"version_announcement_overlay_release_notes": "julkaisutiedoissa",
|
||||
"version_announcement_overlay_text_1": "Hei, kaveri! Uusi palvelinversio on saatavilla sovelluksesta",
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Quitter l'album",
|
||||
"album_viewer_appbar_share_remove": "Retirer de l'album",
|
||||
"album_viewer_page_share_add_users": "Ajouter des utilisateurs",
|
||||
"asset_list_settings_subtitle": "Paramètres de disposition de la grille de photos",
|
||||
"asset_list_settings_title": "Grille de photos",
|
||||
"backup_album_selection_page_albums_device": "Albums sur l'appareil ({})",
|
||||
"backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure",
|
||||
"backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.",
|
||||
@@ -19,7 +21,27 @@
|
||||
"backup_album_selection_page_selection_info": "Informations sur la sélection",
|
||||
"backup_album_selection_page_total_assets": "Total des éléments uniques",
|
||||
"backup_all": "Tout",
|
||||
"backup_background_service_backup_failed_message": "Échec de la sauvegarde des éléments. Nouvelle tentative...",
|
||||
"backup_background_service_connection_failed_message": "Impossible de se connecter au serveur. Nouvelle tentative...",
|
||||
"backup_background_service_current_upload_notification": "Transfert {}",
|
||||
"backup_background_service_default_notification": "Recherche de nouveaux éléments...",
|
||||
"backup_background_service_error_title": "Erreur de sauvegarde",
|
||||
"backup_background_service_in_progress_notification": "Sauvegarde de vos éléments...",
|
||||
"backup_background_service_upload_failure_notification": "Impossible de transférer {}",
|
||||
"backup_controller_page_albums": "Sauvegarder les albums",
|
||||
"backup_controller_page_background_battery_info_link": "Montrez-moi comment",
|
||||
"backup_controller_page_background_battery_info_message": "Pour une expérience optimale de la sauvegarde en arrière-plan, veuillez désactiver toute optimisation de la batterie limitant l'activité en arrière-plan pour Immich.\n\nÉtant donné que cela est spécifique à chaque appareil, veuillez consulter les informations requises pour le fabricant de votre appareil.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Optimisation de la batterie",
|
||||
"backup_controller_page_background_charging": "Seulement pendant la charge",
|
||||
"backup_controller_page_background_configure_error": "Échec de la configuration du service d'arrière-plan",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Activez le service d'arrière-plan pour sauvegarder automatiquement tous les nouveaux éléments sans avoir à ouvrir l'application.",
|
||||
"backup_controller_page_background_is_off": "La sauvegarde automatique en arrière-plan est désactivée",
|
||||
"backup_controller_page_background_is_on": "La sauvegarde automatique en arrière-plan est activée",
|
||||
"backup_controller_page_background_turn_off": "Désactiver le service d'arrière-plan",
|
||||
"backup_controller_page_background_turn_on": "Activer le service d'arrière-plan",
|
||||
"backup_controller_page_background_wifi": "Uniquement en WiFi",
|
||||
"backup_controller_page_backup": "Sauvegardé",
|
||||
"backup_controller_page_backup_selected": "Sélectionné : ",
|
||||
"backup_controller_page_backup_sub": "Photos et vidéos sauvegardées",
|
||||
@@ -45,9 +67,26 @@
|
||||
"backup_controller_page_total_sub": "Toutes les photos et vidéos uniques des albums sélectionnés",
|
||||
"backup_controller_page_turn_off": "Désactiver la sauvegarde",
|
||||
"backup_controller_page_turn_on": "Activer la sauvegarde",
|
||||
"backup_controller_page_uploading_file_info": "Envoi d'informations sur le fichier",
|
||||
"backup_controller_page_uploading_file_info": "Transfert des informations du fichier",
|
||||
"backup_err_only_album": "Impossible de retirer le seul album",
|
||||
"backup_info_card_assets": "éléments",
|
||||
"cache_settings_album_thumbnails": "Miniatures de la page bibliothèque ({} éléments)",
|
||||
"cache_settings_clear_cache_button": "Effacer le cache",
|
||||
"cache_settings_clear_cache_button_title": "Efface le cache de l'application. Cela aura un impact significatif sur les performances de l'application jusqu'à ce que le cache soit reconstruit.",
|
||||
"cache_settings_image_cache_size": "Taille du cache des images ({} éléments)",
|
||||
"cache_settings_statistics_album": "Miniatures de la bibliothèque",
|
||||
"cache_settings_statistics_assets": "{} éléments ({})",
|
||||
"cache_settings_statistics_full": "Images complètes",
|
||||
"cache_settings_statistics_shared": "Miniatures d'albums partagés",
|
||||
"cache_settings_statistics_thumbnail": "Miniatures",
|
||||
"cache_settings_statistics_title": "Utilisation du cache",
|
||||
"cache_settings_subtitle": "Contrôler le comportement de mise en cache de l'application mobile Immich",
|
||||
"cache_settings_thumbnail_size": "Taille du cache des miniatures ({} éléments)",
|
||||
"cache_settings_title": "Paramètres de mise en cache",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Supprimer",
|
||||
"control_bottom_app_bar_share": "Partager",
|
||||
"create_album_page_untitled": "Sans titre",
|
||||
@@ -65,6 +104,12 @@
|
||||
"exif_bottom_sheet_description": "Ajouter une description...",
|
||||
"exif_bottom_sheet_details": "DÉTAILS",
|
||||
"exif_bottom_sheet_location": "LOCALISATION",
|
||||
"experimental_settings_new_asset_list_subtitle": "En cours de développement",
|
||||
"experimental_settings_new_asset_list_title": "Activez la grille de photos expérimentale",
|
||||
"experimental_settings_subtitle": "Utilisez à vos dépends !",
|
||||
"experimental_settings_title": "Expérimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albums",
|
||||
"library_page_new_album": "Nouvel album",
|
||||
"login_form_button_text": "Connexion",
|
||||
@@ -75,12 +120,15 @@
|
||||
"login_form_err_invalid_email": "Email invalide",
|
||||
"login_form_err_leading_whitespace": "Espace en début de ligne",
|
||||
"login_form_err_trailing_whitespace": "Espace de fin de ligne",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Erreur de connexion, vérifiez l'url du serveur, l'email et le mot de passe",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Mot de passe",
|
||||
"login_form_password_hint": "mot de passe",
|
||||
"login_form_save_login": "Rester connecté",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour",
|
||||
"profile_drawer_settings": "Paramètres",
|
||||
"profile_drawer_sign_out": "Se déconnecter",
|
||||
@@ -93,6 +141,25 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||
"select_user_for_sharing_page_err_album": "Échec de la création de l'album",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifier les échecs de la sauvegarde en arrière-plan : {}",
|
||||
"setting_notifications_notify_hours": "{} heures",
|
||||
"setting_notifications_notify_immediately": "immédiatement",
|
||||
"setting_notifications_notify_minutes": "{} minutes",
|
||||
"setting_notifications_notify_never": "jamais",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Informations détaillées sur la progression du transfert par élément",
|
||||
"setting_notifications_single_progress_title": "Afficher la progression du détail de la sauvegarde en arrière-plan",
|
||||
"setting_notifications_subtitle": "Ajustez vos préférences de notification",
|
||||
"setting_notifications_title": "Notifications",
|
||||
"setting_notifications_total_progress_subtitle": "Progrès global du transfert (effectué/total des éléments)",
|
||||
"setting_notifications_total_progress_title": "Afficher la progression totale de la sauvegarde en arrière-plan",
|
||||
"setting_pages_app_bar_settings": "Paramètres",
|
||||
"settings_require_restart": "Veuillez redémarrer Immich pour appliquer ce paramètre",
|
||||
"share_add": "Ajouter",
|
||||
"share_add_photos": "Ajouter des photos",
|
||||
"share_add_title": "Ajouter un titre",
|
||||
@@ -108,6 +175,16 @@
|
||||
"tab_controller_nav_photos": "Photos",
|
||||
"tab_controller_nav_search": "Recherche",
|
||||
"tab_controller_nav_sharing": "Partage",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Afficher l'indicateur de stockage sur les tuiles des éléments",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Nombre d'éléments par ligne ({})",
|
||||
"theme_setting_dark_mode_switch": "Mode sombre",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Ajustez la qualité de la visionneuse d'images détaillées",
|
||||
"theme_setting_image_viewer_quality_title": "Qualité de la visualisation des images",
|
||||
"theme_setting_system_theme_switch": "Automatique (suivre les paramètres du système)",
|
||||
"theme_setting_theme_subtitle": "Choisissez le thème de l'application",
|
||||
"theme_setting_theme_title": "Thème",
|
||||
"theme_setting_three_stage_loading_subtitle": "Le chargement en trois étapes peut améliorer les performances de chargement, mais entraîne une augmentation significative de la charge du réseau.",
|
||||
"theme_setting_three_stage_loading_title": "Activer le chargement en trois étapes",
|
||||
"version_announcement_overlay_ack": "Confirmer",
|
||||
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
||||
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
"album_thumbnail_card_items": "{} elementi",
|
||||
"album_thumbnail_card_shared": "Condiviso",
|
||||
"album_viewer_appbar_share_delete": "Elimina album ",
|
||||
"album_viewer_appbar_share_err_delete": "Errore nel cancellare l'album ",
|
||||
"album_viewer_appbar_share_err_leave": "Errore nel lasciare l'album ",
|
||||
"album_viewer_appbar_share_err_delete": "Impossibile cancellare l'album ",
|
||||
"album_viewer_appbar_share_err_leave": "Impossibile lasciare l'album ",
|
||||
"album_viewer_appbar_share_err_remove": "Ci sono problemi nel rimuovere oggetti dall'album ",
|
||||
"album_viewer_appbar_share_err_title": "Errore nel cambiare il titolo dell'album ",
|
||||
"album_viewer_appbar_share_err_title": "Impossibile cambiare il titolo dell'album ",
|
||||
"album_viewer_appbar_share_leave": "Lascia album",
|
||||
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
||||
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
||||
@@ -21,22 +21,23 @@
|
||||
"backup_album_selection_page_selection_info": "Informazioni sulla selezione ",
|
||||
"backup_album_selection_page_total_assets": "Numero totale di oggetti unici",
|
||||
"backup_all": "Tutti",
|
||||
"backup_background_service_backup_failed_message": "Impossibile caricare contenuti. Nuovo tentativo…",
|
||||
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Nuovo tentativo…",
|
||||
"backup_background_service_backup_failed_message": "Impossibile caricare i contenuti. Riprovo…",
|
||||
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Riprovo…",
|
||||
"backup_background_service_current_upload_notification": "Caricamento {}",
|
||||
"backup_background_service_default_notification": "Verifica di nuovi contenuti…",
|
||||
"backup_background_service_default_notification": "Ricerca di nuovi contenuti…",
|
||||
"backup_background_service_error_title": "Errore di Backup",
|
||||
"backup_background_service_in_progress_notification": "Backing dei tuoi contenuti…",
|
||||
"backup_background_service_upload_failure_notification": "Impossibile caricare {}",
|
||||
"backup_controller_page_albums": "Backup Album",
|
||||
"backup_controller_page_background_battery_info_link": "Mostrami come",
|
||||
"backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimisazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.",
|
||||
"backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimizzazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Ottimizzazioni batteria",
|
||||
"backup_controller_page_background_charging": "Solo durante la ricarica",
|
||||
"backup_controller_page_background_configure_error": "Impossibile configurare i servizi in background",
|
||||
"backup_controller_page_background_description": "Abilita i servizi in background per sincronizzare tutti i nuovi contenuti senza la necessità di aprire l'app",
|
||||
"backup_controller_page_background_is_off": "Backup automatico spento",
|
||||
"backup_controller_page_background_delay": "Ritarda il backup di nuovi elementi: {}",
|
||||
"backup_controller_page_background_description": "Abilita i servizi in background per fare il backup di tutti i nuovi contenuti senza la necessità di aprire l'app",
|
||||
"backup_controller_page_background_is_off": "Backup automatico disattivato",
|
||||
"backup_controller_page_background_is_on": "Backup automatico attivo",
|
||||
"backup_controller_page_background_turn_off": "Disabilita servizi in background",
|
||||
"backup_controller_page_background_turn_on": "Abilita servizi in background",
|
||||
@@ -66,12 +67,12 @@
|
||||
"backup_controller_page_total_sub": "Tutte le foto e i video unici caricati dagli album selezionati ",
|
||||
"backup_controller_page_turn_off": "Disattiva backup",
|
||||
"backup_controller_page_turn_on": "Attiva backup ",
|
||||
"backup_controller_page_uploading_file_info": "Info sul file caricato",
|
||||
"backup_controller_page_uploading_file_info": "Caricando informazioni sul file",
|
||||
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
||||
"backup_info_card_assets": "oggetti ",
|
||||
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
|
||||
"cache_settings_clear_cache_button": "Cancella cache",
|
||||
"cache_settings_clear_cache_button_title": "Cancella cache app. Questo impatterà sulle prestazioni applicative fino a quando la cache non sarà rigenerata.",
|
||||
"cache_settings_clear_cache_button_title": "Cancella la cache dell'app. Questo impatterà significativamente le prestazioni dell''app fino a quando la cache non sarà rigenerata.",
|
||||
"cache_settings_image_cache_size": "Dimensione cache foto ({} assets)",
|
||||
"cache_settings_statistics_album": "Anteprime librerie",
|
||||
"cache_settings_statistics_assets": "{} contenuti ({})",
|
||||
@@ -79,9 +80,13 @@
|
||||
"cache_settings_statistics_shared": "Anteprime album condivisi",
|
||||
"cache_settings_statistics_thumbnail": "Anteprime",
|
||||
"cache_settings_statistics_title": "Uso della cache",
|
||||
"cache_settings_subtitle": "Controlla il comportamento della cache",
|
||||
"cache_settings_subtitle": "Controlla il comportamento della cache dell'applicazione mobile immich",
|
||||
"cache_settings_thumbnail_size": "Dimensione cache anteprime ({} assets)",
|
||||
"cache_settings_title": "Impostazioni della Cache",
|
||||
"control_bottom_app_bar_add_to_album": "Aggiungi all'album",
|
||||
"control_bottom_app_bar_album_info": "{} elementi",
|
||||
"control_bottom_app_bar_album_info_shared": "{} elementi · Condivisi",
|
||||
"control_bottom_app_bar_create_new_album": "Crea nuovo album",
|
||||
"control_bottom_app_bar_delete": "Elimina",
|
||||
"control_bottom_app_bar_share": "Condividi",
|
||||
"create_album_page_untitled": "Senza titolo",
|
||||
@@ -99,22 +104,31 @@
|
||||
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
|
||||
"exif_bottom_sheet_details": "DETTAGLI",
|
||||
"exif_bottom_sheet_location": "POSIZIONE",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Attiva griglia di foto sperimentale",
|
||||
"experimental_settings_subtitle": "Usalo a tuo rischio!",
|
||||
"experimental_settings_title": "Sperimentale",
|
||||
"home_page_add_to_album_conflicts": "Aggiunti {added} elementi all'album {album}. {failed} elementi erano già presenti nell'album.",
|
||||
"home_page_add_to_album_success": "Aggiunti {added} elementi all'album {album}",
|
||||
"library_page_albums": "Album",
|
||||
"library_page_new_album": "Nuovo Album",
|
||||
"login_form_button_text": "Login",
|
||||
"login_form_email_hint": "tuaemail@email.com",
|
||||
"login_form_endpoint_hint": "http://tuo-ip-del-server:port/api",
|
||||
"login_form_endpoint_hint": "http://ip-del-tuo-server:port/api",
|
||||
"login_form_endpoint_url": "Server Endpoint URL",
|
||||
"login_form_err_http": "Per favore specificare http:// o https://",
|
||||
"login_form_err_invalid_email": "Email non valida",
|
||||
"login_form_err_leading_whitespace": "Spazio bianco all'inizio ",
|
||||
"login_form_err_trailing_whitespace": "Spazio bianco alla fine",
|
||||
"login_form_err_leading_whitespace": "Whitespace all'inizio ",
|
||||
"login_form_err_trailing_whitespace": "Whitespace alla fine",
|
||||
"login_form_failed_get_oauth_server_config": "Errore di login usando OAuth, controlla l'URL del server",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth non è disponibile su questo server",
|
||||
"login_form_failed_login": "Errore nel login, controlla URL del server e le credenziali (email e password)",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Password",
|
||||
"login_form_password_hint": "password ",
|
||||
"login_form_save_login": "Rimani connesso ",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Client e server sono aggiornati",
|
||||
"profile_drawer_settings": "Impostazioni ",
|
||||
"profile_drawer_sign_out": "Logout",
|
||||
@@ -125,24 +139,35 @@
|
||||
"search_page_things": "Oggetti",
|
||||
"search_result_page_new_search_hint": "Nuova ricerca ",
|
||||
"select_additional_user_for_sharing_page_suggestions": "Suggerimenti ",
|
||||
"select_user_for_sharing_page_err_album": "Errore nel creare l'album ",
|
||||
"select_user_for_sharing_page_err_album": "Impossibile nel creare l'album ",
|
||||
"select_user_for_sharing_page_share_suggestions": "Suggerimenti",
|
||||
"setting_image_viewer_help": "Il visualizzatore dettagliato carica una piccola thumbnail per prima, per poi caricare un immagine di media grandezza (se abilitato). Ed infine carica l'originale (se abilitato).",
|
||||
"setting_image_viewer_original_subtitle": "Abilita per caricare l'immagine originale a risoluzione massima (grande!). Disabilita per ridurre l'utilizzo di banda (sia sul network che nella cache del dispositivo).",
|
||||
"setting_image_viewer_original_title": "Carica l'immagine originale",
|
||||
"setting_image_viewer_preview_subtitle": "Abilita per caricare un'immagine a risoluzione media.\nDisabilita per caricare direttamente l'immagine originale o usare la thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Carica immagine di preview",
|
||||
"setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {}",
|
||||
"setting_notifications_notify_hours": "{} Ore",
|
||||
"setting_notifications_notify_immediately": "Immediatamente",
|
||||
"setting_notifications_notify_minutes": "{} Minuti",
|
||||
"setting_notifications_notify_never": "Mai",
|
||||
"setting_notifications_notify_hours": "{} ore",
|
||||
"setting_notifications_notify_immediately": "immediatamente",
|
||||
"setting_notifications_notify_minutes": "{} minuti",
|
||||
"setting_notifications_notify_never": "mai",
|
||||
"setting_notifications_notify_seconds": "{} secondi",
|
||||
"setting_notifications_single_progress_subtitle": "Informazioni dettagliate sul caricamento dell'immagine",
|
||||
"setting_notifications_single_progress_title": "Mostra dettagli del processo di backup in background",
|
||||
"setting_notifications_subtitle": "Cambia le impostazioni di notifica",
|
||||
"setting_notifications_title": "Notifiche",
|
||||
"setting_notifications_total_progress_subtitle": "Progresso di caricamento generale (eseguiti/totale)",
|
||||
"setting_notifications_total_progress_title": "Mostra il progresso di backup totale in background",
|
||||
"setting_pages_app_bar_settings": "Impostazioni",
|
||||
"settings_require_restart": "Si prega di riavviare Immich perché vengano applicate le impostazioni",
|
||||
"share_add": "Aggiungi",
|
||||
"share_add_photos": "Aggiungi foto",
|
||||
"share_add_title": "Aggiungi un titolo ",
|
||||
"share_create_album": "Crea album",
|
||||
"share_dialog_preparing": "Preparo…",
|
||||
"share_invite": "Invitare nell'album ",
|
||||
"share_invite": "Invita nell'album ",
|
||||
"sharing_page_album": "Album condivisi",
|
||||
"sharing_page_description": "Crea un album condiviso per condividere foto e video con persone nel tuo network",
|
||||
"sharing_page_description": "Crea un album condiviso per condividere foto e video con persone sul tuo network",
|
||||
"sharing_page_empty_list": "LISTA VUOTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Crea album condiviso",
|
||||
"sharing_silver_appbar_share_partner": "Condividi con il partner",
|
||||
@@ -155,15 +180,15 @@
|
||||
"theme_setting_dark_mode_switch": "Dark mode",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Cambia la qualità del dettaglio dell'immagine",
|
||||
"theme_setting_image_viewer_quality_title": "Qualità immagine",
|
||||
"theme_setting_system_theme_switch": "Automatico (Vai alle impostazioni di sistema)",
|
||||
"theme_setting_theme_subtitle": "Scegli un'impostazione per il tema",
|
||||
"theme_setting_system_theme_switch": "Automatico (Segue le impostazioni di sistema)",
|
||||
"theme_setting_theme_subtitle": "Scegli un'impostazione per il tema dell'app",
|
||||
"theme_setting_theme_title": "Tema",
|
||||
"theme_setting_three_stage_loading_subtitle": "Il caricamento in 3 stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
||||
"theme_setting_three_stage_loading_subtitle": "Il caricamento a tre stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
||||
"theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage",
|
||||
"version_announcement_overlay_ack": "Riconosci ",
|
||||
"version_announcement_overlay_ack": "Presa visione",
|
||||
"version_announcement_overlay_release_notes": "note di rilascio ",
|
||||
"version_announcement_overlay_text_1": "Ciao amico, c'è una nuova versione di",
|
||||
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per controllare il",
|
||||
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore nella configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico delle immagini docker.",
|
||||
"version_announcement_overlay_title": "Nuova versione del server disponibile! \uD83C\uDF89"
|
||||
"version_announcement_overlay_text_1": "Ciao, c'è una nuova versione di",
|
||||
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per visitare il",
|
||||
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore di configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico dell'applicativo",
|
||||
"version_announcement_overlay_title": "Nuova versione del server disponibile \uD83C\uDF89"
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "除外",
|
||||
"album_info_card_backup_album_included": "選択",
|
||||
"album_info_card_backup_album_excluded": "除外中",
|
||||
"album_info_card_backup_album_included": "選択中",
|
||||
"album_thumbnail_card_item": "項目数: 1",
|
||||
"album_thumbnail_card_items": "項目数: {}",
|
||||
"album_thumbnail_card_shared": "共有済み",
|
||||
"album_viewer_appbar_share_delete": "アルバムを削除",
|
||||
"album_viewer_appbar_share_err_delete": "削除に失敗...",
|
||||
"album_viewer_appbar_share_err_leave": "退会に失敗...",
|
||||
@@ -9,17 +12,39 @@
|
||||
"album_viewer_appbar_share_leave": "アルバムから退会",
|
||||
"album_viewer_appbar_share_remove": "アルバムから除外",
|
||||
"album_viewer_page_share_add_users": "ユーザーを追加",
|
||||
"asset_list_settings_subtitle": "グリッドに関する設定",
|
||||
"asset_list_settings_title": "グリッド",
|
||||
"backup_album_selection_page_albums_device": "端末上のアルバム数は {} だよ",
|
||||
"backup_album_selection_page_albums_tap": "タップで選択、ダブルタップで除外だよ",
|
||||
"backup_album_selection_page_assets_scatter": "写真がいろんなアルバムに登録されてる事があるから、アルバムを含めたり除外したりしてどの写真を保存するか選択できるよ。",
|
||||
"backup_album_selection_page_assets_scatter": "同じ写真がいろんなアルバムに登録されてる事があるから、アルバムを含めたり除外したりしてどの写真を保存するか選択できるよ。",
|
||||
"backup_album_selection_page_select_albums": "アルバムを選択",
|
||||
"backup_album_selection_page_selection_info": "選択、又は除外されてるアルバム",
|
||||
"backup_album_selection_page_selection_info": "選択、又は除外されているアルバム",
|
||||
"backup_album_selection_page_total_assets": "選択されたアルバムの写真と動画の数",
|
||||
"backup_all": "全て",
|
||||
"backup_background_service_backup_failed_message": "アップロードに失敗しました。リトライ中",
|
||||
"backup_background_service_connection_failed_message": "サーバーに接続できません。リトライ中",
|
||||
"backup_background_service_current_upload_notification": " {} をアップロード中",
|
||||
"backup_background_service_default_notification": "新しい写真をチェックしています",
|
||||
"backup_background_service_error_title": "バックアップエラー",
|
||||
"backup_background_service_in_progress_notification": "バックアップ中",
|
||||
"backup_background_service_upload_failure_notification": "{} のアップロードに失敗",
|
||||
"backup_controller_page_albums": "アルバム",
|
||||
"backup_controller_page_backup": "バックアップ",
|
||||
"backup_controller_page_backup_selected": "選択されてる:",
|
||||
"backup_controller_page_backup_sub": "バックアップされた写真と動画の数だよ",
|
||||
"backup_controller_page_background_battery_info_link": "方法を見る",
|
||||
"backup_controller_page_background_battery_info_message": "バックグラウンドバックアップが正常に動作するためにImmichに適用されてるバッテリーの最適化と自動調整をオフにしてね。\n\n端末によって方法が変わるから各々調べてね",
|
||||
"backup_controller_page_background_battery_info_ok": "了解",
|
||||
"backup_controller_page_background_battery_info_title": "バッテリーの最適化",
|
||||
"backup_controller_page_background_charging": "充電中のみに行う",
|
||||
"backup_controller_page_background_configure_error": "バックグラウンドサービスの構築に失敗しました",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "バックグラウンドバックアップをオンにしてアプリを開かなくても自動で画像をアップロードするようにします",
|
||||
"backup_controller_page_background_is_off": "自動バックグラウンドバックアップはオフになってます",
|
||||
"backup_controller_page_background_is_on": "自動バックグラウンドバックアップはオンになってます",
|
||||
"backup_controller_page_background_turn_off": "バックグラウンドサービスをオフにする",
|
||||
"backup_controller_page_background_turn_on": "バックグラウンドサービスをオンにする",
|
||||
"backup_controller_page_background_wifi": "WiFi接続中のみに行う",
|
||||
"backup_controller_page_backup": "バックアップ済み",
|
||||
"backup_controller_page_backup_selected": "選択済み:",
|
||||
"backup_controller_page_backup_sub": "バックアップされた写真と動画の数",
|
||||
"backup_controller_page_cancel": "キャンセルするよ",
|
||||
"backup_controller_page_created": "{} に作成されたよ",
|
||||
"backup_controller_page_desc_backup": "ONにすれば自動的に新しい写真などがバックアップされるようになるよ",
|
||||
@@ -29,29 +54,49 @@
|
||||
"backup_controller_page_id": "ID: {}",
|
||||
"backup_controller_page_info": "バックアップ情報",
|
||||
"backup_controller_page_none_selected": "何も選んでないよ",
|
||||
"backup_controller_page_remainder": "リマインダー",
|
||||
"backup_controller_page_remainder_sub": "残りの写真と動画の数だよ",
|
||||
"backup_controller_page_remainder": "残り",
|
||||
"backup_controller_page_remainder_sub": "残りの写真と動画の数",
|
||||
"backup_controller_page_select": "選択",
|
||||
"backup_controller_page_server_storage": "サーバーの容量",
|
||||
"backup_controller_page_start_backup": "バックアップを開始するよ",
|
||||
"backup_controller_page_status_off": "バックアップがOFFになってるよ",
|
||||
"backup_controller_page_status_on": "バックアップがONになってるよ",
|
||||
"backup_controller_page_storage_format": "{}中、 {}を使用中だよ",
|
||||
"backup_controller_page_start_backup": "バックアップを開始",
|
||||
"backup_controller_page_status_off": "バックアップがOFFだよ",
|
||||
"backup_controller_page_status_on": "バックアップがONだよ",
|
||||
"backup_controller_page_storage_format": "使用済み: {}/{}",
|
||||
"backup_controller_page_to_backup": "バックアップされるアルバム",
|
||||
"backup_controller_page_total": "トータル",
|
||||
"backup_controller_page_total_sub": "選択されたアルバムの写真と動画の数だよ",
|
||||
"backup_controller_page_total_sub": "選択されたアルバムの写真と動画の数",
|
||||
"backup_controller_page_turn_off": "バックアップOFF",
|
||||
"backup_controller_page_turn_on": "バックアップON",
|
||||
"backup_controller_page_uploading_file_info": "アップロードされてるファイルに関する情報",
|
||||
"backup_err_only_album": "唯一のアルバムを除外する事はできないよ",
|
||||
"backup_err_only_album": "唯一のアルバムを削除する事はできないよ",
|
||||
"backup_info_card_assets": "写真と動画",
|
||||
"cache_settings_album_thumbnails": "ライブラリのサムネイル ({}枚)",
|
||||
"cache_settings_clear_cache_button": "キャッシュをクリア",
|
||||
"cache_settings_clear_cache_button_title": "キャッシュを削除するけど、キャッシュを作り直すまでアプリのパフォーマンスが著しく低下するよ",
|
||||
"cache_settings_image_cache_size": "キャッシュのサイズ ({}枚) ",
|
||||
"cache_settings_statistics_album": "ライブラリのサムネイル",
|
||||
"cache_settings_statistics_assets": "{} 枚 ({}枚中)",
|
||||
"cache_settings_statistics_full": "フル画像",
|
||||
"cache_settings_statistics_shared": "共有アルバムのサムネイル",
|
||||
"cache_settings_statistics_thumbnail": "サムネイル",
|
||||
"cache_settings_statistics_title": "キャッシュ",
|
||||
"cache_settings_subtitle": "キャッシュの動作を変更できるよ",
|
||||
"cache_settings_thumbnail_size": "サムネイルのキャッシュのサイズ ({}枚)",
|
||||
"cache_settings_title": "キャッシュの設定",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "削除",
|
||||
"control_bottom_app_bar_share": "共有",
|
||||
"create_album_page_untitled": "タイトル無し",
|
||||
"create_shared_album_page_create": "作成",
|
||||
"create_shared_album_page_share": "共有",
|
||||
"create_shared_album_page_share_add_assets": "写真や動画を追加",
|
||||
"create_shared_album_page_share_add_assets": "写真を追加",
|
||||
"create_shared_album_page_share_select_photos": "写真を選択",
|
||||
"daily_title_text_date": "E, MM月 dd日",
|
||||
"daily_title_text_date_year": "E, yyyy年 MM月 dd日",
|
||||
"date_format": "E, MM月 dd日 • hh時mm分",
|
||||
"daily_title_text_date": "MM月 DD日, EE",
|
||||
"daily_title_text_date_year": "yyyy年 MM月 DD日, EE",
|
||||
"date_format": "MM月 DD日, EE • hh時mm分",
|
||||
"delete_dialog_alert": "サーバーからも端末からも永久的に削除されるけど良いの?",
|
||||
"delete_dialog_cancel": "キャンセル",
|
||||
"delete_dialog_ok": "削除",
|
||||
@@ -59,6 +104,14 @@
|
||||
"exif_bottom_sheet_description": "概要を追加",
|
||||
"exif_bottom_sheet_details": "詳細な情報",
|
||||
"exif_bottom_sheet_location": "撮影地",
|
||||
"experimental_settings_new_asset_list_subtitle": "進行中",
|
||||
"experimental_settings_new_asset_list_title": "試験的なグリッドを有効",
|
||||
"experimental_settings_subtitle": "試験的だから自己責任でね",
|
||||
"experimental_settings_title": "試験的",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "アルバム",
|
||||
"library_page_new_album": "新しいアルバム",
|
||||
"login_form_button_text": "ログイン",
|
||||
"login_form_email_hint": "example@email.com",
|
||||
"login_form_endpoint_hint": "https://example.com:port/api",
|
||||
@@ -67,36 +120,71 @@
|
||||
"login_form_err_invalid_email": "メールアドレスが有効じゃないよ",
|
||||
"login_form_err_leading_whitespace": "最初に半角スペースが含まれてるよ",
|
||||
"login_form_err_trailing_whitespace": "最後に半角スペースが含まれてるよ",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "ログインエラー。サーバーのURL、メールアドレスとパスワードを再確認してね",
|
||||
"login_form_label_email": "メールアドレス",
|
||||
"login_form_label_password": "パスワード",
|
||||
"login_form_password_hint": "パスワード",
|
||||
"login_form_save_login": "ログインしたままにする",
|
||||
"monthly_title_text_date_format": "yyyy年 MM月",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "サーバーとクライアント、両方最新バージョンだよ",
|
||||
"profile_drawer_settings": "設定",
|
||||
"profile_drawer_sign_out": "サインアウト",
|
||||
"search_bar_hint": "写真を検索",
|
||||
"search_page_no_objects": "被写体に関するデータがないよ",
|
||||
"search_page_no_places": "場所に関するデータがないよ",
|
||||
"search_page_no_objects": "被写体に関するデータがなし",
|
||||
"search_page_no_places": "場所に関するデータがなし",
|
||||
"search_page_places": "撮影地",
|
||||
"search_page_things": "カテゴリ",
|
||||
"search_result_page_new_search_hint": "検索",
|
||||
"select_additional_user_for_sharing_page_suggestions": "ユーザーリスト",
|
||||
"select_user_for_sharing_page_err_album": "アルバム作成に失敗...",
|
||||
"select_user_for_sharing_page_share_suggestions": "ユーザーの一覧だよ",
|
||||
"select_user_for_sharing_page_share_suggestions": "ユーザーの一覧",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "バックアップ失敗の通知: {}",
|
||||
"setting_notifications_notify_hours": "{}時間後",
|
||||
"setting_notifications_notify_immediately": "すぐに行う",
|
||||
"setting_notifications_notify_minutes": "{}分後",
|
||||
"setting_notifications_notify_never": "行わない",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "アップロード中の写真の詳細",
|
||||
"setting_notifications_single_progress_title": "バックグランドバックアップの詳細を表示",
|
||||
"setting_notifications_subtitle": "通知設定を変更する",
|
||||
"setting_notifications_title": "通知",
|
||||
"setting_notifications_total_progress_subtitle": "アップロードの進行状況 (完了済み/全体) ",
|
||||
"setting_notifications_total_progress_title": "バックグラウンドバックアップの進行状況を表示",
|
||||
"setting_pages_app_bar_settings": "設定",
|
||||
"settings_require_restart": "設定の適用にImmichの再起動が必要だよ",
|
||||
"share_add": "追加",
|
||||
"share_add_photos": "写真を追加",
|
||||
"share_add_title": "タイトルを追加",
|
||||
"share_create_album": "アルバムを作成",
|
||||
"share_dialog_preparing": "準備中...ちょっと待ってね",
|
||||
"share_invite": "アルバムに参加",
|
||||
"sharing_page_album": "共有アルバム",
|
||||
"sharing_page_description": "共有アルバムを作成して同じネットワークにいる仲間に写真を共有してみよう!",
|
||||
"sharing_page_empty_list": "誰も居ないね ( T_T)\(^-^ ) ドンマイ",
|
||||
"sharing_page_description": "共有アルバムを作成して同じネットワークにいる人たちに写真を共有してみよう!",
|
||||
"sharing_page_empty_list": "共有アルバムが無いよ",
|
||||
"sharing_silver_appbar_create_shared_album": "共有アルバムを作成",
|
||||
"sharing_silver_appbar_share_partner": "パートナーと共有",
|
||||
"tab_controller_nav_library": "ライブラリ",
|
||||
"tab_controller_nav_photos": "写真",
|
||||
"tab_controller_nav_search": "検索",
|
||||
"tab_controller_nav_sharing": "共有",
|
||||
"theme_setting_asset_list_storage_indicator_title": "ストレージに関する情報を表示",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "一列のの写真の数: {} ",
|
||||
"theme_setting_dark_mode_switch": "ダークモード",
|
||||
"theme_setting_image_viewer_quality_subtitle": "画像ビューワの画質の設定",
|
||||
"theme_setting_image_viewer_quality_title": "画像ビューワ",
|
||||
"theme_setting_system_theme_switch": "自動 (端末の設定を反映) ",
|
||||
"theme_setting_theme_subtitle": "アプリの見た目の設定",
|
||||
"theme_setting_theme_title": "テーマ",
|
||||
"theme_setting_three_stage_loading_subtitle": "三段階読み込みを有効にするとパフォーマンスが改善する可能性があるけど、データ使用量が凄く増えるよ",
|
||||
"theme_setting_three_stage_loading_title": "三段階読み込みをオンにする",
|
||||
"version_announcement_overlay_ack": "了解",
|
||||
"version_announcement_overlay_release_notes": "更新情報",
|
||||
"version_announcement_overlay_text_1": "こんにちは、又はこんばんは!新しい",
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"album_info_card_backup_album_excluded": "WYKLUCZONE",
|
||||
"album_info_card_backup_album_included": "WŁĄCZONE",
|
||||
"album_thumbnail_card_item": "1 pozycja",
|
||||
"album_thumbnail_card_items": "{} pozycje",
|
||||
"album_thumbnail_card_shared": "Udostępniony",
|
||||
"album_viewer_appbar_share_delete": "Usuń album",
|
||||
"album_viewer_appbar_share_err_delete": "Nie udało się usunąć albumu",
|
||||
"album_viewer_appbar_share_err_leave": "Nie udało się wyjść z albumu",
|
||||
@@ -9,6 +12,8 @@
|
||||
"album_viewer_appbar_share_leave": "Opuść album",
|
||||
"album_viewer_appbar_share_remove": "Usuń z albumu",
|
||||
"album_viewer_page_share_add_users": "Dodaj użytkowników",
|
||||
"asset_list_settings_subtitle": "Ustawienia układu siatki zdjęć",
|
||||
"asset_list_settings_title": "Siatka Zdjęć",
|
||||
"backup_album_selection_page_albums_device": "Albumy na urządzeniu ({})",
|
||||
"backup_album_selection_page_albums_tap": "Stuknij, aby włączyć, stuknij dwukrotnie, aby wykluczyć",
|
||||
"backup_album_selection_page_assets_scatter": "Pliki mogą być rozproszone w wielu albumach. Dzięki temu albumy mogą być włączane lub wyłączane podczas procesu tworzenia kopii zapasowej.",
|
||||
@@ -16,13 +21,33 @@
|
||||
"backup_album_selection_page_selection_info": "Info o wyborze",
|
||||
"backup_album_selection_page_total_assets": "Łącznie unikalnych plików",
|
||||
"backup_all": "Wszystkie",
|
||||
"backup_background_service_backup_failed_message": "Nie udało się wykonać kopii zapasowej zasobów. Próbuję ponownie...",
|
||||
"backup_background_service_connection_failed_message": "Nie udało się połączyć z serwerem. Próbuję ponownie...",
|
||||
"backup_background_service_current_upload_notification": "Wysyłanie {}",
|
||||
"backup_background_service_default_notification": "Sprawdzanie nowych zasobów...",
|
||||
"backup_background_service_error_title": "Błąd kopii zapasowej",
|
||||
"backup_background_service_in_progress_notification": "Tworzę kopię twoich zasobów...",
|
||||
"backup_background_service_upload_failure_notification": "Nie udało się przesłać {}",
|
||||
"backup_controller_page_albums": "Backup Albumów",
|
||||
"backup_controller_page_backup": "Backup",
|
||||
"backup_controller_page_background_battery_info_link": "Pokaż mi jak",
|
||||
"backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, należy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPonieważ jest to zależne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.",
|
||||
"backup_controller_page_background_battery_info_ok": "OK",
|
||||
"backup_controller_page_background_battery_info_title": "Optymalizacja Baterii",
|
||||
"backup_controller_page_background_charging": "Tylko podczas ładowania",
|
||||
"backup_controller_page_background_configure_error": "Nie udało się skonfigurować usługi w tle",
|
||||
"backup_controller_page_background_delay": "Delay new assets backup: {}",
|
||||
"backup_controller_page_background_description": "Włącz usługę w tle, aby automatycznie tworzyć kopie zapasowe wszelkich nowych zasobów bez konieczności otwierania aplikacji",
|
||||
"backup_controller_page_background_is_off": "Automatyczna kopia zapasowa w tle jest wyłączona",
|
||||
"backup_controller_page_background_is_on": "Automatyczna kopia zapasowa w tle jest włączona",
|
||||
"backup_controller_page_background_turn_off": "Wyłącz usługę w tle",
|
||||
"backup_controller_page_background_turn_on": "Włącz usługę w tle",
|
||||
"backup_controller_page_background_wifi": "Tylko na WiFi",
|
||||
"backup_controller_page_backup": "Kopia Zapasowa",
|
||||
"backup_controller_page_backup_selected": "Zaznaczone: ",
|
||||
"backup_controller_page_backup_sub": "Tworzenie kopii zapasowych zdjęć i filmów",
|
||||
"backup_controller_page_cancel": "Anuluj",
|
||||
"backup_controller_page_created": "Utworzony na: {}",
|
||||
"backup_controller_page_desc_backup": "Włącz backup, aby automatycznie przesyłać nowe zasoby na serwer.",
|
||||
"backup_controller_page_desc_backup": "Włącz kopię zapasową, aby automatycznie przesyłać nowe zasoby na serwer.",
|
||||
"backup_controller_page_excluded": "Wykluczone: ",
|
||||
"backup_controller_page_failed": "Nieudane ({})",
|
||||
"backup_controller_page_filename": "Nazwa pliku: {} [{}]",
|
||||
@@ -33,21 +58,41 @@
|
||||
"backup_controller_page_remainder_sub": "Pozostałe zdjęcia i albumy do wykonania kopii zapasowej z wyboru",
|
||||
"backup_controller_page_select": "Zaznacz",
|
||||
"backup_controller_page_server_storage": "Pamięć Serwera",
|
||||
"backup_controller_page_start_backup": "Rozpocznij Backup",
|
||||
"backup_controller_page_status_off": "Backup jest wyłączony",
|
||||
"backup_controller_page_status_on": "Backup jest włączony",
|
||||
"backup_controller_page_start_backup": "Rozpocznij Kopię Zapasową",
|
||||
"backup_controller_page_status_off": "Kopia Zapasowa jest wyłaczona",
|
||||
"backup_controller_page_status_on": "Kopia Zapasowa jest włączona",
|
||||
"backup_controller_page_storage_format": "{} z {} wykorzystanych",
|
||||
"backup_controller_page_to_backup": "Albumy do backupu",
|
||||
"backup_controller_page_total": "Łącznie",
|
||||
"backup_controller_page_total_sub": "Wszystkie unikalne zdjęcia i filmy z wybranych albumów",
|
||||
"backup_controller_page_turn_off": "Wyłącz Backup",
|
||||
"backup_controller_page_turn_on": "Włącz Backup",
|
||||
"backup_controller_page_turn_off": "Wyłącz Kopię Zapasową",
|
||||
"backup_controller_page_turn_on": "Włącz Kopię Zapasową",
|
||||
"backup_controller_page_uploading_file_info": "Przesyłanie informacji o pliku",
|
||||
"backup_err_only_album": "Nie można usunąć tylko i wyłącznie albumu",
|
||||
"backup_info_card_assets": "pliki",
|
||||
"backup_info_card_assets": "zasoby",
|
||||
"cache_settings_album_thumbnails": "Miniatury stron bibliotek ({} zasobów)",
|
||||
"cache_settings_clear_cache_button": "Wyczyść Cache",
|
||||
"cache_settings_clear_cache_button_title": "Czyści pamięć podręczną aplikacji. Wpłynie to znacząco na wydajność aplikacji, dopóki pamięć podręczna nie zostanie odbudowana.",
|
||||
"cache_settings_image_cache_size": "Rozmiar pamięci podręcznej obrazów ({} zasobów)",
|
||||
"cache_settings_statistics_album": "Biblioteka miniatur",
|
||||
"cache_settings_statistics_assets": "{} zasoby ({})",
|
||||
"cache_settings_statistics_full": "Pełne Zdjęcia",
|
||||
"cache_settings_statistics_shared": "Udostępnione miniatury albumów",
|
||||
"cache_settings_statistics_thumbnail": "Miniatury",
|
||||
"cache_settings_statistics_title": "Użycie Cache",
|
||||
"cache_settings_subtitle": "Kontrolowanie zachowania buforowania aplikacji mobilnej Immich",
|
||||
"cache_settings_thumbnail_size": "Rozmiar pamięci podręcznej miniatur ({} zasobów)",
|
||||
"cache_settings_title": "Ustawienia Buforowania",
|
||||
"control_bottom_app_bar_add_to_album": "Add to album",
|
||||
"control_bottom_app_bar_album_info": "{} items",
|
||||
"control_bottom_app_bar_album_info_shared": "{} items · Shared",
|
||||
"control_bottom_app_bar_create_new_album": "Create new album",
|
||||
"control_bottom_app_bar_delete": "Usuń",
|
||||
"control_bottom_app_bar_share": "Udostępnij",
|
||||
"create_album_page_untitled": "Bez tytułu",
|
||||
"create_shared_album_page_create": "Utwórz",
|
||||
"create_shared_album_page_share": "Udostępnij",
|
||||
"create_shared_album_page_share_add_assets": "DODAJ PLIKI",
|
||||
"create_shared_album_page_share_add_assets": "DODAJ ZASOBY",
|
||||
"create_shared_album_page_share_select_photos": "Zaznacz Zdjęcia",
|
||||
"daily_title_text_date": "E, MMM dd",
|
||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||
@@ -56,24 +101,36 @@
|
||||
"delete_dialog_cancel": "Anuluj",
|
||||
"delete_dialog_ok": "Usuń",
|
||||
"delete_dialog_title": "Usuń trwale",
|
||||
"exif_bottom_sheet_description": "Dodaj opis...",
|
||||
"exif_bottom_sheet_description": "Dodaj Opis...",
|
||||
"exif_bottom_sheet_details": "SZCZEGÓŁY",
|
||||
"exif_bottom_sheet_location": "LOKALIZACJA",
|
||||
"experimental_settings_new_asset_list_subtitle": "Work in progress",
|
||||
"experimental_settings_new_asset_list_title": "Enable experimental photo grid",
|
||||
"experimental_settings_subtitle": "Use at your own risk!",
|
||||
"experimental_settings_title": "Experimental",
|
||||
"home_page_add_to_album_conflicts": "Added {added} assets to album {album}. {failed} assets are already in the album.",
|
||||
"home_page_add_to_album_success": "Added {added} assets to album {album}.",
|
||||
"library_page_albums": "Albumy",
|
||||
"library_page_new_album": "Nowy album",
|
||||
"login_form_button_text": "Login",
|
||||
"login_form_email_hint": "twojmail@email.com",
|
||||
"login_form_endpoint_hint": "http://ip-twojego-serwera:port/api",
|
||||
"login_form_endpoint_url": "URL Serwera",
|
||||
"login_form_err_http": "Proszę określić http:// lub https://",
|
||||
"login_form_err_invalid_email": "Niepoprawny emaill",
|
||||
"login_form_err_invalid_email": "Niepoprawny Email",
|
||||
"login_form_err_leading_whitespace": "Białe znaki",
|
||||
"login_form_err_trailing_whitespace": "Białe znaki po przecinku",
|
||||
"login_form_failed_get_oauth_server_config": "Error logging using OAuth, check server URL",
|
||||
"login_form_failed_get_oauth_server_disable": "OAuth feature is not available on this server",
|
||||
"login_form_failed_login": "Błąd logowania, sprawdź adres url serwera, email i hasło.",
|
||||
"login_form_label_email": "Email",
|
||||
"login_form_label_password": "Hasło",
|
||||
"login_form_password_hint": "hasło",
|
||||
"login_form_save_login": "Pozostań zalogowany",
|
||||
"monthly_title_text_date_format": "MMMM y",
|
||||
"profile_drawer_app_logs": "Logs",
|
||||
"profile_drawer_client_server_up_to_date": "Klient i serwer są aktualne",
|
||||
"profile_drawer_settings": "Ustawienia",
|
||||
"profile_drawer_sign_out": "Wyloguj się",
|
||||
"search_bar_hint": "Szukaj swoich zdjęć",
|
||||
"search_page_no_objects": "Brak informacji o obiektach",
|
||||
@@ -84,20 +141,51 @@
|
||||
"select_additional_user_for_sharing_page_suggestions": "Propozycje",
|
||||
"select_user_for_sharing_page_err_album": "Nie udało się utworzyć albumu",
|
||||
"select_user_for_sharing_page_share_suggestions": "Propozycje",
|
||||
"setting_image_viewer_help": "The detail viewer loads the small thumbnail first, then loads the medium-size preview (if enabled), finally loads the original (if enabled).",
|
||||
"setting_image_viewer_original_subtitle": "Enable to load the original full-resolution image (large!). Disable to reduce data usage (both network and on device cache).",
|
||||
"setting_image_viewer_original_title": "Load original image",
|
||||
"setting_image_viewer_preview_subtitle": "Enable to load a medium-resolution image. Disable to either directly load the original or only use the thumbnail.",
|
||||
"setting_image_viewer_preview_title": "Load preview image",
|
||||
"setting_notifications_notify_failures_grace_period": "Powiadomienie o awariach kopii zapasowych w tle: {}",
|
||||
"setting_notifications_notify_hours": "{} godzin",
|
||||
"setting_notifications_notify_immediately": "natychmiast",
|
||||
"setting_notifications_notify_minutes": "{} minut",
|
||||
"setting_notifications_notify_never": "nigdy",
|
||||
"setting_notifications_notify_seconds": "{} seconds",
|
||||
"setting_notifications_single_progress_subtitle": "Detailed upload progress information per asset",
|
||||
"setting_notifications_single_progress_title": "Show background backup detail progress",
|
||||
"setting_notifications_subtitle": "Dostosuj preferencje powiadomień",
|
||||
"setting_notifications_title": "Powiadomienia",
|
||||
"setting_notifications_total_progress_subtitle": "Overall upload progress (done/total assets)",
|
||||
"setting_notifications_total_progress_title": "Show background backup total progress",
|
||||
"setting_pages_app_bar_settings": "Ustawienia",
|
||||
"settings_require_restart": "Please restart Immich to apply this setting",
|
||||
"share_add": "Dodaj",
|
||||
"share_add_photos": "Dodaj zdjęcia",
|
||||
"share_add_title": "Dodaj tytuł",
|
||||
"share_create_album": "Utwórz album",
|
||||
"share_dialog_preparing": "Przygotowywanie...",
|
||||
"share_invite": "Zaproś do albumu",
|
||||
"sharing_page_album": "Udostępnione albumy",
|
||||
"sharing_page_description": "Twórz wspóldzielone albumy, aby udostępniać zdjęcia i filmy osobom w sieci.",
|
||||
"sharing_page_empty_list": "PUSTA LISTA",
|
||||
"sharing_silver_appbar_create_shared_album": "Utwórz współdzielony album",
|
||||
"sharing_silver_appbar_share_partner": "Udostępnij partnerce/partnerowi",
|
||||
"tab_controller_nav_library": "Biblioteka",
|
||||
"tab_controller_nav_photos": "Zdjęcia",
|
||||
"tab_controller_nav_search": "Szukaj",
|
||||
"tab_controller_nav_sharing": "Udostępnianie",
|
||||
"version_announcement_overlay_ack": "Potwierdzenie",
|
||||
"theme_setting_asset_list_storage_indicator_title": "Pokaż wskaźnik przechowywania na kafelkach zasobów",
|
||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||
"theme_setting_dark_mode_switch": "Ciemny Motyw",
|
||||
"theme_setting_image_viewer_quality_subtitle": "Dostosuj jakość podglądu szczegółowości",
|
||||
"theme_setting_image_viewer_quality_title": "Jakość przeglądania obrazów",
|
||||
"theme_setting_system_theme_switch": "Automatyczny (Postępuj zgodnie z ustawieniami systemu)",
|
||||
"theme_setting_theme_subtitle": "Wybierz ustawienia motywu aplikacji",
|
||||
"theme_setting_theme_title": "Motyw",
|
||||
"theme_setting_three_stage_loading_subtitle": "Trójstopniowe ładowanie może zwiększyć wydajność ładowania, ale powoduje znacznie większe obciążenie sieci",
|
||||
"theme_setting_three_stage_loading_title": "Włączenie trójstopniowego ładowania",
|
||||
"version_announcement_overlay_ack": "Potwierdzam",
|
||||
"version_announcement_overlay_release_notes": "informacje o wydaniu",
|
||||
"version_announcement_overlay_text_1": "Cześć przyjacielu, jest nowe wydanie",
|
||||
"version_announcement_overlay_text_2": "prosimy o poświęcenie czasu na odwiedzenie ",
|
||||
|
||||
BIN
mobile/fonts/Inconsolata-Regular.ttf
Normal file
2
mobile/ios/.gitignore
vendored
@@ -31,4 +31,4 @@ Runner/GeneratedPluginRegistrant.*
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
!default.perspectivev3
|
||||
@@ -360,7 +360,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 71;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -495,7 +495,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 71;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -522,7 +522,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 71;
|
||||
CURRENT_PROJECT_VERSION = 74;
|
||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
|
||||
@@ -17,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.36.0</string>
|
||||
<string>1.37.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>71</string>
|
||||
<string>74</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true />
|
||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||
|
||||
23
mobile/ios/ci_scripts/ci_post_clone.sh
Executable file
@@ -0,0 +1,23 @@
|
||||
#!/bin/sh
|
||||
|
||||
# The default execution directory of this script is the ci_scripts directory.
|
||||
cd $CI_WORKSPACE/mobile
|
||||
|
||||
# Install Flutter using git.
|
||||
git clone https://github.com/flutter/flutter.git --depth 1 -b stable $HOME/flutter
|
||||
export PATH="$PATH:$HOME/flutter/bin"
|
||||
|
||||
# Install Flutter artifacts for iOS (--ios), or macOS (--macos) platforms.
|
||||
flutter precache --ios
|
||||
|
||||
# Install Flutter dependencies.
|
||||
flutter pub get
|
||||
|
||||
# Install CocoaPods using Homebrew.
|
||||
HOMEBREW_NO_AUTO_UPDATE=1 # disable homebrew's automatic updates.
|
||||
brew install cocoapods
|
||||
|
||||
# Install CocoaPods dependencies.
|
||||
cd ios && pod install # run `pod install` in the `ios` directory.
|
||||
|
||||
exit 0
|
||||
@@ -19,7 +19,7 @@ platform :ios do
|
||||
desc "iOS Beta"
|
||||
lane :beta do
|
||||
increment_version_number(
|
||||
version_number: "1.36.0"
|
||||
version_number: "1.38.0"
|
||||
)
|
||||
increment_build_number(
|
||||
build_number: latest_testflight_build_number + 1,
|
||||
|
||||
@@ -5,32 +5,29 @@
|
||||
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000333">
|
||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000334">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.777934">
|
||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="1.671363">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="6.375897">
|
||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="7.167423">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.664307">
|
||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.654653">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="88.90147">
|
||||
<testcase classname="fastlane.lanes" name="4: build_app" time="29.319346">
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="79.807067">
|
||||
<failure message="/usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:229:in `chdir' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:229:in `execute_action' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing' Fastfile:27:in `block (2 levels) in parsing_binding' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/lane.rb:33:in `call' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:49:in `block in execute' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:45:in `chdir' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/runner.rb:45:in `execute' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/commands_generator.rb:354:in `run' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/commands_generator.rb:43:in `start' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off' /usr/local/Cellar/fastlane/2.210.1/libexec/gems/fastlane-2.210.1/bin/fastlane:23:in `<top (required)>' /usr/local/Cellar/fastlane/2.210.1/libexec/bin/fastlane:25:in `load' /usr/local/Cellar/fastlane/2.210.1/libexec/bin/fastlane:25:in `<main>' Error building the application - see the log above" />
|
||||
|
||||
</testcase>
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ const String accessTokenKey = "immichBoxAccessTokenKey"; // Key 1
|
||||
const String deviceIdKey = 'immichBoxDeviceIdKey'; // Key 2
|
||||
const String isLoggedInKey = 'immichIsLoggedInKey'; // Key 3
|
||||
const String serverEndpointKey = 'immichBoxServerEndpoint'; // Key 4
|
||||
const String assetEtagKey = 'immichAssetEtagKey'; // Key 5
|
||||
|
||||
// Login Info
|
||||
const String hiveLoginInfoBox = "immichLoginInfoBox"; // Box
|
||||
@@ -25,7 +26,11 @@ const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box
|
||||
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
||||
const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2
|
||||
const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3
|
||||
const String backupTriggerDelay = "immichBackupTriggerDelay"; // Key 4
|
||||
|
||||
// Duplicate asset
|
||||
const String duplicatedAssetsBox = "immichDuplicatedAssetsBox"; // Box
|
||||
const String duplicatedAssetsKey = "immichDuplicatedAssetsKey"; // Key 1
|
||||
|
||||
// In app logger
|
||||
const String immichLoggerBox = "immichInAppLogger"; // Box
|
||||
@@ -16,11 +16,13 @@ import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.d
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
|
||||
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
|
||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/immich_logger.service.dart';
|
||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||
import 'package:immich_mobile/shared/views/version_announcement_overlay.dart';
|
||||
import 'package:immich_mobile/utils/immich_app_theme.dart';
|
||||
@@ -31,8 +33,10 @@ void main() async {
|
||||
Hive.registerAdapter(HiveSavedLoginInfoAdapter());
|
||||
Hive.registerAdapter(HiveBackupAlbumsAdapter());
|
||||
Hive.registerAdapter(HiveDuplicatedAssetsAdapter());
|
||||
Hive.registerAdapter(ImmichLoggerMessageAdapter());
|
||||
|
||||
await Future.wait([
|
||||
Hive.openBox<ImmichLoggerMessage>(immichLoggerBox),
|
||||
Hive.openBox(userInfoBox),
|
||||
Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox),
|
||||
Hive.openBox(hiveGithubReleaseInfoBox),
|
||||
@@ -58,6 +62,9 @@ void main() async {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize Immich Logger Service
|
||||
ImmichLogger().init();
|
||||
|
||||
runApp(
|
||||
EasyLocalization(
|
||||
supportedLocales: locales,
|
||||
|
||||
@@ -69,7 +69,10 @@ class AlbumService {
|
||||
Iterable<Asset> assets,
|
||||
) async {
|
||||
return createAlbum(
|
||||
_getNextAlbumName(await getAlbums(isShared: false)), assets, []);
|
||||
_getNextAlbumName(await getAlbums(isShared: false)),
|
||||
assets,
|
||||
[],
|
||||
);
|
||||
}
|
||||
|
||||
Future<AlbumResponseDto?> getAlbumDetail(String albumId) async {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -21,8 +19,38 @@ class AlbumThumbnailCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var box = Hive.box(userInfoBox);
|
||||
var cardSize = MediaQuery.of(context).size.width / 2 - 18;
|
||||
var isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
final cardSize = MediaQuery.of(context).size.width / 2 - 18;
|
||||
buildEmptyThumbnail() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isDarkMode ? Colors.grey[800] : Colors.grey[200],
|
||||
),
|
||||
child: SizedBox(
|
||||
height: cardSize,
|
||||
width: cardSize,
|
||||
child: const Center(
|
||||
child: Icon(Icons.no_photography),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
buildAlbumThumbnail() {
|
||||
return CachedNetworkImage(
|
||||
width: cardSize,
|
||||
height: cardSize,
|
||||
fit: BoxFit.cover,
|
||||
fadeInDuration: const Duration(milliseconds: 200),
|
||||
imageUrl: getAlbumThumbnailUrl(
|
||||
album,
|
||||
type: ThumbnailFormat.JPEG,
|
||||
),
|
||||
httpHeaders: {"Authorization": "Bearer ${box.get(accessTokenKey)}"},
|
||||
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
@@ -35,19 +63,9 @@ class AlbumThumbnailCard extends StatelessWidget {
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: CachedNetworkImage(
|
||||
memCacheHeight: max(400, cardSize.toInt() * 3),
|
||||
width: cardSize,
|
||||
height: cardSize,
|
||||
fit: BoxFit.cover,
|
||||
fadeInDuration: const Duration(milliseconds: 200),
|
||||
imageUrl:
|
||||
getAlbumThumbnailUrl(album, type: ThumbnailFormat.JPEG),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
cacheKey: "${album.albumThumbnailAssetId}",
|
||||
),
|
||||
child: album.albumThumbnailAssetId == null
|
||||
? buildEmptyThumbnail()
|
||||
: buildAlbumThumbnail(),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
|
||||
@@ -34,7 +34,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
|
||||
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
|
||||
|
||||
void _onDeleteAlbumPressed(String albumId) async {
|
||||
void onDeleteAlbumPressed(String albumId) async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
bool isSuccess =
|
||||
@@ -62,7 +62,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
}
|
||||
|
||||
void _onLeaveAlbumPressed(String albumId) async {
|
||||
void onLeaveAlbumPressed(String albumId) async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
bool isSuccess =
|
||||
@@ -84,7 +84,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
}
|
||||
|
||||
void _onRemoveFromAlbumPressed(String albumId) async {
|
||||
void onRemoveFromAlbumPressed(String albumId) async {
|
||||
ImmichLoadingOverlayController.appLoader.show();
|
||||
|
||||
bool isSuccess =
|
||||
@@ -110,7 +110,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
ImmichLoadingOverlayController.appLoader.hide();
|
||||
}
|
||||
|
||||
_buildBottomSheetActionButton() {
|
||||
buildBottomSheetActionButton() {
|
||||
if (isMultiSelectionEnable) {
|
||||
if (albumInfo.ownerId == userId) {
|
||||
return ListTile(
|
||||
@@ -119,7 +119,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
'album_viewer_appbar_share_remove',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
onTap: () => _onRemoveFromAlbumPressed(albumId),
|
||||
onTap: () => onRemoveFromAlbumPressed(albumId),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
@@ -132,7 +132,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
'album_viewer_appbar_share_delete',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
onTap: () => _onDeleteAlbumPressed(albumId),
|
||||
onTap: () => onDeleteAlbumPressed(albumId),
|
||||
);
|
||||
} else {
|
||||
return ListTile(
|
||||
@@ -141,13 +141,13 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
'album_viewer_appbar_share_leave',
|
||||
style: TextStyle(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
onTap: () => _onLeaveAlbumPressed(albumId),
|
||||
onTap: () => onLeaveAlbumPressed(albumId),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _buildBottomSheet() {
|
||||
void buildBottomSheet() {
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||
isScrollControlled: false,
|
||||
@@ -157,7 +157,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildBottomSheetActionButton(),
|
||||
buildBottomSheetActionButton(),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -165,7 +165,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildLeadingButton() {
|
||||
buildLeadingButton() {
|
||||
if (isMultiSelectionEnable) {
|
||||
return IconButton(
|
||||
onPressed: () => ref
|
||||
@@ -204,7 +204,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
|
||||
return AppBar(
|
||||
elevation: 0,
|
||||
leading: _buildLeadingButton(),
|
||||
leading: buildLeadingButton(),
|
||||
title: isMultiSelectionEnable
|
||||
? Text('${selectedAssetsInAlbum.length}')
|
||||
: null,
|
||||
@@ -212,7 +212,7 @@ class AlbumViewerAppbar extends HookConsumerWidget with PreferredSizeWidget {
|
||||
actions: [
|
||||
IconButton(
|
||||
splashRadius: 25,
|
||||
onPressed: _buildBottomSheet,
|
||||
onPressed: buildBottomSheet,
|
||||
icon: const Icon(Icons.more_horiz_rounded),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -27,7 +27,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
||||
final isMultiSelectionEnable =
|
||||
ref.watch(assetSelectionProvider).isMultiselectEnable;
|
||||
|
||||
_viewAsset() {
|
||||
viewAsset() {
|
||||
AutoRouter.of(context).push(
|
||||
GalleryViewerRoute(
|
||||
asset: asset,
|
||||
@@ -47,18 +47,18 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_enableMultiSelection() {
|
||||
enableMultiSelection() {
|
||||
ref.watch(assetSelectionProvider.notifier).enableMultiselection();
|
||||
ref
|
||||
.watch(assetSelectionProvider.notifier)
|
||||
.addAssetsInAlbumViewer([asset]);
|
||||
}
|
||||
|
||||
_disableMultiSelection() {
|
||||
disableMultiSelection() {
|
||||
ref.watch(assetSelectionProvider.notifier).disableMultiselection();
|
||||
}
|
||||
|
||||
_buildVideoLabel() {
|
||||
buildVideoLabel() {
|
||||
return Positioned(
|
||||
top: 5,
|
||||
right: 5,
|
||||
@@ -80,7 +80,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildAssetStoreLocationIcon() {
|
||||
buildAssetStoreLocationIcon() {
|
||||
return Positioned(
|
||||
right: 10,
|
||||
bottom: 5,
|
||||
@@ -94,7 +94,7 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildAssetSelectionIcon() {
|
||||
buildAssetSelectionIcon() {
|
||||
bool isSelected = selectedAssetsInAlbumViewer.contains(asset);
|
||||
|
||||
return Positioned(
|
||||
@@ -112,21 +112,21 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildThumbnailImage() {
|
||||
buildThumbnailImage() {
|
||||
return Container(
|
||||
decoration: BoxDecoration(border: drawBorderColor()),
|
||||
child: ImmichImage(asset, width: 300, height: 300),
|
||||
);
|
||||
}
|
||||
|
||||
_handleSelectionGesture() {
|
||||
handleSelectionGesture() {
|
||||
if (selectedAssetsInAlbumViewer.contains(asset)) {
|
||||
ref
|
||||
.watch(assetSelectionProvider.notifier)
|
||||
.removeAssetsInAlbumViewer([asset]);
|
||||
|
||||
if (selectedAssetsInAlbumViewer.isEmpty) {
|
||||
_disableMultiSelection();
|
||||
disableMultiSelection();
|
||||
}
|
||||
} else {
|
||||
ref
|
||||
@@ -136,14 +136,14 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: isMultiSelectionEnable ? _handleSelectionGesture : _viewAsset,
|
||||
onLongPress: _enableMultiSelection,
|
||||
onTap: isMultiSelectionEnable ? handleSelectionGesture : viewAsset,
|
||||
onLongPress: enableMultiSelection,
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildThumbnailImage(),
|
||||
if (showStorageIndicator) _buildAssetStoreLocationIcon(),
|
||||
if (!asset.isImage) _buildVideoLabel(),
|
||||
if (isMultiSelectionEnable) _buildAssetSelectionIcon(),
|
||||
buildThumbnailImage(),
|
||||
if (showStorageIndicator) buildAssetStoreLocationIcon(),
|
||||
if (!asset.isImage) buildVideoLabel(),
|
||||
if (isMultiSelectionEnable) buildAssetSelectionIcon(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -21,7 +21,7 @@ class MonthGroupTitle extends HookConsumerWidget {
|
||||
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
||||
final isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
||||
|
||||
_handleTitleIconClick() {
|
||||
handleTitleIconClick() {
|
||||
HapticFeedback.heavyImpact();
|
||||
|
||||
if (isAlbumExist) {
|
||||
@@ -61,7 +61,7 @@ class MonthGroupTitle extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_getSimplifiedMonth() {
|
||||
getSimplifiedMonth() {
|
||||
var monthAndYear = month.split(',');
|
||||
var yearText = monthAndYear[1].trim();
|
||||
var monthText = monthAndYear[0].trim();
|
||||
@@ -85,7 +85,7 @@ class MonthGroupTitle extends HookConsumerWidget {
|
||||
child: Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: _handleTitleIconClick,
|
||||
onTap: handleTitleIconClick,
|
||||
child: selectedDateGroup.contains(month)
|
||||
? Icon(
|
||||
Icons.check_circle_rounded,
|
||||
@@ -97,11 +97,11 @@ class MonthGroupTitle extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: _handleTitleIconClick,
|
||||
onTap: handleTitleIconClick,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 8.0),
|
||||
child: Text(
|
||||
_getSimplifiedMonth(),
|
||||
getSimplifiedMonth(),
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
color: Theme.of(context).primaryColor,
|
||||
|
||||
@@ -18,7 +18,7 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
||||
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
|
||||
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
||||
|
||||
Widget _buildSelectionIcon(Asset asset) {
|
||||
Widget buildSelectionIcon(Asset asset) {
|
||||
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
|
||||
var isNewlySelected =
|
||||
newAssetsForAlbum.map((item) => item.id).contains(asset.id);
|
||||
@@ -111,7 +111,7 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.all(3.0),
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: _buildSelectionIcon(asset),
|
||||
child: buildSelectionIcon(asset),
|
||||
),
|
||||
),
|
||||
if (!asset.isImage)
|
||||
|
||||
@@ -37,7 +37,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
|
||||
/// Find out if the assets in album exist on the device
|
||||
/// If they exist, add to selected asset state to show they are already selected.
|
||||
void _onAddPhotosPressed(AlbumResponseDto albumInfo) async {
|
||||
void onAddPhotosPressed(AlbumResponseDto albumInfo) async {
|
||||
if (albumInfo.assets.isNotEmpty == true) {
|
||||
ref.watch(assetSelectionProvider.notifier).addNewAssets(
|
||||
albumInfo.assets.map((e) => Asset.remote(e)).toList(),
|
||||
@@ -60,7 +60,8 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
albumId,
|
||||
);
|
||||
|
||||
if (addAssetsResult != null && addAssetsResult.successfullyAdded > 0) {
|
||||
if (addAssetsResult != null &&
|
||||
addAssetsResult.successfullyAdded > 0) {
|
||||
ref.refresh(sharedAlbumDetailProvider(albumId));
|
||||
}
|
||||
|
||||
@@ -73,7 +74,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
void _onAddUsersPressed(AlbumResponseDto albumInfo) async {
|
||||
void onAddUsersPressed(AlbumResponseDto albumInfo) async {
|
||||
List<String>? sharedUserIds =
|
||||
await AutoRouter.of(context).push<List<String>?>(
|
||||
SelectAdditionalUserForSharingRoute(albumInfo: albumInfo),
|
||||
@@ -94,7 +95,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTitle(AlbumResponseDto albumInfo) {
|
||||
Widget buildTitle(AlbumResponseDto albumInfo) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
|
||||
child: userId == albumInfo.ownerId
|
||||
@@ -115,7 +116,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAlbumDateRange(AlbumResponseDto albumInfo) {
|
||||
Widget buildAlbumDateRange(AlbumResponseDto albumInfo) {
|
||||
String startDate = "";
|
||||
DateTime parsedStartDate =
|
||||
DateTime.parse(albumInfo.assets.first.createdAt);
|
||||
@@ -148,14 +149,14 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHeader(AlbumResponseDto albumInfo) {
|
||||
Widget buildHeader(AlbumResponseDto albumInfo) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitle(albumInfo),
|
||||
buildTitle(albumInfo),
|
||||
if (albumInfo.assets.isNotEmpty == true)
|
||||
_buildAlbumDateRange(albumInfo),
|
||||
buildAlbumDateRange(albumInfo),
|
||||
if (albumInfo.shared)
|
||||
SizedBox(
|
||||
height: 60,
|
||||
@@ -188,7 +189,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImageGrid(AlbumResponseDto albumInfo) {
|
||||
Widget buildImageGrid(AlbumResponseDto albumInfo) {
|
||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||
final bool showStorageIndicator =
|
||||
appSettingService.getSetting(AppSettingsEnum.storageIndicator);
|
||||
@@ -220,7 +221,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
|
||||
Widget _buildControlButton(AlbumResponseDto albumInfo) {
|
||||
Widget buildControlButton(AlbumResponseDto albumInfo) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
|
||||
child: SizedBox(
|
||||
@@ -230,13 +231,13 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
children: [
|
||||
AlbumActionOutlinedButton(
|
||||
iconData: Icons.add_photo_alternate_outlined,
|
||||
onPressed: () => _onAddPhotosPressed(albumInfo),
|
||||
onPressed: () => onAddPhotosPressed(albumInfo),
|
||||
labelText: "share_add_photos".tr(),
|
||||
),
|
||||
if (userId == albumInfo.ownerId)
|
||||
AlbumActionOutlinedButton(
|
||||
iconData: Icons.person_add_alt_rounded,
|
||||
onPressed: () => _onAddUsersPressed(albumInfo),
|
||||
onPressed: () => onAddUsersPressed(albumInfo),
|
||||
labelText: "album_viewer_page_share_add_users".tr(),
|
||||
),
|
||||
],
|
||||
@@ -245,7 +246,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody(AlbumResponseDto albumInfo) {
|
||||
Widget buildBody(AlbumResponseDto albumInfo) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
titleFocusNode.unfocus();
|
||||
@@ -257,7 +258,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
child: CustomScrollView(
|
||||
controller: scrollController,
|
||||
slivers: [
|
||||
_buildHeader(albumInfo),
|
||||
buildHeader(albumInfo),
|
||||
SliverPersistentHeader(
|
||||
pinned: true,
|
||||
delegate: ImmichSliverPersistentAppBarDelegate(
|
||||
@@ -265,11 +266,11 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
maxHeight: 50,
|
||||
child: Container(
|
||||
color: Theme.of(context).scaffoldBackgroundColor,
|
||||
child: _buildControlButton(albumInfo),
|
||||
child: buildControlButton(albumInfo),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildImageGrid(albumInfo)
|
||||
buildImageGrid(albumInfo)
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -293,7 +294,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
||||
),
|
||||
body: albumInfo.when(
|
||||
data: (albumInfo) => albumInfo != null
|
||||
? _buildBody(albumInfo)
|
||||
? buildBody(albumInfo)
|
||||
: const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
|
||||
@@ -25,7 +25,7 @@ class AssetSelectionPage extends HookConsumerWidget {
|
||||
|
||||
List<Widget> imageGridGroup = [];
|
||||
|
||||
String _buildAssetCountText() {
|
||||
String buildAssetCountText() {
|
||||
if (isAlbumExist) {
|
||||
return (selectedAssets.length + newAssetsForAlbum.length).toString();
|
||||
} else {
|
||||
@@ -33,7 +33,7 @@ class AssetSelectionPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
Widget buildBody() {
|
||||
assetGroupMonthYear.forEach((monthYear, assetGroup) {
|
||||
imageGridGroup
|
||||
.add(MonthGroupTitle(month: monthYear, assetGroup: assetGroup));
|
||||
@@ -71,7 +71,7 @@ class AssetSelectionPage extends HookConsumerWidget {
|
||||
style: TextStyle(fontSize: 18),
|
||||
).tr()
|
||||
: Text(
|
||||
_buildAssetCountText(),
|
||||
buildAssetCountText(),
|
||||
style: const TextStyle(fontSize: 18),
|
||||
),
|
||||
centerTitle: false,
|
||||
@@ -94,7 +94,7 @@ class AssetSelectionPage extends HookConsumerWidget {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _buildBody(),
|
||||
body: buildBody(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,11 +29,11 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
||||
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
_showSelectUserPage() {
|
||||
showSelectUserPage() {
|
||||
AutoRouter.of(context).push(const SelectUserForSharingRoute());
|
||||
}
|
||||
|
||||
void _onBackgroundTapped() {
|
||||
void onBackgroundTapped() {
|
||||
albumTitleTextFieldFocusNode.unfocus();
|
||||
isAlbumTitleTextFieldFocus.value = false;
|
||||
|
||||
@@ -45,7 +45,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_onSelectPhotosButtonPressed() async {
|
||||
onSelectPhotosButtonPressed() async {
|
||||
ref.watch(assetSelectionProvider.notifier).setIsAlbumExist(false);
|
||||
|
||||
AssetSelectionPageResult? selectedAsset = await AutoRouter.of(context)
|
||||
@@ -56,7 +56,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_buildTitleInputField() {
|
||||
buildTitleInputField() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 10,
|
||||
@@ -71,7 +71,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildTitle() {
|
||||
buildTitle() {
|
||||
if (selectedAssets.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
@@ -90,7 +90,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
|
||||
_buildSelectPhotosButton() {
|
||||
buildSelectPhotosButton() {
|
||||
if (selectedAssets.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
@@ -109,7 +109,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
),
|
||||
onPressed: _onSelectPhotosButtonPressed,
|
||||
onPressed: onSelectPhotosButtonPressed,
|
||||
icon: Icon(
|
||||
Icons.add_rounded,
|
||||
color: Theme.of(context).primaryColor,
|
||||
@@ -132,7 +132,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
|
||||
_buildControlButton() {
|
||||
buildControlButton() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 12.0, top: 16, bottom: 16),
|
||||
child: SizedBox(
|
||||
@@ -142,7 +142,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
children: [
|
||||
AlbumActionOutlinedButton(
|
||||
iconData: Icons.add_photo_alternate_outlined,
|
||||
onPressed: _onSelectPhotosButtonPressed,
|
||||
onPressed: onSelectPhotosButtonPressed,
|
||||
labelText: "share_add_photos".tr(),
|
||||
),
|
||||
],
|
||||
@@ -151,7 +151,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildSelectedImageGrid() {
|
||||
buildSelectedImageGrid() {
|
||||
if (selectedAssets.isNotEmpty) {
|
||||
return SliverPadding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
@@ -164,7 +164,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
return GestureDetector(
|
||||
onTap: _onBackgroundTapped,
|
||||
onTap: onBackgroundTapped,
|
||||
child: SharedAlbumThumbnailImage(
|
||||
asset: selectedAssets.elementAt(index),
|
||||
),
|
||||
@@ -179,7 +179,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
|
||||
_createNonSharedAlbum() async {
|
||||
createNonSharedAlbum() async {
|
||||
var newAlbum = await ref.watch(albumProvider.notifier).createAlbum(
|
||||
ref.watch(albumTitleProvider),
|
||||
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum,
|
||||
@@ -216,7 +216,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
if (isSharedAlbum)
|
||||
TextButton(
|
||||
onPressed: albumTitleController.text.isNotEmpty
|
||||
? _showSelectUserPage
|
||||
? showSelectUserPage
|
||||
: null,
|
||||
child: Text(
|
||||
'create_shared_album_page_share'.tr(),
|
||||
@@ -230,7 +230,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
TextButton(
|
||||
onPressed: albumTitleController.text.isNotEmpty &&
|
||||
selectedAssets.isNotEmpty
|
||||
? _createNonSharedAlbum
|
||||
? createNonSharedAlbum
|
||||
: null,
|
||||
child: Text(
|
||||
'create_shared_album_page_create'.tr(),
|
||||
@@ -242,7 +242,7 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
],
|
||||
),
|
||||
body: GestureDetector(
|
||||
onTap: _onBackgroundTapped,
|
||||
onTap: onBackgroundTapped,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
@@ -255,15 +255,15 @@ class CreateAlbumPage extends HookConsumerWidget {
|
||||
preferredSize: const Size.fromHeight(66.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildTitleInputField(),
|
||||
if (selectedAssets.isNotEmpty) _buildControlButton(),
|
||||
buildTitleInputField(),
|
||||
if (selectedAssets.isNotEmpty) buildControlButton(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildTitle(),
|
||||
_buildSelectPhotosButton(),
|
||||
_buildSelectedImageGrid(),
|
||||
buildTitle(),
|
||||
buildSelectPhotosButton(),
|
||||
buildSelectedImageGrid(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -19,12 +19,12 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
ref.watch(suggestedSharedUsersProvider);
|
||||
final sharedUsersList = useState<Set<UserResponseDto>>({});
|
||||
|
||||
_addNewUsersHandler() {
|
||||
addNewUsersHandler() {
|
||||
AutoRouter.of(context)
|
||||
.pop(sharedUsersList.value.map((e) => e.id).toList());
|
||||
}
|
||||
|
||||
_buildTileIcon(UserResponseDto user) {
|
||||
buildTileIcon(UserResponseDto user) {
|
||||
if (sharedUsersList.value.contains(user)) {
|
||||
return CircleAvatar(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
@@ -42,7 +42,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_buildUserList(List<UserResponseDto> users) {
|
||||
buildUserList(List<UserResponseDto> users) {
|
||||
List<Widget> usersChip = [];
|
||||
|
||||
for (var user in sharedUsersList.value) {
|
||||
@@ -84,7 +84,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
shrinkWrap: true,
|
||||
itemBuilder: ((context, index) {
|
||||
return ListTile(
|
||||
leading: _buildTileIcon(users[index]),
|
||||
leading: buildTileIcon(users[index]),
|
||||
title: Text(
|
||||
users[index].email,
|
||||
style: const TextStyle(
|
||||
@@ -131,7 +131,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
sharedUsersList.value.isEmpty ? null : _addNewUsersHandler,
|
||||
sharedUsersList.value.isEmpty ? null : addNewUsersHandler,
|
||||
child: const Text(
|
||||
"share_add",
|
||||
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
@@ -147,7 +147,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return _buildUserList(users);
|
||||
return buildUserList(users);
|
||||
},
|
||||
error: (e, _) => Text("Error loading suggested users $e"),
|
||||
loading: () => const Center(
|
||||
|
||||
@@ -20,7 +20,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
AsyncValue<List<UserResponseDto>> suggestedShareUsers =
|
||||
ref.watch(suggestedSharedUsersProvider);
|
||||
|
||||
_createSharedAlbum() async {
|
||||
createSharedAlbum() async {
|
||||
var newAlbum =
|
||||
await ref.watch(sharedAlbumProvider.notifier).createSharedAlbum(
|
||||
ref.watch(albumTitleProvider),
|
||||
@@ -44,7 +44,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildTileIcon(UserResponseDto user) {
|
||||
buildTileIcon(UserResponseDto user) {
|
||||
if (sharedUsersList.value.contains(user)) {
|
||||
return CircleAvatar(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
@@ -62,7 +62,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_buildUserList(List<UserResponseDto> users) {
|
||||
buildUserList(List<UserResponseDto> users) {
|
||||
List<Widget> usersChip = [];
|
||||
|
||||
for (var user in sharedUsersList.value) {
|
||||
@@ -104,7 +104,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
shrinkWrap: true,
|
||||
itemBuilder: ((context, index) {
|
||||
return ListTile(
|
||||
leading: _buildTileIcon(users[index]),
|
||||
leading: buildTileIcon(users[index]),
|
||||
title: Text(
|
||||
users[index].email,
|
||||
style: const TextStyle(
|
||||
@@ -153,8 +153,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
onPressed:
|
||||
sharedUsersList.value.isEmpty ? null : _createSharedAlbum,
|
||||
onPressed: sharedUsersList.value.isEmpty ? null : createSharedAlbum,
|
||||
child: const Text(
|
||||
"share_create_album",
|
||||
style: TextStyle(
|
||||
@@ -168,7 +167,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
|
||||
),
|
||||
body: suggestedShareUsers.when(
|
||||
data: (users) {
|
||||
return _buildUserList(users);
|
||||
return buildUserList(users);
|
||||
},
|
||||
error: (e, _) => Text("Error loading suggested users $e"),
|
||||
loading: () => const Center(
|
||||
|
||||
@@ -28,7 +28,7 @@ class SharingPage extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
_buildAlbumList() {
|
||||
buildAlbumList() {
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
@@ -42,10 +42,9 @@ class SharingPage extends HookConsumerWidget {
|
||||
child: CachedNetworkImage(
|
||||
width: 60,
|
||||
height: 60,
|
||||
memCacheHeight: 200,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: getAlbumThumbnailUrl(album),
|
||||
cacheKey: album.albumThumbnailAssetId,
|
||||
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
@@ -71,7 +70,7 @@ class SharingPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildEmptyListIndication() {
|
||||
buildEmptyListIndication() {
|
||||
return SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
@@ -136,8 +135,8 @@ class SharingPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
sharedAlbums.isNotEmpty
|
||||
? _buildAlbumList()
|
||||
: _buildEmptyListIndication()
|
||||
? buildAlbumList()
|
||||
: buildEmptyListIndication()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -64,5 +64,7 @@ class ImageViewerStateNotifier extends StateNotifier<ImageViewerPageState> {
|
||||
final imageViewerStateProvider =
|
||||
StateNotifierProvider<ImageViewerStateNotifier, ImageViewerPageState>(
|
||||
((ref) => ImageViewerStateNotifier(
|
||||
ref.watch(imageViewerServiceProvider), ref.watch(shareServiceProvider))),
|
||||
ref.watch(imageViewerServiceProvider),
|
||||
ref.watch(shareServiceProvider),
|
||||
)),
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||
|
||||
class ExifBottomSheet extends ConsumerWidget {
|
||||
final Asset assetDetail;
|
||||
@@ -15,7 +16,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
_buildMap() {
|
||||
buildMap() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||
child: Container(
|
||||
@@ -66,7 +67,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
|
||||
ExifResponseDto? exifInfo = assetDetail.remote?.exifInfo;
|
||||
|
||||
_buildLocationText() {
|
||||
buildLocationText() {
|
||||
return Text(
|
||||
"${exifInfo?.city}, ${exifInfo?.state}",
|
||||
style: TextStyle(
|
||||
@@ -120,11 +121,11 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
).tr(),
|
||||
if (assetDetail.latitude != null &&
|
||||
assetDetail.longitude != null)
|
||||
_buildMap(),
|
||||
buildMap(),
|
||||
if (exifInfo != null &&
|
||||
exifInfo.city != null &&
|
||||
exifInfo.state != null)
|
||||
_buildLocationText(),
|
||||
buildLocationText(),
|
||||
Text(
|
||||
"${assetDetail.latitude?.toStringAsFixed(4)}, ${assetDetail.longitude?.toStringAsFixed(4)}",
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey[400]),
|
||||
@@ -162,7 +163,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
),
|
||||
subtitle: exifInfo.exifImageHeight != null
|
||||
? Text(
|
||||
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${exifInfo.fileSizeInByte!}B ",
|
||||
"${exifInfo.exifImageHeight} x ${exifInfo.exifImageWidth} ${formatBytes(exifInfo.fileSizeInByte!)} ",
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -178,7 +179,7 @@ class ExifBottomSheet extends ConsumerWidget {
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
subtitle: Text(
|
||||
"ƒ/${exifInfo.fNumber} 1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)} ${exifInfo.focalLength}mm ISO${exifInfo.iso} ",
|
||||
"ƒ/${exifInfo.fNumber} 1/${(1 / (exifInfo.exposureTime ?? 1)).toStringAsFixed(0)} ${exifInfo.focalLength} mm ISO${exifInfo.iso} ",
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -20,10 +20,10 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool allowMoving = _status == _RemoteImageStatus.full;
|
||||
final bool forbidZoom = _status == _RemoteImageStatus.thumbnail;
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: !allowMoving,
|
||||
ignoring: forbidZoom,
|
||||
child: Listener(
|
||||
onPointerMove: handleSwipUpDown,
|
||||
child: PhotoView(
|
||||
@@ -115,7 +115,7 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
|
||||
_thumbnailProvider = _authorizedImageProvider(
|
||||
getThumbnailUrl(widget.asset.remote!),
|
||||
widget.asset.id,
|
||||
getThumbnailCacheKey(widget.asset.remote!),
|
||||
);
|
||||
_imageProvider = _thumbnailProvider;
|
||||
|
||||
@@ -128,10 +128,10 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
}),
|
||||
);
|
||||
|
||||
if (widget.threeStageLoading) {
|
||||
if (widget.loadPreview) {
|
||||
_previewProvider = _authorizedImageProvider(
|
||||
getThumbnailUrl(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
||||
"${widget.asset.id}_previewStage",
|
||||
getThumbnailCacheKey(widget.asset.remote!, type: ThumbnailFormat.JPEG),
|
||||
);
|
||||
_previewProvider.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||
@@ -140,15 +140,17 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
||||
);
|
||||
}
|
||||
|
||||
_fullProvider = _authorizedImageProvider(
|
||||
getImageUrl(widget.asset.remote!),
|
||||
"${widget.asset.id}_fullStage",
|
||||
);
|
||||
_fullProvider.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||
_performStateTransition(_RemoteImageStatus.full, _fullProvider);
|
||||
}),
|
||||
);
|
||||
if (widget.loadOriginal) {
|
||||
_fullProvider = _authorizedImageProvider(
|
||||
getImageUrl(widget.asset.remote!),
|
||||
getImageCacheKey(widget.asset.remote!),
|
||||
);
|
||||
_fullProvider.resolve(const ImageConfiguration()).addListener(
|
||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||
_performStateTransition(_RemoteImageStatus.full, _fullProvider);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -178,7 +180,8 @@ class RemotePhotoView extends StatefulWidget {
|
||||
Key? key,
|
||||
required this.asset,
|
||||
required this.authToken,
|
||||
required this.threeStageLoading,
|
||||
required this.loadPreview,
|
||||
required this.loadOriginal,
|
||||
required this.isZoomedFunction,
|
||||
required this.isZoomedListener,
|
||||
required this.onSwipeDown,
|
||||
@@ -187,7 +190,8 @@ class RemotePhotoView extends StatefulWidget {
|
||||
|
||||
final Asset asset;
|
||||
final String authToken;
|
||||
final bool threeStageLoading;
|
||||
final bool loadPreview;
|
||||
final bool loadOriginal;
|
||||
final void Function() onSwipeDown;
|
||||
final void Function() onSwipeUp;
|
||||
final void Function() isZoomedFunction;
|
||||
|
||||
@@ -31,8 +31,9 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final Box<dynamic> box = Hive.box(userInfoBox);
|
||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||
final threeStageLoading = useState(false);
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final isLoadPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||
final isLoadOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||
final isZoomed = useState<bool>(false);
|
||||
final indexOfAsset = useState(assetList.indexOf(asset));
|
||||
final isPlayingMotionVideo = useState(false);
|
||||
@@ -43,8 +44,10 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
threeStageLoading.value = appSettingService
|
||||
.getSetting<bool>(AppSettingsEnum.threeStageLoading);
|
||||
isLoadPreview.value =
|
||||
settings.getSetting<bool>(AppSettingsEnum.loadPreview);
|
||||
isLoadOriginal.value =
|
||||
settings.getSetting<bool>(AppSettingsEnum.loadOriginal);
|
||||
isPlayingMotionVideo.value = false;
|
||||
return null;
|
||||
},
|
||||
@@ -140,7 +143,8 @@ class GalleryViewerPage extends HookConsumerWidget {
|
||||
isZoomedListener: isZoomedListener,
|
||||
asset: assetList[index],
|
||||
heroTag: assetList[index].id,
|
||||
threeStageLoading: threeStageLoading.value,
|
||||
loadPreview: isLoadPreview.value,
|
||||
loadOriginal: isLoadOriginal.value,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -17,7 +17,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
||||
final String authToken;
|
||||
final ValueNotifier<bool> isZoomedListener;
|
||||
final void Function() isZoomedFunction;
|
||||
final bool threeStageLoading;
|
||||
final bool loadPreview;
|
||||
final bool loadOriginal;
|
||||
|
||||
ImageViewerPage({
|
||||
Key? key,
|
||||
@@ -26,7 +27,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
||||
required this.authToken,
|
||||
required this.isZoomedFunction,
|
||||
required this.isZoomedListener,
|
||||
required this.threeStageLoading,
|
||||
required this.loadPreview,
|
||||
required this.loadOriginal,
|
||||
}) : super(key: key);
|
||||
|
||||
Asset? assetDetail;
|
||||
@@ -74,7 +76,8 @@ class ImageViewerPage extends HookConsumerWidget {
|
||||
child: RemotePhotoView(
|
||||
asset: asset,
|
||||
authToken: authToken,
|
||||
threeStageLoading: threeStageLoading,
|
||||
loadPreview: loadPreview,
|
||||
loadOriginal: loadOriginal,
|
||||
isZoomedFunction: isZoomedFunction,
|
||||
isZoomedListener: isZoomedListener,
|
||||
onSwipeDown: () => AutoRouter.of(context).pop(),
|
||||
|
||||
@@ -86,6 +86,8 @@ class BackgroundService {
|
||||
Future<bool> configureService({
|
||||
bool requireUnmetered = true,
|
||||
bool requireCharging = false,
|
||||
int triggerUpdateDelay = 5000,
|
||||
int triggerMaxDelay = 50000,
|
||||
}) async {
|
||||
if (!Platform.isAndroid) {
|
||||
return true;
|
||||
@@ -93,7 +95,12 @@ class BackgroundService {
|
||||
try {
|
||||
final bool ok = await _foregroundChannel.invokeMethod(
|
||||
'configure',
|
||||
[requireUnmetered, requireCharging],
|
||||
[
|
||||
requireUnmetered,
|
||||
requireCharging,
|
||||
triggerUpdateDelay,
|
||||
triggerMaxDelay
|
||||
],
|
||||
);
|
||||
return ok;
|
||||
} catch (error) {
|
||||
@@ -349,7 +356,6 @@ class BackgroundService {
|
||||
Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
|
||||
Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
|
||||
]);
|
||||
|
||||
ApiService apiService = ApiService();
|
||||
apiService.setEndpoint(Hive.box(userInfoBox).get(serverEndpointKey));
|
||||
apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey));
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// ignore_for_file: implementation_imports
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:easy_localization/src/asset_loader.dart';
|
||||
import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||
|
||||
@@ -18,6 +18,7 @@ class BackUpState {
|
||||
final bool backgroundBackup;
|
||||
final bool backupRequireWifi;
|
||||
final bool backupRequireCharging;
|
||||
final int backupTriggerDelay;
|
||||
|
||||
/// All available albums on the device
|
||||
final List<AvailableAlbum> availableAlbums;
|
||||
@@ -42,6 +43,7 @@ class BackUpState {
|
||||
required this.backgroundBackup,
|
||||
required this.backupRequireWifi,
|
||||
required this.backupRequireCharging,
|
||||
required this.backupTriggerDelay,
|
||||
required this.availableAlbums,
|
||||
required this.selectedBackupAlbums,
|
||||
required this.excludedBackupAlbums,
|
||||
@@ -59,6 +61,7 @@ class BackUpState {
|
||||
bool? backgroundBackup,
|
||||
bool? backupRequireWifi,
|
||||
bool? backupRequireCharging,
|
||||
int? backupTriggerDelay,
|
||||
List<AvailableAlbum>? availableAlbums,
|
||||
Set<AvailableAlbum>? selectedBackupAlbums,
|
||||
Set<AvailableAlbum>? excludedBackupAlbums,
|
||||
@@ -76,6 +79,7 @@ class BackUpState {
|
||||
backupRequireWifi: backupRequireWifi ?? this.backupRequireWifi,
|
||||
backupRequireCharging:
|
||||
backupRequireCharging ?? this.backupRequireCharging,
|
||||
backupTriggerDelay: backupTriggerDelay ?? this.backupTriggerDelay,
|
||||
availableAlbums: availableAlbums ?? this.availableAlbums,
|
||||
selectedBackupAlbums: selectedBackupAlbums ?? this.selectedBackupAlbums,
|
||||
excludedBackupAlbums: excludedBackupAlbums ?? this.excludedBackupAlbums,
|
||||
@@ -88,7 +92,7 @@ class BackUpState {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
return 'BackUpState(backupProgress: $backupProgress, allAssetsInDatabase: $allAssetsInDatabase, progressInPercentage: $progressInPercentage, cancelToken: $cancelToken, serverInfo: $serverInfo, backgroundBackup: $backgroundBackup, backupRequireWifi: $backupRequireWifi, backupRequireCharging: $backupRequireCharging, backupTriggerDelay: $backupTriggerDelay, availableAlbums: $availableAlbums, selectedBackupAlbums: $selectedBackupAlbums, excludedBackupAlbums: $excludedBackupAlbums, allUniqueAssets: $allUniqueAssets, selectedAlbumsBackupAssetsIds: $selectedAlbumsBackupAssetsIds, currentUploadAsset: $currentUploadAsset)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -105,6 +109,7 @@ class BackUpState {
|
||||
other.backgroundBackup == backgroundBackup &&
|
||||
other.backupRequireWifi == backupRequireWifi &&
|
||||
other.backupRequireCharging == backupRequireCharging &&
|
||||
other.backupTriggerDelay == backupTriggerDelay &&
|
||||
collectionEquals(other.availableAlbums, availableAlbums) &&
|
||||
collectionEquals(other.selectedBackupAlbums, selectedBackupAlbums) &&
|
||||
collectionEquals(other.excludedBackupAlbums, excludedBackupAlbums) &&
|
||||
@@ -126,6 +131,7 @@ class BackUpState {
|
||||
backgroundBackup.hashCode ^
|
||||
backupRequireWifi.hashCode ^
|
||||
backupRequireCharging.hashCode ^
|
||||
backupTriggerDelay.hashCode ^
|
||||
availableAlbums.hashCode ^
|
||||
selectedBackupAlbums.hashCode ^
|
||||
excludedBackupAlbums.hashCode ^
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cancellation_token_http/http.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
@@ -18,6 +18,7 @@ import 'package:immich_mobile/modules/login/models/authentication_state.model.da
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/server_info.service.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
@@ -37,6 +38,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
backgroundBackup: false,
|
||||
backupRequireWifi: true,
|
||||
backupRequireCharging: false,
|
||||
backupTriggerDelay: 5000,
|
||||
serverInfo: ServerInfoResponseDto(
|
||||
diskAvailable: "0",
|
||||
diskAvailableRaw: 0,
|
||||
@@ -62,6 +64,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
getBackupInfo();
|
||||
}
|
||||
|
||||
final log = Logger('BackupNotifier');
|
||||
final BackupService _backupService;
|
||||
final ServerInfoService _serverInfoService;
|
||||
final AuthenticationState _authState;
|
||||
@@ -117,18 +120,26 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
bool? enabled,
|
||||
bool? requireWifi,
|
||||
bool? requireCharging,
|
||||
int? triggerDelay,
|
||||
required void Function(String msg) onError,
|
||||
required void Function() onBatteryInfo,
|
||||
}) async {
|
||||
assert(enabled != null || requireWifi != null || requireCharging != null);
|
||||
assert(
|
||||
enabled != null ||
|
||||
requireWifi != null ||
|
||||
requireCharging != null ||
|
||||
triggerDelay != null,
|
||||
);
|
||||
if (Platform.isAndroid) {
|
||||
final bool wasEnabled = state.backgroundBackup;
|
||||
final bool wasWifi = state.backupRequireWifi;
|
||||
final bool wasCharing = state.backupRequireCharging;
|
||||
final bool wasCharging = state.backupRequireCharging;
|
||||
final int oldTriggerDelay = state.backupTriggerDelay;
|
||||
state = state.copyWith(
|
||||
backgroundBackup: enabled,
|
||||
backupRequireWifi: requireWifi,
|
||||
backupRequireCharging: requireCharging,
|
||||
backupTriggerDelay: triggerDelay,
|
||||
);
|
||||
|
||||
if (state.backgroundBackup) {
|
||||
@@ -143,17 +154,22 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await _backgroundService.configureService(
|
||||
requireUnmetered: state.backupRequireWifi,
|
||||
requireCharging: state.backupRequireCharging,
|
||||
triggerUpdateDelay: state.backupTriggerDelay,
|
||||
triggerMaxDelay: state.backupTriggerDelay * 10,
|
||||
);
|
||||
if (success) {
|
||||
await Hive.box(backgroundBackupInfoBox)
|
||||
.put(backupRequireWifi, state.backupRequireWifi);
|
||||
await Hive.box(backgroundBackupInfoBox)
|
||||
.put(backupRequireCharging, state.backupRequireCharging);
|
||||
final box = Hive.box(backgroundBackupInfoBox);
|
||||
await Future.wait([
|
||||
box.put(backupRequireWifi, state.backupRequireWifi),
|
||||
box.put(backupRequireCharging, state.backupRequireCharging),
|
||||
box.put(backupTriggerDelay, state.backupTriggerDelay),
|
||||
]);
|
||||
} else {
|
||||
state = state.copyWith(
|
||||
backgroundBackup: wasEnabled,
|
||||
backupRequireWifi: wasWifi,
|
||||
backupRequireCharging: wasCharing,
|
||||
backupRequireCharging: wasCharging,
|
||||
backupTriggerDelay: oldTriggerDelay,
|
||||
);
|
||||
onError("backup_controller_page_background_configure_error");
|
||||
}
|
||||
@@ -171,9 +187,10 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
/// Get all album on the device
|
||||
/// Get all selected and excluded album from the user's persistent storage
|
||||
/// If this is the first time performing backup - set the default selected album to be
|
||||
/// the one that has all assets (Recent on Android, Recents on iOS)
|
||||
/// the one that has all assets (`Recent` on Android, `Recents` on iOS)
|
||||
///
|
||||
Future<void> _getBackupAlbumsInfo() async {
|
||||
Stopwatch stopwatch = Stopwatch()..start();
|
||||
// Get all albums on the device
|
||||
List<AvailableAlbum> availableAlbums = [];
|
||||
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
||||
@@ -181,6 +198,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
type: RequestType.common,
|
||||
);
|
||||
|
||||
log.info('Found ${albums.length} local albums');
|
||||
|
||||
for (AssetPathEntity album in albums) {
|
||||
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
||||
|
||||
@@ -218,13 +237,16 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
|
||||
if (backupAlbumInfo == null) {
|
||||
debugPrint("[ERROR] getting Hive backup album infomation");
|
||||
log.severe(
|
||||
"backupAlbumInfo == null",
|
||||
"Failed to get Hive backup album information",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// First time backup - set isAll album is the default one for backup.
|
||||
if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
|
||||
debugPrint("First time backup setup recent album as default");
|
||||
log.info("First time backup; setup 'Recent(s)' album as default");
|
||||
|
||||
// Get album that contains all assets
|
||||
var list = await PhotoManager.getAssetPathList(
|
||||
@@ -286,9 +308,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
selectedBackupAlbums: selectedAlbums,
|
||||
excludedBackupAlbums: excludedAlbums,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint("[ERROR] Failed to generate album from id $e");
|
||||
} catch (e, stackTrace) {
|
||||
log.severe("Failed to generate album from id", e, stackTrace);
|
||||
}
|
||||
|
||||
debugPrint("_getBackupAlbumsInfo takes ${stopwatch.elapsedMilliseconds}ms");
|
||||
}
|
||||
|
||||
///
|
||||
@@ -338,7 +362,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
|
||||
if (allUniqueAssets.isEmpty) {
|
||||
debugPrint("No Asset On Device");
|
||||
log.info("Not found albums or assets on the device to backup");
|
||||
state = state.copyWith(
|
||||
backupProgress: BackUpProgressEnum.idle,
|
||||
allAssetsInDatabase: allAssetsInDatabase,
|
||||
@@ -360,14 +384,14 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
return;
|
||||
}
|
||||
|
||||
///
|
||||
/// Get all necessary information for calculating the available albums,
|
||||
/// which albums are selected or excluded
|
||||
/// and then update the UI according to those information
|
||||
///
|
||||
Future<void> getBackupInfo() async {
|
||||
final bool isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
||||
var isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
||||
|
||||
state = state.copyWith(backgroundBackup: isEnabled);
|
||||
|
||||
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
||||
await _getBackupAlbumsInfo();
|
||||
await _updateServerInfo();
|
||||
@@ -375,10 +399,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Save user selection of selected albums and excluded albums to
|
||||
/// Hive database
|
||||
///
|
||||
void _updatePersistentAlbumsSelection() {
|
||||
final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
||||
Box<HiveBackupAlbums> backupAlbumInfoBox =
|
||||
@@ -398,10 +420,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Invoke backup process
|
||||
///
|
||||
Future<void> startBackupProcess() async {
|
||||
debugPrint("Start backup process");
|
||||
assert(state.backupProgress == BackUpProgressEnum.idle);
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
||||
|
||||
@@ -412,13 +433,12 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await PhotoManager.clearFileCache();
|
||||
|
||||
if (state.allUniqueAssets.isEmpty) {
|
||||
debugPrint("No Asset On Device - Abort Backup Process");
|
||||
log.info("No Asset On Device - Abort Backup Process");
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
||||
return;
|
||||
}
|
||||
|
||||
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
||||
|
||||
// Remove item that has already been backed up
|
||||
for (var assetId in state.allAssetsInDatabase) {
|
||||
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
||||
@@ -530,7 +550,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
|
||||
// User has been logged out return
|
||||
if (accessKey == null || !_authState.isAuthenticated) {
|
||||
debugPrint("[resumeBackup] not authenticated - abort");
|
||||
log.info("[_resumeBackup] not authenticated - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -539,17 +559,17 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
_authState.deviceInfo.isAutoBackup) {
|
||||
// check if backup is alreayd in process - then return
|
||||
if (state.backupProgress == BackUpProgressEnum.inProgress) {
|
||||
debugPrint("[resumeBackup] Backup is already in progress - abort");
|
||||
log.info("[_resumeBackup] Backup is already in progress - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.backupProgress == BackUpProgressEnum.inBackground) {
|
||||
debugPrint("[resumeBackup] Background backup is running - abort");
|
||||
log.info("[_resumeBackup] Background backup is running - abort");
|
||||
return;
|
||||
}
|
||||
|
||||
// Run backup
|
||||
debugPrint("[resumeBackup] Start back up");
|
||||
log.info("[_resumeBackup] Start back up");
|
||||
await startBackupProcess();
|
||||
}
|
||||
|
||||
@@ -565,7 +585,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground);
|
||||
final bool hasLock = await _backgroundService.acquireLock();
|
||||
if (!hasLock) {
|
||||
debugPrint("WARNING [resumeBackup] failed to acquireLock");
|
||||
log.warning("WARNING [resumeBackup] failed to acquireLock");
|
||||
return;
|
||||
}
|
||||
await Future.wait([
|
||||
@@ -596,6 +616,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
excludedBackupAlbums: excludedAlbums,
|
||||
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
||||
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
||||
backupTriggerDelay: backgroundBox.get(backupTriggerDelay),
|
||||
);
|
||||
}
|
||||
return _resumeBackup();
|
||||
@@ -612,7 +633,11 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]);
|
||||
result.add(a.copyWith(lastBackup: times[i]));
|
||||
} on StateError {
|
||||
debugPrint("[_updateAlbumBackupTime] failed to find album in state");
|
||||
log.severe(
|
||||
"[_updateAlbumBackupTime] failed to find album in state",
|
||||
"State Error",
|
||||
StackTrace.current,
|
||||
);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -631,21 +656,29 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
||||
await Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).close();
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
log.info("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
}
|
||||
try {
|
||||
if (Hive.isBoxOpen(duplicatedAssetsBox)) {
|
||||
await Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox).close();
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
} catch (error, stackTrace) {
|
||||
log.severe(
|
||||
"[_notifyBackgroundServiceCanRun] failed to close box",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
|
||||
await Hive.box(backgroundBackupInfoBox).close();
|
||||
}
|
||||
} catch (error) {
|
||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||
} catch (error, stackTrace) {
|
||||
log.severe(
|
||||
"[_notifyBackgroundServiceCanRun] failed to close box",
|
||||
error,
|
||||
stackTrace,
|
||||
);
|
||||
}
|
||||
_backgroundService.releaseLock();
|
||||
}
|
||||
|
||||
@@ -376,8 +376,8 @@ class BackupService {
|
||||
DeviceTypeEnum deviceType,
|
||||
) async {
|
||||
try {
|
||||
var updatedDeviceInfo = await _apiService.deviceInfoApi.updateDeviceInfo(
|
||||
UpdateDeviceInfoDto(
|
||||
var updatedDeviceInfo = await _apiService.deviceInfoApi.upsertDeviceInfo(
|
||||
UpsertDeviceInfoDto(
|
||||
deviceId: deviceId,
|
||||
deviceType: deviceType,
|
||||
isAutoBackup: status,
|
||||
|
||||
@@ -33,7 +33,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
ColorFilter unselectedFilter =
|
||||
const ColorFilter.mode(Colors.black, BlendMode.color);
|
||||
|
||||
_buildSelectedTextBox() {
|
||||
buildSelectedTextBox() {
|
||||
if (isSelected) {
|
||||
return Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
@@ -67,7 +67,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
_buildImageFilter() {
|
||||
buildImageFilter() {
|
||||
if (isSelected) {
|
||||
return selectedFilter;
|
||||
} else if (isExcluded) {
|
||||
@@ -163,7 +163,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
topRight: Radius.circular(12),
|
||||
),
|
||||
image: DecorationImage(
|
||||
colorFilter: _buildImageFilter(),
|
||||
colorFilter: buildImageFilter(),
|
||||
image: imageData != null
|
||||
? MemoryImage(imageData!)
|
||||
: const AssetImage(
|
||||
@@ -177,7 +177,7 @@ class AlbumInfoCard extends HookConsumerWidget {
|
||||
Positioned(
|
||||
bottom: 10,
|
||||
left: 25,
|
||||
child: _buildSelectedTextBox(),
|
||||
child: buildSelectedTextBox(),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@@ -15,14 +15,16 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final assets = useState<List<AssetEntity>>([]);
|
||||
|
||||
_getAssetsInAlbum() async {
|
||||
getAssetsInAlbum() async {
|
||||
assets.value = await album.getAssetListRange(
|
||||
start: 0, end: await album.assetCountAsync);
|
||||
start: 0,
|
||||
end: await album.assetCountAsync,
|
||||
);
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
_getAssetsInAlbum();
|
||||
getAssetsInAlbum();
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
@@ -34,7 +36,7 @@ class AlbumPreviewPage extends HookConsumerWidget {
|
||||
title: Column(
|
||||
children: [
|
||||
Text(
|
||||
"${album.name} (${album.assetCountAsync})",
|
||||
album.name,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
|
||||
),
|
||||
Padding(
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/immich_colors.dart';
|
||||
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
@@ -14,10 +15,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
const BackupAlbumSelectionPage({Key? key}) : super(key: key);
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final availableAlbums = ref.watch(backupProvider).availableAlbums;
|
||||
// final availableAlbums = ref.watch(backupProvider).availableAlbums;
|
||||
final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
|
||||
final albums = useState<List<AvailableAlbum>>(
|
||||
ref.watch(backupProvider).availableAlbums,
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
@@ -27,8 +31,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
_buildAlbumSelectionList() {
|
||||
if (availableAlbums.isEmpty) {
|
||||
buildAlbumSelectionList() {
|
||||
if (albums.value.isEmpty) {
|
||||
return const Center(
|
||||
child: ImmichLoadingIndicator(),
|
||||
);
|
||||
@@ -38,17 +42,17 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
height: 265,
|
||||
child: ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: availableAlbums.length,
|
||||
itemCount: albums.value.length,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemBuilder: ((context, index) {
|
||||
var thumbnailData = availableAlbums[index].thumbnailData;
|
||||
var thumbnailData = albums.value[index].thumbnailData;
|
||||
return Padding(
|
||||
padding: index == 0
|
||||
? const EdgeInsets.only(left: 16.00)
|
||||
: const EdgeInsets.all(0),
|
||||
child: AlbumInfoCard(
|
||||
imageData: thumbnailData,
|
||||
albumInfo: availableAlbums[index],
|
||||
albumInfo: albums.value[index],
|
||||
),
|
||||
);
|
||||
}),
|
||||
@@ -56,7 +60,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildSelectedAlbumNameChip() {
|
||||
buildSelectedAlbumNameChip() {
|
||||
return selectedBackupAlbums.map((album) {
|
||||
void removeSelection() {
|
||||
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
|
||||
@@ -79,15 +83,13 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
label: Text(
|
||||
album.name,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
color: isDarkTheme ? Colors.black : Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
@@ -104,7 +106,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
_buildExcludedAlbumNameChip() {
|
||||
buildExcludedAlbumNameChip() {
|
||||
return excludedBackupAlbums.map((album) {
|
||||
void removeSelection() {
|
||||
ref
|
||||
@@ -119,7 +121,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Chip(
|
||||
visualDensity: VisualDensity.compact,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
label: Text(
|
||||
album.name,
|
||||
@@ -143,6 +145,46 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
}).toSet();
|
||||
}
|
||||
|
||||
buildSearchBar() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
|
||||
child: TextFormField(
|
||||
onChanged: (searchValue) {
|
||||
albums.value = ref
|
||||
.watch(backupProvider)
|
||||
.availableAlbums
|
||||
.where(
|
||||
(album) => album.name
|
||||
.toLowerCase()
|
||||
.contains(searchValue.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
},
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 8.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
hintText: "Search",
|
||||
hintStyle: TextStyle(
|
||||
color: isDarkTheme ? Colors.white : Colors.grey,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
prefixIcon: const Icon(
|
||||
Icons.search,
|
||||
color: Colors.grey,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: isDarkTheme ? Colors.white30 : Colors.grey[200],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
@@ -177,8 +219,8 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Wrap(
|
||||
children: [
|
||||
..._buildSelectedAlbumNameChip(),
|
||||
..._buildExcludedAlbumNameChip()
|
||||
...buildSelectedAlbumNameChip(),
|
||||
...buildExcludedAlbumNameChip()
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -188,7 +230,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
child: Card(
|
||||
margin: const EdgeInsets.all(0),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
side: BorderSide(
|
||||
color: isDarkTheme
|
||||
? const Color.fromARGB(255, 0, 0, 0)
|
||||
@@ -225,8 +267,11 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
|
||||
ListTile(
|
||||
title: Text(
|
||||
"backup_album_selection_page_albums_device"
|
||||
.tr(args: [availableAlbums.length.toString()]),
|
||||
"backup_album_selection_page_albums_device".tr(
|
||||
args: [
|
||||
ref.watch(backupProvider).availableAlbums.length.toString()
|
||||
],
|
||||
),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
),
|
||||
subtitle: Padding(
|
||||
@@ -254,7 +299,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
elevation: 5,
|
||||
title: Text(
|
||||
@@ -284,9 +329,11 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
|
||||
buildSearchBar(),
|
||||
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: _buildAlbumSelectionList(),
|
||||
child: buildAlbumSelectionList(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -45,7 +45,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
Widget _buildStorageInformation() {
|
||||
Widget buildStorageInformation() {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.storage_rounded,
|
||||
@@ -84,7 +84,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
ListTile _buildAutoBackupController() {
|
||||
ListTile buildAutoBackupController() {
|
||||
var backUpOption = authenticationState.deviceInfo.isAutoBackup
|
||||
? "backup_controller_page_status_on".tr()
|
||||
: "backup_controller_page_status_off".tr();
|
||||
@@ -143,7 +143,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _showErrorToUser(String msg) {
|
||||
void showErrorToUser(String msg) {
|
||||
final snackBar = SnackBar(
|
||||
content: Text(
|
||||
msg.tr(),
|
||||
@@ -153,7 +153,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
|
||||
void _showBatteryOptimizationInfoToUser() {
|
||||
void showBatteryOptimizationInfoToUser() {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
@@ -193,11 +193,51 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
ListTile _buildBackgroundBackupController() {
|
||||
ListTile buildBackgroundBackupController() {
|
||||
final bool isBackgroundEnabled = backupState.backgroundBackup;
|
||||
final bool isWifiRequired = backupState.backupRequireWifi;
|
||||
final bool isChargingRequired = backupState.backupRequireCharging;
|
||||
final Color activeColor = Theme.of(context).primaryColor;
|
||||
|
||||
String formatBackupDelaySliderValue(double v) {
|
||||
if (v == 0.0) {
|
||||
return 'setting_notifications_notify_seconds'.tr(args: const ['5']);
|
||||
} else if (v == 1.0) {
|
||||
return 'setting_notifications_notify_seconds'.tr(args: const ['30']);
|
||||
} else if (v == 2.0) {
|
||||
return 'setting_notifications_notify_minutes'.tr(args: const ['2']);
|
||||
} else {
|
||||
return 'setting_notifications_notify_minutes'.tr(args: const ['10']);
|
||||
}
|
||||
}
|
||||
|
||||
int backupDelayToMilliseconds(double v) {
|
||||
if (v == 0.0) {
|
||||
return 5000;
|
||||
} else if (v == 1.0) {
|
||||
return 30000;
|
||||
} else if (v == 2.0) {
|
||||
return 120000;
|
||||
} else {
|
||||
return 600000;
|
||||
}
|
||||
}
|
||||
|
||||
double backupDelayToSliderValue(int ms) {
|
||||
if (ms == 5000) {
|
||||
return 0.0;
|
||||
} else if (ms == 30000) {
|
||||
return 1.0;
|
||||
} else if (ms == 120000) {
|
||||
return 2.0;
|
||||
} else {
|
||||
return 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
final triggerDelay =
|
||||
useState(backupDelayToSliderValue(backupState.backupTriggerDelay));
|
||||
|
||||
return ListTile(
|
||||
isThreeLine: true,
|
||||
leading: isBackgroundEnabled
|
||||
@@ -238,8 +278,8 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(
|
||||
requireWifi: isChecked,
|
||||
onError: _showErrorToUser,
|
||||
onBatteryInfo: _showBatteryOptimizationInfoToUser,
|
||||
onError: showErrorToUser,
|
||||
onBatteryInfo: showBatteryOptimizationInfoToUser,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
@@ -259,17 +299,46 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(
|
||||
requireCharging: isChecked,
|
||||
onError: _showErrorToUser,
|
||||
onBatteryInfo: _showBatteryOptimizationInfoToUser,
|
||||
onError: showErrorToUser,
|
||||
onBatteryInfo: showBatteryOptimizationInfoToUser,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (isBackgroundEnabled)
|
||||
ListTile(
|
||||
isThreeLine: false,
|
||||
dense: true,
|
||||
enabled: hasExclusiveAccess,
|
||||
title: const Text(
|
||||
'backup_controller_page_background_delay',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
).tr(args: [formatBackupDelaySliderValue(triggerDelay.value)]),
|
||||
subtitle: Slider(
|
||||
value: triggerDelay.value,
|
||||
onChanged: hasExclusiveAccess
|
||||
? (double v) => triggerDelay.value = v
|
||||
: null,
|
||||
onChangeEnd: (double v) => ref
|
||||
.read(backupProvider.notifier)
|
||||
.configureBackgroundBackup(
|
||||
triggerDelay: backupDelayToMilliseconds(v),
|
||||
onError: showErrorToUser,
|
||||
onBatteryInfo: showBatteryOptimizationInfoToUser,
|
||||
),
|
||||
max: 3.0,
|
||||
divisions: 3,
|
||||
label: formatBackupDelaySliderValue(triggerDelay.value),
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () =>
|
||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
||||
enabled: !isBackgroundEnabled,
|
||||
onError: _showErrorToUser,
|
||||
onBatteryInfo: _showBatteryOptimizationInfoToUser,
|
||||
onError: showErrorToUser,
|
||||
onBatteryInfo: showBatteryOptimizationInfoToUser,
|
||||
),
|
||||
child: Text(
|
||||
isBackgroundEnabled
|
||||
@@ -284,7 +353,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSelectedAlbumName() {
|
||||
Widget buildSelectedAlbumName() {
|
||||
var text = "backup_controller_page_backup_selected".tr();
|
||||
var albums = ref.watch(backupProvider).selectedBackupAlbums;
|
||||
|
||||
@@ -323,7 +392,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildExcludedAlbumName() {
|
||||
Widget buildExcludedAlbumName() {
|
||||
var text = "backup_controller_page_excluded".tr();
|
||||
var albums = ref.watch(backupProvider).excludedBackupAlbums;
|
||||
|
||||
@@ -348,7 +417,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
_buildFolderSelectionTile() {
|
||||
buildFolderSelectionTile() {
|
||||
return Card(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(5), // if you need this
|
||||
@@ -374,8 +443,8 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
"backup_controller_page_to_backup",
|
||||
style: TextStyle(fontSize: 12),
|
||||
).tr(),
|
||||
_buildSelectedAlbumName(),
|
||||
_buildExcludedAlbumName()
|
||||
buildSelectedAlbumName(),
|
||||
buildExcludedAlbumName()
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -398,7 +467,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildCurrentBackupAssetInfoCard() {
|
||||
buildCurrentBackupAssetInfoCard() {
|
||||
return ListTile(
|
||||
leading: Icon(
|
||||
Icons.info_outline_rounded,
|
||||
@@ -606,7 +675,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildFolderSelectionTile(),
|
||||
buildFolderSelectionTile(),
|
||||
BackupInfoCard(
|
||||
title: "backup_controller_page_total".tr(),
|
||||
subtitle: "backup_controller_page_total_sub".tr(),
|
||||
@@ -624,13 +693,13 @@ class BackupControllerPage extends HookConsumerWidget {
|
||||
"${backupState.allUniqueAssets.length - backupState.selectedAlbumsBackupAssetsIds.length}",
|
||||
),
|
||||
const Divider(),
|
||||
_buildAutoBackupController(),
|
||||
buildAutoBackupController(),
|
||||
if (Platform.isAndroid) const Divider(),
|
||||
if (Platform.isAndroid) _buildBackgroundBackupController(),
|
||||
if (Platform.isAndroid) buildBackgroundBackupController(),
|
||||
const Divider(),
|
||||
_buildStorageInformation(),
|
||||
buildStorageInformation(),
|
||||
const Divider(),
|
||||
_buildCurrentBackupAssetInfoCard(),
|
||||
buildCurrentBackupAssetInfoCard(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 24,
|
||||
|
||||
@@ -10,8 +10,10 @@ import 'package:immich_mobile/modules/backup/services/backup.service.dart';
|
||||
import 'package:immich_mobile/shared/models/asset.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/api.service.dart';
|
||||
import 'package:immich_mobile/utils/openapi_extensions.dart';
|
||||
import 'package:immich_mobile/utils/tuple.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:photo_manager/src/types/entity.dart';
|
||||
|
||||
final assetServiceProvider = Provider(
|
||||
(ref) => AssetService(
|
||||
@@ -25,42 +27,31 @@ class AssetService {
|
||||
final ApiService _apiService;
|
||||
final BackupService _backupService;
|
||||
final BackgroundService _backgroundService;
|
||||
final log = Logger('AssetService');
|
||||
|
||||
AssetService(this._apiService, this._backupService, this._backgroundService);
|
||||
|
||||
/// Returns all local, remote assets in that order
|
||||
Future<List<Asset>> getAllAsset({bool urgent = false}) async {
|
||||
final List<Asset> assets = [];
|
||||
/// Returns `null` if the server state did not change, else list of assets
|
||||
Future<Pair<List<Asset>?, String?>> getRemoteAssets({String? etag}) async {
|
||||
try {
|
||||
// not using `await` here to fetch local & remote assets concurrently
|
||||
final Future<List<AssetResponseDto>?> remoteTask =
|
||||
_apiService.assetApi.getAllAssets();
|
||||
final Iterable<AssetEntity> newLocalAssets;
|
||||
final List<AssetEntity> localAssets = await _getLocalAssets(urgent);
|
||||
final List<AssetResponseDto> remoteAssets = await remoteTask ?? [];
|
||||
if (remoteAssets.isNotEmpty && localAssets.isNotEmpty) {
|
||||
final String deviceId = Hive.box(userInfoBox).get(deviceIdKey);
|
||||
final Set<String> existingIds = remoteAssets
|
||||
.where((e) => e.deviceId == deviceId)
|
||||
.map((e) => e.deviceAssetId)
|
||||
.toSet();
|
||||
newLocalAssets = localAssets.where((e) => !existingIds.contains(e.id));
|
||||
} else {
|
||||
newLocalAssets = localAssets;
|
||||
final Pair<List<AssetResponseDto>, String?>? remote =
|
||||
await _apiService.assetApi.getAllAssetsWithETag(eTag: etag);
|
||||
if (remote == null) {
|
||||
return const Pair(null, null);
|
||||
}
|
||||
|
||||
assets.addAll(newLocalAssets.map((e) => Asset.local(e)));
|
||||
// the order (first all local, then remote assets) is important!
|
||||
assets.addAll(remoteAssets.map((e) => Asset.remote(e)));
|
||||
} catch (e) {
|
||||
debugPrint("Error [getAllAsset] ${e.toString()}");
|
||||
return Pair(
|
||||
remote.first.map(Asset.remote).toList(growable: false),
|
||||
remote.second,
|
||||
);
|
||||
} catch (e, stack) {
|
||||
log.severe('Error while getting remote assets', e, stack);
|
||||
return const Pair(null, null);
|
||||
}
|
||||
return assets;
|
||||
}
|
||||
|
||||
/// if [urgent] is `true`, do not block by waiting on the background service
|
||||
/// to finish running. Returns an empty list instead after a timeout.
|
||||
Future<List<AssetEntity>> _getLocalAssets(bool urgent) async {
|
||||
/// to finish running. Returns `null` instead after a timeout.
|
||||
Future<List<Asset>?> getLocalAssets({bool urgent = false}) async {
|
||||
try {
|
||||
final Future<bool> hasAccess = urgent
|
||||
? _backgroundService.hasAccess
|
||||
@@ -71,15 +62,16 @@ class AssetService {
|
||||
}
|
||||
final box = await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
||||
final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey);
|
||||
|
||||
return backupAlbumInfo != null
|
||||
? await _backupService
|
||||
.buildUploadCandidates(backupAlbumInfo.deepCopy())
|
||||
: [];
|
||||
if (backupAlbumInfo != null) {
|
||||
return (await _backupService
|
||||
.buildUploadCandidates(backupAlbumInfo.deepCopy()))
|
||||
.map(Asset.local)
|
||||
.toList(growable: false);
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint("Error [_getLocalAssets] ${e.toString()}");
|
||||
return [];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<Asset?> getAssetById(String assetId) async {
|
||||
|
||||
@@ -75,14 +75,11 @@ class ControlBottomAppBar extends ConsumerWidget {
|
||||
width: 100,
|
||||
height: 100,
|
||||
fit: BoxFit.cover,
|
||||
imageUrl: getAlbumThumbnailUrl(
|
||||
album,
|
||||
type: ThumbnailFormat.JPEG,
|
||||
),
|
||||
imageUrl: getAlbumThumbnailUrl(album),
|
||||
httpHeaders: {
|
||||
"Authorization": "Bearer ${box.get(accessTokenKey)}"
|
||||
},
|
||||
cacheKey: "${album.albumThumbnailAssetId}",
|
||||
cacheKey: getAlbumThumbNailCacheKey(album),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
|
||||
@@ -32,7 +32,9 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
||||
snap: false,
|
||||
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(5)),
|
||||
borderRadius: BorderRadius.all(
|
||||
Radius.circular(5),
|
||||
),
|
||||
),
|
||||
leading: Builder(
|
||||
builder: (BuildContext context) {
|
||||
|
||||
@@ -2,12 +2,12 @@ import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/profile_drawer_header.dart';
|
||||
import 'package:immich_mobile/modules/home/ui/profile_drawer/server_info_box.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
|
||||
class ProfileDrawer extends HookConsumerWidget {
|
||||
@@ -15,7 +15,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
_buildSignoutButton() {
|
||||
buildSignoutButton() {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 0,
|
||||
leading: SizedBox(
|
||||
@@ -46,7 +46,7 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildSettingButton() {
|
||||
buildSettingButton() {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 0,
|
||||
leading: SizedBox(
|
||||
@@ -70,6 +70,30 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
buildAppLogButton() {
|
||||
return ListTile(
|
||||
horizontalTitleGap: 0,
|
||||
leading: SizedBox(
|
||||
height: double.infinity,
|
||||
child: Icon(
|
||||
Icons.assignment_outlined,
|
||||
color: Theme.of(context).textTheme.labelMedium?.color,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
"profile_drawer_app_logs",
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelLarge
|
||||
?.copyWith(fontWeight: FontWeight.bold),
|
||||
).tr(),
|
||||
onTap: () {
|
||||
AutoRouter.of(context).push(const AppLogRoute());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return Drawer(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
@@ -79,8 +103,9 @@ class ProfileDrawer extends HookConsumerWidget {
|
||||
padding: EdgeInsets.zero,
|
||||
children: [
|
||||
const ProfileDrawerHeader(),
|
||||
_buildSettingButton(),
|
||||
_buildSignoutButton(),
|
||||
buildSettingButton(),
|
||||
buildAppLogButton(),
|
||||
buildSignoutButton(),
|
||||
],
|
||||
),
|
||||
const ServerInfoBox()
|
||||
|
||||
@@ -25,7 +25,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||
var dummmy = Random().nextInt(1024);
|
||||
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
_buildUserProfileImage() {
|
||||
buildUserProfileImage() {
|
||||
if (authState.profileImagePath.isEmpty) {
|
||||
return const CircleAvatar(
|
||||
radius: 35,
|
||||
@@ -77,7 +77,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
_pickUserProfileImage() async {
|
||||
pickUserProfileImage() async {
|
||||
final XFile? image = await ImagePicker().pickImage(
|
||||
source: ImageSource.gallery,
|
||||
maxHeight: 1024,
|
||||
@@ -98,7 +98,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
_buildUserProfileImage();
|
||||
buildUserProfileImage();
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
@@ -129,12 +129,12 @@ class ProfileDrawerHeader extends HookConsumerWidget {
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
_buildUserProfileImage(),
|
||||
buildUserProfileImage(),
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
right: -5,
|
||||
child: GestureDetector(
|
||||
onTap: _pickUserProfileImage,
|
||||
onTap: pickUserProfileImage,
|
||||
child: Material(
|
||||
color: Colors.grey[100],
|
||||
elevation: 3,
|
||||
|
||||
@@ -17,7 +17,7 @@ class ServerInfoBox extends HookConsumerWidget {
|
||||
|
||||
final appInfo = useState({});
|
||||
|
||||
_getPackageInfo() async {
|
||||
getPackageInfo() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
appInfo.value = {
|
||||
@@ -28,7 +28,7 @@ class ServerInfoBox extends HookConsumerWidget {
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
_getPackageInfo();
|
||||
getPackageInfo();
|
||||
return null;
|
||||
},
|
||||
[],
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -20,6 +22,7 @@ import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||
import 'package:immich_mobile/shared/services/share.service.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||
import 'package:immich_mobile/shared/ui/immich_toast.dart';
|
||||
import 'package:openapi/api.dart';
|
||||
|
||||
@@ -37,6 +40,8 @@ class HomePage extends HookConsumerWidget {
|
||||
final albums = ref.watch(albumProvider);
|
||||
final albumService = ref.watch(albumServiceProvider);
|
||||
|
||||
final tipOneOpacity = useState(0.0);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
ref.read(websocketProvider.notifier).connect();
|
||||
@@ -146,6 +151,49 @@ class HomePage extends HookConsumerWidget {
|
||||
}
|
||||
}
|
||||
|
||||
buildLoadingIndicator() {
|
||||
Timer(const Duration(seconds: 2), () {
|
||||
tipOneOpacity.value = 1;
|
||||
});
|
||||
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const ImmichLoadingIndicator(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Text(
|
||||
'Building the timeline',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).primaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
opacity: tipOneOpacity.value,
|
||||
child: const SizedBox(
|
||||
width: 250,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: 8.0),
|
||||
child: Text(
|
||||
'If this is your first time using the app, please make sure to choose a backup album(s) so that the timeline can populate photos and videos in the album(s).',
|
||||
textAlign: TextAlign.justify,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return SafeArea(
|
||||
bottom: !multiselectEnabled.state,
|
||||
top: true,
|
||||
@@ -164,15 +212,17 @@ class HomePage extends HookConsumerWidget {
|
||||
top: selectionEnabledHook.value ? 0 : 60,
|
||||
bottom: 0.0,
|
||||
),
|
||||
child: ImmichAssetGrid(
|
||||
renderList: renderList,
|
||||
assetsPerRow:
|
||||
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
showStorageIndicator: appSettingService
|
||||
.getSetting(AppSettingsEnum.storageIndicator),
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
),
|
||||
child: ref.watch(assetProvider).isEmpty
|
||||
? buildLoadingIndicator()
|
||||
: ImmichAssetGrid(
|
||||
renderList: renderList,
|
||||
assetsPerRow: appSettingService
|
||||
.getSetting(AppSettingsEnum.tilesPerRow),
|
||||
showStorageIndicator: appSettingService
|
||||
.getSetting(AppSettingsEnum.storageIndicator),
|
||||
listener: selectionListener,
|
||||
selectionActive: selectionEnabledHook.value,
|
||||
),
|
||||
),
|
||||
if (selectionEnabledHook.value)
|
||||
ControlBottomAppBar(
|
||||
|
||||
@@ -90,6 +90,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
|
||||
return setSuccessLoginInfo(
|
||||
accessToken: loginResponse.accessToken,
|
||||
serverUrl: serverEndpoint,
|
||||
isSavedLoginInfo: isSavedLoginInfo,
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -100,11 +101,14 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
}
|
||||
|
||||
Future<bool> logout() async {
|
||||
Hive.box(userInfoBox).delete(accessTokenKey);
|
||||
state = state.copyWith(isAuthenticated: false);
|
||||
_assetCacheService.invalidate();
|
||||
_albumCacheService.invalidate();
|
||||
_sharedAlbumCacheService.invalidate();
|
||||
await Future.wait([
|
||||
Hive.box(userInfoBox).delete(accessTokenKey),
|
||||
Hive.box(userInfoBox).delete(assetEtagKey),
|
||||
_assetCacheService.invalidate(),
|
||||
_albumCacheService.invalidate(),
|
||||
_sharedAlbumCacheService.invalidate(),
|
||||
]);
|
||||
|
||||
// Remove login info from local storage
|
||||
var loginInfo =
|
||||
@@ -114,7 +118,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
loginInfo.password = "";
|
||||
loginInfo.isSaveLogin = false;
|
||||
|
||||
Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
||||
await Hive.box<HiveSavedLoginInfo>(hiveLoginInfoBox).put(
|
||||
savedLoginInfoKey,
|
||||
loginInfo,
|
||||
);
|
||||
@@ -159,16 +163,18 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
|
||||
Future<bool> setSuccessLoginInfo({
|
||||
required String accessToken,
|
||||
required String serverUrl,
|
||||
required bool isSavedLoginInfo,
|
||||
}) async {
|
||||
Hive.box(userInfoBox).put(accessTokenKey, accessToken);
|
||||
|
||||
_apiService.setAccessToken(accessToken);
|
||||
var userResponseDto = await _apiService.userApi.getMyUserInfo();
|
||||
|
||||
if (userResponseDto != null) {
|
||||
var userInfoHiveBox = await Hive.openBox(userInfoBox);
|
||||
var deviceInfo = await _deviceInfoService.getDeviceInfo();
|
||||
Hive.box(userInfoBox).put(deviceIdKey, deviceInfo["deviceId"]);
|
||||
userInfoHiveBox.put(deviceIdKey, deviceInfo["deviceId"]);
|
||||
userInfoHiveBox.put(accessTokenKey, accessToken);
|
||||
userInfoHiveBox.put(serverEndpointKey, serverUrl);
|
||||
|
||||
state = state.copyWith(
|
||||
isAuthenticated: true,
|
||||
@@ -191,7 +197,7 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
email: "",
|
||||
password: "",
|
||||
isSaveLogin: true,
|
||||
serverUrl: Hive.box(userInfoBox).get(serverEndpointKey),
|
||||
serverUrl: serverUrl,
|
||||
accessToken: accessToken,
|
||||
),
|
||||
);
|
||||
@@ -204,8 +210,8 @@ class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
|
||||
// Register device info
|
||||
try {
|
||||
DeviceInfoResponseDto? deviceInfo =
|
||||
await _apiService.deviceInfoApi.createDeviceInfo(
|
||||
CreateDeviceInfoDto(
|
||||
await _apiService.deviceInfoApi.upsertDeviceInfo(
|
||||
UpsertDeviceInfoDto(
|
||||
deviceId: state.deviceId,
|
||||
deviceType: state.deviceType,
|
||||
),
|
||||
|
||||
@@ -2,5 +2,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/login/services/oauth.service.dart';
|
||||
import 'package:immich_mobile/shared/providers/api.provider.dart';
|
||||
|
||||
final OAuthServiceProvider =
|
||||
final oAuthServiceProvider =
|
||||
Provider((ref) => OAuthService(ref.watch(apiServiceProvider)));
|
||||
|
||||
@@ -83,6 +83,13 @@ class LoginForm extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
populateTestLoginInfo() {
|
||||
usernameController.text = 'testuser@email.com';
|
||||
passwordController.text = 'password';
|
||||
serverEndpointController.text = 'http://10.1.15.216:2283/api';
|
||||
isSaveLoginInfo.value = true;
|
||||
}
|
||||
|
||||
return Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 300),
|
||||
@@ -92,10 +99,13 @@ class LoginForm extends HookConsumerWidget {
|
||||
runSpacing: 16,
|
||||
alignment: WrapAlignment.center,
|
||||
children: [
|
||||
const Image(
|
||||
image: AssetImage('assets/immich-logo-no-outline.png'),
|
||||
width: 100,
|
||||
filterQuality: FilterQuality.high,
|
||||
GestureDetector(
|
||||
onDoubleTap: () => populateTestLoginInfo(),
|
||||
child: const Image(
|
||||
image: AssetImage('assets/immich-logo-no-outline.png'),
|
||||
width: 100,
|
||||
filterQuality: FilterQuality.high,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'IMMICH',
|
||||
@@ -349,7 +359,7 @@ class OAuthLoginButton extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
var oAuthService = ref.watch(OAuthServiceProvider);
|
||||
var oAuthService = ref.watch(oAuthServiceProvider);
|
||||
|
||||
void performOAuthLogin() async {
|
||||
ref.watch(assetProvider.notifier).clearAllAsset();
|
||||
@@ -380,6 +390,7 @@ class OAuthLoginButton extends ConsumerWidget {
|
||||
.setSuccessLoginInfo(
|
||||
accessToken: loginResponseDto.accessToken,
|
||||
isSavedLoginInfo: isSavedLoginInfo,
|
||||
serverUrl: serverEndpointController.text,
|
||||
);
|
||||
|
||||
if (isSuccess) {
|
||||
|
||||
@@ -1,14 +1,65 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/login/ui/login_form.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class LoginPage extends HookConsumerWidget {
|
||||
const LoginPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return const Scaffold(
|
||||
body: LoginForm(),
|
||||
final appVersion = useState('0.0.0');
|
||||
|
||||
getAppInfo() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
appVersion.value = packageInfo.version;
|
||||
}
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
getAppInfo();
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: const LoginForm(),
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: SizedBox(
|
||||
height: 50,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'v${appVersion.value}',
|
||||
style: const TextStyle(
|
||||
color: Colors.grey,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
),
|
||||
const Text(' '),
|
||||
GestureDetector(
|
||||
child: Text(
|
||||
'Logs',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).primaryColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: "Inconsolata",
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
AutoRouter.of(context).push(const AppLogRoute());
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +39,14 @@ class SearchPage extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
_onSearchSubmitted(String searchTerm) async {
|
||||
onSearchSubmitted(String searchTerm) async {
|
||||
searchFocusNode.unfocus();
|
||||
ref.watch(searchPageStateProvider.notifier).disableSearch();
|
||||
|
||||
AutoRouter.of(context).push(SearchResultRoute(searchTerm: searchTerm));
|
||||
}
|
||||
|
||||
_buildPlaces() {
|
||||
buildPlaces() {
|
||||
return curatedLocation.when(
|
||||
loading: () => SizedBox(
|
||||
height: imageSize,
|
||||
@@ -97,7 +97,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildThings() {
|
||||
buildThings() {
|
||||
return curatedObjects.when(
|
||||
loading: () => SizedBox(
|
||||
height: imageSize,
|
||||
@@ -155,7 +155,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
return Scaffold(
|
||||
appBar: SearchBar(
|
||||
searchFocusNode: searchFocusNode,
|
||||
onSubmitted: _onSearchSubmitted,
|
||||
onSubmitted: onSearchSubmitted,
|
||||
),
|
||||
body: GestureDetector(
|
||||
onTap: () {
|
||||
@@ -174,7 +174,7 @@ class SearchPage extends HookConsumerWidget {
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
).tr(),
|
||||
),
|
||||
_buildPlaces(),
|
||||
buildPlaces(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: const Text(
|
||||
@@ -182,11 +182,11 @@ class SearchPage extends HookConsumerWidget {
|
||||
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
|
||||
).tr(),
|
||||
),
|
||||
_buildThings()
|
||||
buildThings()
|
||||
],
|
||||
),
|
||||
if (isSearchEnabled)
|
||||
SearchSuggestionList(onSubmitted: _onSearchSubmitted),
|
||||
SearchSuggestionList(onSubmitted: onSearchSubmitted),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -38,7 +38,7 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
[],
|
||||
);
|
||||
|
||||
_onSearchSubmitted(String newSearchTerm) {
|
||||
onSearchSubmitted(String newSearchTerm) {
|
||||
debugPrint("Re-Search with $newSearchTerm");
|
||||
searchFocusNode?.unfocus();
|
||||
isNewSearch.value = false;
|
||||
@@ -46,7 +46,7 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
ref.watch(searchResultPageProvider.notifier).search(newSearchTerm);
|
||||
}
|
||||
|
||||
_buildTextField() {
|
||||
buildTextField() {
|
||||
return TextField(
|
||||
controller: searchTermController,
|
||||
focusNode: searchFocusNode,
|
||||
@@ -60,7 +60,7 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
onSubmitted: (searchTerm) {
|
||||
if (searchTerm.isNotEmpty) {
|
||||
searchTermController.clear();
|
||||
_onSearchSubmitted(searchTerm);
|
||||
onSearchSubmitted(searchTerm);
|
||||
} else {
|
||||
isNewSearch.value = false;
|
||||
}
|
||||
@@ -80,7 +80,7 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildChip() {
|
||||
buildChip() {
|
||||
return Chip(
|
||||
label: Wrap(
|
||||
spacing: 5,
|
||||
@@ -108,7 +108,7 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
_buildSearchResult() {
|
||||
buildSearchResult() {
|
||||
var searchResultPageState = ref.watch(searchResultPageProvider);
|
||||
var searchResultRenderList = ref.watch(searchRenderListProvider);
|
||||
|
||||
@@ -154,7 +154,7 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
isNewSearch.value = true;
|
||||
searchFocusNode?.requestFocus();
|
||||
},
|
||||
child: isNewSearch.value ? _buildTextField() : _buildChip(),
|
||||
child: isNewSearch.value ? buildTextField() : buildChip(),
|
||||
),
|
||||
centerTitle: false,
|
||||
),
|
||||
@@ -168,9 +168,9 @@ class SearchResultPage extends HookConsumerWidget {
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildSearchResult(),
|
||||
buildSearchResult(),
|
||||
if (isNewSearch.value)
|
||||
SearchSuggestionList(onSubmitted: _onSearchSubmitted),
|
||||
SearchSuggestionList(onSubmitted: onSearchSubmitted),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -2,7 +2,8 @@ import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:immich_mobile/constants/hive_box.dart';
|
||||
|
||||
enum AppSettingsEnum<T> {
|
||||
threeStageLoading<bool>("threeStageLoading", false),
|
||||
loadPreview<bool>("loadPreview", true),
|
||||
loadOriginal<bool>("loadOriginal", false),
|
||||
themeMode<String>("themeMode", "system"), // "light","dark","system"
|
||||
tilesPerRow<int>("tilesPerRow", 4),
|
||||
uploadErrorNotificationGracePeriod<int>(
|
||||
|
||||
24
mobile/lib/modules/settings/ui/common.dart
Normal file
@@ -0,0 +1,24 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
|
||||
SwitchListTile buildSwitchListTile(
|
||||
BuildContext context,
|
||||
AppSettingsService appSettingService,
|
||||
ValueNotifier<bool> valueNotifier,
|
||||
AppSettingsEnum settingsEnum, {
|
||||
required String title,
|
||||
String? subtitle,
|
||||
}) {
|
||||
return SwitchListTile.adaptive(
|
||||
key: Key(settingsEnum.name),
|
||||
value: valueNotifier.value,
|
||||
onChanged: (value) {
|
||||
valueNotifier.value = value;
|
||||
appSettingService.setSetting(settingsEnum, value);
|
||||
},
|
||||
activeColor: Theme.of(context).primaryColor,
|
||||
dense: true,
|
||||
title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
subtitle: subtitle != null ? Text(subtitle) : null,
|
||||
);
|
||||
}
|
||||
@@ -1,14 +1,30 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/image_viewer_quality_setting/three_stage_loading.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||
import 'package:immich_mobile/modules/settings/ui/common.dart';
|
||||
|
||||
class ImageViewerQualitySetting extends StatelessWidget {
|
||||
class ImageViewerQualitySetting extends HookConsumerWidget {
|
||||
const ImageViewerQualitySetting({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settings = ref.watch(appSettingsServiceProvider);
|
||||
final isPreview = useState(AppSettingsEnum.loadPreview.defaultValue);
|
||||
final isOriginal = useState(AppSettingsEnum.loadOriginal.defaultValue);
|
||||
|
||||
useEffect(
|
||||
() {
|
||||
isPreview.value = settings.getSetting(AppSettingsEnum.loadPreview);
|
||||
isOriginal.value = settings.getSetting(AppSettingsEnum.loadOriginal);
|
||||
return null;
|
||||
},
|
||||
);
|
||||
|
||||
return ExpansionTile(
|
||||
textColor: Theme.of(context).primaryColor,
|
||||
title: const Text(
|
||||
@@ -23,8 +39,27 @@ class ImageViewerQualitySetting extends StatelessWidget {
|
||||
fontSize: 13,
|
||||
),
|
||||
).tr(),
|
||||
children: const [
|
||||
ThreeStageLoading(),
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('setting_image_viewer_help').tr(),
|
||||
dense: true,
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
settings,
|
||||
isPreview,
|
||||
AppSettingsEnum.loadPreview,
|
||||
title: "setting_image_viewer_preview_title".tr(),
|
||||
subtitle: "setting_image_viewer_preview_subtitle".tr(),
|
||||
),
|
||||
buildSwitchListTile(
|
||||
context,
|
||||
settings,
|
||||
isOriginal,
|
||||
AppSettingsEnum.loadOriginal,
|
||||
title: "setting_image_viewer_original_title".tr(),
|
||||
subtitle: "setting_image_viewer_original_subtitle".tr(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||