Compare commits

...

33 Commits

Author SHA1 Message Date
Alex The Bot
d2bad1d553 Version v1.74.0 2023-08-19 06:09:16 +00:00
Jason Rasmussen
3e31ad51be feat: shared link album time buckets (#3776) 2023-08-18 22:19:42 -05:00
Jason Rasmussen
fbeb4664f7 feat(web): archive from album (#3773) 2023-08-18 17:55:06 -05:00
Steffen Auer
4ee8a30a5a chore(mobile): ios map launch, use OSM as map fallback, use dates as labels (#3772) 2023-08-18 17:53:50 -05:00
martyfuhry
6243bce46c chore(mobile): Bump to Flutter 3.13 (#3767)
* Bump to Flutter 3.13.0

* Updates permission status

* Adds hidden to app livecycle state

* Updates and switches to WakelockPlus

* bump flutter version github action

* mobile test version

* fix format

* video player

* video uri

* ios test

* Update android target sdk requirement to PlayStore

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-18 17:52:40 -05:00
Daniele Ricci
98b72fdb9b feat: set person birth date (web only) (#3721)
* Person birth date (data layer)

* Person birth date (data layer)

* Person birth date (service layer)

* Person birth date (service layer, API)

* Person birth date (service layer, API)

* Person birth date (UI) (wip)

* Person birth date (UI) (wip)

* Person birth date (UI) (wip)

* Person birth date (UI) (wip)

* UI: Use "date of birth" everywhere

* UI: better modal dialog

Similar to the API key modal.

* UI: set date of birth from people page

* Use typed events for modal dispatcher

* Date of birth tests (wip)

* Regenerate API

* Code formatting

* Fix Svelte typing

* Fix Svelte typing

* Fix person model [skip ci]

* Minor refactoring [skip ci]

* Typed event dispatcher [skip ci]

* Refactor typed event dispatcher [skip ci]

* Fix unchanged birthdate check [skip ci]

* Remove unnecessary custom transformer [skip ci]

* PersonUpdate: call search index update job only when needed

* Regenerate API

* Code formatting

* Fix tests

* Fix DTO

* Regenerate API

* chore: verbiage and view mode

* feat: show current age

* test: person e2e

* fix: show name for birth date selection

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-08-18 16:10:29 -04:00
Jason Rasmussen
5e901e4d21 feat(web,server): run jobs for specific assets (#3712)
* feat(web,server): manually queue asset job

* chore: open api

* chore: tests
2023-08-18 09:31:48 -05:00
Craeckie
66490d5db4 chore: Enable logging, but reduce verboseness of typesense container (#3761)
Co-authored-by: ultrabook <ultrabook>
2023-08-18 09:25:52 -05:00
Jason Rasmussen
2b839088c7 feat(web,server): server features (#3756)
* feat: server features

* chore: open api

* icon size

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-18 04:55:26 +00:00
Alex
28d3d3e679 fix(mobile): invalid range on label builder crash timeline (#3759)
* fix(mobile): invalid date on label builder crash timeline

* actual fix

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-17 23:50:41 -05:00
Alex
2de30e34f4 feat(mobile): Improve album UI and Interactions (#3754)
* fix: outlick editable field does not change edit icon

* fix: unfocus on submit change album name

* styling

* styling

* confirm dialog

* Confirm deletion

* render user

* user avatar with image

* use UserCircleAvatar

* rights

* stlying

* remove/leave options

* styling

* state management

---------

Co-authored-by: Alex Tran <Alex.Tran@conductix.com>
2023-08-17 23:26:12 -05:00
Jason Rasmussen
2ff71b0d27 fix(web): play videos on safari (#3748)
* fix(web): play videos on safari

* autoplay

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-17 13:52:50 -05:00
Jason Rasmussen
cdb45364c3 feat(server): add support for the tif extension (#3743) 2023-08-17 10:27:29 -05:00
Jason Rasmussen
8ba338fbe1 refactor(web): harden video can play method (#3745) 2023-08-17 10:02:12 -05:00
Kevin
ce84f9c755 feat(web): album list options (#3667)
* Album view option for cover or list view

* Dropdown can now receive list of icons to display with selected option

* Formatting

* Use table element with formatting similar to other pages

* Make table rows clickable with hover styling

* Also make row navigateable using keyboard without mouse

* Formatting

* Define DropdownOption interface

* Album view mode type definition for typescript support in if statements

* format

* fix typing

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-17 08:46:39 -05:00
shalong-tanwen
d1e74a28d9 fix(mobile): LivePhoto video not uploaded during manual asset upload (#3732) 2023-08-17 07:29:49 -05:00
martin
78a2a9e666 refactor(web): user-settings (#3700)
* refactor(web): user-settings

* feat: move the logic to the server

* use const

* fix: error 403

* fix: remove console.log
2023-08-16 22:56:06 -05:00
Lucas Eduardo
53f5643994 fix: shebangs (#3643)
Signed-off-by: lucasew <lucas59356@gmail.com>
2023-08-16 22:50:01 -05:00
Daniele Ricci
4ee634766d fix(web): label for attribute (#3731) 2023-08-16 16:09:38 -05:00
Jason Rasmussen
bab739efbd restore: bulk actions (#3730)
* feat: improve bulk isArchive and isFavorite updates

* chore: open api
2023-08-16 15:04:55 -05:00
Daniele Ricci
8568ec838a fix(web): Fix label for attribute (#3726) 2023-08-16 13:27:57 -05:00
Jason Rasmussen
4cbb18aabc feat(web): remove and delete from album (#3725) 2023-08-16 13:25:39 -05:00
Daniele Ricci
3fb60aca4f chore(web): better explain what the thumbnails type are for (#3724) 2023-08-16 13:25:07 -05:00
Skyler Mäntysaari
19bbdebdf7 fix(mobile): Do not show version announcement if user is not admin. (#3703) 2023-08-15 21:12:49 -05:00
martin
bc66b1a556 fix(web): user-management layout (#3704)
* fix: user-management layout

* better user form scrollbar

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-08-16 01:46:23 +00:00
Jason Rasmussen
4762fd83d4 fix(server): link live photos after metadata extraction finishes (#3702)
* fix(server): link live photos after metadata extraction finishes

* chore: fix test

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2023-08-15 20:34:57 -05:00
martin
c27c12d975 fix(server): people sorting (#3713) 2023-08-15 19:06:49 -05:00
Jason Rasmussen
0abbd85134 fix(web,server): album share performance (#3698) 2023-08-15 13:34:02 -05:00
Jason Rasmussen
af1f00dff9 chore(server): cleanup (#3699) 2023-08-15 11:05:32 -05:00
Vantao
35b4c9d375 doc: update README_zh_CN.md (#3701) 2023-08-15 16:05:00 +00:00
Sergey Kondrikov
74da15e20d fix(web,server): disable partner's archive access (#3695) 2023-08-15 11:02:38 -05:00
Jason Rasmussen
efc7fdb669 fix(web,server): use POST request to get download info (#3694)
* fix(web,server): use POST request to get download info

* chore: open api
2023-08-15 10:49:32 -05:00
Alex
a75f368d5b chore: post update 2023-08-15 09:42:28 -05:00
244 changed files with 4795 additions and 1365 deletions

View File

@@ -45,7 +45,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.5"
flutter-version: "3.13.0"
cache: true
- name: Create the Keystore

View File

@@ -23,7 +23,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.5"
flutter-version: "3.13.0"
- name: Install dependencies
run: dart pub get

View File

@@ -149,7 +149,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.10.5"
flutter-version: "3.13.0"
- name: Run tests
working-directory: ./mobile
run: flutter test -j 1

View File

@@ -13,7 +13,7 @@
</p>
<h3 align="center">Immich - 高性能的自托管照片和视频备份方案</h3>
<p align="center">
请注意: 此README不是由Immich团队维护, 这意味着它在某一时间点不会被更新,因为我们是依靠贡献者来更新的。感谢理解。
请注意: 此 README 不是由 Immich 团队维护, 而是依靠贡献者来更新的,这意味着它可能并不会被及时更新。感谢理解。
</p>
<br/>
<a href="https://immich.app">
@@ -31,29 +31,31 @@
## 免责声明
- ⚠️ 本项目正在 **非常活跃** 开发中。
- ⚠️ 可能存在bug或者重大变更。
- ⚠️ **不要把本软件作为存储照片或视频的唯一方式!**
- ⚠️ 本项目正在 **非常活跃** 开发中。
- ⚠️ 可能存在 bug 或者随时有重大变更。
- ⚠️ **不要把本软件作为存储照片或视频的唯一方式**
- ⚠️ 为了您宝贵的照片与视频,始终遵守 [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) 备份方案!
## 目录
- [官方文档](https://immich.app/docs/overview/introduction)
- [官方文档](https://immich.app/docs)
- [路线图](https://github.com/orgs/immich-app/projects/1)
- [示例](#示例)
- [功能特性](#功能特性)
- [介绍](https://immich.app/docs/overview/introduction)
- [安装](https://immich.app/docs/install/requirements)
- [贡献指南](https://immich.app/docs/overview/support-the-project)
- [支持本项目](#support-the-project)
- [已知问题](#known-issues)
- [支持本项目](#支持本项目)
## 官方文档
可以在 https://immich.app/ 找到包含安装手册的官方文档.
可以在 https://immich.app/ 找到官方文档(包含安装手册)。
## 示例
可以在 https://demo.immich.app 访问示例.
可以在 https://demo.immich.app 访问示例
在移动端, 可以使用 `https://demo.immich.app/api`获取`服务终端链接`
在移动端, 可以使用 `https://demo.immich.app/api` 获取 `服务终端链接`
```bash title="示例认证信息"
认证信息
@@ -62,57 +64,52 @@
```
```
规格: 甲骨文免费虚拟机套餐-阿姆斯特丹 4核 2.4Ghz ARM64 CPU, 24GB RAM。
规格: 甲骨文免费虚拟机套餐——阿姆斯特丹 4核 2.4Ghz ARM64 CPU, 24GB RAM。
```
# 功能特性
| 功能特性 | 移动端 | 网页端 |
| ------------------------------------------- | ------- | --- |
| 上传并查看照片和视频 | 是 | 是 |
| 软件运行时自动备份 | 是 | N/A |
| 上传并查看照片和视频 | 是 | 是 |
| 软件运行时自动备份 | 是 | N/A |
| 选择需要备份的相册 | 是 | N/A |
| 下载照片和视频到本地 | 是 | 是 |
| 下载照片和视频到本地 | 是 | 是 |
| 多用户支持 | 是 | 是 |
| 相册 | 是 | 是 |
| 共享相册 | 是 | 是 |
| 可拖动的快速导航栏 | 是 | 是 |
| 支持RAW格式 (HEIC, HEIF, DNG, Apple ProRaw) | 是 | 是 |
| 元数据视图 (EXIF, 地图) | 是 | 是 |
| 通过元数据、对象和标签进行搜索 | 是 | No |
| 管理功能 (用户管理) | N/A | 是 |
| 后台备份 | Android | N/A |
| 元数据视图EXIF, 地图 | 是 | 是 |
| 通过元数据、对象和标签进行搜索 | 是 | |
| 管理功能用户管理 | | 是 |
| 后台备份 | | N/A |
| 虚拟滚动 | 是 | 是 |
| OAuth支持 | 是 | 是 |
| 实时照片备份和查看 (仅iOS) | 是 | 是 |
| OAuth 支持 | 是 | 是 |
| API Keys|N/A|是|
| 实况照片备份和查看 | 仅 iOS | 是 |
|用户自定义存储结构|是|是|
|公共分享|否|是|
|归档与收藏功能|是|是|
|全局地图|否|是|
|好友分享|是|是|
|人像识别与分组|是|是|
|回忆(那年今日)|是|是|
|离线支持|是|否|
|只读相册|是|是|
# 支持本项目
我已经致力于本项目并且将我会持续更新文档、新增功能和修复问题。但是我不能一个人走下去,所以我需要给予我下去的动力。
我已经致力于本项目并且将我会持续更新文档、新增功能和修复问题。但是独木不成林,我需要给予我坚持下去的动力。
就像我主页里面 [selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) 说的一样,这是我和团队的一项艰巨任务。我希望某一天我能够全职开发本项目,在此我希望你们能够助我梦想成真。
就像我 [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`
- 通过 GitHub Sponsors [按月捐赠](https://github.com/sponsors/alextran1502)
- 通过 Github Sponsors [次捐赠](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502)
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- 比特币: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.73.0
* The version of the OpenAPI document: 1.74.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
@@ -344,6 +344,31 @@ export interface AllJobStatusResponseDto {
*/
'videoConversion': JobStatusDto;
}
/**
*
* @export
* @interface AssetBulkUpdateDto
*/
export interface AssetBulkUpdateDto {
/**
*
* @type {Array<string>}
* @memberof AssetBulkUpdateDto
*/
'ids': Array<string>;
/**
*
* @type {boolean}
* @memberof AssetBulkUpdateDto
*/
'isArchived'?: boolean;
/**
*
* @type {boolean}
* @memberof AssetBulkUpdateDto
*/
'isFavorite'?: boolean;
}
/**
*
* @export
@@ -500,6 +525,42 @@ export const AssetIdsResponseDtoErrorEnum = {
export type AssetIdsResponseDtoErrorEnum = typeof AssetIdsResponseDtoErrorEnum[keyof typeof AssetIdsResponseDtoErrorEnum];
/**
*
* @export
* @enum {string}
*/
export const AssetJobName = {
RegenerateThumbnail: 'regenerate-thumbnail',
RefreshMetadata: 'refresh-metadata',
TranscodeVideo: 'transcode-video'
} as const;
export type AssetJobName = typeof AssetJobName[keyof typeof AssetJobName];
/**
*
* @export
* @interface AssetJobsDto
*/
export interface AssetJobsDto {
/**
*
* @type {Array<string>}
* @memberof AssetJobsDto
*/
'assetIds': Array<string>;
/**
*
* @type {AssetJobName}
* @memberof AssetJobsDto
*/
'name': AssetJobName;
}
/**
*
* @export
@@ -1132,6 +1193,37 @@ export interface DownloadArchiveInfo {
*/
'size': number;
}
/**
*
* @export
* @interface DownloadInfoDto
*/
export interface DownloadInfoDto {
/**
*
* @type {string}
* @memberof DownloadInfoDto
*/
'albumId'?: string;
/**
*
* @type {number}
* @memberof DownloadInfoDto
*/
'archiveSize'?: number;
/**
*
* @type {Array<string>}
* @memberof DownloadInfoDto
*/
'assetIds'?: Array<string>;
/**
*
* @type {string}
* @memberof DownloadInfoDto
*/
'userId'?: string;
}
/**
*
* @export
@@ -1748,6 +1840,12 @@ export interface PeopleUpdateDto {
* @interface PeopleUpdateItem
*/
export interface PeopleUpdateItem {
/**
* Person date of birth.
* @type {string}
* @memberof PeopleUpdateItem
*/
'birthDate'?: string | null;
/**
* Asset is used to get the feature face thumbnail.
* @type {string}
@@ -1779,6 +1877,12 @@ export interface PeopleUpdateItem {
* @interface PersonResponseDto
*/
export interface PersonResponseDto {
/**
*
* @type {string}
* @memberof PersonResponseDto
*/
'birthDate': string | null;
/**
*
* @type {string}
@@ -1810,6 +1914,12 @@ export interface PersonResponseDto {
* @interface PersonUpdateDto
*/
export interface PersonUpdateDto {
/**
* Person date of birth.
* @type {string}
* @memberof PersonUpdateDto
*/
'birthDate'?: string | null;
/**
* Asset is used to get the feature face thumbnail.
* @type {string}
@@ -2031,6 +2141,43 @@ export interface SearchResponseDto {
*/
'assets': SearchAssetResponseDto;
}
/**
*
* @export
* @interface ServerFeaturesDto
*/
export interface ServerFeaturesDto {
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'machineLearning': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'oauth': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'oauthAutoLaunch': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'passwordLogin': boolean;
/**
*
* @type {boolean}
* @memberof ServerFeaturesDto
*/
'search': boolean;
}
/**
*
* @export
@@ -2152,25 +2299,25 @@ export interface ServerStatsResponseDto {
/**
*
* @export
* @interface ServerVersionReponseDto
* @interface ServerVersionResponseDto
*/
export interface ServerVersionReponseDto {
export interface ServerVersionResponseDto {
/**
*
* @type {number}
* @memberof ServerVersionReponseDto
* @memberof ServerVersionResponseDto
*/
'major': number;
/**
*
* @type {number}
* @memberof ServerVersionReponseDto
* @memberof ServerVersionResponseDto
*/
'minor': number;
/**
*
* @type {number}
* @memberof ServerVersionReponseDto
* @memberof ServerVersionResponseDto
*/
'patch': number;
}
@@ -4880,7 +5027,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
downloadArchive: async (assetIdsDto: AssetIdsDto, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetIdsDto' is not null or undefined
assertParamExists('downloadArchive', 'assetIdsDto', assetIdsDto)
const localVarPath = `/asset/download`;
const localVarPath = `/asset/download/archive`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
@@ -5379,16 +5526,15 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
},
/**
*
* @param {Array<string>} [assetIds]
* @param {string} [albumId]
* @param {string} [userId]
* @param {number} [archiveSize]
* @param {DownloadInfoDto} downloadInfoDto
* @param {string} [key]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getDownloadInfo: async (assetIds?: Array<string>, albumId?: string, userId?: string, archiveSize?: number, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/asset/download`;
getDownloadInfo: async (downloadInfoDto: DownloadInfoDto, key?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'downloadInfoDto' is not null or undefined
assertParamExists('getDownloadInfo', 'downloadInfoDto', downloadInfoDto)
const localVarPath = `/asset/download/info`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
@@ -5396,7 +5542,7 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
@@ -5409,31 +5555,18 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
if (assetIds) {
localVarQueryParameter['assetIds'] = assetIds;
}
if (albumId !== undefined) {
localVarQueryParameter['albumId'] = albumId;
}
if (userId !== undefined) {
localVarQueryParameter['userId'] = userId;
}
if (archiveSize !== undefined) {
localVarQueryParameter['archiveSize'] = archiveSize;
}
if (key !== undefined) {
localVarQueryParameter['key'] = key;
}
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(downloadInfoDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
@@ -5705,6 +5838,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
options: localVarRequestOptions,
};
},
/**
*
* @param {AssetJobsDto} assetJobsDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
runAssetJobs: async (assetJobsDto: AssetJobsDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetJobsDto' is not null or undefined
assertParamExists('runAssetJobs', 'assetJobsDto', assetJobsDto)
const localVarPath = `/asset/jobs`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(assetJobsDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {SearchAssetDto} searchAssetDto
@@ -5854,6 +6031,50 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
options: localVarRequestOptions,
};
},
/**
*
* @param {AssetBulkUpdateDto} assetBulkUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updateAssets: async (assetBulkUpdateDto: AssetBulkUpdateDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
// verify required parameter 'assetBulkUpdateDto' is not null or undefined
assertParamExists('updateAssets', 'assetBulkUpdateDto', assetBulkUpdateDto)
const localVarPath = `/asset`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'PUT', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
// authentication cookie required
// authentication api_key required
await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration)
// authentication bearer required
// http bearer authentication required
await setBearerAuthToObject(localVarHeaderParameter, configuration)
localVarHeaderParameter['Content-Type'] = 'application/json';
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
localVarRequestOptions.data = serializeDataIfNeeded(assetBulkUpdateDto, localVarRequestOptions, configuration)
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {File} assetData
@@ -6141,16 +6362,13 @@ export const AssetApiFp = function(configuration?: Configuration) {
},
/**
*
* @param {Array<string>} [assetIds]
* @param {string} [albumId]
* @param {string} [userId]
* @param {number} [archiveSize]
* @param {DownloadInfoDto} downloadInfoDto
* @param {string} [key]
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getDownloadInfo(assetIds?: Array<string>, albumId?: string, userId?: string, archiveSize?: number, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DownloadResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getDownloadInfo(assetIds, albumId, userId, archiveSize, key, options);
async getDownloadInfo(downloadInfoDto: DownloadInfoDto, key?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DownloadResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getDownloadInfo(downloadInfoDto, key, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
@@ -6211,6 +6429,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.importFile(importAssetDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {AssetJobsDto} assetJobsDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async runAssetJobs(assetJobsDto: AssetJobsDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.runAssetJobs(assetJobsDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {SearchAssetDto} searchAssetDto
@@ -6245,6 +6473,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(id, updateAssetDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {AssetBulkUpdateDto} assetBulkUpdateDto
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async updateAssets(assetBulkUpdateDto: AssetBulkUpdateDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssets(assetBulkUpdateDto, options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {File} assetData
@@ -6406,8 +6644,8 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getDownloadInfo(requestParameters: AssetApiGetDownloadInfoRequest = {}, options?: AxiosRequestConfig): AxiosPromise<DownloadResponseDto> {
return localVarFp.getDownloadInfo(requestParameters.assetIds, requestParameters.albumId, requestParameters.userId, requestParameters.archiveSize, requestParameters.key, options).then((request) => request(axios, basePath));
getDownloadInfo(requestParameters: AssetApiGetDownloadInfoRequest, options?: AxiosRequestConfig): AxiosPromise<DownloadResponseDto> {
return localVarFp.getDownloadInfo(requestParameters.downloadInfoDto, requestParameters.key, options).then((request) => request(axios, basePath));
},
/**
*
@@ -6454,6 +6692,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
importFile(requestParameters: AssetApiImportFileRequest, options?: AxiosRequestConfig): AxiosPromise<AssetFileUploadResponseDto> {
return localVarFp.importFile(requestParameters.importAssetDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig): AxiosPromise<void> {
return localVarFp.runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiSearchAssetRequest} requestParameters Request parameters.
@@ -6481,6 +6728,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
updateAsset(requestParameters: AssetApiUpdateAssetRequest, options?: AxiosRequestConfig): AxiosPromise<AssetResponseDto> {
return localVarFp.updateAsset(requestParameters.id, requestParameters.updateAssetDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiUpdateAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
updateAssets(requestParameters: AssetApiUpdateAssetsRequest, options?: AxiosRequestConfig): AxiosPromise<void> {
return localVarFp.updateAssets(requestParameters.assetBulkUpdateDto, options).then((request) => request(axios, basePath));
},
/**
*
* @param {AssetApiUploadFileRequest} requestParameters Request parameters.
@@ -6788,31 +7044,10 @@ export interface AssetApiGetByTimeBucketRequest {
export interface AssetApiGetDownloadInfoRequest {
/**
*
* @type {Array<string>}
* @type {DownloadInfoDto}
* @memberof AssetApiGetDownloadInfo
*/
readonly assetIds?: Array<string>
/**
*
* @type {string}
* @memberof AssetApiGetDownloadInfo
*/
readonly albumId?: string
/**
*
* @type {string}
* @memberof AssetApiGetDownloadInfo
*/
readonly userId?: string
/**
*
* @type {number}
* @memberof AssetApiGetDownloadInfo
*/
readonly archiveSize?: number
readonly downloadInfoDto: DownloadInfoDto
/**
*
@@ -6948,6 +7183,20 @@ export interface AssetApiImportFileRequest {
readonly importAssetDto: ImportAssetDto
}
/**
* Request parameters for runAssetJobs operation in AssetApi.
* @export
* @interface AssetApiRunAssetJobsRequest
*/
export interface AssetApiRunAssetJobsRequest {
/**
*
* @type {AssetJobsDto}
* @memberof AssetApiRunAssetJobs
*/
readonly assetJobsDto: AssetJobsDto
}
/**
* Request parameters for searchAsset operation in AssetApi.
* @export
@@ -7018,6 +7267,20 @@ export interface AssetApiUpdateAssetRequest {
readonly updateAssetDto: UpdateAssetDto
}
/**
* Request parameters for updateAssets operation in AssetApi.
* @export
* @interface AssetApiUpdateAssetsRequest
*/
export interface AssetApiUpdateAssetsRequest {
/**
*
* @type {AssetBulkUpdateDto}
* @memberof AssetApiUpdateAssets
*/
readonly assetBulkUpdateDto: AssetBulkUpdateDto
}
/**
* Request parameters for uploadFile operation in AssetApi.
* @export
@@ -7281,8 +7544,8 @@ export class AssetApi extends BaseAPI {
* @throws {RequiredError}
* @memberof AssetApi
*/
public getDownloadInfo(requestParameters: AssetApiGetDownloadInfoRequest = {}, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).getDownloadInfo(requestParameters.assetIds, requestParameters.albumId, requestParameters.userId, requestParameters.archiveSize, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
public getDownloadInfo(requestParameters: AssetApiGetDownloadInfoRequest, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).getDownloadInfo(requestParameters.downloadInfoDto, requestParameters.key, options).then((request) => request(this.axios, this.basePath));
}
/**
@@ -7340,6 +7603,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).importFile(requestParameters.importAssetDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiRunAssetJobsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public runAssetJobs(requestParameters: AssetApiRunAssetJobsRequest, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).runAssetJobs(requestParameters.assetJobsDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiSearchAssetRequest} requestParameters Request parameters.
@@ -7373,6 +7647,17 @@ export class AssetApi extends BaseAPI {
return AssetApiFp(this.configuration).updateAsset(requestParameters.id, requestParameters.updateAssetDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiUpdateAssetsRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof AssetApi
*/
public updateAssets(requestParameters: AssetApiUpdateAssetsRequest, options?: AxiosRequestConfig) {
return AssetApiFp(this.configuration).updateAssets(requestParameters.assetBulkUpdateDto, options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {AssetApiUploadFileRequest} requestParameters Request parameters.
@@ -10050,6 +10335,35 @@ export class SearchApi extends BaseAPI {
*/
export const ServerInfoApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getServerFeatures: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/server-info/features`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {*} [options] Override http request option.
@@ -10223,6 +10537,15 @@ export const ServerInfoApiAxiosParamCreator = function (configuration?: Configur
export const ServerInfoApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = ServerInfoApiAxiosParamCreator(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getServerFeatures(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerFeaturesDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getServerFeatures(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
/**
*
* @param {*} [options] Override http request option.
@@ -10237,7 +10560,7 @@ export const ServerInfoApiFp = function(configuration?: Configuration) {
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerVersionReponseDto>> {
async getServerVersion(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<ServerVersionResponseDto>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.getServerVersion(options);
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
},
@@ -10278,6 +10601,14 @@ export const ServerInfoApiFp = function(configuration?: Configuration) {
export const ServerInfoApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = ServerInfoApiFp(configuration)
return {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getServerFeatures(options?: AxiosRequestConfig): AxiosPromise<ServerFeaturesDto> {
return localVarFp.getServerFeatures(options).then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
@@ -10291,7 +10622,7 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
getServerVersion(options?: AxiosRequestConfig): AxiosPromise<ServerVersionReponseDto> {
getServerVersion(options?: AxiosRequestConfig): AxiosPromise<ServerVersionResponseDto> {
return localVarFp.getServerVersion(options).then((request) => request(axios, basePath));
},
/**
@@ -10328,6 +10659,16 @@ export const ServerInfoApiFactory = function (configuration?: Configuration, bas
* @extends {BaseAPI}
*/
export class ServerInfoApi extends BaseAPI {
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof ServerInfoApi
*/
public getServerFeatures(options?: AxiosRequestConfig) {
return ServerInfoApiFp(this.configuration).getServerFeatures(options).then((request) => request(this.axios, this.basePath));
}
/**
*
* @param {*} [options] Override http request option.

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.73.0
* The version of the OpenAPI document: 1.74.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.73.0
* The version of the OpenAPI document: 1.74.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.73.0
* The version of the OpenAPI document: 1.74.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,7 +4,7 @@
* Immich
* Immich API
*
* The version of the OpenAPI document: 1.73.0
* The version of the OpenAPI document: 1.74.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).

View File

@@ -4,14 +4,14 @@ import { SessionService } from '../services/session.service';
import { LoginError } from '../cores/errors/login-error';
import { exit } from 'node:process';
import os from 'os';
import { ServerVersionReponseDto, UserResponseDto } from 'src/api/open-api';
import { ServerVersionResponseDto, UserResponseDto } from 'src/api/open-api';
export abstract class BaseCommand {
protected sessionService!: SessionService;
protected immichApi!: ImmichApi;
protected deviceId!: string;
protected user!: UserResponseDto;
protected serverVersion!: ServerVersionReponseDto;
protected serverVersion!: ServerVersionResponseDto;
protected configDir;
protected authPath;

View File

@@ -100,8 +100,8 @@ services:
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
logging:
driver: none
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data

View File

@@ -68,8 +68,8 @@ services:
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
logging:
driver: none
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
restart: always

View File

@@ -54,6 +54,8 @@ services:
environment:
- TYPESENSE_API_KEY=${TYPESENSE_API_KEY}
- TYPESENSE_DATA_DIR=/data
# remove this to get debug messages
- GLOG_minloglevel=1
volumes:
- tsdata:/data
restart: always

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.73.0"
version = "1.74.0"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"

View File

@@ -1,4 +1,4 @@
{
"flutterSdkVersion": "3.10.5",
"flutterSdkVersion": "3.13.0",
"flavors": {}
}

View File

@@ -52,7 +52,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "app.alextran.immich"
minSdkVersion 23
minSdkVersion 26
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@@ -56,7 +56,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<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" />

View File

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

View File

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000239">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.00023">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="68.788432">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="67.877631">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="29.76592">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="23.895222">
</testcase>

View File

@@ -300,5 +300,6 @@
"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"
}
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"translated_text_options": "Options"
}

View File

@@ -33,7 +33,7 @@ PODS:
- FlutterMacOS
- path_provider_ios (0.0.1):
- Flutter
- permission_handler_apple (9.0.4):
- permission_handler_apple (9.1.1):
- Flutter
- photo_manager (2.0.0):
- Flutter
@@ -53,7 +53,7 @@ PODS:
- Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- wakelock (0.0.1):
- wakelock_plus (0.0.1):
- Flutter
DEPENDENCIES:
@@ -78,7 +78,7 @@ DEPENDENCIES:
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
SPEC REPOS:
trunk:
@@ -130,8 +130,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
wakelock_plus:
:path: ".symlinks/plugins/wakelock_plus/ios"
SPEC CHECKSUMS:
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
@@ -141,25 +141,25 @@ SPEC CHECKSUMS:
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
integration_test: 13825b8a9334a850581300559b8839134b124670
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382

View File

@@ -171,7 +171,7 @@
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
@@ -379,7 +379,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 113;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -515,7 +515,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 113;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@@ -543,7 +543,7 @@
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 110;
CURRENT_PROJECT_VERSION = 113;
DEVELOPMENT_TEAM = 2F67MQ8R79;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -59,11 +59,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.70.0</string>
<string>1.73.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>110</string>
<string>113</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>

View File

@@ -1,4 +1,4 @@
#!/bin/sh
#!/usr/bin/env sh
# The default execution directory of this script is the ci_scripts directory.
cd $CI_WORKSPACE/mobile

View File

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

View File

@@ -5,32 +5,32 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000211">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000187">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.108738">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="2.403882">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="28.952846">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.068392">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.821481">
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.988079">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="99.212621">
<testcase classname="fastlane.lanes" name="4: build_app" time="96.47923">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="68.366701">
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="57.517755">
</testcase>

View File

@@ -139,6 +139,10 @@ class ImmichAppState extends ConsumerState<ImmichApp>
debugPrint("[APP STATE] detached");
ref.read(appStateProvider.notifier).handleAppDetached();
break;
case AppLifecycleState.hidden:
debugPrint("[APP STATE] hidden");
ref.read(appStateProvider.notifier).handleAppHidden();
break;
}
}

View File

@@ -56,6 +56,16 @@ class SharedAlbumNotifier extends StateNotifier<List<Album>> {
return _albumService.removeAssetFromAlbum(album, assets);
}
Future<bool> removeUserFromAlbum(Album album, User user) async {
final result = await _albumService.removeUserFromAlbum(album, user);
if (result && album.sharedUsers.isEmpty) {
state = state.where((element) => element.id != album.id).toList();
}
return result;
}
@override
void dispose() {
_streamSub.cancel();

View File

@@ -348,6 +348,26 @@ class AlbumService {
}
}
Future<bool> removeUserFromAlbum(
Album album,
User user,
) async {
try {
await _apiService.albumApi.removeUserFromAlbum(
album.remoteId!,
user.id,
);
album.sharedUsers.remove(user);
await _db.writeTxn(() => album.sharedUsers.update(unlink: [user]));
return true;
} catch (e) {
debugPrint("Error removeUserFromAlbum ${e.toString()}");
return false;
}
}
Future<bool> changeTitleAlbum(
Album album,
String newAlbumTitle,

View File

@@ -49,7 +49,7 @@ class AlbumThumbnailListTile extends StatelessWidget {
type: ThumbnailFormat.JPEG,
),
httpHeaders: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
},
cacheKey: getAlbumThumbNailCacheKey(album, type: ThumbnailFormat.JPEG),
errorWidget: (context, url, error) =>
@@ -105,9 +105,9 @@ class AlbumThumbnailListTile extends StatelessWidget {
style: TextStyle(
fontSize: 12,
),
).tr()
).tr(),
],
)
),
],
),
),

View File

@@ -69,6 +69,11 @@ class AlbumTitleTextField extends ConsumerWidget {
borderRadius: BorderRadius.circular(10),
),
hintText: 'share_add_title'.tr(),
hintStyle: TextStyle(
fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
focusColor: Colors.grey[300],
fillColor: isDarkTheme
? const Color.fromARGB(255, 32, 33, 35)

View File

@@ -39,7 +39,7 @@ class AlbumViewerAppbar extends HookConsumerWidget
final newAlbumTitle = ref.watch(albumViewerProvider).editTitleText;
final isEditAlbum = ref.watch(albumViewerProvider).isEditAlbum;
void onDeleteAlbumPressed() async {
deleteAlbum() async {
ImmichLoadingOverlayController.appLoader.show();
final bool success;
@@ -65,6 +65,52 @@ class AlbumViewerAppbar extends HookConsumerWidget
ImmichLoadingOverlayController.appLoader.hide();
}
Future<void> showConfirmationDialog() async {
return showDialog<void>(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Delete album'),
content: const Text(
'Are you sure you want to delete this album from your account?',
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: Text(
'Cancel',
style: TextStyle(
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
),
),
TextButton(
onPressed: () {
Navigator.pop(context, 'Confirm');
deleteAlbum();
},
child: Text(
'Confirm',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).brightness == Brightness.light
? Colors.red
: Colors.red[300],
),
),
),
],
);
},
);
}
void onDeleteAlbumPressed() async {
showConfirmationDialog();
}
void onLeaveAlbumPressed() async {
ImmichLoadingOverlayController.appLoader.show();
@@ -152,43 +198,61 @@ class AlbumViewerAppbar extends HookConsumerWidget
}
void buildBottomSheet() {
final ownerActions = [
ListTile(
leading: const Icon(Icons.person_add_alt_rounded),
onTap: () {
Navigator.pop(context);
onAddUsers!(album);
},
title: const Text(
"album_viewer_page_share_add_users",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
ListTile(
leading: const Icon(Icons.settings_rounded),
onTap: () =>
AutoRouter.of(context).navigate(AlbumOptionsRoute(album: album)),
title: const Text(
"translated_text_options",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
];
final commonActions = [
ListTile(
leading: const Icon(Icons.add_photo_alternate_outlined),
onTap: () {
Navigator.pop(context);
onAddPhotos!(album);
},
title: const Text(
"share_add_photos",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
];
showModalBottomSheet(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
isScrollControlled: false,
context: context,
builder: (context) {
return SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildBottomSheetActionButton(),
if (selected.isEmpty && onAddPhotos != null)
ListTile(
leading: const Icon(Icons.add_photo_alternate_outlined),
onTap: () {
Navigator.pop(context);
onAddPhotos!(album);
},
title: const Text(
"share_add_photos",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
if (selected.isEmpty &&
onAddPhotos != null &&
userId == album.ownerId)
ListTile(
leading: const Icon(Icons.person_add_alt_rounded),
onTap: () {
Navigator.pop(context);
onAddUsers!(album);
},
title: const Text(
"album_viewer_page_share_add_users",
style: TextStyle(fontWeight: FontWeight.bold),
).tr(),
),
],
child: Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildBottomSheetActionButton(),
if (selected.isEmpty && onAddPhotos != null) ...commonActions,
if (selected.isEmpty &&
onAddPhotos != null &&
userId == album.ownerId)
...ownerActions,
],
),
),
);
},
@@ -217,6 +281,8 @@ class AlbumViewerAppbar extends HookConsumerWidget
toastType: ToastType.error,
);
}
titleFocusNode.unfocus();
},
icon: const Icon(Icons.check_rounded),
splashRadius: 25,

View File

@@ -84,6 +84,11 @@ class AlbumViewerEditableTitle extends HookConsumerWidget {
: Colors.grey[200],
filled: titleFocusNode.hasFocus,
hintText: 'share_add_title'.tr(),
hintStyle: TextStyle(
fontSize: 28,
color: isDarkTheme ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.bold,
),
),
);
}

View File

@@ -0,0 +1,205 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/providers/shared_album.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
class AlbumOptionsPage extends HookConsumerWidget {
final Album album;
const AlbumOptionsPage({super.key, required this.album});
@override
Widget build(BuildContext context, WidgetRef ref) {
final sharedUsers = useState(album.sharedUsers.toList());
final owner = album.owner.value;
final userId = ref.watch(authenticationProvider).userId;
final isOwner = owner?.id == userId;
void showErrorMessage() {
Navigator.pop(context);
ImmichToast.show(
context: context,
msg: "Error leaving/removing from album",
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
}
void leaveAlbum() async {
ImmichLoadingOverlayController.appLoader.show();
try {
final isSuccess =
await ref.read(sharedAlbumProvider.notifier).leaveAlbum(album);
if (isSuccess) {
AutoRouter.of(context)
.navigate(const TabControllerRoute(children: [SharingRoute()]));
} else {
showErrorMessage();
}
} catch (_) {
showErrorMessage();
}
ImmichLoadingOverlayController.appLoader.hide();
}
void removeUserFromAlbum(User user) async {
ImmichLoadingOverlayController.appLoader.show();
try {
await ref
.read(sharedAlbumProvider.notifier)
.removeUserFromAlbum(album, user);
album.sharedUsers.remove(user);
sharedUsers.value = album.sharedUsers.toList();
} catch (error) {
showErrorMessage();
}
Navigator.pop(context);
ImmichLoadingOverlayController.appLoader.hide();
}
void handleUserClick(User user) {
var actions = [];
if (user.id == userId) {
actions = [
ListTile(
leading: const Icon(Icons.exit_to_app_rounded),
title: const Text("Leave album"),
onTap: leaveAlbum,
),
];
}
if (isOwner) {
actions = [
ListTile(
leading: const Icon(Icons.person_remove_rounded),
title: const Text("Remove user from album"),
onTap: () => removeUserFromAlbum(user),
),
];
}
showModalBottomSheet(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
isScrollControlled: false,
context: context,
builder: (context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.only(top: 24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [...actions],
),
),
);
},
);
}
buildOwnerInfo() {
return ListTile(
leading: owner != null
? UserCircleAvatar(
user: owner,
useRandomBackgroundColor: true,
)
: const SizedBox(),
title: Text(
album.owner.value?.firstName ?? "",
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
album.owner.value?.email ?? "",
style: TextStyle(color: Colors.grey[500]),
),
trailing: const Text(
"Owner",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
);
}
buildSharedUsersList() {
return ListView.builder(
shrinkWrap: true,
itemCount: sharedUsers.value.length,
itemBuilder: (context, index) {
final user = sharedUsers.value[index];
return ListTile(
leading: UserCircleAvatar(
user: user,
useRandomBackgroundColor: true,
radius: 22,
),
title: Text(
user.firstName,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
subtitle: Text(
user.email,
style: TextStyle(color: Colors.grey[500]),
),
trailing: userId == user.id || isOwner
? const Icon(Icons.more_horiz_rounded)
: const SizedBox(),
onTap: userId == user.id || isOwner
? () => handleUserClick(user)
: null,
);
},
);
}
buildSectionTitle(String text) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Text(text, style: Theme.of(context).textTheme.bodySmall),
);
}
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios_new_rounded),
onPressed: () {
AutoRouter.of(context).pop(null);
},
),
centerTitle: true,
title: Text("translated_text_options".tr()),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildSectionTitle("PEOPLE"),
buildOwnerInfo(),
buildSharedUsersList(),
],
),
);
}
}

View File

@@ -17,6 +17,7 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
class AlbumViewerPage extends HookConsumerWidget {
@@ -116,7 +117,7 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget buildControlButton(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 8),
padding: const EdgeInsets.only(left: 16.0, top: 8, bottom: 16),
child: SizedBox(
height: 40,
child: ListView(
@@ -141,7 +142,7 @@ class AlbumViewerPage extends HookConsumerWidget {
Widget buildTitle(Album album) {
return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 16),
padding: const EdgeInsets.only(left: 8, right: 8, top: 24),
child: userId == album.ownerId && album.isRemote
? AlbumViewerEditableTitle(
album: album,
@@ -172,7 +173,6 @@ class AlbumViewerPage extends HookConsumerWidget {
return Padding(
padding: EdgeInsets.only(
left: 16.0,
top: 8.0,
bottom: album.shared ? 0.0 : 8.0,
),
child: Text(
@@ -180,7 +180,34 @@ class AlbumViewerPage extends HookConsumerWidget {
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Colors.grey,
),
),
);
}
Widget buildSharedUserIconsRow(Album album) {
return GestureDetector(
onTap: () async {
await AutoRouter.of(context).push(AlbumOptionsRoute(album: album));
ref.invalidate(albumDetailProvider(album.id));
},
child: SizedBox(
height: 50,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16),
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: UserCircleAvatar(
user: album.sharedUsers.toList()[index],
radius: 18,
size: 36,
useRandomBackgroundColor: true,
),
);
}),
itemCount: album.sharedUsers.length,
),
),
);
@@ -193,33 +220,7 @@ class AlbumViewerPage extends HookConsumerWidget {
children: [
buildTitle(album),
if (album.assets.isNotEmpty == true) buildAlbumDateRange(album),
if (album.shared)
SizedBox(
height: 50,
child: ListView.builder(
padding: const EdgeInsets.only(left: 16),
scrollDirection: Axis.horizontal,
itemBuilder: ((context, index) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: CircleAvatar(
backgroundColor: Colors.grey[300],
radius: 18,
child: Padding(
padding: const EdgeInsets.all(2.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Image.asset(
'assets/immich-logo-no-outline.png',
),
),
),
),
);
}),
itemCount: album.sharedUsers.length,
),
),
if (album.shared) buildSharedUserIconsRow(album),
],
);
}

View File

@@ -73,9 +73,12 @@ class AssetSelectionPage extends HookConsumerWidget {
AutoRouter.of(context)
.popForced<AssetSelectionPageResult>(payload);
},
child: const Text(
child: Text(
"share_add",
style: TextStyle(fontWeight: FontWeight.bold),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
).tr(),
),
],

View File

@@ -30,7 +30,8 @@ class CreateAlbumPage extends HookConsumerWidget {
final albumTitleTextFieldFocusNode = useFocusNode();
final isAlbumTitleTextFieldFocus = useState(false);
final isAlbumTitleEmpty = useState(true);
final selectedAssets = useState<Set<Asset>>(initialAssets != null ? Set.from(initialAssets!) : const {});
final selectedAssets = useState<Set<Asset>>(
initialAssets != null ? Set.from(initialAssets!) : const {},);
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
showSelectUserPage() async {
@@ -248,8 +249,9 @@ class CreateAlbumPage extends HookConsumerWidget {
: null,
child: Text(
'create_shared_album_page_create'.tr(),
style: const TextStyle(
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
),
),

View File

@@ -60,7 +60,7 @@ class LibraryPage extends HookConsumerWidget {
Widget buildSortButton() {
final options = [
"library_page_sort_created".tr(),
"library_page_sort_title".tr()
"library_page_sort_title".tr(),
];
return PopupMenuButton(
@@ -87,7 +87,7 @@ class LibraryPage extends HookConsumerWidget {
color: selected ? Theme.of(context).primaryColor : null,
fontSize: 12.0,
),
)
),
],
),
);

View File

@@ -7,6 +7,7 @@ import 'package:immich_mobile/modules/album/providers/suggested_shared_users.pro
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
final Album album;
@@ -35,10 +36,8 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
),
);
} else {
return CircleAvatar(
backgroundImage:
const AssetImage('assets/immich-logo-no-outline.png'),
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
return UserCircleAvatar(
user: user,
);
}
}
@@ -103,7 +102,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
} else {
sharedUsersList.value = {
...sharedUsersList.value,
users[index]
users[index],
};
}
},
@@ -136,7 +135,7 @@ class SelectAdditionalUserForSharingPage extends HookConsumerWidget {
"share_add",
style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold),
).tr(),
)
),
],
),
body: suggestedShareUsers.when(

View File

@@ -10,6 +10,7 @@ import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
class SelectUserForSharingPage extends HookConsumerWidget {
const SelectUserForSharingPage({Key? key, required this.assets})
@@ -56,10 +57,8 @@ class SelectUserForSharingPage extends HookConsumerWidget {
),
);
} else {
return CircleAvatar(
backgroundImage:
const AssetImage('assets/immich-logo-no-outline.png'),
backgroundColor: Theme.of(context).primaryColor.withAlpha(50),
return UserCircleAvatar(
user: user,
);
}
}
@@ -124,7 +123,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
} else {
sharedUsersList.value = {
...sharedUsersList.value,
users[index]
users[index],
};
}
},
@@ -164,7 +163,7 @@ class SelectUserForSharingPage extends HookConsumerWidget {
// color: Theme.of(context).primaryColor,
),
).tr(),
)
),
],
),
body: suggestedShareUsers.when(

View File

@@ -160,7 +160,7 @@ class SharingPage extends HookConsumerWidget {
maxLines: 1,
).tr(),
),
)
),
],
),
);

View File

@@ -91,7 +91,7 @@ class ArchivePage extends HookConsumerWidget {
selectionEnabledHook.value = false;
}
},
)
),
],
),
),
@@ -124,7 +124,7 @@ class ArchivePage extends HookConsumerWidget {
),
if (selectionEnabledHook.value) buildBottomBar(),
if (processing.value)
const Center(child: ImmichLoadingIndicator())
const Center(child: ImmichLoadingIndicator()),
],
),
),

View File

@@ -16,16 +16,32 @@ class ExifBottomSheet extends HookConsumerWidget {
const ExifBottomSheet({Key? key, required this.asset}) : super(key: key);
bool get showMap =>
bool get hasCoordinates =>
asset.exifInfo?.latitude != null && asset.exifInfo?.longitude != null;
Future<Uri> _createCoordinatesUri(double latitude, double longitude) async {
const zoomLevel = 5;
String get formattedDateTime {
final fileCreatedAt = asset.fileCreatedAt.toLocal();
final date = DateFormat.yMMMEd().format(fileCreatedAt);
final time = DateFormat.jm().format(fileCreatedAt);
return '$date$time';
}
Future<Uri?> _createCoordinatesUri() async {
if (!hasCoordinates) {
return null;
}
double latitude = asset.exifInfo!.latitude!;
double longitude = asset.exifInfo!.longitude!;
const zoomLevel = 16;
if (Platform.isAndroid) {
Uri uri = Uri(
scheme: 'geo',
host: '$latitude,$longitude',
queryParameters: {'z': '$zoomLevel', 'q': '$latitude,$longitude'},
queryParameters: {'z': '$zoomLevel', 'q': formattedDateTime},
);
if (await canLaunchUrl(uri)) {
return uri;
@@ -33,16 +49,20 @@ class ExifBottomSheet extends HookConsumerWidget {
} else if (Platform.isIOS) {
var params = {
'll': '$latitude,$longitude',
'q': '$latitude, $longitude',
'q': formattedDateTime,
'z': '$zoomLevel',
};
Uri uri = Uri.https('maps.apple.com', '/', params);
if (!await canLaunchUrl(uri)) {
if (await canLaunchUrl(uri)) {
return uri;
}
}
return Uri.https(
'www.google.com',
'/maps/place/$latitude,$longitude/@$latitude,$longitude,${zoomLevel}z',
return Uri(
scheme: 'https',
host: 'openstreetmap.org',
queryParameters: {'mlat': '$latitude', 'mlon': '$longitude'},
fragment: 'map=$zoomLevel/$latitude/$longitude',
);
}
@@ -72,16 +92,14 @@ class ExifBottomSheet extends HookConsumerWidget {
),
zoom: 16.0,
onTap: (tapPosition, latLong) async {
if (exifInfo != null &&
exifInfo.latitude != null &&
exifInfo.longitude != null) {
launchUrl(
await _createCoordinatesUri(
exifInfo.latitude!,
exifInfo.longitude!,
),
);
Uri? uri = await _createCoordinatesUri();
if (uri == null) {
return;
}
debugPrint('Opening Map Uri: $uri');
launchUrl(uri);
},
),
nonRotatedChildren: [
@@ -151,7 +169,7 @@ class ExifBottomSheet extends HookConsumerWidget {
buildLocation() {
// Guard no lat/lng
if (!showMap) {
if (!hasCoordinates) {
return Container();
}
@@ -199,7 +217,7 @@ class ExifBottomSheet extends HookConsumerWidget {
Text(
"${exifInfo!.latitude!.toStringAsFixed(4)}, ${exifInfo.longitude!.toStringAsFixed(4)}",
style: const TextStyle(fontSize: 12),
)
),
],
),
],
@@ -207,12 +225,8 @@ class ExifBottomSheet extends HookConsumerWidget {
}
buildDate() {
final fileCreatedAt = asset.fileCreatedAt.toLocal();
final date = DateFormat.yMMMEd().format(fileCreatedAt);
final time = DateFormat.jm().format(fileCreatedAt);
return Text(
'$date$time',
formattedDateTime,
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
@@ -306,7 +320,7 @@ class ExifBottomSheet extends HookConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: showMap ? 5 : 0,
flex: hasCoordinates ? 5 : 0,
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: buildLocation(),
@@ -336,7 +350,7 @@ class ExifBottomSheet extends HookConsumerWidget {
if (asset.isRemote) DescriptionInput(asset: asset),
const SizedBox(height: 8.0),
buildLocation(),
SizedBox(height: showMap ? 16.0 : 0.0),
SizedBox(height: hasCoordinates ? 16.0 : 0.0),
buildDetail(),
const SizedBox(height: 50),
],

View File

@@ -128,7 +128,7 @@ class TopControlAppBar extends HookConsumerWidget {
if (asset.isLocal && !asset.isRemote) buildUploadButton(),
if (asset.isRemote && !asset.isLocal) buildDownloadButton(),
if (asset.isRemote) buildAddToAlbumButtom(),
buildMoreInfoButton()
buildMoreInfoButton(),
],
);
}

View File

@@ -11,7 +11,7 @@ import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:video_player/video_player.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
// ignore: must_be_immutable
class VideoViewerPage extends HookConsumerWidget {
@@ -136,16 +136,16 @@ class _VideoPlayerState extends State<VideoPlayer> {
videoPlayerController.addListener(() {
if (videoPlayerController.value.isInitialized) {
if (videoPlayerController.value.isPlaying) {
Wakelock.enable();
WakelockPlus.enable();
widget.onPlaying?.call();
} else if (!videoPlayerController.value.isPlaying) {
Wakelock.disable();
WakelockPlus.disable();
widget.onPaused?.call();
}
if (videoPlayerController.value.position ==
videoPlayerController.value.duration) {
Wakelock.disable();
WakelockPlus.disable();
widget.onVideoEnded();
}
}
@@ -155,8 +155,8 @@ class _VideoPlayerState extends State<VideoPlayer> {
Future<void> initializePlayer() async {
try {
videoPlayerController = widget.file == null
? VideoPlayerController.network(
widget.url!,
? VideoPlayerController.networkUrl(
Uri.parse(widget.url!),
httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"},
)
: VideoPlayerController.file(widget.file!);
@@ -210,8 +210,7 @@ class _VideoPlayerState extends State<VideoPlayer> {
child: Center(
child: Stack(
children: [
if (widget.placeholder != null)
widget.placeholder!,
if (widget.placeholder != null) widget.placeholder!,
const Center(
child: ImmichLoadingIndicator(),
),

View File

@@ -90,7 +90,7 @@ class BackgroundService {
requireUnmetered,
requireCharging,
triggerUpdateDelay,
triggerMaxDelay
triggerMaxDelay,
],
);
return ok;

View File

@@ -511,7 +511,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
state = state.copyWith(
selectedAlbumsBackupAssetsIds: {
...state.selectedAlbumsBackupAssetsIds,
deviceAssetId
deviceAssetId,
},
allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId],
);

View File

@@ -149,16 +149,30 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
}
Future<bool> _startUpload(Iterable<Asset> allManualUploads) async {
bool hasErrors = false;
try {
_backupProvider.updateBackupProgress(BackUpProgressEnum.manualInProgress);
if (ref.read(galleryPermissionNotifier.notifier).hasPermission) {
await PhotoManager.clearFileCache();
Set<AssetEntity> allUploadAssets = allManualUploads
.where((e) => e.isLocal && e.local != null)
.map((e) => e.local!)
.toSet();
// We do not have 1:1 mapping of all AssetEntity fields to Asset. This results in cases
// where platform specific fields such as `subtype` used to detect platform specific assets such as
// LivePhoto in iOS is lost when we directly fetch the local asset from Asset using Asset.local
List<AssetEntity?> allAssetsFromDevice = await Future.wait(
allManualUploads
// Filter local only assets
.where((e) => e.isLocal && !e.isRemote)
.map((e) => e.local!.obtainForNewProperties()),
);
if (allAssetsFromDevice.length != allManualUploads.length) {
_log.warning(
'[_startUpload] Refreshed upload list -> ${allManualUploads.length - allAssetsFromDevice.length} asset will not be uploaded',
);
}
Set<AssetEntity> allUploadAssets = allAssetsFromDevice.nonNulls.toSet();
if (allUploadAssets.isEmpty) {
debugPrint("[_startUpload] No Assets to upload - Abort Process");
@@ -213,7 +227,7 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
'[_startUpload] Manual Upload Completed - success: ${state.successfulUploads},'
' failed: ${state.totalAssetsToUpload - state.successfulUploads}',
);
bool hasErrors = false;
// User cancelled upload
if (!ok && state.cancelToken.isCancelled) {
await _localNotificationService.showOrUpdateManualUploadStatus(
@@ -237,32 +251,29 @@ class ManualUploadNotifier extends StateNotifier<ManualUploadState> {
presentBanner: true,
);
}
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
_handleAppInActivity();
await _backupProvider.notifyBackgroundServiceCanRun();
return !hasErrors;
} else {
openAppSettings();
debugPrint("[_startUpload] Do not have permission to the gallery");
}
} catch (e) {
debugPrint("ERROR _startUpload: ${e.toString()}");
hasErrors = true;
} finally {
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
_handleAppInActivity();
await _localNotificationService.closeNotification(
LocalNotificationService.manualUploadDetailedNotificationID,
);
await _backupProvider.notifyBackgroundServiceCanRun();
}
_backupProvider.updateBackupProgress(BackUpProgressEnum.idle);
_handleAppInActivity();
await _localNotificationService.closeNotification(
LocalNotificationService.manualUploadDetailedNotificationID,
);
await _backupProvider.notifyBackgroundServiceCanRun();
return false;
return !hasErrors;
}
void _handleAppInActivity() {
final appState = ref.read(appStateProvider.notifier).getAppState();
// The app is currently in background. Perform the necessary cleanups which
// are on-hold for upload completion
if (appState != AppStateEnum.active || appState != AppStateEnum.resumed) {
if (appState != AppStateEnum.active && appState != AppStateEnum.resumed) {
ref.read(appStateProvider.notifier).handleAppInactivity();
}
}

View File

@@ -174,7 +174,7 @@ class AlbumInfoCard extends HookConsumerWidget {
bottom: 10,
right: 25,
child: buildSelectedTextBox(),
)
),
],
),
),
@@ -218,7 +218,7 @@ class AlbumInfoCard extends HookConsumerWidget {
}),
future: albumInfo.assetCount,
),
)
),
],
),
),

View File

@@ -212,7 +212,7 @@ class CurrentUploadingAssetInfoBox extends HookConsumerWidget {
Text(
" ${uploadProgress.toStringAsFixed(0)}%",
style: const TextStyle(fontSize: 12),
)
),
],
),
),

View File

@@ -247,7 +247,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
child: Wrap(
children: [
...buildSelectedAlbumNameChip(),
...buildExcludedAlbumNameChip()
...buildExcludedAlbumNameChip(),
],
),
),
@@ -301,7 +301,7 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
.watch(backupProvider)
.availableAlbums
.length
.toString()
.toString(),
],
),
style: const TextStyle(

View File

@@ -26,7 +26,7 @@ import 'package:immich_mobile/shared/ui/confirm_dialog.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:wakelock/wakelock.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
class BackupControllerPage extends HookConsumerWidget {
const BackupControllerPage({Key? key}) : super(key: key);
@@ -114,7 +114,7 @@ class BackupControllerPage extends HookConsumerWidget {
);
return;
}
Wakelock.enable();
WakelockPlus.enable();
const limit = 100;
final toDelete = await ref
.read(backupVerificationServiceProvider)
@@ -140,7 +140,7 @@ class BackupControllerPage extends HookConsumerWidget {
);
}
} finally {
Wakelock.disable();
WakelockPlus.disable();
checkInProgress.value = false;
}
}
@@ -202,7 +202,7 @@ class BackupControllerPage extends HookConsumerWidget {
child: const Text('backup_controller_page_storage_format').tr(
args: [
backupState.serverInfo.diskUse,
backupState.serverInfo.diskSize
backupState.serverInfo.diskSize,
],
),
),
@@ -256,7 +256,7 @@ class BackupControllerPage extends HookConsumerWidget {
),
),
),
)
),
],
),
),
@@ -624,7 +624,7 @@ class BackupControllerPage extends HookConsumerWidget {
style: TextStyle(fontSize: 12),
).tr(),
buildSelectedAlbumName(),
buildExcludedAlbumName()
buildExcludedAlbumName(),
],
),
),
@@ -776,7 +776,7 @@ class BackupControllerPage extends HookConsumerWidget {
const Divider(),
const CurrentUploadingAssetInfoBox(),
if (!hasExclusiveAccess) buildBackgroundBackupInfo(),
buildBackupButton()
buildBackupButton(),
],
),
),

View File

@@ -129,7 +129,7 @@ class FailedBackupStatusPage extends HookConsumerWidget {
],
),
),
)
),
],
),
),

View File

@@ -83,7 +83,7 @@ class FavoritesPage extends HookConsumerWidget {
style: TextStyle(fontSize: 14),
),
onTap: processing.value ? null : unfavorite,
)
),
],
),
),
@@ -108,7 +108,7 @@ class FavoritesPage extends HookConsumerWidget {
selectionActive: selectionEnabledHook.value,
listener: selectionListener,
),
if (selectionEnabledHook.value) buildBottomBar()
if (selectionEnabledHook.value) buildBottomBar(),
],
),
),

View File

@@ -57,7 +57,7 @@ class GroupDividerTitle extends ConsumerWidget {
Icons.check_circle_outline_rounded,
color: Colors.grey,
),
)
),
],
),
);

View File

@@ -89,7 +89,7 @@ class ImmichAssetGrid extends HookConsumerWidget {
perRow.value = 7 - scaleFactor.value.toInt();
}
};
})
}),
},
child: ImmichAssetGridView(
onRefresh: onRefresh,

View File

@@ -225,7 +225,7 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
right: i + 1 == num ? 0.0 : widget.margin,
),
color: Colors.grey,
)
),
],
);
}
@@ -300,7 +300,13 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
}
Text _labelBuilder(int pos) {
final date = widget.renderList.elements[pos].date;
final maxLength = widget.renderList.elements.length;
if (pos < 0 || pos >= maxLength) {
return const Text("");
}
final date = widget.renderList.elements[pos % maxLength].date;
return Text(
DateFormat.yMMMM().format(date),
style: const TextStyle(
@@ -335,7 +341,8 @@ class ImmichAssetGridViewState extends State<ImmichAssetGridView> {
itemBuilder: _itemBuilder,
itemPositionsListener: _itemPositionsListener,
itemScrollController: _itemScrollController,
itemCount: widget.renderList.elements.length + (widget.topWidget != null ? 1 : 0),
itemCount: widget.renderList.elements.length +
(widget.topWidget != null ? 1 : 0),
addRepaintBoundaries: true,
);

View File

@@ -155,7 +155,7 @@ class ControlBottomAppBar extends ConsumerWidget {
if (hasRemote)
const SliverToBoxAdapter(
child: SizedBox(height: 200),
)
),
],
),
);

View File

@@ -1,7 +1,8 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/home/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
@@ -29,7 +30,7 @@ class HomePageAppBar extends ConsumerWidget implements PreferredSizeWidget {
backupState.backgroundBackup || backupState.autoBackup;
final ServerInfoState serverInfoState = ref.watch(serverInfoProvider);
AuthenticationState authState = ref.watch(authenticationProvider);
final user = Store.get(StoreKey.currentUser);
buildProfilePhoto() {
if (authState.profileImagePath.isEmpty) {
return IconButton(
@@ -47,9 +48,10 @@ class HomePageAppBar extends ConsumerWidget implements PreferredSizeWidget {
onTap: () {
Scaffold.of(context).openDrawer();
},
child: const UserCircleAvatar(
child: UserCircleAvatar(
radius: 18,
size: 33,
user: user,
),
);
}

View File

@@ -108,7 +108,7 @@ class ProfileDrawer extends HookConsumerWidget {
buildSignOutButton(),
],
),
const ServerInfoBox()
const ServerInfoBox(),
],
),
);

View File

@@ -3,7 +3,8 @@ import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:image_picker/image_picker.dart';
import 'package:immich_mobile/modules/home/providers/upload_profile_image.provider.dart';
import 'package:immich_mobile/modules/home/ui/user_circle_avatar.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/user_circle_avatar.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
@@ -19,11 +20,13 @@ class ProfileDrawerHeader extends HookConsumerWidget {
final uploadProfileImageStatus =
ref.watch(uploadProfileImageProvider).status;
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
final user = Store.get(StoreKey.currentUser);
buildUserProfileImage() {
var userImage = const UserCircleAvatar(
var userImage = UserCircleAvatar(
radius: 35,
size: 66,
user: user,
);
if (authState.profileImagePath.isEmpty) {
@@ -153,7 +156,7 @@ class ProfileDrawerHeader extends HookConsumerWidget {
Text(
authState.userEmail,
style: Theme.of(context).textTheme.labelMedium,
)
),
],
),
);

View File

@@ -1,44 +0,0 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
class UserCircleAvatar extends ConsumerWidget {
final double radius;
final double size;
const UserCircleAvatar({super.key, required this.radius, required this.size});
@override
Widget build(BuildContext context, WidgetRef ref) {
AuthenticationState authState = ref.watch(authenticationProvider);
var profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${authState.userId}?d=${Random().nextInt(1024)}';
return CircleAvatar(
backgroundColor: Theme.of(context).primaryColor,
radius: radius,
child: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: FadeInImage(
fit: BoxFit.cover,
placeholder: MemoryImage(kTransparentImage),
width: size,
height: size,
image: NetworkImage(
profileImageUrl,
headers: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"
},
),
fadeInDuration: const Duration(milliseconds: 200),
imageErrorBuilder: (context, error, stackTrace) =>
Image.memory(kTransparentImage),
),
),
);
}
}

View File

@@ -221,7 +221,7 @@ class HomePage extends HookConsumerWidget {
namedArgs: {
"album": album.name,
"added": result.successfullyAdded.toString(),
"failed": result.alreadyInAlbum.length.toString()
"failed": result.alreadyInAlbum.length.toString(),
},
),
);
@@ -323,7 +323,7 @@ class HomePage extends HookConsumerWidget {
).tr(),
),
),
)
),
],
),
);
@@ -365,7 +365,7 @@ class HomePage extends HookConsumerWidget {
enabled: !processing.value,
selectionAssetState: selectionAssetState.value,
),
if (processing.value) const Center(child: ImmichLoadingIndicator())
if (processing.value) const Center(child: ImmichLoadingIndicator()),
],
),
);

View File

@@ -94,7 +94,7 @@ class ChangePasswordForm extends HookConsumerWidget {
),
],
),
)
),
],
),
),

View File

@@ -110,7 +110,7 @@ class MemoryCard extends HookConsumerWidget {
left: 18.0,
bottom: 18.0,
child: buildTitle(),
)
),
],
),
);

View File

@@ -153,6 +153,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
child = buildRequestPermission();
break;
case PermissionStatus.granted:
case PermissionStatus.provisional:
child = buildPermissionGranted();
break;
case PermissionStatus.restricted:
@@ -183,7 +184,7 @@ class PermissionOnboardingPage extends HookConsumerWidget {
),
TextButton(
child: const Text('permission_onboarding_log_out').tr(),
onPressed: () {
onPressed: () {
ref.read(authenticationProvider.notifier).logout();
AutoRouter.of(context).replace(
const LoginRoute(),

View File

@@ -44,7 +44,7 @@ class PartnerPage extends HookConsumerWidget {
Text("${u.firstName} ${u.lastName}"),
],
),
)
),
],
);
},
@@ -151,7 +151,7 @@ class PartnerPage extends HookConsumerWidget {
availableUsers.whenOrNull(data: (data) => addNewUsersHandler),
icon: const Icon(Icons.person_add),
tooltip: "partner_page_add_partner".tr(),
)
),
],
),
body: buildUserList(partners),

View File

@@ -50,7 +50,7 @@ class CuratedPeopleRow extends StatelessWidget {
itemBuilder: (context, index) {
final person = content[index];
final headers = {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}"
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
};
return Padding(
padding: const EdgeInsets.only(right: 18.0),
@@ -102,7 +102,7 @@ class CuratedPeopleRow extends StatelessWidget {
fontSize: 13.0,
),
),
)
),
],
),
),

View File

@@ -39,7 +39,7 @@ class SearchSuggestionList extends ConsumerWidget {
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
)
),
],
),
),

View File

@@ -46,7 +46,7 @@ class ThumbnailWithInfo extends StatelessWidget {
imageUrl: imageUrl!,
httpHeaders: {
"Authorization":
"Bearer ${Store.get(StoreKey.accessToken)}"
"Bearer ${Store.get(StoreKey.accessToken)}",
},
errorWidget: (context, url, error) =>
const Icon(Icons.image_not_supported_outlined),

View File

@@ -56,7 +56,7 @@ class PersonResultPage extends HookConsumerWidget {
style: TextStyle(fontWeight: FontWeight.bold),
),
onTap: showEditNameDialog,
)
),
],
),
);
@@ -134,7 +134,7 @@ class PersonResultPage extends HookConsumerWidget {
getFaceThumbnailUrl(personId),
headers: {
"Authorization":
"Bearer ${isar_store.Store.get(isar_store.StoreKey.accessToken)}"
"Bearer ${isar_store.Store.get(isar_store.StoreKey.accessToken)}",
},
),
),

View File

@@ -42,7 +42,7 @@ class SettingsPage extends HookConsumerWidget {
const AssetListSettings(),
const NotificationSetting(),
// const ExperimentalSettings(),
const AdvancedSettings()
const AdvancedSettings(),
],
).toList(),
],

View File

@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/album/models/asset_selection_page_result.model.dart';
import 'package:immich_mobile/modules/album/views/album_options_part.dart';
import 'package:immich_mobile/modules/album/views/album_viewer_page.dart';
import 'package:immich_mobile/modules/album/views/asset_selection_page.dart';
import 'package:immich_mobile/modules/album/views/create_album_page.dart';
@@ -74,7 +75,7 @@ part 'router.gr.dart';
AutoRoute(page: HomePage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SearchPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SharingPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: LibraryPage, guards: [AuthGuard, DuplicateGuard])
AutoRoute(page: LibraryPage, guards: [AuthGuard, DuplicateGuard]),
],
transitionsBuilder: TransitionsBuilders.fadeIn,
),
@@ -152,6 +153,7 @@ part 'router.gr.dart';
),
AutoRoute(page: AllPeoplePage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: MemoryPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: AlbumOptionsPage, guards: [AuthGuard, DuplicateGuard]),
],
)
class AppRouter extends _$AppRouter {

View File

@@ -296,6 +296,16 @@ class _$AppRouter extends RootStackRouter {
),
);
},
AlbumOptionsRoute.name: (routeData) {
final args = routeData.argsAs<AlbumOptionsRouteArgs>();
return MaterialPageX<dynamic>(
routeData: routeData,
child: AlbumOptionsPage(
key: args.key,
album: args.album,
),
);
},
HomeRoute.name: (routeData) {
return MaterialPageX<dynamic>(
routeData: routeData,
@@ -595,6 +605,14 @@ class _$AppRouter extends RootStackRouter {
duplicateGuard,
],
),
RouteConfig(
AlbumOptionsRoute.name,
path: '/album-options-page',
guards: [
authGuard,
duplicateGuard,
],
),
];
}
@@ -1319,6 +1337,40 @@ class MemoryRouteArgs {
}
}
/// generated route for
/// [AlbumOptionsPage]
class AlbumOptionsRoute extends PageRouteInfo<AlbumOptionsRouteArgs> {
AlbumOptionsRoute({
Key? key,
required Album album,
}) : super(
AlbumOptionsRoute.name,
path: '/album-options-page',
args: AlbumOptionsRouteArgs(
key: key,
album: album,
),
);
static const String name = 'AlbumOptionsRoute';
}
class AlbumOptionsRouteArgs {
const AlbumOptionsRouteArgs({
this.key,
required this.album,
});
final Key? key;
final Album album;
@override
String toString() {
return 'AlbumOptionsRouteArgs{key: $key, album: $album}';
}
}
/// generated route for
/// [HomePage]
class HomeRoute extends PageRouteInfo<void> {

View File

@@ -1,7 +1,7 @@
import 'package:openapi/api.dart';
class ServerInfoState {
final ServerVersionReponseDto serverVersion;
final ServerVersionResponseDto serverVersion;
final bool isVersionMismatch;
final String versionMismatchErrorMessage;
@@ -12,7 +12,7 @@ class ServerInfoState {
});
ServerInfoState copyWith({
ServerVersionReponseDto? serverVersion,
ServerVersionResponseDto? serverVersion,
bool? isVersionMismatch,
String? versionMismatchErrorMessage,
}) {

View File

@@ -157,7 +157,7 @@ User _userDeserialize(
isPartnerSharedBy: reader.readBoolOrNull(offsets[4]) ?? false,
isPartnerSharedWith: reader.readBoolOrNull(offsets[5]) ?? false,
lastName: reader.readString(offsets[6]),
memoryEnabled: reader.readBoolOrNull(offsets[7]) ?? true,
memoryEnabled: reader.readBoolOrNull(offsets[7]),
profileImagePath: reader.readStringOrNull(offsets[8]) ?? '',
updatedAt: reader.readDateTime(offsets[9]),
);
@@ -186,7 +186,7 @@ P _userDeserializeProp<P>(
case 6:
return (reader.readString(offset)) as P;
case 7:
return (reader.readBoolOrNull(offset) ?? true) as P;
return (reader.readBoolOrNull(offset)) as P;
case 8:
return (reader.readStringOrNull(offset) ?? '') as P;
case 9:
@@ -979,8 +979,24 @@ extension UserQueryFilter on QueryBuilder<User, User, QFilterCondition> {
});
}
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledIsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'memoryEnabled',
));
});
}
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledIsNotNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNotNull(
property: r'memoryEnabled',
));
});
}
QueryBuilder<User, User, QAfterFilterCondition> memoryEnabledEqualTo(
bool value) {
bool? value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'memoryEnabled',
@@ -1661,7 +1677,7 @@ extension UserQueryProperty on QueryBuilder<User, User, QQueryProperty> {
});
}
QueryBuilder<User, bool, QQueryOperations> memoryEnabledProperty() {
QueryBuilder<User, bool?, QQueryOperations> memoryEnabledProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'memoryEnabled');
});

View File

@@ -0,0 +1,7 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/user.provider.dart';
final isAdminProvider = Provider<bool>((ref) {
final currentUser = ref.watch(currentUserProvider);
return currentUser?.isAdmin ?? false; // Default to non-admin if no user
});

View File

@@ -21,6 +21,7 @@ enum AppStateEnum {
paused,
resumed,
detached,
hidden,
}
class AppStateNotiifer extends StateNotifier<AppStateEnum> {
@@ -84,6 +85,10 @@ class AppStateNotiifer extends StateNotifier<AppStateEnum> {
state = AppStateEnum.detached;
ref.watch(manualUploadProvider.notifier).cancelBackup();
}
void handleAppHidden() {
state = AppStateEnum.hidden;
}
}
final appStateProvider =

View File

@@ -10,7 +10,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
ServerInfoNotifier(this._serverInfoService)
: super(
ServerInfoState(
serverVersion: ServerVersionReponseDto(
serverVersion: ServerVersionResponseDto(
major: 0,
patch_: 0,
minor: 0,
@@ -23,7 +23,7 @@ class ServerInfoNotifier extends StateNotifier<ServerInfoState> {
final ServerInfoService _serverInfoService;
getServerVersion() async {
ServerVersionReponseDto? serverVersion =
ServerVersionResponseDto? serverVersion =
await _serverInfoService.getServerVersion();
if (serverVersion == null) {

View File

@@ -102,7 +102,7 @@ class LocalNotificationService {
cancelUploadActionID,
'Cancel',
showsUserInterface: true,
)
),
]
: null,
)

View File

@@ -24,7 +24,7 @@ class ServerInfoService {
}
}
Future<ServerVersionReponseDto?> getServerVersion() async {
Future<ServerVersionResponseDto?> getServerVersion() async {
try {
return await _apiService.serverInfoApi.getServerVersion();
} catch (e) {

View File

@@ -16,7 +16,7 @@ class IgnorableChangeNotifier extends ChangeNotifier {
if (_ignorableListeners == null) {
AssertionError([
'A $runtimeType was used after being disposed.',
'Once you have called dispose() on a $runtimeType, it can no longer be used.'
'Once you have called dispose() on a $runtimeType, it can no longer be used.',
]);
}
return true;

View File

@@ -15,7 +15,7 @@ class ShareDialog extends StatelessWidget {
margin: const EdgeInsets.only(top: 12),
child: const Text('share_dialog_preparing')
.tr(),
)
),
],
),
);

View File

@@ -0,0 +1,75 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/models/user.dart';
import 'package:immich_mobile/shared/ui/transparent_image.dart';
// ignore: must_be_immutable
class UserCircleAvatar extends ConsumerWidget {
final User user;
double radius;
double size;
bool useRandomBackgroundColor;
UserCircleAvatar({
super.key,
this.radius = 22,
this.size = 44,
this.useRandomBackgroundColor = false,
required this.user,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
final randomColors = [
Colors.red[200],
Colors.blue[200],
Colors.green[200],
Colors.yellow[200],
Colors.purple[200],
Colors.orange[200],
Colors.pink[200],
Colors.teal[200],
Colors.indigo[200],
Colors.cyan[200],
Colors.brown[200],
];
final profileImageUrl =
'${Store.get(StoreKey.serverEndpoint)}/user/profile-image/${user.id}?d=${Random().nextInt(1024)}';
return CircleAvatar(
backgroundColor: useRandomBackgroundColor
? randomColors[Random().nextInt(randomColors.length)]
: Theme.of(context).primaryColor,
radius: radius,
child: user.profileImagePath == ""
? Text(
user.firstName[0],
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black,
),
)
: ClipRRect(
borderRadius: BorderRadius.circular(50),
child: FadeInImage(
fit: BoxFit.cover,
placeholder: MemoryImage(kTransparentImage),
width: size,
height: size,
image: NetworkImage(
profileImageUrl,
headers: {
"Authorization": "Bearer ${Store.get(StoreKey.accessToken)}",
},
),
fadeInDuration: const Duration(milliseconds: 200),
imageErrorBuilder: (context, error, stackTrace) =>
Image.memory(kTransparentImage),
),
),
);
}
}

View File

@@ -47,7 +47,7 @@ class AppLogDetailPage extends HookConsumerWidget {
size: 16.0,
color: Theme.of(context).primaryColor,
),
)
),
],
),
Container(
@@ -106,7 +106,7 @@ class AppLogDetailPage extends HookConsumerWidget {
size: 16.0,
color: Theme.of(context).primaryColor,
),
)
),
],
),
Container(
@@ -181,7 +181,7 @@ class AppLogDetailPage extends HookConsumerWidget {
if (logMessage.context1 != null)
buildLogContext1(logMessage.context1.toString()),
if (logMessage.context2 != null)
buildStackMessage(logMessage.context2.toString())
buildStackMessage(logMessage.context2.toString()),
],
),
),

View File

@@ -148,7 +148,7 @@ class TabControllerPage extends HookConsumerWidget {
color: Theme.of(context).primaryColor,
),
),
)
),
],
);
}
@@ -159,7 +159,7 @@ class TabControllerPage extends HookConsumerWidget {
const HomeRoute(),
SearchRoute(),
const SharingRoute(),
const LibraryRoute()
const LibraryRoute(),
],
builder: (context, child, animation) {
final tabsRouter = AutoTabsRouter.of(context);

View File

@@ -3,6 +3,7 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/shared/providers/release_info.provider.dart';
import 'package:immich_mobile/shared/providers/admin_provider.dart';
import 'package:url_launcher/url_launcher.dart';
class VersionAnnouncementOverlay extends HookConsumerWidget {
@@ -12,6 +13,12 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isAdmin = ref.watch(isAdminProvider);
if (!isAdmin) {
return const SizedBox.shrink(); // Don't show anything for non-admins
}
void goToReleaseNote() async {
final Uri url =
Uri.parse('https://github.com/immich-app/immich/releases/latest');
@@ -92,7 +99,7 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
text:
"version_announcement_overlay_text_3"
.tr(),
)
),
],
),
),
@@ -119,7 +126,7 @@ class VersionAnnouncementOverlay extends HookConsumerWidget {
),
).tr(),
),
)
),
],
),
),

View File

@@ -15,6 +15,7 @@ doc/AlbumCountResponseDto.md
doc/AlbumResponseDto.md
doc/AllJobStatusResponseDto.md
doc/AssetApi.md
doc/AssetBulkUpdateDto.md
doc/AssetBulkUploadCheckDto.md
doc/AssetBulkUploadCheckItem.md
doc/AssetBulkUploadCheckResponseDto.md
@@ -22,6 +23,8 @@ doc/AssetBulkUploadCheckResult.md
doc/AssetFileUploadResponseDto.md
doc/AssetIdsDto.md
doc/AssetIdsResponseDto.md
doc/AssetJobName.md
doc/AssetJobsDto.md
doc/AssetResponseDto.md
doc/AssetStatsResponseDto.md
doc/AssetTypeEnum.md
@@ -45,6 +48,7 @@ doc/DeleteAssetDto.md
doc/DeleteAssetResponseDto.md
doc/DeleteAssetStatus.md
doc/DownloadArchiveInfo.md
doc/DownloadInfoDto.md
doc/DownloadResponseDto.md
doc/ExifResponseDto.md
doc/ImportAssetDto.md
@@ -83,12 +87,13 @@ doc/SearchExploreResponseDto.md
doc/SearchFacetCountResponseDto.md
doc/SearchFacetResponseDto.md
doc/SearchResponseDto.md
doc/ServerFeaturesDto.md
doc/ServerInfoApi.md
doc/ServerInfoResponseDto.md
doc/ServerMediaTypesResponseDto.md
doc/ServerPingResponse.md
doc/ServerStatsResponseDto.md
doc/ServerVersionReponseDto.md
doc/ServerVersionResponseDto.md
doc/SharedLinkApi.md
doc/SharedLinkCreateDto.md
doc/SharedLinkEditDto.md
@@ -157,6 +162,7 @@ lib/model/api_key_create_dto.dart
lib/model/api_key_create_response_dto.dart
lib/model/api_key_response_dto.dart
lib/model/api_key_update_dto.dart
lib/model/asset_bulk_update_dto.dart
lib/model/asset_bulk_upload_check_dto.dart
lib/model/asset_bulk_upload_check_item.dart
lib/model/asset_bulk_upload_check_response_dto.dart
@@ -164,6 +170,8 @@ lib/model/asset_bulk_upload_check_result.dart
lib/model/asset_file_upload_response_dto.dart
lib/model/asset_ids_dto.dart
lib/model/asset_ids_response_dto.dart
lib/model/asset_job_name.dart
lib/model/asset_jobs_dto.dart
lib/model/asset_response_dto.dart
lib/model/asset_stats_response_dto.dart
lib/model/asset_type_enum.dart
@@ -186,6 +194,7 @@ lib/model/delete_asset_dto.dart
lib/model/delete_asset_response_dto.dart
lib/model/delete_asset_status.dart
lib/model/download_archive_info.dart
lib/model/download_info_dto.dart
lib/model/download_response_dto.dart
lib/model/exif_response_dto.dart
lib/model/import_asset_dto.dart
@@ -219,11 +228,12 @@ lib/model/search_explore_response_dto.dart
lib/model/search_facet_count_response_dto.dart
lib/model/search_facet_response_dto.dart
lib/model/search_response_dto.dart
lib/model/server_features_dto.dart
lib/model/server_info_response_dto.dart
lib/model/server_media_types_response_dto.dart
lib/model/server_ping_response.dart
lib/model/server_stats_response_dto.dart
lib/model/server_version_reponse_dto.dart
lib/model/server_version_response_dto.dart
lib/model/shared_link_create_dto.dart
lib/model/shared_link_edit_dto.dart
lib/model/shared_link_response_dto.dart
@@ -268,6 +278,7 @@ test/api_key_create_response_dto_test.dart
test/api_key_response_dto_test.dart
test/api_key_update_dto_test.dart
test/asset_api_test.dart
test/asset_bulk_update_dto_test.dart
test/asset_bulk_upload_check_dto_test.dart
test/asset_bulk_upload_check_item_test.dart
test/asset_bulk_upload_check_response_dto_test.dart
@@ -275,6 +286,8 @@ test/asset_bulk_upload_check_result_test.dart
test/asset_file_upload_response_dto_test.dart
test/asset_ids_dto_test.dart
test/asset_ids_response_dto_test.dart
test/asset_job_name_test.dart
test/asset_jobs_dto_test.dart
test/asset_response_dto_test.dart
test/asset_stats_response_dto_test.dart
test/asset_type_enum_test.dart
@@ -298,6 +311,7 @@ test/delete_asset_dto_test.dart
test/delete_asset_response_dto_test.dart
test/delete_asset_status_test.dart
test/download_archive_info_test.dart
test/download_info_dto_test.dart
test/download_response_dto_test.dart
test/exif_response_dto_test.dart
test/import_asset_dto_test.dart
@@ -336,12 +350,13 @@ test/search_explore_response_dto_test.dart
test/search_facet_count_response_dto_test.dart
test/search_facet_response_dto_test.dart
test/search_response_dto_test.dart
test/server_features_dto_test.dart
test/server_info_api_test.dart
test/server_info_response_dto_test.dart
test/server_media_types_response_dto_test.dart
test/server_ping_response_test.dart
test/server_stats_response_dto_test.dart
test/server_version_reponse_dto_test.dart
test/server_version_response_dto_test.dart
test/shared_link_api_test.dart
test/shared_link_create_dto_test.dart
test/shared_link_edit_dto_test.dart

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.73.0
- API version: 1.74.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -91,7 +91,7 @@ Class | Method | HTTP request | Description
*AssetApi* | [**checkDuplicateAsset**](doc//AssetApi.md#checkduplicateasset) | **POST** /asset/check |
*AssetApi* | [**checkExistingAssets**](doc//AssetApi.md#checkexistingassets) | **POST** /asset/exist |
*AssetApi* | [**deleteAsset**](doc//AssetApi.md#deleteasset) | **DELETE** /asset |
*AssetApi* | [**downloadArchive**](doc//AssetApi.md#downloadarchive) | **POST** /asset/download |
*AssetApi* | [**downloadArchive**](doc//AssetApi.md#downloadarchive) | **POST** /asset/download/archive |
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **POST** /asset/download/{id} |
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
@@ -101,15 +101,17 @@ Class | Method | HTTP request | Description
*AssetApi* | [**getByTimeBucket**](doc//AssetApi.md#getbytimebucket) | **GET** /asset/time-bucket |
*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
*AssetApi* | [**getDownloadInfo**](doc//AssetApi.md#getdownloadinfo) | **GET** /asset/download |
*AssetApi* | [**getDownloadInfo**](doc//AssetApi.md#getdownloadinfo) | **POST** /asset/download/info |
*AssetApi* | [**getMapMarkers**](doc//AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
*AssetApi* | [**getMemoryLane**](doc//AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
*AssetApi* | [**getTimeBuckets**](doc//AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
*AssetApi* | [**importFile**](doc//AssetApi.md#importfile) | **POST** /asset/import |
*AssetApi* | [**runAssetJobs**](doc//AssetApi.md#runassetjobs) | **POST** /asset/jobs |
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file/{id} |
*AssetApi* | [**updateAsset**](doc//AssetApi.md#updateasset) | **PUT** /asset/{id} |
*AssetApi* | [**updateAssets**](doc//AssetApi.md#updateassets) | **PUT** /asset |
*AssetApi* | [**uploadFile**](doc//AssetApi.md#uploadfile) | **POST** /asset/upload |
*AuthenticationApi* | [**adminSignUp**](doc//AuthenticationApi.md#adminsignup) | **POST** /auth/admin-sign-up |
*AuthenticationApi* | [**changePassword**](doc//AuthenticationApi.md#changepassword) | **POST** /auth/change-password |
@@ -139,6 +141,7 @@ Class | Method | HTTP request | Description
*SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore |
*SearchApi* | [**getSearchConfig**](doc//SearchApi.md#getsearchconfig) | **GET** /search/config |
*SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search |
*ServerInfoApi* | [**getServerFeatures**](doc//ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features |
*ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info |
*ServerInfoApi* | [**getServerVersion**](doc//ServerInfoApi.md#getserverversion) | **GET** /server-info/version |
*ServerInfoApi* | [**getStats**](doc//ServerInfoApi.md#getstats) | **GET** /server-info/stats |
@@ -187,6 +190,7 @@ Class | Method | HTTP request | Description
- [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
- [AlbumResponseDto](doc//AlbumResponseDto.md)
- [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)
- [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md)
- [AssetBulkUploadCheckDto](doc//AssetBulkUploadCheckDto.md)
- [AssetBulkUploadCheckItem](doc//AssetBulkUploadCheckItem.md)
- [AssetBulkUploadCheckResponseDto](doc//AssetBulkUploadCheckResponseDto.md)
@@ -194,6 +198,8 @@ Class | Method | HTTP request | Description
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
- [AssetIdsDto](doc//AssetIdsDto.md)
- [AssetIdsResponseDto](doc//AssetIdsResponseDto.md)
- [AssetJobName](doc//AssetJobName.md)
- [AssetJobsDto](doc//AssetJobsDto.md)
- [AssetResponseDto](doc//AssetResponseDto.md)
- [AssetStatsResponseDto](doc//AssetStatsResponseDto.md)
- [AssetTypeEnum](doc//AssetTypeEnum.md)
@@ -216,6 +222,7 @@ Class | Method | HTTP request | Description
- [DeleteAssetResponseDto](doc//DeleteAssetResponseDto.md)
- [DeleteAssetStatus](doc//DeleteAssetStatus.md)
- [DownloadArchiveInfo](doc//DownloadArchiveInfo.md)
- [DownloadInfoDto](doc//DownloadInfoDto.md)
- [DownloadResponseDto](doc//DownloadResponseDto.md)
- [ExifResponseDto](doc//ExifResponseDto.md)
- [ImportAssetDto](doc//ImportAssetDto.md)
@@ -249,11 +256,12 @@ Class | Method | HTTP request | Description
- [SearchFacetCountResponseDto](doc//SearchFacetCountResponseDto.md)
- [SearchFacetResponseDto](doc//SearchFacetResponseDto.md)
- [SearchResponseDto](doc//SearchResponseDto.md)
- [ServerFeaturesDto](doc//ServerFeaturesDto.md)
- [ServerInfoResponseDto](doc//ServerInfoResponseDto.md)
- [ServerMediaTypesResponseDto](doc//ServerMediaTypesResponseDto.md)
- [ServerPingResponse](doc//ServerPingResponse.md)
- [ServerStatsResponseDto](doc//ServerStatsResponseDto.md)
- [ServerVersionReponseDto](doc//ServerVersionReponseDto.md)
- [ServerVersionResponseDto](doc//ServerVersionResponseDto.md)
- [SharedLinkCreateDto](doc//SharedLinkCreateDto.md)
- [SharedLinkEditDto](doc//SharedLinkEditDto.md)
- [SharedLinkResponseDto](doc//SharedLinkResponseDto.md)

View File

@@ -13,7 +13,7 @@ Method | HTTP request | Description
[**checkDuplicateAsset**](AssetApi.md#checkduplicateasset) | **POST** /asset/check |
[**checkExistingAssets**](AssetApi.md#checkexistingassets) | **POST** /asset/exist |
[**deleteAsset**](AssetApi.md#deleteasset) | **DELETE** /asset |
[**downloadArchive**](AssetApi.md#downloadarchive) | **POST** /asset/download |
[**downloadArchive**](AssetApi.md#downloadarchive) | **POST** /asset/download/archive |
[**downloadFile**](AssetApi.md#downloadfile) | **POST** /asset/download/{id} |
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{id} |
@@ -23,15 +23,17 @@ Method | HTTP request | Description
[**getByTimeBucket**](AssetApi.md#getbytimebucket) | **GET** /asset/time-bucket |
[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
[**getDownloadInfo**](AssetApi.md#getdownloadinfo) | **GET** /asset/download |
[**getDownloadInfo**](AssetApi.md#getdownloadinfo) | **POST** /asset/download/info |
[**getMapMarkers**](AssetApi.md#getmapmarkers) | **GET** /asset/map-marker |
[**getMemoryLane**](AssetApi.md#getmemorylane) | **GET** /asset/memory-lane |
[**getTimeBuckets**](AssetApi.md#gettimebuckets) | **GET** /asset/time-buckets |
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
[**importFile**](AssetApi.md#importfile) | **POST** /asset/import |
[**runAssetJobs**](AssetApi.md#runassetjobs) | **POST** /asset/jobs |
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file/{id} |
[**updateAsset**](AssetApi.md#updateasset) | **PUT** /asset/{id} |
[**updateAssets**](AssetApi.md#updateassets) | **PUT** /asset |
[**uploadFile**](AssetApi.md#uploadfile) | **POST** /asset/upload |
@@ -842,7 +844,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getDownloadInfo**
> DownloadResponseDto getDownloadInfo(assetIds, albumId, userId, archiveSize, key)
> DownloadResponseDto getDownloadInfo(downloadInfoDto, key)
@@ -865,14 +867,11 @@ import 'package:openapi/api.dart';
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetIds = []; // List<String> |
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
final archiveSize = 8.14; // num |
final downloadInfoDto = DownloadInfoDto(); // DownloadInfoDto |
final key = key_example; // String |
try {
final result = api_instance.getDownloadInfo(assetIds, albumId, userId, archiveSize, key);
final result = api_instance.getDownloadInfo(downloadInfoDto, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getDownloadInfo: $e\n');
@@ -883,10 +882,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetIds** | [**List<String>**](String.md)| | [optional] [default to const []]
**albumId** | **String**| | [optional]
**userId** | **String**| | [optional]
**archiveSize** | **num**| | [optional]
**downloadInfoDto** | [**DownloadInfoDto**](DownloadInfoDto.md)| |
**key** | **String**| | [optional]
### Return type
@@ -899,7 +895,7 @@ Name | Type | Description | Notes
### HTTP request headers
- **Content-Type**: Not defined
- **Content-Type**: application/json
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
@@ -1197,6 +1193,60 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **runAssetJobs**
> runAssetJobs(assetJobsDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetJobsDto = AssetJobsDto(); // AssetJobsDto |
try {
api_instance.runAssetJobs(assetJobsDto);
} catch (e) {
print('Exception when calling AssetApi->runAssetJobs: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetJobsDto** | [**AssetJobsDto**](AssetJobsDto.md)| |
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: Not defined
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **searchAsset**
> List<AssetResponseDto> searchAsset(searchAssetDto)
@@ -1372,6 +1422,60 @@ Name | Type | Description | Notes
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **updateAssets**
> updateAssets(assetBulkUpdateDto)
### Example
```dart
import 'package:openapi/api.dart';
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
// TODO Configure API key authorization: api_key
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('api_key').apiKeyPrefix = 'Bearer';
// TODO Configure HTTP Bearer authorization: bearer
// Case 1. Use String Token
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
final api_instance = AssetApi();
final assetBulkUpdateDto = AssetBulkUpdateDto(); // AssetBulkUpdateDto |
try {
api_instance.updateAssets(assetBulkUpdateDto);
} catch (e) {
print('Exception when calling AssetApi->updateAssets: $e\n');
}
```
### Parameters
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetBulkUpdateDto** | [**AssetBulkUpdateDto**](AssetBulkUpdateDto.md)| |
### Return type
void (empty response body)
### Authorization
[cookie](../README.md#cookie), [api_key](../README.md#api_key), [bearer](../README.md#bearer)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: Not defined
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **uploadFile**
> AssetFileUploadResponseDto uploadFile(assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, key, duration, isArchived, isReadOnly, isVisible, livePhotoData, sidecarData)

17
mobile/openapi/doc/AssetBulkUpdateDto.md generated Normal file
View File

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

14
mobile/openapi/doc/AssetJobName.md generated Normal file
View File

@@ -0,0 +1,14 @@
# openapi.model.AssetJobName
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

16
mobile/openapi/doc/AssetJobsDto.md generated Normal file
View File

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

18
mobile/openapi/doc/DownloadInfoDto.md generated Normal file
View File

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

View File

@@ -8,6 +8,7 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. | [optional]
**featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional]
**id** | **String** | Person id. |
**isHidden** | **bool** | Person visibility | [optional]

View File

@@ -8,6 +8,7 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**birthDate** | [**DateTime**](DateTime.md) | |
**id** | **String** | |
**isHidden** | **bool** | |
**name** | **String** | |

View File

@@ -8,6 +8,7 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**birthDate** | [**DateTime**](DateTime.md) | Person date of birth. | [optional]
**featureFaceAssetId** | **String** | Asset is used to get the feature face thumbnail. | [optional]
**isHidden** | **bool** | Person visibility | [optional]
**name** | **String** | Person name. | [optional]

19
mobile/openapi/doc/ServerFeaturesDto.md generated Normal file
View File

@@ -0,0 +1,19 @@
# openapi.model.ServerFeaturesDto
## Load the model package
```dart
import 'package:openapi/api.dart';
```
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**machineLearning** | **bool** | |
**oauth** | **bool** | |
**oauthAutoLaunch** | **bool** | |
**passwordLogin** | **bool** | |
**search** | **bool** | |
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)

View File

@@ -9,6 +9,7 @@ All URIs are relative to */api*
Method | HTTP request | Description
------------- | ------------- | -------------
[**getServerFeatures**](ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features |
[**getServerInfo**](ServerInfoApi.md#getserverinfo) | **GET** /server-info |
[**getServerVersion**](ServerInfoApi.md#getserverversion) | **GET** /server-info/version |
[**getStats**](ServerInfoApi.md#getstats) | **GET** /server-info/stats |
@@ -16,6 +17,43 @@ Method | HTTP request | Description
[**pingServer**](ServerInfoApi.md#pingserver) | **GET** /server-info/ping |
# **getServerFeatures**
> ServerFeaturesDto getServerFeatures()
### Example
```dart
import 'package:openapi/api.dart';
final api_instance = ServerInfoApi();
try {
final result = api_instance.getServerFeatures();
print(result);
} catch (e) {
print('Exception when calling ServerInfoApi->getServerFeatures: $e\n');
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**ServerFeaturesDto**](ServerFeaturesDto.md)
### Authorization
No authorization required
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getServerInfo**
> ServerInfoResponseDto getServerInfo()
@@ -68,7 +106,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getServerVersion**
> ServerVersionReponseDto getServerVersion()
> ServerVersionResponseDto getServerVersion()
@@ -91,7 +129,7 @@ This endpoint does not need any parameter.
### Return type
[**ServerVersionReponseDto**](ServerVersionReponseDto.md)
[**ServerVersionResponseDto**](ServerVersionResponseDto.md)
### Authorization

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