Compare commits
52 Commits
v1.25.0_35
...
v1.27.0_37
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0c59efe7 | ||
|
|
c19d26f4f3 | ||
|
|
2edfc75c8a | ||
|
|
4c977d2c1f | ||
|
|
1425f2ec78 | ||
|
|
b081eda76f | ||
|
|
7f6837c751 | ||
|
|
a467936e73 | ||
|
|
2677ddccaa | ||
|
|
564ace3ddf | ||
|
|
a81ef7497c | ||
|
|
caa7b07398 | ||
|
|
6976a7241e | ||
|
|
172eda3ce5 | ||
|
|
552340add7 | ||
|
|
bd92dde117 | ||
|
|
617c54ab81 | ||
|
|
c76f7804ab | ||
|
|
0799aa2c72 | ||
|
|
b80dca74ef | ||
|
|
f5f00e0f6c | ||
|
|
75d2d82d05 | ||
|
|
5172242f88 | ||
|
|
25e68cf826 | ||
|
|
e527685ebf | ||
|
|
e745cb5e4b | ||
|
|
dfaa4969da | ||
|
|
f980a2f27a | ||
|
|
6b7c97c02a | ||
|
|
fdd9f37abd | ||
|
|
a09bba454c | ||
|
|
4be9aa091b | ||
|
|
33b810de74 | ||
|
|
44ccb1eec1 | ||
|
|
bef38c670c | ||
|
|
025d7bf192 | ||
|
|
5ad2d62039 | ||
|
|
a128833e68 | ||
|
|
87f7b0849a | ||
|
|
4596a8ee01 | ||
|
|
f9b1b12b10 | ||
|
|
68b1655e7f | ||
|
|
658b64df74 | ||
|
|
e344503834 | ||
|
|
bf2760ffef | ||
|
|
db2ed2d881 | ||
|
|
fb0fa742f5 | ||
|
|
3b55cdc0be | ||
|
|
0efcc99f3e | ||
|
|
7a85164a1e | ||
|
|
ba2cda8955 | ||
|
|
9048be4c8e |
134
CODE_OF_CONDUCT.md
Normal file
134
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation
|
||||||
|
in our community a harassment-free experience for everyone, regardless
|
||||||
|
of age, body size, visible or invisible disability, ethnicity, sex
|
||||||
|
characteristics, gender identity and expression, level of experience,
|
||||||
|
education, socio-economic status, nationality, personal appearance,
|
||||||
|
race, religion, or sexual identity and orientation.
|
||||||
|
|
||||||
|
We pledge to act and interact in ways that contribute to an open,
|
||||||
|
welcoming, diverse, inclusive, and healthy community.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment for
|
||||||
|
our community include:
|
||||||
|
|
||||||
|
- Demonstrating empathy and kindness toward other people
|
||||||
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
- Giving and gracefully accepting constructive feedback
|
||||||
|
- Accepting responsibility and apologizing to those affected by our
|
||||||
|
mistakes, and learning from the experience
|
||||||
|
- Focusing on what is best not just for us as individuals, but for the
|
||||||
|
overall community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior include:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery, and sexual attention or
|
||||||
|
advances of any kind
|
||||||
|
- Trolling, insulting or derogatory comments, and personal or
|
||||||
|
political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information, such as a physical or email
|
||||||
|
address, without their explicit permission
|
||||||
|
- Other conduct which could reasonably be considered inappropriate in
|
||||||
|
a professional setting
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Community leaders are responsible for clarifying and enforcing our
|
||||||
|
standards of acceptable behavior and will take appropriate and fair
|
||||||
|
corrective action in response to any behavior that they deem
|
||||||
|
inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
Community leaders have the right and responsibility to remove, edit,
|
||||||
|
or reject comments, commits, code, wiki edits, issues, and other
|
||||||
|
contributions that are not aligned to this Code of Conduct, and will
|
||||||
|
communicate reasons for moderation decisions when appropriate.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all community spaces, and also
|
||||||
|
applies when an individual is officially representing the community in
|
||||||
|
public spaces. Examples of representing our community include using an
|
||||||
|
official e-mail address, posting via an official social media account,
|
||||||
|
or acting as an appointed representative at an online or offline
|
||||||
|
event.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||||
|
may be reported to the community leaders responsible for enforcement
|
||||||
|
at our Discord channel. All complaints
|
||||||
|
will be reviewed and investigated promptly and fairly.
|
||||||
|
|
||||||
|
All community leaders are obligated to respect the privacy and
|
||||||
|
security of the reporter of any incident.
|
||||||
|
|
||||||
|
## Enforcement Guidelines
|
||||||
|
|
||||||
|
Community leaders will follow these Community Impact Guidelines in
|
||||||
|
determining the consequences for any action they deem in violation of
|
||||||
|
this Code of Conduct:
|
||||||
|
|
||||||
|
### 1. Correction
|
||||||
|
|
||||||
|
**Community Impact**: Use of inappropriate language or other behavior
|
||||||
|
deemed unprofessional or unwelcome in the community.
|
||||||
|
|
||||||
|
**Consequence**: A private, written warning from community leaders,
|
||||||
|
providing clarity around the nature of the violation and an
|
||||||
|
explanation of why the behavior was inappropriate. A public apology
|
||||||
|
may be requested.
|
||||||
|
|
||||||
|
### 2. Warning
|
||||||
|
|
||||||
|
**Community Impact**: A violation through a single incident or series
|
||||||
|
of actions.
|
||||||
|
|
||||||
|
**Consequence**: A warning with consequences for continued
|
||||||
|
behavior. No interaction with the people involved, including
|
||||||
|
unsolicited interaction with those enforcing the Code of Conduct, for
|
||||||
|
a specified period of time. This includes avoiding interactions in
|
||||||
|
community spaces as well as external channels like social
|
||||||
|
media. Violating these terms may lead to a temporary or permanent ban.
|
||||||
|
|
||||||
|
### 3. Temporary Ban
|
||||||
|
|
||||||
|
**Community Impact**: A serious violation of community standards,
|
||||||
|
including sustained inappropriate behavior.
|
||||||
|
|
||||||
|
**Consequence**: A temporary ban from any sort of interaction or
|
||||||
|
public communication with the community for a specified period of
|
||||||
|
time. No public or private interaction with the people involved,
|
||||||
|
including unsolicited interaction with those enforcing the Code of
|
||||||
|
Conduct, is allowed during this period. Violating these terms may lead
|
||||||
|
to a permanent ban.
|
||||||
|
|
||||||
|
### 4. Permanent Ban
|
||||||
|
|
||||||
|
**Community Impact**: Demonstrating a pattern of violation of
|
||||||
|
community standards, including sustained inappropriate behavior,
|
||||||
|
harassment of an individual, or aggression toward or disparagement of
|
||||||
|
classes of individuals.
|
||||||
|
|
||||||
|
**Consequence**: A permanent ban from any sort of public interaction
|
||||||
|
within the community.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor
|
||||||
|
Covenant][homepage], version 2.0, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||||
|
|
||||||
|
Community Impact Guidelines were inspired by [Mozilla's code of
|
||||||
|
conduct enforcement ladder](https://github.com/mozilla/diversity).
|
||||||
|
|
||||||
|
[homepage]: https://www.contributor-covenant.org
|
||||||
|
|
||||||
|
For answers to common questions about this code of conduct, see the
|
||||||
|
FAQ at https://www.contributor-covenant.org/faq. Translations are
|
||||||
|
available at https://www.contributor-covenant.org/translations.
|
||||||
28
README.md
28
README.md
@@ -36,20 +36,22 @@
|
|||||||
|
|
||||||
> ⚠️ WARNING: **NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS**. This project is under heavy development, there will be continuous functions, features and api changes.
|
> ⚠️ WARNING: **NOT READY FOR PRODUCTION! DO NOT USE TO STORE YOUR ASSETS**. This project is under heavy development, there will be continuous functions, features and api changes.
|
||||||
|
|
||||||
| | Mobile | Web |
|
| Features | Mobile | Web |
|
||||||
| - | - | - |
|
| - | - | - |
|
||||||
| ☁️ Upload and view videos and photos | Yes | Yes
|
| Upload and view videos and photos | Yes | Yes
|
||||||
| 🔄 Auto backup when the app is opened | Yes | N/A
|
| Auto backup when the app is opened | Yes | N/A
|
||||||
| ☑️ Selective album(s) for backup | Yes | N/A
|
| Selective album(s) for backup | Yes | N/A
|
||||||
| ⬇️ Download photos and videos to local device | Yes | Yes
|
| Download photos and videos to local device | Yes | Yes
|
||||||
| 👪 Multi-user support | Yes | Yes
|
| Multi-user support | Yes | Yes
|
||||||
| 🖼️ Album | Yes | Yes
|
| Album | Yes | Yes
|
||||||
| 🤝 Shared Albums | Yes | Yes
|
| Shared Albums | Yes | Yes
|
||||||
| 🚀 Quick navigation with draggable scrollbar | Yes | Yes
|
| Quick navigation with draggable scrollbar | Yes | Yes
|
||||||
| 🗃️ Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
|
| Support RAW (HEIC, HEIF, DNG, Apple ProRaw) | Yes | Yes
|
||||||
| 🧭 Metadata view (EXIF, map) | Yes | Yes
|
| Metadata view (EXIF, map) | Yes | Yes
|
||||||
| 🔎 Search by metadata, objects and image tags | Yes | No
|
| Search by metadata, objects and image tags | Yes | No
|
||||||
| ⚙️ Administrative functions (user management) | N/A | Yes
|
| Administrative functions (user management) | N/A | Yes
|
||||||
|
| Background backup | Android | N/A
|
||||||
|
| Virtual scroll | N/A | Yes
|
||||||
|
|
||||||
|
|
||||||
<br/>
|
<br/>
|
||||||
|
|||||||
@@ -61,6 +61,6 @@ MAPBOX_KEY=
|
|||||||
####################################################################################
|
####################################################################################
|
||||||
|
|
||||||
# Custom message on the login page, should be written in HTML form.
|
# Custom message on the login page, should be written in HTML form.
|
||||||
# For example VITE_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
|
# For example PUBLIC_LOGIN_PAGE_MESSAGE="This is a demo instance of Immich.<br><br>Email: <i>demo@demo.de</i><br>Password: <i>demo</i>"
|
||||||
|
|
||||||
VITE_LOGIN_PAGE_MESSAGE=
|
PUBLIC_LOGIN_PAGE_MESSAGE=
|
||||||
@@ -6,6 +6,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ../server
|
context: ../server
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: builder
|
||||||
command: npm run start:dev immich
|
command: npm run start:dev immich
|
||||||
volumes:
|
volumes:
|
||||||
- ../server:/usr/src/app
|
- ../server:/usr/src/app
|
||||||
@@ -24,6 +25,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ../machine-learning
|
context: ../machine-learning
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: builder
|
||||||
command: npm run start:dev
|
command: npm run start:dev
|
||||||
volumes:
|
volumes:
|
||||||
- ../machine-learning:/usr/src/app
|
- ../machine-learning:/usr/src/app
|
||||||
@@ -41,6 +43,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ../server
|
context: ../server
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: builder
|
||||||
command: npm run start:dev microservices
|
command: npm run start:dev microservices
|
||||||
volumes:
|
volumes:
|
||||||
- ../server:/usr/src/app
|
- ../server:/usr/src/app
|
||||||
|
|||||||
@@ -9,6 +9,14 @@ upload:
|
|||||||
locale_code: de-DE
|
locale_code: de-DE
|
||||||
- file: mobile/assets/i18n/fr-FR.json
|
- file: mobile/assets/i18n/fr-FR.json
|
||||||
locale_code: fr-FR
|
locale_code: fr-FR
|
||||||
|
- file: mobile/assets/i18n/it-IT.json
|
||||||
|
locale_code: it-IT
|
||||||
|
- file: mobile/assets/i18n/nl-NL.json
|
||||||
|
locale_code: nl-NL
|
||||||
|
- file: mobile/assets/i18n/ko-KR.json
|
||||||
|
locale_code: ko-KR
|
||||||
|
- file: mobile/assets/i18n/da-DK.json
|
||||||
|
locale_code: da-DK
|
||||||
download:
|
download:
|
||||||
files:
|
files:
|
||||||
- file: mobile/assets/i18n/en-US.json
|
- file: mobile/assets/i18n/en-US.json
|
||||||
@@ -17,3 +25,11 @@ download:
|
|||||||
locale_code: de-DE
|
locale_code: de-DE
|
||||||
- file: mobile/assets/i18n/fr-FR.json
|
- file: mobile/assets/i18n/fr-FR.json
|
||||||
locale_code: fr-FR
|
locale_code: fr-FR
|
||||||
|
- file: mobile/assets/i18n/it-IT.json
|
||||||
|
locale_code: it-IT
|
||||||
|
- file: mobile/assets/i18n/nl-NL.json
|
||||||
|
locale_code: nl-NL
|
||||||
|
- file: mobile/assets/i18n/ko-KR.json
|
||||||
|
locale_code: ko-KR
|
||||||
|
- file: mobile/assets/i18n/da-DK.json
|
||||||
|
locale_code: da-DK
|
||||||
|
|||||||
3
mobile/android/.gitignore
vendored
3
mobile/android/.gitignore
vendored
@@ -11,3 +11,6 @@ GeneratedPluginRegistrant.java
|
|||||||
key.properties
|
key.properties
|
||||||
**/*.keystore
|
**/*.keystore
|
||||||
**/*.jks
|
**/*.jks
|
||||||
|
|
||||||
|
# Fastlane
|
||||||
|
/fastlane/report.xml
|
||||||
|
|||||||
@@ -69,26 +69,8 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
|
|||||||
"isEnabled" -> {
|
"isEnabled" -> {
|
||||||
result.success(BackupWorker.isEnabled(ctx))
|
result.success(BackupWorker.isEnabled(ctx))
|
||||||
}
|
}
|
||||||
"disableBatteryOptimizations" -> {
|
"isIgnoringBatteryOptimizations" -> {
|
||||||
if(!BackupWorker.isIgnoringBatteryOptimizations(ctx)) {
|
result.success(BackupWorker.isIgnoringBatteryOptimizations(ctx))
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
|
||||||
val text = args.get(0) as String
|
|
||||||
Toast.makeText(ctx, text, Toast.LENGTH_LONG).show()
|
|
||||||
val intent = Intent(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)
|
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
|
||||||
intent.setData(Uri.parse("package:" + ctx.getPackageName()))
|
|
||||||
try {
|
|
||||||
ctx.startActivity(intent)
|
|
||||||
} catch(e: Exception) {
|
|
||||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
|
||||||
try {
|
|
||||||
ctx.startActivity(intent)
|
|
||||||
} catch (e2: Exception) {
|
|
||||||
return result.success(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.success(true)
|
|
||||||
}
|
}
|
||||||
else -> result.notImplemented()
|
else -> result.notImplemented()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 35,
|
"android.injected.version.code" => 37,
|
||||||
"android.injected.version.name" => "1.25.0",
|
"android.injected.version.name" => "1.27.0",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
* Fixed rendering blank when failed to parse datetime on main timeline
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
* Add cache setting and improve caching mechanism
|
||||||
|
* Persist WiFi + charging settings of background backup
|
||||||
@@ -5,12 +5,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000212">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000222">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="3.608039">
|
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="55.311329">
|
||||||
|
|
||||||
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
|
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="30.070842">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"album_info_card_backup_album_excluded": "AUSGESCHLOSSEN",
|
"album_info_card_backup_album_excluded": "AUSGESCHLOSSEN",
|
||||||
"album_info_card_backup_album_included": "EINGESCHLOSSEN",
|
"album_info_card_backup_album_included": "EINGESCHLOSSEN",
|
||||||
|
"album_thumbnail_card_item": "1 Element",
|
||||||
|
"album_thumbnail_card_items": "{} Elemente",
|
||||||
|
"album_thumbnail_card_shared": " · Geteilt",
|
||||||
"album_viewer_appbar_share_delete": "Album löschen",
|
"album_viewer_appbar_share_delete": "Album löschen",
|
||||||
"album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden",
|
"album_viewer_appbar_share_err_delete": "Album konnte nicht gelöscht werden",
|
||||||
"album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden",
|
"album_viewer_appbar_share_err_leave": "Album konnte nicht verlassen werden",
|
||||||
@@ -9,6 +12,8 @@
|
|||||||
"album_viewer_appbar_share_leave": "Album verlassen",
|
"album_viewer_appbar_share_leave": "Album verlassen",
|
||||||
"album_viewer_appbar_share_remove": "Entferne vom Album",
|
"album_viewer_appbar_share_remove": "Entferne vom Album",
|
||||||
"album_viewer_page_share_add_users": "Nutzer hinzufügen",
|
"album_viewer_page_share_add_users": "Nutzer hinzufügen",
|
||||||
|
"asset_list_settings_subtitle": "",
|
||||||
|
"asset_list_settings_title": "",
|
||||||
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
"backup_album_selection_page_albums_device": "Alben auf dem Gerät ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
|
"backup_album_selection_page_albums_tap": "Tippen um einzuschließen, doppelt tippen um zu entfernen",
|
||||||
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
"backup_album_selection_page_assets_scatter": "Elemente können sich über mehrere Alben verteilen. Daher können diese vor der Sicherung eingeschlossen oder ausgeschlossen werden",
|
||||||
@@ -16,7 +21,26 @@
|
|||||||
"backup_album_selection_page_selection_info": "Auswahl",
|
"backup_album_selection_page_selection_info": "Auswahl",
|
||||||
"backup_album_selection_page_total_assets": "Elemente",
|
"backup_album_selection_page_total_assets": "Elemente",
|
||||||
"backup_all": "Alle",
|
"backup_all": "Alle",
|
||||||
|
"backup_background_service_backup_failed_message": "",
|
||||||
|
"backup_background_service_connection_failed_message": "",
|
||||||
|
"backup_background_service_current_upload_notification": "",
|
||||||
|
"backup_background_service_default_notification": "",
|
||||||
|
"backup_background_service_error_title": "",
|
||||||
|
"backup_background_service_in_progress_notification": "",
|
||||||
|
"backup_background_service_upload_failure_notification": "",
|
||||||
"backup_controller_page_albums": "Gesicherte Alben",
|
"backup_controller_page_albums": "Gesicherte Alben",
|
||||||
|
"backup_controller_page_background_battery_info_link": "",
|
||||||
|
"backup_controller_page_background_battery_info_message": "",
|
||||||
|
"backup_controller_page_background_battery_info_ok": "",
|
||||||
|
"backup_controller_page_background_battery_info_title": "",
|
||||||
|
"backup_controller_page_background_charging": "",
|
||||||
|
"backup_controller_page_background_configure_error": "",
|
||||||
|
"backup_controller_page_background_description": "",
|
||||||
|
"backup_controller_page_background_is_off": "",
|
||||||
|
"backup_controller_page_background_is_on": "",
|
||||||
|
"backup_controller_page_background_turn_off": "",
|
||||||
|
"backup_controller_page_background_turn_on": "",
|
||||||
|
"backup_controller_page_background_wifi": "",
|
||||||
"backup_controller_page_backup": "Sicherung",
|
"backup_controller_page_backup": "Sicherung",
|
||||||
"backup_controller_page_backup_selected": "Ausgewählt: ",
|
"backup_controller_page_backup_selected": "Ausgewählt: ",
|
||||||
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
|
"backup_controller_page_backup_sub": "Gesicherte Fotos und Videos",
|
||||||
@@ -45,10 +69,25 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Informationen",
|
"backup_controller_page_uploading_file_info": "Informationen",
|
||||||
"backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
|
"backup_err_only_album": "Das einzige Album kann nicht entfernt werden",
|
||||||
"backup_info_card_assets": "Elemente",
|
"backup_info_card_assets": "Elemente",
|
||||||
|
"cache_settings_album_thumbnails": "",
|
||||||
|
"cache_settings_clear_cache_button": "",
|
||||||
|
"cache_settings_clear_cache_button_title": "",
|
||||||
|
"cache_settings_image_cache_size": "",
|
||||||
|
"cache_settings_statistics_album": "",
|
||||||
|
"cache_settings_statistics_assets": "",
|
||||||
|
"cache_settings_statistics_full": "",
|
||||||
|
"cache_settings_statistics_shared": "",
|
||||||
|
"cache_settings_statistics_thumbnail": "",
|
||||||
|
"cache_settings_statistics_title": "",
|
||||||
|
"cache_settings_subtitle": "",
|
||||||
|
"cache_settings_thumbnail_size": "",
|
||||||
|
"cache_settings_title": "",
|
||||||
"control_bottom_app_bar_delete": "Löschen",
|
"control_bottom_app_bar_delete": "Löschen",
|
||||||
"create_shared_album_page_share": "Teilen",
|
"control_bottom_app_bar_share": "Teilen",
|
||||||
|
"create_album_page_untitled": "Unbenannt",
|
||||||
"create_shared_album_page_create": "Erstellen",
|
"create_shared_album_page_create": "Erstellen",
|
||||||
"create_shared_album_page_share_add_assets": "FOTOS HINZUFÜGEN",
|
"create_shared_album_page_share": "Teilen",
|
||||||
|
"create_shared_album_page_share_add_assets": "ELEMENTE HINZUFÜGEN",
|
||||||
"create_shared_album_page_share_select_photos": "Fotos auswählen",
|
"create_shared_album_page_share_select_photos": "Fotos auswählen",
|
||||||
"daily_title_text_date": "E, dd MMM",
|
"daily_title_text_date": "E, dd MMM",
|
||||||
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
||||||
@@ -60,6 +99,8 @@
|
|||||||
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
|
"exif_bottom_sheet_description": "Beschreibung hinzufügen...",
|
||||||
"exif_bottom_sheet_details": "DETAILS",
|
"exif_bottom_sheet_details": "DETAILS",
|
||||||
"exif_bottom_sheet_location": "STANDORT",
|
"exif_bottom_sheet_location": "STANDORT",
|
||||||
|
"library_page_albums": "Alben",
|
||||||
|
"library_page_new_album": "Neues Album",
|
||||||
"login_form_button_text": "Anmelden",
|
"login_form_button_text": "Anmelden",
|
||||||
"login_form_email_hint": "deine@email.de",
|
"login_form_email_hint": "deine@email.de",
|
||||||
"login_form_endpoint_hint": "http://deine-server-ip:port/api",
|
"login_form_endpoint_hint": "http://deine-server-ip:port/api",
|
||||||
@@ -68,15 +109,15 @@
|
|||||||
"login_form_err_invalid_email": "Ungültige E-Mail",
|
"login_form_err_invalid_email": "Ungültige E-Mail",
|
||||||
"login_form_err_leading_whitespace": "Führendes Leerzichen",
|
"login_form_err_leading_whitespace": "Führendes Leerzichen",
|
||||||
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
|
"login_form_err_trailing_whitespace": "Folgendes Leerzeichen",
|
||||||
"login_form_failed_login": "Fehler bei der Anmeldung, überprüfen Sie Server URL, E-Mail und Passwort",
|
"login_form_failed_login": "Error logging you in, check server url, email and password",
|
||||||
"login_form_label_email": "E-Mail",
|
"login_form_label_email": "E-Mail",
|
||||||
"login_form_label_password": "Passwort",
|
"login_form_label_password": "Passwort",
|
||||||
"login_form_password_hint": "Passwort",
|
"login_form_password_hint": "password",
|
||||||
"login_form_save_login": "Angemeldet bleiben",
|
"login_form_save_login": "Angemeldet bleiben",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
"profile_drawer_client_server_up_to_date": "App und Server sind aktuell",
|
||||||
"profile_drawer_sign_out": "Abmelden",
|
|
||||||
"profile_drawer_settings": "Einstellungen",
|
"profile_drawer_settings": "Einstellungen",
|
||||||
|
"profile_drawer_sign_out": "Abmelden",
|
||||||
"search_bar_hint": "Durchsuche deine Fotos",
|
"search_bar_hint": "Durchsuche deine Fotos",
|
||||||
"search_page_no_objects": "Keine Objektinformationen verfügbar",
|
"search_page_no_objects": "Keine Objektinformationen verfügbar",
|
||||||
"search_page_no_places": "Keine Informationen über Orte verfügbar",
|
"search_page_no_places": "Keine Informationen über Orte verfügbar",
|
||||||
@@ -85,42 +126,44 @@
|
|||||||
"search_result_page_new_search_hint": "Neue Suche",
|
"search_result_page_new_search_hint": "Neue Suche",
|
||||||
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
"select_additional_user_for_sharing_page_suggestions": "Vorschläge",
|
||||||
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
"select_user_for_sharing_page_err_album": "Album konnte nicht erstellt werden",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Vorschläge",
|
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||||
|
"setting_notifications_notify_failures_grace_period": "",
|
||||||
|
"setting_notifications_notify_hours": "",
|
||||||
|
"setting_notifications_notify_immediately": "",
|
||||||
|
"setting_notifications_notify_minutes": "",
|
||||||
|
"setting_notifications_notify_never": "",
|
||||||
|
"setting_notifications_subtitle": "",
|
||||||
|
"setting_notifications_title": "",
|
||||||
|
"setting_pages_app_bar_settings": "Einstellungen",
|
||||||
"share_add": "Hinzufügen",
|
"share_add": "Hinzufügen",
|
||||||
"share_add_photos": "Fotos hinzufügen",
|
"share_add_photos": "Fotos hinzufügen",
|
||||||
"share_add_title": "Titel hinzufügen",
|
"share_add_title": "Titel hinzufügen",
|
||||||
"share_create_album": "Album erstellen",
|
"share_create_album": "Album erstellen",
|
||||||
|
"share_dialog_preparing": "Vorbereiten...",
|
||||||
"share_invite": "Zum Album einladen",
|
"share_invite": "Zum Album einladen",
|
||||||
"sharing_page_album": "Geteilte Alben",
|
"sharing_page_album": "Geteilte Alben",
|
||||||
"sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.",
|
"sharing_page_description": "Erstelle ein geteiltes Album um Fotos und Videos mit Personen in deinem Netzwerk zu teilen.",
|
||||||
"sharing_page_empty_list": "LEERE LISTE",
|
"sharing_page_empty_list": "LEERE LISTE",
|
||||||
"sharing_silver_appbar_create_shared_album": "Neues geteiltes Album",
|
"sharing_silver_appbar_create_shared_album": "Neues geteiltes Album",
|
||||||
"sharing_silver_appbar_share_partner": "Teile mit Partner",
|
"sharing_silver_appbar_share_partner": "Teile mit Partner",
|
||||||
|
"tab_controller_nav_library": "Bibliothek",
|
||||||
"tab_controller_nav_photos": "Fotos",
|
"tab_controller_nav_photos": "Fotos",
|
||||||
"tab_controller_nav_search": "Suche",
|
"tab_controller_nav_search": "Suche",
|
||||||
"tab_controller_nav_sharing": "Teilen",
|
"tab_controller_nav_sharing": "Teilen",
|
||||||
"tab_controller_nav_library": "Bibliothek",
|
"theme_setting_asset_list_storage_indicator_title": "",
|
||||||
|
"theme_setting_asset_list_tiles_per_row_title": "",
|
||||||
|
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
||||||
|
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
||||||
|
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
||||||
|
"theme_setting_system_theme_switch": "Automatisch (Systemeinstellung folgen)",
|
||||||
|
"theme_setting_theme_subtitle": "Wählen Sie die Themeneinstellung der App",
|
||||||
|
"theme_setting_theme_title": "Theme",
|
||||||
|
"theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich",
|
||||||
|
"theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren",
|
||||||
"version_announcement_overlay_ack": "Ich habe verstanden",
|
"version_announcement_overlay_ack": "Ich habe verstanden",
|
||||||
"version_announcement_overlay_release_notes": "Änderungsprotokoll",
|
"version_announcement_overlay_release_notes": "Änderungsprotokoll",
|
||||||
"version_announcement_overlay_text_1": "Hallo mein Freund! Es gibt eine neue Version von",
|
"version_announcement_overlay_text_1": "Hallo mein Freund! Es gibt eine neue Version von",
|
||||||
"version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
|
"version_announcement_overlay_text_2": "Bitte nehm dir die Zeit und lese das ",
|
||||||
"version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
|
"version_announcement_overlay_text_3": " und achte darauf, dass deine docker-compose und .env Dateien aktuell sind, vor allem wenn du ein System für automatische Updates benutzt (z.B. Watchtower).",
|
||||||
"version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89",
|
"version_announcement_overlay_title": "Neue Server-Version verfügbar \uD83C\uDF89"
|
||||||
"album_thumbnail_card_item": "1 Element",
|
}
|
||||||
"album_thumbnail_card_items": "{} Elemente",
|
|
||||||
"album_thumbnail_card_shared": " · Geteilt",
|
|
||||||
"library_page_albums": "Alben",
|
|
||||||
"library_page_new_album": "Neues Album",
|
|
||||||
"create_album_page_untitled": "Unbenannt",
|
|
||||||
"share_dialog_preparing": "Vorbereiten...",
|
|
||||||
"control_bottom_app_bar_share": "Teilen",
|
|
||||||
"setting_pages_app_bar_settings": "Einstellungen",
|
|
||||||
"theme_setting_theme_title": "Theme",
|
|
||||||
"theme_setting_theme_subtitle": "Wählen Sie die Themeneinstellung der App",
|
|
||||||
"theme_setting_system_theme_switch": "Automatisch (Systemeinstellung folgen)",
|
|
||||||
"theme_setting_dark_mode_switch": "Dunkler Modus",
|
|
||||||
"theme_setting_image_viewer_quality_title": "Qualität des Bildbetrachters",
|
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Einstellen der Qualität des Detailbildbetrachters",
|
|
||||||
"theme_setting_three_stage_loading_title": "Dreistufiges Laden aktivieren",
|
|
||||||
"theme_setting_three_stage_loading_subtitle": "Das dreistufige Ladeverfahren kann die Performance beim Laden verbessern, erhöht allerdings den Datenverbrauch deutlich"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"album_info_card_backup_album_excluded": "EXCLUDED",
|
"album_info_card_backup_album_excluded": "EXCLUDED",
|
||||||
"album_info_card_backup_album_included": "INCLUDED",
|
"album_info_card_backup_album_included": "INCLUDED",
|
||||||
|
"album_thumbnail_card_item": "1 item",
|
||||||
|
"album_thumbnail_card_items": "{} items",
|
||||||
|
"album_thumbnail_card_shared": " · Shared",
|
||||||
"album_viewer_appbar_share_delete": "Delete album",
|
"album_viewer_appbar_share_delete": "Delete album",
|
||||||
"album_viewer_appbar_share_err_delete": "Failed to delete album",
|
"album_viewer_appbar_share_err_delete": "Failed to delete album",
|
||||||
"album_viewer_appbar_share_err_leave": "Failed to leave album",
|
"album_viewer_appbar_share_err_leave": "Failed to leave album",
|
||||||
@@ -9,6 +12,8 @@
|
|||||||
"album_viewer_appbar_share_leave": "Leave album",
|
"album_viewer_appbar_share_leave": "Leave album",
|
||||||
"album_viewer_appbar_share_remove": "Remove from album",
|
"album_viewer_appbar_share_remove": "Remove from album",
|
||||||
"album_viewer_page_share_add_users": "Add users",
|
"album_viewer_page_share_add_users": "Add users",
|
||||||
|
"asset_list_settings_subtitle": "Photo grid layout settings",
|
||||||
|
"asset_list_settings_title": "Photo Grid",
|
||||||
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
"backup_album_selection_page_albums_device": "Albums on device ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
"backup_album_selection_page_albums_tap": "Tap to include, double tap to exclude",
|
||||||
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
"backup_album_selection_page_assets_scatter": "Assets can scatter across multiple albums. Thus, albums can be included or excluded during the backup process.",
|
||||||
@@ -16,26 +21,29 @@
|
|||||||
"backup_album_selection_page_selection_info": "Selection Info",
|
"backup_album_selection_page_selection_info": "Selection Info",
|
||||||
"backup_album_selection_page_total_assets": "Total unique assets",
|
"backup_album_selection_page_total_assets": "Total unique assets",
|
||||||
"backup_all": "All",
|
"backup_all": "All",
|
||||||
"backup_background_service_default_notification": "Checking for new assets…",
|
|
||||||
"backup_background_service_disable_battery_optimizations": "Please disable battery optimization for Immich to enable background backup",
|
|
||||||
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
|
||||||
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
|
||||||
"backup_background_service_current_upload_notification": "Uploading {}",
|
|
||||||
"backup_background_service_error_title": "Backup error",
|
|
||||||
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
|
||||||
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
"backup_background_service_backup_failed_message": "Failed to backup assets. Retrying…",
|
||||||
|
"backup_background_service_connection_failed_message": "Failed to connect to the server. Retrying…",
|
||||||
|
"backup_background_service_current_upload_notification": "Uploading {}",
|
||||||
|
"backup_background_service_default_notification": "Checking for new assets…",
|
||||||
|
"backup_background_service_error_title": "Backup error",
|
||||||
|
"backup_background_service_in_progress_notification": "Backing up your assets…",
|
||||||
|
"backup_background_service_upload_failure_notification": "Failed to upload {}",
|
||||||
"backup_controller_page_albums": "Backup Albums",
|
"backup_controller_page_albums": "Backup Albums",
|
||||||
|
"backup_controller_page_background_battery_info_link": "Show me how",
|
||||||
|
"backup_controller_page_background_battery_info_message": "For the best background backup experience, please disable any battery optimizations restricting background activity for Immich.\n\nSince this is device-specific, please lookup the required information for your device manufacturer.",
|
||||||
|
"backup_controller_page_background_battery_info_ok": "OK",
|
||||||
|
"backup_controller_page_background_battery_info_title": "Battery optimizations",
|
||||||
|
"backup_controller_page_background_charging": "Only while charging",
|
||||||
|
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
||||||
|
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
||||||
|
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
||||||
|
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
||||||
|
"backup_controller_page_background_turn_off": "Turn off background service",
|
||||||
|
"backup_controller_page_background_turn_on": "Turn on background service",
|
||||||
|
"backup_controller_page_background_wifi": "Only on WiFi",
|
||||||
"backup_controller_page_backup": "Backup",
|
"backup_controller_page_backup": "Backup",
|
||||||
"backup_controller_page_backup_selected": "Selected: ",
|
"backup_controller_page_backup_selected": "Selected: ",
|
||||||
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
"backup_controller_page_backup_sub": "Backed up photos and videos",
|
||||||
"backup_controller_page_background_description": "Turn on the background service to automatically backup any new assets without needing to open the app",
|
|
||||||
"backup_controller_page_background_wifi": "Only on WiFi",
|
|
||||||
"backup_controller_page_background_charging": "Only while charging",
|
|
||||||
"backup_controller_page_background_is_on": "Automatic background backup is on",
|
|
||||||
"backup_controller_page_background_is_off": "Automatic background backup is off",
|
|
||||||
"backup_controller_page_background_turn_on": "Turn on background service",
|
|
||||||
"backup_controller_page_background_turn_off": "Turn off background service",
|
|
||||||
"backup_controller_page_background_configure_error": "Failed to configure the background service",
|
|
||||||
"backup_controller_page_cancel": "Cancel",
|
"backup_controller_page_cancel": "Cancel",
|
||||||
"backup_controller_page_created": "Created on: {}",
|
"backup_controller_page_created": "Created on: {}",
|
||||||
"backup_controller_page_desc_backup": "Turn on backup to automatically upload new assets to the server.",
|
"backup_controller_page_desc_backup": "Turn on backup to automatically upload new assets to the server.",
|
||||||
@@ -61,10 +69,25 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Uploading file info",
|
"backup_controller_page_uploading_file_info": "Uploading file info",
|
||||||
"backup_err_only_album": "Cannot remove the only album",
|
"backup_err_only_album": "Cannot remove the only album",
|
||||||
"backup_info_card_assets": "assets",
|
"backup_info_card_assets": "assets",
|
||||||
|
"cache_settings_album_thumbnails": "Library page thumbnails ({} assets)",
|
||||||
|
"cache_settings_clear_cache_button": "Clear cache",
|
||||||
|
"cache_settings_clear_cache_button_title": "Clears the app's cache. This will significantly impact the app's performance until the cache has rebuilt.",
|
||||||
|
"cache_settings_image_cache_size": "Image cache size ({} assets)",
|
||||||
|
"cache_settings_statistics_album": "Library thumbnails",
|
||||||
|
"cache_settings_statistics_assets": "{} assets ({})",
|
||||||
|
"cache_settings_statistics_full": "Full images",
|
||||||
|
"cache_settings_statistics_shared": "Shared album thumbnails",
|
||||||
|
"cache_settings_statistics_thumbnail": "Thumbnails",
|
||||||
|
"cache_settings_statistics_title": "Cache usage",
|
||||||
|
"cache_settings_subtitle": "Control the caching behaviour of the Immich mobile application",
|
||||||
|
"cache_settings_thumbnail_size": "Thumbnail cache size ({} assets)",
|
||||||
|
"cache_settings_title": "Caching Settings",
|
||||||
"control_bottom_app_bar_delete": "Delete",
|
"control_bottom_app_bar_delete": "Delete",
|
||||||
"create_shared_album_page_share": "Share",
|
"control_bottom_app_bar_share": "Share",
|
||||||
|
"create_album_page_untitled": "Untitled",
|
||||||
"create_shared_album_page_create": "Create",
|
"create_shared_album_page_create": "Create",
|
||||||
"create_shared_album_page_share_add_assets": "ADD PHOTOS",
|
"create_shared_album_page_share": "Share",
|
||||||
|
"create_shared_album_page_share_add_assets": "ADD ASSETS",
|
||||||
"create_shared_album_page_share_select_photos": "Select Photos",
|
"create_shared_album_page_share_select_photos": "Select Photos",
|
||||||
"daily_title_text_date": "E, MMM dd",
|
"daily_title_text_date": "E, MMM dd",
|
||||||
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||||
@@ -76,6 +99,8 @@
|
|||||||
"exif_bottom_sheet_description": "Add Description...",
|
"exif_bottom_sheet_description": "Add Description...",
|
||||||
"exif_bottom_sheet_details": "DETAILS",
|
"exif_bottom_sheet_details": "DETAILS",
|
||||||
"exif_bottom_sheet_location": "LOCATION",
|
"exif_bottom_sheet_location": "LOCATION",
|
||||||
|
"library_page_albums": "Albums",
|
||||||
|
"library_page_new_album": "New album",
|
||||||
"login_form_button_text": "Login",
|
"login_form_button_text": "Login",
|
||||||
"login_form_email_hint": "youremail@email.com",
|
"login_form_email_hint": "youremail@email.com",
|
||||||
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
"login_form_endpoint_hint": "http://your-server-ip:port/api",
|
||||||
@@ -91,8 +116,8 @@
|
|||||||
"login_form_save_login": "Stay logged in",
|
"login_form_save_login": "Stay logged in",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
"profile_drawer_client_server_up_to_date": "Client and Server are up-to-date",
|
||||||
"profile_drawer_sign_out": "Sign out",
|
|
||||||
"profile_drawer_settings": "Settings",
|
"profile_drawer_settings": "Settings",
|
||||||
|
"profile_drawer_sign_out": "Sign Out",
|
||||||
"search_bar_hint": "Search your photos",
|
"search_bar_hint": "Search your photos",
|
||||||
"search_page_no_objects": "No Objects Info Available",
|
"search_page_no_objects": "No Objects Info Available",
|
||||||
"search_page_no_places": "No Places Info Available",
|
"search_page_no_places": "No Places Info Available",
|
||||||
@@ -102,52 +127,43 @@
|
|||||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||||
"select_user_for_sharing_page_err_album": "Failed to create album",
|
"select_user_for_sharing_page_err_album": "Failed to create album",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||||
|
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
||||||
|
"setting_notifications_notify_hours": "{} hours",
|
||||||
|
"setting_notifications_notify_immediately": "immediately",
|
||||||
|
"setting_notifications_notify_minutes": "{} minutes",
|
||||||
|
"setting_notifications_notify_never": "never",
|
||||||
|
"setting_notifications_subtitle": "Adjust your notification preferences",
|
||||||
|
"setting_notifications_title": "Notifications",
|
||||||
|
"setting_pages_app_bar_settings": "Settings",
|
||||||
"share_add": "Add",
|
"share_add": "Add",
|
||||||
"share_add_photos": "Add photos",
|
"share_add_photos": "Add photos",
|
||||||
"share_add_title": "Add a title",
|
"share_add_title": "Add a title",
|
||||||
"share_create_album": "Create album",
|
"share_create_album": "Create album",
|
||||||
|
"share_dialog_preparing": "Preparing...",
|
||||||
"share_invite": "Invite to album",
|
"share_invite": "Invite to album",
|
||||||
"sharing_page_album": "Shared albums",
|
"sharing_page_album": "Shared albums",
|
||||||
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
"sharing_page_description": "Create shared albums to share photos and videos with people in your network.",
|
||||||
"sharing_page_empty_list": "EMPTY LIST",
|
"sharing_page_empty_list": "EMPTY LIST",
|
||||||
"sharing_silver_appbar_create_shared_album": "Create shared album",
|
"sharing_silver_appbar_create_shared_album": "Create shared album",
|
||||||
"sharing_silver_appbar_share_partner": "Share with partner",
|
"sharing_silver_appbar_share_partner": "Share with partner",
|
||||||
|
"tab_controller_nav_library": "Library",
|
||||||
"tab_controller_nav_photos": "Photos",
|
"tab_controller_nav_photos": "Photos",
|
||||||
"tab_controller_nav_search": "Search",
|
"tab_controller_nav_search": "Search",
|
||||||
"tab_controller_nav_sharing": "Sharing",
|
"tab_controller_nav_sharing": "Sharing",
|
||||||
"tab_controller_nav_library": "Library",
|
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
||||||
|
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
||||||
|
"theme_setting_dark_mode_switch": "Dark mode",
|
||||||
|
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
||||||
|
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
||||||
|
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
||||||
|
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
||||||
|
"theme_setting_theme_title": "Theme",
|
||||||
|
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
||||||
|
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
||||||
"version_announcement_overlay_ack": "Acknowledge",
|
"version_announcement_overlay_ack": "Acknowledge",
|
||||||
"version_announcement_overlay_release_notes": "release notes",
|
"version_announcement_overlay_release_notes": "release notes",
|
||||||
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
"version_announcement_overlay_text_1": "Hi friend, there is a new release of",
|
||||||
"version_announcement_overlay_text_2": "please take your time to visit the ",
|
"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_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"
|
||||||
"album_thumbnail_card_item": "1 item",
|
}
|
||||||
"album_thumbnail_card_items": "{} items",
|
|
||||||
"album_thumbnail_card_shared": " · Shared",
|
|
||||||
"library_page_albums": "Albums",
|
|
||||||
"library_page_new_album": "New album",
|
|
||||||
"create_album_page_untitled": "Untitled",
|
|
||||||
"share_dialog_preparing": "Preparing...",
|
|
||||||
"control_bottom_app_bar_share": "Share",
|
|
||||||
"setting_pages_app_bar_settings": "Settings",
|
|
||||||
"theme_setting_theme_title": "Theme",
|
|
||||||
"theme_setting_theme_subtitle": "Choose the app's theme setting",
|
|
||||||
"theme_setting_system_theme_switch": "Automatic (Follow system setting)",
|
|
||||||
"theme_setting_dark_mode_switch": "Dark mode",
|
|
||||||
"theme_setting_image_viewer_quality_title": "Image viewer quality",
|
|
||||||
"theme_setting_image_viewer_quality_subtitle": "Adjust the quality of the detail image viewer",
|
|
||||||
"theme_setting_three_stage_loading_title": "Enable three-stage loading",
|
|
||||||
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
|
||||||
"asset_list_settings_title": "Photo Grid",
|
|
||||||
"asset_list_settings_subtitle": "Photo grid layout settings",
|
|
||||||
"theme_setting_asset_list_storage_indicator_title": "Show storage indicator on asset tiles",
|
|
||||||
"theme_setting_asset_list_tiles_per_row_title": "Number of assets per row ({})",
|
|
||||||
"setting_notifications_title": "Notifications",
|
|
||||||
"setting_notifications_subtitle": "Adjust your notification preferences",
|
|
||||||
"setting_notifications_notify_failures_grace_period": "Notify background backup failures: {}",
|
|
||||||
"setting_notifications_notify_immediately": "immediately",
|
|
||||||
"setting_notifications_notify_minutes": "{} minutes",
|
|
||||||
"setting_notifications_notify_hours": "{} hours",
|
|
||||||
"setting_notifications_notify_never": "never"
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
{
|
{
|
||||||
"album_info_card_backup_album_excluded": "EXCLU",
|
"album_info_card_backup_album_excluded": "EXCLU",
|
||||||
"album_info_card_backup_album_included": "INCLUS",
|
"album_info_card_backup_album_included": "INCLUS",
|
||||||
|
"album_thumbnail_card_item": "1 élément",
|
||||||
|
"album_thumbnail_card_items": "{} éléments",
|
||||||
|
"album_thumbnail_card_shared": " · Partagé",
|
||||||
"album_viewer_appbar_share_delete": "Supprimer l'album",
|
"album_viewer_appbar_share_delete": "Supprimer l'album",
|
||||||
"album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album",
|
"album_viewer_appbar_share_err_delete": "Échec de la suppression de l'album",
|
||||||
"album_viewer_appbar_share_err_leave": "Impossible de quitter l'album",
|
"album_viewer_appbar_share_err_leave": "Impossible de quitter l'album",
|
||||||
@@ -9,6 +12,8 @@
|
|||||||
"album_viewer_appbar_share_leave": "Quitter l'album",
|
"album_viewer_appbar_share_leave": "Quitter l'album",
|
||||||
"album_viewer_appbar_share_remove": "Retirer de l'album",
|
"album_viewer_appbar_share_remove": "Retirer de l'album",
|
||||||
"album_viewer_page_share_add_users": "Ajouter des utilisateurs",
|
"album_viewer_page_share_add_users": "Ajouter des utilisateurs",
|
||||||
|
"asset_list_settings_subtitle": "",
|
||||||
|
"asset_list_settings_title": "",
|
||||||
"backup_album_selection_page_albums_device": "Albums sur l'appareil ({})",
|
"backup_album_selection_page_albums_device": "Albums sur l'appareil ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure",
|
"backup_album_selection_page_albums_tap": "Tapez pour inclure, tapez deux fois pour exclure",
|
||||||
"backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.",
|
"backup_album_selection_page_assets_scatter": "Les éléments peuvent être répartis sur plusieurs albums. De ce fait, les albums peuvent être inclus ou exclus pendant le processus de sauvegarde.",
|
||||||
@@ -16,7 +21,26 @@
|
|||||||
"backup_album_selection_page_selection_info": "Informations sur la sélection",
|
"backup_album_selection_page_selection_info": "Informations sur la sélection",
|
||||||
"backup_album_selection_page_total_assets": "Total des éléments uniques",
|
"backup_album_selection_page_total_assets": "Total des éléments uniques",
|
||||||
"backup_all": "Tout",
|
"backup_all": "Tout",
|
||||||
|
"backup_background_service_backup_failed_message": "",
|
||||||
|
"backup_background_service_connection_failed_message": "",
|
||||||
|
"backup_background_service_current_upload_notification": "",
|
||||||
|
"backup_background_service_default_notification": "",
|
||||||
|
"backup_background_service_error_title": "",
|
||||||
|
"backup_background_service_in_progress_notification": "",
|
||||||
|
"backup_background_service_upload_failure_notification": "",
|
||||||
"backup_controller_page_albums": "Sauvegarder les albums",
|
"backup_controller_page_albums": "Sauvegarder les albums",
|
||||||
|
"backup_controller_page_background_battery_info_link": "",
|
||||||
|
"backup_controller_page_background_battery_info_message": "",
|
||||||
|
"backup_controller_page_background_battery_info_ok": "",
|
||||||
|
"backup_controller_page_background_battery_info_title": "",
|
||||||
|
"backup_controller_page_background_charging": "",
|
||||||
|
"backup_controller_page_background_configure_error": "",
|
||||||
|
"backup_controller_page_background_description": "",
|
||||||
|
"backup_controller_page_background_is_off": "",
|
||||||
|
"backup_controller_page_background_is_on": "",
|
||||||
|
"backup_controller_page_background_turn_off": "",
|
||||||
|
"backup_controller_page_background_turn_on": "",
|
||||||
|
"backup_controller_page_background_wifi": "",
|
||||||
"backup_controller_page_backup": "Sauvegardé",
|
"backup_controller_page_backup": "Sauvegardé",
|
||||||
"backup_controller_page_backup_selected": "Sélectionné : ",
|
"backup_controller_page_backup_selected": "Sélectionné : ",
|
||||||
"backup_controller_page_backup_sub": "Photos et vidéos sauvegardées",
|
"backup_controller_page_backup_sub": "Photos et vidéos sauvegardées",
|
||||||
@@ -45,7 +69,23 @@
|
|||||||
"backup_controller_page_uploading_file_info": "Envoi d'informations sur le fichier",
|
"backup_controller_page_uploading_file_info": "Envoi d'informations sur le fichier",
|
||||||
"backup_err_only_album": "Impossible de retirer le seul album",
|
"backup_err_only_album": "Impossible de retirer le seul album",
|
||||||
"backup_info_card_assets": "éléments",
|
"backup_info_card_assets": "éléments",
|
||||||
|
"cache_settings_album_thumbnails": "",
|
||||||
|
"cache_settings_clear_cache_button": "",
|
||||||
|
"cache_settings_clear_cache_button_title": "",
|
||||||
|
"cache_settings_image_cache_size": "",
|
||||||
|
"cache_settings_statistics_album": "",
|
||||||
|
"cache_settings_statistics_assets": "",
|
||||||
|
"cache_settings_statistics_full": "",
|
||||||
|
"cache_settings_statistics_shared": "",
|
||||||
|
"cache_settings_statistics_thumbnail": "",
|
||||||
|
"cache_settings_statistics_title": "",
|
||||||
|
"cache_settings_subtitle": "",
|
||||||
|
"cache_settings_thumbnail_size": "",
|
||||||
|
"cache_settings_title": "",
|
||||||
"control_bottom_app_bar_delete": "Supprimer",
|
"control_bottom_app_bar_delete": "Supprimer",
|
||||||
|
"control_bottom_app_bar_share": "Partager",
|
||||||
|
"create_album_page_untitled": "Sans titre",
|
||||||
|
"create_shared_album_page_create": "Créer",
|
||||||
"create_shared_album_page_share": "Partager",
|
"create_shared_album_page_share": "Partager",
|
||||||
"create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS",
|
"create_shared_album_page_share_add_assets": "AJOUTER DES ÉLÉMENTS",
|
||||||
"create_shared_album_page_share_select_photos": "Sélectionner les photos",
|
"create_shared_album_page_share_select_photos": "Sélectionner les photos",
|
||||||
@@ -59,6 +99,8 @@
|
|||||||
"exif_bottom_sheet_description": "Ajouter une description...",
|
"exif_bottom_sheet_description": "Ajouter une description...",
|
||||||
"exif_bottom_sheet_details": "DÉTAILS",
|
"exif_bottom_sheet_details": "DÉTAILS",
|
||||||
"exif_bottom_sheet_location": "LOCALISATION",
|
"exif_bottom_sheet_location": "LOCALISATION",
|
||||||
|
"library_page_albums": "Albums",
|
||||||
|
"library_page_new_album": "Nouvel album",
|
||||||
"login_form_button_text": "Connexion",
|
"login_form_button_text": "Connexion",
|
||||||
"login_form_email_hint": "votreemail@email.com",
|
"login_form_email_hint": "votreemail@email.com",
|
||||||
"login_form_endpoint_hint": "http://adresse-ip-serveur:port/api",
|
"login_form_endpoint_hint": "http://adresse-ip-serveur:port/api",
|
||||||
@@ -74,6 +116,7 @@
|
|||||||
"login_form_save_login": "Rester connecté",
|
"login_form_save_login": "Rester connecté",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour",
|
"profile_drawer_client_server_up_to_date": "Le client et le serveur sont à jour",
|
||||||
|
"profile_drawer_settings": "Paramètres",
|
||||||
"profile_drawer_sign_out": "Se déconnecter",
|
"profile_drawer_sign_out": "Se déconnecter",
|
||||||
"search_bar_hint": "Rechercher vos photos",
|
"search_bar_hint": "Rechercher vos photos",
|
||||||
"search_page_no_objects": "Aucune information disponible sur les objets",
|
"search_page_no_objects": "Aucune information disponible sur les objets",
|
||||||
@@ -84,19 +127,39 @@
|
|||||||
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
"select_additional_user_for_sharing_page_suggestions": "Suggestions",
|
||||||
"select_user_for_sharing_page_err_album": "Échec de la création de l'album",
|
"select_user_for_sharing_page_err_album": "Échec de la création de l'album",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
"select_user_for_sharing_page_share_suggestions": "Suggestions",
|
||||||
|
"setting_notifications_notify_failures_grace_period": "",
|
||||||
|
"setting_notifications_notify_hours": "",
|
||||||
|
"setting_notifications_notify_immediately": "",
|
||||||
|
"setting_notifications_notify_minutes": "",
|
||||||
|
"setting_notifications_notify_never": "",
|
||||||
|
"setting_notifications_subtitle": "",
|
||||||
|
"setting_notifications_title": "",
|
||||||
|
"setting_pages_app_bar_settings": "",
|
||||||
"share_add": "Ajouter",
|
"share_add": "Ajouter",
|
||||||
"share_add_photos": "Ajouter des photos",
|
"share_add_photos": "Ajouter des photos",
|
||||||
"share_add_title": "Ajouter un titre",
|
"share_add_title": "Ajouter un titre",
|
||||||
"share_create_album": "Créer un album",
|
"share_create_album": "Créer un album",
|
||||||
|
"share_dialog_preparing": "Préparation...",
|
||||||
"share_invite": "Inviter à l'album",
|
"share_invite": "Inviter à l'album",
|
||||||
"sharing_page_album": "Albums partagés",
|
"sharing_page_album": "Albums partagés",
|
||||||
"sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.",
|
"sharing_page_description": "Créez des albums partagés pour partager des photos et des vidéos avec les personnes de votre réseau.",
|
||||||
"sharing_page_empty_list": "LISTE VIDE",
|
"sharing_page_empty_list": "LISTE VIDE",
|
||||||
"sharing_silver_appbar_create_shared_album": "Créer un album partagé",
|
"sharing_silver_appbar_create_shared_album": "Créer un album partagé",
|
||||||
"sharing_silver_appbar_share_partner": "Partager avec un partenaire",
|
"sharing_silver_appbar_share_partner": "Partager avec un partenaire",
|
||||||
|
"tab_controller_nav_library": "Bibliothèque",
|
||||||
"tab_controller_nav_photos": "Photos",
|
"tab_controller_nav_photos": "Photos",
|
||||||
"tab_controller_nav_search": "Recherche",
|
"tab_controller_nav_search": "Recherche",
|
||||||
"tab_controller_nav_sharing": "Partage",
|
"tab_controller_nav_sharing": "Partage",
|
||||||
|
"theme_setting_asset_list_storage_indicator_title": "",
|
||||||
|
"theme_setting_asset_list_tiles_per_row_title": "",
|
||||||
|
"theme_setting_dark_mode_switch": "",
|
||||||
|
"theme_setting_image_viewer_quality_subtitle": "",
|
||||||
|
"theme_setting_image_viewer_quality_title": "",
|
||||||
|
"theme_setting_system_theme_switch": "",
|
||||||
|
"theme_setting_theme_subtitle": "",
|
||||||
|
"theme_setting_theme_title": "",
|
||||||
|
"theme_setting_three_stage_loading_subtitle": "",
|
||||||
|
"theme_setting_three_stage_loading_title": "",
|
||||||
"version_announcement_overlay_ack": "Confirmer",
|
"version_announcement_overlay_ack": "Confirmer",
|
||||||
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
"version_announcement_overlay_release_notes": "notes de mise à jour",
|
||||||
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
"version_announcement_overlay_text_1": "Bonjour, une nouvelle version de",
|
||||||
|
|||||||
@@ -1,28 +1,52 @@
|
|||||||
{
|
{
|
||||||
"album_info_card_backup_album_excluded": "ESCLUSI",
|
"album_info_card_backup_album_excluded": "ESCLUSI",
|
||||||
"album_info_card_backup_album_included": "INCLUSI",
|
"album_info_card_backup_album_included": "INCLUSI",
|
||||||
|
"album_thumbnail_card_item": "1 elemento ",
|
||||||
|
"album_thumbnail_card_items": "{} elementi",
|
||||||
|
"album_thumbnail_card_shared": "Condiviso",
|
||||||
"album_viewer_appbar_share_delete": "Elimina album ",
|
"album_viewer_appbar_share_delete": "Elimina album ",
|
||||||
"album_viewer_appbar_share_err_delete": "Fallito nel cancellare l'album ",
|
"album_viewer_appbar_share_err_delete": "Errore nel cancellare l'album ",
|
||||||
"album_viewer_appbar_share_err_leave": "Fallito nel lasciare l'album ",
|
"album_viewer_appbar_share_err_leave": "Errore nel lasciare l'album ",
|
||||||
"album_viewer_appbar_share_err_remove": "Ci sono problemi nel rimuovere oggetti dall'album ",
|
"album_viewer_appbar_share_err_remove": "Ci sono problemi nel rimuovere oggetti dall'album ",
|
||||||
"album_viewer_appbar_share_err_title": "Fallito nel cambiare titolo dell'album ",
|
"album_viewer_appbar_share_err_title": "Errore nel cambiare il titolo dell'album ",
|
||||||
"album_viewer_appbar_share_leave": "Lascia l'album",
|
"album_viewer_appbar_share_leave": "Lascia album",
|
||||||
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
"album_viewer_appbar_share_remove": "Rimuovere dall'album ",
|
||||||
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
"album_viewer_page_share_add_users": "Aggiungi utenti",
|
||||||
"backup_album_selection_page_albums_device": "Albums nel device ({})",
|
"asset_list_settings_subtitle": "Impostazion del layout della griglia delle foto",
|
||||||
|
"asset_list_settings_title": "Griglia foto",
|
||||||
|
"backup_album_selection_page_albums_device": "Albums sul device ({})",
|
||||||
"backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere.",
|
"backup_album_selection_page_albums_tap": "Tap per includere, doppio tap per escludere.",
|
||||||
"backup_album_selection_page_assets_scatter": "Stesse immagini e video possono trovarsi tra più album, così gli album possono essere inclusi o esclusi dal backup.",
|
"backup_album_selection_page_assets_scatter": "Stesse immagini e video possono trovarsi tra più album, così gli album possono essere inclusi o esclusi dal backup.",
|
||||||
"backup_album_selection_page_select_albums": "Seleziona gli album",
|
"backup_album_selection_page_select_albums": "Seleziona gli album",
|
||||||
"backup_album_selection_page_selection_info": "Informazioni sulla selezione ",
|
"backup_album_selection_page_selection_info": "Informazioni sulla selezione ",
|
||||||
"backup_album_selection_page_total_assets": "Numero totale di oggetti unici",
|
"backup_album_selection_page_total_assets": "Numero totale di oggetti unici",
|
||||||
"backup_all": "Tutti",
|
"backup_all": "Tutti",
|
||||||
"backup_controller_page_albums": "Backup album",
|
"backup_background_service_backup_failed_message": "Impossibile caricare contenuti. Nuovo tentativo…",
|
||||||
|
"backup_background_service_connection_failed_message": "Impossibile connettersi al server. Nuovo tentativo…",
|
||||||
|
"backup_background_service_current_upload_notification": "Caricamento {}",
|
||||||
|
"backup_background_service_default_notification": "Verifica di nuovi contenuti…",
|
||||||
|
"backup_background_service_error_title": "Errore di Backup",
|
||||||
|
"backup_background_service_in_progress_notification": "Backing dei tuoi contenuti…",
|
||||||
|
"backup_background_service_upload_failure_notification": "Impossibile caricare {}",
|
||||||
|
"backup_controller_page_albums": "Backup Album",
|
||||||
|
"backup_controller_page_background_battery_info_link": "Mostrami come",
|
||||||
|
"backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimisazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.",
|
||||||
|
"backup_controller_page_background_battery_info_ok": "OK",
|
||||||
|
"backup_controller_page_background_battery_info_title": "Ottimizzazioni batteria",
|
||||||
|
"backup_controller_page_background_charging": "Solo durante la ricarica",
|
||||||
|
"backup_controller_page_background_configure_error": "Impossibile configurare i servizi in background",
|
||||||
|
"backup_controller_page_background_description": "Abilita i servizi in background per sincronizzare tutti i nuovi contenuti senza la necessità di aprire l'app",
|
||||||
|
"backup_controller_page_background_is_off": "Backup automatico spento",
|
||||||
|
"backup_controller_page_background_is_on": "Backup automatico attivo",
|
||||||
|
"backup_controller_page_background_turn_off": "Disabilita servizi in background",
|
||||||
|
"backup_controller_page_background_turn_on": "Abilita servizi in background",
|
||||||
|
"backup_controller_page_background_wifi": "Solo su WiFi",
|
||||||
"backup_controller_page_backup": "Backup",
|
"backup_controller_page_backup": "Backup",
|
||||||
"backup_controller_page_backup_selected": "Selezionati:",
|
"backup_controller_page_backup_selected": "Selezionati:",
|
||||||
"backup_controller_page_backup_sub": "Photo e video salvati",
|
"backup_controller_page_backup_sub": "Foto e video caricati",
|
||||||
"backup_controller_page_cancel": "Cancella ",
|
"backup_controller_page_cancel": "Cancella ",
|
||||||
"backup_controller_page_created": "Creato il: {}",
|
"backup_controller_page_created": "Creato il: {}",
|
||||||
"backup_controller_page_desc_backup": "Attiva il backup automatico per eseguire upload sul server",
|
"backup_controller_page_desc_backup": "Attiva il backup per eseguire il caricamento automatico sul server",
|
||||||
"backup_controller_page_excluded": "Esclusi:",
|
"backup_controller_page_excluded": "Esclusi:",
|
||||||
"backup_controller_page_failed": "Falliti: ({})",
|
"backup_controller_page_failed": "Falliti: ({})",
|
||||||
"backup_controller_page_filename": "Nome del file: {} [{}]",
|
"backup_controller_page_filename": "Nome del file: {} [{}]",
|
||||||
@@ -30,39 +54,57 @@
|
|||||||
"backup_controller_page_info": "Informazioni sul backup",
|
"backup_controller_page_info": "Informazioni sul backup",
|
||||||
"backup_controller_page_none_selected": "Nessuna selezione",
|
"backup_controller_page_none_selected": "Nessuna selezione",
|
||||||
"backup_controller_page_remainder": "Promemoria ",
|
"backup_controller_page_remainder": "Promemoria ",
|
||||||
"backup_controller_page_remainder_sub": "Photo e album selezionati che rimangono da salvare",
|
"backup_controller_page_remainder_sub": "Photo e album selezionati che rimangono da caricare",
|
||||||
"backup_controller_page_select": "Seleziona ",
|
"backup_controller_page_select": "Seleziona ",
|
||||||
"backup_controller_page_server_storage": "Spazio nel server",
|
"backup_controller_page_server_storage": "Spazio sul server",
|
||||||
"backup_controller_page_start_backup": "Inizia backup ",
|
"backup_controller_page_start_backup": "Inizia backup ",
|
||||||
"backup_controller_page_status_off": "Backup è disattivato ",
|
"backup_controller_page_status_off": "Backup è disattivato ",
|
||||||
"backup_controller_page_status_on": "Backup è attivato",
|
"backup_controller_page_status_on": "Backup è attivato",
|
||||||
"backup_controller_page_storage_format": "{} di {} usati",
|
"backup_controller_page_storage_format": "{} di {} usati",
|
||||||
"backup_controller_page_to_backup": "Album da salvare",
|
"backup_controller_page_to_backup": "Album da caricare",
|
||||||
"backup_controller_page_total": "Totale",
|
"backup_controller_page_total": "Totale",
|
||||||
"backup_controller_page_total_sub": "Tutte le foto e i video unici salvati dagli album selezionati ",
|
"backup_controller_page_total_sub": "Tutte le foto e i video unici caricati dagli album selezionati ",
|
||||||
"backup_controller_page_turn_off": "Disattiva backup",
|
"backup_controller_page_turn_off": "Disattiva backup",
|
||||||
"backup_controller_page_turn_on": "Attiva backup ",
|
"backup_controller_page_turn_on": "Attiva backup ",
|
||||||
"backup_controller_page_uploading_file_info": "Info sul file caricato",
|
"backup_controller_page_uploading_file_info": "Info sul file caricato",
|
||||||
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
"backup_err_only_album": "Non è possibile rimuovere l'unico album",
|
||||||
"backup_info_card_assets": "Oggetti ",
|
"backup_info_card_assets": "oggetti ",
|
||||||
|
"cache_settings_album_thumbnails": "Anteprime pagine librerie ({} assets)",
|
||||||
|
"cache_settings_clear_cache_button": "Cancella cache",
|
||||||
|
"cache_settings_clear_cache_button_title": "Cancella cache app. Questo impatterà sulle prestazioni applicative fino a quando la cache non sarà rigenerata.",
|
||||||
|
"cache_settings_image_cache_size": "Dimensione cache foto ({} assets)",
|
||||||
|
"cache_settings_statistics_album": "Anteprime librerie",
|
||||||
|
"cache_settings_statistics_assets": "{} contenuti ({})",
|
||||||
|
"cache_settings_statistics_full": "Immagini complete",
|
||||||
|
"cache_settings_statistics_shared": "Anteprime album condivisi",
|
||||||
|
"cache_settings_statistics_thumbnail": "Anteprime",
|
||||||
|
"cache_settings_statistics_title": "Uso della cache",
|
||||||
|
"cache_settings_subtitle": "Controlla il comportamento della cache",
|
||||||
|
"cache_settings_thumbnail_size": "Dimensione cache anteprime ({} assets)",
|
||||||
|
"cache_settings_title": "Impostazioni della Cache",
|
||||||
"control_bottom_app_bar_delete": "Elimina",
|
"control_bottom_app_bar_delete": "Elimina",
|
||||||
|
"control_bottom_app_bar_share": "Condividi",
|
||||||
|
"create_album_page_untitled": "Senza titolo",
|
||||||
|
"create_shared_album_page_create": "Crea",
|
||||||
"create_shared_album_page_share": "Condividi",
|
"create_shared_album_page_share": "Condividi",
|
||||||
"create_shared_album_page_share_add_assets": "AGGIUNGI OGGETTI",
|
"create_shared_album_page_share_add_assets": "AGGIUNGI OGGETTI",
|
||||||
"create_shared_album_page_share_select_photos": "Seleziona foto",
|
"create_shared_album_page_share_select_photos": "Seleziona foto",
|
||||||
"daily_title_text_date": "E, dd MMM",
|
"daily_title_text_date": "E, dd MMM",
|
||||||
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
"daily_title_text_date_year": "E, dd MMM, yyyy",
|
||||||
"date_format": "E, d LLL, y • hh:mm",
|
"date_format": "E, d LLL, y • hh:mm",
|
||||||
"delete_dialog_alert": "Questi oggetti saranno cancellati permanentemente da Immich e dal tuo device",
|
"delete_dialog_alert": "Questi oggetti saranno cancellati definitivamente da Immich e dal tuo device",
|
||||||
"delete_dialog_cancel": "Annulla",
|
"delete_dialog_cancel": "Annulla",
|
||||||
"delete_dialog_ok": "Elimina",
|
"delete_dialog_ok": "Elimina",
|
||||||
"delete_dialog_title": "Cancella in modo permanente ",
|
"delete_dialog_title": "Cancella definitivamente",
|
||||||
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
|
"exif_bottom_sheet_description": "Aggiungi una descrizione...",
|
||||||
"exif_bottom_sheet_details": "DETTAGLI",
|
"exif_bottom_sheet_details": "DETTAGLI",
|
||||||
"exif_bottom_sheet_location": "POSIZIONE",
|
"exif_bottom_sheet_location": "POSIZIONE",
|
||||||
"login_form_button_text": "Accedi",
|
"library_page_albums": "Album",
|
||||||
|
"library_page_new_album": "Nuovo Album",
|
||||||
|
"login_form_button_text": "Login",
|
||||||
"login_form_email_hint": "tuaemail@email.com",
|
"login_form_email_hint": "tuaemail@email.com",
|
||||||
"login_form_endpoint_hint": "http://tuo-ip-del-server:port/api",
|
"login_form_endpoint_hint": "http://tuo-ip-del-server:port/api",
|
||||||
"login_form_endpoint_url": "URL del Server Endpoint",
|
"login_form_endpoint_url": "Server Endpoint URL",
|
||||||
"login_form_err_http": "Per favore specificare http:// o https://",
|
"login_form_err_http": "Per favore specificare http:// o https://",
|
||||||
"login_form_err_invalid_email": "Email non valida",
|
"login_form_err_invalid_email": "Email non valida",
|
||||||
"login_form_err_leading_whitespace": "Spazio bianco all'inizio ",
|
"login_form_err_leading_whitespace": "Spazio bianco all'inizio ",
|
||||||
@@ -74,33 +116,54 @@
|
|||||||
"login_form_save_login": "Rimani connesso ",
|
"login_form_save_login": "Rimani connesso ",
|
||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"profile_drawer_client_server_up_to_date": "Client e server sono aggiornati",
|
"profile_drawer_client_server_up_to_date": "Client e server sono aggiornati",
|
||||||
"profile_drawer_sign_out": "Esci",
|
"profile_drawer_settings": "Impostazioni ",
|
||||||
|
"profile_drawer_sign_out": "Logout",
|
||||||
"search_bar_hint": "Cerca le tue foto",
|
"search_bar_hint": "Cerca le tue foto",
|
||||||
"search_page_no_objects": "Nessuna Informazione relativa all'Oggetto Disponibile",
|
"search_page_no_objects": "Nessuna informazione relativa all'oggetto disponibile",
|
||||||
"search_page_no_places": "Nessun informazione sulla posizione ",
|
"search_page_no_places": "Nessun informazione sul luogo disponibile",
|
||||||
"search_page_places": "Luoghi",
|
"search_page_places": "Luoghi",
|
||||||
"search_page_things": "Oggetti",
|
"search_page_things": "Oggetti",
|
||||||
"search_result_page_new_search_hint": "Nuova ricerca ",
|
"search_result_page_new_search_hint": "Nuova ricerca ",
|
||||||
"select_additional_user_for_sharing_page_suggestions": "Suggerimenti ",
|
"select_additional_user_for_sharing_page_suggestions": "Suggerimenti ",
|
||||||
"select_user_for_sharing_page_err_album": "Fallito nel creare l'album ",
|
"select_user_for_sharing_page_err_album": "Errore nel creare l'album ",
|
||||||
"select_user_for_sharing_page_share_suggestions": "Suggerimenti",
|
"select_user_for_sharing_page_share_suggestions": "Suggerimenti",
|
||||||
|
"setting_notifications_notify_failures_grace_period": "Notifica caricamenti falliti in background: {}",
|
||||||
|
"setting_notifications_notify_hours": "{} Ore",
|
||||||
|
"setting_notifications_notify_immediately": "Immediatamente",
|
||||||
|
"setting_notifications_notify_minutes": "{} Minuti",
|
||||||
|
"setting_notifications_notify_never": "Mai",
|
||||||
|
"setting_notifications_subtitle": "Cambia le impostazioni di notifica",
|
||||||
|
"setting_notifications_title": "Notifiche",
|
||||||
|
"setting_pages_app_bar_settings": "Impostazioni",
|
||||||
"share_add": "Aggiungi",
|
"share_add": "Aggiungi",
|
||||||
"share_add_photos": "Aggiungi foto",
|
"share_add_photos": "Aggiungi foto",
|
||||||
"share_add_title": "Aggiungi un titolo ",
|
"share_add_title": "Aggiungi un titolo ",
|
||||||
"share_create_album": "Crea album",
|
"share_create_album": "Crea album",
|
||||||
"share_invite": "Invitare all'album ",
|
"share_dialog_preparing": "Preparo…",
|
||||||
|
"share_invite": "Invitare nell'album ",
|
||||||
"sharing_page_album": "Album condivisi",
|
"sharing_page_album": "Album condivisi",
|
||||||
"sharing_page_description": "Crea un album condiviso per condividere foto e video con gente nel tuo network",
|
"sharing_page_description": "Crea un album condiviso per condividere foto e video con persone nel tuo network",
|
||||||
"sharing_page_empty_list": "LISTA VUOTA",
|
"sharing_page_empty_list": "LISTA VUOTA",
|
||||||
"sharing_silver_appbar_create_shared_album": "Crea album condiviso",
|
"sharing_silver_appbar_create_shared_album": "Crea album condiviso",
|
||||||
"sharing_silver_appbar_share_partner": "Condividi con il partner",
|
"sharing_silver_appbar_share_partner": "Condividi con il partner",
|
||||||
|
"tab_controller_nav_library": "Libreria",
|
||||||
"tab_controller_nav_photos": "Foto",
|
"tab_controller_nav_photos": "Foto",
|
||||||
"tab_controller_nav_search": "Cerca",
|
"tab_controller_nav_search": "Cerca",
|
||||||
"tab_controller_nav_sharing": "Condividi",
|
"tab_controller_nav_sharing": "Condividi",
|
||||||
|
"theme_setting_asset_list_storage_indicator_title": "Mostra indicatore dello storage nei titoli dei contenuti",
|
||||||
|
"theme_setting_asset_list_tiles_per_row_title": "Numero di contenuti per riga ({})",
|
||||||
|
"theme_setting_dark_mode_switch": "Dark mode",
|
||||||
|
"theme_setting_image_viewer_quality_subtitle": "Cambia la qualità del dettaglio dell'immagine",
|
||||||
|
"theme_setting_image_viewer_quality_title": "Qualità immagine",
|
||||||
|
"theme_setting_system_theme_switch": "Automatico (Vai alle impostazioni di sistema)",
|
||||||
|
"theme_setting_theme_subtitle": "Scegli un'impostazione per il tema",
|
||||||
|
"theme_setting_theme_title": "Tema",
|
||||||
|
"theme_setting_three_stage_loading_subtitle": "Il caricamento in 3 stage aumenterà le performance di caricamento ma anche il consumo di banda",
|
||||||
|
"theme_setting_three_stage_loading_title": "Abilita il caricamento a tre stage",
|
||||||
"version_announcement_overlay_ack": "Riconosci ",
|
"version_announcement_overlay_ack": "Riconosci ",
|
||||||
"version_announcement_overlay_release_notes": "le note di rilascio ",
|
"version_announcement_overlay_release_notes": "note di rilascio ",
|
||||||
"version_announcement_overlay_text_1": "Ciao amico, c'è una nuova versione di",
|
"version_announcement_overlay_text_1": "Ciao amico, c'è una nuova versione di",
|
||||||
"version_announcement_overlay_text_2": "prova a controllare ",
|
"version_announcement_overlay_text_2": "per favore prenditi il tuo tempo per controllare il",
|
||||||
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore nella configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico delle immagini docker.",
|
"version_announcement_overlay_text_3": "e verifica che il tuo docker-compose e il file .env siano aggiornati per impedire qualsiasi errore nella configurazione, specialmente se utilizzate WatchTower o altri strumenti per l'aggiornamento automatico delle immagini docker.",
|
||||||
"version_announcement_overlay_title": "Nuova versione di server disponibile! \uD83C\uDF89"
|
"version_announcement_overlay_title": "Nuova versione del server disponibile! \uD83C\uDF89"
|
||||||
}
|
}
|
||||||
152
mobile/assets/i18n/ko-KR.json
Normal file
152
mobile/assets/i18n/ko-KR.json
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"album_info_card_backup_album_excluded": "제외됨",
|
||||||
|
"album_info_card_backup_album_included": "포함됨",
|
||||||
|
"album_viewer_appbar_share_delete": "앨범 삭제",
|
||||||
|
"album_viewer_appbar_share_err_delete": "앨범 삭제 실패",
|
||||||
|
"album_viewer_appbar_share_err_leave": "앨범에서 나가지 못했습니다",
|
||||||
|
"album_viewer_appbar_share_err_remove": "앨범에서 미디어를 제거하는 데 문제가 있습니다",
|
||||||
|
"album_viewer_appbar_share_err_title": "앨범 제목 변경 실패",
|
||||||
|
"album_viewer_appbar_share_leave": "앨범 나가기",
|
||||||
|
"album_viewer_appbar_share_remove": "앨범에서 제거",
|
||||||
|
"album_viewer_page_share_add_users": "사용자 추가",
|
||||||
|
"backup_album_selection_page_albums_device": "기기의 앨범({})",
|
||||||
|
"backup_album_selection_page_albums_tap": "포함하려면 탭하고 제외하려면 두 번 탭하세요",
|
||||||
|
"backup_album_selection_page_assets_scatter": "미디어파일은 여러 앨범에 분산될 수 있습니다. 따라서 백업 프로세스 중에 앨범에서 포함하거나 제외할 수 있습니다.",
|
||||||
|
"backup_album_selection_page_select_albums": "앨범 선택",
|
||||||
|
"backup_album_selection_page_selection_info": "선택 정보",
|
||||||
|
"backup_album_selection_page_total_assets": "총 미디어파일 수",
|
||||||
|
"backup_all": "모두",
|
||||||
|
"backup_background_service_default_notification": "새 미디어파일 확인중...",
|
||||||
|
"backup_background_service_upload_failure_notification": "{} 업로드 실패",
|
||||||
|
"backup_background_service_in_progress_notification": "미디어파일 백업 중...",
|
||||||
|
"backup_background_service_current_upload_notification": "{} 업로드 중",
|
||||||
|
"backup_background_service_error_title": "백업 오류",
|
||||||
|
"backup_background_service_connection_failed_message": "서버에 연결하지 못했습니다. 다시 시도하는 중...",
|
||||||
|
"backup_background_service_backup_failed_message": "미디어파일을 백업하지 못했습니다. 다시 시도하는 중...",
|
||||||
|
"backup_controller_page_albums": "백업대상",
|
||||||
|
"backup_controller_page_backup": "백업",
|
||||||
|
"backup_controller_page_backup_selected": "선택됨: ",
|
||||||
|
"backup_controller_page_backup_sub": "백업된 사진 및 비디오",
|
||||||
|
"backup_controller_page_background_description": "백그라운드 서비스를 켜서 앱을 열지 않고도 새 미디어파일을 자동으로 백업합니다.",
|
||||||
|
"backup_controller_page_background_wifi": "WiFi에서만",
|
||||||
|
"backup_controller_page_background_charging": "충전 중일 때만",
|
||||||
|
"backup_controller_page_background_is_on": "자동 백그라운드 백업이 켜져 있습니다",
|
||||||
|
"backup_controller_page_background_is_off": "자동 백그라운드 백업이 꺼져 있습니다",
|
||||||
|
"backup_controller_page_background_turn_on": "백그라운드 서비스 켜기",
|
||||||
|
"backup_controller_page_background_turn_off": "백그라운드 서비스 끄기",
|
||||||
|
"backup_controller_page_background_configure_error": "백그라운드 서비스를 구성하지 못했습니다",
|
||||||
|
"backup_controller_page_cancel": "취소",
|
||||||
|
"backup_controller_page_created": "생성일: {}",
|
||||||
|
"backup_controller_page_desc_backup": "새 미디어파일을 서버에 자동으로 업로드하려면 백업을 켜주세요.",
|
||||||
|
"backup_controller_page_excluded": "제외됨: ",
|
||||||
|
"backup_controller_page_failed": "실패함 ({})",
|
||||||
|
"backup_controller_page_filename": "파일 이름: {} [{}]",
|
||||||
|
"backup_controller_page_id": "ID: {}",
|
||||||
|
"backup_controller_page_info": "정보",
|
||||||
|
"backup_controller_page_none_selected": "선택되지 않음",
|
||||||
|
"backup_controller_page_remainder": "남은 백업파일",
|
||||||
|
"backup_controller_page_remainder_sub": "백업 대기중인 남은 사진 및 비디오",
|
||||||
|
"backup_controller_page_select": "선택",
|
||||||
|
"backup_controller_page_server_storage": "서버 저장소",
|
||||||
|
"backup_controller_page_start_backup": "백업 시작",
|
||||||
|
"backup_controller_page_status_off": "백업이 꺼져 있습니다",
|
||||||
|
"backup_controller_page_status_on": "백업이 켜져 있습니다",
|
||||||
|
"backup_controller_page_storage_format": "{}/{} 사용",
|
||||||
|
"backup_controller_page_to_backup": "백업할 앨범",
|
||||||
|
"backup_controller_page_total": "전체 백업대상",
|
||||||
|
"backup_controller_page_total_sub": "선택한 앨범의 모든 사진 및 비디오",
|
||||||
|
"backup_controller_page_turn_off": "백업 끄기",
|
||||||
|
"backup_controller_page_turn_on": "백업 켜기",
|
||||||
|
"backup_controller_page_uploading_file_info": "파일 정보 업로드 중",
|
||||||
|
"backup_err_only_album": "유일한 앨범은 제거할 수 없습니다",
|
||||||
|
"backup_info_card_assets": "미디어",
|
||||||
|
"control_bottom_app_bar_delete": "삭제",
|
||||||
|
"create_shared_album_page_share": "공유",
|
||||||
|
"create_shared_album_page_create": "만들기",
|
||||||
|
"create_shared_album_page_share_add_assets": "사진 추가",
|
||||||
|
"create_shared_album_page_share_select_photos": "사진 선택",
|
||||||
|
"daily_title_text_date": "E, M월 d일",
|
||||||
|
"daily_title_text_date_year": "E, M월 d일, yyyy",
|
||||||
|
"date_format": "yyyy년 M월 d일, EEEE • a h:mm",
|
||||||
|
"delete_dialog_alert": "이 항목은 Immich 및 휴대폰에서 영구적으로 삭제됩니다",
|
||||||
|
"delete_dialog_cancel": "취소",
|
||||||
|
"delete_dialog_ok": "삭제",
|
||||||
|
"delete_dialog_title": "영구적으로 삭제",
|
||||||
|
"exif_bottom_sheet_description": "설명 추가...",
|
||||||
|
"exif_bottom_sheet_details": "상세정보",
|
||||||
|
"exif_bottom_sheet_location": "위치",
|
||||||
|
"login_form_button_text": "로그인",
|
||||||
|
"login_form_email_hint": "youremail@email.com",
|
||||||
|
"login_form_endpoint_hint": "https://your-server-ip:port/api",
|
||||||
|
"login_form_endpoint_url": "서버 엔드포인트 URL",
|
||||||
|
"login_form_err_http": "엔드포인트는 http:// 또는 https://로 시작해야 합니다",
|
||||||
|
"login_form_err_invalid_email": "잘못된 이메일 형식입니다",
|
||||||
|
"login_form_err_leading_whitespace": "이메일 앞에 공백문자가 포함되어 있습니다",
|
||||||
|
"login_form_err_trailing_whitespace": "이메일 뒤에 공백문자가 포함되어 있습니다",
|
||||||
|
"login_form_failed_login": "로그인 오류, 서버 URL, 이메일 및 비밀번호를 확인하세요",
|
||||||
|
"login_form_label_email": "이메일",
|
||||||
|
"login_form_label_password": "비밀번호",
|
||||||
|
"login_form_password_hint": "비밀번호",
|
||||||
|
"login_form_save_login": "로그인상태 유지",
|
||||||
|
"monthly_title_text_date_format": "y년 M월",
|
||||||
|
"profile_drawer_client_server_up_to_date": "클라이언트와 서버가 최신 상태입니다",
|
||||||
|
"profile_drawer_sign_out": "로그아웃",
|
||||||
|
"profile_drawer_settings": "설정",
|
||||||
|
"search_bar_hint": "사진 검색",
|
||||||
|
"search_page_no_objects": "발견된 사물이\n없습니다",
|
||||||
|
"search_page_no_places": "발견된 장소가\n없습니다",
|
||||||
|
"search_page_places": "장소",
|
||||||
|
"search_page_things": "사물",
|
||||||
|
"search_result_page_new_search_hint": "새 검색",
|
||||||
|
"select_additional_user_for_sharing_page_suggestions": "초대 가능한 사용자 제안",
|
||||||
|
"select_user_for_sharing_page_err_album": "앨범 생성 실패",
|
||||||
|
"select_user_for_sharing_page_share_suggestions": "초대 가능한 사용자 제안",
|
||||||
|
"share_add": "추가",
|
||||||
|
"share_add_photos": "사진 추가",
|
||||||
|
"share_add_title": "새 앨범제목",
|
||||||
|
"share_create_album": "앨범 만들기",
|
||||||
|
"share_invite": "앨범에 초대",
|
||||||
|
"sharing_page_album": "공유앨범",
|
||||||
|
"sharing_page_description": "공유앨범을 만들어 다른 사용자들과 사진 및 비디오를 공유합니다.",
|
||||||
|
"sharing_page_empty_list": "공유앨범 없음",
|
||||||
|
"sharing_silver_appbar_create_shared_album": "공유앨범 만들기",
|
||||||
|
"sharing_silver_appbar_share_partner": "파트너와 공유",
|
||||||
|
"tab_controller_nav_photos": "사진",
|
||||||
|
"tab_controller_nav_search": "검색",
|
||||||
|
"tab_controller_nav_sharing": "공유",
|
||||||
|
"tab_controller_nav_library": "라이브러리",
|
||||||
|
"version_announcement_overlay_ack": "승인",
|
||||||
|
"version_announcement_overlay_release_notes": "릴리스 정보",
|
||||||
|
"version_announcement_overlay_text_1": "안녕하세요!",
|
||||||
|
"version_announcement_overlay_text_2": "앱에 새로운 업데이트가 있습니다!",
|
||||||
|
"version_announcement_overlay_text_3": "특히 WatchTower 또는 서버 응용 프로그램 자동 업데이트를 처리하는 메커니즘을 사용하는 경우 잘못된 구성을 방지하기 위해 docker-compose 및 .env 설정이 최신 상태인지 확인하세요.",
|
||||||
|
"version_announcement_overlay_title": "새 서버 버전 사용 가능 \uD83C\uDF89",
|
||||||
|
"album_thumbnail_card_item": "1개 항목",
|
||||||
|
"album_thumbnail_card_items": "{}개 항목",
|
||||||
|
"album_thumbnail_card_shared": " · 공유",
|
||||||
|
"library_page_albums": "앨범",
|
||||||
|
"library_page_new_album": "새 앨범",
|
||||||
|
"create_album_page_untitled": "제목없음",
|
||||||
|
"share_dialog_preparing": "준비중...",
|
||||||
|
"control_bottom_app_bar_share": "공유",
|
||||||
|
"setting_pages_app_bar_settings": "설정",
|
||||||
|
"theme_setting_theme_title": "테마",
|
||||||
|
"theme_setting_theme_subtitle": "앱테마 선택",
|
||||||
|
"theme_setting_system_theme_switch": "자동(시스템 설정에 따름)",
|
||||||
|
"theme_setting_dark_mode_switch": "다크모드",
|
||||||
|
"theme_setting_image_viewer_quality_title": "이미지 뷰어 품질",
|
||||||
|
"theme_setting_image_viewer_quality_subtitle": "디테일 이미지 뷰어 품질 조정",
|
||||||
|
"theme_setting_three_stage_loading_title": "3단계 로딩 활성화",
|
||||||
|
"theme_setting_three_stage_loading_subtitle": "이 기능은 로딩 성능을 향상시킬 수 있지만 훨씬 더 많은 데이터를 사용합니다.",
|
||||||
|
"asset_list_settings_title": "사진 배열",
|
||||||
|
"asset_list_settings_subtitle": "사진 배열 레이아웃 설정",
|
||||||
|
"theme_setting_asset_list_storage_indicator_title": "미디어 타일에 스토리지 싱크여부 표시",
|
||||||
|
"theme_setting_asset_list_tiles_per_row_title": "한 줄에 표시할 미디어 수 ({})",
|
||||||
|
"setting_notifications_title": "알림",
|
||||||
|
"setting_notifications_subtitle": "알림 기본 설정 조정",
|
||||||
|
"setting_notifications_notify_failures_grace_period": "백그라운드 백업 실패 알림: {}",
|
||||||
|
"setting_notifications_notify_immediately": "즉시",
|
||||||
|
"setting_notifications_notify_minutes": "{}분 뒤",
|
||||||
|
"setting_notifications_notify_hours": "{}시간 뒤",
|
||||||
|
"setting_notifications_notify_never": "알리지 않음"
|
||||||
|
}
|
||||||
152
mobile/assets/i18n/nl-NL.json
Normal file
152
mobile/assets/i18n/nl-NL.json
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
{
|
||||||
|
"album_info_card_backup_album_excluded": "UITGESLOTEN",
|
||||||
|
"album_info_card_backup_album_included": "INGESLOTEN",
|
||||||
|
"album_viewer_appbar_share_delete": "Verwijder album",
|
||||||
|
"album_viewer_appbar_share_err_delete": "Fout bij verwijderen album",
|
||||||
|
"album_viewer_appbar_share_err_leave": "Fout bij verlaten album",
|
||||||
|
"album_viewer_appbar_share_err_remove": "Er gaat iets mis bij het verwijderen van items uit het album",
|
||||||
|
"album_viewer_appbar_share_err_title": "Fout bij wijzigen album titel",
|
||||||
|
"album_viewer_appbar_share_leave": "Verlaat album",
|
||||||
|
"album_viewer_appbar_share_remove": "Verwijder uit album",
|
||||||
|
"album_viewer_page_share_add_users": "Voeg gebruiker toe",
|
||||||
|
"backup_album_selection_page_albums_device": "Albums op apparaat ({})",
|
||||||
|
"backup_album_selection_page_albums_tap": "Tik om in te voegen, dubbel tik om uit te sluiten",
|
||||||
|
"backup_album_selection_page_assets_scatter": "Items kunnen over verschillende albums verdeeld zijn, dus albums kunnen ingesloten of uitgesloten zijn van het backup proces.",
|
||||||
|
"backup_album_selection_page_select_albums": "Selecteer albums",
|
||||||
|
"backup_album_selection_page_selection_info": "Selectie info",
|
||||||
|
"backup_album_selection_page_total_assets": "Totaal unieke items",
|
||||||
|
"backup_all": "Alle",
|
||||||
|
"backup_background_service_default_notification": "Controleren op nieuw items…",
|
||||||
|
"backup_background_service_upload_failure_notification": "Fout bij upload {}",
|
||||||
|
"backup_background_service_in_progress_notification": "Backuppen van items…",
|
||||||
|
"backup_background_service_current_upload_notification": "Uploaden {}",
|
||||||
|
"backup_background_service_error_title": "Backup fout",
|
||||||
|
"backup_background_service_connection_failed_message": "Fout bij verbinden server. Opnieuw proberen…",
|
||||||
|
"backup_background_service_backup_failed_message": "Fout bij backuppen items. Opnieuw proberen…",
|
||||||
|
"backup_controller_page_albums": "Backup Albums",
|
||||||
|
"backup_controller_page_backup": "Backup",
|
||||||
|
"backup_controller_page_backup_selected": "Geselecteerd: ",
|
||||||
|
"backup_controller_page_backup_sub": "Foto's en video's gebackupped",
|
||||||
|
"backup_controller_page_background_description": "Gebruik achtergrondservice om automatisch nieuwe items te uploaden naar server zonder de app te openen",
|
||||||
|
"backup_controller_page_background_wifi": "Alleen op WiFi",
|
||||||
|
"backup_controller_page_background_charging": "Alleen tijdens opladen",
|
||||||
|
"backup_controller_page_background_is_on": "Automatische achtergrond backup staat aan",
|
||||||
|
"backup_controller_page_background_is_off": "Automatische achtergrond backup staat uit",
|
||||||
|
"backup_controller_page_background_turn_on": "Zet achtergrondservice aan",
|
||||||
|
"backup_controller_page_background_turn_off": "Zet achtergrondservice uit",
|
||||||
|
"backup_controller_page_background_configure_error": "Achtergrondservice configuratie mislukt",
|
||||||
|
"backup_controller_page_cancel": "Annuleren",
|
||||||
|
"backup_controller_page_created": "Gemaakt op: {}",
|
||||||
|
"backup_controller_page_desc_backup": "Configureer backup om automatisch nieuwe items te uploaden naar server.",
|
||||||
|
"backup_controller_page_excluded": "Uitgezonderd: ",
|
||||||
|
"backup_controller_page_failed": "Mislukt ({})",
|
||||||
|
"backup_controller_page_filename": "Bestandsnaam: {} [{}]",
|
||||||
|
"backup_controller_page_id": "ID: {}",
|
||||||
|
"backup_controller_page_info": "Backup informatie",
|
||||||
|
"backup_controller_page_none_selected": "Geen geselecteerd",
|
||||||
|
"backup_controller_page_remainder": "Rest",
|
||||||
|
"backup_controller_page_remainder_sub": "Overgebleven foto's en video's om te backuppen uit selectie",
|
||||||
|
"backup_controller_page_select": "Selecteer",
|
||||||
|
"backup_controller_page_server_storage": "Server Opslag",
|
||||||
|
"backup_controller_page_start_backup": "Start Backup",
|
||||||
|
"backup_controller_page_status_off": "Backup staat uit",
|
||||||
|
"backup_controller_page_status_on": "Backup staat aan",
|
||||||
|
"backup_controller_page_storage_format": "{} van {} gebruikt",
|
||||||
|
"backup_controller_page_to_backup": "Albums om te backuppen",
|
||||||
|
"backup_controller_page_total": "Totaal",
|
||||||
|
"backup_controller_page_total_sub": "Alle unieke foto's en video's uit geselecteerde albums",
|
||||||
|
"backup_controller_page_turn_off": "Backup uitzetten",
|
||||||
|
"backup_controller_page_turn_on": "Backup aanzetten",
|
||||||
|
"backup_controller_page_uploading_file_info": "Bestandsgegevens uploaden",
|
||||||
|
"backup_err_only_album": "Kan niet alleen het album verwijderen",
|
||||||
|
"backup_info_card_assets": "items",
|
||||||
|
"control_bottom_app_bar_delete": "Verwijderen",
|
||||||
|
"create_shared_album_page_share": "Delen",
|
||||||
|
"create_shared_album_page_create": "Aanmaken",
|
||||||
|
"create_shared_album_page_share_add_assets": "VOEG FOTO'S TOE",
|
||||||
|
"create_shared_album_page_share_select_photos": "Selecteer Foto's",
|
||||||
|
"daily_title_text_date": "E, MMM dd",
|
||||||
|
"daily_title_text_date_year": "E, MMM dd, yyyy",
|
||||||
|
"date_format": "E, LLL d, y • h:mm a",
|
||||||
|
"delete_dialog_alert": "Deze items zullen permanent verwijderd worden van Immich en je apparaat",
|
||||||
|
"delete_dialog_cancel": "Annuleren",
|
||||||
|
"delete_dialog_ok": "Verwijderen",
|
||||||
|
"delete_dialog_title": "Verwijder permanent",
|
||||||
|
"exif_bottom_sheet_description": "Voeg beschrijving toe...",
|
||||||
|
"exif_bottom_sheet_details": "DETAILS",
|
||||||
|
"exif_bottom_sheet_location": "LOCATIE",
|
||||||
|
"login_form_button_text": "Login",
|
||||||
|
"login_form_email_hint": "jouwemail@email.com",
|
||||||
|
"login_form_endpoint_hint": "http://jouw-server-ip:port/api",
|
||||||
|
"login_form_endpoint_url": "Server URL",
|
||||||
|
"login_form_err_http": "Voer http:// of https:// in",
|
||||||
|
"login_form_err_invalid_email": "Ongeldige Email",
|
||||||
|
"login_form_err_leading_whitespace": "Spatie aan het begin",
|
||||||
|
"login_form_err_trailing_whitespace": "Spatie aan het eind",
|
||||||
|
"login_form_failed_login": "Fout bij inloggen, controleer server url, email en wachtwoord",
|
||||||
|
"login_form_label_email": "Email",
|
||||||
|
"login_form_label_password": "Wachtwoord",
|
||||||
|
"login_form_password_hint": "wachtwoord",
|
||||||
|
"login_form_save_login": "Ingelogd blijven",
|
||||||
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
|
"profile_drawer_client_server_up_to_date": "Client en Server zijn up-to-date",
|
||||||
|
"profile_drawer_sign_out": "Uitloggen",
|
||||||
|
"profile_drawer_settings": "Instellingen",
|
||||||
|
"search_bar_hint": "Zoek je foto's",
|
||||||
|
"search_page_no_objects": "Geen object gegevens beschikbaar",
|
||||||
|
"search_page_no_places": "Geen locatie gegevens beschikbaar",
|
||||||
|
"search_page_places": "Plaatsen",
|
||||||
|
"search_page_things": "Dingen",
|
||||||
|
"search_result_page_new_search_hint": "Nieuw resultaat",
|
||||||
|
"select_additional_user_for_sharing_page_suggestions": "Suggesties",
|
||||||
|
"select_user_for_sharing_page_err_album": "Album aanmaken mislukt",
|
||||||
|
"select_user_for_sharing_page_share_suggestions": "Suggesties",
|
||||||
|
"share_add": "Toevoegen",
|
||||||
|
"share_add_photos": "Foto's toevoegen",
|
||||||
|
"share_add_title": "Titel toevoegen",
|
||||||
|
"share_create_album": "Album aanmaken",
|
||||||
|
"share_invite": "Uitnodigen voor album",
|
||||||
|
"sharing_page_album": "Gedeelde albums",
|
||||||
|
"sharing_page_description": "Maak gedeelde albums om foto's en video's te delen met mensen in je netwerk.",
|
||||||
|
"sharing_page_empty_list": "LEGE LIJST",
|
||||||
|
"sharing_silver_appbar_create_shared_album": "Maak gedeeld album",
|
||||||
|
"sharing_silver_appbar_share_partner": "Delen met partner",
|
||||||
|
"tab_controller_nav_photos": "Foto's",
|
||||||
|
"tab_controller_nav_search": "Zoeken",
|
||||||
|
"tab_controller_nav_sharing": "Delen",
|
||||||
|
"tab_controller_nav_library": "Bibliotheek",
|
||||||
|
"version_announcement_overlay_ack": "Bevestig",
|
||||||
|
"version_announcement_overlay_release_notes": "release opmerkingen",
|
||||||
|
"version_announcement_overlay_text_1": "Er is een nieuwe versie beschikbaar van",
|
||||||
|
"version_announcement_overlay_text_2": "neem je tijd en bezoek de ",
|
||||||
|
"version_announcement_overlay_text_3": " controleer of je docker-compose en .env up-to-date zijn om te voorkomen dat er misconfiguraties zijn, in het bijzonder als je gebruik maakt van WatchTower of een ander mechanisme dat je server automatisch configureert.",
|
||||||
|
"version_announcement_overlay_title": "Nieuwe server versie beschikbaar \uD83C\uDF89",
|
||||||
|
"album_thumbnail_card_item": "1 item",
|
||||||
|
"album_thumbnail_card_items": "{} items",
|
||||||
|
"album_thumbnail_card_shared": " · Gedeeld",
|
||||||
|
"library_page_albums": "Albums",
|
||||||
|
"library_page_new_album": "Nieuw album",
|
||||||
|
"create_album_page_untitled": "Naamloos",
|
||||||
|
"share_dialog_preparing": "Voorbereiden...",
|
||||||
|
"control_bottom_app_bar_share": "Delen",
|
||||||
|
"setting_pages_app_bar_settings": "Instellingen",
|
||||||
|
"theme_setting_theme_title": "Thema",
|
||||||
|
"theme_setting_theme_subtitle": "Kies de thema instelling van de app",
|
||||||
|
"theme_setting_system_theme_switch": "Automatisch (volg systeeminstelling)",
|
||||||
|
"theme_setting_dark_mode_switch": "Donkere modus",
|
||||||
|
"theme_setting_image_viewer_quality_title": "Foto weergave kwaliteit",
|
||||||
|
"theme_setting_image_viewer_quality_subtitle": "Pas de kwaliteit aan van de gedetailleerde foto weergave",
|
||||||
|
"theme_setting_three_stage_loading_title": "Drie-laags laden inschakelen",
|
||||||
|
"theme_setting_three_stage_loading_subtitle": "Three-stage loading might increase the loading performance but causes significantly higher network load",
|
||||||
|
"asset_list_settings_title": "Foto Grid",
|
||||||
|
"asset_list_settings_subtitle": "Foto grid layout instellingen",
|
||||||
|
"theme_setting_asset_list_storage_indicator_title": "Laat ruimte indicator zien bij item tegels",
|
||||||
|
"theme_setting_asset_list_tiles_per_row_title": "Aantal items per rij ({})",
|
||||||
|
"setting_notifications_title": "Notificaties",
|
||||||
|
"setting_notifications_subtitle": "Werk je notificatievoorkeuren bij",
|
||||||
|
"setting_notifications_notify_failures_grace_period": "Melding achtergrond backup fouten: {}",
|
||||||
|
"setting_notifications_notify_immediately": "meteen",
|
||||||
|
"setting_notifications_notify_minutes": "{} minuten",
|
||||||
|
"setting_notifications_notify_hours": "{} uur",
|
||||||
|
"setting_notifications_notify_never": "nooit"
|
||||||
|
}
|
||||||
@@ -17,7 +17,6 @@
|
|||||||
"backup_album_selection_page_total_assets": "Total de recursos exclusivos",
|
"backup_album_selection_page_total_assets": "Total de recursos exclusivos",
|
||||||
"backup_all": "Todos",
|
"backup_all": "Todos",
|
||||||
"backup_background_service_default_notification": "Checking for new assets…",
|
"backup_background_service_default_notification": "Checking for new assets…",
|
||||||
"backup_background_service_disable_battery_optimizations": "Por favor, desabilite a otimização da bateria para Immich para habilitar o backup em segundo plano",
|
|
||||||
"backup_background_service_upload_failure_notification": "Falha ao carregar {}",
|
"backup_background_service_upload_failure_notification": "Falha ao carregar {}",
|
||||||
"backup_background_service_in_progress_notification": "Fazendo backup de seus ativos…",
|
"backup_background_service_in_progress_notification": "Fazendo backup de seus ativos…",
|
||||||
"backup_background_service_current_upload_notification": "Enviando {}",
|
"backup_background_service_current_upload_notification": "Enviando {}",
|
||||||
|
|||||||
@@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>9.0</string>
|
<string>11.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/wakelock/ios"
|
:path: ".symlinks/plugins/wakelock/ios"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
|
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||||
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
|
||||||
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
fluttertoast: 16fbe6039d06a763f3533670197d01fc73459037
|
||||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||||
|
|||||||
@@ -360,7 +360,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 40;
|
CURRENT_PROJECT_VERSION = 52;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -495,7 +495,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 40;
|
CURRENT_PROJECT_VERSION = 52;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -522,7 +522,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 40;
|
CURRENT_PROJECT_VERSION = 52;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.21.0</string>
|
<string>1.27.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>40</string>
|
<string>52</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true />
|
<true />
|
||||||
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
|
||||||
@@ -92,7 +92,10 @@
|
|||||||
<string>it</string>
|
<string>it</string>
|
||||||
<string>fi</string>
|
<string>fi</string>
|
||||||
<string>ja</string>
|
<string>ja</string>
|
||||||
|
<string>ko</string>
|
||||||
|
<string>nl</string>
|
||||||
<string>pl</string>
|
<string>pl</string>
|
||||||
|
<string>pt</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Beta"
|
desc "iOS Beta"
|
||||||
lane :beta do
|
lane :beta do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.25.0"
|
version_number: "1.27.0"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
|||||||
@@ -5,32 +5,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000205">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000269">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.360401">
|
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.499192">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="4.012696">
|
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="30.057077">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.378836">
|
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.438506">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: build_app" time="80.023705">
|
<testcase classname="fastlane.lanes" name="4: build_app" time="91.259106">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="98.18403">
|
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="102.092139">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -22,4 +22,6 @@ const String userSettingInfoBox = "immichUserSettingInfoBox";
|
|||||||
|
|
||||||
// Background backup Info
|
// Background backup Info
|
||||||
const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box
|
const String backgroundBackupInfoBox = "immichBackgroundBackupInfoBox"; // Box
|
||||||
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
const String backupFailedSince = "immichBackupFailedSince"; // Key 1
|
||||||
|
const String backupRequireWifi = "immichBackupRequireWifi"; // Key 2
|
||||||
|
const String backupRequireCharging = "immichBackupRequireCharging"; // Key 3
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ const List<Locale> locales = [
|
|||||||
Locale('fr', 'FR'),
|
Locale('fr', 'FR'),
|
||||||
Locale('it', 'IT'),
|
Locale('it', 'IT'),
|
||||||
Locale('ja', 'JP'),
|
Locale('ja', 'JP'),
|
||||||
|
Locale('nl', 'NL'),
|
||||||
Locale('pl', 'PL'),
|
Locale('pl', 'PL'),
|
||||||
Locale('pt', 'PR')
|
Locale('pt', 'PR'),
|
||||||
|
Locale('ko', 'KR'),
|
||||||
];
|
];
|
||||||
|
|
||||||
const String translationsPath = 'assets/i18n';
|
const String translationsPath = 'assets/i18n';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -8,6 +9,7 @@ import 'package:immich_mobile/constants/hive_box.dart';
|
|||||||
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@@ -15,17 +17,18 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
final AssetResponseDto asset;
|
final AssetResponseDto asset;
|
||||||
final List<AssetResponseDto> assetList;
|
final List<AssetResponseDto> assetList;
|
||||||
final bool showStorageIndicator;
|
final bool showStorageIndicator;
|
||||||
|
final BaseCacheManager? cacheManager;
|
||||||
|
|
||||||
const AlbumViewerThumbnail({
|
const AlbumViewerThumbnail({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.assetList,
|
required this.assetList,
|
||||||
|
this.cacheManager,
|
||||||
this.showStorageIndicator = true,
|
this.showStorageIndicator = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final cacheKey = useState(1);
|
|
||||||
var box = Hive.box(userInfoBox);
|
var box = Hive.box(userInfoBox);
|
||||||
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
||||||
var deviceId = ref.watch(authenticationProvider).deviceId;
|
var deviceId = ref.watch(authenticationProvider).deviceId;
|
||||||
@@ -123,7 +126,8 @@ class AlbumViewerThumbnail extends HookConsumerWidget {
|
|||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(border: drawBorderColor()),
|
decoration: BoxDecoration(border: drawBorderColor()),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
cacheKey: "${asset.id}-${cacheKey.value}",
|
cacheManager: cacheManager,
|
||||||
|
cacheKey: asset.id,
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
memCacheHeight: 200,
|
memCacheHeight: 200,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:hive_flutter/hive_flutter.dart';
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
|
import 'package:immich_mobile/modules/album/providers/asset_selection.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class SelectionThumbnailImage extends HookConsumerWidget {
|
class SelectionThumbnailImage extends HookConsumerWidget {
|
||||||
@@ -15,15 +17,14 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final cacheKey = useState(1);
|
|
||||||
var box = Hive.box(userInfoBox);
|
var box = Hive.box(userInfoBox);
|
||||||
var thumbnailRequestUrl =
|
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
||||||
'${box.get(serverEndpointKey)}/asset/thumbnail/${asset.id}';
|
|
||||||
var selectedAsset =
|
var selectedAsset =
|
||||||
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
ref.watch(assetSelectionProvider).selectedNewAssetsForAlbum;
|
||||||
var newAssetsForAlbum =
|
var newAssetsForAlbum =
|
||||||
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
|
ref.watch(assetSelectionProvider).selectedAdditionalAssetsForAlbum;
|
||||||
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
var isAlbumExist = ref.watch(assetSelectionProvider).isAlbumExist;
|
||||||
|
final cacheService = ref.watch(cacheServiceProvider);
|
||||||
|
|
||||||
Widget _buildSelectionIcon(AssetResponseDto asset) {
|
Widget _buildSelectionIcon(AssetResponseDto asset) {
|
||||||
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
|
var isSelected = selectedAsset.map((item) => item.id).contains(asset.id);
|
||||||
@@ -113,7 +114,8 @@ class SelectionThumbnailImage extends HookConsumerWidget {
|
|||||||
Container(
|
Container(
|
||||||
decoration: BoxDecoration(border: drawBorderColor()),
|
decoration: BoxDecoration(border: drawBorderColor()),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
cacheKey: "${asset.id}-${cacheKey.value}",
|
cacheManager: cacheService.getCache(CacheType.thumbnail),
|
||||||
|
cacheKey: asset.id,
|
||||||
width: 150,
|
width: 150,
|
||||||
height: 150,
|
height: 150,
|
||||||
memCacheHeight: asset.type == AssetTypeEnum.IMAGE ? 150 : 150,
|
memCacheHeight: asset.type == AssetTypeEnum.IMAGE ? 150 : 150,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:flutter_hooks/flutter_hooks.dart';
|
|||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/constants/hive_box.dart';
|
import 'package:immich_mobile/constants/hive_box.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@@ -15,8 +16,7 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final cacheKey = useState(1);
|
final cacheService = ref.watch(cacheServiceProvider);
|
||||||
|
|
||||||
var box = Hive.box(userInfoBox);
|
var box = Hive.box(userInfoBox);
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
@@ -26,7 +26,8 @@ class SharedAlbumThumbnailImage extends HookConsumerWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
CachedNetworkImage(
|
CachedNetworkImage(
|
||||||
cacheKey: "${asset.id}-${cacheKey.value}",
|
cacheManager: cacheService.getCache(CacheType.thumbnail),
|
||||||
|
cacheKey: asset.id,
|
||||||
width: 500,
|
width: 500,
|
||||||
height: 500,
|
height: 500,
|
||||||
memCacheHeight: 500,
|
memCacheHeight: 500,
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:immich_mobile/modules/album/ui/album_viewer_thumbnail.dart';
|
|||||||
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
|
import 'package:immich_mobile/shared/ui/immich_sliver_persistent_app_bar_delegate.dart';
|
||||||
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
import 'package:immich_mobile/shared/views/immich_loading_overlay.dart';
|
||||||
@@ -191,6 +192,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||||
final bool showStorageIndicator =
|
final bool showStorageIndicator =
|
||||||
appSettingService.getSetting(AppSettingsEnum.storageIndicator);
|
appSettingService.getSetting(AppSettingsEnum.storageIndicator);
|
||||||
|
final cacheService = ref.watch(cacheServiceProvider);
|
||||||
|
|
||||||
if (albumInfo.assets.isNotEmpty) {
|
if (albumInfo.assets.isNotEmpty) {
|
||||||
return SliverPadding(
|
return SliverPadding(
|
||||||
@@ -205,6 +207,7 @@ class AlbumViewerPage extends HookConsumerWidget {
|
|||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
return AlbumViewerThumbnail(
|
return AlbumViewerThumbnail(
|
||||||
|
cacheManager: cacheService.getCache(CacheType.thumbnail),
|
||||||
asset: albumInfo.assets[index],
|
asset: albumInfo.assets[index],
|
||||||
assetList: albumInfo.assets,
|
assetList: albumInfo.assets,
|
||||||
showStorageIndicator: showStorageIndicator,
|
showStorageIndicator: showStorageIndicator,
|
||||||
|
|||||||
@@ -95,12 +95,12 @@ class SharingPage extends HookConsumerWidget {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 5.0, bottom: 5),
|
padding: const EdgeInsets.only(left: 5.0, bottom: 5),
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.offline_share_outlined,
|
Icons.offline_share_outlined,
|
||||||
size: 50,
|
size: 50,
|
||||||
// color: Theme.of(context).primaryColor,
|
color: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:photo_view/photo_view.dart';
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
|
||||||
enum _RemoteImageStatus { empty, thumbnail, preview, full }
|
enum _RemoteImageStatus { empty, thumbnail, preview, full }
|
||||||
@@ -63,11 +64,13 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
widget.onLoadingCompleted();
|
widget.onLoadingCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedNetworkImageProvider _authorizedImageProvider(String url) {
|
CachedNetworkImageProvider _authorizedImageProvider(
|
||||||
|
String url, String cacheKey, BaseCacheManager? cacheManager) {
|
||||||
return CachedNetworkImageProvider(
|
return CachedNetworkImageProvider(
|
||||||
url,
|
url,
|
||||||
headers: {"Authorization": widget.authToken},
|
headers: {"Authorization": widget.authToken},
|
||||||
cacheKey: url,
|
cacheKey: cacheKey,
|
||||||
|
cacheManager: cacheManager,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,8 +104,11 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _loadImages() {
|
void _loadImages() {
|
||||||
CachedNetworkImageProvider thumbnailProvider =
|
CachedNetworkImageProvider thumbnailProvider = _authorizedImageProvider(
|
||||||
_authorizedImageProvider(widget.thumbnailUrl);
|
widget.thumbnailUrl,
|
||||||
|
widget.cacheKey,
|
||||||
|
widget.thumbnailCacheManager,
|
||||||
|
);
|
||||||
_imageProvider = thumbnailProvider;
|
_imageProvider = thumbnailProvider;
|
||||||
|
|
||||||
thumbnailProvider.resolve(const ImageConfiguration()).addListener(
|
thumbnailProvider.resolve(const ImageConfiguration()).addListener(
|
||||||
@@ -115,8 +121,11 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (widget.previewUrl != null) {
|
if (widget.previewUrl != null) {
|
||||||
CachedNetworkImageProvider previewProvider =
|
CachedNetworkImageProvider previewProvider = _authorizedImageProvider(
|
||||||
_authorizedImageProvider(widget.previewUrl!);
|
widget.previewUrl!,
|
||||||
|
"${widget.cacheKey}_previewStage",
|
||||||
|
widget.previewCacheManager,
|
||||||
|
);
|
||||||
previewProvider.resolve(const ImageConfiguration()).addListener(
|
previewProvider.resolve(const ImageConfiguration()).addListener(
|
||||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||||
_performStateTransition(_RemoteImageStatus.preview, previewProvider);
|
_performStateTransition(_RemoteImageStatus.preview, previewProvider);
|
||||||
@@ -124,8 +133,11 @@ class _RemotePhotoViewState extends State<RemotePhotoView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CachedNetworkImageProvider fullProvider =
|
CachedNetworkImageProvider fullProvider = _authorizedImageProvider(
|
||||||
_authorizedImageProvider(widget.imageUrl);
|
widget.imageUrl,
|
||||||
|
"${widget.cacheKey}_fullStage",
|
||||||
|
widget.fullCacheManager,
|
||||||
|
);
|
||||||
fullProvider.resolve(const ImageConfiguration()).addListener(
|
fullProvider.resolve(const ImageConfiguration()).addListener(
|
||||||
ImageStreamListener((ImageInfo imageInfo, _) {
|
ImageStreamListener((ImageInfo imageInfo, _) {
|
||||||
_performStateTransition(_RemoteImageStatus.full, fullProvider);
|
_performStateTransition(_RemoteImageStatus.full, fullProvider);
|
||||||
@@ -153,6 +165,10 @@ class RemotePhotoView extends StatefulWidget {
|
|||||||
this.previewUrl,
|
this.previewUrl,
|
||||||
required this.onLoadingCompleted,
|
required this.onLoadingCompleted,
|
||||||
required this.onLoadingStart,
|
required this.onLoadingStart,
|
||||||
|
this.thumbnailCacheManager,
|
||||||
|
this.previewCacheManager,
|
||||||
|
this.fullCacheManager,
|
||||||
|
required this.cacheKey,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final String thumbnailUrl;
|
final String thumbnailUrl;
|
||||||
@@ -161,6 +177,10 @@ class RemotePhotoView extends StatefulWidget {
|
|||||||
final String? previewUrl;
|
final String? previewUrl;
|
||||||
final Function onLoadingCompleted;
|
final Function onLoadingCompleted;
|
||||||
final Function onLoadingStart;
|
final Function onLoadingStart;
|
||||||
|
final BaseCacheManager? thumbnailCacheManager;
|
||||||
|
final BaseCacheManager? previewCacheManager;
|
||||||
|
final BaseCacheManager? fullCacheManager;
|
||||||
|
final String cacheKey;
|
||||||
|
|
||||||
final void Function() onSwipeDown;
|
final void Function() onSwipeDown;
|
||||||
final void Function() onSwipeUp;
|
final void Function() onSwipeUp;
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -12,7 +10,7 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
required this.onMoreInfoPressed,
|
required this.onMoreInfoPressed,
|
||||||
required this.onDownloadPressed,
|
required this.onDownloadPressed,
|
||||||
required this.onSharePressed,
|
required this.onSharePressed,
|
||||||
this.loading = false
|
this.loading = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final AssetResponseDto asset;
|
final AssetResponseDto asset;
|
||||||
@@ -26,6 +24,8 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
double iconSize = 18.0;
|
double iconSize = 18.0;
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
|
// iconTheme: IconThemeData(color: Colors.grey[100]),
|
||||||
|
// actionsIconTheme: IconThemeData(color: Colors.grey[100]),
|
||||||
foregroundColor: Colors.grey[100],
|
foregroundColor: Colors.grey[100],
|
||||||
toolbarHeight: 60,
|
toolbarHeight: 60,
|
||||||
backgroundColor: Colors.black,
|
backgroundColor: Colors.black,
|
||||||
@@ -33,37 +33,32 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
AutoRouter.of(context).pop();
|
AutoRouter.of(context).pop();
|
||||||
},
|
},
|
||||||
icon: const Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back_ios_new_rounded,
|
Icons.arrow_back_ios_new_rounded,
|
||||||
size: 20.0,
|
size: 20.0,
|
||||||
|
color: Colors.grey[200],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (loading) Center(
|
if (loading)
|
||||||
child: Container(
|
Center(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 15.0),
|
child: Container(
|
||||||
width: iconSize,
|
margin: const EdgeInsets.symmetric(horizontal: 15.0),
|
||||||
height: iconSize,
|
width: iconSize,
|
||||||
child: const CircularProgressIndicator(strokeWidth: 2.0),
|
height: iconSize,
|
||||||
|
child: const CircularProgressIndicator(strokeWidth: 2.0),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
) ,
|
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
splashRadius: iconSize,
|
splashRadius: iconSize,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onDownloadPressed();
|
onDownloadPressed();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.cloud_download_rounded),
|
icon: Icon(
|
||||||
),
|
Icons.cloud_download_rounded,
|
||||||
IconButton(
|
color: Colors.grey[200],
|
||||||
iconSize: iconSize,
|
),
|
||||||
splashRadius: iconSize,
|
|
||||||
onPressed: () {
|
|
||||||
log("favorite");
|
|
||||||
},
|
|
||||||
icon: asset.isFavorite
|
|
||||||
? const Icon(Icons.favorite_rounded)
|
|
||||||
: const Icon(Icons.favorite_border_rounded),
|
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
@@ -71,7 +66,10 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
onSharePressed();
|
onSharePressed();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.share),
|
icon: Icon(
|
||||||
|
Icons.share,
|
||||||
|
color: Colors.grey[200],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
iconSize: iconSize,
|
iconSize: iconSize,
|
||||||
@@ -79,7 +77,10 @@ class TopControlAppBar extends ConsumerWidget with PreferredSizeWidget {
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
onMoreInfoPressed();
|
onMoreInfoPressed();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.more_horiz_rounded),
|
icon: Icon(
|
||||||
|
Icons.more_horiz_rounded,
|
||||||
|
color: Colors.grey[200],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:immich_mobile/modules/asset_viewer/ui/download_loading_indicator
|
|||||||
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/exif_bottom_sheet.dart';
|
||||||
import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart';
|
import 'package:immich_mobile/modules/asset_viewer/ui/remote_photo_view.dart';
|
||||||
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
import 'package:immich_mobile/modules/home/services/asset.service.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
import 'package:immich_mobile/utils/image_url_builder.dart';
|
import 'package:immich_mobile/utils/image_url_builder.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
@@ -40,6 +41,7 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final downloadAssetStatus =
|
final downloadAssetStatus =
|
||||||
ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
ref.watch(imageViewerStateProvider).downloadAssetStatus;
|
||||||
|
final cacheService = ref.watch(cacheServiceProvider);
|
||||||
|
|
||||||
getAssetExif() async {
|
getAssetExif() async {
|
||||||
assetDetail =
|
assetDetail =
|
||||||
@@ -73,6 +75,7 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
tag: heroTag,
|
tag: heroTag,
|
||||||
child: RemotePhotoView(
|
child: RemotePhotoView(
|
||||||
thumbnailUrl: getThumbnailUrl(asset),
|
thumbnailUrl: getThumbnailUrl(asset),
|
||||||
|
cacheKey: asset.id,
|
||||||
imageUrl: getImageUrl(asset),
|
imageUrl: getImageUrl(asset),
|
||||||
previewUrl: threeStageLoading
|
previewUrl: threeStageLoading
|
||||||
? getThumbnailUrl(asset, type: ThumbnailFormat.JPEG)
|
? getThumbnailUrl(asset, type: ThumbnailFormat.JPEG)
|
||||||
@@ -84,6 +87,12 @@ class ImageViewerPage extends HookConsumerWidget {
|
|||||||
onSwipeUp: () => showInfo(),
|
onSwipeUp: () => showInfo(),
|
||||||
onLoadingCompleted: onLoadingCompleted,
|
onLoadingCompleted: onLoadingCompleted,
|
||||||
onLoadingStart: onLoadingStart,
|
onLoadingStart: onLoadingStart,
|
||||||
|
thumbnailCacheManager:
|
||||||
|
cacheService.getCache(CacheType.thumbnail),
|
||||||
|
previewCacheManager:
|
||||||
|
cacheService.getCache(CacheType.imageViewerPreview),
|
||||||
|
fullCacheManager:
|
||||||
|
cacheService.getCache(CacheType.imageViewerFull),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -122,8 +122,8 @@ class BackgroundService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens an activity to let the user disable battery optimizations for Immich
|
/// Returns `true` if battery optimizations are disabled
|
||||||
Future<bool> disableBatteryOptimizations() async {
|
Future<bool> isIgnoringBatteryOptimizations() async {
|
||||||
if (!Platform.isAndroid) {
|
if (!Platform.isAndroid) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -131,12 +131,8 @@ class BackgroundService {
|
|||||||
if (!_isForegroundInitialized) {
|
if (!_isForegroundInitialized) {
|
||||||
await _initialize();
|
await _initialize();
|
||||||
}
|
}
|
||||||
final String message =
|
return await _foregroundChannel
|
||||||
"backup_background_service_disable_battery_optimizations".tr();
|
.invokeMethod('isIgnoringBatteryOptimizations');
|
||||||
return await _foregroundChannel.invokeMethod(
|
|
||||||
'disableBatteryOptimizations',
|
|
||||||
message,
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
bool? requireWifi,
|
bool? requireWifi,
|
||||||
bool? requireCharging,
|
bool? requireCharging,
|
||||||
required void Function(String msg) onError,
|
required void Function(String msg) onError,
|
||||||
|
required void Function() onBatteryInfo,
|
||||||
}) async {
|
}) async {
|
||||||
assert(enabled != null || requireWifi != null || requireCharging != null);
|
assert(enabled != null || requireWifi != null || requireCharging != null);
|
||||||
if (Platform.isAndroid) {
|
if (Platform.isAndroid) {
|
||||||
@@ -131,14 +132,21 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
|
|
||||||
if (state.backgroundBackup) {
|
if (state.backgroundBackup) {
|
||||||
if (!wasEnabled) {
|
if (!wasEnabled) {
|
||||||
await _backgroundService.disableBatteryOptimizations();
|
if (!await _backgroundService.isIgnoringBatteryOptimizations()) {
|
||||||
|
onBatteryInfo();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final bool success = await _backgroundService.stopService() &&
|
final bool success = await _backgroundService.stopService() &&
|
||||||
await _backgroundService.startService(
|
await _backgroundService.startService(
|
||||||
requireUnmetered: state.backupRequireWifi,
|
requireUnmetered: state.backupRequireWifi,
|
||||||
requireCharging: state.backupRequireCharging,
|
requireCharging: state.backupRequireCharging,
|
||||||
);
|
);
|
||||||
if (!success) {
|
if (success) {
|
||||||
|
await Hive.box(backgroundBackupInfoBox)
|
||||||
|
.put(backupRequireWifi, state.backupRequireWifi);
|
||||||
|
await Hive.box(backgroundBackupInfoBox)
|
||||||
|
.put(backupRequireCharging, state.backupRequireCharging);
|
||||||
|
} else {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backgroundBackup: wasEnabled,
|
backgroundBackup: wasEnabled,
|
||||||
backupRequireWifi: wasWifi,
|
backupRequireWifi: wasWifi,
|
||||||
@@ -549,10 +557,13 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
albums.lastExcludedBackupTime,
|
albums.lastExcludedBackupTime,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
final Box backgroundBox = await Hive.openBox(backgroundBackupInfoBox);
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
backupProgress: previous,
|
backupProgress: previous,
|
||||||
selectedBackupAlbums: selectedAlbums,
|
selectedBackupAlbums: selectedAlbums,
|
||||||
excludedBackupAlbums: excludedAlbums,
|
excludedBackupAlbums: excludedAlbums,
|
||||||
|
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
||||||
|
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return _resumeBackup();
|
return _resumeBackup();
|
||||||
@@ -590,6 +601,13 @@ class BackupNotifier extends StateNotifier<BackUpState> {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
|
if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
|
||||||
|
await Hive.box(backgroundBackupInfoBox).close();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
||||||
|
}
|
||||||
_backgroundService.releaseLock();
|
_backgroundService.releaseLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import 'package:immich_mobile/routing/router.dart';
|
|||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
|
import 'package:immich_mobile/modules/backup/ui/backup_info_card.dart';
|
||||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class BackupControllerPage extends HookConsumerWidget {
|
class BackupControllerPage extends HookConsumerWidget {
|
||||||
const BackupControllerPage({Key? key}) : super(key: key);
|
const BackupControllerPage({Key? key}) : super(key: key);
|
||||||
@@ -156,6 +157,46 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _showBatteryOptimizationInfoToUser() {
|
||||||
|
final buttonTextColor = Theme.of(context).primaryColor;
|
||||||
|
showDialog<void>(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: const Text(
|
||||||
|
'backup_controller_page_background_battery_info_title',
|
||||||
|
).tr(),
|
||||||
|
content: SingleChildScrollView(
|
||||||
|
child: const Text(
|
||||||
|
'backup_controller_page_background_battery_info_message',
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => launchUrl(
|
||||||
|
Uri.parse('https://dontkillmyapp.com'),
|
||||||
|
mode: LaunchMode.externalApplication),
|
||||||
|
child: Text(
|
||||||
|
"backup_controller_page_background_battery_info_link",
|
||||||
|
style: TextStyle(color: buttonTextColor),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
child: Text(
|
||||||
|
'backup_controller_page_background_battery_info_ok',
|
||||||
|
style: TextStyle(color: buttonTextColor),
|
||||||
|
).tr(),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
ListTile _buildBackgroundBackupController() {
|
ListTile _buildBackgroundBackupController() {
|
||||||
final bool isBackgroundEnabled = backupState.backgroundBackup;
|
final bool isBackgroundEnabled = backupState.backgroundBackup;
|
||||||
final bool isWifiRequired = backupState.backupRequireWifi;
|
final bool isWifiRequired = backupState.backupRequireWifi;
|
||||||
@@ -197,6 +238,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
.configureBackgroundBackup(
|
.configureBackgroundBackup(
|
||||||
requireWifi: isChecked,
|
requireWifi: isChecked,
|
||||||
onError: _showErrorToUser,
|
onError: _showErrorToUser,
|
||||||
|
onBatteryInfo: _showBatteryOptimizationInfoToUser,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -217,6 +259,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
.configureBackgroundBackup(
|
.configureBackgroundBackup(
|
||||||
requireCharging: isChecked,
|
requireCharging: isChecked,
|
||||||
onError: _showErrorToUser,
|
onError: _showErrorToUser,
|
||||||
|
onBatteryInfo: _showBatteryOptimizationInfoToUser,
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -225,6 +268,7 @@ class BackupControllerPage extends HookConsumerWidget {
|
|||||||
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
ref.read(backupProvider.notifier).configureBackgroundBackup(
|
||||||
enabled: !isBackgroundEnabled,
|
enabled: !isBackgroundEnabled,
|
||||||
onError: _showErrorToUser,
|
onError: _showErrorToUser,
|
||||||
|
onBatteryInfo: _showBatteryOptimizationInfoToUser,
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
isBackgroundEnabled
|
isBackgroundEnabled
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
|
import 'package:immich_mobile/modules/home/ui/thumbnail_image.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
@@ -9,11 +10,13 @@ class ImageGrid extends ConsumerWidget {
|
|||||||
final List<AssetResponseDto> sortedAssetGroup;
|
final List<AssetResponseDto> sortedAssetGroup;
|
||||||
final int tilesPerRow;
|
final int tilesPerRow;
|
||||||
final bool showStorageIndicator;
|
final bool showStorageIndicator;
|
||||||
|
final BaseCacheManager? cacheManager;
|
||||||
|
|
||||||
ImageGrid({
|
ImageGrid({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.assetGroup,
|
required this.assetGroup,
|
||||||
required this.sortedAssetGroup,
|
required this.sortedAssetGroup,
|
||||||
|
this.cacheManager,
|
||||||
this.tilesPerRow = 4,
|
this.tilesPerRow = 4,
|
||||||
this.showStorageIndicator = true,
|
this.showStorageIndicator = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@@ -36,6 +39,7 @@ class ImageGrid extends ConsumerWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
ThumbnailImage(
|
ThumbnailImage(
|
||||||
|
cacheManager: cacheManager,
|
||||||
asset: assetGroup[index],
|
asset: assetGroup[index],
|
||||||
assetList: sortedAssetGroup,
|
assetList: sortedAssetGroup,
|
||||||
showStorageIndicator: showStorageIndicator,
|
showStorageIndicator: showStorageIndicator,
|
||||||
|
|||||||
@@ -42,9 +42,10 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
top: 5,
|
top: 5,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
icon: const Icon(
|
icon: Icon(
|
||||||
Icons.face_outlined,
|
Icons.face_outlined,
|
||||||
size: 30,
|
size: 30,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Scaffold.of(context).openDrawer();
|
Scaffold.of(context).openDrawer();
|
||||||
@@ -109,7 +110,10 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
splashRadius: 25,
|
splashRadius: 25,
|
||||||
iconSize: 30,
|
iconSize: 30,
|
||||||
icon: isEnableAutoBackup
|
icon: isEnableAutoBackup
|
||||||
? const Icon(Icons.backup_rounded)
|
? Icon(
|
||||||
|
Icons.backup_rounded,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
)
|
||||||
: Badge(
|
: Badge(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
elevation: 3,
|
elevation: 3,
|
||||||
@@ -120,7 +124,10 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
|||||||
size: 8,
|
size: 8,
|
||||||
color: Colors.indigo,
|
color: Colors.indigo,
|
||||||
),
|
),
|
||||||
child: const Icon(Icons.backup_rounded),
|
child: Icon(
|
||||||
|
Icons.backup_rounded,
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var onPop = await AutoRouter.of(context)
|
var onPop = await AutoRouter.of(context)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart';
|
|||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
@@ -16,18 +17,18 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
final AssetResponseDto asset;
|
final AssetResponseDto asset;
|
||||||
final List<AssetResponseDto> assetList;
|
final List<AssetResponseDto> assetList;
|
||||||
final bool showStorageIndicator;
|
final bool showStorageIndicator;
|
||||||
|
final BaseCacheManager? cacheManager;
|
||||||
|
|
||||||
const ThumbnailImage(
|
const ThumbnailImage({
|
||||||
{Key? key,
|
Key? key,
|
||||||
required this.asset,
|
required this.asset,
|
||||||
required this.assetList,
|
required this.assetList,
|
||||||
this.showStorageIndicator = true})
|
this.cacheManager,
|
||||||
: super(key: key);
|
this.showStorageIndicator = true,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final cacheKey = useState(1);
|
|
||||||
|
|
||||||
var box = Hive.box(userInfoBox);
|
var box = Hive.box(userInfoBox);
|
||||||
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
var thumbnailRequestUrl = getThumbnailUrl(asset);
|
||||||
var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
|
var selectedAsset = ref.watch(homePageStateProvider).selectedItems;
|
||||||
@@ -94,7 +95,8 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
: const Border(),
|
: const Border(),
|
||||||
),
|
),
|
||||||
child: CachedNetworkImage(
|
child: CachedNetworkImage(
|
||||||
cacheKey: "${asset.id}-${cacheKey.value}",
|
cacheKey: asset.id,
|
||||||
|
cacheManager: cacheManager,
|
||||||
width: 300,
|
width: 300,
|
||||||
height: 300,
|
height: 300,
|
||||||
memCacheHeight: asset.type == AssetTypeEnum.IMAGE ? 250 : 400,
|
memCacheHeight: asset.type == AssetTypeEnum.IMAGE ? 250 : 400,
|
||||||
@@ -128,17 +130,18 @@ class ThumbnailImage extends HookConsumerWidget {
|
|||||||
child: _buildSelectionIcon(asset),
|
child: _buildSelectionIcon(asset),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (showStorageIndicator) Positioned(
|
if (showStorageIndicator)
|
||||||
right: 10,
|
Positioned(
|
||||||
bottom: 5,
|
right: 10,
|
||||||
child: Icon(
|
bottom: 5,
|
||||||
(deviceId != asset.deviceId)
|
child: Icon(
|
||||||
? Icons.cloud_done_outlined
|
(deviceId != asset.deviceId)
|
||||||
: Icons.photo_library_rounded,
|
? Icons.cloud_done_outlined
|
||||||
color: Colors.white,
|
: Icons.photo_library_rounded,
|
||||||
size: 18,
|
color: Colors.white,
|
||||||
),
|
size: 18,
|
||||||
)
|
),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:immich_mobile/modules/settings/services/app_settings.service.dar
|
|||||||
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
import 'package:immich_mobile/shared/providers/asset.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
import 'package:immich_mobile/shared/providers/websocket.provider.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
|
||||||
class HomePage extends HookConsumerWidget {
|
class HomePage extends HookConsumerWidget {
|
||||||
@@ -24,6 +25,7 @@ class HomePage extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final appSettingService = ref.watch(appSettingsServiceProvider);
|
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||||
|
final cacheService = ref.watch(cacheServiceProvider);
|
||||||
|
|
||||||
ScrollController scrollController = useScrollController();
|
ScrollController scrollController = useScrollController();
|
||||||
var assetGroupByDateTime = ref.watch(assetGroupByDateTimeProvider);
|
var assetGroupByDateTime = ref.watch(assetGroupByDateTimeProvider);
|
||||||
@@ -65,37 +67,46 @@ class HomePage extends HookConsumerWidget {
|
|||||||
int? lastMonth;
|
int? lastMonth;
|
||||||
|
|
||||||
assetGroupByDateTime.forEach((dateGroup, immichAssetList) {
|
assetGroupByDateTime.forEach((dateGroup, immichAssetList) {
|
||||||
DateTime parseDateGroup = DateTime.parse(dateGroup);
|
try {
|
||||||
int currentMonth = parseDateGroup.month;
|
DateTime parseDateGroup = DateTime.parse(dateGroup);
|
||||||
|
int currentMonth = parseDateGroup.month;
|
||||||
|
|
||||||
if (lastMonth != null) {
|
if (lastMonth != null) {
|
||||||
if (currentMonth - lastMonth! != 0) {
|
if (currentMonth - lastMonth! != 0) {
|
||||||
imageGridGroup.add(
|
imageGridGroup.add(
|
||||||
MonthlyTitleText(
|
MonthlyTitleText(
|
||||||
isoDate: dateGroup,
|
isoDate: dateGroup,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
imageGridGroup.add(
|
||||||
|
DailyTitleText(
|
||||||
|
key: Key('${dateGroup.toString()}title'),
|
||||||
|
isoDate: dateGroup,
|
||||||
|
assetGroup: immichAssetList,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
imageGridGroup.add(
|
||||||
|
ImageGrid(
|
||||||
|
cacheManager: cacheService.getCache(CacheType.thumbnail),
|
||||||
|
assetGroup: immichAssetList,
|
||||||
|
sortedAssetGroup: sortedAssetList,
|
||||||
|
tilesPerRow:
|
||||||
|
appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
||||||
|
showStorageIndicator: appSettingService
|
||||||
|
.getSetting(AppSettingsEnum.storageIndicator),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
lastMonth = currentMonth;
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint(
|
||||||
|
"[ERROR] Cannot parse $dateGroup - Wrong create date format : ${immichAssetList.map((asset) => asset.createdAt).toList()}",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
imageGridGroup.add(
|
|
||||||
DailyTitleText(
|
|
||||||
key: Key('${dateGroup.toString()}title'),
|
|
||||||
isoDate: dateGroup,
|
|
||||||
assetGroup: immichAssetList,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
imageGridGroup.add(
|
|
||||||
ImageGrid(
|
|
||||||
assetGroup: immichAssetList,
|
|
||||||
sortedAssetGroup: sortedAssetList,
|
|
||||||
tilesPerRow: appSettingService.getSetting(AppSettingsEnum.tilesPerRow),
|
|
||||||
showStorageIndicator: appSettingService.getSetting(AppSettingsEnum.storageIndicator),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
lastMonth = currentMonth;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ enum AppSettingsEnum<T> {
|
|||||||
tilesPerRow<int>("tilesPerRow", 4),
|
tilesPerRow<int>("tilesPerRow", 4),
|
||||||
uploadErrorNotificationGracePeriod<int>(
|
uploadErrorNotificationGracePeriod<int>(
|
||||||
"uploadErrorNotificationGracePeriod", 2),
|
"uploadErrorNotificationGracePeriod", 2),
|
||||||
storageIndicator<bool>("storageIndicator", true);
|
storageIndicator<bool>("storageIndicator", true),
|
||||||
|
thumbnailCacheSize<int>("thumbnailCacheSize", 10000),
|
||||||
|
imageCacheSize<int>("imageCacheSize", 350),
|
||||||
|
albumThumbnailCacheSize<int>("albumThumbnailCacheSize", 200);
|
||||||
|
|
||||||
const AppSettingsEnum(this.hiveKey, this.defaultValue);
|
const AppSettingsEnum(this.hiveKey, this.defaultValue);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/ui/cache_settings/cache_settings_slider_pref.dart';
|
||||||
|
import 'package:immich_mobile/shared/services/cache.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/bytes_units.dart';
|
||||||
|
|
||||||
|
class CacheSettings extends HookConsumerWidget {
|
||||||
|
const CacheSettings({
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final CacheService cacheService = ref.watch(cacheServiceProvider);
|
||||||
|
|
||||||
|
final clearCacheState = useState(false);
|
||||||
|
|
||||||
|
Future<void> clearCache() async {
|
||||||
|
await cacheService.emptyAllCaches();
|
||||||
|
clearCacheState.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget cacheStatisticsRow(String name, CacheType type) {
|
||||||
|
final cacheSize = useState(0);
|
||||||
|
final cacheAssets = useState(0);
|
||||||
|
|
||||||
|
if (!clearCacheState.value) {
|
||||||
|
final repo = cacheService.getCacheRepo(type);
|
||||||
|
|
||||||
|
repo.open().then((_) {
|
||||||
|
cacheSize.value = repo.getCacheSize();
|
||||||
|
cacheAssets.value = repo.getNumberOfCachedObjects();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
cacheSize.value = 0;
|
||||||
|
cacheAssets.value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.only(left: 20, bottom: 10),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
name,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Text(
|
||||||
|
"cache_settings_statistics_assets",
|
||||||
|
style: TextStyle(color: Colors.grey),
|
||||||
|
).tr(
|
||||||
|
args: ["${cacheAssets.value}", formatBytes(cacheSize.value)],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ExpansionTile(
|
||||||
|
expandedCrossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
textColor: Theme.of(context).primaryColor,
|
||||||
|
title: const Text(
|
||||||
|
'cache_settings_title',
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
subtitle: const Text(
|
||||||
|
'cache_settings_subtitle',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 13,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
children: [
|
||||||
|
const CacheSettingsSliderPref(
|
||||||
|
setting: AppSettingsEnum.thumbnailCacheSize,
|
||||||
|
translationKey: "cache_settings_thumbnail_size",
|
||||||
|
min: 1000,
|
||||||
|
max: 20000,
|
||||||
|
divisions: 19,
|
||||||
|
),
|
||||||
|
const CacheSettingsSliderPref(
|
||||||
|
setting: AppSettingsEnum.imageCacheSize,
|
||||||
|
translationKey: "cache_settings_image_cache_size",
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
divisions: 20,
|
||||||
|
),
|
||||||
|
const CacheSettingsSliderPref(
|
||||||
|
setting: AppSettingsEnum.albumThumbnailCacheSize,
|
||||||
|
translationKey: "cache_settings_album_thumbnails",
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
divisions: 20,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text(
|
||||||
|
"cache_settings_statistics_title",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
cacheStatisticsRow(
|
||||||
|
"cache_settings_statistics_thumbnail".tr(),
|
||||||
|
CacheType.thumbnail,
|
||||||
|
),
|
||||||
|
cacheStatisticsRow(
|
||||||
|
"cache_settings_statistics_album".tr(),
|
||||||
|
CacheType.albumThumbnail,
|
||||||
|
),
|
||||||
|
cacheStatisticsRow(
|
||||||
|
"cache_settings_statistics_shared".tr(),
|
||||||
|
CacheType.sharedAlbumThumbnail,
|
||||||
|
),
|
||||||
|
cacheStatisticsRow(
|
||||||
|
"cache_settings_statistics_full".tr(),
|
||||||
|
CacheType.imageViewerFull,
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: const Text(
|
||||||
|
"cache_settings_clear_cache_button_title",
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: clearCache,
|
||||||
|
child: const Text(
|
||||||
|
"cache_settings_clear_cache_button",
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
).tr(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
|
|
||||||
|
class CacheSettingsSliderPref extends HookConsumerWidget {
|
||||||
|
final AppSettingsEnum<int> setting;
|
||||||
|
final String translationKey;
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final int divisions;
|
||||||
|
|
||||||
|
const CacheSettingsSliderPref({
|
||||||
|
Key? key,
|
||||||
|
required this.setting,
|
||||||
|
required this.translationKey,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
required this.divisions,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final appSettingService = ref.watch(appSettingsServiceProvider);
|
||||||
|
|
||||||
|
final itemsValue = useState(appSettingService.getSetting<int>(setting));
|
||||||
|
|
||||||
|
void sliderChanged(double value) {
|
||||||
|
itemsValue.value = value.toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void sliderChangedEnd(double value) {
|
||||||
|
appSettingService.setSetting(setting, value.toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text(
|
||||||
|
translationKey,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
).tr(args: ["${itemsValue.value.toInt()}"]),
|
||||||
|
),
|
||||||
|
Slider(
|
||||||
|
onChangeEnd: sliderChangedEnd,
|
||||||
|
onChanged: sliderChanged,
|
||||||
|
value: itemsValue.value.toDouble(),
|
||||||
|
min: min.toDouble(),
|
||||||
|
max: max.toDouble(),
|
||||||
|
divisions: divisions,
|
||||||
|
label: "${itemsValue.value.toInt()}",
|
||||||
|
activeColor: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_settings.dart';
|
import 'package:immich_mobile/modules/settings/ui/asset_list_settings/asset_list_settings.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/ui/cache_settings/cache_settings.dart';
|
||||||
import 'package:immich_mobile/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart';
|
import 'package:immich_mobile/modules/settings/ui/image_viewer_quality_setting/image_viewer_quality_setting.dart';
|
||||||
import 'package:immich_mobile/modules/settings/ui/notification_setting/notification_setting.dart';
|
import 'package:immich_mobile/modules/settings/ui/notification_setting/notification_setting.dart';
|
||||||
import 'package:immich_mobile/modules/settings/ui/theme_setting/theme_setting.dart';
|
import 'package:immich_mobile/modules/settings/ui/theme_setting/theme_setting.dart';
|
||||||
@@ -41,6 +42,7 @@ class SettingsPage extends HookConsumerWidget {
|
|||||||
const ImageViewerQualitySetting(),
|
const ImageViewerQualitySetting(),
|
||||||
const ThemeSetting(),
|
const ThemeSetting(),
|
||||||
const AssetListSettings(),
|
const AssetListSettings(),
|
||||||
|
const CacheSettings(),
|
||||||
if (Platform.isAndroid) const NotificationSetting(),
|
if (Platform.isAndroid) const NotificationSetting(),
|
||||||
],
|
],
|
||||||
).toList(),
|
).toList(),
|
||||||
|
|||||||
@@ -1,21 +1,79 @@
|
|||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/providers/app_settings.provider.dart';
|
||||||
|
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
|
||||||
|
import 'package:immich_mobile/utils/immich_cache_info_repository.dart';
|
||||||
|
|
||||||
enum CacheType {
|
enum CacheType {
|
||||||
|
// Shared cache for asset thumbnails in various modules
|
||||||
|
thumbnail,
|
||||||
|
|
||||||
|
imageViewerPreview,
|
||||||
|
imageViewerFull,
|
||||||
albumThumbnail,
|
albumThumbnail,
|
||||||
sharedAlbumThumbnail;
|
sharedAlbumThumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
final cacheServiceProvider = Provider((_) => CacheService());
|
final cacheServiceProvider = Provider(
|
||||||
|
(ref) => CacheService(ref.watch(appSettingsServiceProvider)),
|
||||||
|
);
|
||||||
|
|
||||||
class CacheService {
|
class CacheService {
|
||||||
|
final AppSettingsService _settingsService;
|
||||||
|
final _cacheRepositoryInstances = <CacheType, ImmichCacheRepository>{};
|
||||||
|
|
||||||
|
CacheService(this._settingsService);
|
||||||
|
|
||||||
BaseCacheManager getCache(CacheType type) {
|
BaseCacheManager getCache(CacheType type) {
|
||||||
return _getDefaultCache(type.name);
|
return _getDefaultCache(
|
||||||
|
type.name,
|
||||||
|
_getCacheSize(type) + 1,
|
||||||
|
getCacheRepo(type),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseCacheManager _getDefaultCache(String cacheName) {
|
ImmichCacheRepository getCacheRepo(CacheType type) {
|
||||||
return CacheManager(Config(cacheName));
|
if (!_cacheRepositoryInstances.containsKey(type)) {
|
||||||
|
final repo = ImmichCacheInfoRepository(
|
||||||
|
"cache_${type.name}",
|
||||||
|
"cacheKeys_${type.name}",
|
||||||
|
);
|
||||||
|
_cacheRepositoryInstances[type] = repo;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _cacheRepositoryInstances[type]!;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
Future<void> emptyAllCaches() async {
|
||||||
|
for (var type in CacheType.values) {
|
||||||
|
await getCache(type).emptyCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _getCacheSize(CacheType type) {
|
||||||
|
switch (type) {
|
||||||
|
case CacheType.thumbnail:
|
||||||
|
return _settingsService.getSetting(AppSettingsEnum.thumbnailCacheSize);
|
||||||
|
case CacheType.imageViewerPreview:
|
||||||
|
case CacheType.imageViewerFull:
|
||||||
|
return _settingsService.getSetting(AppSettingsEnum.imageCacheSize);
|
||||||
|
case CacheType.sharedAlbumThumbnail:
|
||||||
|
case CacheType.albumThumbnail:
|
||||||
|
return _settingsService
|
||||||
|
.getSetting(AppSettingsEnum.albumThumbnailCacheSize);
|
||||||
|
default:
|
||||||
|
return 200;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseCacheManager _getDefaultCache(
|
||||||
|
String cacheName, int size, CacheInfoRepository repo) {
|
||||||
|
return CacheManager(
|
||||||
|
Config(
|
||||||
|
cacheName,
|
||||||
|
maxNrOfCacheObjects: size,
|
||||||
|
repo: repo,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
15
mobile/lib/utils/bytes_units.dart
Normal file
15
mobile/lib/utils/bytes_units.dart
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
String formatBytes(int bytes) {
|
||||||
|
if (bytes < 1000) {
|
||||||
|
return "$bytes B";
|
||||||
|
} else if (bytes < 1000000) {
|
||||||
|
final kb = (bytes / 1000).toStringAsFixed(1);
|
||||||
|
return "$kb kB";
|
||||||
|
} else if (bytes < 1000000000) {
|
||||||
|
final mb = (bytes / 1000000).toStringAsFixed(1);
|
||||||
|
return "$mb MB";
|
||||||
|
} else {
|
||||||
|
final gb = (bytes / 1000000000).toStringAsFixed(1);
|
||||||
|
return "$gb GB";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,9 @@ class FileHelper {
|
|||||||
case 'png':
|
case 'png':
|
||||||
return {"type": "image", "subType": "png"};
|
return {"type": "image", "subType": "png"};
|
||||||
|
|
||||||
|
case 'tif':
|
||||||
|
return {"type": "image", "subType": "tiff"};
|
||||||
|
|
||||||
case 'mov':
|
case 'mov':
|
||||||
return {"type": "video", "subType": "quicktime"};
|
return {"type": "video", "subType": "quicktime"};
|
||||||
|
|
||||||
@@ -38,6 +41,9 @@ class FileHelper {
|
|||||||
case 'webp':
|
case 'webp':
|
||||||
return {"type": "image", "subType": "webp"};
|
return {"type": "image", "subType": "webp"};
|
||||||
|
|
||||||
|
case '3gp':
|
||||||
|
return {"type": "video", "subType": "3gpp"};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return {"type": "unsupport", "subType": "unsupport"};
|
return {"type": "unsupport", "subType": "unsupport"};
|
||||||
}
|
}
|
||||||
|
|||||||
204
mobile/lib/utils/immich_cache_info_repository.dart
Normal file
204
mobile/lib/utils/immich_cache_info_repository.dart
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
|
import 'package:flutter_cache_manager/src/storage/cache_object.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
// Implementation of a CacheInfoRepository based on Hive
|
||||||
|
abstract class ImmichCacheRepository extends CacheInfoRepository {
|
||||||
|
int getNumberOfCachedObjects();
|
||||||
|
int getCacheSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImmichCacheInfoRepository extends ImmichCacheRepository {
|
||||||
|
final String hiveBoxName;
|
||||||
|
final String keyLookupHiveBoxName;
|
||||||
|
|
||||||
|
// To circumvent some of the limitations of a non-relational key-value database,
|
||||||
|
// we use two hive boxes per cache.
|
||||||
|
// [cacheObjectLookupBox] maps ids to cache objects.
|
||||||
|
// [keyLookupHiveBox] maps keys to ids.
|
||||||
|
// The lookup of a cache object by key therefore involves two steps:
|
||||||
|
// id = keyLookupHiveBox[key]
|
||||||
|
// object = cacheObjectLookupBox[id]
|
||||||
|
late Box<Map<dynamic, dynamic>> cacheObjectLookupBox;
|
||||||
|
late Box<int> keyLookupHiveBox;
|
||||||
|
|
||||||
|
ImmichCacheInfoRepository(this.hiveBoxName, this.keyLookupHiveBoxName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> close() async {
|
||||||
|
await cacheObjectLookupBox.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> delete(int id) async {
|
||||||
|
if (cacheObjectLookupBox.containsKey(id)) {
|
||||||
|
await cacheObjectLookupBox.delete(id);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> deleteAll(Iterable<int> ids) async {
|
||||||
|
int deleted = 0;
|
||||||
|
for (var id in ids) {
|
||||||
|
if (cacheObjectLookupBox.containsKey(id)) {
|
||||||
|
deleted++;
|
||||||
|
await cacheObjectLookupBox.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deleteDataFile() async {
|
||||||
|
await cacheObjectLookupBox.clear();
|
||||||
|
await keyLookupHiveBox.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> exists() async {
|
||||||
|
return cacheObjectLookupBox.isNotEmpty && keyLookupHiveBox.isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CacheObject?> get(String key) async {
|
||||||
|
if (!keyLookupHiveBox.containsKey(key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int id = keyLookupHiveBox.get(key)!;
|
||||||
|
if (!cacheObjectLookupBox.containsKey(id)) {
|
||||||
|
keyLookupHiveBox.delete(key);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return _deserialize(cacheObjectLookupBox.get(id)!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<CacheObject>> getAllObjects() async {
|
||||||
|
return cacheObjectLookupBox.values.map(_deserialize).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<CacheObject>> getObjectsOverCapacity(int capacity) async {
|
||||||
|
if (cacheObjectLookupBox.length <= capacity) {
|
||||||
|
return List.empty();
|
||||||
|
}
|
||||||
|
var values = cacheObjectLookupBox.values.map(_deserialize).toList();
|
||||||
|
values.sort((CacheObject a, CacheObject b) {
|
||||||
|
final aTouched = a.touched ?? DateTime.fromMicrosecondsSinceEpoch(0);
|
||||||
|
final bTouched = b.touched ?? DateTime.fromMicrosecondsSinceEpoch(0);
|
||||||
|
|
||||||
|
return aTouched.compareTo(bTouched);
|
||||||
|
});
|
||||||
|
return values.skip(capacity).take(10).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<CacheObject>> getOldObjects(Duration maxAge) async {
|
||||||
|
return cacheObjectLookupBox.values
|
||||||
|
.map(_deserialize)
|
||||||
|
.where((CacheObject element) {
|
||||||
|
DateTime touched =
|
||||||
|
element.touched ?? DateTime.fromMicrosecondsSinceEpoch(0);
|
||||||
|
return touched.isBefore(DateTime.now().subtract(maxAge));
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<CacheObject> insert(CacheObject cacheObject,
|
||||||
|
{bool setTouchedToNow = true}) async {
|
||||||
|
int newId = keyLookupHiveBox.length == 0
|
||||||
|
? 0
|
||||||
|
: keyLookupHiveBox.values.reduce(max) + 1;
|
||||||
|
cacheObject = cacheObject.copyWith(id: newId);
|
||||||
|
|
||||||
|
keyLookupHiveBox.put(cacheObject.key, newId);
|
||||||
|
cacheObjectLookupBox.put(newId, cacheObject.toMap());
|
||||||
|
|
||||||
|
return cacheObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<bool> open() async {
|
||||||
|
cacheObjectLookupBox = await Hive.openBox(hiveBoxName);
|
||||||
|
keyLookupHiveBox = await Hive.openBox(keyLookupHiveBoxName);
|
||||||
|
|
||||||
|
// The cache might have cleared by the operating system.
|
||||||
|
// This could create inconsistencies between the file system cache and database.
|
||||||
|
// To check whether the cache was cleared, a file within the cache directory
|
||||||
|
// is created for each database. If the file is absent, the cache was cleared and therefore
|
||||||
|
// the database has to be cleared as well.
|
||||||
|
if (!await _checkAndCreateAnchorFile()) {
|
||||||
|
await cacheObjectLookupBox.clear();
|
||||||
|
await keyLookupHiveBox.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheObjectLookupBox.isOpen;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<int> update(CacheObject cacheObject,
|
||||||
|
{bool setTouchedToNow = true}) async {
|
||||||
|
if (cacheObject.id != null) {
|
||||||
|
cacheObjectLookupBox.put(cacheObject.id, cacheObject.toMap());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future updateOrInsert(CacheObject cacheObject) {
|
||||||
|
if (cacheObject.id == null) {
|
||||||
|
return insert(cacheObject);
|
||||||
|
} else {
|
||||||
|
return update(cacheObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int getNumberOfCachedObjects() {
|
||||||
|
return cacheObjectLookupBox.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int getCacheSize() {
|
||||||
|
final cacheElementsWithSize =
|
||||||
|
cacheObjectLookupBox.values.map(_deserialize).map((e) => e.length ?? 0);
|
||||||
|
|
||||||
|
if (cacheElementsWithSize.isEmpty) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheElementsWithSize.reduce((value, element) => value + element);
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheObject _deserialize(Map serData) {
|
||||||
|
Map<String, dynamic> converted = {};
|
||||||
|
|
||||||
|
serData.forEach((key, value) {
|
||||||
|
converted[key.toString()] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
return CacheObject.fromMap(converted);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _checkAndCreateAnchorFile() async {
|
||||||
|
final tmpDir = await getTemporaryDirectory();
|
||||||
|
final cacheFile = File(p.join(tmpDir.path, "$hiveBoxName.tmp"));
|
||||||
|
|
||||||
|
if (await cacheFile.exists()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await cacheFile.create();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,8 @@ doc/AdminSignupResponseDto.md
|
|||||||
doc/AlbumApi.md
|
doc/AlbumApi.md
|
||||||
doc/AlbumResponseDto.md
|
doc/AlbumResponseDto.md
|
||||||
doc/AssetApi.md
|
doc/AssetApi.md
|
||||||
|
doc/AssetCountByTimeBucket.md
|
||||||
|
doc/AssetCountByTimeBucketResponseDto.md
|
||||||
doc/AssetFileUploadResponseDto.md
|
doc/AssetFileUploadResponseDto.md
|
||||||
doc/AssetResponseDto.md
|
doc/AssetResponseDto.md
|
||||||
doc/AssetTypeEnum.md
|
doc/AssetTypeEnum.md
|
||||||
@@ -27,6 +29,8 @@ doc/DeviceInfoApi.md
|
|||||||
doc/DeviceInfoResponseDto.md
|
doc/DeviceInfoResponseDto.md
|
||||||
doc/DeviceTypeEnum.md
|
doc/DeviceTypeEnum.md
|
||||||
doc/ExifResponseDto.md
|
doc/ExifResponseDto.md
|
||||||
|
doc/GetAssetByTimeBucketDto.md
|
||||||
|
doc/GetAssetCountByTimeBucketDto.md
|
||||||
doc/LoginCredentialDto.md
|
doc/LoginCredentialDto.md
|
||||||
doc/LoginResponseDto.md
|
doc/LoginResponseDto.md
|
||||||
doc/LogoutResponseDto.md
|
doc/LogoutResponseDto.md
|
||||||
@@ -39,6 +43,7 @@ doc/ServerVersionReponseDto.md
|
|||||||
doc/SignUpDto.md
|
doc/SignUpDto.md
|
||||||
doc/SmartInfoResponseDto.md
|
doc/SmartInfoResponseDto.md
|
||||||
doc/ThumbnailFormat.md
|
doc/ThumbnailFormat.md
|
||||||
|
doc/TimeGroupEnum.md
|
||||||
doc/UpdateAlbumDto.md
|
doc/UpdateAlbumDto.md
|
||||||
doc/UpdateDeviceInfoDto.md
|
doc/UpdateDeviceInfoDto.md
|
||||||
doc/UpdateUserDto.md
|
doc/UpdateUserDto.md
|
||||||
@@ -66,6 +71,8 @@ lib/model/add_assets_dto.dart
|
|||||||
lib/model/add_users_dto.dart
|
lib/model/add_users_dto.dart
|
||||||
lib/model/admin_signup_response_dto.dart
|
lib/model/admin_signup_response_dto.dart
|
||||||
lib/model/album_response_dto.dart
|
lib/model/album_response_dto.dart
|
||||||
|
lib/model/asset_count_by_time_bucket.dart
|
||||||
|
lib/model/asset_count_by_time_bucket_response_dto.dart
|
||||||
lib/model/asset_file_upload_response_dto.dart
|
lib/model/asset_file_upload_response_dto.dart
|
||||||
lib/model/asset_response_dto.dart
|
lib/model/asset_response_dto.dart
|
||||||
lib/model/asset_type_enum.dart
|
lib/model/asset_type_enum.dart
|
||||||
@@ -83,6 +90,8 @@ lib/model/delete_asset_status.dart
|
|||||||
lib/model/device_info_response_dto.dart
|
lib/model/device_info_response_dto.dart
|
||||||
lib/model/device_type_enum.dart
|
lib/model/device_type_enum.dart
|
||||||
lib/model/exif_response_dto.dart
|
lib/model/exif_response_dto.dart
|
||||||
|
lib/model/get_asset_by_time_bucket_dto.dart
|
||||||
|
lib/model/get_asset_count_by_time_bucket_dto.dart
|
||||||
lib/model/login_credential_dto.dart
|
lib/model/login_credential_dto.dart
|
||||||
lib/model/login_response_dto.dart
|
lib/model/login_response_dto.dart
|
||||||
lib/model/logout_response_dto.dart
|
lib/model/logout_response_dto.dart
|
||||||
@@ -94,6 +103,7 @@ lib/model/server_version_reponse_dto.dart
|
|||||||
lib/model/sign_up_dto.dart
|
lib/model/sign_up_dto.dart
|
||||||
lib/model/smart_info_response_dto.dart
|
lib/model/smart_info_response_dto.dart
|
||||||
lib/model/thumbnail_format.dart
|
lib/model/thumbnail_format.dart
|
||||||
|
lib/model/time_group_enum.dart
|
||||||
lib/model/update_album_dto.dart
|
lib/model/update_album_dto.dart
|
||||||
lib/model/update_device_info_dto.dart
|
lib/model/update_device_info_dto.dart
|
||||||
lib/model/update_user_dto.dart
|
lib/model/update_user_dto.dart
|
||||||
|
|||||||
@@ -79,10 +79,12 @@ Class | Method | HTTP request | Description
|
|||||||
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download |
|
*AssetApi* | [**downloadFile**](doc//AssetApi.md#downloadfile) | **GET** /asset/download |
|
||||||
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
|
*AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset |
|
||||||
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
*AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
||||||
*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/searchTerm |
|
*AssetApi* | [**getAssetByTimeBucket**](doc//AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket |
|
||||||
|
*AssetApi* | [**getAssetCountByTimeBucket**](doc//AssetApi.md#getassetcountbytimebucket) | **POST** /asset/count-by-time-bucket |
|
||||||
|
*AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
|
||||||
*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
*AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
||||||
*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/allLocation |
|
*AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
|
||||||
*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/allObjects |
|
*AssetApi* | [**getCuratedObjects**](doc//AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
|
||||||
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
*AssetApi* | [**getUserAssetsByDeviceId**](doc//AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
*AssetApi* | [**searchAsset**](doc//AssetApi.md#searchasset) | **POST** /asset/search |
|
||||||
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file |
|
*AssetApi* | [**serveFile**](doc//AssetApi.md#servefile) | **GET** /asset/file |
|
||||||
@@ -112,6 +114,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [AddUsersDto](doc//AddUsersDto.md)
|
- [AddUsersDto](doc//AddUsersDto.md)
|
||||||
- [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
|
- [AdminSignupResponseDto](doc//AdminSignupResponseDto.md)
|
||||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||||
|
- [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md)
|
||||||
|
- [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md)
|
||||||
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
|
- [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md)
|
||||||
- [AssetResponseDto](doc//AssetResponseDto.md)
|
- [AssetResponseDto](doc//AssetResponseDto.md)
|
||||||
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
- [AssetTypeEnum](doc//AssetTypeEnum.md)
|
||||||
@@ -129,6 +133,8 @@ Class | Method | HTTP request | Description
|
|||||||
- [DeviceInfoResponseDto](doc//DeviceInfoResponseDto.md)
|
- [DeviceInfoResponseDto](doc//DeviceInfoResponseDto.md)
|
||||||
- [DeviceTypeEnum](doc//DeviceTypeEnum.md)
|
- [DeviceTypeEnum](doc//DeviceTypeEnum.md)
|
||||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||||
|
- [GetAssetByTimeBucketDto](doc//GetAssetByTimeBucketDto.md)
|
||||||
|
- [GetAssetCountByTimeBucketDto](doc//GetAssetCountByTimeBucketDto.md)
|
||||||
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
- [LoginCredentialDto](doc//LoginCredentialDto.md)
|
||||||
- [LoginResponseDto](doc//LoginResponseDto.md)
|
- [LoginResponseDto](doc//LoginResponseDto.md)
|
||||||
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
- [LogoutResponseDto](doc//LogoutResponseDto.md)
|
||||||
@@ -140,6 +146,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [SignUpDto](doc//SignUpDto.md)
|
- [SignUpDto](doc//SignUpDto.md)
|
||||||
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
- [SmartInfoResponseDto](doc//SmartInfoResponseDto.md)
|
||||||
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
- [ThumbnailFormat](doc//ThumbnailFormat.md)
|
||||||
|
- [TimeGroupEnum](doc//TimeGroupEnum.md)
|
||||||
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
- [UpdateAlbumDto](doc//UpdateAlbumDto.md)
|
||||||
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
|
- [UpdateDeviceInfoDto](doc//UpdateDeviceInfoDto.md)
|
||||||
- [UpdateUserDto](doc//UpdateUserDto.md)
|
- [UpdateUserDto](doc//UpdateUserDto.md)
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ Name | Type | Description | Notes
|
|||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
# **getAllAlbums**
|
# **getAllAlbums**
|
||||||
> List<AlbumResponseDto> getAllAlbums(shared)
|
> List<AlbumResponseDto> getAllAlbums(shared, assetId)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -275,9 +275,10 @@ import 'package:openapi/api.dart';
|
|||||||
|
|
||||||
final api_instance = AlbumApi();
|
final api_instance = AlbumApi();
|
||||||
final shared = true; // bool |
|
final shared = true; // bool |
|
||||||
|
final assetId = assetId_example; // String | Only returns albums that contain the asset Ignores the shared parameter undefined: get all albums
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.getAllAlbums(shared);
|
final result = api_instance.getAllAlbums(shared, assetId);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling AlbumApi->getAllAlbums: $e\n');
|
print('Exception when calling AlbumApi->getAllAlbums: $e\n');
|
||||||
@@ -289,6 +290,7 @@ try {
|
|||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------- | ------------- | ------------- | -------------
|
------------- | ------------- | ------------- | -------------
|
||||||
**shared** | **bool**| | [optional]
|
**shared** | **bool**| | [optional]
|
||||||
|
**assetId** | **String**| Only returns albums that contain the asset Ignores the shared parameter undefined: get all albums | [optional]
|
||||||
|
|
||||||
### Return type
|
### Return type
|
||||||
|
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ Method | HTTP request | Description
|
|||||||
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download |
|
[**downloadFile**](AssetApi.md#downloadfile) | **GET** /asset/download |
|
||||||
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
|
[**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset |
|
||||||
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
[**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |
|
||||||
[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/searchTerm |
|
[**getAssetByTimeBucket**](AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket |
|
||||||
|
[**getAssetCountByTimeBucket**](AssetApi.md#getassetcountbytimebucket) | **POST** /asset/count-by-time-bucket |
|
||||||
|
[**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |
|
||||||
[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
[**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |
|
||||||
[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/allLocation |
|
[**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |
|
||||||
[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/allObjects |
|
[**getCuratedObjects**](AssetApi.md#getcuratedobjects) | **GET** /asset/curated-objects |
|
||||||
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
[**getUserAssetsByDeviceId**](AssetApi.md#getuserassetsbydeviceid) | **GET** /asset/{deviceId} |
|
||||||
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
[**searchAsset**](AssetApi.md#searchasset) | **POST** /asset/search |
|
||||||
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file |
|
[**serveFile**](AssetApi.md#servefile) | **GET** /asset/file |
|
||||||
@@ -267,6 +269,100 @@ Name | Type | Description | Notes
|
|||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
# **getAssetByTimeBucket**
|
||||||
|
> List<AssetResponseDto> getAssetByTimeBucket(getAssetByTimeBucketDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
// TODO Configure HTTP Bearer authorization: bearer
|
||||||
|
// Case 1. Use String Token
|
||||||
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
||||||
|
// Case 2. Use Function which generate token.
|
||||||
|
// String yourTokenGeneratorFunction() { ... }
|
||||||
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
|
final api_instance = AssetApi();
|
||||||
|
final getAssetByTimeBucketDto = GetAssetByTimeBucketDto(); // GetAssetByTimeBucketDto |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.getAssetByTimeBucket(getAssetByTimeBucketDto);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling AssetApi->getAssetByTimeBucket: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**getAssetByTimeBucketDto** | [**GetAssetByTimeBucketDto**](GetAssetByTimeBucketDto.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**List<AssetResponseDto>**](AssetResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
[bearer](../README.md#bearer)
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: application/json
|
||||||
|
- **Accept**: application/json
|
||||||
|
|
||||||
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
# **getAssetCountByTimeBucket**
|
||||||
|
> AssetCountByTimeBucketResponseDto getAssetCountByTimeBucket(getAssetCountByTimeBucketDto)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### Example
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
// TODO Configure HTTP Bearer authorization: bearer
|
||||||
|
// Case 1. Use String Token
|
||||||
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN');
|
||||||
|
// Case 2. Use Function which generate token.
|
||||||
|
// String yourTokenGeneratorFunction() { ... }
|
||||||
|
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
|
||||||
|
|
||||||
|
final api_instance = AssetApi();
|
||||||
|
final getAssetCountByTimeBucketDto = GetAssetCountByTimeBucketDto(); // GetAssetCountByTimeBucketDto |
|
||||||
|
|
||||||
|
try {
|
||||||
|
final result = api_instance.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto);
|
||||||
|
print(result);
|
||||||
|
} catch (e) {
|
||||||
|
print('Exception when calling AssetApi->getAssetCountByTimeBucket: $e\n');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Parameters
|
||||||
|
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------- | ------------- | ------------- | -------------
|
||||||
|
**getAssetCountByTimeBucketDto** | [**GetAssetCountByTimeBucketDto**](GetAssetCountByTimeBucketDto.md)| |
|
||||||
|
|
||||||
|
### Return type
|
||||||
|
|
||||||
|
[**AssetCountByTimeBucketResponseDto**](AssetCountByTimeBucketResponseDto.md)
|
||||||
|
|
||||||
|
### Authorization
|
||||||
|
|
||||||
|
[bearer](../README.md#bearer)
|
||||||
|
|
||||||
|
### HTTP request headers
|
||||||
|
|
||||||
|
- **Content-Type**: application/json
|
||||||
|
- **Accept**: application/json
|
||||||
|
|
||||||
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
# **getAssetSearchTerms**
|
# **getAssetSearchTerms**
|
||||||
> List<String> getAssetSearchTerms()
|
> List<String> getAssetSearchTerms()
|
||||||
|
|
||||||
|
|||||||
16
mobile/openapi/doc/AssetCountByTimeBucket.md
Normal file
16
mobile/openapi/doc/AssetCountByTimeBucket.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.AssetCountByTimeBucket
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeBucket** | **String** | |
|
||||||
|
**count** | **int** | |
|
||||||
|
|
||||||
|
[[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/AssetCountByTimeBucketResponseDto.md
Normal file
16
mobile/openapi/doc/AssetCountByTimeBucketResponseDto.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.AssetCountByTimeBucketResponseDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**totalCount** | **int** | |
|
||||||
|
**buckets** | [**List<AssetCountByTimeBucket>**](AssetCountByTimeBucket.md) | | [default to const []]
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
16
mobile/openapi/doc/AssetCountByTimeGroupDto.md
Normal file
16
mobile/openapi/doc/AssetCountByTimeGroupDto.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.AssetCountByTimeGroupDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeGroup** | **String** | |
|
||||||
|
**count** | **int** | |
|
||||||
|
|
||||||
|
[[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/AssetCountByTimeGroupResponseDto.md
Normal file
16
mobile/openapi/doc/AssetCountByTimeGroupResponseDto.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# openapi.model.AssetCountByTimeGroupResponseDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**count** | **int** | |
|
||||||
|
**buckets** | [**List<AssetCountByTimeBucketResponseDto>**](AssetCountByTimeBucketResponseDto.md) | | [default to const []]
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
15
mobile/openapi/doc/GetAssetByTimeBucketDto.md
Normal file
15
mobile/openapi/doc/GetAssetByTimeBucketDto.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.GetAssetByTimeBucketDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeBucket** | **List<String>** | | [default to const []]
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
15
mobile/openapi/doc/GetAssetCountByTimeBucketDto.md
Normal file
15
mobile/openapi/doc/GetAssetCountByTimeBucketDto.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.GetAssetCountByTimeBucketDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
15
mobile/openapi/doc/GetAssetCountByTimeGroupDto.md
Normal file
15
mobile/openapi/doc/GetAssetCountByTimeGroupDto.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# openapi.model.GetAssetCountByTimeGroupDto
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**timeGroup** | [**TimeGroupEnum**](TimeGroupEnum.md) | |
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
14
mobile/openapi/doc/TimeBucketEnum.md
Normal file
14
mobile/openapi/doc/TimeBucketEnum.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.TimeBucketEnum
|
||||||
|
|
||||||
|
## Load the model package
|
||||||
|
```dart
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
Name | Type | Description | Notes
|
||||||
|
------------ | ------------- | ------------- | -------------
|
||||||
|
|
||||||
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
||||||
14
mobile/openapi/doc/TimeGroupEnum.md
Normal file
14
mobile/openapi/doc/TimeGroupEnum.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# openapi.model.TimeGroupEnum
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
|
||||||
|
|
||||||
@@ -38,6 +38,8 @@ part 'model/add_assets_dto.dart';
|
|||||||
part 'model/add_users_dto.dart';
|
part 'model/add_users_dto.dart';
|
||||||
part 'model/admin_signup_response_dto.dart';
|
part 'model/admin_signup_response_dto.dart';
|
||||||
part 'model/album_response_dto.dart';
|
part 'model/album_response_dto.dart';
|
||||||
|
part 'model/asset_count_by_time_bucket.dart';
|
||||||
|
part 'model/asset_count_by_time_bucket_response_dto.dart';
|
||||||
part 'model/asset_file_upload_response_dto.dart';
|
part 'model/asset_file_upload_response_dto.dart';
|
||||||
part 'model/asset_response_dto.dart';
|
part 'model/asset_response_dto.dart';
|
||||||
part 'model/asset_type_enum.dart';
|
part 'model/asset_type_enum.dart';
|
||||||
@@ -55,6 +57,8 @@ part 'model/delete_asset_status.dart';
|
|||||||
part 'model/device_info_response_dto.dart';
|
part 'model/device_info_response_dto.dart';
|
||||||
part 'model/device_type_enum.dart';
|
part 'model/device_type_enum.dart';
|
||||||
part 'model/exif_response_dto.dart';
|
part 'model/exif_response_dto.dart';
|
||||||
|
part 'model/get_asset_by_time_bucket_dto.dart';
|
||||||
|
part 'model/get_asset_count_by_time_bucket_dto.dart';
|
||||||
part 'model/login_credential_dto.dart';
|
part 'model/login_credential_dto.dart';
|
||||||
part 'model/login_response_dto.dart';
|
part 'model/login_response_dto.dart';
|
||||||
part 'model/logout_response_dto.dart';
|
part 'model/logout_response_dto.dart';
|
||||||
@@ -66,6 +70,7 @@ part 'model/server_version_reponse_dto.dart';
|
|||||||
part 'model/sign_up_dto.dart';
|
part 'model/sign_up_dto.dart';
|
||||||
part 'model/smart_info_response_dto.dart';
|
part 'model/smart_info_response_dto.dart';
|
||||||
part 'model/thumbnail_format.dart';
|
part 'model/thumbnail_format.dart';
|
||||||
|
part 'model/time_group_enum.dart';
|
||||||
part 'model/update_album_dto.dart';
|
part 'model/update_album_dto.dart';
|
||||||
part 'model/update_device_info_dto.dart';
|
part 'model/update_device_info_dto.dart';
|
||||||
part 'model/update_user_dto.dart';
|
part 'model/update_user_dto.dart';
|
||||||
|
|||||||
@@ -259,7 +259,10 @@ class AlbumApi {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [bool] shared:
|
/// * [bool] shared:
|
||||||
Future<Response> getAllAlbumsWithHttpInfo({ bool? shared, }) async {
|
///
|
||||||
|
/// * [String] assetId:
|
||||||
|
/// Only returns albums that contain the asset Ignores the shared parameter undefined: get all albums
|
||||||
|
Future<Response> getAllAlbumsWithHttpInfo({ bool? shared, String? assetId, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/album';
|
final path = r'/album';
|
||||||
|
|
||||||
@@ -273,6 +276,9 @@ class AlbumApi {
|
|||||||
if (shared != null) {
|
if (shared != null) {
|
||||||
queryParams.addAll(_queryParams('', 'shared', shared));
|
queryParams.addAll(_queryParams('', 'shared', shared));
|
||||||
}
|
}
|
||||||
|
if (assetId != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'assetId', assetId));
|
||||||
|
}
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
@@ -291,8 +297,11 @@ class AlbumApi {
|
|||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [bool] shared:
|
/// * [bool] shared:
|
||||||
Future<List<AlbumResponseDto>?> getAllAlbums({ bool? shared, }) async {
|
///
|
||||||
final response = await getAllAlbumsWithHttpInfo( shared: shared, );
|
/// * [String] assetId:
|
||||||
|
/// Only returns albums that contain the asset Ignores the shared parameter undefined: get all albums
|
||||||
|
Future<List<AlbumResponseDto>?> getAllAlbums({ bool? shared, String? assetId, }) async {
|
||||||
|
final response = await getAllAlbumsWithHttpInfo( shared: shared, assetId: assetId, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -298,10 +298,107 @@ class AssetApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /asset/searchTerm' operation and returns the [Response].
|
/// Performs an HTTP 'POST /asset/time-bucket' operation and returns the [Response].
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [GetAssetByTimeBucketDto] getAssetByTimeBucketDto (required):
|
||||||
|
Future<Response> getAssetByTimeBucketWithHttpInfo(GetAssetByTimeBucketDto getAssetByTimeBucketDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'/asset/time-bucket';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = getAssetByTimeBucketDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'POST',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [GetAssetByTimeBucketDto] getAssetByTimeBucketDto (required):
|
||||||
|
Future<List<AssetResponseDto>?> getAssetByTimeBucket(GetAssetByTimeBucketDto getAssetByTimeBucketDto,) async {
|
||||||
|
final response = await getAssetByTimeBucketWithHttpInfo(getAssetByTimeBucketDto,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||||
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
|
// FormatException when trying to decode an empty string.
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
final responseBody = await _decodeBodyBytes(response);
|
||||||
|
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
|
||||||
|
.cast<AssetResponseDto>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'POST /asset/count-by-time-bucket' operation and returns the [Response].
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [GetAssetCountByTimeBucketDto] getAssetCountByTimeBucketDto (required):
|
||||||
|
Future<Response> getAssetCountByTimeBucketWithHttpInfo(GetAssetCountByTimeBucketDto getAssetCountByTimeBucketDto,) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'/asset/count-by-time-bucket';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody = getAssetCountByTimeBucketDto;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>['application/json'];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'POST',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
/// * [GetAssetCountByTimeBucketDto] getAssetCountByTimeBucketDto (required):
|
||||||
|
Future<AssetCountByTimeBucketResponseDto?> getAssetCountByTimeBucket(GetAssetCountByTimeBucketDto getAssetCountByTimeBucketDto,) async {
|
||||||
|
final response = await getAssetCountByTimeBucketWithHttpInfo(getAssetCountByTimeBucketDto,);
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||||
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
|
// FormatException when trying to decode an empty string.
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'AssetCountByTimeBucketResponseDto',) as AssetCountByTimeBucketResponseDto;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an HTTP 'GET /asset/search-terms' operation and returns the [Response].
|
||||||
Future<Response> getAssetSearchTermsWithHttpInfo() async {
|
Future<Response> getAssetSearchTermsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/searchTerm';
|
final path = r'/asset/search-terms';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
@@ -398,10 +495,10 @@ class AssetApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /asset/allLocation' operation and returns the [Response].
|
/// Performs an HTTP 'GET /asset/curated-locations' operation and returns the [Response].
|
||||||
Future<Response> getCuratedLocationsWithHttpInfo() async {
|
Future<Response> getCuratedLocationsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/allLocation';
|
final path = r'/asset/curated-locations';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
@@ -442,10 +539,10 @@ class AssetApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs an HTTP 'GET /asset/allObjects' operation and returns the [Response].
|
/// Performs an HTTP 'GET /asset/curated-objects' operation and returns the [Response].
|
||||||
Future<Response> getCuratedObjectsWithHttpInfo() async {
|
Future<Response> getCuratedObjectsWithHttpInfo() async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/allObjects';
|
final path = r'/asset/curated-objects';
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
// ignore: prefer_final_locals
|
||||||
Object? postBody;
|
Object? postBody;
|
||||||
|
|||||||
@@ -200,6 +200,10 @@ class ApiClient {
|
|||||||
return AdminSignupResponseDto.fromJson(value);
|
return AdminSignupResponseDto.fromJson(value);
|
||||||
case 'AlbumResponseDto':
|
case 'AlbumResponseDto':
|
||||||
return AlbumResponseDto.fromJson(value);
|
return AlbumResponseDto.fromJson(value);
|
||||||
|
case 'AssetCountByTimeBucket':
|
||||||
|
return AssetCountByTimeBucket.fromJson(value);
|
||||||
|
case 'AssetCountByTimeBucketResponseDto':
|
||||||
|
return AssetCountByTimeBucketResponseDto.fromJson(value);
|
||||||
case 'AssetFileUploadResponseDto':
|
case 'AssetFileUploadResponseDto':
|
||||||
return AssetFileUploadResponseDto.fromJson(value);
|
return AssetFileUploadResponseDto.fromJson(value);
|
||||||
case 'AssetResponseDto':
|
case 'AssetResponseDto':
|
||||||
@@ -234,6 +238,10 @@ class ApiClient {
|
|||||||
return DeviceTypeEnumTypeTransformer().decode(value);
|
return DeviceTypeEnumTypeTransformer().decode(value);
|
||||||
case 'ExifResponseDto':
|
case 'ExifResponseDto':
|
||||||
return ExifResponseDto.fromJson(value);
|
return ExifResponseDto.fromJson(value);
|
||||||
|
case 'GetAssetByTimeBucketDto':
|
||||||
|
return GetAssetByTimeBucketDto.fromJson(value);
|
||||||
|
case 'GetAssetCountByTimeBucketDto':
|
||||||
|
return GetAssetCountByTimeBucketDto.fromJson(value);
|
||||||
case 'LoginCredentialDto':
|
case 'LoginCredentialDto':
|
||||||
return LoginCredentialDto.fromJson(value);
|
return LoginCredentialDto.fromJson(value);
|
||||||
case 'LoginResponseDto':
|
case 'LoginResponseDto':
|
||||||
@@ -256,6 +264,8 @@ class ApiClient {
|
|||||||
return SmartInfoResponseDto.fromJson(value);
|
return SmartInfoResponseDto.fromJson(value);
|
||||||
case 'ThumbnailFormat':
|
case 'ThumbnailFormat':
|
||||||
return ThumbnailFormatTypeTransformer().decode(value);
|
return ThumbnailFormatTypeTransformer().decode(value);
|
||||||
|
case 'TimeGroupEnum':
|
||||||
|
return TimeGroupEnumTypeTransformer().decode(value);
|
||||||
case 'UpdateAlbumDto':
|
case 'UpdateAlbumDto':
|
||||||
return UpdateAlbumDto.fromJson(value);
|
return UpdateAlbumDto.fromJson(value);
|
||||||
case 'UpdateDeviceInfoDto':
|
case 'UpdateDeviceInfoDto':
|
||||||
|
|||||||
@@ -67,6 +67,9 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is ThumbnailFormat) {
|
if (value is ThumbnailFormat) {
|
||||||
return ThumbnailFormatTypeTransformer().encode(value).toString();
|
return ThumbnailFormatTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
if (value is TimeGroupEnum) {
|
||||||
|
return TimeGroupEnumTypeTransformer().encode(value).toString();
|
||||||
|
}
|
||||||
return value.toString();
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
119
mobile/openapi/lib/model/asset_count_by_time_bucket.dart
Normal file
119
mobile/openapi/lib/model/asset_count_by_time_bucket.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class AssetCountByTimeBucket {
|
||||||
|
/// Returns a new [AssetCountByTimeBucket] instance.
|
||||||
|
AssetCountByTimeBucket({
|
||||||
|
required this.timeBucket,
|
||||||
|
required this.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
String timeBucket;
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeBucket &&
|
||||||
|
other.timeBucket == timeBucket &&
|
||||||
|
other.count == count;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeBucket.hashCode) +
|
||||||
|
(count.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetCountByTimeBucket[timeBucket=$timeBucket, count=$count]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeBucket'] = timeBucket;
|
||||||
|
_json[r'count'] = count;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetCountByTimeBucket] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetCountByTimeBucket? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "AssetCountByTimeBucket[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AssetCountByTimeBucket[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AssetCountByTimeBucket(
|
||||||
|
timeBucket: mapValueOfType<String>(json, r'timeBucket')!,
|
||||||
|
count: mapValueOfType<int>(json, r'count')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetCountByTimeBucket>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetCountByTimeBucket>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetCountByTimeBucket.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetCountByTimeBucket> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetCountByTimeBucket>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeBucket.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetCountByTimeBucket-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetCountByTimeBucket>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetCountByTimeBucket>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeBucket.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'timeBucket',
|
||||||
|
'count',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class AssetCountByTimeBucketResponseDto {
|
||||||
|
/// Returns a new [AssetCountByTimeBucketResponseDto] instance.
|
||||||
|
AssetCountByTimeBucketResponseDto({
|
||||||
|
required this.totalCount,
|
||||||
|
this.buckets = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
int totalCount;
|
||||||
|
|
||||||
|
List<AssetCountByTimeBucket> buckets;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeBucketResponseDto &&
|
||||||
|
other.totalCount == totalCount &&
|
||||||
|
other.buckets == buckets;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(totalCount.hashCode) +
|
||||||
|
(buckets.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetCountByTimeBucketResponseDto[totalCount=$totalCount, buckets=$buckets]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'totalCount'] = totalCount;
|
||||||
|
_json[r'buckets'] = buckets;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetCountByTimeBucketResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetCountByTimeBucketResponseDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "AssetCountByTimeBucketResponseDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AssetCountByTimeBucketResponseDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AssetCountByTimeBucketResponseDto(
|
||||||
|
totalCount: mapValueOfType<int>(json, r'totalCount')!,
|
||||||
|
buckets: AssetCountByTimeBucket.listFromJson(json[r'buckets'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetCountByTimeBucketResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetCountByTimeBucketResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetCountByTimeBucketResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetCountByTimeBucketResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetCountByTimeBucketResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeBucketResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetCountByTimeBucketResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetCountByTimeBucketResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetCountByTimeBucketResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeBucketResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'totalCount',
|
||||||
|
'buckets',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
119
mobile/openapi/lib/model/asset_count_by_time_group_dto.dart
Normal file
119
mobile/openapi/lib/model/asset_count_by_time_group_dto.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class AssetCountByTimeGroupDto {
|
||||||
|
/// Returns a new [AssetCountByTimeGroupDto] instance.
|
||||||
|
AssetCountByTimeGroupDto({
|
||||||
|
required this.timeGroup,
|
||||||
|
required this.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
String timeGroup;
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeGroupDto &&
|
||||||
|
other.timeGroup == timeGroup &&
|
||||||
|
other.count == count;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeGroup.hashCode) +
|
||||||
|
(count.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetCountByTimeGroupDto[timeGroup=$timeGroup, count=$count]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeGroup'] = timeGroup;
|
||||||
|
_json[r'count'] = count;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetCountByTimeGroupDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetCountByTimeGroupDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "AssetCountByTimeGroupDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AssetCountByTimeGroupDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AssetCountByTimeGroupDto(
|
||||||
|
timeGroup: mapValueOfType<String>(json, r'timeGroup')!,
|
||||||
|
count: mapValueOfType<int>(json, r'count')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetCountByTimeGroupDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetCountByTimeGroupDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetCountByTimeGroupDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetCountByTimeGroupDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetCountByTimeGroupDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetCountByTimeGroupDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetCountByTimeGroupDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetCountByTimeGroupDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'timeGroup',
|
||||||
|
'count',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class AssetCountByTimeGroupResponseDto {
|
||||||
|
/// Returns a new [AssetCountByTimeGroupResponseDto] instance.
|
||||||
|
AssetCountByTimeGroupResponseDto({
|
||||||
|
required this.count,
|
||||||
|
this.buckets = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
List<AssetCountByTimeBucketResponseDto> buckets;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is AssetCountByTimeGroupResponseDto &&
|
||||||
|
other.count == count &&
|
||||||
|
other.buckets == buckets;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(count.hashCode) +
|
||||||
|
(buckets.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'AssetCountByTimeGroupResponseDto[count=$count, buckets=$buckets]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'count'] = count;
|
||||||
|
_json[r'buckets'] = buckets;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [AssetCountByTimeGroupResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static AssetCountByTimeGroupResponseDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "AssetCountByTimeGroupResponseDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "AssetCountByTimeGroupResponseDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return AssetCountByTimeGroupResponseDto(
|
||||||
|
count: mapValueOfType<int>(json, r'count')!,
|
||||||
|
buckets: AssetCountByTimeBucketResponseDto.listFromJson(json[r'buckets'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<AssetCountByTimeGroupResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <AssetCountByTimeGroupResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = AssetCountByTimeGroupResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, AssetCountByTimeGroupResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, AssetCountByTimeGroupResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of AssetCountByTimeGroupResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<AssetCountByTimeGroupResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<AssetCountByTimeGroupResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = AssetCountByTimeGroupResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'count',
|
||||||
|
'buckets',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
113
mobile/openapi/lib/model/get_asset_by_time_bucket_dto.dart
Normal file
113
mobile/openapi/lib/model/get_asset_by_time_bucket_dto.dart
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class GetAssetByTimeBucketDto {
|
||||||
|
/// Returns a new [GetAssetByTimeBucketDto] instance.
|
||||||
|
GetAssetByTimeBucketDto({
|
||||||
|
this.timeBucket = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<String> timeBucket;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is GetAssetByTimeBucketDto &&
|
||||||
|
other.timeBucket == timeBucket;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeBucket.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GetAssetByTimeBucketDto[timeBucket=$timeBucket]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeBucket'] = timeBucket;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [GetAssetByTimeBucketDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static GetAssetByTimeBucketDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "GetAssetByTimeBucketDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "GetAssetByTimeBucketDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return GetAssetByTimeBucketDto(
|
||||||
|
timeBucket: json[r'timeBucket'] is List
|
||||||
|
? (json[r'timeBucket'] as List).cast<String>()
|
||||||
|
: const [],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<GetAssetByTimeBucketDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <GetAssetByTimeBucketDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = GetAssetByTimeBucketDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, GetAssetByTimeBucketDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, GetAssetByTimeBucketDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetByTimeBucketDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of GetAssetByTimeBucketDto-objects as value to a dart map
|
||||||
|
static Map<String, List<GetAssetByTimeBucketDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<GetAssetByTimeBucketDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetByTimeBucketDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'timeBucket',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
111
mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart
Normal file
111
mobile/openapi/lib/model/get_asset_count_by_time_bucket_dto.dart
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class GetAssetCountByTimeBucketDto {
|
||||||
|
/// Returns a new [GetAssetCountByTimeBucketDto] instance.
|
||||||
|
GetAssetCountByTimeBucketDto({
|
||||||
|
required this.timeGroup,
|
||||||
|
});
|
||||||
|
|
||||||
|
TimeGroupEnum timeGroup;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeBucketDto &&
|
||||||
|
other.timeGroup == timeGroup;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeGroup.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GetAssetCountByTimeBucketDto[timeGroup=$timeGroup]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeGroup'] = timeGroup;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [GetAssetCountByTimeBucketDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static GetAssetCountByTimeBucketDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "GetAssetCountByTimeBucketDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "GetAssetCountByTimeBucketDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return GetAssetCountByTimeBucketDto(
|
||||||
|
timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<GetAssetCountByTimeBucketDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <GetAssetCountByTimeBucketDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = GetAssetCountByTimeBucketDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, GetAssetCountByTimeBucketDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, GetAssetCountByTimeBucketDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetCountByTimeBucketDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of GetAssetCountByTimeBucketDto-objects as value to a dart map
|
||||||
|
static Map<String, List<GetAssetCountByTimeBucketDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<GetAssetCountByTimeBucketDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetCountByTimeBucketDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'timeGroup',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
111
mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart
Normal file
111
mobile/openapi/lib/model/get_asset_count_by_time_group_dto.dart
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
class GetAssetCountByTimeGroupDto {
|
||||||
|
/// Returns a new [GetAssetCountByTimeGroupDto] instance.
|
||||||
|
GetAssetCountByTimeGroupDto({
|
||||||
|
required this.timeGroup,
|
||||||
|
});
|
||||||
|
|
||||||
|
TimeGroupEnum timeGroup;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is GetAssetCountByTimeGroupDto &&
|
||||||
|
other.timeGroup == timeGroup;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(timeGroup.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'GetAssetCountByTimeGroupDto[timeGroup=$timeGroup]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final _json = <String, dynamic>{};
|
||||||
|
_json[r'timeGroup'] = timeGroup;
|
||||||
|
return _json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [GetAssetCountByTimeGroupDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static GetAssetCountByTimeGroupDto? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "GetAssetCountByTimeGroupDto[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "GetAssetCountByTimeGroupDto[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return GetAssetCountByTimeGroupDto(
|
||||||
|
timeGroup: TimeGroupEnum.fromJson(json[r'timeGroup'])!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<GetAssetCountByTimeGroupDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <GetAssetCountByTimeGroupDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = GetAssetCountByTimeGroupDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, GetAssetCountByTimeGroupDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, GetAssetCountByTimeGroupDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetCountByTimeGroupDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of GetAssetCountByTimeGroupDto-objects as value to a dart map
|
||||||
|
static Map<String, List<GetAssetCountByTimeGroupDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<GetAssetCountByTimeGroupDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = GetAssetCountByTimeGroupDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'timeGroup',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
85
mobile/openapi/lib/model/time_bucket_enum.dart
Normal file
85
mobile/openapi/lib/model/time_bucket_enum.dart
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
|
class TimeBucketEnum {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const TimeBucketEnum._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const day = TimeBucketEnum._(r'day');
|
||||||
|
static const month = TimeBucketEnum._(r'month');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][TimeBucketEnum].
|
||||||
|
static const values = <TimeBucketEnum>[
|
||||||
|
day,
|
||||||
|
month,
|
||||||
|
];
|
||||||
|
|
||||||
|
static TimeBucketEnum? fromJson(dynamic value) => TimeBucketEnumTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<TimeBucketEnum>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimeBucketEnum>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimeBucketEnum.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [TimeBucketEnum] to String,
|
||||||
|
/// and [decode] dynamic data back to [TimeBucketEnum].
|
||||||
|
class TimeBucketEnumTypeTransformer {
|
||||||
|
factory TimeBucketEnumTypeTransformer() => _instance ??= const TimeBucketEnumTypeTransformer._();
|
||||||
|
|
||||||
|
const TimeBucketEnumTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(TimeBucketEnum data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a TimeBucketEnum.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
TimeBucketEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'day': return TimeBucketEnum.day;
|
||||||
|
case r'month': return TimeBucketEnum.month;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [TimeBucketEnumTypeTransformer] instance.
|
||||||
|
static TimeBucketEnumTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
85
mobile/openapi/lib/model/time_group_enum.dart
Normal file
85
mobile/openapi/lib/model/time_group_enum.dart
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
|
class TimeGroupEnum {
|
||||||
|
/// Instantiate a new enum with the provided [value].
|
||||||
|
const TimeGroupEnum._(this.value);
|
||||||
|
|
||||||
|
/// The underlying value of this enum member.
|
||||||
|
final String value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => value;
|
||||||
|
|
||||||
|
String toJson() => value;
|
||||||
|
|
||||||
|
static const day = TimeGroupEnum._(r'day');
|
||||||
|
static const month = TimeGroupEnum._(r'month');
|
||||||
|
|
||||||
|
/// List of all possible values in this [enum][TimeGroupEnum].
|
||||||
|
static const values = <TimeGroupEnum>[
|
||||||
|
day,
|
||||||
|
month,
|
||||||
|
];
|
||||||
|
|
||||||
|
static TimeGroupEnum? fromJson(dynamic value) => TimeGroupEnumTypeTransformer().decode(value);
|
||||||
|
|
||||||
|
static List<TimeGroupEnum>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimeGroupEnum>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimeGroupEnum.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Transformation class that can [encode] an instance of [TimeGroupEnum] to String,
|
||||||
|
/// and [decode] dynamic data back to [TimeGroupEnum].
|
||||||
|
class TimeGroupEnumTypeTransformer {
|
||||||
|
factory TimeGroupEnumTypeTransformer() => _instance ??= const TimeGroupEnumTypeTransformer._();
|
||||||
|
|
||||||
|
const TimeGroupEnumTypeTransformer._();
|
||||||
|
|
||||||
|
String encode(TimeGroupEnum data) => data.value;
|
||||||
|
|
||||||
|
/// Decodes a [dynamic value][data] to a TimeGroupEnum.
|
||||||
|
///
|
||||||
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
|
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||||
|
///
|
||||||
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
|
/// and users are still using an old app with the old code.
|
||||||
|
TimeGroupEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||||
|
if (data != null) {
|
||||||
|
switch (data.toString()) {
|
||||||
|
case r'day': return TimeGroupEnum.day;
|
||||||
|
case r'month': return TimeGroupEnum.month;
|
||||||
|
default:
|
||||||
|
if (!allowNull) {
|
||||||
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Singleton [TimeGroupEnumTypeTransformer] instance.
|
||||||
|
static TimeGroupEnumTypeTransformer? _instance;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for AssetCountByTimeBucketResponseDto
|
||||||
|
void main() {
|
||||||
|
// final instance = AssetCountByTimeBucketResponseDto();
|
||||||
|
|
||||||
|
group('test AssetCountByTimeBucketResponseDto', () {
|
||||||
|
// String timeGroup
|
||||||
|
test('to test the property `timeGroup`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// int count
|
||||||
|
test('to test the property `count`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
32
mobile/openapi/test/asset_count_by_time_bucket_test.dart
Normal file
32
mobile/openapi/test/asset_count_by_time_bucket_test.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for AssetCountByTimeBucket
|
||||||
|
void main() {
|
||||||
|
// final instance = AssetCountByTimeBucket();
|
||||||
|
|
||||||
|
group('test AssetCountByTimeBucket', () {
|
||||||
|
// String timeBucket
|
||||||
|
test('to test the property `timeBucket`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// int count
|
||||||
|
test('to test the property `count`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
32
mobile/openapi/test/asset_count_by_time_group_dto_test.dart
Normal file
32
mobile/openapi/test/asset_count_by_time_group_dto_test.dart
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for AssetCountByTimeGroupDto
|
||||||
|
void main() {
|
||||||
|
// final instance = AssetCountByTimeGroupDto();
|
||||||
|
|
||||||
|
group('test AssetCountByTimeGroupDto', () {
|
||||||
|
// String timeGroup
|
||||||
|
test('to test the property `timeGroup`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// int count
|
||||||
|
test('to test the property `count`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for AssetCountByTimeGroupResponseDto
|
||||||
|
void main() {
|
||||||
|
// final instance = AssetCountByTimeGroupResponseDto();
|
||||||
|
|
||||||
|
group('test AssetCountByTimeGroupResponseDto', () {
|
||||||
|
// int totalAssets
|
||||||
|
test('to test the property `totalAssets`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// List<AssetCountByTimeGroupDto> groups (default value: const [])
|
||||||
|
test('to test the property `groups`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
27
mobile/openapi/test/get_asset_by_time_bucket_dto_test.dart
Normal file
27
mobile/openapi/test/get_asset_by_time_bucket_dto_test.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for GetAssetByTimeBucketDto
|
||||||
|
void main() {
|
||||||
|
// final instance = GetAssetByTimeBucketDto();
|
||||||
|
|
||||||
|
group('test GetAssetByTimeBucketDto', () {
|
||||||
|
// List<String> timeBucket (default value: const [])
|
||||||
|
test('to test the property `timeBucket`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for GetAssetCountByTimeBucketDto
|
||||||
|
void main() {
|
||||||
|
// final instance = GetAssetCountByTimeBucketDto();
|
||||||
|
|
||||||
|
group('test GetAssetCountByTimeBucketDto', () {
|
||||||
|
// TimeGroupEnum timeGroup
|
||||||
|
test('to test the property `timeGroup`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for GetAssetCountByTimeGroupDto
|
||||||
|
void main() {
|
||||||
|
// final instance = GetAssetCountByTimeGroupDto();
|
||||||
|
|
||||||
|
group('test GetAssetCountByTimeGroupDto', () {
|
||||||
|
// String timeGroup (default value: 'month')
|
||||||
|
test('to test the property `timeGroup`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
21
mobile/openapi/test/time_bucket_enum_test.dart
Normal file
21
mobile/openapi/test/time_bucket_enum_test.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for TimeBucketEnum
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test TimeBucketEnum', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
21
mobile/openapi/test/time_group_enum_test.dart
Normal file
21
mobile/openapi/test/time_group_enum_test.dart
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||||
|
//
|
||||||
|
// @dart=2.12
|
||||||
|
|
||||||
|
// ignore_for_file: unused_element, unused_import
|
||||||
|
// ignore_for_file: always_put_required_named_parameters_first
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars
|
||||||
|
|
||||||
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
// tests for TimeGroupEnum
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
group('test TimeGroupEnum', () {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ packages:
|
|||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.2"
|
version: "2.9.0"
|
||||||
auto_route:
|
auto_route:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -161,7 +161,7 @@ packages:
|
|||||||
name: characters
|
name: characters
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
charcode:
|
charcode:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -189,7 +189,7 @@ packages:
|
|||||||
name: clock
|
name: clock
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
code_builder:
|
code_builder:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -287,7 +287,7 @@ packages:
|
|||||||
name: fake_async
|
name: fake_async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.1"
|
||||||
ffi:
|
ffi:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -601,21 +601,21 @@ packages:
|
|||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.11"
|
version: "0.12.12"
|
||||||
material_color_utilities:
|
material_color_utilities:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.4"
|
version: "0.1.5"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.8.0"
|
||||||
mgrs_dart:
|
mgrs_dart:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -706,7 +706,7 @@ packages:
|
|||||||
name: path
|
name: path
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.1"
|
version: "1.8.2"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1026,7 +1026,7 @@ packages:
|
|||||||
name: source_span
|
name: source_span
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.9.0"
|
||||||
sprintf:
|
sprintf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1082,7 +1082,7 @@ packages:
|
|||||||
name: string_scanner
|
name: string_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.1"
|
||||||
synchronized:
|
synchronized:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1096,14 +1096,14 @@ packages:
|
|||||||
name: term_glyph
|
name: term_glyph
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.1"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.9"
|
version: "0.4.12"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 1.25.0+35
|
version: 1.27.0+37
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ WORKDIR /usr/src/app
|
|||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
|
|
||||||
RUN apk add --update-cache build-base python3 libheif vips-dev
|
RUN apk add --update-cache build-base python3 libheif vips-dev ffmpeg
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -22,7 +22,7 @@ COPY package.json package-lock.json ./
|
|||||||
COPY start-server.sh start-microservices.sh ./
|
COPY start-server.sh start-microservices.sh ./
|
||||||
|
|
||||||
RUN mkdir -p /usr/src/app/dist \
|
RUN mkdir -p /usr/src/app/dist \
|
||||||
&& apk add --no-cache libheif vips ffmpeg
|
&& apk add --no-cache libheif vips ffmpeg
|
||||||
|
|
||||||
COPY --from=builder /usr/src/app/node_modules ./node_modules
|
COPY --from=builder /usr/src/app/node_modules ./node_modules
|
||||||
COPY --from=builder /usr/src/app/dist ./dist
|
COPY --from=builder /usr/src/app/dist ./dist
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export interface IAlbumRepository {
|
|||||||
removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise<AlbumEntity>;
|
removeAssets(album: AlbumEntity, removeAssets: RemoveAssetsDto): Promise<AlbumEntity>;
|
||||||
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity>;
|
addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity>;
|
||||||
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>;
|
||||||
|
getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ALBUM_REPOSITORY = 'ALBUM_REPOSITORY';
|
export const ALBUM_REPOSITORY = 'ALBUM_REPOSITORY';
|
||||||
@@ -149,6 +150,31 @@ export class AlbumRepository implements IAlbumRepository {
|
|||||||
return albums;
|
return albums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]> {
|
||||||
|
let query = this.albumRepository.createQueryBuilder('album');
|
||||||
|
|
||||||
|
const albums = await query
|
||||||
|
.where('album.ownerId = :ownerId', { ownerId: userId })
|
||||||
|
.andWhere((qb) => {
|
||||||
|
// shared with userId
|
||||||
|
const subQuery = qb
|
||||||
|
.subQuery()
|
||||||
|
.select('assetAlbum.albumId')
|
||||||
|
.from(AssetAlbumEntity, 'assetAlbum')
|
||||||
|
.where('assetAlbum.assetId = :assetId', {assetId: assetId})
|
||||||
|
.getQuery();
|
||||||
|
return `album.id IN ${subQuery}`;
|
||||||
|
})
|
||||||
|
.leftJoinAndSelect('album.assets', 'assets')
|
||||||
|
.leftJoinAndSelect('assets.assetInfo', 'assetInfo')
|
||||||
|
.leftJoinAndSelect('album.sharedUsers', 'sharedUser')
|
||||||
|
.leftJoinAndSelect('sharedUser.userInfo', 'userInfo')
|
||||||
|
.orderBy('"assetInfo"."createdAt"::timestamptz', 'ASC')
|
||||||
|
.getMany();
|
||||||
|
|
||||||
|
return albums;
|
||||||
|
}
|
||||||
|
|
||||||
async get(albumId: string): Promise<AlbumEntity | undefined> {
|
async get(albumId: string): Promise<AlbumEntity | undefined> {
|
||||||
let query = this.albumRepository.createQueryBuilder('album');
|
let query = this.albumRepository.createQueryBuilder('album');
|
||||||
|
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ describe('Album service', () => {
|
|||||||
removeAssets: jest.fn(),
|
removeAssets: jest.fn(),
|
||||||
removeUser: jest.fn(),
|
removeUser: jest.fn(),
|
||||||
updateAlbum: jest.fn(),
|
updateAlbum: jest.fn(),
|
||||||
|
getListByAssetId: jest.fn()
|
||||||
};
|
};
|
||||||
sut = new AlbumService(albumRepositoryMock);
|
sut = new AlbumService(albumRepositoryMock);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -48,8 +48,11 @@ export class AlbumService {
|
|||||||
* @returns All Shared Album And Its Members
|
* @returns All Shared Album And Its Members
|
||||||
*/
|
*/
|
||||||
async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
|
||||||
|
if (typeof getAlbumsDto.assetId === 'string') {
|
||||||
|
const albums = await this._albumRepository.getListByAssetId(authUser.id, getAlbumsDto.assetId);
|
||||||
|
return albums.map(mapAlbumExcludeAssetInfo);
|
||||||
|
}
|
||||||
const albums = await this._albumRepository.getList(authUser.id, getAlbumsDto);
|
const albums = await this._albumRepository.getList(authUser.id, getAlbumsDto);
|
||||||
|
|
||||||
return albums.map((album) => mapAlbumExcludeAssetInfo(album));
|
return albums.map((album) => mapAlbumExcludeAssetInfo(album));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,11 @@ export class GetAlbumsDto {
|
|||||||
* undefined: shared and owned albums
|
* undefined: shared and owned albums
|
||||||
*/
|
*/
|
||||||
shared?: boolean;
|
shared?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only returns albums that contain the asset
|
||||||
|
* Ignores the shared parameter
|
||||||
|
* undefined: get all albums
|
||||||
|
*/
|
||||||
|
assetId?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
228
server/apps/immich/src/api-v1/asset/asset-repository.ts
Normal file
228
server/apps/immich/src/api-v1/asset/asset-repository.ts
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
import { SearchPropertiesDto } from './dto/search-properties.dto';
|
||||||
|
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||||
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm/repository/Repository';
|
||||||
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
|
import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
|
import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
|
||||||
|
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||||
|
|
||||||
|
export interface IAssetRepository {
|
||||||
|
create(
|
||||||
|
createAssetDto: CreateAssetDto,
|
||||||
|
ownerId: string,
|
||||||
|
originalPath: string,
|
||||||
|
mimeType: string,
|
||||||
|
checksum?: Buffer,
|
||||||
|
): Promise<AssetEntity>;
|
||||||
|
getAllByUserId(userId: string): Promise<AssetEntity[]>;
|
||||||
|
getAllByDeviceId(userId: string, deviceId: string): Promise<string[]>;
|
||||||
|
getById(assetId: string): Promise<AssetEntity>;
|
||||||
|
getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]>;
|
||||||
|
getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>;
|
||||||
|
getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>;
|
||||||
|
getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise<AssetCountByTimeBucket[]>;
|
||||||
|
getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>;
|
||||||
|
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ASSET_REPOSITORY = 'ASSET_REPOSITORY';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AssetRepository implements IAssetRepository {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(AssetEntity)
|
||||||
|
private assetRepository: Repository<AssetEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> {
|
||||||
|
// Get asset entity from a list of time buckets
|
||||||
|
return await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.where('asset.userId = :userId', { userId: userId })
|
||||||
|
.andWhere(`date_trunc('month', "createdAt") IN (:...buckets)`, {
|
||||||
|
buckets: [...getAssetByTimeBucketDto.timeBucket],
|
||||||
|
})
|
||||||
|
.andWhere('asset.resizePath is not NULL')
|
||||||
|
.orderBy('asset.createdAt', 'DESC')
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum) {
|
||||||
|
let result: AssetCountByTimeBucket[] = [];
|
||||||
|
|
||||||
|
if (timeBucket === TimeGroupEnum.Month) {
|
||||||
|
result = await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.select(`COUNT(asset.id)::int`, 'count')
|
||||||
|
.addSelect(`date_trunc('month', "createdAt")`, 'timeBucket')
|
||||||
|
.where('"userId" = :userId', { userId: userId })
|
||||||
|
.groupBy(`date_trunc('month', "createdAt")`)
|
||||||
|
.orderBy(`date_trunc('month', "createdAt")`, 'DESC')
|
||||||
|
.getRawMany();
|
||||||
|
} else if (timeBucket === TimeGroupEnum.Day) {
|
||||||
|
result = await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.select(`COUNT(asset.id)::int`, 'count')
|
||||||
|
.addSelect(`date_trunc('day', "createdAt")`, 'timeBucket')
|
||||||
|
.where('"userId" = :userId', { userId: userId })
|
||||||
|
.groupBy(`date_trunc('day', "createdAt")`)
|
||||||
|
.orderBy(`date_trunc('day', "createdAt")`, 'DESC')
|
||||||
|
.getRawMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]> {
|
||||||
|
return await this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.where('asset.userId = :userId', { userId: userId })
|
||||||
|
.leftJoin('asset.exifInfo', 'ei')
|
||||||
|
.leftJoin('asset.smartInfo', 'si')
|
||||||
|
.select('si.tags', 'tags')
|
||||||
|
.addSelect('si.objects', 'objects')
|
||||||
|
.addSelect('asset.type', 'assetType')
|
||||||
|
.addSelect('ei.orientation', 'orientation')
|
||||||
|
.addSelect('ei."lensModel"', 'lensModel')
|
||||||
|
.addSelect('ei.make', 'make')
|
||||||
|
.addSelect('ei.model', 'model')
|
||||||
|
.addSelect('ei.city', 'city')
|
||||||
|
.addSelect('ei.state', 'state')
|
||||||
|
.addSelect('ei.country', 'country')
|
||||||
|
.distinctOn(['si.tags'])
|
||||||
|
.getRawMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]> {
|
||||||
|
return await this.assetRepository.query(
|
||||||
|
`
|
||||||
|
SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
|
||||||
|
FROM assets a
|
||||||
|
LEFT JOIN smart_info si ON a.id = si."assetId"
|
||||||
|
WHERE a."userId" = $1
|
||||||
|
AND si.objects IS NOT NULL
|
||||||
|
`,
|
||||||
|
[userId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLocationsByUserId(userId: string): Promise<CuratedLocationsResponseDto[]> {
|
||||||
|
return await this.assetRepository.query(
|
||||||
|
`
|
||||||
|
SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
|
||||||
|
FROM assets a
|
||||||
|
LEFT JOIN exif e ON a.id = e."assetId"
|
||||||
|
WHERE a."userId" = $1
|
||||||
|
AND e.city IS NOT NULL
|
||||||
|
AND a.type = 'IMAGE';
|
||||||
|
`,
|
||||||
|
[userId],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single asset information by its ID
|
||||||
|
* - include exif info
|
||||||
|
* @param assetId
|
||||||
|
*/
|
||||||
|
async getById(assetId: string): Promise<AssetEntity> {
|
||||||
|
return await this.assetRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
id: assetId,
|
||||||
|
},
|
||||||
|
relations: ['exifInfo'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all assets belong to the user on the database
|
||||||
|
* @param userId
|
||||||
|
*/
|
||||||
|
async getAllByUserId(userId: string): Promise<AssetEntity[]> {
|
||||||
|
const query = this.assetRepository
|
||||||
|
.createQueryBuilder('asset')
|
||||||
|
.where('asset.userId = :userId', { userId: userId })
|
||||||
|
.andWhere('asset.resizePath is not NULL')
|
||||||
|
.leftJoinAndSelect('asset.exifInfo', 'exifInfo')
|
||||||
|
.orderBy('asset.createdAt', 'DESC');
|
||||||
|
|
||||||
|
return await query.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create new asset information in database
|
||||||
|
* @param createAssetDto
|
||||||
|
* @param ownerId
|
||||||
|
* @param originalPath
|
||||||
|
* @param mimeType
|
||||||
|
* @returns Promise<AssetEntity>
|
||||||
|
*/
|
||||||
|
async create(
|
||||||
|
createAssetDto: CreateAssetDto,
|
||||||
|
ownerId: string,
|
||||||
|
originalPath: string,
|
||||||
|
mimeType: string,
|
||||||
|
checksum?: Buffer,
|
||||||
|
): Promise<AssetEntity> {
|
||||||
|
const asset = new AssetEntity();
|
||||||
|
asset.deviceAssetId = createAssetDto.deviceAssetId;
|
||||||
|
asset.userId = ownerId;
|
||||||
|
asset.deviceId = createAssetDto.deviceId;
|
||||||
|
asset.type = createAssetDto.assetType || AssetType.OTHER;
|
||||||
|
asset.originalPath = originalPath;
|
||||||
|
asset.createdAt = createAssetDto.createdAt;
|
||||||
|
asset.modifiedAt = createAssetDto.modifiedAt;
|
||||||
|
asset.isFavorite = createAssetDto.isFavorite;
|
||||||
|
asset.mimeType = mimeType;
|
||||||
|
asset.duration = createAssetDto.duration || null;
|
||||||
|
asset.checksum = checksum || null;
|
||||||
|
|
||||||
|
const createdAsset = await this.assetRepository.save(asset);
|
||||||
|
|
||||||
|
if (!createdAsset) {
|
||||||
|
throw new BadRequestException('Asset not created');
|
||||||
|
}
|
||||||
|
return createdAsset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get assets by device's Id on the database
|
||||||
|
* @param userId
|
||||||
|
* @param deviceId
|
||||||
|
*
|
||||||
|
* @returns Promise<string[]> - Array of assetIds belong to the device
|
||||||
|
*/
|
||||||
|
async getAllByDeviceId(userId: string, deviceId: string): Promise<string[]> {
|
||||||
|
const rows = await this.assetRepository.find({
|
||||||
|
where: {
|
||||||
|
userId: userId,
|
||||||
|
deviceId: deviceId,
|
||||||
|
},
|
||||||
|
select: ['deviceAssetId'],
|
||||||
|
});
|
||||||
|
const res: string[] = [];
|
||||||
|
rows.forEach((v) => res.push(v.deviceAssetId));
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get asset by checksum on the database
|
||||||
|
* @param userId
|
||||||
|
* @param checksum
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity> {
|
||||||
|
return this.assetRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
checksum,
|
||||||
|
},
|
||||||
|
relations: ['exifInfo'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ import {
|
|||||||
Controller,
|
Controller,
|
||||||
Post,
|
Post,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
UploadedFiles,
|
|
||||||
Body,
|
Body,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Get,
|
Get,
|
||||||
@@ -16,6 +15,7 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
UploadedFile,
|
UploadedFile,
|
||||||
|
Header,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../modules/immich-jwt/guards/jwt-auth.guard';
|
||||||
import { AssetService } from './asset.service';
|
import { AssetService } from './asset.service';
|
||||||
@@ -44,6 +44,10 @@ import { CreateAssetDto } from './dto/create-asset.dto';
|
|||||||
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto } from './dto/get-asset-thumbnail.dto';
|
||||||
|
import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
|
import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto';
|
||||||
|
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||||
|
import { QueryFailedError } from 'typeorm';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@@ -71,9 +75,17 @@ export class AssetController {
|
|||||||
@UploadedFile() file: Express.Multer.File,
|
@UploadedFile() file: Express.Multer.File,
|
||||||
@Body(ValidationPipe) assetInfo: CreateAssetDto,
|
@Body(ValidationPipe) assetInfo: CreateAssetDto,
|
||||||
): Promise<AssetFileUploadResponseDto> {
|
): Promise<AssetFileUploadResponseDto> {
|
||||||
|
const checksum = await this.assetService.calculateChecksum(file.path);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype);
|
const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype, checksum);
|
||||||
|
|
||||||
if (!savedAsset) {
|
if (!savedAsset) {
|
||||||
|
await this.backgroundTaskService.deleteFileOnDisk([
|
||||||
|
{
|
||||||
|
originalPath: file.path,
|
||||||
|
} as any,
|
||||||
|
]); // simulate asset to make use of delete queue (or use fs.unlink instead)
|
||||||
throw new BadRequestException('Asset not created');
|
throw new BadRequestException('Asset not created');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +96,20 @@ export class AssetController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return new AssetFileUploadResponseDto(savedAsset.id);
|
return new AssetFileUploadResponseDto(savedAsset.id);
|
||||||
} catch (e) {
|
} catch (err) {
|
||||||
Logger.error(`Error uploading file ${e}`);
|
await this.backgroundTaskService.deleteFileOnDisk([
|
||||||
throw new BadRequestException(`Error uploading file`, `${e}`);
|
{
|
||||||
|
originalPath: file.path,
|
||||||
|
} as any,
|
||||||
|
]); // simulate asset to make use of delete queue (or use fs.unlink instead)
|
||||||
|
|
||||||
|
if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') {
|
||||||
|
const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum)
|
||||||
|
return new AssetFileUploadResponseDto(existedAsset.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.error(`Error uploading file ${err}`);
|
||||||
|
throw new BadRequestException(`Error uploading file`, `${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,23 +134,24 @@ export class AssetController {
|
|||||||
|
|
||||||
@Get('/thumbnail/:assetId')
|
@Get('/thumbnail/:assetId')
|
||||||
async getAssetThumbnail(
|
async getAssetThumbnail(
|
||||||
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
|
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
return this.assetService.getAssetThumbnail(assetId, query);
|
return this.assetService.getAssetThumbnail(assetId, query, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/allObjects')
|
@Get('/curated-objects')
|
||||||
async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||||
return this.assetService.getCuratedObject(authUser);
|
return this.assetService.getCuratedObject(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/allLocation')
|
@Get('/curated-locations')
|
||||||
async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||||
return this.assetService.getCuratedLocation(authUser);
|
return this.assetService.getCuratedLocation(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/searchTerm')
|
@Get('/search-terms')
|
||||||
async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<string[]> {
|
async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise<string[]> {
|
||||||
return this.assetService.getAssetSearchTerm(authUser);
|
return this.assetService.getAssetSearchTerm(authUser);
|
||||||
}
|
}
|
||||||
@@ -140,6 +164,14 @@ export class AssetController {
|
|||||||
return this.assetService.searchAsset(authUser, searchAssetDto);
|
return this.assetService.searchAsset(authUser, searchAssetDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/count-by-time-bucket')
|
||||||
|
async getAssetCountByTimeBucket(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
|
@Body(ValidationPipe) getAssetCountByTimeGroupDto: GetAssetCountByTimeBucketDto,
|
||||||
|
): Promise<AssetCountByTimeBucketResponseDto> {
|
||||||
|
return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all AssetEntity belong to the user
|
* Get all AssetEntity belong to the user
|
||||||
*/
|
*/
|
||||||
@@ -148,6 +180,13 @@ export class AssetController {
|
|||||||
return await this.assetService.getAllAssets(authUser);
|
return await this.assetService.getAllAssets(authUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('/time-bucket')
|
||||||
|
async getAssetByTimeBucket(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
|
@Body(ValidationPipe) getAssetByTimeBucketDto: GetAssetByTimeBucketDto,
|
||||||
|
): Promise<AssetResponseDto[]> {
|
||||||
|
return await this.assetService.getAssetByTimeBucket(authUser, getAssetByTimeBucketDto);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Get all asset of a device that are in the database, ID only.
|
* Get all asset of a device that are in the database, ID only.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { BackgroundTaskModule } from '../../modules/background-task/background-t
|
|||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
|
||||||
import { CommunicationModule } from '../communication/communication.module';
|
import { CommunicationModule } from '../communication/communication.module';
|
||||||
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
||||||
|
import { AssetRepository, ASSET_REPOSITORY } from './asset-repository';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -24,7 +25,14 @@ import { assetUploadedQueueName } from '@app/job/constants/queue-name.constant';
|
|||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
controllers: [AssetController],
|
controllers: [AssetController],
|
||||||
providers: [AssetService, BackgroundTaskService],
|
providers: [
|
||||||
|
AssetService,
|
||||||
|
BackgroundTaskService,
|
||||||
|
{
|
||||||
|
provide: ASSET_REPOSITORY,
|
||||||
|
useClass: AssetRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
exports: [AssetService],
|
exports: [AssetService],
|
||||||
})
|
})
|
||||||
export class AssetModule {}
|
export class AssetModule {}
|
||||||
|
|||||||
95
server/apps/immich/src/api-v1/asset/asset.service.spec.ts
Normal file
95
server/apps/immich/src/api-v1/asset/asset.service.spec.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { AssetRepository, IAssetRepository } from './asset-repository';
|
||||||
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
|
import { AssetService } from './asset.service';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
|
|
||||||
|
describe('AssetService', () => {
|
||||||
|
let sui: AssetService;
|
||||||
|
let a: Repository<AssetEntity>; // TO BE DELETED AFTER FINISHED REFACTORING
|
||||||
|
let assetRepositoryMock: jest.Mocked<IAssetRepository>;
|
||||||
|
|
||||||
|
const authUser: AuthUserDto = Object.freeze({
|
||||||
|
id: '3ea54709-e168-42b7-90b0-a0dfe8a7ecbd',
|
||||||
|
email: 'auth@test.com',
|
||||||
|
});
|
||||||
|
|
||||||
|
const _getCreateAssetDto = (): CreateAssetDto => {
|
||||||
|
const createAssetDto = new CreateAssetDto();
|
||||||
|
createAssetDto.deviceAssetId = 'deviceAssetId';
|
||||||
|
createAssetDto.deviceId = 'deviceId';
|
||||||
|
createAssetDto.assetType = AssetType.OTHER;
|
||||||
|
createAssetDto.createdAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
createAssetDto.modifiedAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
createAssetDto.isFavorite = false;
|
||||||
|
createAssetDto.duration = '0:00:00.000000';
|
||||||
|
|
||||||
|
return createAssetDto;
|
||||||
|
};
|
||||||
|
const _getAsset = () => {
|
||||||
|
const assetEntity = new AssetEntity();
|
||||||
|
|
||||||
|
assetEntity.id = 'e8edabfd-7d8a-45d0-9d61-7c7ca60f2c67';
|
||||||
|
assetEntity.userId = '3ea54709-e168-42b7-90b0-a0dfe8a7ecbd';
|
||||||
|
assetEntity.deviceAssetId = '4967046344801';
|
||||||
|
assetEntity.deviceId = '116766fd-2ef2-52dc-a3ef-149988997291';
|
||||||
|
assetEntity.type = AssetType.VIDEO;
|
||||||
|
assetEntity.originalPath =
|
||||||
|
'upload/3ea54709-e168-42b7-90b0-a0dfe8a7ecbd/original/116766fd-2ef2-52dc-a3ef-149988997291/51c97f95-244f-462d-bdf0-e1dc19913516.jpg';
|
||||||
|
assetEntity.resizePath = '';
|
||||||
|
assetEntity.createdAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
assetEntity.modifiedAt = '2022-06-19T23:41:36.910Z';
|
||||||
|
assetEntity.isFavorite = false;
|
||||||
|
assetEntity.mimeType = 'image/jpeg';
|
||||||
|
assetEntity.webpPath = '';
|
||||||
|
assetEntity.encodedVideoPath = '';
|
||||||
|
assetEntity.duration = '0:00:00.000000';
|
||||||
|
|
||||||
|
return assetEntity;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
assetRepositoryMock = {
|
||||||
|
create: jest.fn(),
|
||||||
|
getAllByUserId: jest.fn(),
|
||||||
|
getAllByDeviceId: jest.fn(),
|
||||||
|
getAssetCountByTimeBucket: jest.fn(),
|
||||||
|
getById: jest.fn(),
|
||||||
|
getDetectedObjectsByUserId: jest.fn(),
|
||||||
|
getLocationsByUserId: jest.fn(),
|
||||||
|
getSearchPropertiesByUserId: jest.fn(),
|
||||||
|
getAssetByTimeBucket: jest.fn(),
|
||||||
|
getAssetByChecksum: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
sui = new AssetService(assetRepositoryMock, a);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Currently failing due to calculate checksum from a file
|
||||||
|
// it('create an asset', async () => {
|
||||||
|
// const assetEntity = _getAsset();
|
||||||
|
|
||||||
|
// assetRepositoryMock.create.mockImplementation(() => Promise.resolve<AssetEntity>(assetEntity));
|
||||||
|
|
||||||
|
// const originalPath =
|
||||||
|
// 'upload/3ea54709-e168-42b7-90b0-a0dfe8a7ecbd/original/116766fd-2ef2-52dc-a3ef-149988997291/51c97f95-244f-462d-bdf0-e1dc19913516.jpg';
|
||||||
|
// const mimeType = 'image/jpeg';
|
||||||
|
// const createAssetDto = _getCreateAssetDto();
|
||||||
|
// const result = await sui.createUserAsset(authUser, createAssetDto, originalPath, mimeType);
|
||||||
|
|
||||||
|
// expect(result.userId).toEqual(authUser.id);
|
||||||
|
// expect(result.resizePath).toEqual('');
|
||||||
|
// expect(result.webpPath).toEqual('');
|
||||||
|
// });
|
||||||
|
|
||||||
|
it('get assets by device id', async () => {
|
||||||
|
assetRepositoryMock.getAllByDeviceId.mockImplementation(() => Promise.resolve<string[]>(['4967046344801']));
|
||||||
|
|
||||||
|
const deviceId = '116766fd-2ef2-52dc-a3ef-149988997291';
|
||||||
|
const result = await sui.getUserAssetsByDeviceId(authUser, deviceId);
|
||||||
|
|
||||||
|
expect(result.length).toEqual(1);
|
||||||
|
expect(result[0]).toEqual('4967046344801');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
Inject,
|
||||||
Injectable,
|
Injectable,
|
||||||
InternalServerErrorException,
|
InternalServerErrorException,
|
||||||
Logger,
|
Logger,
|
||||||
@@ -7,7 +9,8 @@ import {
|
|||||||
StreamableFile,
|
StreamableFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { IsNull, Not, Repository } from 'typeorm';
|
import { createHash } from 'node:crypto';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
import { AssetEntity, AssetType } from '@app/database/entities/asset.entity';
|
||||||
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
||||||
@@ -20,88 +23,70 @@ import fs from 'fs/promises';
|
|||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
|
import { AssetResponseDto, mapAsset } from './response-dto/asset-response.dto';
|
||||||
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
||||||
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
||||||
|
import { ASSET_REPOSITORY, IAssetRepository } from './asset-repository';
|
||||||
|
import { SearchPropertiesDto } from './dto/search-properties.dto';
|
||||||
|
import {
|
||||||
|
AssetCountByTimeBucketResponseDto,
|
||||||
|
mapAssetCountByTimeBucket,
|
||||||
|
} from './response-dto/asset-count-by-time-group-response.dto';
|
||||||
|
import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto';
|
||||||
|
import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AssetService {
|
export class AssetService {
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(ASSET_REPOSITORY)
|
||||||
|
private _assetRepository: IAssetRepository,
|
||||||
|
|
||||||
@InjectRepository(AssetEntity)
|
@InjectRepository(AssetEntity)
|
||||||
private assetRepository: Repository<AssetEntity>,
|
private assetRepository: Repository<AssetEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async updateThumbnailInfo(asset: AssetEntity, thumbnailPath: string): Promise<AssetEntity> {
|
|
||||||
const updatedAsset = await this.assetRepository
|
|
||||||
.createQueryBuilder('assets')
|
|
||||||
.update<AssetEntity>(AssetEntity, { ...asset, resizePath: thumbnailPath })
|
|
||||||
.where('assets.id = :id', { id: asset.id })
|
|
||||||
.returning('*')
|
|
||||||
.updateEntity(true)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
return updatedAsset.raw[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createUserAsset(
|
public async createUserAsset(
|
||||||
authUser: AuthUserDto,
|
authUser: AuthUserDto,
|
||||||
assetInfo: CreateAssetDto,
|
createAssetDto: CreateAssetDto,
|
||||||
path: string,
|
originalPath: string,
|
||||||
mimeType: string,
|
mimeType: string,
|
||||||
): Promise<AssetEntity | undefined> {
|
checksum: Buffer,
|
||||||
const asset = new AssetEntity();
|
): Promise<AssetEntity> {
|
||||||
asset.deviceAssetId = assetInfo.deviceAssetId;
|
const assetEntity = await this._assetRepository.create(
|
||||||
asset.userId = authUser.id;
|
createAssetDto,
|
||||||
asset.deviceId = assetInfo.deviceId;
|
authUser.id,
|
||||||
asset.type = assetInfo.assetType || AssetType.OTHER;
|
originalPath,
|
||||||
asset.originalPath = path;
|
mimeType,
|
||||||
asset.createdAt = assetInfo.createdAt;
|
checksum,
|
||||||
asset.modifiedAt = assetInfo.modifiedAt;
|
);
|
||||||
asset.isFavorite = assetInfo.isFavorite;
|
|
||||||
asset.mimeType = mimeType;
|
|
||||||
asset.duration = assetInfo.duration || null;
|
|
||||||
|
|
||||||
const createdAsset = await this.assetRepository.save(asset);
|
return assetEntity;
|
||||||
if (!createdAsset) {
|
|
||||||
throw new Error('Asset not created');
|
|
||||||
}
|
|
||||||
return createdAsset;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
public async getUserAssetsByDeviceId(authUser: AuthUserDto, deviceId: string) {
|
||||||
const rows = await this.assetRepository.find({
|
return this._assetRepository.getAllByDeviceId(authUser.id, deviceId);
|
||||||
where: {
|
|
||||||
userId: authUser.id,
|
|
||||||
deviceId: deviceId,
|
|
||||||
},
|
|
||||||
select: ['deviceAssetId'],
|
|
||||||
});
|
|
||||||
|
|
||||||
const res: string[] = [];
|
|
||||||
rows.forEach((v) => res.push(v.deviceAssetId));
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> {
|
public async getAllAssets(authUser: AuthUserDto): Promise<AssetResponseDto[]> {
|
||||||
const assets = await this.assetRepository.find({
|
const assets = await this._assetRepository.getAllByUserId(authUser.id);
|
||||||
where: {
|
|
||||||
userId: authUser.id,
|
|
||||||
resizePath: Not(IsNull()),
|
|
||||||
},
|
|
||||||
relations: ['exifInfo'],
|
|
||||||
order: {
|
|
||||||
createdAt: 'DESC',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return assets.map((asset) => mapAsset(asset));
|
return assets.map((asset) => mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async findAssetOfDevice(deviceId: string, assetId: string): Promise<AssetResponseDto> {
|
public async getAssetByTimeBucket(
|
||||||
|
authUser: AuthUserDto,
|
||||||
|
getAssetByTimeBucketDto: GetAssetByTimeBucketDto,
|
||||||
|
): Promise<AssetResponseDto[]> {
|
||||||
|
const assets = await this._assetRepository.getAssetByTimeBucket(authUser.id, getAssetByTimeBucketDto);
|
||||||
|
|
||||||
|
return assets.map((asset) => mapAsset(asset));
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - Refactor this to get asset by its own id
|
||||||
|
private async findAssetOfDevice(deviceId: string, assetId: string): Promise<AssetResponseDto> {
|
||||||
const rows = await this.assetRepository.query(
|
const rows = await this.assetRepository.query(
|
||||||
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
|
'SELECT * FROM assets a WHERE a."deviceAssetId" = $1 AND a."deviceId" = $2',
|
||||||
[assetId, deviceId],
|
[assetId, deviceId],
|
||||||
@@ -117,16 +102,7 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
public async getAssetById(authUser: AuthUserDto, assetId: string): Promise<AssetResponseDto> {
|
||||||
const asset = await this.assetRepository.findOne({
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
where: {
|
|
||||||
id: assetId,
|
|
||||||
},
|
|
||||||
relations: ['exifInfo'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!asset) {
|
|
||||||
throw new NotFoundException('Asset not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapAsset(asset);
|
return mapAsset(asset);
|
||||||
}
|
}
|
||||||
@@ -189,7 +165,7 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetThumbnail(assetId: string, query: GetAssetThumbnailDto) {
|
public async getAssetThumbnail(assetId: string, query: GetAssetThumbnailDto, res: Res) {
|
||||||
let fileReadStream: ReadStream;
|
let fileReadStream: ReadStream;
|
||||||
|
|
||||||
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
|
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
|
||||||
@@ -220,8 +196,10 @@ export class AssetService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
res.header('Cache-Control', 'max-age=300');
|
||||||
return new StreamableFile(fileReadStream);
|
return new StreamableFile(fileReadStream);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
res.header('Cache-Control', 'none');
|
||||||
Logger.error(`Cannot create read stream for asset ${asset.id}`, 'getAssetThumbnail');
|
Logger.error(`Cannot create read stream for asset ${asset.id}`, 'getAssetThumbnail');
|
||||||
throw new InternalServerErrorException(
|
throw new InternalServerErrorException(
|
||||||
e,
|
e,
|
||||||
@@ -394,45 +372,35 @@ export class AssetService {
|
|||||||
|
|
||||||
async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
|
async getAssetSearchTerm(authUser: AuthUserDto): Promise<string[]> {
|
||||||
const possibleSearchTerm = new Set<string>();
|
const possibleSearchTerm = new Set<string>();
|
||||||
// TODO: should use query builder
|
|
||||||
const rows = await this.assetRepository.query(
|
|
||||||
`
|
|
||||||
SELECT DISTINCT si.tags, si.objects, e.orientation, e."lensModel", e.make, e.model , a.type, e.city, e.state, e.country
|
|
||||||
FROM assets a
|
|
||||||
LEFT JOIN exif e ON a.id = e."assetId"
|
|
||||||
LEFT JOIN smart_info si ON a.id = si."assetId"
|
|
||||||
WHERE a."userId" = $1;
|
|
||||||
`,
|
|
||||||
[authUser.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
rows.forEach((row: { [x: string]: any }) => {
|
const rows = await this._assetRepository.getSearchPropertiesByUserId(authUser.id);
|
||||||
|
rows.forEach((row: SearchPropertiesDto) => {
|
||||||
// tags
|
// tags
|
||||||
row['tags']?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase()));
|
row.tags?.map((tag: string) => possibleSearchTerm.add(tag?.toLowerCase()));
|
||||||
|
|
||||||
// objects
|
// objects
|
||||||
row['objects']?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase()));
|
row.objects?.map((object: string) => possibleSearchTerm.add(object?.toLowerCase()));
|
||||||
|
|
||||||
// asset's tyoe
|
// asset's tyoe
|
||||||
possibleSearchTerm.add(row['type']?.toLowerCase());
|
possibleSearchTerm.add(row.assetType?.toLowerCase() || '');
|
||||||
|
|
||||||
// image orientation
|
// image orientation
|
||||||
possibleSearchTerm.add(row['orientation']?.toLowerCase());
|
possibleSearchTerm.add(row.orientation?.toLowerCase() || '');
|
||||||
|
|
||||||
// Lens model
|
// Lens model
|
||||||
possibleSearchTerm.add(row['lensModel']?.toLowerCase());
|
possibleSearchTerm.add(row.lensModel?.toLowerCase() || '');
|
||||||
|
|
||||||
// Make and model
|
// Make and model
|
||||||
possibleSearchTerm.add(row['make']?.toLowerCase());
|
possibleSearchTerm.add(row.make?.toLowerCase() || '');
|
||||||
possibleSearchTerm.add(row['model']?.toLowerCase());
|
possibleSearchTerm.add(row.model?.toLowerCase() || '');
|
||||||
|
|
||||||
// Location
|
// Location
|
||||||
possibleSearchTerm.add(row['city']?.toLowerCase());
|
possibleSearchTerm.add(row.city?.toLowerCase() || '');
|
||||||
possibleSearchTerm.add(row['state']?.toLowerCase());
|
possibleSearchTerm.add(row.state?.toLowerCase() || '');
|
||||||
possibleSearchTerm.add(row['country']?.toLowerCase());
|
possibleSearchTerm.add(row.country?.toLowerCase() || '');
|
||||||
});
|
});
|
||||||
|
|
||||||
return Array.from(possibleSearchTerm).filter((x) => x != null);
|
return Array.from(possibleSearchTerm).filter((x) => x != null && x != '');
|
||||||
}
|
}
|
||||||
|
|
||||||
async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise<AssetResponseDto[]> {
|
async searchAsset(authUser: AuthUserDto, searchAssetDto: SearchAssetDto): Promise<AssetResponseDto[]> {
|
||||||
@@ -459,33 +427,12 @@ export class AssetService {
|
|||||||
return searchResults.map((asset) => mapAsset(asset));
|
return searchResults.map((asset) => mapAsset(asset));
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCuratedLocation(authUser: AuthUserDto) {
|
async getCuratedLocation(authUser: AuthUserDto): Promise<CuratedLocationsResponseDto[]> {
|
||||||
return await this.assetRepository.query(
|
return this._assetRepository.getLocationsByUserId(authUser.id);
|
||||||
`
|
|
||||||
SELECT DISTINCT ON (e.city) a.id, e.city, a."resizePath", a."deviceAssetId", a."deviceId"
|
|
||||||
FROM assets a
|
|
||||||
LEFT JOIN exif e ON a.id = e."assetId"
|
|
||||||
WHERE a."userId" = $1
|
|
||||||
AND e.city IS NOT NULL
|
|
||||||
AND a.type = 'IMAGE';
|
|
||||||
`,
|
|
||||||
[authUser.id],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
async getCuratedObject(authUser: AuthUserDto): Promise<CuratedObjectsResponseDto[]> {
|
||||||
const curatedObjects: CuratedObjectsResponseDto[] = await this.assetRepository.query(
|
return this._assetRepository.getDetectedObjectsByUserId(authUser.id);
|
||||||
`
|
|
||||||
SELECT DISTINCT ON (unnest(si.objects)) a.id, unnest(si.objects) as "object", a."resizePath", a."deviceAssetId", a."deviceId"
|
|
||||||
FROM assets a
|
|
||||||
LEFT JOIN smart_info si ON a.id = si."assetId"
|
|
||||||
WHERE a."userId" = $1
|
|
||||||
AND si.objects IS NOT NULL
|
|
||||||
`,
|
|
||||||
[authUser.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
return curatedObjects;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkDuplicatedAsset(
|
async checkDuplicatedAsset(
|
||||||
@@ -504,4 +451,32 @@ export class AssetService {
|
|||||||
|
|
||||||
return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id);
|
return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getAssetCountByTimeBucket(
|
||||||
|
authUser: AuthUserDto,
|
||||||
|
getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto,
|
||||||
|
): Promise<AssetCountByTimeBucketResponseDto> {
|
||||||
|
const result = await this._assetRepository.getAssetCountByTimeBucket(
|
||||||
|
authUser.id,
|
||||||
|
getAssetCountByTimeBucketDto.timeGroup,
|
||||||
|
);
|
||||||
|
|
||||||
|
return mapAssetCountByTimeBucket(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetByChecksum(userId: string, checksum: Buffer) {
|
||||||
|
return this._assetRepository.getAssetByChecksum(userId, checksum);
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateChecksum(filePath: string): Promise<Buffer> {
|
||||||
|
const fileReadStream = createReadStream(filePath);
|
||||||
|
const sha1Hash = createHash('sha1');
|
||||||
|
const deferred = new Promise<Buffer>((resolve, reject) => {
|
||||||
|
sha1Hash.once('error', (err) => reject(err));
|
||||||
|
sha1Hash.once('finish', () => resolve(sha1Hash.read()));
|
||||||
|
});
|
||||||
|
|
||||||
|
fileReadStream.pipe(sha1Hash);
|
||||||
|
return deferred;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class GetAssetByTimeBucketDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty({
|
||||||
|
isArray: true,
|
||||||
|
type: String,
|
||||||
|
title: 'Array of date time buckets',
|
||||||
|
example: ['2015-06-01T00:00:00.000Z', '2016-02-01T00:00:00.000Z', '2016-03-01T00:00:00.000Z'],
|
||||||
|
})
|
||||||
|
timeBucket!: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export enum TimeGroupEnum {
|
||||||
|
Day = 'day',
|
||||||
|
Month = 'month',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetAssetCountByTimeBucketDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
enum: TimeGroupEnum,
|
||||||
|
enumName: 'TimeGroupEnum',
|
||||||
|
})
|
||||||
|
timeGroup!: TimeGroupEnum;
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export class SearchPropertiesDto {
|
||||||
|
tags?: string[];
|
||||||
|
objects?: string[];
|
||||||
|
assetType?: string;
|
||||||
|
orientation?: string;
|
||||||
|
lensModel?: string;
|
||||||
|
make?: string;
|
||||||
|
model?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
country?: string;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user