Compare commits

...

34 Commits

Author SHA1 Message Date
Immich Release Bot
7d6d51f4a5 Version v1.50.0 2023-03-01 03:22:31 +00:00
Alex Tran
5777693fad Merge branch 'main' of github.com:immich-app/immich 2023-02-28 21:21:40 -06:00
Alex Tran
b53cc4f9db chore: test pump 2023-02-28 21:21:33 -06:00
martyfuhry
9d57039274 feat(mobile): Responsive list and grid view of backup album selection and fixes search filter (#1895)
* rebuilding gridview

* adds listview, gridview and responsive display to backup album selection

* aligned selection info title and chips to the left

* fixed search

* style: album tile

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-02-28 21:10:53 -06:00
martyfuhry
12217bde8a feat(mobile): Adds onboarding for permissions (#1865)
* adds onboarding

* fixed error where login was taking you to permission page

* fixed a bad rebase and added more checks to not start backup service on login if no gallery permission

* forgot the permission handler import in AppDelegate

* reverts album selection page

* change to ref watch

* added device_info_plus to podspec

* removed unused import

---------

Co-authored-by: Marty Fuhry <marty@fuhry.farm>
2023-02-28 10:22:18 -06:00
Alex Tran
37f802d1fe Merge branch 'main' of github.com:immich-app/immich 2023-02-28 08:58:54 -06:00
Ikko Eltociear Ashimine
df1710f4cc chore: update README.md (#1892)
Github -> GitHub
2023-02-27 22:05:22 -06:00
Alex Tran
0fec34d316 Merge branch 'main' of github.com:immich-app/immich 2023-02-27 21:02:52 -06:00
martyfuhry
a0b8312ce4 adds safe area to album to stop from clipping bottom of albums (#1889) 2023-02-27 18:35:10 -06:00
Alex Tran
8abe6909ca Merge branch 'main' of github.com:immich-app/immich 2023-02-27 18:29:02 -06:00
Alex
25cff6a748 fix(server) long album load time on Album and Sharing page (#1890)
* chore: update package-lock.json version

* rfix(server) long album load time

* remove all eagerness

* generate index

* remove console.log

* remove deadcode

* fix: shared link album owner
2023-02-27 18:28:45 -06:00
Michel Heusschen
243c98a02e feat(web): re-add version announcement (#1887)
* feat(web): re-add version announcement

* show notification for every update
2023-02-27 17:13:39 -06:00
Matthias Rupp
c9a6820de7 chore(mobile): Favorite provider unit test (#1874)
* Favorite provider tests

* Remove unused mock

* Add setUp function to avoid duplicate code
2023-02-27 21:15:25 +01:00
Alex Tran
7d586492f3 Merge branch 'main' of github.com:immich-app/immich 2023-02-26 21:23:58 -06:00
Michel Heusschen
807bdfeda9 fix(web): layout nesting (#1881)
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2023-02-26 21:23:43 -06:00
Alex Tran
1f25df308a Merge branch 'main' of github.com:immich-app/immich 2023-02-26 20:58:57 -06:00
Michel Heusschen
2efa8b6960 fix(web): setInterval outside onMount (#1883) 2023-02-26 15:55:06 -06:00
Alex Tran
7c9d2018d8 Merge branch 'main' of github.com:immich-app/immich 2023-02-26 15:04:10 -06:00
Olly Welch
ab90b01122 feat(server) Enable ML support for ARM CPUs (#1880)
* Install nightly release of pytorch to enable ML support for arm CPUs

* Remove linux/arm/v7 from ML docker builds

* Add --no-cache-dir to torch installation command in ML image build

* Use PIP_NO_CACHE_DIR option in ML build to further decrease image size
2023-02-26 15:00:09 -06:00
Alex Tran
d04ef319b8 Merge branch 'main' of github.com:immich-app/immich 2023-02-26 13:59:48 -06:00
Michel Heusschen
368142e79b feat(web): improved server stats (#1870)
* feat(web): improved server stats

* fix(web): don't log unauthorized errors

* Revert "fix(web): don't log unauthorized errors"

This reverts commit 7fc2987a77.
2023-02-26 13:57:34 -06:00
Alex
7d45ae68a6 chore: update package-lock.json version (#1884) 2023-02-26 13:30:50 -06:00
Alex Tran
98bedcf1e5 chore: update package-lock.json version 2023-02-26 13:24:57 -06:00
Michel Heusschen
3377fa4640 fix(web): use correct api for asset share page (#1879) 2023-02-26 13:21:59 -06:00
Michel Heusschen
641c05c6fe chore(web): update dependencies (#1877)
* chore(web): update dependencies

* remove invalid alt attribute
2023-02-26 13:18:26 -06:00
Michel Heusschen
e157a69d86 fix(web): don't log unauthorized errors (#1871)
* fix(web): don't log unauthorized errors

* fix docker build error
2023-02-26 10:50:18 -06:00
Zack Pollard
3d468c369c fix: machine learning only take results with > 90% confidence (#1875) 2023-02-25 22:02:35 -06:00
Jason Rasmussen
6c7679714b refactor(server): jobs and processors (#1787)
* refactor: jobs and processors

* refactor: storage migration processor

* fix: tests

* fix: code warning

* chore: ignore coverage from infra

* fix: sync move asset logic between job core and asset core

* refactor: move error handling inside of catch

* refactor(server): job core into dedicated service calls

* refactor: smart info

* fix: tests

* chore: smart info tests

* refactor: use asset repository

* refactor: thumbnail processor

* chore: coverage reqs
2023-02-25 08:12:03 -06:00
Alex
71d8567f18 [Localizely] Translations update (#1860) 2023-02-24 14:42:33 -06:00
Michel Heusschen
cc6253ba38 fix(web): sharing of access token in server API (#1858) 2023-02-24 14:42:20 -06:00
Alex
3ea107be5a chore Add Norweigain localization setup (#1859)
* chore(localization): Add Norwegian localization setup

* chore(localization): Add Norwegian localization setup
2023-02-24 12:50:56 -06:00
martyfuhry
4ed96cf1bd fix(mobile): Prevents duplicate taps navigating to the same route twice (#1855) 2023-02-24 10:51:35 -06:00
Michel Heusschen
9323cc76d9 feat(server): improve API specification (#1853) 2023-02-24 10:01:10 -06:00
Alex
da9b9c8c69 chore: post release tasks (#1849) 2023-02-23 15:16:16 -06:00
285 changed files with 7715 additions and 4466 deletions

View File

@@ -29,7 +29,7 @@ jobs:
platforms: "linux/arm/v7,linux/amd64,linux/arm64"
- context: "machine-learning"
image: "immich-machine-learning"
platforms: "linux/amd64"
platforms: "linux/amd64,linux/arm64"
- context: "nginx"
image: "immich-proxy"
platforms: "linux/arm/v7,linux/amd64,linux/arm64"

View File

@@ -92,7 +92,7 @@ If you feel like this is the right cause and the app is something you are seeing
## Donation
- [Monthly donation](https://github.com/sponsors/alextran1502) via GitHub Sponsors
- [One-time donation](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via Github Sponsors
- [One-time donation](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via GitHub Sponsors
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX

View File

@@ -44,3 +44,5 @@ download:
locale_code: ru-RU
- file: mobile/assets/i18n/cs-CZ.json
locale_code: cs-CZ
- file: mobile/assets/i18n/no-NO.json
locale_code: no-NO

View File

@@ -1,15 +1,16 @@
FROM python:3.10
ENV TRANSFORMERS_CACHE=/cache
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=true
WORKDIR /usr/src/app
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir torch==1.13.1+cpu -f https://download.pytorch.org/whl/torch_stable.html
RUN pip install --pre torch -f https://download.pytorch.org/whl/nightly/cpu/torch_nightly.html
RUN pip install transformers tqdm numpy scikit-learn scipy nltk sentencepiece flask Pillow
RUN pip install --no-deps sentence-transformers

View File

@@ -45,11 +45,8 @@ def run_engine(engine, path):
for index, pred in enumerate(predictions):
tags = pred['label'].split(', ')
if (index == 0):
result = tags
else:
if (pred['score'] > 0.5):
result = [*result, *tags]
if (pred['score'] > 0.9):
result = [*result, *tags]
if (len(result) > 1):
result = list(set(result))

View File

@@ -76,10 +76,11 @@ sed -i "s/\"android\.injected\.version\.code\" => $CURRENT_MOBILE,/\"android\.in
sed -i "s/^version: $CURRENT_SERVER+$CURRENT_MOBILE$/version: $NEXT_SERVER+$NEXT_MOBILE/" mobile/pubspec.yaml
# OpenApi Generated Files
sed -i "s/\"version\": \"$CURRENT_SERVER\",$/\"version\": \"$NEXT_SERVER\",/" mobile/openapi/README.md
sed -i "s/\"document\": \"$CURRENT_SERVER\",$/\"document\": \"$NEXT_SERVER\",/" web/src/api/open-api/api.ts
sed -i "s/\"document\": \"$CURRENT_SERVER\",$/\"document\": \"$NEXT_SERVER\",/" web/src/api/open-api/base.ts
sed -i "s/\"document\": \"$CURRENT_SERVER\",$/\"document\": \"$NEXT_SERVER\",/" web/src/api/open-api/common.ts
sed -i "s/\"document\": \"$CURRENT_SERVER\",$/\"document\": \"$NEXT_SERVER\",/" web/src/api/open-api/configuration.ts
sed -i "s/\"document\": \"$CURRENT_SERVER\",$/\"document\": \"$NEXT_SERVER\",/" web/src/api/open-api/index.ts
sed -i "s/API version: $CURRENT_SERVER,$/API version: $NEXT_SERVER/" mobile/openapi/README.md
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/api.ts
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/base.ts
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/common.ts
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/configuration.ts
sed -i "s/OpenAPI document: $CURRENT_SERVER,$/OpenAPI document: $NEXT_SERVER/" web/src/api/open-api/index.ts
echo "IMMICH_VERSION=v$NEXT_SERVER" >>$GITHUB_ENV

View File

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

View File

@@ -0,0 +1,2 @@
* Fix no album thumbnail lead to no album selection shown and add global logs
* Fix remove asset from gallery view

View File

@@ -5,17 +5,17 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000284">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000219">
</testcase>
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="282.422814">
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="66.030339">
</testcase>
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="43.555992">
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="24.488297">
</testcase>

View File

@@ -1,6 +1,6 @@
{
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"add_to_album_bottom_sheet_added": "Přidáno do {album}",
"add_to_album_bottom_sheet_already_exists": "Již v {album}",
"album_info_card_backup_album_excluded": "VYLOUČENO",
"album_info_card_backup_album_included": "ZAHRNUTO",
"album_thumbnail_card_item": "1 položka",
@@ -14,10 +14,10 @@
"album_viewer_appbar_share_leave": "Opustit album",
"album_viewer_appbar_share_remove": "Odstranit z alba",
"album_viewer_page_share_add_users": "Přidat uživatele",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_settings_dynamic_layout_title": "Dynamické rozložení",
"asset_list_layout_settings_group_by": "Seskupit položky podle",
"asset_list_layout_settings_group_by_month": "Měsíc",
"asset_list_layout_settings_group_by_month_day": "Měsíc + den",
"asset_list_settings_subtitle": "Nastavení rozložení mřížky fotografií",
"asset_list_settings_title": "Fotografická mřížka",
"backup_album_selection_page_albums_device": "Alba v zařízení ({})",
@@ -27,21 +27,24 @@
"backup_album_selection_page_selection_info": "Informace o výběru",
"backup_album_selection_page_total_assets": "Celkový počet jedinečných souborů",
"backup_all": "Vše",
"backup_background_service_backup_failed_message": "Zálohování zdrojů selhalo. Zkouším to znovu...",
"backup_background_service_backup_failed_message": "Zálohování médií selhalo. Zkouším to znovu...",
"backup_background_service_connection_failed_message": "Nepodařilo se připojit k serveru. Zkouším to znovu...",
"backup_background_service_current_upload_notification": "Nahrávání {}",
"backup_background_service_default_notification": "Kontrola nových zdrojů {}",
"backup_background_service_default_notification": "Kontrola nových médií {}",
"backup_background_service_error_title": "Chyba zálohování",
"backup_background_service_in_progress_notification": "Vytvářím kopii vašich zdrojů...",
"backup_background_service_in_progress_notification": "Vytvářím kopii vašich médií...",
"backup_background_service_upload_failure_notification": "Nepodařilo se nahrát {}",
"backup_controller_page_albums": "Zálohovaná alba",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Ukaž mi jak",
"backup_controller_page_background_battery_info_message": "Chcete-li dosáhnout nejlepších výsledků při zálohování na pozadí, vypněte všechny optimalizace baterie, které omezují aktivitu na pozadí pro Immich ve vašem zařízení. Jelikož to závisí na zařízení, zkontrolujte požadované informace pro výrobce vašeho zařízení.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Optimalizace baterie",
"backup_controller_page_background_charging": "Pouze během nabíjení",
"backup_controller_page_background_configure_error": "Nepodařilo se nakonfigurovat službu na pozadí",
"backup_controller_page_background_delay": "Zpoždění zálohování nových zdrojů: {}",
"backup_controller_page_background_delay": "Zpoždění zálohování nových médií: {}",
"backup_controller_page_background_description": "Povolte službu na pozadí pro automatické zálohování všech nových aktiv bez nutnosti otevření aplikace",
"backup_controller_page_background_is_off": "Automatické zálohování na pozadí je vypnuto",
"backup_controller_page_background_is_on": "Automatické zálohování na pozadí je zapnuto",
@@ -89,21 +92,21 @@
"cache_settings_subtitle": "Ovládání chování mobilní aplikace Immich v mezipaměti",
"cache_settings_thumbnail_size": "Velikost vyrovnávací paměti náhledů (položek {})",
"cache_settings_title": "Nastavení vyrovnávací paměti",
"change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_shared": "Shared",
"change_password_form_confirm_password": "Potvrďte heslo",
"change_password_form_description": "Dobrý den, {firstName} {lastName},\n\nJe to buď poprvé, co se přihlašujete do systému, nebo byl podán požadavek na změnu hesla. Níže prosím zadejte nové heslo.",
"change_password_form_new_password": "Nové heslo",
"change_password_form_password_mismatch": "Hesla se neshodují",
"change_password_form_reenter_new_password": "Znovu zadejte nové heslo",
"common_add_to_album": "Přidat do alba",
"common_change_password": "Změnit heslo",
"common_create_new_album": "Vytvořit nové album",
"common_shared": "Sdílené",
"control_bottom_app_bar_add_to_album": "Přidat do alba",
"control_bottom_app_bar_album_info": "{} položky",
"control_bottom_app_bar_album_info_shared": "{} položky - sdílené",
"control_bottom_app_bar_create_new_album": "Vytvořit nové album",
"control_bottom_app_bar_delete": "Vymazat",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_favorite": "Oblíbené",
"control_bottom_app_bar_share": "Sdílet",
"create_album_page_untitled": "Bez názvu",
"create_shared_album_page_create": "Vytvořit",
@@ -126,13 +129,13 @@
"experimental_settings_title": "Experimentální",
"favorites_page_title": "Oblíbené",
"home_page_add_to_album_conflicts": "Přidány {added} položky do alba {album}. {failed} položky jsou již v albu.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_err_local": "Zatím není možné přidat lokální média do alb, přeskakuje se",
"home_page_add_to_album_success": "Přidány položky {added} do alba {album}.",
"home_page_building_timeline": "Vytváraní časové osy",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_local": "Zatím není možné zařadit lokální média mezi oblíbená, přeskakuje se",
"home_page_first_time_notice": "Pokud aplikaci používáte poprvé, nezapomeňte si vybrat zálohovaná alba, aby se na časové ose mohly nacházet fotografie a videa z vybraných albech.",
"image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_download_error": "Chyba stahování",
"image_viewer_page_state_provider_download_success": "Stahování bylo úspěšné",
"library_page_albums": "Alba",
"library_page_favorites": "Oblíbené",
"library_page_new_album": "Nové album",
@@ -156,12 +159,12 @@
"login_form_password_hint": "heslo",
"login_form_save_login": "Zůstat přihlášen",
"monthly_title_text_date_format": "LLLL y",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"notification_permission_dialog_cancel": "Zrušit",
"notification_permission_dialog_content": "Chcete-li povolit oznámení, přejděte do Nastavení a vyberte možnost Povolit.",
"notification_permission_dialog_settings": "Nastavení",
"notification_permission_list_tile_content": "Udělte oprávnění k aktivaci oznámení.",
"notification_permission_list_tile_enable_button": "Povolit oznámení",
"notification_permission_list_tile_title": "Povolení oznámení",
"profile_drawer_app_logs": "Logy",
"profile_drawer_client_server_up_to_date": "Klient a server jsou aktuální",
"profile_drawer_settings": "Nastavení",
@@ -175,8 +178,8 @@
"select_additional_user_for_sharing_page_suggestions": "Návrhy",
"select_user_for_sharing_page_err_album": "Nepodařilo se vytvořit album",
"select_user_for_sharing_page_share_suggestions": "Návrhy",
"server_info_box_app_version": "App Version",
"server_info_box_server_version": "Server Version",
"server_info_box_app_version": "Verze aplikace",
"server_info_box_server_version": "Verze serveru",
"setting_image_viewer_help": "V prohlížeči detailů se nejprve načte malá miniatura, poté se načte náhled střední velikosti (je-li povolen) a nakonec se načte originál (je-li povolen).",
"setting_image_viewer_original_subtitle": "Umožňuje načíst původní obrázek v plném rozlišení (velký!). Zakázat pro snížení používání dat (v síti iv mezipaměti zařízení).",
"setting_image_viewer_original_title": "Načíst původní obrázek",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Tager backup af dine elementer...",
"backup_background_service_upload_failure_notification": "Failed to upload {}",
"backup_controller_page_albums": "Sikkerhedskopiér albummer",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Vis mig hvordan",
"backup_controller_page_background_battery_info_message": "For den bedste oplevelse med baggrundsbackup, bør du slå batterioptimering, der begrænder baggrundsaktivitet, fra.\n\nSiden dette er afhængigt af enheden, bør du undersøge denne information leveret af din enheds producent.",
"backup_controller_page_background_battery_info_ok": "OK",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Backing up your assets…",
"backup_background_service_upload_failure_notification": "Failed to upload {}",
"backup_controller_page_albums": "Gesicherte Alben",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"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",

View File

@@ -35,6 +35,9 @@
"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_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"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",
@@ -227,7 +230,13 @@
"version_announcement_overlay_text_2": "please take your time to visit the ",
"version_announcement_overlay_text_3": " and ensure your docker-compose and .env setup is up-to-date to prevent any misconfigurations, especially if you use WatchTower or any mechanism that handles updating your server application automatically.",
"version_announcement_overlay_title": "New Server Version Available \uD83C\uDF89",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings"
"permission_onboarding_request": "Immich requires permission to view your photos and videos.",
"permission_onboarding_grant_permission": "Grant permission",
"permission_onboarding_permission_granted": "Permission granted! You are all set.",
"permission_onboarding_permission_denied": "Permission denied. To use Immich, grant photo and video permissions in Settings.",
"permission_onboarding_get_started": "Get started",
"permission_onboarding_go_to_settings": "Go to settings",
"permission_onboarding_permission_limited": "Permission limited. To let Immich backup and manage your entire gallery collection, grant photo and video permissions in Settings.",
"permission_onboarding_continue_anyway": "Continue anyway",
"permission_onboarding_log_out": "Log out"
}

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Backing up your assets…",
"backup_background_service_upload_failure_notification": "Failed to upload {}",
"backup_controller_page_albums": "Álbumes de copia de seguridad",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"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",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Varmuuskopioidaan kohteita...",
"backup_background_service_upload_failure_notification": "Lähetys palvelimelle epäonnistui {}",
"backup_controller_page_albums": "Varmuuskopioi albumit",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Näytä minulle miten",
"backup_controller_page_background_battery_info_message": "Kytke pois päältä kaikki Immichin taustatyöskentelyyn liittyvät akun optimoinnit, jotta varmistat taustavarmuuskopioinnin parhaan mahdollisen toiminnan.\n\nKoska tämä on laitekohtaista, tarkista tarvittavat toimet laitevalmistajan ohjeista.",
"backup_controller_page_background_battery_info_ok": "OK",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Sauvegarde de vos éléments...",
"backup_background_service_upload_failure_notification": "Impossible de transférer {}",
"backup_controller_page_albums": "Sauvegarder les albums",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Montrez-moi comment",
"backup_controller_page_background_battery_info_message": "Pour une expérience optimale de la sauvegarde en arrière-plan, veuillez désactiver toute optimisation de la batterie limitant l'activité en arrière-plan pour Immich.\n\nÉtant donné que cela est spécifique à chaque appareil, veuillez consulter les informations requises pour le fabricant de votre appareil.",
"backup_controller_page_background_battery_info_ok": "OK",

View File

@@ -35,6 +35,9 @@
"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_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Mostrami come",
"backup_controller_page_background_battery_info_message": "Per una migliore esperienza di backup, disabilita le ottimizzazioni della batteria per l'app Immich.\n\nDal momento che è una funzionalità specifica del dispositivo, per favore consulta il manuale del produttore.",
"backup_controller_page_background_battery_info_ok": "OK",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "バックアップ中",
"backup_background_service_upload_failure_notification": "{} のアップロードに失敗",
"backup_controller_page_albums": "アルバム",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "方法を見る",
"backup_controller_page_background_battery_info_message": "バックグラウンドバックアップが正常に動作するためにImmichに適用されてるバッテリーの最適化と自動調整をオフにしてね。\n\n端末によって方法が変わるから各々調べてね",
"backup_controller_page_background_battery_info_ok": "了解",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "미디어파일 백업 중...",
"backup_background_service_upload_failure_notification": "{} 업로드 실패",
"backup_controller_page_albums": "백업대상",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "사용 가이드",
"backup_controller_page_background_battery_info_message": "최상의 백업 환경을 위해 Immich 앱의 백그라운드 활동을 제한하는 배터리 최적화기능을 꺼주세요.\n\n휴대폰마다 설정방법이 다르므로 제조업체별로 설정방법을 확인하세요.",
"backup_controller_page_background_battery_info_ok": "확인",
@@ -226,5 +229,5 @@
"version_announcement_overlay_text_1": "안녕하세요!",
"version_announcement_overlay_text_2": "앱에 새로운 업데이트가 있습니다!",
"version_announcement_overlay_text_3": "특히 WatchTower 또는 서버 응용 프로그램 자동 업데이트를 처리하는 메커니즘을 사용하는 경우 잘못된 구성을 방지하기 위해 docker-compose 및 .env 설정이 최신 상태인지 확인하세요.",
"version_announcement_overlay_title": "새 서버 버전 사용 가능 🎉"
"version_announcement_overlay_title": "새 서버 버전 사용 가능 \uD83C\uDF89"
}

View File

@@ -1,6 +1,6 @@
{
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"add_to_album_bottom_sheet_added": "Toegevoegd aan {album}",
"add_to_album_bottom_sheet_already_exists": "Staat al in {album}",
"album_info_card_backup_album_excluded": "UITGESLOTEN",
"album_info_card_backup_album_included": "INGESLOTEN",
"album_thumbnail_card_item": "1 item",
@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Back-up maken van items…",
"backup_background_service_upload_failure_notification": "Fout bij upload {}",
"backup_controller_page_albums": "Back-up albums",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Toon me hoe",
"backup_controller_page_background_battery_info_message": "Schakel voor de beste back-up ervaring op de achtergrond alle batterij optimalisaties uit, die de achtergrondactiviteit van Immich beperkt.\n\nAangezien dit apparaatspecifiek is, zoek de vereiste informatie op voor de fabrikant van je apparaat.",
"backup_controller_page_background_battery_info_ok": "OK",
@@ -89,21 +92,21 @@
"cache_settings_subtitle": "Beheer het cachegedrag van de Immich app",
"cache_settings_thumbnail_size": "Thumbnail cachegrootte ({} items)",
"cache_settings_title": "Cache instellingen",
"change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_shared": "Shared",
"change_password_form_confirm_password": "Bevestig wachtwoord",
"change_password_form_description": "Hallo {firstName} {lastName},\n\nDit is ofwel de eerste keer dat je inlogd of er is een verzoek gedaan om je wachtwoord te wijzigen. Vul hieronder een nieuw wachtwoord in.",
"change_password_form_new_password": "Nieuw wachtwoord",
"change_password_form_password_mismatch": "Wachtwoorden komen niet overeen",
"change_password_form_reenter_new_password": "Vul het wachtwoord opnieuw in",
"common_add_to_album": "Toevoegen aan album",
"common_change_password": "Wachtwoord wijzigen",
"common_create_new_album": "Maak nieuw album",
"common_shared": "Gedeeld",
"control_bottom_app_bar_add_to_album": "Toevoegen aan album",
"control_bottom_app_bar_album_info": "{} items",
"control_bottom_app_bar_album_info_shared": "{} items · Gedeeld",
"control_bottom_app_bar_create_new_album": "Maak nieuw album",
"control_bottom_app_bar_delete": "Verwijderen",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_favorite": "Favoriet",
"control_bottom_app_bar_share": "Delen",
"create_album_page_untitled": "Naamloos",
"create_shared_album_page_create": "Aanmaken",
@@ -126,13 +129,13 @@
"experimental_settings_title": "Experimenteel",
"favorites_page_title": "Favorieten",
"home_page_add_to_album_conflicts": "{added} items toegevoegd aan album {album}. {failed} items staan al in het album.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_err_local": "Lokale items kunnen nog niet aan albums worden toegevoegd, overslaan",
"home_page_add_to_album_success": "{added} items toegevoegd aan album {album}.",
"home_page_building_timeline": "Tijdlijn opbouwen",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_local": "Lokale items kunnen nog niet als favoriet worden aangemerkt, overslaan",
"home_page_first_time_notice": "Als dit de eerste keer is dat je de app gebruikt, zorg er dan voor dat je een back-up album kiest, zodat de tijdlijn gevuld kan worden met foto's en video's uit het album.",
"image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_download_error": "Download mislukt",
"image_viewer_page_state_provider_download_success": "Download succesvol",
"library_page_albums": "Albums",
"library_page_favorites": "Favorieten",
"library_page_new_album": "Nieuw album",
@@ -156,12 +159,12 @@
"login_form_password_hint": "wachtwoord",
"login_form_save_login": "Ingelogd blijven",
"monthly_title_text_date_format": "MMMM y",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"notification_permission_dialog_cancel": "Annuleren",
"notification_permission_dialog_content": "Om meldingen in te schakelen, ga naar Instellingen en selecteer toestaan.",
"notification_permission_dialog_settings": "Instellingen",
"notification_permission_list_tile_content": "Geef toestemming om meldingen in te schakelen.",
"notification_permission_list_tile_enable_button": "Meldingen inschakelen",
"notification_permission_list_tile_title": "Toestemming meldingen",
"profile_drawer_app_logs": "Logboek",
"profile_drawer_client_server_up_to_date": "App en server zijn up-to-date",
"profile_drawer_settings": "Instellingen",
@@ -175,8 +178,8 @@
"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",
"server_info_box_app_version": "App Version",
"server_info_box_server_version": "Server Version",
"server_info_box_app_version": "App versie",
"server_info_box_server_version": "Server versie",
"setting_image_viewer_help": "De gedetailleerde weergave laadt eerst de kleine thumbnail, vervolgens het middelgrote voorbeeld (indien ingeschakeld) en ten slotte het origineel (indien ingeschakeld).",
"setting_image_viewer_original_subtitle": "Inschakelen om de originele afbeelding met volledige resolutie (groot!) te laden. Uitschakelen om datagebruik te verminderen (zowel netwerk- als apparaatcache).",
"setting_image_viewer_original_title": "Originele afbeelding laden",

View File

@@ -0,0 +1,233 @@
{
"add_to_album_bottom_sheet_added": "Lagt til i {album}",
"add_to_album_bottom_sheet_already_exists": "Allerede i {album}",
"album_info_card_backup_album_excluded": "EKSKLUDERT",
"album_info_card_backup_album_included": "INKLUDERT",
"album_thumbnail_card_item": "1 objekt",
"album_thumbnail_card_items": "{} objekter",
"album_thumbnail_card_shared": "Delt",
"album_viewer_appbar_share_delete": "Slett album",
"album_viewer_appbar_share_err_delete": "Feilet ved sletting av album",
"album_viewer_appbar_share_err_leave": "Kunne ikke forlate albumet",
"album_viewer_appbar_share_err_remove": "Det oppstod ett problem ved fjerning av objekter fra albumet",
"album_viewer_appbar_share_err_title": "Feilet ved endring av albumtittel",
"album_viewer_appbar_share_leave": "Forlat album",
"album_viewer_appbar_share_remove": "Fjern fra album",
"album_viewer_page_share_add_users": "Legg til brukere",
"asset_list_layout_settings_dynamic_layout_title": "Dynamisk layout",
"asset_list_layout_settings_group_by": "Grupper bilder etter",
"asset_list_layout_settings_group_by_month": "Måned",
"asset_list_layout_settings_group_by_month_day": "Måned + dag",
"asset_list_settings_subtitle": "Innstillinger for layout for fotorutenett",
"asset_list_settings_title": "Fotorutenett",
"backup_album_selection_page_albums_device": "Albumer på enhet ({})",
"backup_album_selection_page_albums_tap": "Trykk for å inkludere, dobbelttrykk for å ekskludere",
"backup_album_selection_page_assets_scatter": "Objekter kan eksistere i flere album. Men album kan kun bli inkludert eller ekskludert under backup-prosessen.",
"backup_album_selection_page_select_albums": "Velg album",
"backup_album_selection_page_selection_info": "Valginfo",
"backup_album_selection_page_total_assets": "Totalt unike objekter",
"backup_all": "Alle",
"backup_background_service_backup_failed_message": "Feilet ved backup av objekter. Prøver på nytt...",
"backup_background_service_connection_failed_message": "Feilet ved tilkobling til server. Prøver på nytt...",
"backup_background_service_current_upload_notification": "Laster opp {}",
"backup_background_service_default_notification": "Sjekker for nye objekter...",
"backup_background_service_error_title": "Backup feil",
"backup_background_service_in_progress_notification": "Foretar backup av objekter...",
"backup_background_service_upload_failure_notification": "Filet under opplasting {}",
"backup_controller_page_albums": "Sikkerhetskopier albumer",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Hvis meg hvordan",
"backup_controller_page_background_battery_info_message": "For den beste bakgrunnsbackup opplevelsen, deaktiver enhver batterioptimalisering som begrenser bakgrunnsaktiviteten til Immich.\n\nSiden dette er en enhets-spesifik justering, se innstillinger for den aktuelle enhet.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Batterioptimalisering",
"backup_controller_page_background_charging": "Kun ved lading",
"backup_controller_page_background_configure_error": "Feilet under konfigurering av bakgrunnstjenesten",
"backup_controller_page_background_delay": "Forsink backup av nye objekter: {}",
"backup_controller_page_background_description": "Skru på bakgrunnstjenesten for å automatisk ta backup av alle nye objekter uten å måtte åpne appen",
"backup_controller_page_background_is_off": "Automatisk bakgrunnsbackup er deaktivert",
"backup_controller_page_background_is_on": "Automatisk bakgrunnsbackup er aktivert",
"backup_controller_page_background_turn_off": "Skru av bakgrunnstjenesten",
"backup_controller_page_background_turn_on": "Skru på bakgrunnstjenesten",
"backup_controller_page_background_wifi": "Kun på WiFi",
"backup_controller_page_backup": "Sikkerhetskopier",
"backup_controller_page_backup_selected": "Valgte:",
"backup_controller_page_backup_sub": "Opplastede bilder og videoer",
"backup_controller_page_cancel": "Avbryt",
"backup_controller_page_created": "Opprettet den: {}",
"backup_controller_page_desc_backup": "Slå på sikkerhetskopiering i forgrunnen for automatisk å laste opp nye objekter til serveren når du åpner appen.",
"backup_controller_page_excluded": "Ekskludert:",
"backup_controller_page_failed": "Mislyktes ({})",
"backup_controller_page_filename": "Filnavn: {} [{}]",
"backup_controller_page_id": "ID: {}",
"backup_controller_page_info": "Sikkerhetskopi informasjon",
"backup_controller_page_none_selected": "Ingen valgt",
"backup_controller_page_remainder": "Gjenstår",
"backup_controller_page_remainder_sub": "Gjenstående bilder og videoer å laste opp fra utvalg",
"backup_controller_page_select": "Velg",
"backup_controller_page_server_storage": "Serverlagring",
"backup_controller_page_start_backup": "Start backup",
"backup_controller_page_status_off": "Automatisk sikkerhetskopiering i forgrunnen er av",
"backup_controller_page_status_on": "Automatisk sikkerhetskopiering i forgrunnen er på",
"backup_controller_page_storage_format": "{} av {} brukt",
"backup_controller_page_to_backup": "Albumer som skal sikkerhetskopieres",
"backup_controller_page_total": "Totalt",
"backup_controller_page_total_sub": "Alle unike bilder og videoer fra valgte album",
"backup_controller_page_turn_off": "Slå av sikkerhetskopiering i forgrunnen",
"backup_controller_page_turn_on": "Slå på sikkerhetskopiering i forgrunnen",
"backup_controller_page_uploading_file_info": "Filinformasjon på opplastende fil",
"backup_err_only_album": "Kan ikke fjerne det eneste albumet",
"backup_info_card_assets": "objekter",
"cache_settings_album_thumbnails": "Bibliotekside miniatyrbilder ({} objekter)",
"cache_settings_clear_cache_button": "Tøm buffer",
"cache_settings_clear_cache_button_title": "Tømmer app'ens buffer. Dette vil ha betydelig innvirkning på appens ytelse inntil buffer er gjenoppbygd.",
"cache_settings_image_cache_size": "Bilde bufferstørrelse ({} objekter)",
"cache_settings_statistics_album": "Bibliotek miniatyrbilder",
"cache_settings_statistics_assets": "{} objekter ({})",
"cache_settings_statistics_full": "Originalbilder",
"cache_settings_statistics_shared": "Delte album miniatyrbilder",
"cache_settings_statistics_thumbnail": "Miniatyrbilder",
"cache_settings_statistics_title": "Bufferbruk",
"cache_settings_subtitle": "Kontroller bufringsadferden til Immich-mobilapplikasjonen",
"cache_settings_thumbnail_size": "MIniatyrbilder bufferstørrelse ({} objekter)",
"cache_settings_title": "Bufringsinnstillinger",
"change_password_form_confirm_password": "Bekreft passord",
"change_password_form_description": "Hei {firstName} {lastName}!\n\nDette er enten første gang du logger på systemet, eller det er sendt en forespørsel om å endre passordet ditt. Vennligst skriv inn det nye passordet nedenfor.",
"change_password_form_new_password": "Nytt passord",
"change_password_form_password_mismatch": "Passordene stemmer ikke",
"change_password_form_reenter_new_password": "Skriv nytt passord igjen",
"common_add_to_album": "Legg til i album",
"common_change_password": "Endre passord",
"common_create_new_album": "Lag nytt album",
"common_shared": "Delt",
"control_bottom_app_bar_add_to_album": "Legg til i album",
"control_bottom_app_bar_album_info": "{} objekter",
"control_bottom_app_bar_album_info_shared": "{} objekter · Delt",
"control_bottom_app_bar_create_new_album": "Lag nytt album",
"control_bottom_app_bar_delete": "Slett",
"control_bottom_app_bar_favorite": "Favoritt",
"control_bottom_app_bar_share": "Del",
"create_album_page_untitled": "Uten navn",
"create_shared_album_page_create": "Opprett",
"create_shared_album_page_share": "Del",
"create_shared_album_page_share_add_assets": "LEGG TIL OBJEKTER",
"create_shared_album_page_share_select_photos": "Velg bilder",
"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": "Disse objektene vil bli slettet permanent fra Immich og fra enheten",
"delete_dialog_cancel": "Avbryt",
"delete_dialog_ok": "Slett",
"delete_dialog_title": "Slett permanent",
"exif_bottom_sheet_description": "Legg til beskrivelse...",
"exif_bottom_sheet_details": "DETALJER",
"exif_bottom_sheet_location": "PLASSERING",
"experimental_settings_new_asset_list_subtitle": "Under utvikling",
"experimental_settings_new_asset_list_title": "Aktiver eksperimentell grid-visning",
"experimental_settings_subtitle": "Bruk på egen risiko!",
"experimental_settings_title": "Eksperimentelt",
"favorites_page_title": "Favoritter",
"home_page_add_to_album_conflicts": "Lagt til {added} objekter til album {album}. {failed} objekter er allerede i albumet.",
"home_page_add_to_album_err_local": "Kan ikke legge til lokale objekter til album enda, hopper over",
"home_page_add_to_album_success": "Lagt til {added} objekter til album {album}.",
"home_page_building_timeline": "Genererer tidslinjen",
"home_page_favorite_err_local": "Kan ikke sette favoritt på lokale objekter enda, hopper over",
"home_page_first_time_notice": "Hvis dette er første gangen du benytter appen, så velg ett album(eller flere) slik at tidslinjen kan genereres med dine bilder og videoer.",
"image_viewer_page_state_provider_download_error": "Nedlasting feilet",
"image_viewer_page_state_provider_download_success": "Nedlasting vellykket",
"library_page_albums": "Albumer",
"library_page_favorites": "Favoritter",
"library_page_new_album": "Nytt album",
"library_page_sharing": "Deling",
"library_page_sort_created": "Nylig opplastet",
"library_page_sort_title": "Album tittel",
"login_form_button_text": "Logg inn",
"login_form_email_hint": "dinepost@epost.no",
"login_form_endpoint_hint": "http://din-server-ip:port/api",
"login_form_endpoint_url": "Serverendepunkt-URL",
"login_form_err_http": "Vennligst spesifiser http:// eller https://",
"login_form_err_invalid_email": "Ugyldig Epostadresse",
"login_form_err_invalid_url": "Ugyldig URL",
"login_form_err_leading_whitespace": "Ledende mellomrom",
"login_form_err_trailing_whitespace": "Etterfølgende mellomrom",
"login_form_failed_get_oauth_server_config": "Feil innlogging ved bruk av OAuth, sjekk server URL",
"login_form_failed_get_oauth_server_disable": "OAuth innlogging er ikke tilgjengelig på denne serveren",
"login_form_failed_login": "Feil ved innlogging, sjekk server URL, epost og passord",
"login_form_label_email": "Epostadresse",
"login_form_label_password": "Passord",
"login_form_password_hint": "passord",
"login_form_save_login": "Forbli innlogget",
"monthly_title_text_date_format": "MMMM y",
"notification_permission_dialog_cancel": "Avbryt",
"notification_permission_dialog_content": "For å aktivere notifikasjoner, gå til Innstillinger og velg tillat.",
"notification_permission_dialog_settings": "Innstillinger",
"notification_permission_list_tile_content": "Tillat tilgang for å aktivere notifikasjoner",
"notification_permission_list_tile_enable_button": "Aktiver notifikasjoner",
"notification_permission_list_tile_title": "Notifikasjonstilgang",
"profile_drawer_app_logs": "Logg",
"profile_drawer_client_server_up_to_date": "Klient og Server er oppdatert",
"profile_drawer_settings": "Innstillinger",
"profile_drawer_sign_out": "Logg ut",
"search_bar_hint": "Søk i dine bilder",
"search_page_no_objects": "Ingen objektinfo tilgjengelig",
"search_page_no_places": "Ingen plasseringsinfo tilgjengelig",
"search_page_places": "Plasser",
"search_page_things": "Ting",
"search_result_page_new_search_hint": "Nytt søk",
"select_additional_user_for_sharing_page_suggestions": "Forslag",
"select_user_for_sharing_page_err_album": "Feilet ved oppretting av album",
"select_user_for_sharing_page_share_suggestions": "Forslag",
"server_info_box_app_version": "App versjon",
"server_info_box_server_version": "Server versjon",
"setting_image_viewer_help": "Først lastes mikrobilder, deretter middels-oppløsningbildet (hvis aktivert), til slutt lastes original (hvis aktivert).",
"setting_image_viewer_original_subtitle": "Aktiver for å laste originalbildet i full oppløsning (Stort!). Deaktiver for å spare databruk (både nettverksbruk og bufferdata på enheten).",
"setting_image_viewer_original_title": "Last originalbildet",
"setting_image_viewer_preview_subtitle": "Aktiver for å laste ett bilde av middels-oppløsning. Deaktiver for å enten direkte laste inn originalen eller kun benytte miniatyrbilde.",
"setting_image_viewer_preview_title": "Last forhåndsvisningsbilde",
"setting_notifications_notify_failures_grace_period": "Varsle om sikkerhetskopieringsfeil i bakgrunnen: {}",
"setting_notifications_notify_hours": "{} timer",
"setting_notifications_notify_immediately": "umiddelbart",
"setting_notifications_notify_minutes": "{} minutter",
"setting_notifications_notify_never": "aldri",
"setting_notifications_notify_seconds": "{} sekunder",
"setting_notifications_single_progress_subtitle": "Detaljert opplastingsinformasjon pr objekt",
"setting_notifications_single_progress_title": "Vis detaljert status på bakgrunnsbackup",
"setting_notifications_subtitle": "Juster notifikasjonsinnstillinger",
"setting_notifications_title": "Notifikasjoner",
"setting_notifications_total_progress_subtitle": "Total opplastingsstatus (fullført/totalt objekter)",
"setting_notifications_total_progress_title": "Vis status på bakgrunnsbackup",
"setting_pages_app_bar_settings": "Innstillinger",
"settings_require_restart": "Vennligst restart Immich for å aktivere denne innstillingen",
"share_add": "Legg til",
"share_add_photos": "Legg til bilder",
"share_add_title": "Legg til tittel",
"share_create_album": "Opprett album",
"share_dialog_preparing": "Forbereder...",
"share_invite": "Inviter til album",
"sharing_page_album": "Delte album",
"sharing_page_description": "Lag delte album for å dele bilder og videoer med folk i nettverket ditt.",
"sharing_page_empty_list": "TOM LISTE",
"sharing_silver_appbar_create_shared_album": "Lag delt album",
"sharing_silver_appbar_share_partner": "Del med partner",
"tab_controller_nav_library": "Bibliotek",
"tab_controller_nav_photos": "Bilder",
"tab_controller_nav_search": "Søk",
"tab_controller_nav_sharing": "Deling",
"theme_setting_asset_list_storage_indicator_title": "Vis lagringsindiaktor på objektfliser",
"theme_setting_asset_list_tiles_per_row_title": "Antall ressurser per rad ({})",
"theme_setting_dark_mode_switch": "Mørk modus",
"theme_setting_image_viewer_quality_subtitle": "Juster kvaliteten på detaljer med bildeviser",
"theme_setting_image_viewer_quality_title": "Bilderviser-kvalitet",
"theme_setting_system_theme_switch": "Automatisk (følg system)",
"theme_setting_theme_subtitle": "Velg app'ens temainnstilling",
"theme_setting_theme_title": "Tema",
"theme_setting_three_stage_loading_subtitle": "Tre-trinns lasting kan øke lasteytelsen, men forårsaker betydelig høyere nettverksbelastning",
"theme_setting_three_stage_loading_title": "Aktiver 3-stegs innlasting",
"version_announcement_overlay_ack": "Bekreft",
"version_announcement_overlay_release_notes": "Endringslogg",
"version_announcement_overlay_text_1": "Hei, det er en ny versjon av",
"version_announcement_overlay_text_2": "vennligst ta deg tid til å besøke",
"version_announcement_overlay_text_3": "og verifiser at din docker-compose og .env oppsett er oppdatert for å forhindre en eventuell miskonfigurasjon. Spesielt hvis du benytter WatchTower eller en annen tjeneste som håndterer oppdatering av applikasjoner på serveren automatisk.",
"version_announcement_overlay_title": "Ny serverversjon tilgjengelig"
}

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Tworzę kopię twoich zasobów...",
"backup_background_service_upload_failure_notification": "Nie udało się przesłać {}",
"backup_controller_page_albums": "Backup Albumów",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Pokaż mi jak",
"backup_controller_page_background_battery_info_message": "Aby uzyskać najlepsze rezultaty podczas tworzenia kopii zapasowej w tle, należy wyłączyć wszelkie optymalizacje baterii ograniczające aktywność w tle dla Immich w urządzeniu.\n\nPonieważ jest to zależne od urządzenia, proszę sprawdzić wymagane informacje dla producenta urządzenia.",
"backup_controller_page_background_battery_info_ok": "OK",

View File

@@ -1,6 +1,6 @@
{
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"add_to_album_bottom_sheet_added": "Добавлено в {album}",
"add_to_album_bottom_sheet_already_exists": "Уже в {album}",
"album_info_card_backup_album_excluded": "ИСКЛЮЧЕН",
"album_info_card_backup_album_included": "ВКЛЮЧЕН",
"album_thumbnail_card_item": "1 объект",
@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Резервное копирование ваших объектов…",
"backup_background_service_upload_failure_notification": "Ошибка загрузки {}",
"backup_controller_page_albums": "Резервное копирование альбомов",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Показать как",
"backup_controller_page_background_battery_info_message": "Для наилучшего фонового резервного копирования отключите любые настройки оптимизации батареи, ограничивающие фоновую активность для Immich.\n\nПоскольку это зависит от устройства, найдите необходимую информацию для производителя вашего устройства.",
"backup_controller_page_background_battery_info_ok": "ОК",
@@ -89,21 +92,21 @@
"cache_settings_subtitle": "Управление кэшированием мобильного приложения Immich",
"cache_settings_thumbnail_size": "Размер кэша эскизов ({} объектов)",
"cache_settings_title": "Настройки кэширования",
"change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_shared": "Shared",
"change_password_form_confirm_password": "Подтвердите пароль",
"change_password_form_description": "Привет {firstName} {lastName},\n\nЭто либо ваш первый вход в систему, либо был сделан запрос на смену пароля. Пожалуйста, введите новый пароль ниже.",
"change_password_form_new_password": "Новый пароль",
"change_password_form_password_mismatch": "Пароли не совпадают",
"change_password_form_reenter_new_password": "Повторно введите новый пароль",
"common_add_to_album": "Добавить в альбом",
"common_change_password": "Изменить пароль",
"common_create_new_album": "Создать новый альбом",
"common_shared": "Общие",
"control_bottom_app_bar_add_to_album": "Добавить в альбом",
"control_bottom_app_bar_album_info": "{} файлов",
"control_bottom_app_bar_album_info_shared": "{} файлов · Общий",
"control_bottom_app_bar_create_new_album": "\nСоздать новый альбом",
"control_bottom_app_bar_delete": "Удалить",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_favorite": "Избранное",
"control_bottom_app_bar_share": "Поделиться",
"create_album_page_untitled": "Без названия",
"create_shared_album_page_create": "Создать",
@@ -126,13 +129,13 @@
"experimental_settings_title": "Экспериментальные функции",
"favorites_page_title": "Избранное",
"home_page_add_to_album_conflicts": "Добавлено {added} объектов в альбом {album}. Объекты {failed} уже есть в альбоме.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_err_local": "Пока нельзя добавлять локальные объекты в альбомы, пропускаем",
"home_page_add_to_album_success": "Добавлено {added} объектов в альбом {album}.",
"home_page_building_timeline": "Построение временной шкалы",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_favorite_err_local": "Пока не удается добавить в избранное локальные объекты, пропускаем",
"home_page_first_time_notice": "Если вы используете приложение впервые, убедитесь, что вы выбрали резервный(е) альбом(ы), чтобы временная шкала могла заполнить фотографии и видео в альбоме(ах).",
"image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_success": "Download Success",
"image_viewer_page_state_provider_download_error": "Ошибка загрузки",
"image_viewer_page_state_provider_download_success": "Успешно загружено",
"library_page_albums": "Альбомы",
"library_page_favorites": "Избранное",
"library_page_new_album": "Новый альбом",
@@ -156,12 +159,12 @@
"login_form_password_hint": "пароль",
"login_form_save_login": "Оставаться в системе",
"monthly_title_text_date_format": "MMMM y",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"notification_permission_dialog_cancel": "Отмена",
"notification_permission_dialog_content": "Чтобы включить уведомления, перейдите в «Настройки» и выберите «Разрешить».",
"notification_permission_dialog_settings": "Настройки",
"notification_permission_list_tile_content": "Предоставьте разрешение на включение уведомлений",
"notification_permission_list_tile_enable_button": "Включить уведомления",
"notification_permission_list_tile_title": "Разрешение на уведомление",
"profile_drawer_app_logs": "Журналы",
"profile_drawer_client_server_up_to_date": "Клиент и сервер обновлены",
"profile_drawer_settings": "Настройки",
@@ -175,8 +178,8 @@
"select_additional_user_for_sharing_page_suggestions": "Предложения",
"select_user_for_sharing_page_err_album": "\nНе удалось создать альбом",
"select_user_for_sharing_page_share_suggestions": "Предложения",
"server_info_box_app_version": "App Version",
"server_info_box_server_version": "Server Version",
"server_info_box_app_version": "Версия приложения",
"server_info_box_server_version": "Версия сервера",
"setting_image_viewer_help": "Средство просмотра деталей сначала загружает маленькую миниатюру, затем загружает предварительный просмотр среднего размера (если включено) и, наконец, загружает оригинал (если включено).",
"setting_image_viewer_original_subtitle": "Включите загрузку оригинального изображения в полном разрешении (большое!). Отключите, чтобы уменьшить объем данных (как в сети, так и в кеше устройства).",
"setting_image_viewer_original_title": "Загрузить исходное изображение",

View File

@@ -1,6 +1,6 @@
{
"add_to_album_bottom_sheet_added": "Added to {album}",
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
"add_to_album_bottom_sheet_added": "Pridané do {album}",
"add_to_album_bottom_sheet_already_exists": "Už v {album}",
"album_info_card_backup_album_excluded": "VYLÚČENÉ",
"album_info_card_backup_album_included": "ZAHRNUTÉ",
"album_thumbnail_card_item": "1 položka",
@@ -14,10 +14,10 @@
"album_viewer_appbar_share_leave": "Opustiť album",
"album_viewer_appbar_share_remove": "Odstrániť z albumu",
"album_viewer_page_share_add_users": "Pridať používateľov",
"asset_list_layout_settings_dynamic_layout_title": "Dynamic layout",
"asset_list_layout_settings_group_by": "Group assets by",
"asset_list_layout_settings_group_by_month": "Month",
"asset_list_layout_settings_group_by_month_day": "Month + day",
"asset_list_layout_settings_dynamic_layout_title": "Dynamické rozloženie",
"asset_list_layout_settings_group_by": "Zoskupiť položky podľa",
"asset_list_layout_settings_group_by_month": "Mesiac",
"asset_list_layout_settings_group_by_month_day": "Mesiac + d",
"asset_list_settings_subtitle": "Nastavenia rozloženia mriežky fotografií",
"asset_list_settings_title": "Fotografická mriežka",
"backup_album_selection_page_albums_device": "Albumy v zariadení ({})",
@@ -27,21 +27,24 @@
"backup_album_selection_page_selection_info": "Informácie o výbere",
"backup_album_selection_page_total_assets": "Celkový počet jedinečných súborov",
"backup_all": "Všetko",
"backup_background_service_backup_failed_message": "Zálohovanie zdrojov zlyhalo. Skúšam to znova...",
"backup_background_service_backup_failed_message": "Zálohovanie médií zlyhalo. Skúšam to znova...",
"backup_background_service_connection_failed_message": "Nepodarilo sa pripojiť k serveru. Skúšam to znova...",
"backup_background_service_current_upload_notification": "Nahrávanie {}",
"backup_background_service_default_notification": "Kontrola nových zdrojov {}",
"backup_background_service_default_notification": "Kontrola nových médií {}",
"backup_background_service_error_title": "Chyba zálohovania",
"backup_background_service_in_progress_notification": "Vytváram kópiu vašich zdrojov...",
"backup_background_service_in_progress_notification": "Vytváram kópiu vašich médií...",
"backup_background_service_upload_failure_notification": "Nepodarilo sa nahrať {}",
"backup_controller_page_albums": "Zálohované albumy",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Ukáž mi ako",
"backup_controller_page_background_battery_info_message": "Ak chcete dosiahnuť najlepšie výsledky pri zálohovaní na pozadí, vypnite všetky optimalizácie batérie, ktoré obmedzujú aktivitu na pozadí pre Immich vo vašom zariadení. Keďže to závisí od zariadenia, skontrolujte požadované informácie pre výrobcu vášho zariadenia.",
"backup_controller_page_background_battery_info_ok": "OK",
"backup_controller_page_background_battery_info_title": "Optimalizácia batérie",
"backup_controller_page_background_charging": "Len počas nabíjania",
"backup_controller_page_background_configure_error": "Nepodarilo sa nakonfigurovať službu na pozadí",
"backup_controller_page_background_delay": "Oneskorenie zálohovania nových zdrojov: {}",
"backup_controller_page_background_delay": "Oneskorenie zálohovania nových médií: {}",
"backup_controller_page_background_description": "Povoľte službu na pozadí na automatické zálohovanie všetkých nových aktív bez nutnosti otvorenia aplikácie",
"backup_controller_page_background_is_off": "Automatické zálohovanie na pozadí je vypnuté",
"backup_controller_page_background_is_on": "Automatické zálohovanie na pozadí je zapnuté",
@@ -89,21 +92,21 @@
"cache_settings_subtitle": "Ovládanie správania mobilnej aplikácie Immich v medzipamäti",
"cache_settings_thumbnail_size": "Veľkosť vyrovnávacej pamäte náhľadov (položiek {})",
"cache_settings_title": "Nastavenia vyrovnávacej pamäte",
"change_password_form_confirm_password": "Confirm Password",
"change_password_form_description": "Hi {firstName} {lastName},\n\nThis is either the first time you are signing into the system or a request has been made to change your password. Please enter the new password below.",
"change_password_form_new_password": "New Password",
"change_password_form_password_mismatch": "Passwords do not match",
"change_password_form_reenter_new_password": "Re-enter New Password",
"common_add_to_album": "Add to album",
"common_change_password": "Change Password",
"common_create_new_album": "Create new album",
"common_shared": "Shared",
"change_password_form_confirm_password": "Potvrďte heslo",
"change_password_form_description": "Dobrý deň, {firstName} {lastName},\n\nBuď sa do systému prihlasujete prvýkrát, alebo bola podaná žiadosť o zmenu hesla. Prosím, zadajte nové heslo nižšie.",
"change_password_form_new_password": "Nové heslo",
"change_password_form_password_mismatch": "Heslá sa nezhodujú",
"change_password_form_reenter_new_password": "Znova zadajte nové heslo",
"common_add_to_album": "Pridať do albumu",
"common_change_password": "Zmeniť heslo",
"common_create_new_album": "Vytvoriť nový album",
"common_shared": "Zdieľané",
"control_bottom_app_bar_add_to_album": "Pridať do albumu",
"control_bottom_app_bar_album_info": "{} položky",
"control_bottom_app_bar_album_info_shared": "{} položky - zdieľané",
"control_bottom_app_bar_create_new_album": "Vytvoriť nový album",
"control_bottom_app_bar_delete": "Vymazať",
"control_bottom_app_bar_favorite": "Favorite",
"control_bottom_app_bar_favorite": "Obľúbené",
"control_bottom_app_bar_share": "Zdieľať",
"create_album_page_untitled": "Bez názvu",
"create_shared_album_page_create": "Vytvoriť",
@@ -126,13 +129,13 @@
"experimental_settings_title": "Experimentálne",
"favorites_page_title": "Obľúbené",
"home_page_add_to_album_conflicts": "Pridané {added} položky do albumu {album}. {failed} položky sú už v albume.",
"home_page_add_to_album_err_local": "Can not add local assets to albums yet, skipping",
"home_page_add_to_album_err_local": "Zatiaľ nie je možné pridať lokálne média do albumov, preskakuje sa",
"home_page_add_to_album_success": "Pridané {added} položky do albumu {album}.",
"home_page_building_timeline": "Vytváranie časovej osi",
"home_page_favorite_err_local": "Can not favorite local assets yet, skipping",
"home_page_first_time_notice": "Ak aplikáciu používate prvýkrát, nezabudnite si vybrať zálohované albumy, aby sa na časovej osi mohli nachádzať fotografie a videá z vybraných albumoch.",
"image_viewer_page_state_provider_download_error": "Download Error",
"image_viewer_page_state_provider_download_success": "Download Success",
"home_page_favorite_err_local": "Zatiaľ nie je možné zaradiť lokálne média medzi obľúbené, preskakuje sa",
"home_page_first_time_notice": "Ak aplikáciu používate prvý krát, nezabudnite si vybrať zálohované albumy, aby sa na časovej osi mohli nachádzať fotografie a videá z vybraných albumoch.",
"image_viewer_page_state_provider_download_error": "Chyba sťahovania",
"image_viewer_page_state_provider_download_success": "Sťahovanie bolo úspešné",
"library_page_albums": "Albumy",
"library_page_favorites": "Obľúbené",
"library_page_new_album": "Nový album",
@@ -145,7 +148,7 @@
"login_form_endpoint_url": "URL adresa servera",
"login_form_err_http": "Prosím, uveďte http:// alebo https://",
"login_form_err_invalid_email": "Neplatný e-mail",
"login_form_err_invalid_url": "Neplatná URL",
"login_form_err_invalid_url": "Neplatná URL adresa",
"login_form_err_leading_whitespace": "Úvodná medzera",
"login_form_err_trailing_whitespace": "Koncové medzera",
"login_form_failed_get_oauth_server_config": "Chyba prihlásenia pomocou OAuth, skontrolujte adresu URL servera",
@@ -156,12 +159,12 @@
"login_form_password_hint": "heslo",
"login_form_save_login": "Zostať prihlásený",
"monthly_title_text_date_format": "LLLL y",
"notification_permission_dialog_cancel": "Cancel",
"notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.",
"notification_permission_dialog_settings": "Settings",
"notification_permission_list_tile_content": "Grant permission to enable notifications.",
"notification_permission_list_tile_enable_button": "Enable Notifications",
"notification_permission_list_tile_title": "Notification Permission",
"notification_permission_dialog_cancel": "Zrušiť",
"notification_permission_dialog_content": "Ak chcete povoliť upozornenia, prejdite do Nastavenia a vyberte možnosť Povoliť.",
"notification_permission_dialog_settings": "Nastavenia",
"notification_permission_list_tile_content": "Udeľte oprávnenie k aktivácii oznámení.",
"notification_permission_list_tile_enable_button": "Povoliť upozornenia",
"notification_permission_list_tile_title": "Povolenie oznámení",
"profile_drawer_app_logs": "Logy",
"profile_drawer_client_server_up_to_date": "Klient a server sú aktuálne",
"profile_drawer_settings": "Nastavenia",
@@ -175,12 +178,12 @@
"select_additional_user_for_sharing_page_suggestions": "Návrhy",
"select_user_for_sharing_page_err_album": "Nepodarilo sa vytvoriť album",
"select_user_for_sharing_page_share_suggestions": "Návrhy",
"server_info_box_app_version": "App Version",
"server_info_box_server_version": "Server Version",
"setting_image_viewer_help": "V prehliadači detailov sa najprv načíta malá miniatúra, potom sa načíta náhľad strednej veľkosti (ak je povolený) a nakoniec sa načíta originál (ak je povolený).",
"setting_image_viewer_original_subtitle": "Umožňuje načítať pôvodný obrázok v plnom rozlíšení (veľký!). Zakázať pre zníženie používania dát (v sieti aj v medzipamäti zariadenia).",
"server_info_box_app_version": "Verzia aplikácie",
"server_info_box_server_version": "Verzia servera",
"setting_image_viewer_help": "Prehliadač detailov najprv načíta malú miniatúru, potom načíta náhľad strednej veľkosti (ak je povolený) a nakoniec načíta originál (ak je povolený).",
"setting_image_viewer_original_subtitle": "Povolením umožníte načítať pôvodný obrázok v plnom rozlíšení (veľký!). Zakázaním znížite používania dát (v sieti, aj v dočasnej pamäte zariadenia).",
"setting_image_viewer_original_title": "Načítať pôvodný obrázok",
"setting_image_viewer_preview_subtitle": "Umožňuje načítať obrázok so stredným rozlíšením. Zakážte, ak chcete priamo načítať originál alebo použiť iba miniatúru.",
"setting_image_viewer_preview_subtitle": "Povolením umožníte načítať obrázok so stredným rozlíšením. Zakážte, ak chcete priamo načítať originál alebo použiť iba miniatúru.",
"setting_image_viewer_preview_title": "Načítať náhľad obrázka",
"setting_notifications_notify_failures_grace_period": "Oznámenie o zlyhaní zálohovania na pozadí: {}",
"setting_notifications_notify_hours": "{} hodín",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "Säkerhetskopierar dina foton och videor...",
"backup_background_service_upload_failure_notification": "Kunde inte ladda upp {}",
"backup_controller_page_albums": "Säkerhetskopiera album",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "Visa mig hur",
"backup_controller_page_background_battery_info_message": "För optimal säkerhetskopiering i bakgrunden bör du stänga av batterioptimering som begränsar bakgrundsaktivitet för Immich.\n\nEftersom detta är enhetsspecifikt så bör du söka instruktioner från din enhetstillverkare.",
"backup_controller_page_background_battery_info_ok": "OK",

View File

@@ -35,6 +35,9 @@
"backup_background_service_in_progress_notification": "正在备份…",
"backup_background_service_upload_failure_notification": "上传失败 {}",
"backup_controller_page_albums": "备份相册",
"backup_controller_page_background_app_refresh_disabled_content": "Enable background app refresh in Settings > General > Background App Refresh in order to use background backup.",
"backup_controller_page_background_app_refresh_disabled_title": "Background app refresh disabled",
"backup_controller_page_background_app_refresh_enable_button_text": "Go to settings",
"backup_controller_page_background_battery_info_link": "怎么做",
"backup_controller_page_background_battery_info_message": "为了获得最佳的后台备份体验,请禁用任何限制 Immich 后台活动的电池优化。\n\n由于这是设备相关的因此请查找设备制造商所需的信息。",
"backup_controller_page_background_battery_info_ok": "我知道了",

View File

@@ -72,7 +72,7 @@ post_install do |installer|
# 'PERMISSION_SPEECH_RECOGNIZER=1',
## dart: PermissionGroup.photos
# 'PERMISSION_PHOTOS=1',
'PERMISSION_PHOTOS=1',
## dart: [PermissionGroup.location, PermissionGroup.locationAlways, PermissionGroup.locationWhenInUse]
# 'PERMISSION_LOCATION=1',

View File

@@ -1,4 +1,6 @@
PODS:
- device_info_plus (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_native_splash (0.0.1):
- Flutter
@@ -49,6 +51,7 @@ PODS:
- Flutter
DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_udid (from `.symlinks/plugins/flutter_udid/ios`)
@@ -76,6 +79,8 @@ SPEC REPOS:
- Toast
EXTERNAL SOURCES:
device_info_plus:
:path: ".symlinks/plugins/device_info_plus/ios"
Flutter:
:path: Flutter
flutter_native_splash:
@@ -116,29 +121,30 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/wakelock/ios"
SPEC CHECKSUMS:
device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
flutter_udid: 0848809dbed4c055175747ae6a45a8b4f6771e1c
flutter_web_auth: c25208760459cec375a3c39f6a8759165ca0fa4d
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
image_picker_ios: b786a5dcf033a8336a657191401bfdf12017dabb
image_picker_ios: 58b9c4269cb176f89acea5e5d043c9358f2d25f8
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e
path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852
path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca
shared_preferences_foundation: 986fc17f3d3251412d18b0265f9c64113a8c2472
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: fb12c43172927bb5cf75aeebd073f883801f1993
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
video_player_avfoundation: 6d971a232d72e6ee25368378d48a079dea01f1cf
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
PODFILE CHECKSUM: 4a7e0475ea85ab7bf89955bc4c7ea9d18b54dfd8
PODFILE CHECKSUM: 0606648e8a9ecd5a59eafa5ab3187b45a9004a28
COCOAPODS: 1.11.3

View File

@@ -4,6 +4,7 @@ import Flutter
import BackgroundTasks
import path_provider_ios
import photo_manager
import permission_handler_apple
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
@@ -30,6 +31,10 @@ import photo_manager
if !registry.hasPlugin("org.cocoapods.shared-preferences-foundation") {
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.shared-preferences-foundation")!)
}
if !registry.hasPlugin("org.cocoapods.permission-handler-apple") {
PermissionHandlerPlugin.register(with: registry.registrar(forPlugin: "org.cocoapods.permission-handler-apple")!)
}
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)

View File

@@ -1,112 +1,113 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>app.alextran.immich.backgroundFetch</string>
<string>app.alextran.immich.backgroundProcessing</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Immich</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>cs</string>
<string>da</string>
<string>de</string>
<string>en</string>
<string>es</string>
<string>fi</string>
<string>fr</string>
<string>it</string>
<string>ja</string>
<string>ko</string>
<string>nl</string>
<string>pl</string>
<string>pt</string>
<string>ru</string>
<string>se</string>
<string>sk</string>
<string>zh</string>
</array>
<key>CFBundleName</key>
<string>immich_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.48.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>87</string>
<key>FLTEnableImpeller</key>
<true/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>io.flutter.embedded_views_preview</key>
<true/>
</dict>
</plist>
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>app.alextran.immich.backgroundFetch</string>
<string>app.alextran.immich.backgroundProcessing</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true />
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Immich</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLocalizations</key>
<array>
<string>cs</string>
<string>da</string>
<string>de</string>
<string>en</string>
<string>es</string>
<string>fi</string>
<string>fr</string>
<string>it</string>
<string>ja</string>
<string>ko</string>
<string>nl</string>
<string>pl</string>
<string>pt</string>
<string>ru</string>
<string>se</string>
<string>sk</string>
<string>zh</string>
<string>no</string>
</array>
<key>CFBundleName</key>
<string>immich_mobile</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.49.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>87</string>
<key>FLTEnableImpeller</key>
<true />
<key>ITSAppUsesNonExemptEncryption</key>
<false />
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true />
<key>MGLMapboxMetricsEnabledSettingShownInApp</key>
<true />
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true />
</dict>
<key>NSCameraUsageDescription</key>
<string>We need to access the camera to let you take beautiful video using this app</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Enable location setting to show position of assets on map</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to access the microphone to let you take beautiful video using this app</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need to manage backup your photos album</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true />
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIStatusBarHidden</key>
<false />
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true />
<key>io.flutter.embedded_views_preview</key>
<true />
</dict>
</plist>

View File

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

View File

@@ -5,32 +5,19 @@
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000542">
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000255">
</testcase>
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="11.028001">
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="6.769548">
</testcase>
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="5.912096">
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="24.256379">
</testcase>
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="1.65488">
</testcase>
<testcase classname="fastlane.lanes" name="4: build_app" time="110.93313">
</testcase>
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="80.292248">
<failure message="/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/actions/actions_helper.rb:67:in `execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:255:in `block in execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:229:in `chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:229:in `execute_action&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:157:in `trigger_action_by_name&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/fast_file.rb:159:in `method_missing&apos;&#10;Fastfile:25:in `block (2 levels) in parsing_binding&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/lane.rb:33:in `call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:49:in `block in execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:45:in `chdir&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/runner.rb:45:in `execute&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/lane_manager.rb:47:in `cruise_lane&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/command_line_handler.rb:36:in `handle&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/commands_generator.rb:110:in `block (2 levels) in run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:187:in `call&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/command.rb:157:in `run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/runner.rb:444:in `run_active_command&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane_core/lib/fastlane_core/ui/fastlane_runner.rb:124:in `run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/commander-4.6.0/lib/commander/delegates.rb:18:in `run!&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/commands_generator.rb:354:in `run&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/commands_generator.rb:43:in `start&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/fastlane/lib/fastlane/cli_tools_distributor.rb:123:in `take_off&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/gems/fastlane-2.212.0/bin/fastlane:23:in `&lt;top (required)&gt;&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/bin/fastlane:25:in `load&apos;&#10;/opt/homebrew/Cellar/fastlane/2.212.0/libexec/bin/fastlane:25:in `&lt;main&gt;&apos;&#10;&#10;Access forbidden" />
</testcase>

View File

@@ -20,6 +20,7 @@ const List<Locale> locales = [
Locale('sv', 'SE'),
Locale('sk', 'SK'),
Locale('zh', 'CN'),
Locale('no', 'NO'),
];
const String translationsPath = 'assets/i18n';

View File

@@ -15,7 +15,8 @@ import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/backup/providers/ios_background_settings.provider.dart';
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/settings/providers/permission.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/routing/tab_navigation_observer.dart';
import 'package:immich_mobile/shared/models/immich_logger_message.model.dart';
@@ -34,6 +35,7 @@ import 'package:immich_mobile/utils/migration.dart';
import 'package:isar/isar.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'constants/hive_box.dart';
void main() async {
@@ -129,8 +131,10 @@ class ImmichAppState extends ConsumerState<ImmichApp>
ref.watch(appStateProvider.notifier).state = AppStateEnum.resumed;
var isAuthenticated = ref.watch(authenticationProvider).isAuthenticated;
final permission = ref.watch(galleryPermissionNotifier);
if (isAuthenticated) {
// Needs to be logged in and have gallery permissions
if (isAuthenticated && (permission.isGranted || permission.isLimited)) {
ref.read(backupProvider.notifier).resumeBackup();
ref.read(backgroundServiceProvider).resumeServiceIfEnabled();
ref.watch(assetProvider.notifier).getAllAsset();
@@ -141,9 +145,10 @@ class ImmichAppState extends ConsumerState<ImmichApp>
ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo();
ref
.watch(notificationPermissionProvider.notifier)
.getNotificationPermission();
ref.watch(notificationPermissionProvider.notifier)
.getNotificationPermission();
ref.watch(galleryPermissionNotifier.notifier)
.getGalleryPermissionStatus();
ref.read(iOSBackgroundSettingsProvider.notifier).refresh();

View File

@@ -278,7 +278,9 @@ class AlbumViewerPage extends HookConsumerWidget {
),
),
),
buildImageGrid(album)
SliverSafeArea(
sliver: buildImageGrid(album),
),
],
),
),

View File

@@ -560,6 +560,9 @@ class BackgroundService {
}
Future<DateTime?> getIOSBackupLastRun(IosBackgroundTask task) async {
if (!Platform.isIOS) {
return null;
}
// Seconds since last run
final double? lastRun = task == IosBackgroundTask.fetch
? await _foregroundChannel.invokeMethod('lastBackgroundFetchTime')
@@ -572,10 +575,16 @@ class BackgroundService {
}
Future<int> getIOSBackupNumberOfProcesses() async {
if (!Platform.isIOS) {
return 0;
}
return await _foregroundChannel.invokeMethod('numberOfBackgroundProcesses');
}
Future<bool> getIOSBackgroundAppRefreshEnabled() async {
if (!Platform.isIOS) {
return false;
}
return await _foregroundChannel.invokeMethod('backgroundAppRefreshEnabled');
}
}

View File

@@ -14,10 +14,12 @@ import 'package:immich_mobile/modules/backup/background_service/background.servi
import 'package:immich_mobile/modules/backup/services/backup.service.dart';
import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
import 'package:immich_mobile/shared/services/server_info.service.dart';
import 'package:logging/logging.dart';
import 'package:openapi/api.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:photo_manager/photo_manager.dart';
class BackupNotifier extends StateNotifier<BackUpState> {
@@ -26,6 +28,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
this._serverInfoService,
this._authState,
this._backgroundService,
this._galleryPermissionNotifier,
this.ref,
) : super(
BackUpState(
@@ -65,6 +68,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
final ServerInfoService _serverInfoService;
final AuthenticationState _authState;
final BackgroundService _backgroundService;
final GalleryPermissionNotifier _galleryPermissionNotifier;
final Ref ref;
///
@@ -431,8 +435,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
await getBackupInfo();
var authResult = await PhotoManager.requestPermissionExtend();
if (authResult.isAuth) {
final hasPermission = _galleryPermissionNotifier.hasPermission;
if (hasPermission) {
await PhotoManager.clearFileCache();
if (state.allUniqueAssets.isEmpty) {
@@ -463,7 +467,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
);
await _notifyBackgroundServiceCanRun();
} else {
PhotoManager.openSetting();
openAppSettings();
}
}
@@ -704,6 +708,7 @@ final backupProvider =
ref.watch(serverInfoServiceProvider),
ref.watch(authenticationProvider),
ref.watch(backgroundServiceProvider),
ref.watch(galleryPermissionNotifier.notifier),
ref,
);
});

View File

@@ -137,6 +137,7 @@ class AlbumInfoCard extends HookConsumerWidget {
}
},
child: Card(
clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.all(1),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), // if you need this
@@ -150,20 +151,17 @@ class AlbumInfoCard extends HookConsumerWidget {
elevation: 0,
borderOnForeground: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Stack(
children: [
Container(
width: 200,
height: 200,
decoration: BoxDecoration(
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12),
),
image: DecorationImage(
colorFilter: buildImageFilter(),
Expanded(
child: Stack(
clipBehavior: Clip.hardEdge,
children: [
ColorFiltered(
colorFilter: buildImageFilter(),
child: Image(
width: double.infinity,
height: double.infinity,
image: imageData != null
? MemoryImage(imageData!)
: const AssetImage(
@@ -172,58 +170,56 @@ class AlbumInfoCard extends HookConsumerWidget {
fit: BoxFit.cover,
),
),
child: null,
),
Positioned(
bottom: 10,
left: 25,
child: buildSelectedTextBox(),
)
],
Positioned(
bottom: 10,
right: 25,
child: buildSelectedTextBox(),
)
],
),
),
Padding(
padding: const EdgeInsets.only(top: 8.0),
padding: const EdgeInsets.only(
left: 25,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
width: 140,
child: Padding(
padding: const EdgeInsets.only(left: 25.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
albumInfo.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
albumInfo.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: FutureBuilder(
builder: ((context, snapshot) {
if (snapshot.hasData) {
return Text(
snapshot.data.toString() +
(albumInfo.isAll
? " (${'backup_all'.tr()})"
: ""),
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
);
}
return const Text("0");
}),
future: albumInfo.assetCount,
),
)
],
),
),
Padding(
padding: const EdgeInsets.only(top: 2.0),
child: FutureBuilder(
builder: ((context, snapshot) {
if (snapshot.hasData) {
return Text(
snapshot.data.toString() +
(albumInfo.isAll
? " (${'backup_all'.tr()})"
: ""),
style: TextStyle(
fontSize: 12,
color: Colors.grey[600],
),
);
}
return const Text("0");
}),
future: albumInfo.assetCount,
),
)
],
),
),
IconButton(

View File

@@ -0,0 +1,176 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
class AlbumInfoListTile extends HookConsumerWidget {
final Uint8List? imageData;
final AvailableAlbum albumInfo;
const AlbumInfoListTile({Key? key, this.imageData, required this.albumInfo})
: super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final bool isSelected =
ref.watch(backupProvider).selectedBackupAlbums.contains(albumInfo);
final bool isExcluded =
ref.watch(backupProvider).excludedBackupAlbums.contains(albumInfo);
ColorFilter selectedFilter = ColorFilter.mode(
Theme.of(context).primaryColor.withAlpha(100),
BlendMode.darken,
);
ColorFilter excludedFilter =
ColorFilter.mode(Colors.red.withAlpha(75), BlendMode.darken);
ColorFilter unselectedFilter =
const ColorFilter.mode(Colors.black, BlendMode.color);
var isDarkTheme = Theme.of(context).brightness == Brightness.dark;
var assetCount = useState(0);
useEffect(
() {
albumInfo.assetCount.then((value) => assetCount.value = value);
return null;
},
[],
);
buildImageFilter() {
if (isSelected) {
return selectedFilter;
} else if (isExcluded) {
return excludedFilter;
} else {
return unselectedFilter;
}
}
buildTileColor() {
if (isSelected) {
return isDarkTheme
? Theme.of(context).primaryColor.withAlpha(100)
: Theme.of(context).primaryColor.withAlpha(25);
} else if (isExcluded) {
return isDarkTheme
? Colors.red[300]?.withAlpha(150)
: Colors.red[100]?.withAlpha(150);
} else {
return Colors.transparent;
}
}
return GestureDetector(
onDoubleTap: () {
HapticFeedback.selectionClick();
if (isExcluded) {
// Remove from exclude album list
ref
.watch(backupProvider.notifier)
.removeExcludedAlbumForBackup(albumInfo);
} else {
// Add to exclude album list
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1 &&
ref
.watch(backupProvider)
.selectedBackupAlbums
.contains(albumInfo)) {
ImmichToast.show(
context: context,
msg: "backup_err_only_album".tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
return;
}
if (albumInfo.id == 'isAll' || albumInfo.name == 'Recents') {
ImmichToast.show(
context: context,
msg: 'Cannot exclude album contains all assets',
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
return;
}
ref
.watch(backupProvider.notifier)
.addExcludedAlbumForBackup(albumInfo);
}
},
child: ListTile(
tileColor: buildTileColor(),
contentPadding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
onTap: () {
HapticFeedback.selectionClick();
if (isSelected) {
if (ref.watch(backupProvider).selectedBackupAlbums.length == 1) {
ImmichToast.show(
context: context,
msg: "backup_err_only_album".tr(),
toastType: ToastType.error,
gravity: ToastGravity.BOTTOM,
);
return;
}
ref.watch(backupProvider.notifier).removeAlbumForBackup(albumInfo);
} else {
ref.watch(backupProvider.notifier).addAlbumForBackup(albumInfo);
}
},
leading: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: SizedBox(
height: 80,
width: 80,
child: ColorFiltered(
colorFilter: buildImageFilter(),
child: Image(
width: double.infinity,
height: double.infinity,
image: imageData != null
? MemoryImage(imageData!)
: const AssetImage(
'assets/immich-logo-no-outline.png',
) as ImageProvider,
fit: BoxFit.cover,
),
),
),
),
title: Text(
albumInfo.name,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
subtitle: Text(assetCount.value.toString()),
trailing: IconButton(
onPressed: () {
AutoRouter.of(context).push(
AlbumPreviewRoute(album: albumInfo.albumEntity),
);
},
icon: Icon(
Icons.image_outlined,
color: Theme.of(context).primaryColor,
size: 24,
),
splashRadius: 25,
),
),
);
}
}

View File

@@ -7,6 +7,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/immich_colors.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/backup/ui/album_info_card.dart';
import 'package:immich_mobile/modules/backup/ui/album_info_list_tile.dart';
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
@@ -18,7 +19,12 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
final selectedBackupAlbums = ref.watch(backupProvider).selectedBackupAlbums;
final excludedBackupAlbums = ref.watch(backupProvider).excludedBackupAlbums;
final isDarkTheme = Theme.of(context).brightness == Brightness.dark;
final albums = ref.watch(backupProvider).availableAlbums;
final allAlbums = ref.watch(backupProvider).availableAlbums;
// Albums which are displayed to the user
// by filtering out based on search
final filteredAlbums = useState(allAlbums);
final albums = filteredAlbums.value;
useEffect(
() {
@@ -30,27 +36,53 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
buildAlbumSelectionList() {
if (albums.isEmpty) {
return const Center(
child: ImmichLoadingIndicator(),
return const SliverToBoxAdapter(
child: Center(
child: ImmichLoadingIndicator(),
),
);
}
return SizedBox(
height: 265,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: albums.length,
physics: const BouncingScrollPhysics(),
itemBuilder: ((context, index) {
var thumbnailData = albums[index].thumbnailData;
return Padding(
padding: index == 0
? const EdgeInsets.only(left: 16.00)
: const EdgeInsets.all(0),
child: AlbumInfoCard(
return SliverPadding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
((context, index) {
var thumbnailData = albums[index].thumbnailData;
return AlbumInfoListTile(
imageData: thumbnailData,
albumInfo: albums[index],
),
);
}),
childCount: albums.length,
),
),
);
}
buildAlbumSelectionGrid() {
if (albums.isEmpty) {
return const SliverToBoxAdapter(
child: Center(
child: ImmichLoadingIndicator(),
),
);
}
return SliverPadding(
padding: const EdgeInsets.all(12.0),
sliver: SliverGrid.builder(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 300,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
),
itemCount: albums.length,
itemBuilder: ((context, index) {
var thumbnailData = albums[index].thumbnailData;
return AlbumInfoCard(
imageData: thumbnailData,
albumInfo: albums[index],
);
}),
),
@@ -139,19 +171,17 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
padding: const EdgeInsets.only(left: 16.0, right: 16, bottom: 8.0),
child: TextFormField(
onChanged: (searchValue) {
var avaialbleAlbums = ref
.watch(backupProvider)
.availableAlbums
.where(
(album) => album.name
.toLowerCase()
.contains(searchValue.toLowerCase()),
)
.toList();
ref
.read(backupProvider.notifier)
.setAvailableAlbums(avaialbleAlbums);
if (searchValue.isEmpty) {
filteredAlbums.value = allAlbums;
} else {
filteredAlbums.value = allAlbums
.where(
(album) => album.name
.toLowerCase()
.contains(searchValue.toLowerCase()),
)
.toList();
}
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
@@ -190,143 +220,162 @@ class BackupAlbumSelectionPage extends HookConsumerWidget {
).tr(),
elevation: 0,
),
body: ListView(
body: CustomScrollView(
physics: const ClampingScrollPhysics(),
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
child: const Text(
"backup_album_selection_page_selection_info",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
).tr(),
),
// Selected Album Chips
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Wrap(
slivers: [
SliverToBoxAdapter(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...buildSelectedAlbumNameChip(),
...buildExcludedAlbumNameChip()
Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
child: const Text(
"backup_album_selection_page_selection_info",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
).tr(),
),
// Selected Album Chips
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Wrap(
children: [
...buildSelectedAlbumNameChip(),
...buildExcludedAlbumNameChip()
],
),
),
Padding(
padding:
const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Card(
margin: const EdgeInsets.all(0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: isDarkTheme
? const Color.fromARGB(255, 0, 0, 0)
: const Color.fromARGB(255, 235, 235, 235),
width: 1,
),
),
elevation: 0,
borderOnForeground: false,
child: Column(
children: [
ListTile(
visualDensity: VisualDensity.compact,
title: const Text(
"backup_album_selection_page_total_assets",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
).tr(),
trailing: Text(
ref
.watch(backupProvider)
.allUniqueAssets
.length
.toString(),
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
),
),
ListTile(
title: Text(
"backup_album_selection_page_albums_device".tr(
args: [
ref
.watch(backupProvider)
.availableAlbums
.length
.toString()
],
),
style: const TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
"backup_album_selection_page_albums_tap",
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
).tr(),
),
trailing: IconButton(
splashRadius: 16,
icon: Icon(
Icons.info,
size: 20,
color: Theme.of(context).primaryColor,
),
onPressed: () {
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 5,
title: Text(
'backup_album_selection_page_selection_info',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
).tr(),
content: SingleChildScrollView(
child: ListBody(
children: [
const Text(
'backup_album_selection_page_assets_scatter',
style: TextStyle(
fontSize: 14,
),
).tr(),
],
),
),
);
},
);
},
),
),
buildSearchBar(),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Card(
margin: const EdgeInsets.all(0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: isDarkTheme
? const Color.fromARGB(255, 0, 0, 0)
: const Color.fromARGB(255, 235, 235, 235),
width: 1,
),
),
elevation: 0,
borderOnForeground: false,
child: Column(
children: [
ListTile(
visualDensity: VisualDensity.compact,
title: const Text(
"backup_album_selection_page_total_assets",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 14,
),
).tr(),
trailing: Text(
ref
.watch(backupProvider)
.allUniqueAssets
.length
.toString(),
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
],
),
),
),
ListTile(
title: Text(
"backup_album_selection_page_albums_device".tr(
args: [
ref.watch(backupProvider).availableAlbums.length.toString()
],
),
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
),
subtitle: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Text(
"backup_album_selection_page_albums_tap",
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryColor,
fontWeight: FontWeight.bold,
),
).tr(),
),
trailing: IconButton(
splashRadius: 16,
icon: Icon(
Icons.info,
size: 20,
color: Theme.of(context).primaryColor,
),
onPressed: () {
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 5,
title: Text(
'backup_album_selection_page_selection_info',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryColor,
),
).tr(),
content: SingleChildScrollView(
child: ListBody(
children: [
const Text(
'backup_album_selection_page_assets_scatter',
style: TextStyle(
fontSize: 14,
),
).tr(),
],
),
),
);
},
);
},
),
),
buildSearchBar(),
Padding(
padding: const EdgeInsets.only(bottom: 16.0),
child: buildAlbumSelectionList(),
SliverLayoutBuilder(
builder: (context, constraints) {
if (constraints.crossAxisExtent > 600) {
return buildAlbumSelectionGrid();
} else {
return buildAlbumSelectionList();
}
},
),
],
),

View File

@@ -3,14 +3,15 @@ import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
FavoriteSelectionNotifier(this.ref) : super({}) {
state = ref.watch(assetProvider).allAssets
FavoriteSelectionNotifier(this.assetsState, this.assetNotifier) : super({}) {
state = assetsState.allAssets
.where((asset) => asset.isFavorite)
.map((asset) => asset.id)
.toSet();
}
final Ref ref;
final AssetsState assetsState;
final AssetNotifier assetNotifier;
void _setFavoriteForAssetId(String id, bool favorite) {
if (!favorite) {
@@ -29,7 +30,7 @@ class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
_setFavoriteForAssetId(asset.id, !_isFavorite(asset.id));
await ref.watch(assetProvider.notifier).toggleFavorite(
await assetNotifier.toggleFavorite(
asset,
state.contains(asset.id),
);
@@ -37,8 +38,8 @@ class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
Future<void> addToFavorites(Iterable<Asset> assets) {
state = state.union(assets.map((a) => a.id).toSet());
final futures = assets.map((a) =>
ref.watch(assetProvider.notifier).toggleFavorite(
final futures = assets.map((a) =>
assetNotifier.toggleFavorite(
a,
true,
),
@@ -50,7 +51,10 @@ class FavoriteSelectionNotifier extends StateNotifier<Set<String>> {
final favoriteProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<String>>((ref) {
return FavoriteSelectionNotifier(ref);
return FavoriteSelectionNotifier(
ref.watch(assetProvider),
ref.watch(assetProvider.notifier),
);
});
final favoriteAssetProvider = StateProvider((ref) {

View File

@@ -7,14 +7,18 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/login/providers/oauth.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/shared/ui/immich_logo.dart';
import 'package:immich_mobile/shared/ui/immich_title_text.dart';
import 'package:immich_mobile/shared/ui/immich_toast.dart';
import 'package:immich_mobile/utils/url_helper.dart';
import 'package:openapi/api.dart';
import 'package:permission_handler/permission_handler.dart';
class LoginForm extends HookConsumerWidget {
const LoginForm({Key? key}) : super(key: key);
@@ -105,22 +109,12 @@ class LoginForm extends HookConsumerWidget {
onDoubleTap: () => populateTestLoginInfo(),
child: RotationTransition(
turns: logoAnimationController,
child: const Image(
image: AssetImage('assets/immich-logo-no-outline.png'),
width: 100,
filterQuality: FilterQuality.high,
child: const ImmichLogo(
heroTag: 'logo',
),
),
),
Text(
'IMMICH',
style: TextStyle(
fontFamily: 'SnowburstOne',
fontWeight: FontWeight.bold,
fontSize: 48,
color: Theme.of(context).primaryColor,
),
),
const ImmichTitleText(),
EmailInput(controller: usernameController),
PasswordInput(controller: passwordController),
ServerEndpointInput(
@@ -164,7 +158,10 @@ class LoginForm extends HookConsumerWidget {
isLoading: isLoading,
onLoginSuccess: () {
isLoading.value = false;
ref.watch(backupProvider.notifier).resumeBackup();
final permission = ref.watch(galleryPermissionNotifier);
if (permission.isGranted || permission.isLimited) {
ref.watch(backupProvider.notifier).resumeBackup();
}
AutoRouter.of(context).replace(
const TabControllerRoute(),
);
@@ -313,7 +310,13 @@ class LoginButton extends ConsumerWidget {
!ref.read(authenticationProvider).isAdmin) {
AutoRouter.of(context).push(const ChangePasswordRoute());
} else {
ref.read(backupProvider.notifier).resumeBackup();
final hasPermission = await ref
.read(galleryPermissionNotifier.notifier)
.hasPermission;
if (hasPermission) {
// Don't resume the backup until we have gallery permission
ref.read(backupProvider.notifier).resumeBackup();
}
AutoRouter.of(context).replace(const TabControllerRoute());
}
} else {

View File

@@ -0,0 +1,101 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:permission_handler/permission_handler.dart';
class GalleryPermissionNotifier extends StateNotifier<PermissionStatus> {
GalleryPermissionNotifier()
: super(PermissionStatus.denied) // Denied is the intitial state
{
// Sets the initial state
getGalleryPermissionStatus();
}
get hasPermission => state.isGranted || state.isLimited;
/// Requests the gallery permission
Future<PermissionStatus> requestGalleryPermission() async {
// Android 32 and below uses Permission.storage
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt <= 32) {
// Android 32 and below need storage
final permission = await Permission.storage.request();
state = permission;
return permission;
} else {
// Android 33 need photo & video
final photos = await Permission.photos.request();
if (!photos.isGranted) {
// Don't ask twice for the same permission
return photos;
}
final videos = await Permission.videos.request();
// Return the joint result of those two permissions
final PermissionStatus status;
if (photos.isGranted && videos.isGranted) {
status = PermissionStatus.granted;
} else if (photos.isDenied || videos.isDenied) {
status = PermissionStatus.denied;
} else if (photos.isPermanentlyDenied || videos.isPermanentlyDenied) {
status = PermissionStatus.permanentlyDenied;
} else {
status = PermissionStatus.denied;
}
state = status;
return status;
}
} else {
// iOS can use photos
final photos = await Permission.photos.request();
state = photos;
return photos;
}
}
/// Checks the current state of the gallery permissions without
/// requesting them again
Future<PermissionStatus> getGalleryPermissionStatus() async {
// Android 32 and below uses Permission.storage
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt <= 32) {
// Android 32 and below need storage
final permission = await Permission.storage.status;
state = permission;
return permission;
} else {
// Android 33 needs photo & video
final photos = await Permission.photos.status;
final videos = await Permission.videos.status;
// Return the joint result of those two permissions
final PermissionStatus status;
if (photos.isGranted && videos.isGranted) {
status = PermissionStatus.granted;
} else if (photos.isDenied || videos.isDenied) {
status = PermissionStatus.denied;
} else if (photos.isPermanentlyDenied || videos.isPermanentlyDenied) {
status = PermissionStatus.permanentlyDenied;
} else {
status = PermissionStatus.denied;
}
state = status;
return status;
}
} else {
// iOS can use photos
final photos = await Permission.photos.status;
state = photos;
return photos;
}
}
}
final galleryPermissionNotifier
= StateNotifierProvider<GalleryPermissionNotifier, PermissionStatus>
((ref) => GalleryPermissionNotifier());

View File

@@ -0,0 +1,201 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/ui/immich_logo.dart';
import 'package:immich_mobile/shared/ui/immich_title_text.dart';
import 'package:permission_handler/permission_handler.dart';
class PermissionOnboardingPage extends HookConsumerWidget {
const PermissionOnboardingPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final PermissionStatus permission = ref.watch(galleryPermissionNotifier);
// Navigate to the main Tab Controller when permission is granted
void goToHome() {
// Resume backup (if enable) then navigate
ref.watch(backupProvider.notifier).resumeBackup()
.catchError((error) {
debugPrint('PermissionOnboardingPage error: $error');
});
AutoRouter.of(context).replace(
const TabControllerRoute(),
);
}
// When the permission is denied, we show a request permission page
buildRequestPermission() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'permission_onboarding_request',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
).tr(),
const SizedBox(height: 18),
ElevatedButton(
onPressed: () => ref
.read(galleryPermissionNotifier.notifier)
.requestGalleryPermission()
.then((permission) async {
if (permission.isGranted) {
// If permission is limited, we will show the limited
// permission page
goToHome();
}
}),
child: const Text(
'permission_onboarding_grant_permission',
).tr(),
),
],
);
}
// When permission is granted from outside the app, this will show to
// let them continue on to the main timeline
buildPermissionGranted() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'permission_onboarding_permission_granted',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
).tr(),
const SizedBox(height: 18),
ElevatedButton(
onPressed: () => goToHome(),
child: const Text('permission_onboarding_get_started').tr(),
),
],
);
}
// iOS 14+ has limited permission options, which let someone just share
// a few photos with the app. If someone only has limited permissions, we
// inform that Immich works best when given full permission
buildPermissionLimited() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.warning_outlined,
color: Colors.yellow,
size: 48,
),
const SizedBox(height: 8),
Text(
'permission_onboarding_permission_limited',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
).tr(),
const SizedBox(height: 18),
ElevatedButton(
onPressed: () => openAppSettings(),
child: const Text(
'permission_onboarding_go_to_settings',
).tr(),
),
const SizedBox(height: 8.0),
TextButton(
onPressed: () => goToHome(),
child: const Text(
'permission_onboarding_continue_anyway',
).tr(),
),
],
);
}
buildPermissionDenied() {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.warning_outlined,
color: Colors.red,
size: 48,
),
const SizedBox(height: 8),
Text(
'permission_onboarding_permission_denied',
style: Theme.of(context).textTheme.titleMedium,
textAlign: TextAlign.center,
).tr(),
const SizedBox(height: 18),
ElevatedButton(
onPressed: () => openAppSettings(),
child: const Text(
'permission_onboarding_go_to_settings',
).tr(),
),
],
);
}
final Widget child;
switch (permission) {
case PermissionStatus.limited:
child = buildPermissionLimited();
break;
case PermissionStatus.denied:
child = buildRequestPermission();
break;
case PermissionStatus.granted:
child = buildPermissionGranted();
break;
case PermissionStatus.restricted:
case PermissionStatus.permanentlyDenied:
child = buildPermissionDenied();
break;
}
return Scaffold(
body: SafeArea(
child: Center(
child: SizedBox(
width: 380,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const ImmichLogo(
heroTag: 'logo',
),
const ImmichTitleText(),
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: Padding(
padding: const EdgeInsets.all(18.0),
child: child,
),
),
TextButton(
child: const Text('permission_onboarding_log_out').tr(),
onPressed: () {
ref.read(authenticationProvider.notifier).logout();
AutoRouter.of(context).replace(
const LoginRoute(),
);
},
),
],
),
),
),
),
);
}
}

View File

@@ -1,21 +0,0 @@
import 'package:permission_handler/permission_handler.dart';
/// This class is for requesting permissions in the app
class PermissionService {
/// Requests the notification permission
/// Note: In Android, this is always granted
Future<PermissionStatus> requestNotificationPermission() {
return Permission.notification.request();
}
/// Whether the user has the permission or not
/// Note: In Android, this is always true
Future<bool> hasNotificationPermission() {
return Permission.notification.isGranted;
}
/// Either the permission was granted already or else ask for the permission
Future<bool> hasOrAskForNotificationPermission() {
return requestNotificationPermission().then((p) => p.isGranted);
}
}

View File

@@ -3,7 +3,7 @@ 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/providers/permission.provider.dart';
import 'package:immich_mobile/modules/settings/providers/notification_permission.provider.dart';
import 'package:immich_mobile/modules/settings/services/app_settings.service.dart';
import 'package:immich_mobile/modules/settings/ui/settings_switch_list_tile.dart';
import 'package:permission_handler/permission_handler.dart';

View File

@@ -0,0 +1,17 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/foundation.dart';
/// Guards against duplicate navigation to this route
class DuplicateGuard extends AutoRouteGuard {
DuplicateGuard();
@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
// Duplicate navigation
if (resolver.route.name == router.current.name) {
debugPrint('DuplicateGuard: Preventing duplicate route navigation for ${resolver.route.name}');
resolver.next(false);
} else {
resolver.next(true);
}
}
}

View File

@@ -0,0 +1,19 @@
import 'package:auto_route/auto_route.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/routing/router.dart';
class GalleryPermissionGuard extends AutoRouteGuard {
final GalleryPermissionNotifier _permission;
GalleryPermissionGuard(this._permission);
@override
void onNavigation(NavigationResolver resolver, StackRouter router) async {
final p = _permission.hasPermission;
if (p) {
resolver.next(true);
} else {
router.replaceAll([const PermissionOnboardingRoute()]);
}
}
}

View File

@@ -19,10 +19,14 @@ import 'package:immich_mobile/modules/favorite/views/favorites_page.dart';
import 'package:immich_mobile/modules/home/views/home_page.dart';
import 'package:immich_mobile/modules/login/views/change_password_page.dart';
import 'package:immich_mobile/modules/login/views/login_page.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/modules/onboarding/views/permission_onboarding_page.dart';
import 'package:immich_mobile/modules/search/views/search_page.dart';
import 'package:immich_mobile/modules/search/views/search_result_page.dart';
import 'package:immich_mobile/modules/settings/views/settings_page.dart';
import 'package:immich_mobile/routing/auth_guard.dart';
import 'package:immich_mobile/routing/duplicate_guard.dart';
import 'package:immich_mobile/routing/gallery_permission_guard.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/album.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
@@ -38,49 +42,59 @@ part 'router.gr.dart';
replaceInRouteName: 'Page,Route',
routes: <AutoRoute>[
AutoRoute(page: SplashScreenPage, initial: true),
AutoRoute(page: LoginPage),
AutoRoute(page: PermissionOnboardingPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: LoginPage,
guards: [
DuplicateGuard,
],
),
AutoRoute(page: ChangePasswordPage),
CustomRoute(
page: TabControllerPage,
guards: [AuthGuard],
guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard],
children: [
AutoRoute(page: HomePage, guards: [AuthGuard]),
AutoRoute(page: SearchPage, guards: [AuthGuard]),
AutoRoute(page: SharingPage, guards: [AuthGuard]),
AutoRoute(page: LibraryPage, guards: [AuthGuard])
AutoRoute(page: HomePage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SearchPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SharingPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: LibraryPage, guards: [AuthGuard, DuplicateGuard])
],
transitionsBuilder: TransitionsBuilders.fadeIn,
),
AutoRoute(page: GalleryViewerPage, guards: [AuthGuard]),
AutoRoute(page: VideoViewerPage, guards: [AuthGuard]),
AutoRoute(page: BackupControllerPage, guards: [AuthGuard]),
AutoRoute(page: SearchResultPage, guards: [AuthGuard]),
AutoRoute(page: CreateAlbumPage, guards: [AuthGuard]),
AutoRoute(page: FavoritesPage, guards: [AuthGuard]),
AutoRoute(page: GalleryViewerPage, guards: [AuthGuard, DuplicateGuard, GalleryPermissionGuard]),
AutoRoute(page: VideoViewerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: BackupControllerPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: SearchResultPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: CreateAlbumPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: FavoritesPage, guards: [AuthGuard, DuplicateGuard]),
CustomRoute<AssetSelectionPageResult?>(
page: AssetSelectionPage,
guards: [AuthGuard],
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom,
),
CustomRoute<List<String>>(
page: SelectUserForSharingPage,
guards: [AuthGuard],
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom,
),
AutoRoute(page: AlbumViewerPage, guards: [AuthGuard]),
AutoRoute(page: AlbumViewerPage, guards: [AuthGuard, DuplicateGuard]),
CustomRoute<List<String>?>(
page: SelectAdditionalUserForSharingPage,
guards: [AuthGuard],
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom,
),
AutoRoute(page: BackupAlbumSelectionPage, guards: [AuthGuard]),
AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard]),
AutoRoute(page: BackupAlbumSelectionPage, guards: [AuthGuard, DuplicateGuard]),
AutoRoute(page: AlbumPreviewPage, guards: [AuthGuard, DuplicateGuard]),
CustomRoute(
page: FailedBackupStatusPage,
guards: [AuthGuard],
guards: [AuthGuard, DuplicateGuard],
transitionsBuilder: TransitionsBuilders.slideBottom,
),
AutoRoute(page: SettingsPage, guards: [AuthGuard]),
AutoRoute(page: SettingsPage,
guards: [
AuthGuard,
DuplicateGuard,
],
),
CustomRoute(
page: AppLogPage,
transitionsBuilder: TransitionsBuilders.slideBottom,
@@ -91,8 +105,15 @@ class AppRouter extends _$AppRouter {
// ignore: unused_field
final ApiService _apiService;
AppRouter(this._apiService) : super(authGuard: AuthGuard(_apiService));
AppRouter(
this._apiService,
GalleryPermissionNotifier galleryPermissionNotifier,
) : super(
authGuard: AuthGuard(_apiService),
duplicateGuard: DuplicateGuard(),
galleryPermissionGuard: GalleryPermissionGuard(galleryPermissionNotifier),
);
}
final appRouterProvider =
Provider((ref) => AppRouter(ref.watch(apiServiceProvider)));
Provider((ref) => AppRouter(ref.watch(apiServiceProvider), ref.watch(galleryPermissionNotifier.notifier)));

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class ImmichLogo extends StatelessWidget {
final double size;
final dynamic heroTag;
const ImmichLogo({
super.key,
this.size = 100,
this.heroTag,
});
@override
Widget build(BuildContext context) {
return Hero(
tag: heroTag,
child: Image(
image: const AssetImage('assets/immich-logo-no-outline.png'),
width: size,
filterQuality: FilterQuality.high,
),
);
}
}

View File

@@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class ImmichTitleText extends StatelessWidget {
final double fontSize;
final Color? color;
const ImmichTitleText({
super.key,
this.fontSize = 48,
this.color,
});
@override
Widget build(BuildContext context) {
return Text(
'IMMICH',
style: TextStyle(
fontFamily: 'SnowburstOne',
fontWeight: FontWeight.bold,
fontSize: fontSize,
color: color ?? Theme.of(context).primaryColor,
),
);
}
}

View File

@@ -7,6 +7,7 @@ import 'package:immich_mobile/constants/hive_box.dart';
import 'package:immich_mobile/modules/backup/providers/backup.provider.dart';
import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart';
import 'package:immich_mobile/routing/router.dart';
import 'package:immich_mobile/shared/providers/api.provider.dart';
@@ -32,8 +33,13 @@ class SplashScreenPage extends HookConsumerWidget {
serverUrl: loginInfo.serverUrl,
);
if (isSuccess) {
// Resume backup (if enable) then navigate
ref.watch(backupProvider.notifier).resumeBackup();
final hasPermission = await ref
.read(galleryPermissionNotifier.notifier)
.hasPermission;
if (hasPermission) {
// Resume backup (if enable) then navigate
ref.watch(backupProvider.notifier).resumeBackup();
}
AutoRouter.of(context).replace(const TabControllerRoute());
} else {
AutoRouter.of(context).replace(const LoginRoute());

View File

@@ -3,7 +3,7 @@ Immich API
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.48.1
- API version: 1.49.0
- Build package: org.openapitools.codegen.languages.DartClientCodegen
## Requirements
@@ -39,6 +39,16 @@ Please follow the [installation procedure](#installation--usage) and then run th
```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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final aPIKeyCreateDto = APIKeyCreateDto(); // APIKeyCreateDto |
@@ -231,6 +241,12 @@ Class | Method | HTTP request | Description
- **Type**: HTTP Bearer authentication
## cookie
- **Type**: API key
- **API key parameter name**: immich_access_token
- **Location**:
## Author

View File

@@ -26,6 +26,16 @@ Method | HTTP request | Description
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final aPIKeyCreateDto = APIKeyCreateDto(); // APIKeyCreateDto |
@@ -50,7 +60,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -69,6 +79,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final id = id_example; // String |
@@ -92,7 +112,7 @@ void (empty response body)
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -111,6 +131,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final id = id_example; // String |
@@ -135,7 +165,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -154,6 +184,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
@@ -174,7 +214,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -193,6 +233,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = APIKeyApi();
final id = id_example; // String |
@@ -219,7 +269,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -24,7 +24,7 @@ Method | HTTP request | Description
# **addAssetsToAlbum**
> AddAssetsResponseDto addAssetsToAlbum(albumId, addAssetsDto)
> AddAssetsResponseDto addAssetsToAlbum(albumId, addAssetsDto, key)
@@ -39,13 +39,18 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final addAssetsDto = AddAssetsDto(); // AddAssetsDto |
final key = key_example; // String |
try {
final result = api_instance.addAssetsToAlbum(albumId, addAssetsDto);
final result = api_instance.addAssetsToAlbum(albumId, addAssetsDto, key);
print(result);
} catch (e) {
print('Exception when calling AlbumApi->addAssetsToAlbum: $e\n');
@@ -58,6 +63,7 @@ Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**albumId** | **String**| |
**addAssetsDto** | [**AddAssetsDto**](AddAssetsDto.md)| |
**key** | **String**| | [optional]
### Return type
@@ -65,7 +71,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -90,6 +96,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
@@ -116,7 +126,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -141,6 +151,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final createAlbumDto = CreateAlbumDto(); // CreateAlbumDto |
@@ -165,7 +179,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -190,6 +204,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final createAlbumShareLinkDto = CreateAlbumShareLinkDto(); // CreateAlbumShareLinkDto |
@@ -214,7 +232,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -239,6 +257,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
@@ -262,7 +284,7 @@ void (empty response body)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -272,7 +294,7 @@ void (empty response body)
[[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)
# **downloadArchive**
> Object downloadArchive(albumId, skip)
> MultipartFile downloadArchive(albumId, skip, key)
@@ -287,13 +309,18 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final skip = 8.14; // num |
final key = key_example; // String |
try {
final result = api_instance.downloadArchive(albumId, skip);
final result = api_instance.downloadArchive(albumId, skip, key);
print(result);
} catch (e) {
print('Exception when calling AlbumApi->downloadArchive: $e\n');
@@ -306,19 +333,20 @@ Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**albumId** | **String**| |
**skip** | **num**| | [optional]
**key** | **String**| | [optional]
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
- **Accept**: application/zip
[[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)
@@ -338,6 +366,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
@@ -358,7 +390,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -368,7 +400,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAlbumInfo**
> AlbumResponseDto getAlbumInfo(albumId)
> AlbumResponseDto getAlbumInfo(albumId, key)
@@ -383,12 +415,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
final key = key_example; // String |
try {
final result = api_instance.getAlbumInfo(albumId);
final result = api_instance.getAlbumInfo(albumId, key);
print(result);
} catch (e) {
print('Exception when calling AlbumApi->getAlbumInfo: $e\n');
@@ -400,6 +437,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**albumId** | **String**| |
**key** | **String**| | [optional]
### Return type
@@ -407,7 +445,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -432,6 +470,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final shared = true; // bool |
@@ -458,7 +500,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -483,6 +525,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
@@ -509,7 +555,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -534,6 +580,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
@@ -559,7 +609,7 @@ void (empty response body)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -584,6 +634,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AlbumApi();
final albumId = albumId_example; // String |
@@ -610,7 +664,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -35,7 +35,7 @@ Method | HTTP request | Description
# **addAssetsToSharedLink**
> SharedLinkResponseDto addAssetsToSharedLink(addAssetsDto)
> SharedLinkResponseDto addAssetsToSharedLink(addAssetsDto, key)
@@ -50,12 +50,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final addAssetsDto = AddAssetsDto(); // AddAssetsDto |
final key = key_example; // String |
try {
final result = api_instance.addAssetsToSharedLink(addAssetsDto);
final result = api_instance.addAssetsToSharedLink(addAssetsDto, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->addAssetsToSharedLink: $e\n');
@@ -67,6 +72,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**addAssetsDto** | [**AddAssetsDto**](AddAssetsDto.md)| |
**key** | **String**| | [optional]
### Return type
@@ -74,7 +80,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -84,7 +90,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)
# **checkDuplicateAsset**
> CheckDuplicateAssetResponseDto checkDuplicateAsset(checkDuplicateAssetDto)
> CheckDuplicateAssetResponseDto checkDuplicateAsset(checkDuplicateAssetDto, key)
@@ -99,12 +105,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final checkDuplicateAssetDto = CheckDuplicateAssetDto(); // CheckDuplicateAssetDto |
final key = key_example; // String |
try {
final result = api_instance.checkDuplicateAsset(checkDuplicateAssetDto);
final result = api_instance.checkDuplicateAsset(checkDuplicateAssetDto, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->checkDuplicateAsset: $e\n');
@@ -116,6 +127,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**checkDuplicateAssetDto** | [**CheckDuplicateAssetDto**](CheckDuplicateAssetDto.md)| |
**key** | **String**| | [optional]
### Return type
@@ -123,7 +135,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -148,6 +160,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final checkExistingAssetsDto = CheckExistingAssetsDto(); // CheckExistingAssetsDto |
@@ -172,7 +188,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -197,6 +213,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final createAssetsShareLinkDto = CreateAssetsShareLinkDto(); // CreateAssetsShareLinkDto |
@@ -221,7 +241,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -246,6 +266,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final deleteAssetDto = DeleteAssetDto(); // DeleteAssetDto |
@@ -270,7 +294,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -280,7 +304,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)
# **downloadFile**
> Object downloadFile(assetId)
> MultipartFile downloadFile(assetId, key)
@@ -295,12 +319,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final key = key_example; // String |
try {
final result = api_instance.downloadFile(assetId);
final result = api_instance.downloadFile(assetId, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->downloadFile: $e\n');
@@ -312,24 +341,25 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**key** | **String**| | [optional]
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
- **Accept**: application/octet-stream
[[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)
# **downloadFiles**
> Object downloadFiles(downloadFilesDto)
> MultipartFile downloadFiles(downloadFilesDto, key)
@@ -344,12 +374,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final downloadFilesDto = DownloadFilesDto(); // DownloadFilesDto |
final key = key_example; // String |
try {
final result = api_instance.downloadFiles(downloadFilesDto);
final result = api_instance.downloadFiles(downloadFilesDto, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->downloadFiles: $e\n');
@@ -361,24 +396,25 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**downloadFilesDto** | [**DownloadFilesDto**](DownloadFilesDto.md)| |
**key** | **String**| | [optional]
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
- **Content-Type**: application/json
- **Accept**: application/json
- **Accept**: application/octet-stream
[[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)
# **downloadLibrary**
> Object downloadLibrary(skip)
> MultipartFile downloadLibrary(skip, key)
@@ -393,12 +429,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final skip = 8.14; // num |
final key = key_example; // String |
try {
final result = api_instance.downloadLibrary(skip);
final result = api_instance.downloadLibrary(skip, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->downloadLibrary: $e\n');
@@ -410,19 +451,20 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**skip** | **num**| | [optional]
**key** | **String**| | [optional]
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
- **Accept**: application/octet-stream
[[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)
@@ -442,6 +484,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final isFavorite = true; // bool |
@@ -470,7 +516,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -480,7 +526,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)
# **getAssetById**
> AssetResponseDto getAssetById(assetId)
> AssetResponseDto getAssetById(assetId, key)
@@ -495,12 +541,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final key = key_example; // String |
try {
final result = api_instance.getAssetById(assetId);
final result = api_instance.getAssetById(assetId, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getAssetById: $e\n');
@@ -512,6 +563,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**key** | **String**| | [optional]
### Return type
@@ -519,7 +571,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -544,6 +596,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final getAssetByTimeBucketDto = GetAssetByTimeBucketDto(); // GetAssetByTimeBucketDto |
@@ -568,7 +624,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -593,6 +649,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final getAssetCountByTimeBucketDto = GetAssetCountByTimeBucketDto(); // GetAssetCountByTimeBucketDto |
@@ -617,7 +677,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -642,6 +702,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
@@ -662,7 +726,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -687,6 +751,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
@@ -707,7 +775,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -717,7 +785,7 @@ This endpoint does not need any parameter.
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
# **getAssetThumbnail**
> Object getAssetThumbnail(assetId, format)
> MultipartFile getAssetThumbnail(assetId, format, key)
@@ -732,13 +800,18 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final format = ; // ThumbnailFormat |
final key = key_example; // String |
try {
final result = api_instance.getAssetThumbnail(assetId, format);
final result = api_instance.getAssetThumbnail(assetId, format, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->getAssetThumbnail: $e\n');
@@ -751,19 +824,20 @@ Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**assetId** | **String**| |
**format** | [**ThumbnailFormat**](.md)| | [optional]
**key** | **String**| | [optional]
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
- **Accept**: application/octet-stream
[[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)
@@ -783,6 +857,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
@@ -803,7 +881,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -828,6 +906,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
@@ -848,7 +930,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -873,6 +955,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final deviceId = deviceId_example; // String |
@@ -897,7 +983,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -907,7 +993,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)
# **removeAssetsFromSharedLink**
> SharedLinkResponseDto removeAssetsFromSharedLink(removeAssetsDto)
> SharedLinkResponseDto removeAssetsFromSharedLink(removeAssetsDto, key)
@@ -922,12 +1008,17 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final removeAssetsDto = RemoveAssetsDto(); // RemoveAssetsDto |
final key = key_example; // String |
try {
final result = api_instance.removeAssetsFromSharedLink(removeAssetsDto);
final result = api_instance.removeAssetsFromSharedLink(removeAssetsDto, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->removeAssetsFromSharedLink: $e\n');
@@ -939,6 +1030,7 @@ try {
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**removeAssetsDto** | [**RemoveAssetsDto**](RemoveAssetsDto.md)| |
**key** | **String**| | [optional]
### Return type
@@ -946,7 +1038,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -971,6 +1063,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final searchAssetDto = SearchAssetDto(); // SearchAssetDto |
@@ -995,7 +1091,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -1005,7 +1101,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)
# **serveFile**
> Object serveFile(assetId, isThumb, isWeb)
> MultipartFile serveFile(assetId, isThumb, isWeb, key)
@@ -1020,14 +1116,19 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
final isThumb = true; // bool |
final isWeb = true; // bool |
final key = key_example; // String |
try {
final result = api_instance.serveFile(assetId, isThumb, isWeb);
final result = api_instance.serveFile(assetId, isThumb, isWeb, key);
print(result);
} catch (e) {
print('Exception when calling AssetApi->serveFile: $e\n');
@@ -1041,19 +1142,20 @@ Name | Type | Description | Notes
**assetId** | **String**| |
**isThumb** | **bool**| | [optional]
**isWeb** | **bool**| | [optional]
**key** | **String**| | [optional]
### Return type
[**Object**](Object.md)
[**MultipartFile**](MultipartFile.md)
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: application/json
- **Accept**: application/octet-stream
[[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)
@@ -1073,6 +1175,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetId = assetId_example; // String |
@@ -1099,7 +1205,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -1109,7 +1215,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)
# **uploadFile**
> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration)
> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration)
@@ -1124,6 +1230,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AssetApi();
final assetType = ; // AssetTypeEnum |
@@ -1134,12 +1244,13 @@ final fileCreatedAt = fileCreatedAt_example; // String |
final fileModifiedAt = fileModifiedAt_example; // String |
final isFavorite = true; // bool |
final fileExtension = fileExtension_example; // String |
final key = key_example; // String |
final livePhotoData = BINARY_DATA_HERE; // MultipartFile |
final isVisible = true; // bool |
final duration = duration_example; // String |
try {
final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData, isVisible, duration);
final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration);
print(result);
} catch (e) {
print('Exception when calling AssetApi->uploadFile: $e\n');
@@ -1158,6 +1269,7 @@ Name | Type | Description | Notes
**fileModifiedAt** | **String**| |
**isFavorite** | **bool**| |
**fileExtension** | **String**| |
**key** | **String**| | [optional]
**livePhotoData** | **MultipartFile**| | [optional]
**isVisible** | **bool**| | [optional]
**duration** | **String**| | [optional]
@@ -1168,7 +1280,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -75,6 +75,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AuthenticationApi();
final changePasswordDto = ChangePasswordDto(); // ChangePasswordDto |
@@ -99,7 +103,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -161,6 +165,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AuthenticationApi();
@@ -181,7 +195,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -206,6 +220,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = AuthenticationApi();
@@ -226,7 +244,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -28,6 +28,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = DeviceInfoApi();
final upsertDeviceInfoDto = UpsertDeviceInfoDto(); // UpsertDeviceInfoDto |
@@ -52,7 +56,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -29,6 +29,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = JobApi();
@@ -49,7 +53,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -74,6 +78,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = JobApi();
final jobId = ; // JobId |
@@ -100,7 +108,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -112,6 +112,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = OAuthApi();
final oAuthCallbackDto = OAuthCallbackDto(); // OAuthCallbackDto |
@@ -136,7 +146,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -193,6 +203,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = OAuthApi();
@@ -213,7 +233,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -103,6 +103,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ServerInfoApi();
@@ -123,7 +133,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -8,11 +8,9 @@ import 'package:openapi/api.dart';
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**photos** | **int** | |
**videos** | **int** | |
**objects** | **int** | |
**usageRaw** | **int** | |
**usage** | **String** | |
**photos** | **int** | | [default to 0]
**videos** | **int** | | [default to 0]
**usage** | **int** | | [default to 0]
**usageByUser** | [**List<UsageByUserDto>**](UsageByUserDto.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)

View File

@@ -26,6 +26,16 @@ Method | HTTP request | Description
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final id = id_example; // String |
@@ -52,7 +62,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -71,6 +81,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
@@ -91,7 +111,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -101,7 +121,7 @@ No authorization required
[[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)
# **getMySharedLink**
> SharedLinkResponseDto getMySharedLink()
> SharedLinkResponseDto getMySharedLink(key)
@@ -110,11 +130,22 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final key = key_example; // String |
try {
final result = api_instance.getMySharedLink();
final result = api_instance.getMySharedLink(key);
print(result);
} catch (e) {
print('Exception when calling ShareApi->getMySharedLink: $e\n');
@@ -122,7 +153,10 @@ try {
```
### Parameters
This endpoint does not need any parameter.
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------
**key** | **String**| | [optional]
### Return type
@@ -130,7 +164,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -149,6 +183,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final id = id_example; // String |
@@ -173,7 +217,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -192,6 +236,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = ShareApi();
final id = id_example; // String |
@@ -215,7 +269,7 @@ void (empty response body)
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -31,6 +31,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = SystemConfigApi();
@@ -51,7 +55,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -76,6 +80,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = SystemConfigApi();
@@ -96,7 +104,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -121,6 +129,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = SystemConfigApi();
@@ -141,7 +153,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -166,6 +178,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = SystemConfigApi();
final systemConfigDto = SystemConfigDto(); // SystemConfigDto |
@@ -190,7 +206,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -26,6 +26,16 @@ Method | HTTP request | Description
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final createTagDto = CreateTagDto(); // CreateTagDto |
@@ -50,7 +60,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -69,6 +79,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final id = id_example; // String |
@@ -92,7 +112,7 @@ void (empty response body)
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -111,6 +131,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
@@ -131,7 +161,7 @@ This endpoint does not need any parameter.
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -150,6 +180,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final id = id_example; // String |
@@ -174,7 +214,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -193,6 +233,16 @@ No authorization required
### 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);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = TagApi();
final id = id_example; // String |
@@ -219,7 +269,7 @@ Name | Type | Description | Notes
### Authorization
No authorization required
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -9,10 +9,11 @@ import 'package:openapi/api.dart';
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**userId** | **String** | |
**videos** | **int** | |
**userFirstName** | **String** | |
**userLastName** | **String** | |
**photos** | **int** | |
**usageRaw** | **int** | |
**usage** | **String** | |
**videos** | **int** | |
**usage** | **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)

View File

@@ -37,6 +37,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final file = BINARY_DATA_HERE; // MultipartFile |
@@ -61,7 +65,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -86,6 +90,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final createUserDto = CreateUserDto(); // CreateUserDto |
@@ -110,7 +118,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -135,6 +143,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final userId = userId_example; // String |
@@ -159,7 +171,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -184,6 +196,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final isAll = true; // bool |
@@ -208,7 +224,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -233,6 +249,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
@@ -253,7 +273,7 @@ This endpoint does not need any parameter.
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -407,6 +427,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final userId = userId_example; // String |
@@ -431,7 +455,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers
@@ -456,6 +480,10 @@ import 'package:openapi/api.dart';
// Case 2. Use Function which generate token.
// String yourTokenGeneratorFunction() { ... }
//defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction);
// TODO Configure API key authorization: cookie
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKey = 'YOUR_API_KEY';
// uncomment below to setup prefix (e.g. Bearer) for API key, if needed
//defaultApiClient.getAuthentication<ApiKeyAuth>('cookie').apiKeyPrefix = 'Bearer';
final api_instance = UserApi();
final updateUserDto = UpdateUserDto(); // UpdateUserDto |
@@ -480,7 +508,7 @@ Name | Type | Description | Notes
### Authorization
[bearer](../README.md#bearer)
[bearer](../README.md#bearer), [cookie](../README.md#cookie)
### HTTP request headers

View File

@@ -25,7 +25,9 @@ class AlbumApi {
/// * [String] albumId (required):
///
/// * [AddAssetsDto] addAssetsDto (required):
Future<Response> addAssetsToAlbumWithHttpInfo(String albumId, AddAssetsDto addAssetsDto,) async {
///
/// * [String] key:
Future<Response> addAssetsToAlbumWithHttpInfo(String albumId, AddAssetsDto addAssetsDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/album/{albumId}/assets'
.replaceAll('{albumId}', albumId);
@@ -37,6 +39,10 @@ class AlbumApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['application/json'];
@@ -58,8 +64,10 @@ class AlbumApi {
/// * [String] albumId (required):
///
/// * [AddAssetsDto] addAssetsDto (required):
Future<AddAssetsResponseDto?> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto,) async {
final response = await addAssetsToAlbumWithHttpInfo(albumId, addAssetsDto,);
///
/// * [String] key:
Future<AddAssetsResponseDto?> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto, { String? key, }) async {
final response = await addAssetsToAlbumWithHttpInfo(albumId, addAssetsDto, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -288,7 +296,9 @@ class AlbumApi {
/// * [String] albumId (required):
///
/// * [num] skip:
Future<Response> downloadArchiveWithHttpInfo(String albumId, { num? skip, }) async {
///
/// * [String] key:
Future<Response> downloadArchiveWithHttpInfo(String albumId, { num? skip, String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/album/{albumId}/download'
.replaceAll('{albumId}', albumId);
@@ -303,6 +313,9 @@ class AlbumApi {
if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip));
}
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -325,8 +338,10 @@ class AlbumApi {
/// * [String] albumId (required):
///
/// * [num] skip:
Future<Object?> downloadArchive(String albumId, { num? skip, }) async {
final response = await downloadArchiveWithHttpInfo(albumId, skip: skip, );
///
/// * [String] key:
Future<MultipartFile?> downloadArchive(String albumId, { num? skip, String? key, }) async {
final response = await downloadArchiveWithHttpInfo(albumId, skip: skip, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -334,7 +349,7 @@ class AlbumApi {
// 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), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
@@ -391,7 +406,9 @@ class AlbumApi {
/// Parameters:
///
/// * [String] albumId (required):
Future<Response> getAlbumInfoWithHttpInfo(String albumId,) async {
///
/// * [String] key:
Future<Response> getAlbumInfoWithHttpInfo(String albumId, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/album/{albumId}'
.replaceAll('{albumId}', albumId);
@@ -403,6 +420,10 @@ class AlbumApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -422,8 +443,10 @@ class AlbumApi {
/// Parameters:
///
/// * [String] albumId (required):
Future<AlbumResponseDto?> getAlbumInfo(String albumId,) async {
final response = await getAlbumInfoWithHttpInfo(albumId,);
///
/// * [String] key:
Future<AlbumResponseDto?> getAlbumInfo(String albumId, { String? key, }) async {
final response = await getAlbumInfoWithHttpInfo(albumId, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -23,7 +23,9 @@ class AssetApi {
/// Parameters:
///
/// * [AddAssetsDto] addAssetsDto (required):
Future<Response> addAssetsToSharedLinkWithHttpInfo(AddAssetsDto addAssetsDto,) async {
///
/// * [String] key:
Future<Response> addAssetsToSharedLinkWithHttpInfo(AddAssetsDto addAssetsDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/shared-link/add';
@@ -34,6 +36,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['application/json'];
@@ -53,8 +59,10 @@ class AssetApi {
/// Parameters:
///
/// * [AddAssetsDto] addAssetsDto (required):
Future<SharedLinkResponseDto?> addAssetsToSharedLink(AddAssetsDto addAssetsDto,) async {
final response = await addAssetsToSharedLinkWithHttpInfo(addAssetsDto,);
///
/// * [String] key:
Future<SharedLinkResponseDto?> addAssetsToSharedLink(AddAssetsDto addAssetsDto, { String? key, }) async {
final response = await addAssetsToSharedLinkWithHttpInfo(addAssetsDto, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -75,7 +83,9 @@ class AssetApi {
/// Parameters:
///
/// * [CheckDuplicateAssetDto] checkDuplicateAssetDto (required):
Future<Response> checkDuplicateAssetWithHttpInfo(CheckDuplicateAssetDto checkDuplicateAssetDto,) async {
///
/// * [String] key:
Future<Response> checkDuplicateAssetWithHttpInfo(CheckDuplicateAssetDto checkDuplicateAssetDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/check';
@@ -86,6 +96,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['application/json'];
@@ -105,8 +119,10 @@ class AssetApi {
/// Parameters:
///
/// * [CheckDuplicateAssetDto] checkDuplicateAssetDto (required):
Future<CheckDuplicateAssetResponseDto?> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto,) async {
final response = await checkDuplicateAssetWithHttpInfo(checkDuplicateAssetDto,);
///
/// * [String] key:
Future<CheckDuplicateAssetResponseDto?> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto, { String? key, }) async {
final response = await checkDuplicateAssetWithHttpInfo(checkDuplicateAssetDto, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -286,7 +302,9 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
Future<Response> downloadFileWithHttpInfo(String assetId,) async {
///
/// * [String] key:
Future<Response> downloadFileWithHttpInfo(String assetId, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/download/{assetId}'
.replaceAll('{assetId}', assetId);
@@ -298,6 +316,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -317,8 +339,10 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
Future<Object?> downloadFile(String assetId,) async {
final response = await downloadFileWithHttpInfo(assetId,);
///
/// * [String] key:
Future<MultipartFile?> downloadFile(String assetId, { String? key, }) async {
final response = await downloadFileWithHttpInfo(assetId, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -326,7 +350,7 @@ class AssetApi {
// 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), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
@@ -339,7 +363,9 @@ class AssetApi {
/// Parameters:
///
/// * [DownloadFilesDto] downloadFilesDto (required):
Future<Response> downloadFilesWithHttpInfo(DownloadFilesDto downloadFilesDto,) async {
///
/// * [String] key:
Future<Response> downloadFilesWithHttpInfo(DownloadFilesDto downloadFilesDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/download-files';
@@ -350,6 +376,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['application/json'];
@@ -369,8 +399,10 @@ class AssetApi {
/// Parameters:
///
/// * [DownloadFilesDto] downloadFilesDto (required):
Future<Object?> downloadFiles(DownloadFilesDto downloadFilesDto,) async {
final response = await downloadFilesWithHttpInfo(downloadFilesDto,);
///
/// * [String] key:
Future<MultipartFile?> downloadFiles(DownloadFilesDto downloadFilesDto, { String? key, }) async {
final response = await downloadFilesWithHttpInfo(downloadFilesDto, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -378,7 +410,7 @@ class AssetApi {
// 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), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
@@ -391,7 +423,9 @@ class AssetApi {
/// Parameters:
///
/// * [num] skip:
Future<Response> downloadLibraryWithHttpInfo({ num? skip, }) async {
///
/// * [String] key:
Future<Response> downloadLibraryWithHttpInfo({ num? skip, String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/download-library';
@@ -405,6 +439,9 @@ class AssetApi {
if (skip != null) {
queryParams.addAll(_queryParams('', 'skip', skip));
}
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -425,8 +462,10 @@ class AssetApi {
/// Parameters:
///
/// * [num] skip:
Future<Object?> downloadLibrary({ num? skip, }) async {
final response = await downloadLibraryWithHttpInfo( skip: skip, );
///
/// * [String] key:
Future<MultipartFile?> downloadLibrary({ num? skip, String? key, }) async {
final response = await downloadLibraryWithHttpInfo( skip: skip, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -434,7 +473,7 @@ class AssetApi {
// 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), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
@@ -523,7 +562,9 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
Future<Response> getAssetByIdWithHttpInfo(String assetId,) async {
///
/// * [String] key:
Future<Response> getAssetByIdWithHttpInfo(String assetId, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/assetById/{assetId}'
.replaceAll('{assetId}', assetId);
@@ -535,6 +576,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -554,8 +599,10 @@ class AssetApi {
/// Parameters:
///
/// * [String] assetId (required):
Future<AssetResponseDto?> getAssetById(String assetId,) async {
final response = await getAssetByIdWithHttpInfo(assetId,);
///
/// * [String] key:
Future<AssetResponseDto?> getAssetById(String assetId, { String? key, }) async {
final response = await getAssetByIdWithHttpInfo(assetId, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -776,7 +823,9 @@ class AssetApi {
/// * [String] assetId (required):
///
/// * [ThumbnailFormat] format:
Future<Response> getAssetThumbnailWithHttpInfo(String assetId, { ThumbnailFormat? format, }) async {
///
/// * [String] key:
Future<Response> getAssetThumbnailWithHttpInfo(String assetId, { ThumbnailFormat? format, String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/thumbnail/{assetId}'
.replaceAll('{assetId}', assetId);
@@ -791,6 +840,9 @@ class AssetApi {
if (format != null) {
queryParams.addAll(_queryParams('', 'format', format));
}
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -813,8 +865,10 @@ class AssetApi {
/// * [String] assetId (required):
///
/// * [ThumbnailFormat] format:
Future<Object?> getAssetThumbnail(String assetId, { ThumbnailFormat? format, }) async {
final response = await getAssetThumbnailWithHttpInfo(assetId, format: format, );
///
/// * [String] key:
Future<MultipartFile?> getAssetThumbnail(String assetId, { ThumbnailFormat? format, String? key, }) async {
final response = await getAssetThumbnailWithHttpInfo(assetId, format: format, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -822,7 +876,7 @@ class AssetApi {
// 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), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
@@ -985,7 +1039,9 @@ class AssetApi {
/// Parameters:
///
/// * [RemoveAssetsDto] removeAssetsDto (required):
Future<Response> removeAssetsFromSharedLinkWithHttpInfo(RemoveAssetsDto removeAssetsDto,) async {
///
/// * [String] key:
Future<Response> removeAssetsFromSharedLinkWithHttpInfo(RemoveAssetsDto removeAssetsDto, { String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/shared-link/remove';
@@ -996,6 +1052,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['application/json'];
@@ -1015,8 +1075,10 @@ class AssetApi {
/// Parameters:
///
/// * [RemoveAssetsDto] removeAssetsDto (required):
Future<SharedLinkResponseDto?> removeAssetsFromSharedLink(RemoveAssetsDto removeAssetsDto,) async {
final response = await removeAssetsFromSharedLinkWithHttpInfo(removeAssetsDto,);
///
/// * [String] key:
Future<SharedLinkResponseDto?> removeAssetsFromSharedLink(RemoveAssetsDto removeAssetsDto, { String? key, }) async {
final response = await removeAssetsFromSharedLinkWithHttpInfo(removeAssetsDto, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -1096,7 +1158,9 @@ class AssetApi {
/// * [bool] isThumb:
///
/// * [bool] isWeb:
Future<Response> serveFileWithHttpInfo(String assetId, { bool? isThumb, bool? isWeb, }) async {
///
/// * [String] key:
Future<Response> serveFileWithHttpInfo(String assetId, { bool? isThumb, bool? isWeb, String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/file/{assetId}'
.replaceAll('{assetId}', assetId);
@@ -1114,6 +1178,9 @@ class AssetApi {
if (isWeb != null) {
queryParams.addAll(_queryParams('', 'isWeb', isWeb));
}
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -1138,8 +1205,10 @@ class AssetApi {
/// * [bool] isThumb:
///
/// * [bool] isWeb:
Future<Object?> serveFile(String assetId, { bool? isThumb, bool? isWeb, }) async {
final response = await serveFileWithHttpInfo(assetId, isThumb: isThumb, isWeb: isWeb, );
///
/// * [String] key:
Future<MultipartFile?> serveFile(String assetId, { bool? isThumb, bool? isWeb, String? key, }) async {
final response = await serveFileWithHttpInfo(assetId, isThumb: isThumb, isWeb: isWeb, key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}
@@ -1147,7 +1216,7 @@ class AssetApi {
// 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), 'Object',) as Object;
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'MultipartFile',) as MultipartFile;
}
return null;
@@ -1232,12 +1301,14 @@ class AssetApi {
///
/// * [String] fileExtension (required):
///
/// * [String] key:
///
/// * [MultipartFile] livePhotoData:
///
/// * [bool] isVisible:
///
/// * [String] duration:
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async {
Future<Response> uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async {
// ignore: prefer_const_declarations
final path = r'/asset/upload';
@@ -1248,6 +1319,10 @@ class AssetApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>['multipart/form-data'];
bool hasFields = false;
@@ -1333,13 +1408,15 @@ class AssetApi {
///
/// * [String] fileExtension (required):
///
/// * [String] key:
///
/// * [MultipartFile] livePhotoData:
///
/// * [bool] isVisible:
///
/// * [String] duration:
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async {
final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, livePhotoData: livePhotoData, isVisible: isVisible, duration: duration, );
Future<AssetFileUploadResponseDto?> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async {
final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, isVisible: isVisible, duration: duration, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -123,7 +123,11 @@ class ShareApi {
///
///
/// Note: This method returns the HTTP [Response].
Future<Response> getMySharedLinkWithHttpInfo() async {
///
/// Parameters:
///
/// * [String] key:
Future<Response> getMySharedLinkWithHttpInfo({ String? key, }) async {
// ignore: prefer_const_declarations
final path = r'/share/me';
@@ -134,6 +138,10 @@ class ShareApi {
final headerParams = <String, String>{};
final formParams = <String, String>{};
if (key != null) {
queryParams.addAll(_queryParams('', 'key', key));
}
const contentTypes = <String>[];
@@ -149,8 +157,12 @@ class ShareApi {
}
///
Future<SharedLinkResponseDto?> getMySharedLink() async {
final response = await getMySharedLinkWithHttpInfo();
///
/// Parameters:
///
/// * [String] key:
Future<SharedLinkResponseDto?> getMySharedLink({ String? key, }) async {
final response = await getMySharedLinkWithHttpInfo( key: key, );
if (response.statusCode >= HttpStatus.badRequest) {
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
}

View File

@@ -13,11 +13,9 @@ part of openapi.api;
class ServerStatsResponseDto {
/// Returns a new [ServerStatsResponseDto] instance.
ServerStatsResponseDto({
required this.photos,
required this.videos,
required this.objects,
required this.usageRaw,
required this.usage,
this.photos = 0,
this.videos = 0,
this.usage = 0,
this.usageByUser = const [],
});
@@ -25,11 +23,7 @@ class ServerStatsResponseDto {
int videos;
int objects;
int usageRaw;
String usage;
int usage;
List<UsageByUserDto> usageByUser;
@@ -37,8 +31,6 @@ class ServerStatsResponseDto {
bool operator ==(Object other) => identical(this, other) || other is ServerStatsResponseDto &&
other.photos == photos &&
other.videos == videos &&
other.objects == objects &&
other.usageRaw == usageRaw &&
other.usage == usage &&
other.usageByUser == usageByUser;
@@ -47,20 +39,16 @@ class ServerStatsResponseDto {
// ignore: unnecessary_parenthesis
(photos.hashCode) +
(videos.hashCode) +
(objects.hashCode) +
(usageRaw.hashCode) +
(usage.hashCode) +
(usageByUser.hashCode);
@override
String toString() => 'ServerStatsResponseDto[photos=$photos, videos=$videos, objects=$objects, usageRaw=$usageRaw, usage=$usage, usageByUser=$usageByUser]';
String toString() => 'ServerStatsResponseDto[photos=$photos, videos=$videos, usage=$usage, usageByUser=$usageByUser]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'photos'] = this.photos;
json[r'videos'] = this.videos;
json[r'objects'] = this.objects;
json[r'usageRaw'] = this.usageRaw;
json[r'usage'] = this.usage;
json[r'usageByUser'] = this.usageByUser;
return json;
@@ -87,9 +75,7 @@ class ServerStatsResponseDto {
return ServerStatsResponseDto(
photos: mapValueOfType<int>(json, r'photos')!,
videos: mapValueOfType<int>(json, r'videos')!,
objects: mapValueOfType<int>(json, r'objects')!,
usageRaw: mapValueOfType<int>(json, r'usageRaw')!,
usage: mapValueOfType<String>(json, r'usage')!,
usage: mapValueOfType<int>(json, r'usage')!,
usageByUser: UsageByUserDto.listFromJson(json[r'usageByUser'])!,
);
}
@@ -142,8 +128,6 @@ class ServerStatsResponseDto {
static const requiredKeys = <String>{
'photos',
'videos',
'objects',
'usageRaw',
'usage',
'usageByUser',
};

View File

@@ -14,48 +14,54 @@ class UsageByUserDto {
/// Returns a new [UsageByUserDto] instance.
UsageByUserDto({
required this.userId,
required this.videos,
required this.userFirstName,
required this.userLastName,
required this.photos,
required this.usageRaw,
required this.videos,
required this.usage,
});
String userId;
int videos;
String userFirstName;
String userLastName;
int photos;
int usageRaw;
int videos;
String usage;
int usage;
@override
bool operator ==(Object other) => identical(this, other) || other is UsageByUserDto &&
other.userId == userId &&
other.videos == videos &&
other.userFirstName == userFirstName &&
other.userLastName == userLastName &&
other.photos == photos &&
other.usageRaw == usageRaw &&
other.videos == videos &&
other.usage == usage;
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(userId.hashCode) +
(videos.hashCode) +
(userFirstName.hashCode) +
(userLastName.hashCode) +
(photos.hashCode) +
(usageRaw.hashCode) +
(videos.hashCode) +
(usage.hashCode);
@override
String toString() => 'UsageByUserDto[userId=$userId, videos=$videos, photos=$photos, usageRaw=$usageRaw, usage=$usage]';
String toString() => 'UsageByUserDto[userId=$userId, userFirstName=$userFirstName, userLastName=$userLastName, photos=$photos, videos=$videos, usage=$usage]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
json[r'userId'] = this.userId;
json[r'videos'] = this.videos;
json[r'userFirstName'] = this.userFirstName;
json[r'userLastName'] = this.userLastName;
json[r'photos'] = this.photos;
json[r'usageRaw'] = this.usageRaw;
json[r'videos'] = this.videos;
json[r'usage'] = this.usage;
return json;
}
@@ -80,10 +86,11 @@ class UsageByUserDto {
return UsageByUserDto(
userId: mapValueOfType<String>(json, r'userId')!,
videos: mapValueOfType<int>(json, r'videos')!,
userFirstName: mapValueOfType<String>(json, r'userFirstName')!,
userLastName: mapValueOfType<String>(json, r'userLastName')!,
photos: mapValueOfType<int>(json, r'photos')!,
usageRaw: mapValueOfType<int>(json, r'usageRaw')!,
usage: mapValueOfType<String>(json, r'usage')!,
videos: mapValueOfType<int>(json, r'videos')!,
usage: mapValueOfType<int>(json, r'usage')!,
);
}
return null;
@@ -134,9 +141,10 @@ class UsageByUserDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'userId',
'videos',
'userFirstName',
'userLastName',
'photos',
'usageRaw',
'videos',
'usage',
};
}

View File

@@ -19,7 +19,7 @@ void main() {
group('tests for AlbumApi', () {
//
//
//Future<AddAssetsResponseDto> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto) async
//Future<AddAssetsResponseDto> addAssetsToAlbum(String albumId, AddAssetsDto addAssetsDto, { String key }) async
test('test addAssetsToAlbum', () async {
// TODO
});
@@ -54,7 +54,7 @@ void main() {
//
//
//Future<Object> downloadArchive(String albumId, { num skip }) async
//Future<MultipartFile> downloadArchive(String albumId, { num skip, String key }) async
test('test downloadArchive', () async {
// TODO
});
@@ -68,7 +68,7 @@ void main() {
//
//
//Future<AlbumResponseDto> getAlbumInfo(String albumId) async
//Future<AlbumResponseDto> getAlbumInfo(String albumId, { String key }) async
test('test getAlbumInfo', () async {
// TODO
});

View File

@@ -19,14 +19,14 @@ void main() {
group('tests for AssetApi', () {
//
//
//Future<SharedLinkResponseDto> addAssetsToSharedLink(AddAssetsDto addAssetsDto) async
//Future<SharedLinkResponseDto> addAssetsToSharedLink(AddAssetsDto addAssetsDto, { String key }) async
test('test addAssetsToSharedLink', () async {
// TODO
});
// Check duplicated asset before uploading - for Web upload used
//
//Future<CheckDuplicateAssetResponseDto> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto) async
//Future<CheckDuplicateAssetResponseDto> checkDuplicateAsset(CheckDuplicateAssetDto checkDuplicateAssetDto, { String key }) async
test('test checkDuplicateAsset', () async {
// TODO
});
@@ -54,21 +54,21 @@ void main() {
//
//
//Future<Object> downloadFile(String assetId) async
//Future<MultipartFile> downloadFile(String assetId, { String key }) async
test('test downloadFile', () async {
// TODO
});
//
//
//Future<Object> downloadFiles(DownloadFilesDto downloadFilesDto) async
//Future<MultipartFile> downloadFiles(DownloadFilesDto downloadFilesDto, { String key }) async
test('test downloadFiles', () async {
// TODO
});
// Current this is not used in any UI element
//
//Future<Object> downloadLibrary({ num skip }) async
//Future<MultipartFile> downloadLibrary({ num skip, String key }) async
test('test downloadLibrary', () async {
// TODO
});
@@ -82,7 +82,7 @@ void main() {
// Get a single asset's information
//
//Future<AssetResponseDto> getAssetById(String assetId) async
//Future<AssetResponseDto> getAssetById(String assetId, { String key }) async
test('test getAssetById', () async {
// TODO
});
@@ -117,7 +117,7 @@ void main() {
//
//
//Future<Object> getAssetThumbnail(String assetId, { ThumbnailFormat format }) async
//Future<MultipartFile> getAssetThumbnail(String assetId, { ThumbnailFormat format, String key }) async
test('test getAssetThumbnail', () async {
// TODO
});
@@ -145,7 +145,7 @@ void main() {
//
//
//Future<SharedLinkResponseDto> removeAssetsFromSharedLink(RemoveAssetsDto removeAssetsDto) async
//Future<SharedLinkResponseDto> removeAssetsFromSharedLink(RemoveAssetsDto removeAssetsDto, { String key }) async
test('test removeAssetsFromSharedLink', () async {
// TODO
});
@@ -159,7 +159,7 @@ void main() {
//
//
//Future<Object> serveFile(String assetId, { bool isThumb, bool isWeb }) async
//Future<MultipartFile> serveFile(String assetId, { bool isThumb, bool isWeb, String key }) async
test('test serveFile', () async {
// TODO
});
@@ -173,7 +173,7 @@ void main() {
//
//
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { MultipartFile livePhotoData, bool isVisible, String duration }) async
//Future<AssetFileUploadResponseDto> uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, bool isVisible, String duration }) async
test('test uploadFile', () async {
// TODO
});

View File

@@ -16,27 +16,17 @@ void main() {
// final instance = ServerStatsResponseDto();
group('test ServerStatsResponseDto', () {
// int photos
// int photos (default value: 0)
test('to test the property `photos`', () async {
// TODO
});
// int videos
// int videos (default value: 0)
test('to test the property `videos`', () async {
// TODO
});
// int objects
test('to test the property `objects`', () async {
// TODO
});
// int usageRaw
test('to test the property `usageRaw`', () async {
// TODO
});
// String usage
// int usage (default value: 0)
test('to test the property `usage`', () async {
// TODO
});

View File

@@ -33,7 +33,7 @@ void main() {
//
//
//Future<SharedLinkResponseDto> getMySharedLink() async
//Future<SharedLinkResponseDto> getMySharedLink({ String key }) async
test('test getMySharedLink', () async {
// TODO
});

View File

@@ -21,8 +21,13 @@ void main() {
// TODO
});
// int videos
test('to test the property `videos`', () async {
// String userFirstName
test('to test the property `userFirstName`', () async {
// TODO
});
// String userLastName
test('to test the property `userLastName`', () async {
// TODO
});
@@ -31,12 +36,12 @@ void main() {
// TODO
});
// int usageRaw
test('to test the property `usageRaw`', () async {
// int videos
test('to test the property `videos`', () async {
// TODO
});
// String usage
// int usage
test('to test the property `usage`', () async {
// TODO
});

View File

@@ -281,6 +281,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
device_info_plus:
dependency: "direct main"
description:
name: device_info_plus
sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95"
url: "https://pub.dev"
source: hosted
version: "8.1.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64
url: "https://pub.dev"
source: hosted
version: "7.0.0"
easy_image_viewer:
dependency: "direct main"
description:
@@ -740,6 +756,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mockito:
dependency: "direct dev"
description:
name: mockito
sha256: "2a8a17b82b1bde04d514e75d90d634a0ac23f6cb4991f6098009dd56836aeafe"
url: "https://pub.dev"
source: hosted
version: "5.3.2"
nested:
dependency: transitive
description:

View File

@@ -2,7 +2,7 @@ name: immich_mobile
description: Immich - selfhosted backup media file on mobile phone
publish_to: "none"
version: 1.49.0+72
version: 1.50.0+73
isar_version: &isar_version 3.0.5
environment:
@@ -46,6 +46,7 @@ dependencies:
isar: *isar_version
isar_flutter_libs: *isar_version # contains Isar Core
permission_handler: ^10.2.0
device_info_plus: ^8.1.0
openapi:
path: openapi
@@ -64,6 +65,7 @@ dev_dependencies:
flutter_launcher_icons: "^0.9.2"
flutter_native_splash: ^2.2.16
isar_generator: *isar_version
mockito: ^5.3.2
integration_test:
sdk: flutter

View File

@@ -0,0 +1,104 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/favorite/providers/favorite_provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
@GenerateNiceMocks([
MockSpec<AssetsState>(),
MockSpec<AssetNotifier>(),
])
import 'favorite_provider_test.mocks.dart';
Asset _getTestAsset(String id, bool favorite) {
return Asset(
remoteId: id,
deviceAssetId: '',
deviceId: '',
ownerId: '',
fileCreatedAt: DateTime.now(),
fileModifiedAt: DateTime.now(),
durationInSeconds: 0,
fileName: '',
isFavorite: favorite,
);
}
void main() {
group("Test favoriteProvider", () {
late MockAssetsState assetsState;
late MockAssetNotifier assetNotifier;
late ProviderContainer container;
late StateNotifierProvider<FavoriteSelectionNotifier, Set<String>> testFavoritesProvider;
setUp(() {
assetsState = MockAssetsState();
assetNotifier = MockAssetNotifier();
container = ProviderContainer();
testFavoritesProvider =
StateNotifierProvider<FavoriteSelectionNotifier, Set<String>>((ref) {
return FavoriteSelectionNotifier(
assetsState,
assetNotifier,
);
});
},);
test("Empty favorites provider", () {
when(assetsState.allAssets).thenReturn([]);
expect(<String>{}, container.read(testFavoritesProvider));
});
test("Non-empty favorites provider", () {
when(assetsState.allAssets).thenReturn([
_getTestAsset("001", false),
_getTestAsset("002", true),
_getTestAsset("003", false),
_getTestAsset("004", false),
_getTestAsset("005", true),
]);
expect(<String>{"002", "005"}, container.read(testFavoritesProvider));
});
test("Toggle favorite", () {
when(assetNotifier.toggleFavorite(null, false))
.thenAnswer((_) async => false);
final testAsset1 = _getTestAsset("001", false);
final testAsset2 = _getTestAsset("002", true);
when(assetsState.allAssets).thenReturn([testAsset1, testAsset2]);
expect(<String>{"002"}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset2);
expect(<String>{}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).toggleFavorite(testAsset1);
expect(<String>{"001"}, container.read(testFavoritesProvider));
});
test("Add favorites", () {
when(assetNotifier.toggleFavorite(null, false))
.thenAnswer((_) async => false);
when(assetsState.allAssets).thenReturn([]);
expect(<String>{}, container.read(testFavoritesProvider));
container.read(testFavoritesProvider.notifier).addToFavorites(
[
_getTestAsset("001", false),
_getTestAsset("002", false),
],
);
expect(<String>{"001", "002"}, container.read(testFavoritesProvider));
});
});
}

View File

@@ -0,0 +1,259 @@
// Mocks generated by Mockito 5.3.2 from annotations
// in immich_mobile/test/favorite_provider_test.dart.
// Do not manually edit this file.
// ignore_for_file: no_leading_underscores_for_library_prefixes
import 'dart:async' as _i5;
import 'package:hooks_riverpod/hooks_riverpod.dart' as _i7;
import 'package:immich_mobile/modules/home/ui/asset_grid/asset_grid_data_structure.dart'
as _i6;
import 'package:immich_mobile/shared/models/asset.dart' as _i4;
import 'package:immich_mobile/shared/providers/asset.provider.dart' as _i2;
import 'package:logging/logging.dart' as _i3;
import 'package:mockito/mockito.dart' as _i1;
import 'package:state_notifier/state_notifier.dart' as _i8;
// ignore_for_file: type=lint
// ignore_for_file: avoid_redundant_argument_values
// ignore_for_file: avoid_setters_without_getters
// ignore_for_file: comment_references
// ignore_for_file: implementation_imports
// ignore_for_file: invalid_use_of_visible_for_testing_member
// ignore_for_file: prefer_const_constructors
// ignore_for_file: unnecessary_parenthesis
// ignore_for_file: camel_case_types
// ignore_for_file: subtype_of_sealed_class
class _FakeAssetsState_0 extends _i1.SmartFake implements _i2.AssetsState {
_FakeAssetsState_0(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
class _FakeLogger_1 extends _i1.SmartFake implements _i3.Logger {
_FakeLogger_1(
Object parent,
Invocation parentInvocation,
) : super(
parent,
parentInvocation,
);
}
/// A class which mocks [AssetsState].
///
/// See the documentation for Mockito's code generation for more information.
class MockAssetsState extends _i1.Mock implements _i2.AssetsState {
@override
List<_i4.Asset> get allAssets => (super.noSuchMethod(
Invocation.getter(#allAssets),
returnValue: <_i4.Asset>[],
returnValueForMissingStub: <_i4.Asset>[],
) as List<_i4.Asset>);
@override
_i5.Future<_i2.AssetsState> withRenderDataStructure(
_i6.AssetGridLayoutParameters? layout) =>
(super.noSuchMethod(
Invocation.method(
#withRenderDataStructure,
[layout],
),
returnValue: _i5.Future<_i2.AssetsState>.value(_FakeAssetsState_0(
this,
Invocation.method(
#withRenderDataStructure,
[layout],
),
)),
returnValueForMissingStub:
_i5.Future<_i2.AssetsState>.value(_FakeAssetsState_0(
this,
Invocation.method(
#withRenderDataStructure,
[layout],
),
)),
) as _i5.Future<_i2.AssetsState>);
@override
_i2.AssetsState withAdditionalAssets(List<_i4.Asset>? toAdd) =>
(super.noSuchMethod(
Invocation.method(
#withAdditionalAssets,
[toAdd],
),
returnValue: _FakeAssetsState_0(
this,
Invocation.method(
#withAdditionalAssets,
[toAdd],
),
),
returnValueForMissingStub: _FakeAssetsState_0(
this,
Invocation.method(
#withAdditionalAssets,
[toAdd],
),
),
) as _i2.AssetsState);
}
/// A class which mocks [AssetNotifier].
///
/// See the documentation for Mockito's code generation for more information.
class MockAssetNotifier extends _i1.Mock implements _i2.AssetNotifier {
@override
_i3.Logger get log => (super.noSuchMethod(
Invocation.getter(#log),
returnValue: _FakeLogger_1(
this,
Invocation.getter(#log),
),
returnValueForMissingStub: _FakeLogger_1(
this,
Invocation.getter(#log),
),
) as _i3.Logger);
@override
set onError(_i7.ErrorListener? _onError) => super.noSuchMethod(
Invocation.setter(
#onError,
_onError,
),
returnValueForMissingStub: null,
);
@override
bool get mounted => (super.noSuchMethod(
Invocation.getter(#mounted),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i5.Stream<_i2.AssetsState> get stream => (super.noSuchMethod(
Invocation.getter(#stream),
returnValue: _i5.Stream<_i2.AssetsState>.empty(),
returnValueForMissingStub: _i5.Stream<_i2.AssetsState>.empty(),
) as _i5.Stream<_i2.AssetsState>);
@override
_i2.AssetsState get state => (super.noSuchMethod(
Invocation.getter(#state),
returnValue: _FakeAssetsState_0(
this,
Invocation.getter(#state),
),
returnValueForMissingStub: _FakeAssetsState_0(
this,
Invocation.getter(#state),
),
) as _i2.AssetsState);
@override
set state(_i2.AssetsState? value) => super.noSuchMethod(
Invocation.setter(
#state,
value,
),
returnValueForMissingStub: null,
);
@override
_i2.AssetsState get debugState => (super.noSuchMethod(
Invocation.getter(#debugState),
returnValue: _FakeAssetsState_0(
this,
Invocation.getter(#debugState),
),
returnValueForMissingStub: _FakeAssetsState_0(
this,
Invocation.getter(#debugState),
),
) as _i2.AssetsState);
@override
bool get hasListeners => (super.noSuchMethod(
Invocation.getter(#hasListeners),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i5.Future<void> rebuildAssetGridDataStructure() => (super.noSuchMethod(
Invocation.method(
#rebuildAssetGridDataStructure,
[],
),
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
void onNewAssetUploaded(_i4.Asset? newAsset) => super.noSuchMethod(
Invocation.method(
#onNewAssetUploaded,
[newAsset],
),
returnValueForMissingStub: null,
);
@override
dynamic deleteAssets(Set<_i4.Asset>? deleteAssets) => super.noSuchMethod(
Invocation.method(
#deleteAssets,
[deleteAssets],
),
returnValueForMissingStub: null,
);
@override
_i5.Future<bool> toggleFavorite(
_i4.Asset? asset,
bool? status,
) =>
(super.noSuchMethod(
Invocation.method(
#toggleFavorite,
[
asset,
status,
],
),
returnValue: _i5.Future<bool>.value(false),
returnValueForMissingStub: _i5.Future<bool>.value(false),
) as _i5.Future<bool>);
@override
bool updateShouldNotify(
_i2.AssetsState? old,
_i2.AssetsState? current,
) =>
(super.noSuchMethod(
Invocation.method(
#updateShouldNotify,
[
old,
current,
],
),
returnValue: false,
returnValueForMissingStub: false,
) as bool);
@override
_i7.RemoveListener addListener(
_i8.Listener<_i2.AssetsState>? listener, {
bool? fireImmediately = true,
}) =>
(super.noSuchMethod(
Invocation.method(
#addListener,
[listener],
{#fireImmediately: fireImmediately},
),
returnValue: () {},
returnValueForMissingStub: () {},
) as _i7.RemoveListener);
@override
void dispose() => super.noSuchMethod(
Invocation.method(
#dispose,
[],
),
returnValueForMissingStub: null,
);
}

View File

@@ -79,6 +79,7 @@ export class AlbumRepository implements IAlbumRepository {
const queryProperties: FindManyOptions<AlbumEntity> = {
relations: { sharedUsers: true, assets: true, sharedLinks: true, owner: true },
select: { assets: { id: true } },
order: { assets: { fileCreatedAt: 'ASC' }, createdAt: 'ASC' },
};
@@ -112,10 +113,6 @@ export class AlbumRepository implements IAlbumRepository {
});
}
const albums = await albumsQuery;
albums.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
return albumsQuery;
}

View File

@@ -22,7 +22,7 @@ import { AddUsersDto } from './dto/add-users.dto';
import { RemoveAssetsDto } from './dto/remove-assets.dto';
import { UpdateAlbumDto } from './dto/update-album.dto';
import { GetAlbumsDto } from './dto/get-albums.dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { AlbumResponseDto } from '@app/domain';
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
@@ -37,7 +37,6 @@ import { CreateAlbumShareLinkDto as CreateAlbumSharedLinkDto } from './dto/creat
// TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe.
@ApiBearerAuth()
@ApiTags('Album')
@Controller('album')
export class AlbumController {
@@ -134,12 +133,13 @@ export class AlbumController {
@Authenticated({ isShared: true })
@Get('/:albumId/download')
@ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } })
async downloadArchive(
@GetAuthUser() authUser: AuthUserDto,
@Param('albumId', new ParseUUIDPipe({ version: '4' })) albumId: string,
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
@Response({ passthrough: true }) res: Res,
): Promise<any> {
) {
this.albumService.checkDownloadAccess(authUser);
const { stream, fileName, fileSize, fileCount, complete } = await this.albumService.downloadArchive(

View File

@@ -37,6 +37,7 @@ describe('Album service', () => {
shouldChangePassword: false,
oauthId: '',
tags: [],
assets: [],
});
const albumId = 'f19ab956-4761-41ea-a5d6-bae948308d58';
const sharedAlbumOwnerId = '2222';

View File

@@ -66,11 +66,11 @@ export class AlbumService {
*/
async getAllAlbums(authUser: AuthUserDto, getAlbumsDto: GetAlbumsDto): Promise<AlbumResponseDto[]> {
let albums: AlbumEntity[];
if (typeof getAlbumsDto.assetId === 'string') {
albums = await this.albumRepository.getListByAssetId(authUser.id, getAlbumsDto.assetId);
} else {
albums = await this.albumRepository.getList(authUser.id, getAlbumsDto);
if (getAlbumsDto.shared) {
const publicSharingAlbums = await this.albumRepository.getPublicSharingList(authUser.id);
albums = [...albums, ...publicSharingAlbums];

View File

@@ -23,6 +23,7 @@ export interface IAssetRepository {
asset: Omit<AssetEntity, 'id' | 'createdAt' | 'updatedAt' | 'ownerId' | 'livePhotoVideoId'>,
): Promise<AssetEntity>;
remove(asset: AssetEntity): Promise<void>;
save(asset: Partial<AssetEntity>): Promise<AssetEntity>;
update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise<AssetEntity>;
getAll(): Promise<AssetEntity[]>;
@@ -292,6 +293,11 @@ export class AssetRepository implements IAssetRepository {
await this.assetRepository.remove(asset);
}
async save(asset: Partial<AssetEntity>): Promise<AssetEntity> {
const { id } = await this.assetRepository.save(asset);
return this.assetRepository.findOneOrFail({ where: { id } });
}
/**
* Update asset
*/

View File

@@ -28,7 +28,7 @@ import { Response as Res } from 'express';
import { DeleteAssetDto } from './dto/delete-asset.dto';
import { SearchAssetDto } from './dto/search-asset.dto';
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger';
import { ApiBody, ApiConsumes, ApiHeader, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
import { AssetResponseDto, ImmichReadStream } from '@app/domain';
@@ -62,7 +62,6 @@ function asStreamableFile({ stream, type, length }: ImmichReadStream) {
return new StreamableFile(stream, { type, length });
}
@ApiBearerAuth()
@ApiTags('Asset')
@Controller('asset')
export class AssetController {
@@ -108,21 +107,23 @@ export class AssetController {
@Authenticated({ isShared: true })
@Get('/download/:assetId')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
async downloadFile(
@GetAuthUser() authUser: AuthUserDto,
@Response({ passthrough: true }) res: Res,
@Param('assetId') assetId: string,
): Promise<any> {
) {
return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile);
}
@Authenticated({ isShared: true })
@Post('/download-files')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
async downloadFiles(
@GetAuthUser() authUser: AuthUserDto,
@Response({ passthrough: true }) res: Res,
@Body(new ValidationPipe()) dto: DownloadFilesDto,
): Promise<any> {
) {
this.assetService.checkDownloadAccess(authUser);
await this.assetService.checkAssetsAccess(authUser, [...dto.assetIds]);
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadFiles(dto);
@@ -138,11 +139,12 @@ export class AssetController {
*/
@Authenticated({ isShared: true })
@Get('/download-library')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
async downloadLibrary(
@GetAuthUser() authUser: AuthUserDto,
@Query(new ValidationPipe({ transform: true })) dto: DownloadDto,
@Response({ passthrough: true }) res: Res,
): Promise<any> {
) {
this.assetService.checkDownloadAccess(authUser);
const { stream, fileName, fileSize, fileCount, complete } = await this.assetService.downloadLibrary(authUser, dto);
res.attachment(fileName);
@@ -155,13 +157,14 @@ export class AssetController {
@Authenticated({ isShared: true })
@Get('/file/:assetId')
@Header('Cache-Control', 'max-age=31536000')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
async serveFile(
@GetAuthUser() authUser: AuthUserDto,
@Headers() headers: Record<string, string>,
@Response({ passthrough: true }) res: Res,
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
@Param('assetId') assetId: string,
): Promise<any> {
) {
await this.assetService.checkAssetsAccess(authUser, [assetId]);
return this.assetService.serveFile(authUser, assetId, query, res, headers);
}
@@ -169,13 +172,14 @@ export class AssetController {
@Authenticated({ isShared: true })
@Get('/thumbnail/:assetId')
@Header('Cache-Control', 'max-age=31536000')
@ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } })
async getAssetThumbnail(
@GetAuthUser() authUser: AuthUserDto,
@Headers() headers: Record<string, string>,
@Response({ passthrough: true }) res: Res,
@Param('assetId') assetId: string,
@Query(new ValidationPipe({ transform: true })) query: GetAssetThumbnailDto,
): Promise<any> {
) {
await this.assetService.checkAssetsAccess(authUser, [assetId]);
return this.assetService.getAssetThumbnail(assetId, query, res, headers);
}

View File

@@ -1,15 +1,29 @@
import { AuthUserDto, IJobRepository, JobName } from '@app/domain';
import { AssetEntity, UserEntity } from '@app/infra/db/entities';
import { StorageService } from '@app/storage';
import {
AuthUserDto,
IJobRepository,
IStorageRepository,
ISystemConfigRepository,
JobName,
StorageTemplateCore,
} from '@app/domain';
import { AssetEntity, SystemConfig, UserEntity } from '@app/infra/db/entities';
import { Logger } from '@nestjs/common';
import { IAssetRepository } from './asset-repository';
import { CreateAssetDto, UploadFile } from './dto/create-asset.dto';
export class AssetCore {
private templateCore: StorageTemplateCore;
private logger = new Logger(AssetCore.name);
constructor(
private repository: IAssetRepository,
private jobRepository: IJobRepository,
private storageService: StorageService,
) {}
configRepository: ISystemConfigRepository,
config: SystemConfig,
private storageRepository: IStorageRepository,
) {
this.templateCore = new StorageTemplateCore(configRepository, config, storageRepository);
}
async create(
authUser: AuthUserDto,
@@ -42,10 +56,31 @@ export class AssetCore {
sharedLinks: [],
});
asset = await this.storageService.moveAsset(asset, file.originalName);
asset = await this.moveAsset(asset, file.originalName);
await this.jobRepository.add({ name: JobName.ASSET_UPLOADED, data: { asset, fileName: file.originalName } });
await this.jobRepository.queue({ name: JobName.ASSET_UPLOADED, data: { asset, fileName: file.originalName } });
return asset;
}
async moveAsset(asset: AssetEntity, originalName: string) {
const destination = await this.templateCore.getTemplatePath(asset, originalName);
if (asset.originalPath !== destination) {
const source = asset.originalPath;
try {
await this.storageRepository.moveFile(asset.originalPath, destination);
try {
await this.repository.save({ id: asset.id, originalPath: destination });
asset.originalPath = destination;
} catch (error: any) {
this.logger.warn('Unable to save new originalPath to database, undoing move', error?.stack);
await this.storageRepository.moveFile(destination, source);
}
} catch (error: any) {
this.logger.error(`Problem applying storage template`, error?.stack, { id: asset.id, source, destination });
}
}
return asset;
}
}

View File

@@ -3,12 +3,10 @@ import { AssetService } from './asset.service';
import { AssetController } from './asset.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AssetEntity } from '@app/infra';
import { CommunicationModule } from '../communication/communication.module';
import { AssetRepository, IAssetRepository } from './asset-repository';
import { DownloadModule } from '../../modules/download/download.module';
import { TagModule } from '../tag/tag.module';
import { AlbumModule } from '../album/album.module';
import { StorageModule } from '@app/storage';
const ASSET_REPOSITORY_PROVIDER = {
provide: IAssetRepository,
@@ -17,11 +15,10 @@ const ASSET_REPOSITORY_PROVIDER = {
@Module({
imports: [
//
TypeOrmModule.forFeature([AssetEntity]),
CommunicationModule,
DownloadModule,
TagModule,
StorageModule,
AlbumModule,
],
controllers: [AssetController],

View File

@@ -8,19 +8,30 @@ import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
import { DownloadService } from '../../modules/download/download.service';
import { AlbumRepository, IAlbumRepository } from '../album/album-repository';
import { StorageService } from '@app/storage';
import { ICryptoRepository, IJobRepository, ISharedLinkRepository, IStorageRepository, JobName } from '@app/domain';
import {
ICryptoRepository,
IJobRepository,
ISharedLinkRepository,
IStorageRepository,
ISystemConfigRepository,
JobName,
} from '@app/domain';
import {
assetEntityStub,
authStub,
fileStub,
newCryptoRepositoryMock,
newJobRepositoryMock,
newSharedLinkRepositoryMock,
newStorageRepositoryMock,
newSystemConfigRepositoryMock,
sharedLinkResponseStub,
sharedLinkStub,
systemConfigStub,
} from '@app/domain/../test';
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
import { BadRequestException, ForbiddenException } from '@nestjs/common';
import { when } from 'jest-when';
const _getCreateAssetDto = (): CreateAssetDto => {
const createAssetDto = new CreateAssetDto();
@@ -109,8 +120,8 @@ describe('AssetService', () => {
let assetRepositoryMock: jest.Mocked<IAssetRepository>;
let albumRepositoryMock: jest.Mocked<IAlbumRepository>;
let downloadServiceMock: jest.Mocked<Partial<DownloadService>>;
let storageServiceMock: jest.Mocked<StorageService>;
let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
let configMock: jest.Mocked<ISystemConfigRepository>;
let cryptoMock: jest.Mocked<ICryptoRepository>;
let jobMock: jest.Mocked<IJobRepository>;
let storageMock: jest.Mocked<IStorageRepository>;
@@ -120,6 +131,7 @@ describe('AssetService', () => {
get: jest.fn(),
create: jest.fn(),
remove: jest.fn(),
save: jest.fn(),
update: jest.fn(),
getAll: jest.fn(),
@@ -150,13 +162,9 @@ describe('AssetService', () => {
downloadArchive: jest.fn(),
};
storageServiceMock = {
moveAsset: jest.fn(),
removeEmptyDirectories: jest.fn(),
} as unknown as jest.Mocked<StorageService>;
sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
jobMock = newJobRepositoryMock();
configMock = newSystemConfigRepositoryMock();
cryptoMock = newCryptoRepositoryMock();
storageMock = newStorageRepositoryMock();
@@ -165,12 +173,20 @@ describe('AssetService', () => {
albumRepositoryMock,
a,
downloadServiceMock as DownloadService,
storageServiceMock,
sharedLinkRepositoryMock,
jobMock,
configMock,
systemConfigStub.defaults,
cryptoMock,
storageMock,
);
when(assetRepositoryMock.get)
.calledWith(assetEntityStub.livePhotoStillAsset.id)
.mockResolvedValue(assetEntityStub.livePhotoStillAsset);
when(assetRepositoryMock.get)
.calledWith(assetEntityStub.livePhotoMotionAsset.id)
.mockResolvedValue(assetEntityStub.livePhotoMotionAsset);
});
describe('createAssetsSharedLink', () => {
@@ -255,10 +271,16 @@ describe('AssetService', () => {
};
const dto = _getCreateAssetDto();
assetRepositoryMock.create.mockImplementation(() => Promise.resolve(assetEntity));
storageServiceMock.moveAsset.mockResolvedValue({ ...assetEntity, originalPath: 'fake_new_path/asset_123.jpeg' });
assetRepositoryMock.create.mockResolvedValue(assetEntity);
assetRepositoryMock.save.mockResolvedValue(assetEntity);
await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: false, id: 'id_1' });
expect(assetRepositoryMock.create).toHaveBeenCalled();
expect(assetRepositoryMock.save).toHaveBeenCalledWith({
id: 'id_1',
originalPath: 'upload/user_id_1/2022/2022-06-19/asset_1.jpeg',
});
});
it('should handle a duplicate', async () => {
@@ -277,59 +299,43 @@ describe('AssetService', () => {
await expect(sut.uploadFile(authStub.user1, dto, file)).resolves.toEqual({ duplicate: true, id: 'id_1' });
expect(jobMock.add).toHaveBeenCalledWith({
name: JobName.DELETE_FILE_ON_DISK,
data: { assets: [{ originalPath: 'fake_path/asset_1.jpeg', resizePath: null }] },
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.DELETE_FILES,
data: { files: ['fake_path/asset_1.jpeg', undefined] },
});
expect(storageServiceMock.moveAsset).not.toHaveBeenCalled();
expect(storageMock.moveFile).not.toHaveBeenCalled();
});
it('should handle a live photo', async () => {
const file = {
originalPath: 'fake_path/asset_1.jpeg',
mimeType: 'image/jpeg',
checksum: Buffer.from('file hash', 'utf8'),
originalName: 'asset_1.jpeg',
};
const asset = {
id: 'live-photo-asset',
originalPath: file.originalPath,
ownerId: authStub.user1.id,
type: AssetType.IMAGE,
isVisible: true,
} as AssetEntity;
const livePhotoFile = {
originalPath: 'fake_path/asset_1.mp4',
mimeType: 'image/jpeg',
checksum: Buffer.from('live photo file hash', 'utf8'),
originalName: 'asset_1.jpeg',
};
const livePhotoAsset = {
id: 'live-photo-motion',
originalPath: livePhotoFile.originalPath,
ownerId: authStub.user1.id,
type: AssetType.VIDEO,
isVisible: false,
} as AssetEntity;
const dto = _getCreateAssetDto();
const error = new QueryFailedError('', [], '');
(error as any).constraint = 'UQ_userid_checksum';
assetRepositoryMock.create.mockResolvedValueOnce(livePhotoAsset);
assetRepositoryMock.create.mockResolvedValueOnce(asset);
storageServiceMock.moveAsset.mockImplementation((asset) => Promise.resolve(asset));
assetRepositoryMock.create.mockResolvedValueOnce(assetEntityStub.livePhotoMotionAsset);
assetRepositoryMock.save.mockResolvedValueOnce(assetEntityStub.livePhotoMotionAsset);
assetRepositoryMock.create.mockResolvedValueOnce(assetEntityStub.livePhotoStillAsset);
assetRepositoryMock.save.mockResolvedValueOnce(assetEntityStub.livePhotoStillAsset);
await expect(sut.uploadFile(authStub.user1, dto, file, livePhotoFile)).resolves.toEqual({
await expect(
sut.uploadFile(authStub.user1, dto, fileStub.livePhotoStill, fileStub.livePhotoMotion),
).resolves.toEqual({
duplicate: false,
id: 'live-photo-asset',
id: 'live-photo-still-asset',
});
expect(jobMock.add.mock.calls).toEqual([
[{ name: JobName.ASSET_UPLOADED, data: { asset: livePhotoAsset, fileName: file.originalName } }],
[{ name: JobName.ASSET_UPLOADED, data: { asset, fileName: file.originalName } }],
expect(jobMock.queue.mock.calls).toEqual([
[
{
name: JobName.ASSET_UPLOADED,
data: { asset: assetEntityStub.livePhotoMotionAsset, fileName: 'asset_1.mp4' },
},
],
[
{
name: JobName.ASSET_UPLOADED,
data: { asset: assetEntityStub.livePhotoStillAsset, fileName: 'asset_1.jpeg' },
},
],
]);
});
});
@@ -383,7 +389,7 @@ describe('AssetService', () => {
{ id: 'asset1', status: 'FAILED' },
]);
expect(jobMock.add).not.toHaveBeenCalled();
expect(jobMock.queue).not.toHaveBeenCalled();
});
it('should return failed status a delete fails', async () => {
@@ -394,35 +400,66 @@ describe('AssetService', () => {
{ id: 'asset1', status: 'FAILED' },
]);
expect(jobMock.add).not.toHaveBeenCalled();
expect(jobMock.queue).not.toHaveBeenCalled();
});
it('should delete a live photo', async () => {
assetRepositoryMock.get.mockResolvedValueOnce({ id: 'asset1', livePhotoVideoId: 'live-photo' } as AssetEntity);
assetRepositoryMock.get.mockResolvedValueOnce({ id: 'live-photo' } as AssetEntity);
await expect(sut.deleteAll(authStub.user1, { ids: ['asset1'] })).resolves.toEqual([
{ id: 'asset1', status: 'SUCCESS' },
{ id: 'live-photo', status: 'SUCCESS' },
await expect(sut.deleteAll(authStub.user1, { ids: [assetEntityStub.livePhotoStillAsset.id] })).resolves.toEqual([
{ id: assetEntityStub.livePhotoStillAsset.id, status: 'SUCCESS' },
{ id: assetEntityStub.livePhotoMotionAsset.id, status: 'SUCCESS' },
]);
expect(jobMock.add).toHaveBeenCalledWith({
name: JobName.DELETE_FILE_ON_DISK,
data: { assets: [{ id: 'asset1', livePhotoVideoId: 'live-photo' }, { id: 'live-photo' }] },
expect(jobMock.queue).toHaveBeenCalledWith({
name: JobName.DELETE_FILES,
data: {
files: ['fake_path/asset_1.jpeg', undefined, undefined, 'fake_path/asset_1.mp4', undefined, undefined],
},
});
});
it('should delete a batch of assets', async () => {
assetRepositoryMock.get.mockImplementation((id) => Promise.resolve({ id } as AssetEntity));
assetRepositoryMock.remove.mockImplementation(() => Promise.resolve());
const asset1 = {
id: 'asset1',
originalPath: 'original-path-1',
resizePath: 'resize-path-1',
webpPath: 'web-path-1',
};
const asset2 = {
id: 'asset2',
originalPath: 'original-path-2',
resizePath: 'resize-path-2',
webpPath: 'web-path-2',
};
when(assetRepositoryMock.get)
.calledWith(asset1.id)
.mockResolvedValue(asset1 as AssetEntity);
when(assetRepositoryMock.get)
.calledWith(asset2.id)
.mockResolvedValue(asset2 as AssetEntity);
await expect(sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'] })).resolves.toEqual([
{ id: 'asset1', status: 'SUCCESS' },
{ id: 'asset2', status: 'SUCCESS' },
]);
expect(jobMock.add.mock.calls).toEqual([
[{ name: JobName.DELETE_FILE_ON_DISK, data: { assets: [{ id: 'asset1' }, { id: 'asset2' }] } }],
expect(jobMock.queue.mock.calls).toEqual([
[
{
name: JobName.DELETE_FILES,
data: {
files: [
'original-path-1',
'web-path-1',
'resize-path-1',
'original-path-2',
'web-path-2',
'resize-path-2',
],
},
},
],
]);
});
});

View File

@@ -12,7 +12,7 @@ import {
import { InjectRepository } from '@nestjs/typeorm';
import { QueryFailedError, Repository } from 'typeorm';
import { AuthUserDto } from '../../decorators/auth-user.decorator';
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
import { AssetEntity, AssetType, SharedLinkType, SystemConfig } from '@app/infra';
import { constants, createReadStream, ReadStream, stat } from 'fs';
import { ServeFileDto } from './dto/serve-file.dto';
import { Response as Res } from 'express';
@@ -25,7 +25,9 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon
import {
AssetResponseDto,
ImmichReadStream,
INITIAL_SYSTEM_CONFIG,
IStorageRepository,
ISystemConfigRepository,
JobName,
mapAsset,
mapAssetWithoutExif,
@@ -52,7 +54,6 @@ import { ICryptoRepository, IJobRepository } from '@app/domain';
import { DownloadService } from '../../modules/download/download.service';
import { DownloadDto } from './dto/download-library.dto';
import { IAlbumRepository } from '../album/album-repository';
import { StorageService } from '@app/storage';
import { ShareCore } from '@app/domain';
import { ISharedLinkRepository } from '@app/domain';
import { DownloadFilesDto } from './dto/download-files.dto';
@@ -61,6 +62,8 @@ import { mapSharedLink, SharedLinkResponseDto } from '@app/domain';
import { AssetSearchDto } from './dto/asset-search.dto';
import { AddAssetsDto } from '../album/dto/add-assets.dto';
import { RemoveAssetsDto } from '../album/dto/remove-assets.dto';
import path from 'path';
import { getFileNameWithoutExtension } from '../../utils/file-name.util';
const fileInfo = promisify(stat);
@@ -76,13 +79,14 @@ export class AssetService {
@InjectRepository(AssetEntity)
private assetRepository: Repository<AssetEntity>,
private downloadService: DownloadService,
storageService: StorageService,
@Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
@Inject(IJobRepository) private jobRepository: IJobRepository,
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(INITIAL_SYSTEM_CONFIG) config: SystemConfig,
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
@Inject(IStorageRepository) private storage: IStorageRepository,
@Inject(IStorageRepository) private storageRepository: IStorageRepository,
) {
this.assetCore = new AssetCore(_assetRepository, jobRepository, storageService);
this.assetCore = new AssetCore(_assetRepository, jobRepository, configRepository, config, storageRepository);
this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository);
}
@@ -93,7 +97,10 @@ export class AssetService {
livePhotoFile?: UploadFile,
): Promise<AssetFileUploadResponseDto> {
if (livePhotoFile) {
livePhotoFile.originalName = file.originalName;
livePhotoFile = {
...livePhotoFile,
originalName: getFileNameWithoutExtension(file.originalName) + path.extname(livePhotoFile.originalName),
};
}
let livePhotoAsset: AssetEntity | null = null;
@@ -109,16 +116,9 @@ export class AssetService {
return { id: asset.id, duplicate: false };
} catch (error: any) {
// clean up files
await this.jobRepository.add({
name: JobName.DELETE_FILE_ON_DISK,
data: {
assets: [
{
originalPath: file.originalPath,
resizePath: livePhotoFile?.originalPath || null,
} as AssetEntity,
],
},
await this.jobRepository.queue({
name: JobName.DELETE_FILES,
data: { files: [file.originalPath, livePhotoFile?.originalPath] },
});
// handle duplicates with a success response
@@ -204,7 +204,7 @@ export class AssetService {
try {
const asset = await this._assetRepository.get(assetId);
if (asset && asset.originalPath && asset.mimeType) {
return this.storage.createReadStream(asset.originalPath, asset.mimeType);
return this.storageRepository.createReadStream(asset.originalPath, asset.mimeType);
}
} catch (e) {
Logger.error(`Error download asset ${e}`, 'downloadFile');
@@ -412,7 +412,7 @@ export class AssetService {
}
public async deleteAll(authUser: AuthUserDto, dto: DeleteAssetDto): Promise<DeleteAssetResponseDto[]> {
const deleteQueue: AssetEntity[] = [];
const deleteQueue: Array<string | null> = [];
const result: DeleteAssetResponseDto[] = [];
const ids = dto.ids.slice();
@@ -427,7 +427,7 @@ export class AssetService {
await this._assetRepository.remove(asset);
result.push({ id, status: DeleteAssetStatusEnum.SUCCESS });
deleteQueue.push(asset as any);
deleteQueue.push(asset.originalPath, asset.webpPath, asset.resizePath);
// TODO refactor this to use cascades
if (asset.livePhotoVideoId && !ids.includes(asset.livePhotoVideoId)) {
@@ -439,7 +439,7 @@ export class AssetService {
}
if (deleteQueue.length > 0) {
await this.jobRepository.add({ name: JobName.DELETE_FILE_ON_DISK, data: { assets: deleteQueue } });
await this.jobRepository.queue({ name: JobName.DELETE_FILES, data: { files: deleteQueue } });
}
return result;

View File

@@ -1,8 +0,0 @@
import { Module } from '@nestjs/common';
import { CommunicationGateway } from './communication.gateway';
@Module({
providers: [CommunicationGateway],
exports: [CommunicationGateway],
})
export class CommunicationModule {}

View File

@@ -1,5 +1,5 @@
import { Body, Controller, Get, Param, Put, ValidationPipe } from '@nestjs/common';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiTags } from '@nestjs/swagger';
import { Authenticated } from '../../decorators/authenticated.decorator';
import { AllJobStatusResponseDto } from './response-dto/all-job-status-response.dto';
import { GetJobDto } from './dto/get-job.dto';
@@ -8,7 +8,6 @@ import { JobCommandDto } from './dto/job-command.dto';
@Authenticated({ admin: true })
@ApiTags('Job')
@ApiBearerAuth()
@Controller('jobs')
export class JobController {
constructor(private readonly jobService: JobService) {}

View File

@@ -48,14 +48,14 @@ export class JobService {
? await this._assetRepository.getAllVideos()
: await this._assetRepository.getAssetWithNoEncodedVideo();
for (const asset of assets) {
await this.jobRepository.add({ name: JobName.VIDEO_CONVERSION, data: { asset } });
await this.jobRepository.queue({ name: JobName.VIDEO_CONVERSION, data: { asset } });
}
return assets.length;
}
case QueueName.CONFIG:
await this.jobRepository.add({ name: JobName.TEMPLATE_MIGRATION });
case QueueName.STORAGE_TEMPLATE_MIGRATION:
await this.jobRepository.queue({ name: JobName.STORAGE_TEMPLATE_MIGRATION });
return 1;
case QueueName.MACHINE_LEARNING: {
@@ -68,8 +68,8 @@ export class JobService {
: await this._assetRepository.getAssetWithNoSmartInfo();
for (const asset of assets) {
await this.jobRepository.add({ name: JobName.IMAGE_TAGGING, data: { asset } });
await this.jobRepository.add({ name: JobName.OBJECT_DETECTION, data: { asset } });
await this.jobRepository.queue({ name: JobName.IMAGE_TAGGING, data: { asset } });
await this.jobRepository.queue({ name: JobName.OBJECT_DETECTION, data: { asset } });
}
return assets.length;
}
@@ -81,7 +81,7 @@ export class JobService {
for (const asset of assets) {
if (asset.type === AssetType.VIDEO) {
await this.jobRepository.add({
await this.jobRepository.queue({
name: JobName.EXTRACT_VIDEO_METADATA,
data: {
asset,
@@ -89,7 +89,7 @@ export class JobService {
},
});
} else {
await this.jobRepository.add({
await this.jobRepository.queue({
name: JobName.EXIF_EXTRACTION,
data: {
asset,
@@ -107,7 +107,7 @@ export class JobService {
: await this._assetRepository.getAssetWithNoThumbnail();
for (const asset of assets) {
await this.jobRepository.add({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset } });
await this.jobRepository.queue({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { asset } });
}
return assets.length;
}
@@ -129,7 +129,7 @@ export class JobService {
return QueueName.VIDEO_CONVERSION;
case JobId.STORAGE_TEMPLATE_MIGRATION:
return QueueName.CONFIG;
return QueueName.STORAGE_TEMPLATE_MIGRATION;
case JobId.MACHINE_LEARNING:
return QueueName.MACHINE_LEARNING;

View File

@@ -2,28 +2,14 @@ import { ApiProperty } from '@nestjs/swagger';
import { UsageByUserDto } from './usage-by-user-response.dto';
export class ServerStatsResponseDto {
constructor() {
this.photos = 0;
this.videos = 0;
this.usageByUser = [];
this.usageRaw = 0;
this.usage = '';
}
@ApiProperty({ type: 'integer' })
photos = 0;
@ApiProperty({ type: 'integer' })
photos!: number;
@ApiProperty({ type: 'integer' })
videos!: number;
@ApiProperty({ type: 'integer' })
objects!: number;
videos = 0;
@ApiProperty({ type: 'integer', format: 'int64' })
usageRaw!: number;
@ApiProperty({ type: 'string' })
usage!: string;
usage = 0;
@ApiProperty({
isArray: true,
@@ -37,5 +23,5 @@ export class ServerStatsResponseDto {
},
],
})
usageByUser!: UsageByUserDto[];
usageByUser: UsageByUserDto[] = [];
}

View File

@@ -1,22 +1,16 @@
import { ApiProperty } from '@nestjs/swagger';
export class UsageByUserDto {
constructor(userId: string) {
this.userId = userId;
this.videos = 0;
this.photos = 0;
this.usageRaw = 0;
this.usage = '0B';
}
@ApiProperty({ type: 'string' })
userId: string;
userId!: string;
@ApiProperty({ type: 'string' })
userFirstName!: string;
@ApiProperty({ type: 'string' })
userLastName!: string;
@ApiProperty({ type: 'integer' })
videos: number;
photos!: number;
@ApiProperty({ type: 'integer' })
photos: number;
videos!: number;
@ApiProperty({ type: 'integer', format: 'int64' })
usageRaw!: number;
@ApiProperty({ type: 'string' })
usage!: string;
usage!: number;
}

View File

@@ -1,11 +1,11 @@
import { Module } from '@nestjs/common';
import { ServerInfoService } from './server-info.service';
import { ServerInfoController } from './server-info.controller';
import { AssetEntity } from '@app/infra';
import { UserEntity } from '@app/infra';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forFeature([AssetEntity])],
imports: [TypeOrmModule.forFeature([UserEntity])],
controllers: [ServerInfoController],
providers: [ServerInfoService],
})

View File

@@ -4,7 +4,7 @@ import { ServerInfoResponseDto } from './response-dto/server-info-response.dto';
import diskusage from 'diskusage';
import { ServerStatsResponseDto } from './response-dto/server-stats-response.dto';
import { UsageByUserDto } from './response-dto/usage-by-user-response.dto';
import { AssetEntity } from '@app/infra';
import { UserEntity } from '@app/infra';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { asHumanReadable } from '../../utils/human-readable.util';
@@ -12,8 +12,8 @@ import { asHumanReadable } from '../../utils/human-readable.util';
@Injectable()
export class ServerInfoService {
constructor(
@InjectRepository(AssetEntity)
private assetRepository: Repository<AssetEntity>,
@InjectRepository(UserEntity)
private userRepository: Repository<UserEntity>,
) {}
async getServerInfo(): Promise<ServerInfoResponseDto> {
@@ -33,44 +33,48 @@ export class ServerInfoService {
}
async getStats(): Promise<ServerStatsResponseDto> {
const serverStats = new ServerStatsResponseDto();
type UserStatsQueryResponse = {
assetType: string;
assetCount: string;
totalSizeInBytes: string;
ownerId: string;
userId: string;
userFirstName: string;
userLastName: string;
photos: string;
videos: string;
usage: string;
};
const userStatsQueryResponse: UserStatsQueryResponse[] = await this.assetRepository
.createQueryBuilder('a')
.select('COUNT(a.id)', 'assetCount')
.addSelect('SUM(ei.fileSizeInByte)', 'totalSizeInBytes')
.addSelect('a."ownerId"')
.addSelect('a.type', 'assetType')
.where('a.isVisible = true')
.leftJoin('a.exifInfo', 'ei')
.groupBy('a."ownerId"')
.addGroupBy('a.type')
const userStatsQueryResponse: UserStatsQueryResponse[] = await this.userRepository
.createQueryBuilder('users')
.select('users.id', 'userId')
.addSelect('users.firstName', 'userFirstName')
.addSelect('users.lastName', 'userLastName')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'IMAGE' AND assets.isVisible)`, 'photos')
.addSelect(`COUNT(assets.id) FILTER (WHERE assets.type = 'VIDEO' AND assets.isVisible)`, 'videos')
.addSelect('COALESCE(SUM(exif.fileSizeInByte), 0)', 'usage')
.leftJoin('users.assets', 'assets')
.leftJoin('assets.exifInfo', 'exif')
.groupBy('users.id')
.orderBy('users.createdAt', 'ASC')
.getRawMany();
const tmpMap = new Map<string, UsageByUserDto>();
const getUsageByUser = (id: string) => tmpMap.get(id) || new UsageByUserDto(id);
userStatsQueryResponse.forEach((r) => {
const usageByUser = getUsageByUser(r.ownerId);
usageByUser.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0;
usageByUser.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0;
usageByUser.usageRaw += parseInt(r.totalSizeInBytes);
usageByUser.usage = asHumanReadable(usageByUser.usageRaw);
const usageByUser = userStatsQueryResponse.map((userStats) => {
const usage = new UsageByUserDto();
usage.userId = userStats.userId;
usage.userFirstName = userStats.userFirstName;
usage.userLastName = userStats.userLastName;
usage.photos = Number(userStats.photos);
usage.videos = Number(userStats.videos);
usage.usage = Number(userStats.usage);
serverStats.photos += r.assetType === 'IMAGE' ? parseInt(r.assetCount) : 0;
serverStats.videos += r.assetType === 'VIDEO' ? parseInt(r.assetCount) : 0;
serverStats.usageRaw += parseInt(r.totalSizeInBytes);
serverStats.usage = asHumanReadable(serverStats.usageRaw);
tmpMap.set(r.ownerId, usageByUser);
return usage;
});
serverStats.usageByUser = Array.from(tmpMap.values());
const serverStats = new ServerStatsResponseDto();
usageByUser.forEach((user) => {
serverStats.photos += user.photos;
serverStats.videos += user.videos;
serverStats.usage += user.usage;
});
serverStats.usageByUser = usageByUser;
return serverStats;
}

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