From 1feebfdb83e8a597315d7ec46f81b460ad4e2038 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 2 Jul 2025 16:21:03 +0530 Subject: [PATCH 001/109] Better copy --- mobile/lib/generated/intl/messages_ar.dart | 2 -- mobile/lib/generated/intl/messages_de.dart | 2 -- mobile/lib/generated/intl/messages_en.dart | 4 ++-- mobile/lib/generated/intl/messages_es.dart | 2 -- mobile/lib/generated/intl/messages_fr.dart | 2 -- mobile/lib/generated/intl/messages_id.dart | 2 -- mobile/lib/generated/intl/messages_it.dart | 2 -- mobile/lib/generated/intl/messages_ja.dart | 2 -- mobile/lib/generated/intl/messages_lt.dart | 2 -- mobile/lib/generated/intl/messages_nl.dart | 2 -- mobile/lib/generated/intl/messages_no.dart | 5 ----- mobile/lib/generated/intl/messages_pl.dart | 2 -- mobile/lib/generated/intl/messages_pt.dart | 2 -- mobile/lib/generated/intl/messages_pt_BR.dart | 2 -- mobile/lib/generated/intl/messages_pt_PT.dart | 2 -- mobile/lib/generated/intl/messages_ro.dart | 2 -- mobile/lib/generated/intl/messages_ru.dart | 2 -- mobile/lib/generated/intl/messages_tr.dart | 2 -- mobile/lib/generated/intl/messages_uk.dart | 2 -- mobile/lib/generated/intl/messages_vi.dart | 2 -- mobile/lib/generated/intl/messages_zh.dart | 2 -- mobile/lib/generated/l10n.dart | 20 +++++++++---------- mobile/lib/l10n/intl_en.arb | 4 ++-- .../ml/machine_learning_settings_page.dart | 2 +- 24 files changed, 15 insertions(+), 58 deletions(-) diff --git a/mobile/lib/generated/intl/messages_ar.dart b/mobile/lib/generated/intl/messages_ar.dart index 1f53f875e9..3e7734f881 100644 --- a/mobile/lib/generated/intl/messages_ar.dart +++ b/mobile/lib/generated/intl/messages_ar.dart @@ -1096,8 +1096,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("مفتاح الاسترداد غير صحيح"), "indexedItems": MessageLookupByLibrary.simpleMessage("العناصر المفهرسة"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "الفهرسة متوقفة مؤقتًا. سيتم استئنافها تلقائيًا عندما يكون الجهاز جاهزًا."), "ineligible": MessageLookupByLibrary.simpleMessage("غير مؤهل"), "info": MessageLookupByLibrary.simpleMessage("معلومات"), "insecureDevice": MessageLookupByLibrary.simpleMessage("جهاز غير آمن"), diff --git a/mobile/lib/generated/intl/messages_de.dart b/mobile/lib/generated/intl/messages_de.dart index 774c8b5e77..0412e5d31a 100644 --- a/mobile/lib/generated/intl/messages_de.dart +++ b/mobile/lib/generated/intl/messages_de.dart @@ -1171,8 +1171,6 @@ class MessageLookup extends MessageLookupByLibrary { "Falscher Wiederherstellungs-Schlüssel"), "indexedItems": MessageLookupByLibrary.simpleMessage("Indizierte Elemente"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Die Indizierung ist unterbrochen. Sie wird automatisch fortgesetzt, wenn das Gerät bereit ist."), "ineligible": MessageLookupByLibrary.simpleMessage("Unzulässig"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/lib/generated/intl/messages_en.dart index 77f9b672a5..f9bdd4c629 100644 --- a/mobile/lib/generated/intl/messages_en.dart +++ b/mobile/lib/generated/intl/messages_en.dart @@ -1148,8 +1148,8 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage("Incorrect recovery key"), "indexedItems": MessageLookupByLibrary.simpleMessage("Indexed items"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Indexing is paused. It will automatically resume when device is ready."), + "indexingPausedStatusDescription": MessageLookupByLibrary.simpleMessage( + "Indexing is paused. It will automatically resume when the device is ready. The device is considered ready when its battery level, battery health, and thermal status are within a healthy range."), "ineligible": MessageLookupByLibrary.simpleMessage("Ineligible"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_es.dart b/mobile/lib/generated/intl/messages_es.dart index 50d2340bf9..c05563119e 100644 --- a/mobile/lib/generated/intl/messages_es.dart +++ b/mobile/lib/generated/intl/messages_es.dart @@ -1144,8 +1144,6 @@ class MessageLookup extends MessageLookupByLibrary { "Clave de recuperación incorrecta"), "indexedItems": MessageLookupByLibrary.simpleMessage("Elementos indexados"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "La indexación está pausada. Se reanudará automáticamente cuando el dispositivo esté listo."), "ineligible": MessageLookupByLibrary.simpleMessage("Inelegible"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_fr.dart b/mobile/lib/generated/intl/messages_fr.dart index 5fcc40e6cf..cd80d92698 100644 --- a/mobile/lib/generated/intl/messages_fr.dart +++ b/mobile/lib/generated/intl/messages_fr.dart @@ -1190,8 +1190,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Clé de secours non valide"), "indexedItems": MessageLookupByLibrary.simpleMessage("Éléments indexés"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "L\'indexation est en pause. Elle reprendra automatiquement lorsque l\'appareil sera prêt."), "ineligible": MessageLookupByLibrary.simpleMessage("Non compatible"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_id.dart b/mobile/lib/generated/intl/messages_id.dart index bfd30d79be..c97e62ced8 100644 --- a/mobile/lib/generated/intl/messages_id.dart +++ b/mobile/lib/generated/intl/messages_id.dart @@ -768,8 +768,6 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage("Kunci pemulihan salah"), "indexedItems": MessageLookupByLibrary.simpleMessage("Item terindeks"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Proses indeks dijeda, dan akan otomatis dilanjutkan saat perangkat siap."), "insecureDevice": MessageLookupByLibrary.simpleMessage("Perangkat tidak aman"), "installManually": diff --git a/mobile/lib/generated/intl/messages_it.dart b/mobile/lib/generated/intl/messages_it.dart index a21f77dc1c..a31cf49207 100644 --- a/mobile/lib/generated/intl/messages_it.dart +++ b/mobile/lib/generated/intl/messages_it.dart @@ -1158,8 +1158,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Chiave di recupero errata"), "indexedItems": MessageLookupByLibrary.simpleMessage("Elementi indicizzati"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "L\'indicizzazione è in pausa. Riprenderà automaticamente quando il dispositivo è pronto."), "ineligible": MessageLookupByLibrary.simpleMessage("Non idoneo"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_ja.dart b/mobile/lib/generated/intl/messages_ja.dart index 01062ecd62..ca801c8176 100644 --- a/mobile/lib/generated/intl/messages_ja.dart +++ b/mobile/lib/generated/intl/messages_ja.dart @@ -948,8 +948,6 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage("リカバリーキーの誤り"), "indexedItems": MessageLookupByLibrary.simpleMessage("処理済みの項目"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "インデックス作成は一時停止されています。デバイスの準備ができたら自動的に再開します。"), "ineligible": MessageLookupByLibrary.simpleMessage("対象外"), "info": MessageLookupByLibrary.simpleMessage("情報"), "insecureDevice": MessageLookupByLibrary.simpleMessage("安全でないデバイス"), diff --git a/mobile/lib/generated/intl/messages_lt.dart b/mobile/lib/generated/intl/messages_lt.dart index 6f493e38c5..501d17c409 100644 --- a/mobile/lib/generated/intl/messages_lt.dart +++ b/mobile/lib/generated/intl/messages_lt.dart @@ -1141,8 +1141,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Neteisingas atkūrimo raktas"), "indexedItems": MessageLookupByLibrary.simpleMessage("Indeksuoti elementai"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Indeksavimas pristabdytas. Jis bus automatiškai tęsiamas, kai įrenginys yra paruoštas."), "ineligible": MessageLookupByLibrary.simpleMessage("Netinkami"), "info": MessageLookupByLibrary.simpleMessage("Informacija"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_nl.dart b/mobile/lib/generated/intl/messages_nl.dart index 0772f32b0f..906ac331cc 100644 --- a/mobile/lib/generated/intl/messages_nl.dart +++ b/mobile/lib/generated/intl/messages_nl.dart @@ -1141,8 +1141,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Onjuiste herstelsleutel"), "indexedItems": MessageLookupByLibrary.simpleMessage("Geïndexeerde bestanden"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Indexeren is gepauzeerd. Het zal automatisch hervatten wanneer het apparaat klaar is."), "ineligible": MessageLookupByLibrary.simpleMessage("Ongerechtigd"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_no.dart b/mobile/lib/generated/intl/messages_no.dart index 145cf32e47..61a71cda4f 100644 --- a/mobile/lib/generated/intl/messages_no.dart +++ b/mobile/lib/generated/intl/messages_no.dart @@ -292,8 +292,6 @@ class MessageLookup extends MessageLookupByLibrary { static String m114(email) => "Vi har sendt en e-post til ${email}"; - static String m115(name) => "Wish \$${name} a happy birthday! 🎉"; - static String m116(count) => "${Intl.plural(count, other: '${count} år siden')}"; @@ -1073,8 +1071,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Feil gjenopprettingsnøkkel"), "indexedItems": MessageLookupByLibrary.simpleMessage("Indekserte elementer"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Indeksering er satt på pause. Den vil automatisk fortsette når enheten er klar."), "ineligible": MessageLookupByLibrary.simpleMessage("Ikke aktuell"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Usikker enhet"), @@ -2093,7 +2089,6 @@ class MessageLookup extends MessageLookupByLibrary { "whatsNew": MessageLookupByLibrary.simpleMessage("Det som er nytt"), "whyAddTrustContact": MessageLookupByLibrary.simpleMessage( "Betrodd kontakt kan hjelpe til med å gjenopprette dine data."), - "wishThemAHappyBirthday": m115, "yearShort": MessageLookupByLibrary.simpleMessage("år"), "yearly": MessageLookupByLibrary.simpleMessage("Årlig"), "yearsAgo": m116, diff --git a/mobile/lib/generated/intl/messages_pl.dart b/mobile/lib/generated/intl/messages_pl.dart index 81e1ea1cfb..f7b908a5a9 100644 --- a/mobile/lib/generated/intl/messages_pl.dart +++ b/mobile/lib/generated/intl/messages_pl.dart @@ -1000,8 +1000,6 @@ class MessageLookup extends MessageLookupByLibrary { "Nieprawidłowy klucz odzyskiwania"), "indexedItems": MessageLookupByLibrary.simpleMessage("Zindeksowane elementy"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Wstrzymano indeksowanie. Zostanie ono automatycznie wznowione, gdy urządzenie będzie gotowe."), "info": MessageLookupByLibrary.simpleMessage("Informacje"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Niezabezpieczone urządzenie"), diff --git a/mobile/lib/generated/intl/messages_pt.dart b/mobile/lib/generated/intl/messages_pt.dart index 9831a7ba65..ff14267b56 100644 --- a/mobile/lib/generated/intl/messages_pt.dart +++ b/mobile/lib/generated/intl/messages_pt.dart @@ -1066,8 +1066,6 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "Chave de recuperação incorreta"), "indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "A indexação está pausada, será retomada automaticamente quando o dispositivo estiver pronto."), "ineligible": MessageLookupByLibrary.simpleMessage("Inelegível"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_pt_BR.dart b/mobile/lib/generated/intl/messages_pt_BR.dart index 59a7836b17..bbd4765118 100644 --- a/mobile/lib/generated/intl/messages_pt_BR.dart +++ b/mobile/lib/generated/intl/messages_pt_BR.dart @@ -1159,8 +1159,6 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "Chave de recuperação incorreta"), "indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "A indexação parou, ela será retomada automaticamente quando o dispositivo estiver pronto."), "ineligible": MessageLookupByLibrary.simpleMessage("Inelegível"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_pt_PT.dart b/mobile/lib/generated/intl/messages_pt_PT.dart index 3ae4693e24..34dd1d7283 100644 --- a/mobile/lib/generated/intl/messages_pt_PT.dart +++ b/mobile/lib/generated/intl/messages_pt_PT.dart @@ -1160,8 +1160,6 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "Chave de recuperação incorreta"), "indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "A indexação está pausada, será retomada automaticamente quando o dispositivo estiver pronto."), "ineligible": MessageLookupByLibrary.simpleMessage("Inelegível"), "info": MessageLookupByLibrary.simpleMessage("Info"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_ro.dart b/mobile/lib/generated/intl/messages_ro.dart index 52aa5e1447..90e2b190db 100644 --- a/mobile/lib/generated/intl/messages_ro.dart +++ b/mobile/lib/generated/intl/messages_ro.dart @@ -1004,8 +1004,6 @@ class MessageLookup extends MessageLookupByLibrary { "Cheie de recuperare incorectă"), "indexedItems": MessageLookupByLibrary.simpleMessage("Elemente indexate"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Indexarea este în pauză. Va relua automat când dispozitivul este pregătit."), "info": MessageLookupByLibrary.simpleMessage("Informații"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Dispozitiv nesigur"), diff --git a/mobile/lib/generated/intl/messages_ru.dart b/mobile/lib/generated/intl/messages_ru.dart index 1dcfc39b5f..17897c9095 100644 --- a/mobile/lib/generated/intl/messages_ru.dart +++ b/mobile/lib/generated/intl/messages_ru.dart @@ -1126,8 +1126,6 @@ class MessageLookup extends MessageLookupByLibrary { "Неверный ключ восстановления"), "indexedItems": MessageLookupByLibrary.simpleMessage("Проиндексированные элементы"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Индексация приостановлена. Она автоматически возобновится, когда устройство будет готово."), "ineligible": MessageLookupByLibrary.simpleMessage("Неподходящий"), "info": MessageLookupByLibrary.simpleMessage("Информация"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_tr.dart b/mobile/lib/generated/intl/messages_tr.dart index 6b9ace83c6..ef68a3c64f 100644 --- a/mobile/lib/generated/intl/messages_tr.dart +++ b/mobile/lib/generated/intl/messages_tr.dart @@ -1117,8 +1117,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Yanlış kurtarma kodu"), "indexedItems": MessageLookupByLibrary.simpleMessage("Dizinlenmiş öğeler"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Dizin oluşturma duraklatıldı. Cihaz hazır olduğunda otomatik olarak devam edecektir."), "ineligible": MessageLookupByLibrary.simpleMessage("Uygun Değil"), "info": MessageLookupByLibrary.simpleMessage("Bilgi"), "insecureDevice": diff --git a/mobile/lib/generated/intl/messages_uk.dart b/mobile/lib/generated/intl/messages_uk.dart index e306a5aadc..0cd4753343 100644 --- a/mobile/lib/generated/intl/messages_uk.dart +++ b/mobile/lib/generated/intl/messages_uk.dart @@ -982,8 +982,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Невірний ключ відновлення"), "indexedItems": MessageLookupByLibrary.simpleMessage("Індексовані елементи"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Індексація припинена. Автоматично продовжуватиметься, коли пристрій буде готовий."), "info": MessageLookupByLibrary.simpleMessage("Інформація"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Незахищений пристрій"), diff --git a/mobile/lib/generated/intl/messages_vi.dart b/mobile/lib/generated/intl/messages_vi.dart index 154e455eec..68819cd716 100644 --- a/mobile/lib/generated/intl/messages_vi.dart +++ b/mobile/lib/generated/intl/messages_vi.dart @@ -985,8 +985,6 @@ class MessageLookup extends MessageLookupByLibrary { "Khóa khôi phục không chính xác"), "indexedItems": MessageLookupByLibrary.simpleMessage("Các mục đã lập chỉ mục"), - "indexingIsPaused": MessageLookupByLibrary.simpleMessage( - "Chỉ mục đang tạm dừng. Nó sẽ tự động tiếp tục khi thiết bị sẵn sàng."), "info": MessageLookupByLibrary.simpleMessage("Thông tin"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Thiết bị không an toàn"), diff --git a/mobile/lib/generated/intl/messages_zh.dart b/mobile/lib/generated/intl/messages_zh.dart index 8b697d1e49..dada82528f 100644 --- a/mobile/lib/generated/intl/messages_zh.dart +++ b/mobile/lib/generated/intl/messages_zh.dart @@ -915,8 +915,6 @@ class MessageLookup extends MessageLookupByLibrary { "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage("恢复密钥不正确"), "indexedItems": MessageLookupByLibrary.simpleMessage("已索引项目"), - "indexingIsPaused": - MessageLookupByLibrary.simpleMessage("索引已暂停。当设备准备就绪时,它将自动恢复。"), "ineligible": MessageLookupByLibrary.simpleMessage("不合格"), "info": MessageLookupByLibrary.simpleMessage("详情"), "insecureDevice": MessageLookupByLibrary.simpleMessage("设备不安全"), diff --git a/mobile/lib/generated/l10n.dart b/mobile/lib/generated/l10n.dart index 6fb57e9450..5a63f58f68 100644 --- a/mobile/lib/generated/l10n.dart +++ b/mobile/lib/generated/l10n.dart @@ -9376,16 +9376,6 @@ class S { ); } - /// `Indexing is paused. It will automatically resume when device is ready.` - String get indexingIsPaused { - return Intl.message( - 'Indexing is paused. It will automatically resume when device is ready.', - name: 'indexingIsPaused', - desc: '', - args: [], - ); - } - /// `Trim` String get trim { return Intl.message( @@ -12295,6 +12285,16 @@ class S { args: [], ); } + + /// `Indexing is paused. It will automatically resume when the device is ready. The device is considered ready when its battery level, battery health, and thermal status are within a healthy range.` + String get indexingPausedStatusDescription { + return Intl.message( + 'Indexing is paused. It will automatically resume when the device is ready. The device is considered ready when its battery level, battery health, and thermal status are within a healthy range.', + name: 'indexingPausedStatusDescription', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/lib/l10n/intl_en.arb index 121c65bb63..e9cd89b1f7 100644 --- a/mobile/lib/l10n/intl_en.arb +++ b/mobile/lib/l10n/intl_en.arb @@ -1309,7 +1309,6 @@ "faceRecognition": "Face recognition", "foundFaces": "Found faces", "clusteringProgress": "Clustering progress", - "indexingIsPaused": "Indexing is paused. It will automatically resume when device is ready.", "trim": "Trim", "crop": "Crop", "rotate": "Rotate", @@ -1788,5 +1787,6 @@ "cLTitle5": "Birthday Notifications", "cLDesc5": "You will now receive an opt-out notification for all the birthdays your have saved on Ente, along with a collection of their best photos.", "cLTitle6": "Resumable Uploads and Downloads", - "cLDesc6": "No more waiting for uploads/downloads to complete before you can close the app. All uploads and downloads now have the ability to be paused midway, and resume from where you left off." + "cLDesc6": "No more waiting for uploads/downloads to complete before you can close the app. All uploads and downloads now have the ability to be paused midway, and resume from where you left off.", + "indexingPausedStatusDescription": "Indexing is paused. It will automatically resume when the device is ready. The device is considered ready when its battery level, battery health, and thermal status are within a healthy range." } diff --git a/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart b/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart index 82c81e3967..1d64b186fd 100644 --- a/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart +++ b/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart @@ -472,7 +472,7 @@ class MLStatusWidgetState extends State { if (!_isDeviceHealthy && pendingFiles > 0) { return MenuSectionDescriptionWidget( - content: S.of(context).indexingIsPaused, + content: S.of(context).indexingPausedStatusDescription, ); } From 033caedfb584dd6e8847bf889f8fe881824910f2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 2 Jul 2025 19:50:32 +0530 Subject: [PATCH 002/109] Conv --- .../accounts/components/SignUpContents.tsx | 4 +- web/packages/accounts/pages/generate.tsx | 6 +-- web/packages/accounts/pages/verify.tsx | 12 +++--- web/packages/accounts/services/accounts-db.ts | 37 +++++++++++++++++++ web/packages/accounts/services/session.ts | 7 +++- 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index 343683c846..2392333843 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -15,7 +15,7 @@ import { } from "@mui/material"; import { saveJustSignedUp, - setData, + saveOriginalKeyAttributes, stashReferralSource, } from "ente-accounts/services/accounts-db"; import { @@ -146,7 +146,7 @@ export const SignUpContents: React.FC = ({ } const { masterKey, kek, keyAttributes } = gkResult; - setData("originalKeyAttributes", keyAttributes); + saveOriginalKeyAttributes(keyAttributes); stashSRPSetupAttributes(await generateSRPSetupAttributes(kek)); await generateAndSaveInteractiveKeyAttributes( password, diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index a40ef0ca53..c563bdb245 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -8,6 +8,7 @@ import { RecoveryKey } from "ente-accounts/components/RecoveryKey"; import { getData, savedJustSignedUp, + savedOriginalKeyAttributes, saveJustSignedUp, } from "ente-accounts/services/accounts-db"; import { appHomeRoute } from "ente-accounts/services/redirect"; @@ -15,7 +16,7 @@ import { generateSRPSetupAttributes, setupSRP, } from "ente-accounts/services/srp"; -import type { KeyAttributes, User } from "ente-accounts/services/user"; +import type { User } from "ente-accounts/services/user"; import { generateAndSaveInteractiveKeyAttributes, generateKeysAndAttributes, @@ -48,7 +49,6 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const keyAttributes: KeyAttributes = getData("originalKeyAttributes"); const user: User = getData("user"); setUser(user); if (!user?.token) { @@ -60,7 +60,7 @@ const Page: React.FC = () => { } else { void router.push(appHomeRoute); } - } else if (keyAttributes?.encryptedKey) { + } else if (savedOriginalKeyAttributes()?.encryptedKey) { void router.push("/credentials"); } else { setLoading(false); diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index a2aa9816fd..1157a5cf28 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -9,8 +9,10 @@ import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice" import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice"; import { getData, + savedOriginalKeyAttributes, saveIsFirstLogin, - setData, + saveKeyAttributes, + saveOriginalKeyAttributes, setLSUser, unstashReferralSource, } from "ente-accounts/services/accounts-db"; @@ -130,12 +132,10 @@ const Page: React.FC = () => { isTwoFactorEnabled: false, }); if (keyAttributes) { - setData("keyAttributes", keyAttributes); - setData("originalKeyAttributes", keyAttributes); + saveKeyAttributes(keyAttributes); + saveOriginalKeyAttributes(keyAttributes); } else { - const originalKeyAttributes = getData( - "originalKeyAttributes", - ); + const originalKeyAttributes = savedOriginalKeyAttributes(); if (originalKeyAttributes) { await putUserKeyAttributes(originalKeyAttributes); } diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 06b4d81384..011ebd56e9 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -128,6 +128,8 @@ export const isLocalStorageAndIndexedDBMismatch = async () => { * The key attributes are stored in the browser's localStorage. Thus, this * function only works from the main thread, not from web workers (local storage * is not accessible to web workers). + * + * See also: [Note: Original vs interactive key attributes] */ export const savedKeyAttributes = (): KeyAttributes | undefined => { const jsonString = localStorage.getItem("keyAttributes"); @@ -143,6 +145,41 @@ export const savedKeyAttributes = (): KeyAttributes | undefined => { export const saveKeyAttributes = (keyAttributes: KeyAttributes) => localStorage.setItem("keyAttributes", JSON.stringify(keyAttributes)); +/** + * Return the user's original {@link KeyAttributes} if they are present in local + * storage. + * + * [Note: Original vs interactive key attributes] + * + * This function is similar to {@link savedKeyAttributes} except it returns the + * user's "original" key attributes. These are the key attributes that were + * either freshly generated (if the user signed up on this client) or were + * fetched from remote (otherwise). + * + * In contrast, the regular key attributes get overwritten by the local only + * interactive key attributes for the user's convenience. See the documentation + * of {@link generateAndSaveInteractiveKeyAttributes} for more details. + */ +export const savedOriginalKeyAttributes = (): KeyAttributes | undefined => { + const jsonString = localStorage.getItem("originalKeyAttributes"); + if (!jsonString) return undefined; + return RemoteKeyAttributes.parse(JSON.parse(jsonString)); +}; + +/** + * Save the user's {@link KeyAttributes} in local storage. + * + * Once saved, these values are not replaced (in contrast with the regular key + * attributes which can get overwritten with interactive ones). + * + * Use {@link savedOriginalKeyAttributes} to retrieve them. + */ +export const saveOriginalKeyAttributes = (keyAttributes: KeyAttributes) => + localStorage.setItem( + "originalKeyAttributes", + JSON.stringify(keyAttributes), + ); + export const getToken = (): string => { const token = getData("user")?.token; return token; diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index e9d30177b1..8c51839582 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -1,4 +1,7 @@ -import { getData } from "ente-accounts/services/accounts-db"; +import { + getData, + savedOriginalKeyAttributes, +} from "ente-accounts/services/accounts-db"; import type { KeyAttributes } from "ente-accounts/services/user"; import { authenticatedRequestHeaders, HTTPError } from "ente-base/http"; import log from "ente-base/log"; @@ -159,7 +162,7 @@ export const isSessionInvalid = async (): Promise => { const { hasSetKeys } = SessionValidityResponse.parse(await res.json()); if (!hasSetKeys) { - const originalKeyAttributes = getData("originalKeyAttributes"); + const originalKeyAttributes = savedOriginalKeyAttributes(); if (originalKeyAttributes) await putUserKeyAttributes(originalKeyAttributes); } From 0d5b1ef094665cd1080d350771a0a5e2e1828b1e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 2 Jul 2025 20:00:52 +0530 Subject: [PATCH 003/109] Blurbs --- web/packages/accounts/pages/change-email.tsx | 4 ++++ web/packages/accounts/pages/credentials.tsx | 3 +++ web/packages/accounts/pages/login.tsx | 3 +++ web/packages/accounts/pages/signup.tsx | 3 +++ web/packages/accounts/pages/verify.tsx | 3 +++ web/packages/accounts/services/accounts-db.ts | 3 +-- 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/web/packages/accounts/pages/change-email.tsx b/web/packages/accounts/pages/change-email.tsx index 058c3abb07..0a34465b89 100644 --- a/web/packages/accounts/pages/change-email.tsx +++ b/web/packages/accounts/pages/change-email.tsx @@ -19,6 +19,10 @@ import { useCallback, useState } from "react"; import { Trans } from "react-i18next"; import { z } from "zod/v4"; +/** + * A page that allows a user to change the email address associated with their + * Ente account. + */ const Page: React.FC = () => { useRedirectIfNeedsCredentials("/change-email"); diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 62317d1b59..d1b9ed08a1 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -62,6 +62,9 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; +/** + * A page that allows the user to authenticate using their password. + */ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index 0d1618b71f..b57495a36d 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -6,6 +6,9 @@ import { customAPIHost } from "ente-base/origins"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; +/** + * A page that allows the user to login into their existing Ente account. + */ const Page: React.FC = () => { const [loading, setLoading] = useState(true); const [host, setHost] = useState(); diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index 20aaeb55ed..558277b2e2 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -6,6 +6,9 @@ import { customAPIHost } from "ente-base/origins"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; +/** + * A page that allows the user to signup for a new Ente account. + */ const Page: React.FC = () => { const [loading, setLoading] = useState(true); const [host, setHost] = useState(); diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 1157a5cf28..8f72ccac44 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -51,6 +51,9 @@ import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; +/** + * A page that allows the user to verify their email. + */ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 011ebd56e9..acc4ac5aa2 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -6,9 +6,7 @@ import { RemoteKeyAttributes, type KeyAttributes } from "./user"; export type LocalStorageKey = | "user" - // See also savedKeyAttributes. | "keyAttributes" - | "originalKeyAttributes" // Moved to ente-accounts // "srpSetupAttributes" | "srpAttributes"; @@ -28,6 +26,7 @@ export type LocalStorageKey = * * - "user" * - "keyAttributes" + * - "originalKeyAttributes" * - "srpAttributes" */ export const getData = (key: LocalStorageKey) => { From 95c35d7df86a264d75da4fbd014ec1fb73870d0a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 2 Jul 2025 20:30:03 +0530 Subject: [PATCH 004/109] read and prune --- .../components/VerifyMasterPasswordForm.tsx | 9 +++-- .../accounts/services/recovery-key.ts | 34 ++++++++----------- web/packages/base/crypto/index.ts | 13 ++++--- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx index f5ed68553a..d1c0d6570a 100644 --- a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx +++ b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx @@ -6,7 +6,7 @@ import { import type { KeyAttributes, User } from "ente-accounts/services/user"; import { LoadingButton } from "ente-base/components/mui/LoadingButton"; import { ShowHidePasswordInputAdornment } from "ente-base/components/mui/PasswordInputAdornment"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { decryptBox, deriveKey } from "ente-base/crypto"; import log from "ente-base/log"; import { useFormik } from "formik"; import { t } from "i18next"; @@ -118,11 +118,10 @@ export const VerifyMasterPasswordForm: React.FC< password: string, setFieldError: (message: string) => void, ) => { - const cryptoWorker = await sharedCryptoWorker(); let kek: string; if (srpAttributes) { try { - kek = await cryptoWorker.deriveKey( + kek = await deriveKey( password, srpAttributes.kekSalt, srpAttributes.opsLimit, @@ -135,7 +134,7 @@ export const VerifyMasterPasswordForm: React.FC< } } else if (keyAttributes) { try { - kek = await cryptoWorker.deriveKey( + kek = await deriveKey( password, keyAttributes.kekSalt, keyAttributes.opsLimit, @@ -175,7 +174,7 @@ export const VerifyMasterPasswordForm: React.FC< let key: string; try { - key = await cryptoWorker.decryptBox( + key = await decryptBox( { encryptedData: keyAttributes.encryptedKey, nonce: keyAttributes.keyDecryptionNonce, diff --git a/web/packages/accounts/services/recovery-key.ts b/web/packages/accounts/services/recovery-key.ts index 3d2a8be28b..3f2648ee6a 100644 --- a/web/packages/accounts/services/recovery-key.ts +++ b/web/packages/accounts/services/recovery-key.ts @@ -1,15 +1,15 @@ import * as bip39 from "bip39"; -import { getData } from "ente-accounts/services/accounts-db"; -import type { KeyAttributes } from "ente-accounts/services/user"; +import { savedKeyAttributes } from "ente-accounts/services/accounts-db"; import { decryptBox, + encryptBox, fromHex, - sharedCryptoWorker, + generateKey, toHex, } from "ente-base/crypto"; import { ensureMasterKeyFromSession } from "ente-base/session"; import { saveKeyAttributes } from "./accounts-db"; -import { putUserRecoveryKeyAttributes } from "./user"; +import { putUserRecoveryKeyAttributes, type KeyAttributes } from "./user"; // Mobile client library only supports English. bip39.setDefaultWordlist("english"); @@ -67,7 +67,7 @@ export const recoveryKeyToMnemonic = async (recoveryKey: string) => export const getUserRecoveryKey = async () => { const masterKey = await ensureMasterKeyFromSession(); - const keyAttributes: KeyAttributes = getData("keyAttributes"); + const keyAttributes = savedKeyAttributes()!; const { recoveryKeyEncryptedWithMasterKey, recoveryKeyDecryptionNonce } = keyAttributes; @@ -80,7 +80,7 @@ export const getUserRecoveryKey = async () => { masterKey, ); } else { - return createNewRecoveryKey(masterKey); + return createNewRecoveryKey(masterKey, keyAttributes); } }; @@ -93,19 +93,13 @@ export const getUserRecoveryKey = async () => { * * @returns a new base64 encoded recovery key. */ -const createNewRecoveryKey = async (masterKey: string) => { - const existingAttributes = getData("keyAttributes"); - - const cryptoWorker = await sharedCryptoWorker(); - const recoveryKey = await cryptoWorker.generateKey(); - const encryptedMasterKey = await cryptoWorker.encryptBox( - masterKey, - recoveryKey, - ); - const encryptedRecoveryKey = await cryptoWorker.encryptBox( - recoveryKey, - masterKey, - ); +const createNewRecoveryKey = async ( + masterKey: string, + existingKeyAttributes: KeyAttributes, +) => { + const recoveryKey = await generateKey(); + const encryptedMasterKey = await encryptBox(masterKey, recoveryKey); + const encryptedRecoveryKey = await encryptBox(recoveryKey, masterKey); const recoveryKeyAttributes = { masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData, @@ -116,7 +110,7 @@ const createNewRecoveryKey = async (masterKey: string) => { await putUserRecoveryKeyAttributes(recoveryKeyAttributes); - saveKeyAttributes({ ...existingAttributes, ...recoveryKeyAttributes }); + saveKeyAttributes({ ...existingKeyAttributes, ...recoveryKeyAttributes }); return recoveryKey; }; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 8ffa6dfbb5..cf837db59c 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -109,14 +109,17 @@ import type { CryptoWorker } from "./worker"; let _comlinkWorker: ComlinkWorker | undefined; /** - * Lazily created, cached, instance of a CryptoWorker web worker. + * Lazily created, cached, instance of a "shared" CryptoWorker web worker. + * + * Some code which needs to do operations in parallel (e.g. during the upload + * flow) creates its own CryptoWorker web workers. But those are exceptions; the + * rest of the code normally calls the functions in this file, and they all + * implicitly use a default "shared" web worker (unless we're already running in + * the context of a web worker). */ -export const sharedCryptoWorker = () => +const sharedWorker = () => (_comlinkWorker ??= createComlinkCryptoWorker()).remote; -/** A shorter alias of {@link sharedCryptoWorker} for use within this file. */ -const sharedWorker = sharedCryptoWorker; - /** * Create a new instance of a comlink worker that wraps a {@link CryptoWorker} * web worker. From ef7ff0b186581c8143fc12028a7a98ea556ed9f8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 2 Jul 2025 20:43:23 +0530 Subject: [PATCH 005/109] Dep --- web/apps/photos/src/components/AuthenticateUser.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/components/AuthenticateUser.tsx b/web/apps/photos/src/components/AuthenticateUser.tsx index 7962c7d966..0b7e5ce1e2 100644 --- a/web/apps/photos/src/components/AuthenticateUser.tsx +++ b/web/apps/photos/src/components/AuthenticateUser.tsx @@ -83,10 +83,10 @@ export const AuthenticateUser: React.FC = ({ }, []); useEffect(() => { - // Do a non-blocking validation of the session, but show the dialog to - // the user. + // Do a non-blocking validation of the session whenever we show the + // dialog to the user. if (open) void validateSession(); - }, [open]); + }, [open, validateSession]); return ( Date: Thu, 3 Jul 2025 07:42:08 +0530 Subject: [PATCH 006/109] Refactor --- .../src/components/AuthenticateUser.tsx | 47 +++++++------------ .../components/VerifyMasterPasswordForm.tsx | 17 ++++--- web/packages/accounts/pages/credentials.tsx | 26 ++++++++-- 3 files changed, 49 insertions(+), 41 deletions(-) diff --git a/web/apps/photos/src/components/AuthenticateUser.tsx b/web/apps/photos/src/components/AuthenticateUser.tsx index 0b7e5ce1e2..2d476ccce6 100644 --- a/web/apps/photos/src/components/AuthenticateUser.tsx +++ b/web/apps/photos/src/components/AuthenticateUser.tsx @@ -1,7 +1,11 @@ import { VerifyMasterPasswordForm } from "ente-accounts/components/VerifyMasterPasswordForm"; -import { getData } from "ente-accounts/services/accounts-db"; import { checkSessionValidity } from "ente-accounts/services/session"; -import type { KeyAttributes, User } from "ente-accounts/services/user"; +import { + ensureLocalUser, + ensureSavedKeyAttributes, + type KeyAttributes, + type LocalUser, +} from "ente-accounts/services/user"; import { TitledMiniDialog, type MiniDialogAttributes, @@ -30,9 +34,11 @@ export const AuthenticateUser: React.FC = ({ onClose, onAuthenticate, }) => { - const { logout, showMiniDialog, onGenericError } = useBaseContext(); - const [user, setUser] = useState(); - const [keyAttributes, setKeyAttributes] = useState(); + const { logout, showMiniDialog } = useBaseContext(); + const [user, setUser] = useState(); + const [keyAttributes, setKeyAttributes] = useState< + KeyAttributes | undefined + >(undefined); // This is a altered version of the check we do on the password verification // screen, except here it don't try to overwrite local state and instead @@ -56,30 +62,8 @@ export const AuthenticateUser: React.FC = ({ }, [logout, showMiniDialog]); useEffect(() => { - const main = async () => { - try { - const user = getData("user"); - if (!user) { - throw Error("User not found"); - } - setUser(user); - const keyAttributes = getData("keyAttributes"); - if ( - (!user?.token && !user?.encryptedToken) || - (keyAttributes && !keyAttributes.memLimit) - ) { - throw Error("User not logged in"); - } else if (!keyAttributes) { - throw Error("Key attributes not found"); - } else { - setKeyAttributes(keyAttributes); - } - } catch (e) { - onClose(); - onGenericError(e); - } - }; - main(); + setUser(ensureLocalUser()); + setKeyAttributes(ensureSavedKeyAttributes()); }, []); useEffect(() => { @@ -88,6 +72,9 @@ export const AuthenticateUser: React.FC = ({ if (open) void validateSession(); }, [open, validateSession]); + // They'll be read from disk shortly. + if (!user && !keyAttributes) return <>; + return ( = ({ title={t("password")} > { diff --git a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx index d1c0d6570a..c0eaf39b00 100644 --- a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx +++ b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx @@ -3,7 +3,7 @@ import { srpVerificationUnauthorizedErrorMessage, type SRPAttributes, } from "ente-accounts/services/srp"; -import type { KeyAttributes, User } from "ente-accounts/services/user"; +import type { KeyAttributes } from "ente-accounts/services/user"; import { LoadingButton } from "ente-base/components/mui/LoadingButton"; import { ShowHidePasswordInputAdornment } from "ente-base/components/mui/PasswordInputAdornment"; import { decryptBox, deriveKey } from "ente-base/crypto"; @@ -15,9 +15,9 @@ import { twoFactorEnabledErrorMessage } from "./utils/second-factor-choice"; export interface VerifyMasterPasswordFormProps { /** - * The user whose password we're trying to verify. + * The email of the user whose password we're trying to verify. */ - user: User | undefined; + userEmail: string; /** * The user's key attributes. */ @@ -45,7 +45,7 @@ export interface VerifyMasterPasswordFormProps { */ srpAttributes?: SRPAttributes; /** - * The title of the submit button no the form. + * The title of the submit button on the form. */ submitButtonTitle: string; /** @@ -76,11 +76,16 @@ export interface VerifyMasterPasswordFormProps { /** * A form with a text field that can be used to ask the user to verify their * password. + * + * We use it both during the initial authentication (the "/credentials" page, + * shown when logging in, or reopening the web app in a new tab), and when the + * user is trying to perform a sensitive action when already logged in and + * having a session (the {@link AuthenticateUser} component). */ export const VerifyMasterPasswordForm: React.FC< VerifyMasterPasswordFormProps > = ({ - user, + userEmail, keyAttributes, srpAttributes, getKeyAttributes, @@ -196,7 +201,7 @@ export const VerifyMasterPasswordForm: React.FC< name="email" autoComplete="username" type="email" - value={user?.email} + value={userEmail} /> { const { logout, showMiniDialog } = useBaseContext(); @@ -286,6 +294,12 @@ const Page: React.FC = () => { void router.push(unstashRedirect() ?? appHomeRoute); }; + const userEmail = user?.email; + + if (!userEmail) { + return ; + } + if (!keyAttributes && !srpAttributes) { return ; } @@ -320,12 +334,14 @@ const Page: React.FC = () => { // possibility using types. return ( - + From e856a653b8b3192292abb2a542a1c994a22db910 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 08:13:18 +0530 Subject: [PATCH 007/109] State reset --- .../src/components/AuthenticateUser.tsx | 46 ++++++++++++------- .../Collections/AlbumCastDialog.tsx | 11 +++-- .../new/photos/components/DeleteAccount.tsx | 6 ++- 3 files changed, 43 insertions(+), 20 deletions(-) diff --git a/web/apps/photos/src/components/AuthenticateUser.tsx b/web/apps/photos/src/components/AuthenticateUser.tsx index 2d476ccce6..a1559f7b83 100644 --- a/web/apps/photos/src/components/AuthenticateUser.tsx +++ b/web/apps/photos/src/components/AuthenticateUser.tsx @@ -30,11 +30,32 @@ type AuthenticateUserProps = ModalVisibilityProps & { * This is used as precursor to performing various sensitive or locked actions. */ export const AuthenticateUser: React.FC = ({ + open, + onClose, + ...rest +}) => ( + + + +); + +/** + * The contents of the {@link AuthenticateUser} dialog. + * + * See: [Note: MUI dialog state] for why this is a separate component. + */ +const AuthenticateUserDialogContents: React.FC = ({ open, onClose, onAuthenticate, }) => { const { logout, showMiniDialog } = useBaseContext(); + const [user, setUser] = useState(); const [keyAttributes, setKeyAttributes] = useState< KeyAttributes | undefined @@ -76,22 +97,15 @@ export const AuthenticateUser: React.FC = ({ if (!user && !keyAttributes) return <>; return ( - - { - onClose(); - onAuthenticate(); - }} - /> - + { + onClose(); + onAuthenticate(); + }} + /> ); }; diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index f14f0d7cc8..78d44cba1e 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -40,11 +40,15 @@ export const AlbumCastDialog: React.FC = ({ ); /** - * [Note: MUI dialog state reset] + * [Note: MUI dialog state] * - * Keep the dialog contents in a separate component so that they get rendered + * In some cases we keep the dialog contents in a separate component so that (a) + * they get rendered only when the dialog is shown, and (b) they get rendered * afresh when the dialog is unmounted and then shown again. * + * Keeping it separate both resets the state of the component, and also ensures + * that the effects run again when the dialog is shown. + * * Details: * * Any state we keep inside the React component that a MUI Dialog as a child @@ -58,7 +62,8 @@ export const AlbumCastDialog: React.FC = ({ * circumstance. If it is undesirable, there are multiple approaches: * https://github.com/mui/material-ui/issues/16325 * - * One of those is to keep the dialog contents in a separate component. + * One of those approaches is to keep the dialog contents in a separate + * component. */ export const AlbumCastDialogContents: React.FC = ({ open, diff --git a/web/packages/new/photos/components/DeleteAccount.tsx b/web/packages/new/photos/components/DeleteAccount.tsx index abf3980c30..4bc8ad3584 100644 --- a/web/packages/new/photos/components/DeleteAccount.tsx +++ b/web/packages/new/photos/components/DeleteAccount.tsx @@ -47,7 +47,11 @@ export const DeleteAccount: React.FC = ({ ); -// See: [Note: MUI dialog state reset] +/** + * The contents of the {@link DeleteAccount} dialog. + * + * See: [Note: MUI dialog state] for why this is a separate component. + */ const DeleteAccountDialogContents: React.FC< Omit > = ({ onClose, onAuthenticateUser }) => { From aec3ec718bf74ac30bc9bb6726a5c1aff97ce15f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 08:17:55 +0530 Subject: [PATCH 008/109] Move --- .../accounts/components/LoginContents.tsx | 6 ++--- web/packages/accounts/pages/credentials.tsx | 6 +++-- .../accounts/pages/passkeys/finish.tsx | 9 +++++--- web/packages/accounts/services/accounts-db.ts | 22 +++++++++++++++++++ web/packages/accounts/services/passkey.ts | 9 ++++---- web/packages/accounts/services/srp.ts | 22 +------------------ web/packages/accounts/services/user.ts | 2 +- 7 files changed, 41 insertions(+), 35 deletions(-) diff --git a/web/packages/accounts/components/LoginContents.tsx b/web/packages/accounts/components/LoginContents.tsx index eae1479346..7217573145 100644 --- a/web/packages/accounts/components/LoginContents.tsx +++ b/web/packages/accounts/components/LoginContents.tsx @@ -1,9 +1,7 @@ import { Input, Stack, TextField, Typography } from "@mui/material"; import { AccountsPageFooter } from "ente-accounts/components/layouts/centered-paper"; -import { - getSRPAttributes, - saveSRPAttributes, -} from "ente-accounts/services/srp"; +import { saveSRPAttributes } from "ente-accounts/services/accounts-db"; +import { getSRPAttributes } from "ente-accounts/services/srp"; import { savePartialLocalUser, sendOTT } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { LoadingButton } from "ente-base/components/mui/LoadingButton"; diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index bbeb2136a0..7c2a30c03b 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -19,6 +19,8 @@ import { getToken, savedIsFirstLogin, saveIsFirstLogin, + saveKeyAttributes, + saveSRPAttributes, setData, setLSUser, } from "ente-accounts/services/accounts-db"; @@ -105,8 +107,8 @@ const Page: React.FC = () => { case "valid": break; case "validButPasswordChanged": - setData("keyAttributes", session.updatedKeyAttributes); - setData("srpAttributes", session.updatedSRPAttributes); + saveKeyAttributes(session.updatedKeyAttributes); + saveSRPAttributes(session.updatedSRPAttributes); // Set a flag that causes new interactive key attributes to // be generated. saveIsFirstLogin(); diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index b9dadaf7ce..66c5f0df36 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -28,8 +28,8 @@ const Page: React.FC = () => { const response = searchParams.get("response"); if (!passkeySessionID || !response) return; - void saveCredentialsAndNavigateTo(passkeySessionID, response).then( - (slug: string) => router.push(slug), + void saveQueryCredentialsAndNavigateTo(passkeySessionID, response).then( + (slug) => router.push(slug), ); }, [router]); @@ -51,10 +51,13 @@ export default Page; * * @returns the slug that we should navigate to now. */ -const saveCredentialsAndNavigateTo = async ( +const saveQueryCredentialsAndNavigateTo = async ( passkeySessionID: string, response: string, ) => { + // This function's implementation is on the same lines as that of the + // `saveCredentialsAndNavigateTo` function in passkey utilities. + const inflightPasskeySessionID = nullToUndefined( sessionStorage.getItem("inflightPasskeySessionID"), ); diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index acc4ac5aa2..8582d619ec 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -3,6 +3,7 @@ import log from "ente-base/log"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; import { RemoteKeyAttributes, type KeyAttributes } from "./user"; +import { RemoteSRPAttributes, type SRPAttributes } from "./srp"; export type LocalStorageKey = | "user" @@ -179,6 +180,27 @@ export const saveOriginalKeyAttributes = (keyAttributes: KeyAttributes) => JSON.stringify(keyAttributes), ); +/** + * Return the user's {@link SRPAttributes} if they are present in local storage. + * + * Like key attributes, SRP attributes are also stored in the browser's local + * storage so will not be accessible to web workers. + */ +export const savedSRPAttributes = (): SRPAttributes | undefined => { + const jsonString = localStorage.getItem("srpAttributes"); + if (!jsonString) return undefined; + return RemoteSRPAttributes.parse(JSON.parse(jsonString)); +}; + +/** + * Save the user's {@link SRPAttributes} in local storage. + * + * Use {@link savedSRPAttributes} to retrieve them. + */ +export const saveSRPAttributes = (srpAttributes: SRPAttributes) => + localStorage.setItem("srpAttributes", JSON.stringify(srpAttributes)); + + export const getToken = (): string => { const token = getData("user")?.token; return token; diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 9b7bbd0515..47ed81dfac 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,6 +1,6 @@ import { getData, - setData, + saveKeyAttributes, setLSUser, } from "ente-accounts/services/accounts-db"; import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; @@ -243,12 +243,13 @@ export const checkPasskeyVerificationStatus = async ( export const saveCredentialsAndNavigateTo = async ( response: TwoFactorAuthorizationResponse, ) => { - // This method somewhat duplicates `saveCredentialsAndNavigateTo` in the - // /passkeys/finish page. + // The implementation is similar to the `saveQueryCredentialsAndNavigateTo` + // function on the "/passkeys/finish" page. + const { id, encryptedToken, keyAttributes } = response; await setLSUser({ ...getData("user"), encryptedToken, id }); - setData("keyAttributes", keyAttributes); + saveKeyAttributes(keyAttributes); return unstashRedirect() ?? "/credentials"; }; diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index 8618957f89..3fded86f0c 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -190,7 +190,7 @@ export interface SRPAttributes { * it to local storage, so the same schema describes both the remote type and * the local storage type. */ -const RemoteSRPAttributes = z.object({ +export const RemoteSRPAttributes = z.object({ srpUserID: z.string(), srpSalt: z.string(), memLimit: z.number(), @@ -221,26 +221,6 @@ export const getSRPAttributes = async ( .attributes; }; -/** - * Return the user's {@link SRPAttributes} if they are present in local storage. - * - * Like key attributes, SRP attributes are also stored in the browser's local - * storage so will not be accessible to web workers. - */ -export const savedSRPAttributes = (): SRPAttributes | undefined => { - const jsonString = localStorage.getItem("srpAttributes"); - if (!jsonString) return undefined; - return RemoteSRPAttributes.parse(JSON.parse(jsonString)); -}; - -/** - * Save the user's {@link SRPAttributes} in local storage. - * - * Use {@link savedSRPAttributes} to retrieve them. - */ -export const saveSRPAttributes = (srpAttributes: SRPAttributes) => - localStorage.setItem("srpAttributes", JSON.stringify(srpAttributes)); - /** * A local-only structure holding information required for SRP setup. * diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index c34a178d07..eb54e1b94c 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -2,12 +2,12 @@ import { getData, savedKeyAttributes, saveKeyAttributes, + saveSRPAttributes, setLSUser, } from "ente-accounts/services/accounts-db"; import { generateSRPSetupAttributes, getSRPAttributes, - saveSRPAttributes, updateSRPAndKeyAttributes, type UpdatedKeyAttr, } from "ente-accounts/services/srp"; From 802a3edf9be8545d84af5679ff99088f0087da29 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 08:40:50 +0530 Subject: [PATCH 009/109] Handle qp --- .../accounts/pages/passkeys/finish.tsx | 32 ++++++++----------- .../accounts/pages/two-factor/verify.tsx | 2 ++ web/packages/accounts/services/passkey.ts | 14 ++++++-- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index 66c5f0df36..edaeb312dc 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -1,9 +1,10 @@ import { getData, - setData, + saveKeyAttributes, setLSUser, } from "ente-accounts/services/accounts-db"; import { unstashRedirect } from "ente-accounts/services/redirect"; +import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { LoadingIndicator } from "ente-base/components/loaders"; import { fromB64URLSafeNoPadding } from "ente-base/crypto"; import log from "ente-base/log"; @@ -57,6 +58,8 @@ const saveQueryCredentialsAndNavigateTo = async ( ) => { // This function's implementation is on the same lines as that of the // `saveCredentialsAndNavigateTo` function in passkey utilities. + // + // See: [Note: Ending the passkey flow] const inflightPasskeySessionID = nullToUndefined( sessionStorage.getItem("inflightPasskeySessionID"), @@ -78,28 +81,19 @@ const saveQueryCredentialsAndNavigateTo = async ( // Decode response string (inverse of the steps we perform in // `passkeyAuthenticationSuccessRedirectURL`). - const decodedResponse = JSON.parse( - new TextDecoder().decode(await fromB64URLSafeNoPadding(response)), + const decodedResponse = TwoFactorAuthorizationResponse.parse( + JSON.parse( + new TextDecoder().decode(await fromB64URLSafeNoPadding(response)), + ), ); - // Only one of `encryptedToken` or `token` will be present depending on the - // account's lifetime: - // - // - The plaintext "token" will be passed during fresh signups, where we - // don't yet have keys to encrypt it, the account itself is being created - // as we go through this flow. - // - // TODO: Conceptually this cannot happen. During a _real_ fresh signup - // we'll never enter the passkey verification flow. Remove this code after - // making sure that it doesn't get triggered in cases where an existing - // user goes through the new user flow. - // - // - The encrypted `encryptedToken` will be present otherwise (i.e. if the - // user is signing into an existing account). - const { keyAttributes, encryptedToken, token, id } = decodedResponse; + const { id, keyAttributes, encryptedToken } = decodedResponse; + + // TODO: See: [Note: empty token?] + const token = undefined; await setLSUser({ ...getData("user"), token, encryptedToken, id }); - setData("keyAttributes", keyAttributes); + saveKeyAttributes(keyAttributes); return unstashRedirect() ?? "/credentials"; }; diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index a5badf4043..a3f6849c2d 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -49,6 +49,8 @@ const Page: React.FC = () => { await setLSUser({ ...getData("user"), id, + // TODO: [Note: empty token?] + // // The original code was parsing an token which is never going // to be present in the response, so effectively was always // setting token to undefined. So this works, but is it needed? diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 47ed81dfac..e180cf5552 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -243,8 +243,18 @@ export const checkPasskeyVerificationStatus = async ( export const saveCredentialsAndNavigateTo = async ( response: TwoFactorAuthorizationResponse, ) => { - // The implementation is similar to the `saveQueryCredentialsAndNavigateTo` - // function on the "/passkeys/finish" page. + // [Note: Ending the passkey flow] + // + // The implementation of this function is similar to that of the + // `saveQueryCredentialsAndNavigateTo` on the "/passkeys/finish" page. + // + // This one, `saveCredentialsAndNavigateTo`, is used when the user presses + // the check verification status button on the page that triggered the + // passkey flow (when they're using the desktop app). + // + // The other one, `saveQueryCredentialsAndNavigateTo`, is used when the user + // goes through the passkey flow in the browser itself (when they are using + // the web app). const { id, encryptedToken, keyAttributes } = response; From 5c4b4b9194a73b12cf00047161f5b6c3223f9495 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 09:47:47 +0530 Subject: [PATCH 010/109] Rest --- .../accounts/components/SignUpContents.tsx | 6 +-- web/packages/accounts/pages/credentials.tsx | 17 +++---- web/packages/accounts/pages/recover.tsx | 10 ++-- .../accounts/pages/two-factor/verify.tsx | 4 +- web/packages/accounts/pages/verify.tsx | 18 ++++---- web/packages/accounts/services/accounts-db.ts | 46 ++++++++++++++++--- web/packages/accounts/services/session.ts | 14 ++++-- web/packages/accounts/services/srp.ts | 36 +-------------- 8 files changed, 77 insertions(+), 74 deletions(-) diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index 2392333843..3dad9a3ede 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -17,11 +17,9 @@ import { saveJustSignedUp, saveOriginalKeyAttributes, stashReferralSource, -} from "ente-accounts/services/accounts-db"; -import { - generateSRPSetupAttributes, stashSRPSetupAttributes, -} from "ente-accounts/services/srp"; +} from "ente-accounts/services/accounts-db"; +import { generateSRPSetupAttributes } from "ente-accounts/services/srp"; import { generateAndSaveInteractiveKeyAttributes, generateKeysAndAttributes, diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 7c2a30c03b..8579913702 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -18,10 +18,11 @@ import { getData, getToken, savedIsFirstLogin, + savedKeyAttributes, + savedSRPAttributes, saveIsFirstLogin, saveKeyAttributes, saveSRPAttributes, - setData, setLSUser, } from "ente-accounts/services/accounts-db"; import { @@ -138,8 +139,8 @@ const Page: React.FC = () => { return; } const kek = await unstashKeyEncryptionKeyFromSession(); - const keyAttributes: KeyAttributes = getData("keyAttributes"); - const srpAttributes: SRPAttributes = getData("srpAttributes"); + const keyAttributes = savedKeyAttributes(); + const srpAttributes = savedSRPAttributes(); if (getToken()) { setSessionValidityCheck(validateSession()); @@ -153,7 +154,7 @@ const Page: React.FC = () => { }, kek, ); - void postVerification(masterKey, kek, keyAttributes); + await postVerification(masterKey, kek, keyAttributes); return; } @@ -242,7 +243,7 @@ const Page: React.FC = () => { id, isTwoFactorEnabled: false, }); - if (keyAttributes) setData("keyAttributes", keyAttributes); + if (keyAttributes) saveKeyAttributes(keyAttributes); return keyAttributes; } } catch (e) { @@ -278,14 +279,14 @@ const Page: React.FC = () => { await saveMasterKeyInSessionAndSafeStore(masterKey); await decryptAndStoreToken(keyAttributes, masterKey); try { - let srpAttributes: SRPAttributes | null | undefined = - getData("srpAttributes"); + let srpAttributes = savedSRPAttributes(); if (!srpAttributes && user) { srpAttributes = await getSRPAttributes(user.email); if (srpAttributes) { - setData("srpAttributes", srpAttributes); + saveSRPAttributes(srpAttributes); } } + // TODO: todo? log.debug(() => `userSRPSetupPending ${!srpAttributes}`); if (!srpAttributes) { await setupSRP(await generateSRPSetupAttributes(kek)); diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 8b9da40e4f..92d07f86d4 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -3,7 +3,10 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; -import { getData } from "ente-accounts/services/accounts-db"; +import { + getData, + savedKeyAttributes, +} from "ente-accounts/services/accounts-db"; import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import type { KeyAttributes, User } from "ente-accounts/services/user"; @@ -30,13 +33,12 @@ const Page: React.FC = () => { const [keyAttributes, setKeyAttributes] = useState< KeyAttributes | undefined - >(); + >(undefined); const router = useRouter(); useEffect(() => { const user: User = getData("user"); - const keyAttributes: KeyAttributes = getData("keyAttributes"); if (!user?.email) { void router.push("/"); return; @@ -47,6 +49,8 @@ const Page: React.FC = () => { void router.push("/verify"); return; } + + const keyAttributes = savedKeyAttributes(); if (!keyAttributes) { void router.push("/generate"); } else if (haveCredentialsInSession()) { diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index a3f6849c2d..2c8f39f394 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -1,7 +1,7 @@ import { Verify2FACodeForm } from "ente-accounts/components/Verify2FACodeForm"; import { getData, - setData, + saveKeyAttributes, setLSUser, } from "ente-accounts/services/accounts-db"; import type { User } from "ente-accounts/services/user"; @@ -57,7 +57,7 @@ const Page: React.FC = () => { token: undefined, encryptedToken, }); - setData("keyAttributes", keyAttributes); + saveKeyAttributes(keyAttributes); await router.push(unstashRedirect() ?? "/credentials"); } catch (e) { if (isHTTPErrorWithStatus(e, 404)) { diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 8f72ccac44..d65e2c4d4b 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -9,11 +9,14 @@ import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice" import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice"; import { getData, + savedKeyAttributes, savedOriginalKeyAttributes, + savedSRPAttributes, saveIsFirstLogin, saveKeyAttributes, saveOriginalKeyAttributes, setLSUser, + unstashAfterUseSRPSetupAttributes, unstashReferralSource, } from "ente-accounts/services/accounts-db"; import { @@ -24,13 +27,8 @@ import { stashedRedirect, unstashRedirect, } from "ente-accounts/services/redirect"; -import { - getSRPAttributes, - setupSRP, - unstashAndUseSRPSetupAttributes, - type SRPAttributes, -} from "ente-accounts/services/srp"; -import type { KeyAttributes, User } from "ente-accounts/services/user"; +import { getSRPAttributes, setupSRP } from "ente-accounts/services/srp"; +import type { User } from "ente-accounts/services/user"; import { putUserKeyAttributes, sendOTT, @@ -142,7 +140,7 @@ const Page: React.FC = () => { if (originalKeyAttributes) { await putUserKeyAttributes(originalKeyAttributes); } - await unstashAndUseSRPSetupAttributes(setupSRP); + await unstashAfterUseSRPSetupAttributes(setupSRP); } saveIsFirstLogin(); const redirectURL = unstashRedirect(); @@ -261,7 +259,7 @@ const redirectionIfNeeded = async (user: User | undefined) => { return "/"; } - const keyAttributes: KeyAttributes = getData("keyAttributes"); + const keyAttributes = savedKeyAttributes(); if (keyAttributes?.encryptedKey && (user.token || user.encryptedToken)) { return "/credentials"; @@ -281,7 +279,7 @@ const redirectionIfNeeded = async (user: User | undefined) => { // saved them). If they are present and indicate that email verification is // not required, redirect to the password verification page. - const srpAttributes: SRPAttributes = getData("srpAttributes"); + const srpAttributes = savedSRPAttributes(); if (srpAttributes && !srpAttributes.isEmailMFAEnabled) { // Fetch the latest SRP attributes instead of relying on the potentially // stale stored values. This is an infrequent scenario path, so extra diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 8582d619ec..19893578bd 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -2,15 +2,14 @@ import { getKVS, removeKV, setKV } from "ente-base/kv"; import log from "ente-base/log"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; +import { + RemoteSRPAttributes, + SRPSetupAttributes, + type SRPAttributes, +} from "./srp"; import { RemoteKeyAttributes, type KeyAttributes } from "./user"; -import { RemoteSRPAttributes, type SRPAttributes } from "./srp"; -export type LocalStorageKey = - | "user" - | "keyAttributes" - // Moved to ente-accounts - // "srpSetupAttributes" - | "srpAttributes"; +export type LocalStorageKey = "user"; /** * [Note: Accounts DB] @@ -200,6 +199,39 @@ export const savedSRPAttributes = (): SRPAttributes | undefined => { export const saveSRPAttributes = (srpAttributes: SRPAttributes) => localStorage.setItem("srpAttributes", JSON.stringify(srpAttributes)); +/** + * Save {@link SRPSetupAttributes} in local storage for later use via + * {@link unstashAfterUseSRPSetupAttributes}. + * + * See: [Note: SRP setup attributes] + */ +export const stashSRPSetupAttributes = ( + srpSetupAttributes: SRPSetupAttributes, +) => + localStorage.setItem( + "srpSetupAttributes", + JSON.stringify(srpSetupAttributes), + ); + +/** + * Retrieve the {@link SRPSetupAttributes}, if any, that were stashed by a + * previous call to {@link stashSRPSetupAttributes}. + * + * - If they are found, then invoke the provided callback ({@link cb}) with the + * value. If the promise returned by the callback fulfills, then remove the + * stashed value from local storage. + * + * - If they are not found, then the callback is not invoked. + */ +export const unstashAfterUseSRPSetupAttributes = async ( + cb: (srpSetupAttributes: SRPSetupAttributes) => Promise, +) => { + const jsonString = localStorage.getItem("srpSetupAttributes"); + if (!jsonString) return; + const srpSetupAttributes = SRPSetupAttributes.parse(JSON.parse(jsonString)); + await cb(srpSetupAttributes); + localStorage.removeItem("srpSetupAttributes"); +}; export const getToken = (): string => { const token = getData("user")?.token; diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 8c51839582..414fba3378 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -1,6 +1,6 @@ import { - getData, savedOriginalKeyAttributes, + savedSRPAttributes, } from "ente-accounts/services/accounts-db"; import type { KeyAttributes } from "ente-accounts/services/user"; import { authenticatedRequestHeaders, HTTPError } from "ente-base/http"; @@ -27,6 +27,9 @@ type SessionValidity = const SessionValidityResponse = z.object({ hasSetKeys: z.boolean(), + /** + * Will not be present if {@link hasSetKeys} is `false`. + */ keyAttributes: RemoteKeyAttributes.nullish().transform(nullToUndefined), }); @@ -85,15 +88,16 @@ export const checkSessionValidity = async (): Promise => { else throw new HTTPError(res); } - // See if the response contains keyAttributes (they might not for older - // deployments). + // See if the response contains keyAttributes (it will not if `hasSetKeys` + // in the response is `false`). const { keyAttributes } = SessionValidityResponse.parse(await res.json()); if (keyAttributes) { const remoteKeyAttributes = keyAttributes; // We should have these values locally if we reach here. const email = ensureLocalUser().email; - const localSRPAttributes = getData("srpAttributes")!; + // TODO(RE): This is not being set until initial reauthentication. + const localSRPAttributes = savedSRPAttributes()!; // Fetch the remote SRP attributes. // @@ -124,7 +128,7 @@ export const checkSessionValidity = async (): Promise => { } } - // The token is still valid (to the best of our ascertainable knowledge). + // The token is still valid. return { status: "valid" }; }; diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index 3fded86f0c..7e3c1bf209 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -245,7 +245,7 @@ export const getSRPAttributes = async ( * temporarily stash them in local storage using an object that conforms to the * following {@link SRPSetupAttributes} schema. */ -const SRPSetupAttributes = z.object({ +export const SRPSetupAttributes = z.object({ srpUserID: z.string(), srpSalt: z.string(), srpVerifier: z.string(), @@ -301,40 +301,6 @@ const b64ToBuffer = (base64: string) => Buffer.from(base64, "base64"); const bufferToB64 = (buffer: Buffer) => buffer.toString("base64"); -/** - * Save {@link SRPSetupAttributes} in local storage for later use via - * {@link unstashAndUseSRPSetupAttributes}. - * - * See: [Note: SRP setup attributes] - */ -export const stashSRPSetupAttributes = ( - srpSetupAttributes: SRPSetupAttributes, -) => - localStorage.setItem( - "srpSetupAttributes", - JSON.stringify(srpSetupAttributes), - ); - -/** - * Retrieve the {@link SRPSetupAttributes}, if any, that were stashed by a - * previous call to {@link stashSRPSetupAttributes}. - * - * - If they are found, then invoke the provided callback ({@link cb}) with the - * value. If the promise returned by the callback fulfills, then remove the - * stashed value from local storage. - * - * - If they are not found, then the callback is not invoked. - */ -export const unstashAndUseSRPSetupAttributes = async ( - cb: (srpSetupAttributes: SRPSetupAttributes) => Promise, -) => { - const jsonString = localStorage.getItem("srpSetupAttributes"); - if (!jsonString) return; - const srpSetupAttributes = SRPSetupAttributes.parse(JSON.parse(jsonString)); - await cb(srpSetupAttributes); - localStorage.removeItem("srpSetupAttributes"); -}; - /** * Use the provided {@link SRPSetupAttributes} to, well, setup SRP. * From fd39c78e5d77103c176282ac3894b8dcf6f0c0d6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 11:01:31 +0530 Subject: [PATCH 011/109] Cleanup after ourselves --- web/packages/accounts/pages/passkeys/finish.tsx | 1 + web/packages/accounts/services/passkey.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index edaeb312dc..1518a61b94 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -64,6 +64,7 @@ const saveQueryCredentialsAndNavigateTo = async ( const inflightPasskeySessionID = nullToUndefined( sessionStorage.getItem("inflightPasskeySessionID"), ); + if ( !inflightPasskeySessionID || passkeySessionID != inflightPasskeySessionID diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index e180cf5552..52f39ab7ec 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -256,6 +256,8 @@ export const saveCredentialsAndNavigateTo = async ( // goes through the passkey flow in the browser itself (when they are using // the web app). + sessionStorage.removeItem("inflightPasskeySessionID"); + const { id, encryptedToken, keyAttributes } = response; await setLSUser({ ...getData("user"), encryptedToken, id }); From 67c65657a469b69c9c024d92fb853bb73c997aed Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 11:54:42 +0530 Subject: [PATCH 012/109] Split --- web/apps/auth/src/pages/_app.tsx | 6 +- web/apps/photos/src/pages/_app.tsx | 5 +- web/apps/photos/src/utils/file/index.ts | 4 +- .../accounts/components/LoginContents.tsx | 7 +- .../accounts/components/SignUpContents.tsx | 2 +- .../accounts/pages/change-password.tsx | 11 +- web/packages/accounts/pages/credentials.tsx | 8 +- web/packages/accounts/pages/generate.tsx | 8 +- web/packages/accounts/pages/recover.tsx | 7 +- .../accounts/pages/two-factor/verify.tsx | 4 +- web/packages/accounts/pages/verify.tsx | 6 +- web/packages/accounts/services/accounts-db.ts | 169 +++++++++++++++--- web/packages/accounts/services/user.ts | 122 +++---------- .../new/photos/components/gallery/reducer.ts | 22 +-- web/packages/new/photos/services/settings.ts | 8 +- 15 files changed, 220 insertions(+), 169 deletions(-) diff --git a/web/apps/auth/src/pages/_app.tsx b/web/apps/auth/src/pages/_app.tsx index b405e4cacc..28792966c3 100644 --- a/web/apps/auth/src/pages/_app.tsx +++ b/web/apps/auth/src/pages/_app.tsx @@ -1,9 +1,8 @@ import "@fontsource-variable/inter"; import { CssBaseline } from "@mui/material"; import { ThemeProvider } from "@mui/material/styles"; -import { getData } from "ente-accounts/services/accounts-db"; +import { savedLocalUser } from "ente-accounts/services/accounts-db"; import { accountLogout } from "ente-accounts/services/logout"; -import type { User } from "ente-accounts/services/user"; import { staticAppTitle } from "ente-base/app"; import { CustomHead } from "ente-base/components/Head"; import { @@ -32,8 +31,7 @@ const App: React.FC = ({ Component, pageProps }) => { const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog(); useEffect(() => { - const user = getData("user") as User | undefined | null; - logStartupBanner(user?.id); + logStartupBanner(savedLocalUser()?.id); }, []); const logout = useCallback(() => { diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 39dc37b57c..baa3f357e9 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -6,8 +6,8 @@ import { useNotification } from "components/utils/hooks-app"; import { getData, isLocalStorageAndIndexedDBMismatch, + savedLocalUser, } from "ente-accounts/services/accounts-db"; -import type { User } from "ente-accounts/services/user"; import { isDesktop, staticAppTitle } from "ente-base/app"; import { CenteredRow } from "ente-base/components/containers"; import { CustomHead } from "ente-base/components/Head"; @@ -68,8 +68,7 @@ const App: React.FC = ({ Component, pageProps }) => { const logout = useCallback(() => void photosLogout(), []); useEffect(() => { - const user = getData("user") as User | undefined | null; - logStartupBanner(user?.id); + logStartupBanner(savedLocalUser()?.id); void isLocalStorageAndIndexedDBMismatch().then((mismatch) => { if (mismatch) { log.error("Logging out (IndexedDB and local storage mismatch)"); diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 07930d19a1..03ccbc90a0 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,5 +1,5 @@ import { getData } from "ente-accounts/services/accounts-db"; -import type { LocalUser, User } from "ente-accounts/services/user"; +import type { LocalUser, PartialLocalUser } from "ente-accounts/services/user"; import { joinPath } from "ente-base/file-name"; import log from "ente-base/log"; import { type Electron } from "ente-base/types/ipc"; @@ -284,7 +284,7 @@ export const getArchivedFiles = (files: EnteFile[]) => { }; export const getUserOwnedFiles = (files: EnteFile[]) => { - const user: User = getData("user"); + const user: PartialLocalUser = getData("user"); if (!user?.id) { throw Error("user missing"); } diff --git a/web/packages/accounts/components/LoginContents.tsx b/web/packages/accounts/components/LoginContents.tsx index 7217573145..f0677daa52 100644 --- a/web/packages/accounts/components/LoginContents.tsx +++ b/web/packages/accounts/components/LoginContents.tsx @@ -1,8 +1,11 @@ import { Input, Stack, TextField, Typography } from "@mui/material"; import { AccountsPageFooter } from "ente-accounts/components/layouts/centered-paper"; -import { saveSRPAttributes } from "ente-accounts/services/accounts-db"; +import { + savePartialLocalUser, + saveSRPAttributes, +} from "ente-accounts/services/accounts-db"; import { getSRPAttributes } from "ente-accounts/services/srp"; -import { savePartialLocalUser, sendOTT } from "ente-accounts/services/user"; +import { sendOTT } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { LoadingButton } from "ente-base/components/mui/LoadingButton"; import { isMuseumHTTPError } from "ente-base/http"; diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index 3dad9a3ede..3a04d6e7e2 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -16,6 +16,7 @@ import { import { saveJustSignedUp, saveOriginalKeyAttributes, + savePartialLocalUser, stashReferralSource, stashSRPSetupAttributes, } from "ente-accounts/services/accounts-db"; @@ -23,7 +24,6 @@ import { generateSRPSetupAttributes } from "ente-accounts/services/srp"; import { generateAndSaveInteractiveKeyAttributes, generateKeysAndAttributes, - savePartialLocalUser, sendOTT, type GenerateKeysAndAttributesResult, } from "ente-accounts/services/user"; diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index 462121a4eb..aea4641c30 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -5,11 +5,7 @@ import { AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; -import { - changePassword, - localUser, - type LocalUser, -} from "ente-accounts/services/user"; +import { changePassword, type LocalUser } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { LoadingIndicator } from "ente-base/components/loaders"; import { deriveKeyInsufficientMemoryErrorMessage } from "ente-base/crypto/types"; @@ -21,12 +17,13 @@ import { NewPasswordForm, type NewPasswordFormProps, } from "../components/NewPasswordForm"; +import { savedLocalUser } from "../services/accounts-db"; /** * A page that allows a user to reset or change their password. */ const Page: React.FC = () => { - const [user, setUser] = useState(); + const [user, setUser] = useState(undefined); const router = useRouter(); @@ -34,7 +31,7 @@ const Page: React.FC = () => { const isReset = router.query.op == "reset"; useEffect(() => { - const user = localUser(); + const user = savedLocalUser(); if (user) { setUser(user); } else { diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 8579913702..1d91bb89e5 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -45,7 +45,7 @@ import { import { generateAndSaveInteractiveKeyAttributes, type KeyAttributes, - type User, + type PartialLocalUser, } from "ente-accounts/services/user"; import { decryptAndStoreToken } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; @@ -81,7 +81,7 @@ const Page: React.FC = () => { const [srpAttributes, setSrpAttributes] = useState(); const [keyAttributes, setKeyAttributes] = useState(); - const [user, setUser] = useState(); + const [user, setUser] = useState(); const [passkeyVerificationData, setPasskeyVerificationData] = useState< { passkeySessionID: string; url: string } | undefined >(); @@ -127,7 +127,7 @@ const Page: React.FC = () => { useEffect(() => { const main = async () => { - const user: User = getData("user"); + const user: PartialLocalUser = getData("user"); if (!user?.email) { void router.push("/"); return; @@ -280,7 +280,7 @@ const Page: React.FC = () => { await decryptAndStoreToken(keyAttributes, masterKey); try { let srpAttributes = savedSRPAttributes(); - if (!srpAttributes && user) { + if (!srpAttributes && user?.email) { srpAttributes = await getSRPAttributes(user.email); if (srpAttributes) { saveSRPAttributes(srpAttributes); diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index c563bdb245..4e5d38ed1b 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -16,7 +16,7 @@ import { generateSRPSetupAttributes, setupSRP, } from "ente-accounts/services/srp"; -import type { User } from "ente-accounts/services/user"; +import type { PartialLocalUser } from "ente-accounts/services/user"; import { generateAndSaveInteractiveKeyAttributes, generateKeysAndAttributes, @@ -42,14 +42,14 @@ import { const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); - const [user, setUser] = useState(); + const [user, setUser] = useState(); const [openRecoveryKey, setOpenRecoveryKey] = useState(false); const [loading, setLoading] = useState(true); const router = useRouter(); useEffect(() => { - const user: User = getData("user"); + const user: PartialLocalUser = getData("user"); setUser(user); if (!user?.token) { void router.push("/"); @@ -109,7 +109,7 @@ const Page: React.FC = () => { {t("set_password")} diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 92d07f86d4..65e9b71e10 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -9,7 +9,10 @@ import { } from "ente-accounts/services/accounts-db"; import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; -import type { KeyAttributes, User } from "ente-accounts/services/user"; +import type { + KeyAttributes, + PartialLocalUser, +} from "ente-accounts/services/user"; import { sendOTT } from "ente-accounts/services/user"; import { decryptAndStoreToken } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; @@ -38,7 +41,7 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user: User = getData("user"); + const user: PartialLocalUser = getData("user"); if (!user?.email) { void router.push("/"); return; diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index 2c8f39f394..8c32a92b3e 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -4,7 +4,7 @@ import { saveKeyAttributes, setLSUser, } from "ente-accounts/services/accounts-db"; -import type { User } from "ente-accounts/services/user"; +import type { PartialLocalUser } from "ente-accounts/services/user"; import { verifyTwoFactor } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; @@ -27,7 +27,7 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user: User = getData("user"); + const user: PartialLocalUser = getData("user"); if (!user?.email || !user.twoFactorSessionID) { void router.push("/"); } else if ( diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index d65e2c4d4b..4b873a1e0a 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -28,7 +28,7 @@ import { unstashRedirect, } from "ente-accounts/services/redirect"; import { getSRPAttributes, setupSRP } from "ente-accounts/services/srp"; -import type { User } from "ente-accounts/services/user"; +import type { PartialLocalUser } from "ente-accounts/services/user"; import { putUserKeyAttributes, sendOTT, @@ -69,7 +69,7 @@ const Page: React.FC = () => { useEffect(() => { const main = async () => { - const user: User = getData("user"); + const user: PartialLocalUser = getData("user"); const redirect = await redirectionIfNeeded(user); if (redirect) { @@ -253,7 +253,7 @@ export default Page; * * @returns The slug to redirect to, if needed. */ -const redirectionIfNeeded = async (user: User | undefined) => { +const redirectionIfNeeded = async (user: PartialLocalUser | undefined) => { const email = user?.email; if (!email) { return "/"; diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 19893578bd..2aff817739 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -1,18 +1,5 @@ -import { getKVS, removeKV, setKV } from "ente-base/kv"; -import log from "ente-base/log"; -import { nullToUndefined } from "ente-utils/transform"; -import { z } from "zod/v4"; -import { - RemoteSRPAttributes, - SRPSetupAttributes, - type SRPAttributes, -} from "./srp"; -import { RemoteKeyAttributes, type KeyAttributes } from "./user"; - -export type LocalStorageKey = "user"; - /** - * [Note: Accounts DB] + * @file [Note: Accounts DB] * * The accounts package stores various state both during the login / signup * flow, and post login to identify the logged in user. @@ -29,6 +16,145 @@ export type LocalStorageKey = "user"; * - "originalKeyAttributes" * - "srpAttributes" */ + +import { getKVS, removeKV, setKV } from "ente-base/kv"; +import log from "ente-base/log"; +import { getAuthToken } from "ente-base/token"; +import { nullToUndefined } from "ente-utils/transform"; +import { z } from "zod/v4"; +import { + RemoteSRPAttributes, + SRPSetupAttributes, + type SRPAttributes, +} from "./srp"; +import { + RemoteKeyAttributes, + type KeyAttributes, + type LocalUser, +} from "./user"; + +/** + * The local storage data about the user before login or signup is complete. + * + * [Note: Partial local user] + * + * During login or signup, the user object exists in various partial states in + * local storage. + * + * - Initially, there is no user object in local storage. + * + * - When the user enters their email, the email property of the stored object + * is set, but nothing else. + * + * - After they verify their password, we have two cases: if second factor + * verification is not set, and when it is set. + * + * - If second factor verification is not set, then after verifying their + * password their {@link id} and {@link encryptedToken} will get filled in, + * and {@link isTwoFactorEnabled} will be set to false. + * + * - If they have second factor verification set, then after verifying their + * password {@link isTwoFactorEnabled} and {@link twoFactorSessionID} will + * also get filled in. Once they verify their TOTP based second factor, their + * {@link id} and {@link encryptedToken} will also get filled in. + * + * - As the login or signup sequence completes, a {@link token} obtained from + * the {@link encryptedToken} will be written out, and the + * {@link encryptedToken} cleared since it is not needed anymore. + * + * So while the underlying storage is the same, we offer two APIs for code to + * obtain the user: + * + * - Before login is complete, or when it is unknown if login is complete or + * not, then {@link savedPartialLocalUser} can be used to obtain a + * {@link PartialLocalUser} with all of its properties set to be optional (and + * some additional properties not available in the regular user object). + * + * - When we know that the login has completed, we can use either + * {@link savedLocalUser} (which returns `undefined` if our assumption is + * false) or {@link ensureSavedLocalUser} (which throws if our assumption is + * false) to obtain an object with all the properties expected to be present + * for a locally persisted user set to be required. + */ +export interface PartialLocalUser { + id?: number; + email?: string; + token?: string; + encryptedToken?: string; + isTwoFactorEnabled?: boolean; + twoFactorSessionID?: string; +} + +const PartialLocalUser = z.object({ + id: z.number().nullish().transform(nullToUndefined), + email: z.string().nullish().transform(nullToUndefined), + token: z.string().nullish().transform(nullToUndefined), + encryptedToken: z.string().nullish().transform(nullToUndefined), + isTwoFactorEnabled: z.boolean().nullish().transform(nullToUndefined), + twoFactorSessionID: z.string().nullish().transform(nullToUndefined), +}); + +/** + * Zod schema for the {@link LocalUser} TypeScript type. + * + * The type itself is in `user.ts`. + */ +const LocalUser = z.object({ + id: z.number(), + email: z.string(), + token: z.string(), +}); + +/** + * Return the local storage value of the user's data. + * + * This function is meant to be called during the login or signup sequence. + * After the user is logged in, use {@link savedLocalUser} or + * {@link ensureLocalUser} instead. + * + * Use {@link savePartialLocalUser} to updated the saved value. + */ +export const savedPartialLocalUser = (): PartialLocalUser | undefined => { + const jsonString = localStorage.getItem("user"); + if (!jsonString) return undefined; + return PartialLocalUser.parse(JSON.parse(jsonString)); +}; + +/** + * Save the users data as we accrue it during the signup or login flow. + * + * See: [Note: Partial local user]. + * + * TODO: WARNING: This does not update the KV token. The idea is to gradually + * move over uses of setLSUser to this while explicitly setting the KV token + * where needed. + */ +export const savePartialLocalUser = (partialLocalUser: Partial) => + localStorage.setItem("user", JSON.stringify(partialLocalUser)); + +/** + * Return data about the logged-in user, if someone is indeed logged in. + * Otherwise return `undefined`. + * + * The user's data is stored in the browser's localStorage. Thus, this function + * only works from the main thread, not from web workers since local storage is + * not accessible to web workers. + * + * There is no setter corresponding to this function since this is only a view + * on data saved using {@link savePartialLocalUser}. + * + * See: [Note: Partial local user] for more about the whole shebang. + */ +export const savedLocalUser = (): LocalUser | undefined => { + const jsonString = localStorage.getItem("user"); + if (!jsonString) return undefined; + // We might have some data, but not all of it. So do a non-throwing parse. + const { success, data } = LocalUser.safeParse(JSON.parse(jsonString)); + return success ? data : undefined; +}; + +export type LocalStorageKey = "user"; + export const getData = (key: LocalStorageKey) => { try { if ( @@ -110,16 +236,8 @@ export const migrateKVToken = async (user: unknown) => { * This acts a sanity check on IndexedDB by ensuring that if the user has a * token in local storage, then it should also be present in IndexedDB. */ -export const isLocalStorageAndIndexedDBMismatch = async () => { - const oldLSUser = getData("user"); - return ( - oldLSUser && - typeof oldLSUser == "object" && - "token" in oldLSUser && - typeof oldLSUser.token == "string" && - !(await getKVS("token")) - ); -}; +export const isLocalStorageAndIndexedDBMismatch = async () => + savedPartialLocalUser()?.token && !(await getAuthToken()); /** * Return the user's {@link KeyAttributes} if they are present in local storage. @@ -155,6 +273,9 @@ export const saveKeyAttributes = (keyAttributes: KeyAttributes) => * either freshly generated (if the user signed up on this client) or were * fetched from remote (otherwise). * + * > NOTE: Currently the code does not guarantee that savedOriginalKeyAttributes + * > will always be set when savedKeyAttributes is set. + * * In contrast, the regular key attributes get overwritten by the local only * interactive key attributes for the user's convenience. See the documentation * of {@link generateAndSaveInteractiveKeyAttributes} for more details. diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index eb54e1b94c..abfd6ea5ed 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -1,9 +1,11 @@ import { getData, savedKeyAttributes, + savedLocalUser, saveKeyAttributes, saveSRPAttributes, setLSUser, + type PartialLocalUser, } from "ente-accounts/services/accounts-db"; import { generateSRPSetupAttributes, @@ -36,121 +38,51 @@ import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; import { getUserRecoveryKey, recoveryKeyFromMnemonic } from "./recovery-key"; -export interface User { - id: number; - email: string; - token: string; - encryptedToken: string; - isTwoFactorEnabled: boolean; - twoFactorSessionID: string; -} +// TODO(RE): Temporary re-export +export type { PartialLocalUser }; /** - * The local storage data about the user after they've logged in. + * The locally persisted data we have about the user after they've logged in. + * + * This type arguably belongs to accounts-db (since that's what persists it and + * its shadow alias, {@link PartialLocalUser}), but since most code that will + * need this will need this after login has completed, and will be using the + * {@link ensureLocalUser} method below, we keep this in the same file to reduce + * the need to import the type from a separate file. */ -const LocalUser = z.object({ +export interface LocalUser { /** * The user's ID. */ - id: z.number(), + id: number; /** - * The user's email. + * The email associated with the user's Ente account. */ - email: z.string(), + email: string; /** * The user's (plaintext) auth token. * * It is used for making API calls on their behalf, by passing this token as * the value of the X-Auth-Token header in the HTTP request. * - * Deprecated, use `getAuthToken()` instead (which fetches it from IDB). + * Usually you shouldn't be needing to access this property; instead use + * {@link getAuthToken()} which is kept in sync with this value, and lives + * in IndexedDB and thus can also be used in web workers. */ - token: z.string(), -}); + token: string; +} /** - * The local storage data about the user after they've logged in. - */ -export type LocalUser = z.infer; - -/** - * The local storage data about the user before login or signup is complete. + * Return the currently logged in {@link LocalUser}, throwing it the user is not + * logged in. * - * [Note: Partial local user] - * - * During login or signup, the user object exists in various partial states in - * local storage. - * - * - Initially, there is no user object in local storage. - * - * - When the user enters their email, the email property of the stored object - * is set, but nothing else. - * - * - After they verify their password, we have two cases: if second factor - * verification is not set, and when it is set. - * - * - If second factor verification is not set, then after verifying their - * password their {@link id} and {@link encryptedToken} will get filled in, - * and {@link isTwoFactorEnabled} will be set to false. - * - * - If they have second factor verification set, then after verifying their - * password {@link isTwoFactorEnabled} and {@link twoFactorSessionID} will - * also get filled in. Once they verify their TOTP based second factor, their - * {@link id} and {@link encryptedToken} will also get filled in. - * - * So while the underlying storage is the same, we offer two APIs for code to - * obtain the user: - * - * - Before login is complete, or when it is unknown if login is complete or - * not, then {@link partialLocalUser} can be used to obtain a - * {@link LocalUser} with all of its properties set to be optional. - * - * - When we know that the login has completed, we can use either - * {@link localUser} (which returns `undefined` if our presumption is false) - * or {@link ensureLocalUser} (which throws if our presumption is false) to - * obtain an object with all the properties expected to be present for a - * locally persisted user set to be required. - */ -export const partialLocalUser = (): Partial | undefined => { - // TODO: duplicate of getData("user") - const s = localStorage.getItem("user"); - if (!s) return undefined; - return LocalUser.partial().parse(JSON.parse(s)); -}; - -/** - * Save the users data as we accrue it during the signup or login flow. - * - * See: [Note: Partial local user]. - * - * TODO: WARNING: This does not update the KV token. The idea is to gradually - * move over uses of setLSUser to this while explicitly setting the KV token - * where needed. - */ -export const savePartialLocalUser = (partialLocalUser: Partial) => - localStorage.setItem("user", JSON.stringify(partialLocalUser)); - -/** - * Return the logged-in user, if someone is indeed logged in. Otherwise return - * `undefined`. - * - * The user's data is stored in the browser's localStorage. Thus, this function - * only works from the main thread, not from web workers (local storage is not - * accessible to web workers). - */ -export const localUser = (): LocalUser | undefined => { - // TODO: duplicate of getData("user") - const s = localStorage.getItem("user"); - if (!s) return undefined; - const { success, data } = LocalUser.safeParse(JSON.parse(s)); - return success ? data : undefined; -}; - -/** - * A wrapper over {@link localUser} with that throws if no one is logged in. + * This is a wrapper over the {@link savedLocalUser} function that throws if no + * one is logged in. A more appropriate name for this function, keeping in line + * with the conventions the other methods follow, would've been + * {@link ensureSavedLocalUser}. The shorter name is for readability. */ export const ensureLocalUser = (): LocalUser => - ensureExpectedLoggedInValue(localUser()); + ensureExpectedLoggedInValue(savedLocalUser()); /** * A function throws an error if a value that is expected to be truthy when the diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 741ffc39d2..56a2590ff0 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -1,4 +1,4 @@ -import type { User } from "ente-accounts/services/user"; +import { type LocalUser } from "ente-accounts/services/user"; import { groupFilesByCollectionID, sortFiles, @@ -108,15 +108,15 @@ export interface GalleryState { /*--< Mostly static state >--*/ /** - * The logged in {@link User}. + * The logged in {@link LocalUser}. * * This is expected to be undefined only for a brief duration until the code * for the initial "mount" runs (If we're not logged in, then the gallery * will redirect the user to an appropriate authentication page). */ - user: User | undefined; + user: LocalUser | undefined; /** - * Family plan related information for the logged in {@link User}. + * Family plan related information for the logged in {@link LocalUser}. */ familyData: FamilyData | undefined; @@ -449,7 +449,7 @@ export interface GalleryState { export type GalleryAction = | { type: "mount"; - user: User; + user: LocalUser; familyData: FamilyData | undefined; collections: Collection[]; collectionFiles: EnteFile[]; @@ -1225,7 +1225,7 @@ const deriveArchivedFileIDs = ( * Compute favorite file IDs from their dependencies. */ const deriveFavoriteFileIDs = ( - user: User, + user: LocalUser, collections: GalleryState["collections"], collectionFiles: GalleryState["collectionFiles"], unsyncedFavoriteUpdates: GalleryState["unsyncedFavoriteUpdates"], @@ -1278,7 +1278,7 @@ const createFileCollectionIDs = (files: EnteFile[]) => */ const deriveNormalCollectionSummaries = ( normalCollections: Collection[], - user: User, + user: LocalUser, trashItems: GalleryState["trashItems"], collectionFiles: GalleryState["collectionFiles"], hiddenFileIDs: GalleryState["hiddenFileIDs"], @@ -1378,7 +1378,7 @@ const pseudoCollectionOptionsForLatestFileAndCount = ( */ const deriveHiddenCollectionSummaries = ( hiddenCollections: Collection[], - user: User, + user: LocalUser, collectionFiles: GalleryState["collectionFiles"], ) => { const hiddenCollectionSummaries = createCollectionSummaries( @@ -1414,7 +1414,7 @@ const deriveUncategorizedCollectionSummaryID = ( PseudoCollectionID.uncategorizedPlaceholder; const createCollectionSummaries = ( - user: User, + user: LocalUser, collections: Collection[], collectionFiles: EnteFile[], ) => { @@ -1968,7 +1968,7 @@ const enqueuePendingSearchSuggestionsIfNeeded = ( * users who have shared a collection with the user. */ const constructUserIDToEmailMap = ( - user: User, + user: LocalUser, collections: GalleryState["collections"], ): Map => { const userIDToEmail = new Map(); @@ -1990,7 +1990,7 @@ const constructUserIDToEmailMap = ( * are trying to share albums with specific users. */ const createShareSuggestionEmails = ( - user: User, + user: LocalUser, familyData: FamilyData | undefined, collections: Collection[], ): string[] => [ diff --git a/web/packages/new/photos/services/settings.ts b/web/packages/new/photos/services/settings.ts index d5ae3bf14d..aedda42384 100644 --- a/web/packages/new/photos/services/settings.ts +++ b/web/packages/new/photos/services/settings.ts @@ -2,7 +2,7 @@ * @file Storage (in-memory, local, remote) and update of various settings. */ -import { partialLocalUser } from "ente-accounts/services/user"; +import { savedPartialLocalUser } from "ente-accounts/services/accounts-db"; import { isDevBuild } from "ente-base/env"; import log from "ente-base/log"; import { updateShouldDisableCFUploadProxy } from "ente-gallery/services/upload"; @@ -195,10 +195,8 @@ const setSettingsSnapshot = (snapshot: Settings) => { */ export const isDevBuildAndUser = () => isDevBuild && isDevUserViaEmail(); -const isDevUserViaEmail = () => { - const user = partialLocalUser(); - return !!user?.email?.endsWith("@ente.io"); -}; +const isDevUserViaEmail = () => + !!savedPartialLocalUser()?.email?.endsWith("@ente.io"); /** * Persist the user's map enabled preference both locally and on remote. From a3364408b7b32f0704589f44ab5f5bcf1c8417e8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 12:16:09 +0530 Subject: [PATCH 013/109] Conv --- web/packages/accounts/pages/signup.tsx | 15 ++++++-------- web/packages/accounts/pages/verify.tsx | 28 +++++++++++++------------- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index 558277b2e2..ed72e76795 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -1,36 +1,33 @@ import { AccountsPageContents } from "ente-accounts/components/layouts/centered-paper"; import { SignUpContents } from "ente-accounts/components/SignUpContents"; -import { getData } from "ente-accounts/services/accounts-db"; +import { savedPartialLocalUser } from "ente-accounts/services/accounts-db"; import { LoadingIndicator } from "ente-base/components/loaders"; import { customAPIHost } from "ente-base/origins"; import { useRouter } from "next/router"; -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; /** * A page that allows the user to signup for a new Ente account. */ const Page: React.FC = () => { const [loading, setLoading] = useState(true); - const [host, setHost] = useState(); + const [host, setHost] = useState(undefined); const router = useRouter(); useEffect(() => { void customAPIHost().then(setHost); - const user = getData("user"); - if (user?.email) { - void router.push("/verify"); - } + if (savedPartialLocalUser()?.email) void router.push("/verify"); setLoading(false); }, [router]); - const onLogin = () => void router.push("/login"); + const onLogin = useCallback(() => void router.push("/login"), [router]); return loading ? ( ) : ( - + ); }; diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 4b873a1e0a..b111bf5bfa 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -11,6 +11,7 @@ import { getData, savedKeyAttributes, savedOriginalKeyAttributes, + savedPartialLocalUser, savedSRPAttributes, saveIsFirstLogin, saveKeyAttributes, @@ -28,7 +29,6 @@ import { unstashRedirect, } from "ente-accounts/services/redirect"; import { getSRPAttributes, setupSRP } from "ente-accounts/services/srp"; -import type { PartialLocalUser } from "ente-accounts/services/user"; import { putUserKeyAttributes, sendOTT, @@ -60,6 +60,7 @@ const Page: React.FC = () => { const [passkeyVerificationData, setPasskeyVerificationData] = useState< { passkeySessionID: string; url: string } | undefined >(); + const { secondFactorChoiceProps, userVerificationResultAfterResolvingSecondFactorChoice, @@ -68,17 +69,13 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const main = async () => { - const user: PartialLocalUser = getData("user"); - - const redirect = await redirectionIfNeeded(user); - if (redirect) { - void router.push(redirect); + void redirectionIfNeededOrEmail().then((redirectOrEmail) => { + if (typeof redirectOrEmail == "string") { + void router.push(redirectOrEmail); } else { - setEmail(user.email); + setEmail(redirectOrEmail.email); } - }; - void main(); + }); }, [router]); const onSubmit: SingleInputFormProps["onSubmit"] = async ( @@ -251,9 +248,12 @@ export default Page; /** * A function called during page load to see if a redirection is required * - * @returns The slug to redirect to, if needed. + * @returns The slug to redirect to, if needed. Otherwise an object containing + * the saved partial user's email. */ -const redirectionIfNeeded = async (user: PartialLocalUser | undefined) => { +const redirectionIfNeededOrEmail = async () => { + const user = savedPartialLocalUser(); + const email = user?.email; if (!email) { return "/"; @@ -266,7 +266,7 @@ const redirectionIfNeeded = async (user: PartialLocalUser | undefined) => { } // If we're coming here during the recover flow, do not redirect. - if (stashedRedirect() == "/recover") return undefined; + if (stashedRedirect() == "/recover") return { email }; // The user might have email verification disabled, but after previously // entering their email on the login screen, they might've closed the tab @@ -290,5 +290,5 @@ const redirectionIfNeeded = async (user: PartialLocalUser | undefined) => { } } - return undefined; + return { email }; }; From b7e6015720600b5519228beb0606abc580264a24 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 12:40:01 +0530 Subject: [PATCH 014/109] Outline --- .../accounts/pages/change-password.tsx | 2 + web/packages/accounts/pages/credentials.tsx | 9 +- web/packages/accounts/pages/generate.tsx | 22 +++-- web/packages/accounts/pages/login.tsx | 91 +++++++++++++++++-- web/packages/accounts/pages/recover.tsx | 15 +-- web/packages/accounts/pages/signup.tsx | 2 + web/packages/accounts/pages/verify.tsx | 8 +- 7 files changed, 119 insertions(+), 30 deletions(-) diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index aea4641c30..c05ed5f527 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -21,6 +21,8 @@ import { savedLocalUser } from "../services/accounts-db"; /** * A page that allows a user to reset or change their password. + * + * See: [Note: Login pages] */ const Page: React.FC = () => { const [user, setUser] = useState(undefined); diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 1d91bb89e5..f28bfceaaf 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -19,6 +19,7 @@ import { getToken, savedIsFirstLogin, savedKeyAttributes, + savedPartialLocalUser, savedSRPAttributes, saveIsFirstLogin, saveKeyAttributes, @@ -79,15 +80,16 @@ import { useCallback, useEffect, useState } from "react"; const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); - const [srpAttributes, setSrpAttributes] = useState(); + const [user, setUser] = useState(undefined); const [keyAttributes, setKeyAttributes] = useState(); - const [user, setUser] = useState(); + const [srpAttributes, setSrpAttributes] = useState(); const [passkeyVerificationData, setPasskeyVerificationData] = useState< { passkeySessionID: string; url: string } | undefined >(); const [sessionValidityCheck, setSessionValidityCheck] = useState< Promise | undefined >(); + const { secondFactorChoiceProps, userVerificationResultAfterResolvingSecondFactorChoice, @@ -127,11 +129,12 @@ const Page: React.FC = () => { useEffect(() => { const main = async () => { - const user: PartialLocalUser = getData("user"); + const user = savedPartialLocalUser(); if (!user?.email) { void router.push("/"); return; } + setUser(user); await updateSessionFromElectronSafeStorageIfNeeded(); if (await haveAuthenticatedSession()) { diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index 4e5d38ed1b..92d3bb1900 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -6,9 +6,9 @@ import { } from "ente-accounts/components/layouts/centered-paper"; import { RecoveryKey } from "ente-accounts/components/RecoveryKey"; import { - getData, savedJustSignedUp, savedOriginalKeyAttributes, + savedPartialLocalUser, saveJustSignedUp, } from "ente-accounts/services/accounts-db"; import { appHomeRoute } from "ente-accounts/services/redirect"; @@ -39,31 +39,35 @@ import { type NewPasswordFormProps, } from "../components/NewPasswordForm"; +/** + * A page that allows the user to generate key attributes if needed, and shows + * them their recovery key. + * + * See: [Note: Login pages] + */ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); - const [user, setUser] = useState(); + const [user, setUser] = useState(undefined); const [openRecoveryKey, setOpenRecoveryKey] = useState(false); - const [loading, setLoading] = useState(true); const router = useRouter(); useEffect(() => { - const user: PartialLocalUser = getData("user"); - setUser(user); + const user = savedPartialLocalUser(); if (!user?.token) { void router.push("/"); } else if (haveCredentialsInSession()) { if (savedJustSignedUp()) { setOpenRecoveryKey(true); - setLoading(false); + setUser(user); } else { void router.push(appHomeRoute); } } else if (savedOriginalKeyAttributes()?.encryptedKey) { void router.push("/credentials"); } else { - setLoading(false); + setUser(user); } }, [router]); @@ -97,7 +101,7 @@ const Page: React.FC = () => { return ( <> - {loading || !user ? ( + {!user ? ( ) : openRecoveryKey ? ( { {t("set_password")} diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index b57495a36d..bf754056ff 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -1,36 +1,109 @@ import { AccountsPageContents } from "ente-accounts/components/layouts/centered-paper"; import { LoginContents } from "ente-accounts/components/LoginContents"; -import { getData } from "ente-accounts/services/accounts-db"; +import { savedPartialLocalUser } from "ente-accounts/services/accounts-db"; import { LoadingIndicator } from "ente-base/components/loaders"; import { customAPIHost } from "ente-base/origins"; import { useRouter } from "next/router"; -import React, { useEffect, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; /** * A page that allows the user to login into their existing Ente account. + * + * [Note: Login pages] + * + * There are multiple pages that comprise the login flow, with redirects amongst + * themselves depending on various scenarios and partial login states. + * + * - "/signup" - A page that allows the user to signup for a new Ente account. + * + * - Redirects to "/login" if the user chooses the "already have an account" + * option. + * + * - Redirects to "/verify" if there is already an `email` present in the + * saved partial local user, or after obtaining the email. + * + * - "/login" - A page that allows the user to login into their existing Ente + * account. + * + * - Redirects to "/signup" if the user chooses the "create new account" + * option. + * + * - Redirects to "/verify" if there is already an `email` present in the + * saved partial local user, or after obtaining the email if the user's has + * enabled email verification for their account. + * + * - Redirects to "/credentials" after obtaining the email if the user has not + * enabled email verification for their account. + * + * - "/verify" - A page that allows the user to verify their email. + * + * - Redirects to "/" if there is no `email` present in the saved partial + * local user. + * + * - Redirects to "/credentials" if email verification is not needed, and also + * when email verification completes. + * + * - Redirects to "/two-factor/verify" once email verification is complete if + * the user has setup an additional TOTP second factor that also needs to be + * verified. + * + * - Redirects to the passkey app once email verification is complete if the + * user has setup an additional passkey that also needs to be verified. + * + * - "/credentials" - A page that allows the user to enter their password to + * authenticate (initial login) or reauthenticate (new web app tab) + * + * - Redirects to "/" if there is no `email` present in the saved partial + * local user. + * + * - "/generate" - A page that allows the user to generate key attributes if + * needed, and shows them their recovery key. + * + * - Redirects to "/" if there is no `email` present in the saved partial + * local user, or after viewing the recovery key, or after the user sets + * their password (if they did no have key attributes). + * + * - Redirects to "/credentials" if they already have the original key + * attributes. + * + * - "/recover" - A page that allows the user to recover their master key using + * their recovery key. + * + * - Redirects to "/" if there is no `email` present in the saved partial + * local user. + * + * - Redirects to "/verify" if there is no `encryptedToken` (or `token`) + * present in the saved partial local user. + * + * - Redirects to "/generate" if there are no saved key attributes. + * + * - Redirects to "/change-password" once the recovery key is verified. + * + * - "/change-password" - A page that allows the user to reset their password. + * + * - Redirects to "/" if there is no `email` present in the saved partial + * user, and after successfully changing the password. + * */ const Page: React.FC = () => { const [loading, setLoading] = useState(true); - const [host, setHost] = useState(); + const [host, setHost] = useState(undefined); const router = useRouter(); useEffect(() => { void customAPIHost().then(setHost); - const user = getData("user"); - if (user?.email) { - void router.push("/verify"); - } + if (savedPartialLocalUser()?.email) void router.push("/verify"); setLoading(false); }, [router]); - const onSignUp = () => void router.push("/signup"); + const onSignUp = useCallback(() => void router.push("/signup"), [router]); return loading ? ( ) : ( - + ); }; diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 65e9b71e10..ac91d3c9fc 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -4,15 +4,12 @@ import { AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; import { - getData, savedKeyAttributes, + savedPartialLocalUser, } from "ente-accounts/services/accounts-db"; import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; -import type { - KeyAttributes, - PartialLocalUser, -} from "ente-accounts/services/user"; +import type { KeyAttributes } from "ente-accounts/services/user"; import { sendOTT } from "ente-accounts/services/user"; import { decryptAndStoreToken } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; @@ -31,6 +28,12 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; +/** + * A page that allows the user to enter their recovery key to recover their + * master key if they've forgotten their password. + * + * See: [Note: Login pages] + */ const Page: React.FC = () => { const { showMiniDialog } = useBaseContext(); @@ -41,7 +44,7 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user: PartialLocalUser = getData("user"); + const user = savedPartialLocalUser(); if (!user?.email) { void router.push("/"); return; diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index ed72e76795..cff9575c76 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -8,6 +8,8 @@ import React, { useCallback, useEffect, useState } from "react"; /** * A page that allows the user to signup for a new Ente account. + * + * See: [Note: Login pages] */ const Page: React.FC = () => { const [loading, setLoading] = useState(true); diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index b111bf5bfa..7111b57435 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -51,6 +51,8 @@ import { Trans } from "react-i18next"; /** * A page that allows the user to verify their email. + * + * See: [Note: Login pages] */ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); @@ -228,13 +230,13 @@ const Page: React.FC = () => { /> - {resend === 0 && ( + {resend == 0 && ( {t("resend_code")} )} - {resend === 1 && {t("status_sending")}} - {resend === 2 && {t("status_sent")}} + {resend == 1 && {t("status_sending")}} + {resend == 2 && {t("status_sent")}} {t("change_email")} From 0343bdd39316900880c3491034d301172580cd06 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 13:52:52 +0530 Subject: [PATCH 015/109] Unused Notice the typo --- web/packages/accounts/pages/credentials.tsx | 1 - web/packages/accounts/pages/two-factor/recover.tsx | 2 +- web/packages/accounts/pages/verify.tsx | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index f28bfceaaf..ca1ce4874c 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -217,7 +217,6 @@ const Page: React.FC = () => { ...user, passkeySessionID, isTwoFactorEnabled: true, - isTwoFactorPasskeysEnabled: true, }); stashRedirect("/"); const url = passkeyVerificationRedirectURL( diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index e8fa006da2..d6fcb0e2fb 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -69,7 +69,7 @@ const Page: React.FC = ({ twoFactorType }) => { if (!user?.email || !sessionID) { void router.push("/"); } else if ( - !(user.isTwoFactorEnabled || user.isTwoFactorEnabledPasskey) && + !user.isTwoFactorEnabled && (user.encryptedToken || user.token) ) { void router.push("/generate"); diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 7111b57435..a711248d4e 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -106,7 +106,6 @@ const Page: React.FC = () => { ...user, passkeySessionID, isTwoFactorEnabled: true, - isTwoFactorPasskeysEnabled: true, }); saveIsFirstLogin(); const url = passkeyVerificationRedirectURL( From e68b695284b3bbf9998d3b00e0660d77d845b570 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 14:00:10 +0530 Subject: [PATCH 016/109] Used but missing --- web/packages/accounts/services/accounts-db.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 2aff817739..e3411d9767 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -83,6 +83,7 @@ export interface PartialLocalUser { encryptedToken?: string; isTwoFactorEnabled?: boolean; twoFactorSessionID?: string; + passkeySessionID?: string; } const PartialLocalUser = z.object({ @@ -92,6 +93,7 @@ const PartialLocalUser = z.object({ encryptedToken: z.string().nullish().transform(nullToUndefined), isTwoFactorEnabled: z.boolean().nullish().transform(nullToUndefined), twoFactorSessionID: z.string().nullish().transform(nullToUndefined), + passkeySessionID: z.string().nullish().transform(nullToUndefined), }); /** From 9d24914c1c90274b0c725b62877299eb239efada Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 3 Jul 2025 14:39:56 +0530 Subject: [PATCH 017/109] Fix case --- mobile/lib/ui/tools/debug/app_storage_viewer.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/lib/ui/tools/debug/app_storage_viewer.dart b/mobile/lib/ui/tools/debug/app_storage_viewer.dart index 87ba10f1b0..38b4bd5e49 100644 --- a/mobile/lib/ui/tools/debug/app_storage_viewer.dart +++ b/mobile/lib/ui/tools/debug/app_storage_viewer.dart @@ -95,10 +95,10 @@ class _AppStorageViewerState extends State { paths.addAll([ PathStorageItem.name(appDocumentsDirectory.path, "Documents"), PathStorageItem.name(appSupportDirectory.path, "Support"), - PathStorageItem.name(appTemporaryDirectory.path, "App Temp"), + PathStorageItem.name(appTemporaryDirectory.path, "App temp"), PathStorageItem.name( personFaceThumbnails, - "Person Face Thumbnails", + "Face thumbnails", allowCacheClear: true, ), ]); From eb92929647e3b9b7850fc9b1f9a6679dcfc8c095 Mon Sep 17 00:00:00 2001 From: vishnukvmd Date: Thu, 3 Jul 2025 14:40:56 +0530 Subject: [PATCH 018/109] Update --- mobile/lib/ui/tools/debug/app_storage_viewer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/ui/tools/debug/app_storage_viewer.dart b/mobile/lib/ui/tools/debug/app_storage_viewer.dart index 38b4bd5e49..9343f36a3a 100644 --- a/mobile/lib/ui/tools/debug/app_storage_viewer.dart +++ b/mobile/lib/ui/tools/debug/app_storage_viewer.dart @@ -95,7 +95,7 @@ class _AppStorageViewerState extends State { paths.addAll([ PathStorageItem.name(appDocumentsDirectory.path, "Documents"), PathStorageItem.name(appSupportDirectory.path, "Support"), - PathStorageItem.name(appTemporaryDirectory.path, "App temp"), + PathStorageItem.name(appTemporaryDirectory.path, "Temp"), PathStorageItem.name( personFaceThumbnails, "Face thumbnails", From 337c25b670e8296a6374763b8beac7643ace9c59 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 15:05:10 +0530 Subject: [PATCH 019/109] Update --- web/apps/photos/src/pages/_app.tsx | 6 +- web/apps/photos/src/pages/gallery.tsx | 9 +-- web/apps/photos/src/pages/index.tsx | 87 ++++++++++++--------------- 3 files changed, 45 insertions(+), 57 deletions(-) diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index baa3f357e9..960f01de0b 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -4,9 +4,9 @@ import { CssBaseline, Typography } from "@mui/material"; import { styled, ThemeProvider } from "@mui/material/styles"; import { useNotification } from "components/utils/hooks-app"; import { - getData, isLocalStorageAndIndexedDBMismatch, savedLocalUser, + savedPartialLocalUser, } from "ente-accounts/services/accounts-db"; import { isDesktop, staticAppTitle } from "ente-base/app"; import { CenteredRow } from "ente-base/components/containers"; @@ -127,11 +127,11 @@ const App: React.FC = ({ Component, pageProps }) => { useEffect(() => { const query = new URLSearchParams(window.location.search); const needsFamilyRedirect = query.get("redirect") == "families"; - if (needsFamilyRedirect && getData("user")?.token) + if (needsFamilyRedirect && savedPartialLocalUser()?.token) redirectToFamilyPortal(); router.events.on("routeChangeStart", () => { - if (needsFamilyRedirect && getData("user")?.token) { + if (needsFamilyRedirect && savedPartialLocalUser()?.token) { redirectToFamilyPortal(); // https://github.com/vercel/next.js/issues/2476#issuecomment-573460710 diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 46579debcf..4d8b4a2f56 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -17,10 +17,10 @@ import { sessionExpiredDialogAttributes } from "ente-accounts/components/utils/d import { getAndClearIsFirstLogin, getAndClearJustSignedUp, - getData, } from "ente-accounts/services/accounts-db"; import { stashRedirect } from "ente-accounts/services/redirect"; import { isSessionInvalid } from "ente-accounts/services/session"; +import { ensureLocalUser } from "ente-accounts/services/user"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { NavbarBase } from "ente-base/components/Navbar"; import { SingleInputDialog } from "ente-base/components/SingleInputDialog"; @@ -318,13 +318,10 @@ const Page: React.FC = () => { } // Initialize the reducer. - const user = getData("user"); - // TODO: Pass entire snapshot to reducer? - const familyData = userDetailsSnapshot()?.familyData; dispatch({ type: "mount", - user, - familyData, + user: ensureLocalUser(), + familyData: userDetailsSnapshot()?.familyData, collections: await savedCollections(), collectionFiles: await savedCollectionFiles(), trashItems: await savedTrashItems(), diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 291314daa5..3ac7b0d771 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -1,7 +1,7 @@ import { Box, Stack, Typography, styled } from "@mui/material"; import { LoginContents } from "ente-accounts/components/LoginContents"; import { SignUpContents } from "ente-accounts/components/SignUpContents"; -import { getData } from "ente-accounts/services/accounts-db"; +import { savedPartialLocalUser } from "ente-accounts/services/accounts-db"; import { CenteredFill, CenteredRow } from "ente-base/components/containers"; import { EnteLogo } from "ente-base/components/EnteLogo"; import { ActivityIndicator } from "ente-base/components/mui/ActivityIndicator"; @@ -34,53 +34,44 @@ const Page: React.FC = () => { ); useEffect(() => { - refreshHost(); - const currentURL = new URL(window.location.href); - const albumsURL = new URL(albumsAppOrigin()); - currentURL.pathname = router.pathname; - if ( - currentURL.host === albumsURL.host && - currentURL.pathname != "/shared-albums" - ) { - handleAlbumsRedirect(currentURL); - } else { - handleNormalRedirect(); - } - }, [refreshHost]); - - const handleAlbumsRedirect = async (currentURL: URL) => { - const end = currentURL.hash.lastIndexOf("&"); - const hash = currentURL.hash.slice(1, end !== -1 ? end : undefined); - await router.replace({ - pathname: "/shared-albums", - search: currentURL.search, - hash: hash, - }); - await ensureIndexedDBAccess(); - }; - - const handleNormalRedirect = async () => { - const user = getData("user"); - await updateSessionFromElectronSafeStorageIfNeeded(); - if (await haveAuthenticatedSession()) { - await router.push("/gallery"); - } else if (user?.email) { - await router.push("/verify"); - } - await ensureIndexedDBAccess(); - }; - - const ensureIndexedDBAccess = useCallback(async () => { - if (!(await canAccessIndexedDB())) { - showMiniDialog({ - title: t("error"), - message: t("local_storage_not_accessible"), - nonClosable: true, - cancel: false, - }); - } - setLoading(false); - }, [showMiniDialog]); + void (async () => { + refreshHost(); + const currentURL = new URL(window.location.href); + const albumsURL = new URL(albumsAppOrigin()); + currentURL.pathname = router.pathname; + if ( + currentURL.host == albumsURL.host && + currentURL.pathname != "/shared-albums" + ) { + const end = currentURL.hash.lastIndexOf("&"); + const hash = currentURL.hash.slice( + 1, + end !== -1 ? end : undefined, + ); + await router.replace({ + pathname: "/shared-albums", + search: currentURL.search, + hash: hash, + }); + } else { + await updateSessionFromElectronSafeStorageIfNeeded(); + if (await haveAuthenticatedSession()) { + await router.push("/gallery"); + } else if (savedPartialLocalUser()?.email) { + await router.push("/verify"); + } + } + if (!(await canAccessIndexedDB())) { + showMiniDialog({ + title: t("error"), + message: t("local_storage_not_accessible"), + nonClosable: true, + cancel: false, + }); + } + setLoading(false); + })(); + }, [showMiniDialog, router, refreshHost]); return ( From 5b7d4a880688eecea66db9718e328f57874d0681 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 15:09:33 +0530 Subject: [PATCH 020/109] Update --- .../photos/src/services/upload-manager.ts | 8 +++---- web/apps/photos/src/utils/file/index.ts | 21 ++----------------- 2 files changed, 6 insertions(+), 23 deletions(-) diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index 95087cdde5..9c351f8df7 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -1,3 +1,4 @@ +import { ensureLocalUser } from "ente-accounts/services/user"; import { isDesktop } from "ente-base/app"; import { createComlinkCryptoWorker } from "ente-base/crypto"; import { type CryptoWorker } from "ente-base/crypto/worker"; @@ -41,7 +42,6 @@ import { computeNormalCollectionFilesFromSaved } from "ente-new/photos/services/ import { indexNewUpload } from "ente-new/photos/services/ml"; import { wait } from "ente-utils/promise"; import watcher from "services/watch"; -import { getUserOwnedFiles } from "utils/file"; export type FileID = number; @@ -443,9 +443,9 @@ class UploadManager { this.publicAlbumsCredentials.accessToken, ); } else { - this.existingFiles = getUserOwnedFiles( - await computeNormalCollectionFilesFromSaved(), - ); + const files = await computeNormalCollectionFilesFromSaved(); + const userID = ensureLocalUser().id; + this.existingFiles = files.filter((file) => file.ownerID == userID); } this.collections = new Map( collections.map((collection) => [collection.id, collection]), diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 03ccbc90a0..5558efaff0 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,5 +1,4 @@ -import { getData } from "ente-accounts/services/accounts-db"; -import type { LocalUser, PartialLocalUser } from "ente-accounts/services/user"; +import type { LocalUser } from "ente-accounts/services/user"; import { joinPath } from "ente-base/file-name"; import log from "ente-base/log"; import { type Electron } from "ente-base/types/ipc"; @@ -8,11 +7,7 @@ import { downloadManager } from "ente-gallery/services/download"; import { detectFileTypeInfo } from "ente-gallery/utils/detect-type"; import { writeStream } from "ente-gallery/utils/native-stream"; import { EnteFile } from "ente-media/file"; -import { - ItemVisibility, - fileFileName, - isArchivedFile, -} from "ente-media/file-metadata"; +import { ItemVisibility, fileFileName } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { decodeLivePhoto } from "ente-media/live-photo"; import { type FileOp } from "ente-new/photos/components/SelectedFileOptions"; @@ -279,18 +274,6 @@ async function downloadFileDesktop( } } -export const getArchivedFiles = (files: EnteFile[]) => { - return files.filter(isArchivedFile).map((file) => file.id); -}; - -export const getUserOwnedFiles = (files: EnteFile[]) => { - const user: PartialLocalUser = getData("user"); - if (!user?.id) { - throw Error("user missing"); - } - return files.filter((file) => file.ownerID === user.id); -}; - export const shouldShowAvatar = ( file: EnteFile, user: LocalUser | undefined, From 6249211bca6429ddddb66e8acdd2bd563e3e79ab Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 15:21:17 +0530 Subject: [PATCH 021/109] Rename --- web/apps/photos/src/pages/gallery.tsx | 9 +++++---- web/packages/new/photos/services/user-details.ts | 14 +++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 4d8b4a2f56..1fea4027fb 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -108,8 +108,8 @@ import { import type { SearchOption } from "ente-new/photos/services/search/types"; import { initSettings } from "ente-new/photos/services/settings"; import { - initUserDetailsOrTriggerPull, redirectToCustomerPortal, + savedUserDetailsOrTriggerPull, userDetailsSnapshot, verifyStripeSubscription, } from "ente-new/photos/services/user-details"; @@ -301,7 +301,6 @@ const Page: React.FC = () => { // One time inits. preloadImage("/images/subscription-card-background"); initSettings(); - await initUserDetailsOrTriggerPull(); setupSelectAllKeyBoardShortcutHandler(); // Show the initial state while the rest of the sequence proceeds. @@ -318,10 +317,12 @@ const Page: React.FC = () => { } // Initialize the reducer. + const user = ensureLocalUser(); + const userDetails = await savedUserDetailsOrTriggerPull(); dispatch({ type: "mount", - user: ensureLocalUser(), - familyData: userDetailsSnapshot()?.familyData, + user, + familyData: userDetails?.familyData, collections: await savedCollections(), collectionFiles: await savedCollectionFiles(), trashItems: await savedTrashItems(), diff --git a/web/packages/new/photos/services/user-details.ts b/web/packages/new/photos/services/user-details.ts index 4d48d7449e..2a99fdafcb 100644 --- a/web/packages/new/photos/services/user-details.ts +++ b/web/packages/new/photos/services/user-details.ts @@ -177,18 +177,22 @@ export const logoutUserDetails = () => { }; /** - * Read in the locally persisted settings into memory, otherwise initiate a - * network requests to fetch the latest values (but don't wait for it to - * complete). + * Read in the locally persisted user details into memory and return them. + * + * If there are no locally persisted values, initiate a network requests to + * fetch the latest values (but don't wait for it to complete). * * This assumes that the user is already logged in. */ -export const initUserDetailsOrTriggerPull = async () => { +export const savedUserDetailsOrTriggerPull = async () => { const saved = await getKV("userDetails"); if (saved) { - setUserDetailsSnapshot(UserDetails.parse(saved)); + const userDetails = UserDetails.parse(saved); + setUserDetailsSnapshot(userDetails); + return userDetails; } else { void pullUserDetails(); + return undefined; } }; From ef752a244c1d1fb5045336e2d4fdba4f366eb2d8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 15:41:47 +0530 Subject: [PATCH 022/109] Handle family email --- web/apps/photos/src/pages/gallery.tsx | 14 +++++++-- .../new/photos/components/gallery/reducer.ts | 29 ++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 1fea4027fb..fce8113997 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -77,7 +77,10 @@ import { } from "ente-new/photos/components/gallery/reducer"; import { notifyOthersFilesDialogAttributes } from "ente-new/photos/components/utils/dialog-attributes"; import { useIsOffline } from "ente-new/photos/components/utils/use-is-offline"; -import { usePeopleStateSnapshot } from "ente-new/photos/components/utils/use-snapshot"; +import { + usePeopleStateSnapshot, + useUserDetailsSnapshot, +} from "ente-new/photos/components/utils/use-snapshot"; import { shouldShowWhatsNew } from "ente-new/photos/services/changelog"; import { addToFavoritesCollection, @@ -110,7 +113,6 @@ import { initSettings } from "ente-new/photos/services/settings"; import { redirectToCustomerPortal, savedUserDetailsOrTriggerPull, - userDetailsSnapshot, verifyStripeSubscription, } from "ente-new/photos/services/user-details"; import { usePhotosAppContext } from "ente-new/photos/types/context"; @@ -182,6 +184,7 @@ const Page: React.FC = () => { EnteFile[] >([]); + const userDetails = useUserDetailsSnapshot(); const peopleState = usePeopleStateSnapshot(); // The (non-sticky) header shown at the top of the gallery items. @@ -352,6 +355,13 @@ const Page: React.FC = () => { }; }, []); + useEffect(() => { + // Only act on updates after the initial mount has completed. + if (state.user && userDetails) { + dispatch({ type: "setUserDetails", userDetails }); + } + }, [state.user, userDetails]); + useEffect(() => { if (typeof activeCollectionID == "undefined" || !router.isReady) { return; diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 56a2590ff0..d7aedfc0cb 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -38,7 +38,7 @@ import { } from "../../services/collection-summary"; import type { PeopleState, Person } from "../../services/ml/people"; import type { SearchSuggestion } from "../../services/search/types"; -import type { FamilyData } from "../../services/user-details"; +import type { FamilyData, UserDetails } from "../../services/user-details"; /** * Specifies what the bar at the top of the gallery is displaying currently. @@ -455,6 +455,7 @@ export type GalleryAction = collectionFiles: EnteFile[]; trashItems: TrashItem[]; } + | { type: "setUserDetails"; userDetails: UserDetails } | { type: "setCollections"; collections: Collection[] } | { type: "setCollectionFiles"; collectionFiles: EnteFile[] } | { type: "uploadFile"; file: EnteFile } @@ -623,6 +624,32 @@ const galleryReducer: React.Reducer = ( }); } + case "setUserDetails": { + // While user details have more state that can change, the only + // changes that affect the reducer's state (so far) are if the + // user's own email changes, or the list of their family members + // changes. + // + // Both of these affect only the list of share suggestion emails. + + let user = state.user!; + const { email, familyData } = action.userDetails; + if (email != user.email) { + user = { ...user, email }; + } + + return { + ...state, + user, + familyData, + shareSuggestionEmails: createShareSuggestionEmails( + user, + familyData, + state.collections, + ), + }; + } + case "setCollections": { const collections = action.collections; From ea5ebd09659145574b36d5b67b39fdec442572ec Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 16:10:33 +0530 Subject: [PATCH 023/109] Perf I didn't see it being a problem, so this is perhaps premature optimization --- .../new/photos/services/user-details.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/web/packages/new/photos/services/user-details.ts b/web/packages/new/photos/services/user-details.ts index 2a99fdafcb..b7805e1633 100644 --- a/web/packages/new/photos/services/user-details.ts +++ b/web/packages/new/photos/services/user-details.ts @@ -238,7 +238,6 @@ const setUserDetailsSnapshot = (snapshot: UserDetails) => { export const pullUserDetails = async () => { const userDetails = await getUserDetails(); await setKV("userDetails", userDetails); - setUserDetailsSnapshot(userDetails); // Update the email for the local storage user if needed (the user might've // changed their email on a different client). @@ -247,6 +246,32 @@ export const pullUserDetails = async () => { // eslint-disable-next-line @typescript-eslint/no-unsafe-argument await setLSUser({ ...getData("user"), email: userDetails.email }); } + + // The gallery listens for updates to userDetails, so a special case, do a + // deep equality check so as to not rerender it on redundant updates. + // + // [Note: Deep equal check] + // + // React uses `Object.is` to detect changes, which changes for arrays, + // objects and combinations thereof even if the underlying data is the same. + // + // In many cases, the code can be restructured to avoid this being a + // problem, or the rerender might be infrequent enough that it is not a + // problem. + // + // However, when used with useSyncExternalStore, there is an easy way to + // prevent this, by doing a preflight deep equality comparison. + // + // There are arguably faster libraries out there that'll do the deep + // equality check for us, but since it is an infrequent pattern in our code + // base currently, we just use the JSON serialization. + // + // Mark all cases that do this using this note's title so we can audit them + // if we move to a deep equality comparison library in the future. + + if (JSON.stringify(userDetails) != JSON.stringify(userDetailsSnapshot())) { + setUserDetailsSnapshot(userDetails); + } }; /** From 4e8a4250dc5e79e5561bac107303e60f67f8c9a7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 16:31:20 +0530 Subject: [PATCH 024/109] Update --- web/packages/accounts/pages/credentials.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index ca1ce4874c..8b0b1757d0 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -82,7 +82,7 @@ const Page: React.FC = () => { const [user, setUser] = useState(undefined); const [keyAttributes, setKeyAttributes] = useState(); - const [srpAttributes, setSrpAttributes] = useState(); + const [srpAttributes, setSRPAttributes] = useState(); const [passkeyVerificationData, setPasskeyVerificationData] = useState< { passkeySessionID: string; url: string } | undefined >(); @@ -175,7 +175,7 @@ const Page: React.FC = () => { } if (srpAttributes) { - setSrpAttributes(srpAttributes); + setSRPAttributes(srpAttributes); } else { void router.push("/"); } From 9e4a67312f46603ba2f4517f2c3283a838fcfe4e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 16:48:32 +0530 Subject: [PATCH 025/109] Update --- web/packages/accounts/pages/credentials.tsx | 18 ++--- web/packages/accounts/pages/login.tsx | 34 +++++++++- .../accounts/pages/two-factor/recover.tsx | 14 ++-- .../accounts/pages/two-factor/verify.tsx | 66 ++++++++++--------- web/packages/accounts/pages/verify.tsx | 18 ++--- web/packages/accounts/services/accounts-db.ts | 29 +++++++- web/packages/accounts/services/user.ts | 11 +++- .../components/sidebar/TwoFactorSettings.tsx | 23 +++---- .../new/photos/services/user-details.ts | 8 +-- 9 files changed, 137 insertions(+), 84 deletions(-) diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 8b0b1757d0..046770bc43 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -25,6 +25,7 @@ import { saveKeyAttributes, saveSRPAttributes, setLSUser, + updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; import { openPasskeyVerificationURL, @@ -212,12 +213,7 @@ const Page: React.FC = () => { if (passkeySessionID) { await stashKeyEncryptionKeyInSessionStore(kek); - const user = getData("user"); - await setLSUser({ - ...user, - passkeySessionID, - isTwoFactorEnabled: true, - }); + updateSavedLocalUser({ passkeySessionID }); stashRedirect("/"); const url = passkeyVerificationRedirectURL( accountsUrl!, @@ -228,11 +224,9 @@ const Page: React.FC = () => { throw new Error(twoFactorEnabledErrorMessage); } else if (twoFactorSessionID) { await stashKeyEncryptionKeyInSessionStore(kek); - const user = getData("user"); - await setLSUser({ - ...user, - twoFactorSessionID, + updateSavedLocalUser({ isTwoFactorEnabled: true, + twoFactorSessionID, }); void router.push("/two-factor/verify"); throw new Error(twoFactorEnabledErrorMessage); @@ -243,7 +237,9 @@ const Page: React.FC = () => { token, encryptedToken, id, - isTwoFactorEnabled: false, + isTwoFactorEnabled: undefined, + twoFactorSessionID: undefined, + passkeySessionID: undefined, }); if (keyAttributes) saveKeyAttributes(keyAttributes); return keyAttributes; diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index bf754056ff..b98eebaefb 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -49,6 +49,8 @@ import React, { useCallback, useEffect, useState } from "react"; * * - Redirects to the passkey app once email verification is complete if the * user has setup an additional passkey that also needs to be verified. + * Before redirecting, it sets the `inflightPasskeySessionID` in session + * storage. * * - "/credentials" - A page that allows the user to enter their password to * authenticate (initial login) or reauthenticate (new web app tab) @@ -79,11 +81,41 @@ import React, { useCallback, useEffect, useState } from "react"; * * - Redirects to "/change-password" once the recovery key is verified. * - * - "/change-password" - A page that allows the user to reset their password. + * - "/change-password" - A page that allows the user to reset their password. * * - Redirects to "/" if there is no `email` present in the saved partial * user, and after successfully changing the password. * + * - "/two-factor/verify" - A page that allows the user to verify their TOTP + * based second factor. + * + * - Redirects to "/" if there is no `email` or `twoFactorSessionID` in the + * saved partial local user. + * + * - Redirects to "/credentials" if there `isTwoFactorEnabled` is not `true` + * and either of `encryptedToken` or `token` is present in the saved partial + * local user. + * + * - "/passkeys/finish" - A page that the accounts app hands off control back to + * us (the calling app) to continue the rest of the authentication. + * + * - Redirects to "/" if there is no matching `inflightPasskeySessionID` in + * session storage. + * + * - Redirects to "/credentials" otherwise. + * + * - "/two-factor/recover" and "/passkeys/recover" - Pages that allow the user + * to reset or bypass their second factor if they possess their recovery key. + * Both pages work similarly, except the second factor they act on. + * + * - Redirects to "/" if there is no `email` in the saved partial local user, + * or either of `twoFactorSessionID` and `twoFactorSessionID` is set. + * + * - Redirects to "/generate" if there is an `encryptedToken` or `token` in + * the saved partial local user (TODO: Why?). + * + * - Redirects to "/credentials" after recovery. + * */ const Page: React.FC = () => { const [loading, setLoading] = useState(true); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index d6fcb0e2fb..caa3480661 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -4,7 +4,7 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; -import { getData } from "ente-accounts/services/accounts-db"; +import { savedPartialLocalUser } from "ente-accounts/services/accounts-db"; import { recoverTwoFactor, recoverTwoFactorFinish, @@ -64,14 +64,14 @@ const Page: React.FC = ({ twoFactorType }) => { ); useEffect(() => { - const user = getData("user"); - const sessionID = user.passkeySessionID || user.twoFactorSessionID; + const user = savedPartialLocalUser(); + const sessionID = + twoFactorType == "passkey" + ? user?.passkeySessionID + : user?.twoFactorSessionID; if (!user?.email || !sessionID) { void router.push("/"); - } else if ( - !user.isTwoFactorEnabled && - (user.encryptedToken || user.token) - ) { + } else if (user.encryptedToken || user.token) { void router.push("/generate"); } else { setSessionID(sessionID); diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index 8c32a92b3e..918fba2486 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -1,17 +1,17 @@ import { Verify2FACodeForm } from "ente-accounts/components/Verify2FACodeForm"; import { getData, + savedPartialLocalUser, saveKeyAttributes, setLSUser, } from "ente-accounts/services/accounts-db"; -import type { PartialLocalUser } from "ente-accounts/services/user"; import { verifyTwoFactor } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; import { isHTTPErrorWithStatus } from "ente-base/http"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { AccountsPageContents, AccountsPageFooter, @@ -19,15 +19,18 @@ import { } from "../../components/layouts/centered-paper"; import { unstashRedirect } from "../../services/redirect"; +/** + * A page that allows the user to verify their TOTP based second factor. + */ const Page: React.FC = () => { const { logout } = useBaseContext(); - const [sessionID, setSessionID] = useState(""); + const [twoFactorSessionID, setTwoFactorSessionID] = useState(""); const router = useRouter(); useEffect(() => { - const user: PartialLocalUser = getData("user"); + const user = savedPartialLocalUser(); if (!user?.email || !user.twoFactorSessionID) { void router.push("/"); } else if ( @@ -36,37 +39,38 @@ const Page: React.FC = () => { ) { void router.push("/credentials"); } else { - setSessionID(user.twoFactorSessionID); + setTwoFactorSessionID(user.twoFactorSessionID); } }, [router]); - const handleSubmit = async (otp: string) => { - try { - const { keyAttributes, encryptedToken, id } = await verifyTwoFactor( - otp, - sessionID, - ); - await setLSUser({ - ...getData("user"), - id, - // TODO: [Note: empty token?] - // - // The original code was parsing an token which is never going - // to be present in the response, so effectively was always - // setting token to undefined. So this works, but is it needed? - token: undefined, - encryptedToken, - }); - saveKeyAttributes(keyAttributes); - await router.push(unstashRedirect() ?? "/credentials"); - } catch (e) { - if (isHTTPErrorWithStatus(e, 404)) { - logout(); - } else { - throw e; + const handleSubmit = useCallback( + async (otp: string) => { + try { + const { keyAttributes, encryptedToken, id } = + await verifyTwoFactor(otp, twoFactorSessionID); + await setLSUser({ + ...getData("user"), + id, + // TODO: [Note: empty token?] + // + // The original code was parsing an token which is never going + // to be present in the response, so effectively was always + // setting token to undefined. So this works, but is it needed? + token: undefined, + encryptedToken, + }); + saveKeyAttributes(keyAttributes); + await router.push(unstashRedirect() ?? "/credentials"); + } catch (e) { + if (isHTTPErrorWithStatus(e, 404)) { + logout(); + } else { + throw e; + } } - } - }; + }, + [logout, router, twoFactorSessionID], + ); return ( diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index a711248d4e..9eac4d569d 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -8,7 +8,6 @@ import { VerifyingPasskey } from "ente-accounts/components/LoginComponents"; import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice"; import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice"; import { - getData, savedKeyAttributes, savedOriginalKeyAttributes, savedPartialLocalUser, @@ -19,6 +18,7 @@ import { setLSUser, unstashAfterUseSRPSetupAttributes, unstashReferralSource, + updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; import { openPasskeyVerificationURL, @@ -101,12 +101,7 @@ const Page: React.FC = () => { await verifyEmail(email, ott, cleanedReferral), ); if (passkeySessionID) { - const user = getData("user"); - await setLSUser({ - ...user, - passkeySessionID, - isTwoFactorEnabled: true, - }); + updateSavedLocalUser({ passkeySessionID }); saveIsFirstLogin(); const url = passkeyVerificationRedirectURL( accountsUrl!, @@ -115,10 +110,9 @@ const Page: React.FC = () => { setPasskeyVerificationData({ passkeySessionID, url }); openPasskeyVerificationURL({ passkeySessionID, url }); } else if (twoFactorSessionID) { - await setLSUser({ - email, - twoFactorSessionID, + updateSavedLocalUser({ isTwoFactorEnabled: true, + twoFactorSessionID, }); saveIsFirstLogin(); void router.push("/two-factor/verify"); @@ -128,7 +122,9 @@ const Page: React.FC = () => { token, encryptedToken, id, - isTwoFactorEnabled: false, + isTwoFactorEnabled: undefined, + twoFactorSessionID: undefined, + passkeySessionID: undefined, }); if (keyAttributes) { saveKeyAttributes(keyAttributes); diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index e3411d9767..98c4577058 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -58,6 +58,9 @@ import { * also get filled in. Once they verify their TOTP based second factor, their * {@link id} and {@link encryptedToken} will also get filled in. * + * - If they have a passkey set as a second factor set, then after verifying + * their password the {@link passkeySessionID} will be set. + * * - As the login or signup sequence completes, a {@link token} obtained from * the {@link encryptedToken} will be written out, and the * {@link encryptedToken} cleared since it is not needed anymore. @@ -105,6 +108,7 @@ const LocalUser = z.object({ id: z.number(), email: z.string(), token: z.string(), + isTwoFactorEnabled: z.boolean().nullish().transform(nullToUndefined), }); /** @@ -127,13 +131,33 @@ export const savedPartialLocalUser = (): PartialLocalUser | undefined => { * * See: [Note: Partial local user]. * + * This method replaces the existing data. Use {@link updatePartialLocalUser} to + * update selected fields while keeping the other fields as it is. + * * TODO: WARNING: This does not update the KV token. The idea is to gradually * move over uses of setLSUser to this while explicitly setting the KV token * where needed. */ -export const savePartialLocalUser = (partialLocalUser: Partial) => +export const savePartialLocalUser = (partialLocalUser: PartialLocalUser) => localStorage.setItem("user", JSON.stringify(partialLocalUser)); +/** + * Partially update the saved user data. + * + * This is a delta variant of {@link savePartialLocalUser}, which replaces the + * entire saved object, while this function spreads the provided {@link updates} + * onto the currently saved value. + * + * @param updates A subset of {@link PartialLocalUser} fields that we'd like to + * update. The other fields, if any, remain unchanged. + * + * TODO: WARNING: This does not update the KV token. The idea is to gradually + * move over uses of setLSUser to this while explicitly setting the KV token + * where needed. + */ +export const updateSavedLocalUser = (updates: Partial) => + savePartialLocalUser({ ...savedPartialLocalUser(), ...updates }); + /** * Return data about the logged-in user, if someone is indeed logged in. * Otherwise return `undefined`. @@ -143,7 +167,8 @@ export const savePartialLocalUser = (partialLocalUser: Partial) => * not accessible to web workers. * * There is no setter corresponding to this function since this is only a view - * on data saved using {@link savePartialLocalUser}. + * on data saved using {@link savePartialLocalUser} or + * {@link updateSavedLocalUser}. * * See: [Note: Partial local user] for more about the whole shebang. */ diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index abfd6ea5ed..7fb7390c91 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -5,6 +5,7 @@ import { saveKeyAttributes, saveSRPAttributes, setLSUser, + updateSavedLocalUser, type PartialLocalUser, } from "ente-accounts/services/accounts-db"; import { @@ -70,6 +71,10 @@ export interface LocalUser { * in IndexedDB and thus can also be used in web workers. */ token: string; + /** + * `true` if the TOTP based second factor is enabled for the user. + */ + isTwoFactorEnabled?: boolean; } /** @@ -664,7 +669,7 @@ export const generateAndSaveInteractiveKeyAttributes = async ( */ export const changeEmail = async (email: string, ott: string) => { await postChangeEmail(email, ott); - await setLSUser({ ...getData("user"), email }); + updateSavedLocalUser({ email }); }; /** @@ -784,7 +789,7 @@ export const setupTwoFactorFinish = async ( encryptedTwoFactorSecret: box.encryptedData, twoFactorSecretDecryptionNonce: box.nonce, }); - await setLSUser({ ...getData("user"), isTwoFactorEnabled: true }); + updateSavedLocalUser({ isTwoFactorEnabled: true }); }; interface EnableTwoFactorRequest { @@ -956,7 +961,7 @@ export const recoverTwoFactorFinish = async ( await setLSUser({ ...getData("user"), id, - isTwoFactorEnabled: false, + isTwoFactorEnabled: undefined, encryptedToken, token: undefined, }); diff --git a/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx b/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx index badbe850b6..c20f378f7b 100644 --- a/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx +++ b/web/packages/new/photos/components/sidebar/TwoFactorSettings.tsx @@ -1,6 +1,9 @@ import LockIcon from "@mui/icons-material/Lock"; import { Stack, Typography } from "@mui/material"; -import { getData, setLSUser } from "ente-accounts/services/accounts-db"; +import { + savedPartialLocalUser, + updateSavedLocalUser, +} from "ente-accounts/services/accounts-db"; import { RowButton, RowButtonGroup, @@ -24,12 +27,9 @@ export const TwoFactorSettings: React.FC< const [isTwoFactorEnabled, setIsTwoFactorEnabled] = useState(false); useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const isTwoFactorEnabled = - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - getData("user").isTwoFactorEnabled ?? false; - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - setIsTwoFactorEnabled(isTwoFactorEnabled); + if (savedPartialLocalUser()?.isTwoFactorEnabled) { + setIsTwoFactorEnabled(true); + } }, []); useEffect(() => { @@ -37,11 +37,7 @@ export const TwoFactorSettings: React.FC< void (async () => { const isEnabled = await get2FAStatus(); setIsTwoFactorEnabled(isEnabled); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - await setLSUser({ - ...getData("user"), - isTwoFactorEnabled: isEnabled, - }); + updateSavedLocalUser({ isTwoFactorEnabled: isEnabled }); })(); }, [open]); @@ -112,8 +108,7 @@ const ManageDrawerContents: React.FC = ({ onRootClose }) => { const disable = async () => { await disable2FA(); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - await setLSUser({ ...getData("user"), isTwoFactorEnabled: false }); + updateSavedLocalUser({ isTwoFactorEnabled: undefined }); onRootClose(); }; diff --git a/web/packages/new/photos/services/user-details.ts b/web/packages/new/photos/services/user-details.ts index b7805e1633..596112b8a1 100644 --- a/web/packages/new/photos/services/user-details.ts +++ b/web/packages/new/photos/services/user-details.ts @@ -1,4 +1,4 @@ -import { getData, setLSUser } from "ente-accounts/services/accounts-db"; +import { updateSavedLocalUser } from "ente-accounts/services/accounts-db"; import { ensureLocalUser } from "ente-accounts/services/user"; import { isDesktop } from "ente-base/app"; import { authenticatedRequestHeaders, ensureOk } from "ente-base/http"; @@ -241,10 +241,10 @@ export const pullUserDetails = async () => { // Update the email for the local storage user if needed (the user might've // changed their email on a different client). - if (ensureLocalUser().email != userDetails.email) { + const { email } = userDetails; + if (ensureLocalUser().email != email) { log.info("Updating user email to match fetched user details"); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - await setLSUser({ ...getData("user"), email: userDetails.email }); + updateSavedLocalUser({ email }); } // The gallery listens for updates to userDetails, so a special case, do a From 69cf09e13dd33f02bdf3bd2833e72d10c2f0bc51 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 3 Jul 2025 18:52:33 +0530 Subject: [PATCH 026/109] Rework --- web/apps/photos/src/pages/gallery.tsx | 4 +- .../components/VerifyMasterPasswordForm.tsx | 66 +++-- .../components/utils/second-factor-choice.ts | 9 - web/packages/accounts/pages/credentials.tsx | 278 +++++++++--------- web/packages/accounts/pages/login.tsx | 14 +- web/packages/accounts/services/accounts-db.ts | 9 +- web/packages/accounts/services/session.ts | 4 +- web/packages/accounts/services/user.ts | 10 +- web/packages/base/session.ts | 4 +- web/packages/base/token.ts | 38 ++- 10 files changed, 235 insertions(+), 201 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index fce8113997..94e5b7d366 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -38,7 +38,7 @@ import { haveCredentialsInSession, masterKeyFromSession, } from "ente-base/session"; -import { getAuthToken } from "ente-base/token"; +import { savedAuthToken } from "ente-base/token"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; import { type Collection } from "ente-media/collection"; @@ -282,7 +282,7 @@ const Page: React.FC = () => { let syncIntervalID: ReturnType | undefined; void (async () => { - if (!haveCredentialsInSession() || !(await getAuthToken())) { + if (!haveCredentialsInSession() || !(await savedAuthToken())) { // If we don't have master key or auth token, reauthenticate. stashRedirect("/gallery"); router.push("/"); diff --git a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx index c0eaf39b00..1afb5edd76 100644 --- a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx +++ b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx @@ -11,15 +11,33 @@ import log from "ente-base/log"; import { useFormik } from "formik"; import { t } from "i18next"; import { useCallback, useState } from "react"; -import { twoFactorEnabledErrorMessage } from "./utils/second-factor-choice"; export interface VerifyMasterPasswordFormProps { /** * The email of the user whose password we're trying to verify. */ userEmail: string; + /** + * The user's SRP attributes. + * + * The SRP attributes are used to derive the KEK from the user's password. + * If they are not present, the {@link keyAttributes} will be used instead. + * + * At least one of {@link srpAttributes} and {@link keyAttributes} must be + * present, otherwise the verification will fail. + */ + srpAttributes?: SRPAttributes; /** * The user's key attributes. + * + * If they are present, they are used to derive the KEK from the user's + * password when {@link srpAttributes} are not present. This is the case + * when the user has already logged in (or signed up) on this client before, + * and is now doing a reauthentication. + * + * If they are not present, then {@link getKeyAttributes} must be present + * and will be used to obtain the user's key attributes. This is the case + * when the user is logging into a new client. */ keyAttributes: KeyAttributes | undefined; /** @@ -30,20 +48,18 @@ export interface VerifyMasterPasswordFormProps { * used for reauthenticating the user after they've already logged in, then * this function will not be provided. * - * @throws A Error with message {@link twoFactorEnabledErrorMessage} to - * signal to the form that some other form of second factor is enabled and - * the user has been redirected to a two factor verification page. + * @returns The user's key attributes obtained from remote, or + * "redirecting-second-factor" if the user has an additional second factor + * verification required and the app is redirecting there. * * @throws A Error with message * {@link srpVerificationUnauthorizedErrorMessage} to signal that either * that the password is incorrect, or no account with the provided email * exists. */ - getKeyAttributes?: (kek: string) => Promise; - /** - * The user's SRP attributes. - */ - srpAttributes?: SRPAttributes; + getKeyAttributes?: ( + kek: string, + ) => Promise; /** * The title of the submit button on the form. */ @@ -152,24 +168,24 @@ export const VerifyMasterPasswordForm: React.FC< } } else throw new Error("Both SRP and key attributes are missing"); - if (!keyAttributes && typeof getKeyAttributes == "function") { + if (!keyAttributes && getKeyAttributes) { try { - keyAttributes = await getKeyAttributes(kek); + const result = await getKeyAttributes(kek); + if (result == "redirecting-second-factor") { + // Two factor enabled, user has been redirected to the + // corresponding second factor verification page. + return; + } else { + keyAttributes = result; + } } catch (e) { - if (e instanceof Error) { - switch (e.message) { - case twoFactorEnabledErrorMessage: - // Two factor enabled, user has been redirected to - // the two-factor verification page. - return; - - case srpVerificationUnauthorizedErrorMessage: - log.error("Incorrect password or no account", e); - setFieldError( - t("incorrect_password_or_no_account"), - ); - return; - } + if ( + e instanceof Error && + e.message == srpVerificationUnauthorizedErrorMessage + ) { + log.error("Incorrect password or no account", e); + setFieldError(t("incorrect_password_or_no_account")); + return; } throw e; } diff --git a/web/packages/accounts/components/utils/second-factor-choice.ts b/web/packages/accounts/components/utils/second-factor-choice.ts index e2e2ca19a3..1557255ae3 100644 --- a/web/packages/accounts/components/utils/second-factor-choice.ts +++ b/web/packages/accounts/components/utils/second-factor-choice.ts @@ -8,15 +8,6 @@ import { useModalVisibility } from "ente-base/components/utils/modal"; import { useCallback, useMemo, useRef } from "react"; import type { SecondFactorType } from "../SecondFactorChoice"; -/** - * The message of the {@link Error} that is thrown when the user has enabled a - * second factor so further authentication is needed during the login sequence. - * - * TODO: This is not really an error but rather is a code flow flag; consider - * not using exceptions for flow control. - */ -export const twoFactorEnabledErrorMessage = "two factor enabled"; - /** * A convenience hook for keeping track of the state and logic that is needed * after password verification to determine which second factor (if any) we diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 046770bc43..83866b478c 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -6,17 +6,12 @@ import { } from "ente-accounts/components/LoginComponents"; import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice"; import { sessionExpiredDialogAttributes } from "ente-accounts/components/utils/dialog"; -import { - twoFactorEnabledErrorMessage, - useSecondFactorChoiceIfNeeded, -} from "ente-accounts/components/utils/second-factor-choice"; +import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice"; import { VerifyMasterPasswordForm, type VerifyMasterPasswordFormProps, } from "ente-accounts/components/VerifyMasterPasswordForm"; import { - getData, - getToken, savedIsFirstLogin, savedKeyAttributes, savedPartialLocalUser, @@ -24,7 +19,6 @@ import { saveIsFirstLogin, saveKeyAttributes, saveSRPAttributes, - setLSUser, updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; import { @@ -47,13 +41,13 @@ import { import { generateAndSaveInteractiveKeyAttributes, type KeyAttributes, - type PartialLocalUser, } from "ente-accounts/services/user"; import { decryptAndStoreToken } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; import { LoadingIndicator } from "ente-base/components/loaders"; import { useBaseContext } from "ente-base/context"; import { decryptBox } from "ente-base/crypto"; +import { isDevBuild } from "ente-base/env"; import { clearLocalStorage } from "ente-base/local-storage"; import log from "ente-base/log"; import { @@ -63,6 +57,7 @@ import { unstashKeyEncryptionKeyFromSession, updateSessionFromElectronSafeStorageIfNeeded, } from "ente-base/session"; +import { saveAuthToken } from "ente-base/token"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; @@ -77,19 +72,25 @@ import { useCallback, useEffect, useState } from "react"; * - Subsequent reauthentication, when the user opens the web app in a new tab. * Such a tab won't have the user's master key in session storage, so we ask * the user to reauthenticate using their password. + * + * See: [Note: Login pages] */ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); - const [user, setUser] = useState(undefined); - const [keyAttributes, setKeyAttributes] = useState(); - const [srpAttributes, setSRPAttributes] = useState(); + const [userEmail, setUserEmail] = useState(""); + const [keyAttributes, setKeyAttributes] = useState< + KeyAttributes | undefined + >(undefined); + const [srpAttributes, setSRPAttributes] = useState< + SRPAttributes | undefined + >(undefined); const [passkeyVerificationData, setPasskeyVerificationData] = useState< { passkeySessionID: string; url: string } | undefined - >(); + >(undefined); const [sessionValidityCheck, setSessionValidityCheck] = useState< Promise | undefined - >(); + >(undefined); const { secondFactorChoiceProps, @@ -128,28 +129,55 @@ const Page: React.FC = () => { } }, [logout, showMiniDialog]); + const postVerification = useCallback( + async ( + userEmail: string, + masterKey: string, + kek: string, + keyAttributes: KeyAttributes, + ) => { + await saveMasterKeyInSessionAndSafeStore(masterKey); + await decryptAndStoreToken(keyAttributes, masterKey); + try { + let srpAttributes = savedSRPAttributes(); + if (!srpAttributes) { + srpAttributes = await getSRPAttributes(userEmail); + if (srpAttributes) { + saveSRPAttributes(srpAttributes); + } else { + await setupSRP(await generateSRPSetupAttributes(kek)); + } + } + } catch (e) { + log.error("SRP migration failed", e); + } + void router.push(unstashRedirect() ?? appHomeRoute); + }, + [router], + ); + useEffect(() => { - const main = async () => { + void (async () => { const user = savedPartialLocalUser(); - if (!user?.email) { + const userEmail = user?.email; + if (!userEmail) { void router.push("/"); return; } - setUser(user); await updateSessionFromElectronSafeStorageIfNeeded(); if (await haveAuthenticatedSession()) { void router.push(appHomeRoute); return; } + + setUserEmail(userEmail); + if (user.token) setSessionValidityCheck(validateSession()); + const kek = await unstashKeyEncryptionKeyFromSession(); const keyAttributes = savedKeyAttributes(); - const srpAttributes = savedSRPAttributes(); - - if (getToken()) { - setSessionValidityCheck(validateSession()); - } + // Refreshing an existing tab, or desktop app. if (kek && keyAttributes) { const masterKey = await decryptBox( { @@ -158,15 +186,21 @@ const Page: React.FC = () => { }, kek, ); - await postVerification(masterKey, kek, keyAttributes); + await postVerification( + userEmail, + masterKey, + kek, + keyAttributes, + ); return; } + // Reauthentication in a new tab on the web app. Use previously + // generated interactive key attributes to verify password. if (keyAttributes) { - if ( - (!user?.token && !user?.encryptedToken) || - (keyAttributes && !keyAttributes.memLimit) - ) { + if (!user?.token && !user?.encryptedToken) { + // TODO(RE): Why? For now, add a dev mode circuit breaker. + if (isDevBuild) throw new Error("Unexpected case reached"); clearLocalStorage(); void router.push("/"); return; @@ -175,127 +209,103 @@ const Page: React.FC = () => { return; } + // First login on a new client. `getKeyAttributes` from below will + // be used during password verification to generate interactive key + // attributes for subsequent reauthentications. + const srpAttributes = savedSRPAttributes(); if (srpAttributes) { setSRPAttributes(srpAttributes); - } else { - void router.push("/"); + return; } - }; - void main(); - // TODO: validateSession is a dependency, but add that only after we've - // wrapped items from the callback (like logout) in useCallback too. - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + + void router.push("/"); + })(); + }, [router, validateSession, postVerification]); const getKeyAttributes: VerifyMasterPasswordFormProps["getKeyAttributes"] = async (kek: string) => { - try { - // Currently the page will get reloaded if any of the attributes - // have changed, so we don't need to worry about the KEK having - // been generated using stale credentials. This await on the - // promise is here to only ensure we're done with the check - // before we let the user in. - if (sessionValidityCheck) await sessionValidityCheck; + const { + id, + keyAttributes, + token, + encryptedToken, + twoFactorSessionID, + passkeySessionID, + accountsUrl, + } = await userVerificationResultAfterResolvingSecondFactorChoice( + await verifySRP(srpAttributes!, kek), + ); - const { - keyAttributes, - encryptedToken, - token, - id, - twoFactorSessionID, + // If we had to ask remote for the key attributes, it is the initial + // login on this client. + saveIsFirstLogin(); + + if (passkeySessionID) { + await stashKeyEncryptionKeyInSessionStore(kek); + updateSavedLocalUser({ passkeySessionID }); + stashRedirect("/"); + const url = passkeyVerificationRedirectURL( + accountsUrl!, passkeySessionID, - accountsUrl, - } = - await userVerificationResultAfterResolvingSecondFactorChoice( - await verifySRP(srpAttributes!, kek), - ); - saveIsFirstLogin(); - - if (passkeySessionID) { - await stashKeyEncryptionKeyInSessionStore(kek); - updateSavedLocalUser({ passkeySessionID }); - stashRedirect("/"); - const url = passkeyVerificationRedirectURL( - accountsUrl!, - passkeySessionID, - ); - setPasskeyVerificationData({ passkeySessionID, url }); - openPasskeyVerificationURL({ passkeySessionID, url }); - throw new Error(twoFactorEnabledErrorMessage); - } else if (twoFactorSessionID) { - await stashKeyEncryptionKeyInSessionStore(kek); - updateSavedLocalUser({ - isTwoFactorEnabled: true, - twoFactorSessionID, - }); - void router.push("/two-factor/verify"); - throw new Error(twoFactorEnabledErrorMessage); - } else { - const user = getData("user"); - await setLSUser({ - ...user, - token, - encryptedToken, - id, - isTwoFactorEnabled: undefined, - twoFactorSessionID: undefined, - passkeySessionID: undefined, - }); - if (keyAttributes) saveKeyAttributes(keyAttributes); - return keyAttributes; - } - } catch (e) { - if ( - e instanceof Error && - e.message != twoFactorEnabledErrorMessage - ) { - log.error("getKeyAttributes failed", e); - } - throw e; + ); + setPasskeyVerificationData({ passkeySessionID, url }); + openPasskeyVerificationURL({ passkeySessionID, url }); + return "redirecting-second-factor"; + } else if (twoFactorSessionID) { + await stashKeyEncryptionKeyInSessionStore(kek); + updateSavedLocalUser({ + isTwoFactorEnabled: true, + twoFactorSessionID, + }); + void router.push("/two-factor/verify"); + return "redirecting-second-factor"; + } else { + // In rare cases, if the user hasn't already setup their key + // attributes, we might get the plaintext token from remote. + if (token) await saveAuthToken(token); + updateSavedLocalUser({ + id, + token, + encryptedToken, + isTwoFactorEnabled: undefined, + twoFactorSessionID: undefined, + passkeySessionID: undefined, + }); + if (keyAttributes) saveKeyAttributes(keyAttributes); + return keyAttributes; } }; const handleVerifyMasterPassword: VerifyMasterPasswordFormProps["onVerify"] = - (key, kek, keyAttributes, password) => { - void (async () => { - const updatedKeyAttributes = savedIsFirstLogin() - ? await generateAndSaveInteractiveKeyAttributes( - password, - keyAttributes, - key, - ) - : keyAttributes; - await postVerification(key, kek, updatedKeyAttributes); - })(); - }; + useCallback( + (key, kek, keyAttributes, password) => { + void (async () => { + // Currently the page will get reloaded if any of the + // attributes have changed, so we don't need to worry about + // the KEK having been generated using stale credentials. + // + // This await on the promise is here to only ensure we're + // done with the check before we let the user in. + if (sessionValidityCheck) await sessionValidityCheck; - const postVerification = async ( - masterKey: string, - kek: string, - keyAttributes: KeyAttributes, - ) => { - await saveMasterKeyInSessionAndSafeStore(masterKey); - await decryptAndStoreToken(keyAttributes, masterKey); - try { - let srpAttributes = savedSRPAttributes(); - if (!srpAttributes && user?.email) { - srpAttributes = await getSRPAttributes(user.email); - if (srpAttributes) { - saveSRPAttributes(srpAttributes); - } - } - // TODO: todo? - log.debug(() => `userSRPSetupPending ${!srpAttributes}`); - if (!srpAttributes) { - await setupSRP(await generateSRPSetupAttributes(kek)); - } - } catch (e) { - log.error("migrate to srp failed", e); - } - void router.push(unstashRedirect() ?? appHomeRoute); - }; + const updatedKeyAttributes = savedIsFirstLogin() + ? await generateAndSaveInteractiveKeyAttributes( + password, + keyAttributes, + key, + ) + : keyAttributes; - const userEmail = user?.email; + await postVerification( + userEmail, + key, + kek, + updatedKeyAttributes, + ); + })(); + }, + [postVerification, userEmail, sessionValidityCheck], + ); if (!userEmail) { return ; @@ -321,7 +331,7 @@ const Page: React.FC = () => { return ( openPasskeyVerificationURL(passkeyVerificationData) diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index b98eebaefb..2d92a600ee 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -58,6 +58,18 @@ import React, { useCallback, useEffect, useState } from "react"; * - Redirects to "/" if there is no `email` present in the saved partial * local user. * + * - Redirects to "/two-factor/verify" if saved key attributes are not present + * once password is verified and the user has setup an additional TOTP + * second factor that also needs to be verified. + * + * - Redirects to the passkey app once password is verified if saved key + * attributes are not present if the user has setup an additional passkey + * that also needs to be verified. Before redirecting, it sets the + * `inflightPasskeySessionID` in session storage. + * + * - Redirects to the `appHomeRoute` otherwise (e.g. /gallery). The flow is + * complete. + * * - "/generate" - A page that allows the user to generate key attributes if * needed, and shows them their recovery key. * @@ -112,7 +124,7 @@ import React, { useCallback, useEffect, useState } from "react"; * or either of `twoFactorSessionID` and `twoFactorSessionID` is set. * * - Redirects to "/generate" if there is an `encryptedToken` or `token` in - * the saved partial local user (TODO: Why?). + * the saved partial local user. * * - Redirects to "/credentials" after recovery. * diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 98c4577058..18f4259922 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -19,7 +19,7 @@ import { getKVS, removeKV, setKV } from "ente-base/kv"; import log from "ente-base/log"; -import { getAuthToken } from "ente-base/token"; +import { savedAuthToken } from "ente-base/token"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; import { @@ -264,7 +264,7 @@ export const migrateKVToken = async (user: unknown) => { * token in local storage, then it should also be present in IndexedDB. */ export const isLocalStorageAndIndexedDBMismatch = async () => - savedPartialLocalUser()?.token && !(await getAuthToken()); + savedPartialLocalUser()?.token && !(await savedAuthToken()); /** * Return the user's {@link KeyAttributes} if they are present in local storage. @@ -381,11 +381,6 @@ export const unstashAfterUseSRPSetupAttributes = async ( localStorage.removeItem("srpSetupAttributes"); }; -export const getToken = (): string => { - const token = getData("user")?.token; - return token; -}; - /** * Zod schema for the legacy format in which the {@link savedIsFirstLogin} and * {@link savedJustSignedUp} flags were saved in local storage. diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 414fba3378..8bfbc856eb 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -6,7 +6,7 @@ import type { KeyAttributes } from "ente-accounts/services/user"; import { authenticatedRequestHeaders, HTTPError } from "ente-base/http"; import log from "ente-base/log"; import { apiURL } from "ente-base/origins"; -import { getAuthToken } from "ente-base/token"; +import { savedAuthToken } from "ente-base/token"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; import { getSRPAttributes, type SRPAttributes } from "./srp"; @@ -150,7 +150,7 @@ export const checkSessionValidity = async (): Promise => { * e.g. transient network issues. */ export const isSessionInvalid = async (): Promise => { - const token = await getAuthToken(); + const token = await savedAuthToken(); if (!token) { return true; /* No saved token, session is invalid */ } diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 7fb7390c91..23ecf7b599 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -33,7 +33,7 @@ import { ensureMasterKeyFromSession, saveMasterKeyInSessionAndSafeStore, } from "ente-base/session"; -import { getAuthToken } from "ente-base/token"; +import { savedAuthToken } from "ente-base/token"; import { ensure } from "ente-utils/ensure"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; @@ -67,7 +67,7 @@ export interface LocalUser { * the value of the X-Auth-Token header in the HTTP request. * * Usually you shouldn't be needing to access this property; instead use - * {@link getAuthToken()} which is kept in sync with this value, and lives + * {@link savedAuthToken()} which is kept in sync with this value, and lives * in IndexedDB and thus can also be used in web workers. */ token: string; @@ -582,9 +582,9 @@ export const verifyEmail = async ( * Log the user out on remote, if possible and needed. */ export const remoteLogoutIfNeeded = async () => { - if (!(await getAuthToken())) { - // If the logout is attempted during the signup flow itself, then we - // won't have an auth token. + if (!(await savedAuthToken())) { + // If the logout is attempted during the login / signup flow itself, + // then we won't have an auth token. Handle that gracefully. return; } diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index 0adc3003ed..704aaaca43 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -1,7 +1,7 @@ import { z } from "zod/v4"; import { decryptBox, encryptBox, generateKey } from "./crypto"; import log from "./log"; -import { getAuthToken } from "./token"; +import { savedAuthToken } from "./token"; /** * Remove all data stored in session storage (data tied to the browser tab). @@ -159,7 +159,7 @@ export const updateSessionFromElectronSafeStorageIfNeeded = async () => { * and their auth token in KV DB. */ export const haveAuthenticatedSession = async () => - (await masterKeyFromSession()) && !!(await getAuthToken()); + (await masterKeyFromSession()) && !!(await savedAuthToken()); /** * Save the user's encypted key encryption key ("key") in session store diff --git a/web/packages/base/token.ts b/web/packages/base/token.ts index f23d7866c7..87e6626118 100644 --- a/web/packages/base/token.ts +++ b/web/packages/base/token.ts @@ -1,25 +1,35 @@ -import { getKVS } from "./kv"; - -/** - * Return the user's auth token, if present. - * - * The user's auth token is stored in KV DB after they have successfully logged - * in. This function returns that saved auth token. - * - * The underlying data is stored in IndexedDB, and can be accessed from web - * workers. - */ -export const getAuthToken = () => getKVS("token"); +import { getKVS, setKV } from "./kv"; /** * Return the user's auth token, or throw an error. * - * The user's auth token can be retrieved using {@link getAuthToken}. This + * The user's auth token can be retrieved using {@link savedAuthToken}. This * function is a wrapper which throws an error if the token is not found (which * should only happen if the user is not logged in). */ export const ensureAuthToken = async () => { - const token = await getAuthToken(); + const token = await savedAuthToken(); if (!token) throw new Error("Not logged in"); return token; }; + +/** + * Return the user's auth token, if available. + * + * The user's auth token is stored in KV DB using {@link saveAuthToken} during + * the login / signup flow. This function returns that saved auth token. + * + * The underlying data is stored in IndexedDB, and can be accessed from web + * workers. + * + * If your code is running in a context where the user is already expected to be + * logged in, use {@link ensureAuthToken} instead. + */ +export const savedAuthToken = () => getKVS("token"); + +/** + * Save the user's auth token in KV DB. + * + * This is the setter corresponding to {@link savedAuthToken}. + */ +export const saveAuthToken = (token: string) => setKV("token", token); From 5c0b3795c2e602fb3a3ba0433cc8055551d66cd7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 07:21:41 +0530 Subject: [PATCH 027/109] Tweak --- web/apps/photos/src/pages/_app.tsx | 4 +- .../accounts/components/LoginContents.tsx | 7 +- .../accounts/components/SignUpContents.tsx | 6 + .../components/VerifyMasterPasswordForm.tsx | 5 +- web/packages/accounts/pages/credentials.tsx | 104 +++++++++--------- web/packages/accounts/pages/login.tsx | 35 +++--- web/packages/accounts/pages/verify.tsx | 34 +++--- 7 files changed, 105 insertions(+), 90 deletions(-) diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 960f01de0b..9547992de3 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -130,7 +130,9 @@ const App: React.FC = ({ Component, pageProps }) => { if (needsFamilyRedirect && savedPartialLocalUser()?.token) redirectToFamilyPortal(); - router.events.on("routeChangeStart", () => { + router.events.on("routeChangeStart", (url) => { + if (process.env.NEXT_PUBLIC_ENTE_TRACE) console.log("route", url); + if (needsFamilyRedirect && savedPartialLocalUser()?.token) { redirectToFamilyPortal(); diff --git a/web/packages/accounts/components/LoginContents.tsx b/web/packages/accounts/components/LoginContents.tsx index f0677daa52..a741a235b5 100644 --- a/web/packages/accounts/components/LoginContents.tsx +++ b/web/packages/accounts/components/LoginContents.tsx @@ -25,9 +25,10 @@ interface LoginContentsProps { } /** - * Contents of the "login form", maintained as a separate component so that the - * same code can be used both in the standalone /login page, and also within the - * embedded login form shown on the photos index page. + * A contents of the "login" form. + * + * It is used both on the "/login" page, and as the embedded login form on the + * "/" page where the user can toggle between the signup and login forms inline. */ export const LoginContents: React.FC = ({ onSignUp, diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index 3a04d6e7e2..bf82767f64 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -55,6 +55,12 @@ interface SignUpContentsProps { host: string | undefined; } +/** + * A contents of the "signup" form. + * + * It is used both on the "/signup" page itself, and as a subcomponent of the + * "/" page where the user can toggle between the signup and login forms inline. + */ export const SignUpContents: React.FC = ({ router, onLogin, diff --git a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx index 1afb5edd76..c974886508 100644 --- a/web/packages/accounts/components/VerifyMasterPasswordForm.tsx +++ b/web/packages/accounts/components/VerifyMasterPasswordForm.tsx @@ -58,6 +58,7 @@ export interface VerifyMasterPasswordFormProps { * exists. */ getKeyAttributes?: ( + srpAttributes: SRPAttributes, kek: string, ) => Promise; /** @@ -168,9 +169,9 @@ export const VerifyMasterPasswordForm: React.FC< } } else throw new Error("Both SRP and key attributes are missing"); - if (!keyAttributes && getKeyAttributes) { + if (!keyAttributes && getKeyAttributes && srpAttributes) { try { - const result = await getKeyAttributes(kek); + const result = await getKeyAttributes(srpAttributes, kek); if (result == "redirecting-second-factor") { // Two factor enabled, user has been redirected to the // corresponding second factor verification page. diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 83866b478c..b94dcfcb41 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -223,58 +223,62 @@ const Page: React.FC = () => { }, [router, validateSession, postVerification]); const getKeyAttributes: VerifyMasterPasswordFormProps["getKeyAttributes"] = - async (kek: string) => { - const { - id, - keyAttributes, - token, - encryptedToken, - twoFactorSessionID, - passkeySessionID, - accountsUrl, - } = await userVerificationResultAfterResolvingSecondFactorChoice( - await verifySRP(srpAttributes!, kek), - ); - - // If we had to ask remote for the key attributes, it is the initial - // login on this client. - saveIsFirstLogin(); - - if (passkeySessionID) { - await stashKeyEncryptionKeyInSessionStore(kek); - updateSavedLocalUser({ passkeySessionID }); - stashRedirect("/"); - const url = passkeyVerificationRedirectURL( - accountsUrl!, - passkeySessionID, - ); - setPasskeyVerificationData({ passkeySessionID, url }); - openPasskeyVerificationURL({ passkeySessionID, url }); - return "redirecting-second-factor"; - } else if (twoFactorSessionID) { - await stashKeyEncryptionKeyInSessionStore(kek); - updateSavedLocalUser({ - isTwoFactorEnabled: true, - twoFactorSessionID, - }); - void router.push("/two-factor/verify"); - return "redirecting-second-factor"; - } else { - // In rare cases, if the user hasn't already setup their key - // attributes, we might get the plaintext token from remote. - if (token) await saveAuthToken(token); - updateSavedLocalUser({ + useCallback( + async (srpAttributes: SRPAttributes, kek: string) => { + const { id, + keyAttributes, token, encryptedToken, - isTwoFactorEnabled: undefined, - twoFactorSessionID: undefined, - passkeySessionID: undefined, - }); - if (keyAttributes) saveKeyAttributes(keyAttributes); - return keyAttributes; - } - }; + twoFactorSessionID, + passkeySessionID, + accountsUrl, + } = + await userVerificationResultAfterResolvingSecondFactorChoice( + await verifySRP(srpAttributes, kek), + ); + + // If we had to ask remote for the key attributes, it is the + // initial login on this client. + saveIsFirstLogin(); + + if (passkeySessionID) { + await stashKeyEncryptionKeyInSessionStore(kek); + updateSavedLocalUser({ passkeySessionID }); + stashRedirect("/"); + const url = passkeyVerificationRedirectURL( + accountsUrl!, + passkeySessionID, + ); + setPasskeyVerificationData({ passkeySessionID, url }); + openPasskeyVerificationURL({ passkeySessionID, url }); + return "redirecting-second-factor"; + } else if (twoFactorSessionID) { + await stashKeyEncryptionKeyInSessionStore(kek); + updateSavedLocalUser({ + isTwoFactorEnabled: true, + twoFactorSessionID, + }); + void router.push("/two-factor/verify"); + return "redirecting-second-factor"; + } else { + // In rare cases, if the user hasn't already setup their key + // attributes, we might get the plaintext token from remote. + if (token) await saveAuthToken(token); + updateSavedLocalUser({ + id, + token, + encryptedToken, + isTwoFactorEnabled: undefined, + twoFactorSessionID: undefined, + passkeySessionID: undefined, + }); + if (keyAttributes) saveKeyAttributes(keyAttributes); + return keyAttributes; + } + }, + [userVerificationResultAfterResolvingSecondFactorChoice, router], + ); const handleVerifyMasterPassword: VerifyMasterPasswordFormProps["onVerify"] = useCallback( @@ -341,8 +345,6 @@ const Page: React.FC = () => { ); } - // TODO: Handle the case when user is not present, or exclude that - // possibility using types. return ( diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index 2d92a600ee..2d3f37abc5 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -40,17 +40,20 @@ import React, { useCallback, useEffect, useState } from "react"; * - Redirects to "/" if there is no `email` present in the saved partial * local user. * - * - Redirects to "/credentials" if email verification is not needed, and also - * when email verification completes. + * - Redirects to "/credentials" if both saved key attributes and a `token` + * (or `encryptedToken`) in present in the saved partial local user, or if + * email verification is not needed, or when email verification completes + * and remote sent us key attributes (which will happen on login). * - * - Redirects to "/two-factor/verify" once email verification is complete if - * the user has setup an additional TOTP second factor that also needs to be - * verified. + * - Redirects to "/generate" once email verification is complete and the user + * does not have key attributes (which will happen for new signups). * - * - Redirects to the passkey app once email verification is complete if the - * user has setup an additional passkey that also needs to be verified. - * Before redirecting, it sets the `inflightPasskeySessionID` in session - * storage. + * - Redirects to "/two-factor/verify" when email verification completes and + * the user has setup a TOTP second factor that also needs to be verified. + * + * - Redirects to the passkey app when email verification completes and the + * user has setup a passkey that also needs to be verified. Before + * redirecting, `inflightPasskeySessionID` in saved in session storage. * * - "/credentials" - A page that allows the user to enter their password to * authenticate (initial login) or reauthenticate (new web app tab) @@ -59,16 +62,16 @@ import React, { useCallback, useEffect, useState } from "react"; * local user. * * - Redirects to "/two-factor/verify" if saved key attributes are not present - * once password is verified and the user has setup an additional TOTP - * second factor that also needs to be verified. + * once password is verified and the user has setup a TOTP second factor + * that also needs to be verified. * * - Redirects to the passkey app once password is verified if saved key - * attributes are not present if the user has setup an additional passkey - * that also needs to be verified. Before redirecting, it sets the - * `inflightPasskeySessionID` in session storage. + * attributes are not present if the user has setup a passkey that also + * needs to be verified. Before redirecting, `inflightPasskeySessionID` is + * saved in session storage. * - * - Redirects to the `appHomeRoute` otherwise (e.g. /gallery). The flow is - * complete. + * - Redirects to the `appHomeRoute` otherwise (e.g. /gallery). **The flow is + * complete**. * * - "/generate" - A page that allows the user to generate key attributes if * needed, and shows them their recovery key. diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 9eac4d569d..12c6cfa773 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -46,7 +46,7 @@ import log from "ente-base/log"; import { clearSessionStorage } from "ente-base/session"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { Trans } from "react-i18next"; /** @@ -58,7 +58,9 @@ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); const [email, setEmail] = useState(""); - const [resend, setResend] = useState(0); + const [resend, setResend] = useState<"enable" | "sending" | "sent">( + "enable", + ); const [passkeyVerificationData, setPasskeyVerificationData] = useState< { passkeySessionID: string; url: string } | undefined >(); @@ -100,6 +102,7 @@ const Page: React.FC = () => { } = await userVerificationResultAfterResolvingSecondFactorChoice( await verifyEmail(email, ott, cleanedReferral), ); + if (passkeySessionID) { updateSavedLocalUser({ passkeySessionID }); saveIsFirstLogin(); @@ -137,12 +140,11 @@ const Page: React.FC = () => { await unstashAfterUseSRPSetupAttributes(setupSRP); } saveIsFirstLogin(); - const redirectURL = unstashRedirect(); - if (keyAttributes?.encryptedKey) { + if (keyAttributes) { clearSessionStorage(); - void router.push(redirectURL ?? "/credentials"); + void router.push(unstashRedirect() ?? "/credentials"); } else { - void router.push(redirectURL ?? "/generate"); + void router.push(unstashRedirect() ?? "/generate"); } } } catch (e) { @@ -157,12 +159,12 @@ const Page: React.FC = () => { } }; - const resendEmail = async () => { - setResend(1); + const resendEmail = useCallback(async () => { + setResend("sending"); await sendOTT(email, undefined); - setResend(2); - setTimeout(() => setResend(0), 3000); - }; + setResend("sent"); + setTimeout(() => setResend("enable"), 3000); + }, [email]); if (!email) { return ; @@ -225,13 +227,13 @@ const Page: React.FC = () => { /> - {resend == 0 && ( + {resend == "enable" && ( {t("resend_code")} )} - {resend == 1 && {t("status_sending")}} - {resend == 2 && {t("status_sent")}} + {resend == "sending" && {t("status_sending")}} + {resend == "sent" && {t("status_sent")}} {t("change_email")} @@ -256,9 +258,7 @@ const redirectionIfNeededOrEmail = async () => { return "/"; } - const keyAttributes = savedKeyAttributes(); - - if (keyAttributes?.encryptedKey && (user.token || user.encryptedToken)) { + if (savedKeyAttributes() && (user.token || user.encryptedToken)) { return "/credentials"; } From 70b5b8e682c56ffdec54bdbc81d2814b1a3d1a37 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 07:30:27 +0530 Subject: [PATCH 028/109] Update --- .../accounts/components/LoginContents.tsx | 6 +++--- .../accounts/components/SignUpContents.tsx | 4 ++-- web/packages/accounts/pages/verify.tsx | 17 +++++++---------- web/packages/accounts/services/accounts-db.ts | 14 +++++++------- 4 files changed, 19 insertions(+), 22 deletions(-) diff --git a/web/packages/accounts/components/LoginContents.tsx b/web/packages/accounts/components/LoginContents.tsx index a741a235b5..f3107993f5 100644 --- a/web/packages/accounts/components/LoginContents.tsx +++ b/web/packages/accounts/components/LoginContents.tsx @@ -1,7 +1,7 @@ import { Input, Stack, TextField, Typography } from "@mui/material"; import { AccountsPageFooter } from "ente-accounts/components/layouts/centered-paper"; import { - savePartialLocalUser, + replaceSavedLocalUser, saveSRPAttributes, } from "ente-accounts/services/accounts-db"; import { getSRPAttributes } from "ente-accounts/services/srp"; @@ -51,10 +51,10 @@ export const LoginContents: React.FC = ({ } throw e; } - savePartialLocalUser({ email }); + replaceSavedLocalUser({ email }); void router.push("/verify"); } else { - savePartialLocalUser({ email }); + replaceSavedLocalUser({ email }); saveSRPAttributes(srpAttributes); void router.push("/credentials"); } diff --git a/web/packages/accounts/components/SignUpContents.tsx b/web/packages/accounts/components/SignUpContents.tsx index bf82767f64..b4c3372f06 100644 --- a/web/packages/accounts/components/SignUpContents.tsx +++ b/web/packages/accounts/components/SignUpContents.tsx @@ -14,9 +14,9 @@ import { Typography, } from "@mui/material"; import { + replaceSavedLocalUser, saveJustSignedUp, saveOriginalKeyAttributes, - savePartialLocalUser, stashReferralSource, stashSRPSetupAttributes, } from "ente-accounts/services/accounts-db"; @@ -130,7 +130,7 @@ export const SignUpContents: React.FC = ({ throw e; } - savePartialLocalUser({ email }); + replaceSavedLocalUser({ email }); let gkResult: GenerateKeysAndAttributesResult; try { diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 12c6cfa773..3817b4d1fa 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -8,6 +8,7 @@ import { VerifyingPasskey } from "ente-accounts/components/LoginComponents"; import { SecondFactorChoice } from "ente-accounts/components/SecondFactorChoice"; import { useSecondFactorChoiceIfNeeded } from "ente-accounts/components/utils/second-factor-choice"; import { + replaceSavedLocalUser, savedKeyAttributes, savedOriginalKeyAttributes, savedPartialLocalUser, @@ -15,7 +16,6 @@ import { saveIsFirstLogin, saveKeyAttributes, saveOriginalKeyAttributes, - setLSUser, unstashAfterUseSRPSetupAttributes, unstashReferralSource, updateSavedLocalUser, @@ -44,6 +44,7 @@ import { useBaseContext } from "ente-base/context"; import { isHTTPErrorWithStatus } from "ente-base/http"; import log from "ente-base/log"; import { clearSessionStorage } from "ente-base/session"; +import { saveAuthToken } from "ente-base/token"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; @@ -103,6 +104,9 @@ const Page: React.FC = () => { await verifyEmail(email, ott, cleanedReferral), ); + // The following flow is similar to (but not the same) as what + // happens after `verifySRP` in the `/credentials` page. + if (passkeySessionID) { updateSavedLocalUser({ passkeySessionID }); saveIsFirstLogin(); @@ -120,15 +124,8 @@ const Page: React.FC = () => { saveIsFirstLogin(); void router.push("/two-factor/verify"); } else { - await setLSUser({ - email, - token, - encryptedToken, - id, - isTwoFactorEnabled: undefined, - twoFactorSessionID: undefined, - passkeySessionID: undefined, - }); + if (token) await saveAuthToken(token); + replaceSavedLocalUser({ id, email, token, encryptedToken }); if (keyAttributes) { saveKeyAttributes(keyAttributes); saveOriginalKeyAttributes(keyAttributes); diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index 18f4259922..eda8fe69e8 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -118,7 +118,7 @@ const LocalUser = z.object({ * After the user is logged in, use {@link savedLocalUser} or * {@link ensureLocalUser} instead. * - * Use {@link savePartialLocalUser} to updated the saved value. + * Use {@link replaceSavedLocalUser} to updated the saved value. */ export const savedPartialLocalUser = (): PartialLocalUser | undefined => { const jsonString = localStorage.getItem("user"); @@ -131,32 +131,32 @@ export const savedPartialLocalUser = (): PartialLocalUser | undefined => { * * See: [Note: Partial local user]. * - * This method replaces the existing data. Use {@link updatePartialLocalUser} to + * This method replaces the existing data. Use {@link updateSavedLocalUser} to * update selected fields while keeping the other fields as it is. * * TODO: WARNING: This does not update the KV token. The idea is to gradually * move over uses of setLSUser to this while explicitly setting the KV token * where needed. */ -export const savePartialLocalUser = (partialLocalUser: PartialLocalUser) => +export const replaceSavedLocalUser = (partialLocalUser: PartialLocalUser) => localStorage.setItem("user", JSON.stringify(partialLocalUser)); /** * Partially update the saved user data. * - * This is a delta variant of {@link savePartialLocalUser}, which replaces the + * This is a delta variant of {@link replaceSavedLocalUser}, which replaces the * entire saved object, while this function spreads the provided {@link updates} * onto the currently saved value. * * @param updates A subset of {@link PartialLocalUser} fields that we'd like to - * update. The other fields, if any, remain unchanged. + * update. The other fields, if present in local storage, remain unchanged. * * TODO: WARNING: This does not update the KV token. The idea is to gradually * move over uses of setLSUser to this while explicitly setting the KV token * where needed. */ export const updateSavedLocalUser = (updates: Partial) => - savePartialLocalUser({ ...savedPartialLocalUser(), ...updates }); + replaceSavedLocalUser({ ...savedPartialLocalUser(), ...updates }); /** * Return data about the logged-in user, if someone is indeed logged in. @@ -167,7 +167,7 @@ export const updateSavedLocalUser = (updates: Partial) => * not accessible to web workers. * * There is no setter corresponding to this function since this is only a view - * on data saved using {@link savePartialLocalUser} or + * on data saved using {@link replaceSavedLocalUser} or * {@link updateSavedLocalUser}. * * See: [Note: Partial local user] for more about the whole shebang. From c9521fb6262b48aefa7fcf6dc966d45d0679f23a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 08:08:35 +0530 Subject: [PATCH 029/109] Update --- web/apps/photos/src/pages/gallery.tsx | 6 +- .../accounts/components/utils/use-redirect.ts | 4 +- web/packages/accounts/pages/generate.tsx | 82 +++++++++---------- web/packages/accounts/pages/login.tsx | 15 ++-- web/packages/accounts/pages/recover.tsx | 4 +- web/packages/base/session.ts | 4 +- 6 files changed, 58 insertions(+), 57 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 94e5b7d366..f29869493a 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -35,7 +35,7 @@ import { useBaseContext } from "ente-base/context"; import log from "ente-base/log"; import { clearSessionStorage, - haveCredentialsInSession, + haveMasterKeyInSession, masterKeyFromSession, } from "ente-base/session"; import { savedAuthToken } from "ente-base/token"; @@ -282,7 +282,7 @@ const Page: React.FC = () => { let syncIntervalID: ReturnType | undefined; void (async () => { - if (!haveCredentialsInSession() || !(await savedAuthToken())) { + if (!haveMasterKeyInSession() || !(await savedAuthToken())) { // If we don't have master key or auth token, reauthenticate. stashRedirect("/gallery"); router.push("/"); @@ -376,7 +376,7 @@ const Page: React.FC = () => { }, [activeCollectionID, router.isReady]); useEffect(() => { - if (router.isReady && haveCredentialsInSession()) { + if (router.isReady && haveMasterKeyInSession()) { handleSubscriptionCompletionRedirectIfNeeded( showMiniDialog, showLoadingBar, diff --git a/web/packages/accounts/components/utils/use-redirect.ts b/web/packages/accounts/components/utils/use-redirect.ts index a96e79b750..de34afc717 100644 --- a/web/packages/accounts/components/utils/use-redirect.ts +++ b/web/packages/accounts/components/utils/use-redirect.ts @@ -1,4 +1,4 @@ -import { haveCredentialsInSession } from "ente-base/session"; +import { haveMasterKeyInSession } from "ente-base/session"; import { useRouter } from "next/router"; import { useEffect } from "react"; import { stashRedirect } from "../../services/redirect"; @@ -16,7 +16,7 @@ export const useRedirectIfNeedsCredentials = (currentPageSlug: string) => { const router = useRouter(); useEffect(() => { - if (!haveCredentialsInSession()) { + if (!haveMasterKeyInSession()) { stashRedirect(currentPageSlug); void router.push("/"); } diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index 92d3bb1900..c80fa3db7f 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -16,7 +16,6 @@ import { generateSRPSetupAttributes, setupSRP, } from "ente-accounts/services/srp"; -import type { PartialLocalUser } from "ente-accounts/services/user"; import { generateAndSaveInteractiveKeyAttributes, generateKeysAndAttributes, @@ -28,12 +27,12 @@ import { useBaseContext } from "ente-base/context"; import { deriveKeyInsufficientMemoryErrorMessage } from "ente-base/crypto/types"; import log from "ente-base/log"; import { - haveCredentialsInSession, + haveMasterKeyInSession, saveMasterKeyInSessionAndSafeStore, } from "ente-base/session"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { NewPasswordForm, type NewPasswordFormProps, @@ -41,79 +40,76 @@ import { /** * A page that allows the user to generate key attributes if needed, and shows - * them their recovery key. + * them their recovery key if they just signed up. * * See: [Note: Login pages] */ const Page: React.FC = () => { const { logout, showMiniDialog } = useBaseContext(); - const [user, setUser] = useState(undefined); + const [userEmail, setUserEmail] = useState(""); const [openRecoveryKey, setOpenRecoveryKey] = useState(false); const router = useRouter(); useEffect(() => { const user = savedPartialLocalUser(); - if (!user?.token) { + if (!user?.email || !user?.token) { void router.push("/"); - } else if (haveCredentialsInSession()) { + } else if (haveMasterKeyInSession()) { if (savedJustSignedUp()) { setOpenRecoveryKey(true); - setUser(user); } else { void router.push(appHomeRoute); } - } else if (savedOriginalKeyAttributes()?.encryptedKey) { + } else if (savedOriginalKeyAttributes()) { void router.push("/credentials"); } else { - setUser(user); + setUserEmail(user.email); } }, [router]); - const handleSubmit: NewPasswordFormProps["onSubmit"] = async ( - password, - setPasswordsFieldError, - ) => { - try { - const { masterKey, kek, keyAttributes } = - await generateKeysAndAttributes(password); - await putUserKeyAttributes(keyAttributes); - await setupSRP(await generateSRPSetupAttributes(kek)); - await generateAndSaveInteractiveKeyAttributes( - password, - keyAttributes, - masterKey, - ); - await saveMasterKeyInSessionAndSafeStore(masterKey); - saveJustSignedUp(); - setOpenRecoveryKey(true); - } catch (e) { - log.error("failed to generate password", e); - setPasswordsFieldError( - e instanceof Error && - e.message == deriveKeyInsufficientMemoryErrorMessage - ? t("password_generation_failed") - : t("generic_error"), - ); - } - }; + const handleSubmit: NewPasswordFormProps["onSubmit"] = useCallback( + async (password, setPasswordsFieldError) => { + try { + const { masterKey, kek, keyAttributes } = + await generateKeysAndAttributes(password); + await putUserKeyAttributes(keyAttributes); + await setupSRP(await generateSRPSetupAttributes(kek)); + await generateAndSaveInteractiveKeyAttributes( + password, + keyAttributes, + masterKey, + ); + await saveMasterKeyInSessionAndSafeStore(masterKey); + saveJustSignedUp(); + setOpenRecoveryKey(true); + } catch (e) { + log.error("Could not generate key attributes from password", e); + setPasswordsFieldError( + e instanceof Error && + e.message == deriveKeyInsufficientMemoryErrorMessage + ? t("password_generation_failed") + : t("generic_error"), + ); + } + }, + [], + ); return ( <> - {!user ? ( - - ) : openRecoveryKey ? ( + {openRecoveryKey ? ( void router.push(appHomeRoute)} showMiniDialog={showMiniDialog} /> - ) : ( + ) : userEmail ? ( {t("set_password")} @@ -122,6 +118,8 @@ const Page: React.FC = () => { {t("go_back")} + ) : ( + )} ); diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index 2d3f37abc5..6d36a1cd84 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -74,14 +74,17 @@ import React, { useCallback, useEffect, useState } from "react"; * complete**. * * - "/generate" - A page that allows the user to generate key attributes if - * needed, and shows them their recovery key. + * needed, and shows them their recovery key if they just signed up. * - * - Redirects to "/" if there is no `email` present in the saved partial - * local user, or after viewing the recovery key, or after the user sets - * their password (if they did no have key attributes). + * - Redirects to "/" if there is no `email` or `token` present in the saved + * partial user. * - * - Redirects to "/credentials" if they already have the original key - * attributes. + * - Redirects to `appHomeRoute` after viewing the recovery key if they have a + * master key in session, or after setting the password and then viewing the + * recovery key (if they did not have a master key in session store). + * + * - Redirects to "/credentials" if if they don't have a master key in session + * store but have saved original key attributes. * * - "/recover" - A page that allows the user to recover their master key using * their recovery key. diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index ac91d3c9fc..edbe1e9e6a 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -21,7 +21,7 @@ import { useBaseContext } from "ente-base/context"; import { decryptBox } from "ente-base/crypto"; import log from "ente-base/log"; import { - haveCredentialsInSession, + haveMasterKeyInSession, saveMasterKeyInSessionAndSafeStore, } from "ente-base/session"; import { t } from "i18next"; @@ -59,7 +59,7 @@ const Page: React.FC = () => { const keyAttributes = savedKeyAttributes(); if (!keyAttributes) { void router.push("/generate"); - } else if (haveCredentialsInSession()) { + } else if (haveMasterKeyInSession()) { void router.push(appHomeRoute); } else { setKeyAttributes(keyAttributes); diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index 704aaaca43..1486c31952 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -51,7 +51,7 @@ export const ensureMasterKeyFromSession = async () => { * have credentials at hand or not, however it doesn't attempt to verify that * the key present in the session can actually be decrypted. */ -export const haveCredentialsInSession = () => +export const haveMasterKeyInSession = () => !!sessionStorage.getItem("encryptionKey"); /** @@ -140,7 +140,7 @@ export const updateSessionFromElectronSafeStorageIfNeeded = async () => { const electron = globalThis.electron; if (!electron) return; - if (haveCredentialsInSession()) return; + if (haveMasterKeyInSession()) return; let masterKey: string | undefined; try { From bbe10b1618fc6bbbee9cb44d64b592d50bce822a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 08:21:55 +0530 Subject: [PATCH 030/109] Update --- web/packages/accounts/pages/login.tsx | 20 ++--- .../accounts/pages/passkeys/finish.tsx | 7 +- web/packages/accounts/pages/recover.tsx | 89 ++++++++++--------- .../accounts/pages/two-factor/recover.tsx | 36 ++++---- .../accounts/pages/two-factor/verify.tsx | 2 + 5 files changed, 85 insertions(+), 69 deletions(-) diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index 6d36a1cd84..9aab2d2105 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -110,12 +110,12 @@ import React, { useCallback, useEffect, useState } from "react"; * - Redirects to "/" if there is no `email` or `twoFactorSessionID` in the * saved partial local user. * - * - Redirects to "/credentials" if there `isTwoFactorEnabled` is not `true` - * and either of `encryptedToken` or `token` is present in the saved partial + * - Redirects to "/credentials" if `isTwoFactorEnabled` is not `true` and + * either of `encryptedToken` or `token` is present in the saved partial * local user. * - * - "/passkeys/finish" - A page that the accounts app hands off control back to - * us (the calling app) to continue the rest of the authentication. + * - "/passkeys/finish" - A page where the accounts app hands off control back + * to us (the calling app) once the passkey has been verified. * * - Redirects to "/" if there is no matching `inflightPasskeySessionID` in * session storage. @@ -124,15 +124,15 @@ import React, { useCallback, useEffect, useState } from "react"; * * - "/two-factor/recover" and "/passkeys/recover" - Pages that allow the user * to reset or bypass their second factor if they possess their recovery key. - * Both pages work similarly, except the second factor they act on. + * Both pages work similarly, except for the second factor they act on. * - * - Redirects to "/" if there is no `email` in the saved partial local user, - * or either of `twoFactorSessionID` and `twoFactorSessionID` is set. + * - Redirect to "/" if there is no `email` or `twoFactorSessionID` / + * `passkeySessionID` in the saved partial local user. * - * - Redirects to "/generate" if there is an `encryptedToken` or `token` in - * the saved partial local user. + * - Redirect to "/generate" if there is an `encryptedToken` or `token` in the + * saved partial local user. * - * - Redirects to "/credentials" after recovery. + * - Redirect to "/credentials" after recovery. * */ const Page: React.FC = () => { diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index 1518a61b94..b8fd76e89c 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -13,6 +13,11 @@ import { useRouter } from "next/router"; import React, { useEffect } from "react"; /** + * The page where the accounts app hands back control to us once the passkey has + * been verified. + * + * See: [Note: Login pages] + * * [Note: Finish passkey flow in the requesting app] * * The passkey finish step needs to happen in the context of the client which @@ -23,7 +28,7 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - // Extract response from query params + // Extract response from query params. const searchParams = new URLSearchParams(window.location.search); const passkeySessionID = searchParams.get("passkeySessionID"); const response = searchParams.get("response"); diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index edbe1e9e6a..23c789d475 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -26,7 +26,7 @@ import { } from "ente-base/session"; import { t } from "i18next"; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; /** * A page that allows the user to enter their recovery key to recover their @@ -44,58 +44,63 @@ const Page: React.FC = () => { const router = useRouter(); useEffect(() => { - const user = savedPartialLocalUser(); - if (!user?.email) { - void router.push("/"); - return; - } - if (!user?.encryptedToken && !user?.token) { - void sendOTT(user.email, undefined); - stashRedirect("/recover"); - void router.push("/verify"); - return; - } + void (async () => { + const user = savedPartialLocalUser(); + if (!user?.email) { + await router.push("/"); + return; + } - const keyAttributes = savedKeyAttributes(); - if (!keyAttributes) { - void router.push("/generate"); - } else if (haveMasterKeyInSession()) { - void router.push(appHomeRoute); - } else { - setKeyAttributes(keyAttributes); - } + if (!user.encryptedToken && !user.token) { + await sendOTT(user.email, undefined); + stashRedirect("/recover"); + await router.push("/verify"); + return; + } + + const keyAttributes = savedKeyAttributes(); + if (!keyAttributes) { + await router.push("/generate"); + } else if (haveMasterKeyInSession()) { + await router.push(appHomeRoute); + } else { + setKeyAttributes(keyAttributes); + } + })(); }, [router]); - const handleSubmit: SingleInputFormProps["onSubmit"] = async ( - recoveryKeyMnemonic: string, - setFieldError, - ) => { - try { - const keyAttr = keyAttributes!; - const masterKey = await decryptBox( - { - encryptedData: keyAttr.masterKeyEncryptedWithRecoveryKey!, - nonce: keyAttr.masterKeyDecryptionNonce!, - }, - await recoveryKeyFromMnemonic(recoveryKeyMnemonic), - ); - await saveMasterKeyInSessionAndSafeStore(masterKey); - await decryptAndStoreToken(keyAttr, masterKey); + const handleSubmit: SingleInputFormProps["onSubmit"] = useCallback( + async (recoveryKeyMnemonic: string, setFieldError) => { + try { + const keyAttr = keyAttributes!; + const masterKey = await decryptBox( + { + encryptedData: + keyAttr.masterKeyEncryptedWithRecoveryKey!, + nonce: keyAttr.masterKeyDecryptionNonce!, + }, + await recoveryKeyFromMnemonic(recoveryKeyMnemonic), + ); + await saveMasterKeyInSessionAndSafeStore(masterKey); + await decryptAndStoreToken(keyAttr, masterKey); - void router.push("/change-password?op=reset"); - } catch (e) { - log.error("password recovery failed", e); - setFieldError(t("incorrect_recovery_key")); - } - }; + void router.push("/change-password?op=reset"); + } catch (e) { + log.error("Master key recovery failed", e); + setFieldError(t("incorrect_recovery_key")); + } + }, + [router, keyAttributes], + ); - const showNoRecoveryKeyMessage = () => + const showNoRecoveryKeyMessage = useCallback(() => { showMiniDialog({ title: t("sorry"), message: t("no_recovery_key_message"), continue: { color: "secondary" }, cancel: false, }); + }, [showMiniDialog]); return ( diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index caa3480661..6b17dfec6d 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -64,20 +64,23 @@ const Page: React.FC = ({ twoFactorType }) => { ); useEffect(() => { - const user = savedPartialLocalUser(); - const sessionID = - twoFactorType == "passkey" - ? user?.passkeySessionID - : user?.twoFactorSessionID; - if (!user?.email || !sessionID) { - void router.push("/"); - } else if (user.encryptedToken || user.token) { - void router.push("/generate"); - } else { - setSessionID(sessionID); - void recoverTwoFactor(twoFactorType, sessionID) - .then(setRecoveryResponse) - .catch((e: unknown) => { + void (async () => { + const user = savedPartialLocalUser(); + const sessionID = + twoFactorType == "passkey" + ? user?.passkeySessionID + : user?.twoFactorSessionID; + if (!user?.email || !sessionID) { + await router.push("/"); + } else if (user.encryptedToken || user.token) { + await router.push("/generate"); + } else { + setSessionID(sessionID); + try { + setRecoveryResponse( + await recoverTwoFactor(twoFactorType, sessionID), + ); + } catch (e) { log.error("Second factor recovery page setup failed", e); if (isHTTPErrorWithStatus(e, 404)) { logout(); @@ -86,8 +89,9 @@ const Page: React.FC = ({ twoFactorType }) => { } else { onGenericError(e); } - }); - } + } + } + })(); }, [ twoFactorType, logout, diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index 918fba2486..c11a1238a7 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -21,6 +21,8 @@ import { unstashRedirect } from "../../services/redirect"; /** * A page that allows the user to verify their TOTP based second factor. + * + * See: [Note: Login pages] */ const Page: React.FC = () => { const { logout } = useBaseContext(); From 46dc71ebd228309bcd12a578881111b1877d0b6c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 09:07:53 +0530 Subject: [PATCH 031/109] Rework --- web/apps/photos/src/pages/index.tsx | 8 +- web/packages/accounts/pages/credentials.tsx | 11 +-- .../accounts/pages/passkeys/finish.tsx | 14 +-- .../accounts/pages/two-factor/verify.tsx | 21 ++--- web/packages/accounts/services/passkey.ts | 11 ++- web/packages/accounts/services/user.ts | 87 ++++++++++++++++--- web/packages/accounts/utils/helpers.ts | 35 -------- web/packages/base/session.ts | 7 -- web/packages/base/token.ts | 9 +- 9 files changed, 118 insertions(+), 85 deletions(-) delete mode 100644 web/packages/accounts/utils/helpers.ts diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 3ac7b0d771..d452129a18 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -9,9 +9,10 @@ import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton" import { useBaseContext } from "ente-base/context"; import { albumsAppOrigin, customAPIHost } from "ente-base/origins"; import { - haveAuthenticatedSession, + masterKeyFromSession, updateSessionFromElectronSafeStorageIfNeeded, } from "ente-base/session"; +import { savedAuthToken } from "ente-base/token"; import { canAccessIndexedDB } from "ente-gallery/services/files-db"; import { DevSettings } from "ente-new/photos/components/DevSettings"; import { t } from "i18next"; @@ -55,7 +56,10 @@ const Page: React.FC = () => { }); } else { await updateSessionFromElectronSafeStorageIfNeeded(); - if (await haveAuthenticatedSession()) { + if ( + (await masterKeyFromSession()) && + (await savedAuthToken()) + ) { await router.push("/gallery"); } else if (savedPartialLocalUser()?.email) { await router.push("/verify"); diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index b94dcfcb41..66cbd2680a 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -39,10 +39,10 @@ import { verifySRP, } from "ente-accounts/services/srp"; import { + decryptAndStoreToken, generateAndSaveInteractiveKeyAttributes, type KeyAttributes, } from "ente-accounts/services/user"; -import { decryptAndStoreToken } from "ente-accounts/utils/helpers"; import { LinkButton } from "ente-base/components/LinkButton"; import { LoadingIndicator } from "ente-base/components/loaders"; import { useBaseContext } from "ente-base/context"; @@ -51,13 +51,13 @@ import { isDevBuild } from "ente-base/env"; import { clearLocalStorage } from "ente-base/local-storage"; import log from "ente-base/log"; import { - haveAuthenticatedSession, + masterKeyFromSession, saveMasterKeyInSessionAndSafeStore, stashKeyEncryptionKeyInSessionStore, unstashKeyEncryptionKeyFromSession, updateSessionFromElectronSafeStorageIfNeeded, } from "ente-base/session"; -import { saveAuthToken } from "ente-base/token"; +import { saveAuthToken, savedAuthToken } from "ente-base/token"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useCallback, useEffect, useState } from "react"; @@ -166,7 +166,7 @@ const Page: React.FC = () => { } await updateSessionFromElectronSafeStorageIfNeeded(); - if (await haveAuthenticatedSession()) { + if ((await masterKeyFromSession()) && (await savedAuthToken())) { void router.push(appHomeRoute); return; } @@ -177,7 +177,8 @@ const Page: React.FC = () => { const kek = await unstashKeyEncryptionKeyFromSession(); const keyAttributes = savedKeyAttributes(); - // Refreshing an existing tab, or desktop app. + // Refreshing an existing tab, or desktop app, or only the token + // needs to decrypted and set. if (kek && keyAttributes) { const masterKey = await decryptBox( { diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index b8fd76e89c..6d25ba5ba5 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -1,10 +1,12 @@ import { - getData, saveKeyAttributes, - setLSUser, + updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; import { unstashRedirect } from "ente-accounts/services/redirect"; -import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; +import { + resetSavedLocalUserTokens, + TwoFactorAuthorizationResponse, +} from "ente-accounts/services/user"; import { LoadingIndicator } from "ente-base/components/loaders"; import { fromB64URLSafeNoPadding } from "ente-base/crypto"; import log from "ente-base/log"; @@ -95,10 +97,8 @@ const saveQueryCredentialsAndNavigateTo = async ( const { id, keyAttributes, encryptedToken } = decodedResponse; - // TODO: See: [Note: empty token?] - const token = undefined; - - await setLSUser({ ...getData("user"), token, encryptedToken, id }); + await resetSavedLocalUserTokens(id, encryptedToken); + updateSavedLocalUser({ passkeySessionID: undefined }); saveKeyAttributes(keyAttributes); return unstashRedirect() ?? "/credentials"; diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index c11a1238a7..f56c186f8f 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -1,11 +1,13 @@ import { Verify2FACodeForm } from "ente-accounts/components/Verify2FACodeForm"; import { - getData, savedPartialLocalUser, saveKeyAttributes, - setLSUser, + updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; -import { verifyTwoFactor } from "ente-accounts/services/user"; +import { + resetSavedLocalUserTokens, + verifyTwoFactor, +} from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; import { isHTTPErrorWithStatus } from "ente-base/http"; @@ -50,17 +52,8 @@ const Page: React.FC = () => { try { const { keyAttributes, encryptedToken, id } = await verifyTwoFactor(otp, twoFactorSessionID); - await setLSUser({ - ...getData("user"), - id, - // TODO: [Note: empty token?] - // - // The original code was parsing an token which is never going - // to be present in the response, so effectively was always - // setting token to undefined. So this works, but is it needed? - token: undefined, - encryptedToken, - }); + await resetSavedLocalUserTokens(id, encryptedToken); + updateSavedLocalUser({ twoFactorSessionID: undefined }); saveKeyAttributes(keyAttributes); await router.push(unstashRedirect() ?? "/credentials"); } catch (e) { diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 52f39ab7ec..b3cef840a1 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,9 +1,11 @@ import { - getData, saveKeyAttributes, - setLSUser, + updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; -import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; +import { + resetSavedLocalUserTokens, + TwoFactorAuthorizationResponse, +} from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; import { encryptBox, generateKey } from "ente-base/crypto"; import { @@ -260,7 +262,8 @@ export const saveCredentialsAndNavigateTo = async ( const { id, encryptedToken, keyAttributes } = response; - await setLSUser({ ...getData("user"), encryptedToken, id }); + await resetSavedLocalUserTokens(id, encryptedToken); + updateSavedLocalUser({ passkeySessionID: undefined }); saveKeyAttributes(keyAttributes); return unstashRedirect() ?? "/credentials"; diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 23ecf7b599..1729bce23b 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -1,12 +1,13 @@ import { getData, + replaceSavedLocalUser, savedKeyAttributes, savedLocalUser, + savedPartialLocalUser, saveKeyAttributes, saveSRPAttributes, setLSUser, updateSavedLocalUser, - type PartialLocalUser, } from "ente-accounts/services/accounts-db"; import { generateSRPSetupAttributes, @@ -15,12 +16,14 @@ import { type UpdatedKeyAttr, } from "ente-accounts/services/srp"; import { + boxSealOpenBytes, decryptBox, deriveInteractiveKey, deriveSensitiveKey, encryptBox, generateKey, generateKeyPair, + toB64URLSafe, } from "ente-base/crypto"; import { isDevBuild } from "ente-base/env"; import { @@ -33,15 +36,12 @@ import { ensureMasterKeyFromSession, saveMasterKeyInSessionAndSafeStore, } from "ente-base/session"; -import { savedAuthToken } from "ente-base/token"; +import { removeAuthToken, savedAuthToken } from "ente-base/token"; import { ensure } from "ente-utils/ensure"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; import { getUserRecoveryKey, recoveryKeyFromMnemonic } from "./recovery-key"; -// TODO(RE): Temporary re-export -export type { PartialLocalUser }; - /** * The locally persisted data we have about the user after they've logged in. * @@ -738,6 +738,74 @@ export const changePassword = async (password: string) => { } }; +/** + * Update the {@link encryptedToken} present in the saved partial local user. + * + * This function removes the {@link token}, if any, present in the saved partial + * local user and sets the provided {@link encryptedToken}. + * + * It is expected that the code will subsequently redirect to "/credentials", + * which should call {@link decryptAndStoreToken} which will decrypt the newly + * set {@link encryptedToken} and write out the decrypted value as the + * {@link token} in the saved local user. + * + * @param userID The ID of the user whose token this is. This is used as a + * sanity check to ensure that the we do not overwrite saved partial local user + * data for a different user. + * + * @param encryptedToken The newly obtained base64 encoded encrypted token from + * remote (e.g. as a result of the user verifying their email). + */ +export const resetSavedLocalUserTokens = async ( + userID: number, + encryptedToken: string, +) => { + const user = savedPartialLocalUser(); + if (user?.id && user.id != userID) { + throw new Error(`User ID mismatch (${user.id}, ${userID})`); + } + replaceSavedLocalUser({ ...user, token: undefined, encryptedToken }); + return removeAuthToken(); +}; + +/** + * Decrypt the user's {@link encryptedToken}, if present, and use it to update + * both the locally saved user and the KV DB. + * + * @param keyAttributes The user's key attributes. + * + * @param masterKey The user's master key (base64 encoded). + */ +export const decryptAndStoreToken = async ( + keyAttributes: KeyAttributes, + masterKey: string, +) => { + const user = getData("user"); + const { encryptedToken } = user; + + if (encryptedToken && encryptedToken.length > 0) { + const { encryptedSecretKey, secretKeyDecryptionNonce, publicKey } = + keyAttributes; + const privateKey = await decryptBox( + { + encryptedData: encryptedSecretKey, + nonce: secretKeyDecryptionNonce, + }, + masterKey, + ); + + const decryptedToken = await toB64URLSafe( + await boxSealOpenBytes(encryptedToken, { publicKey, privateKey }), + ); + + await setLSUser({ + ...user, + token: decryptedToken, + encryptedToken: null, + }); + } +}; + const TwoFactorSecret = z.object({ /** * The 2FA secret code. @@ -958,12 +1026,11 @@ export const recoverTwoFactorFinish = async ( sessionID, twoFactorSecret, ); - await setLSUser({ - ...getData("user"), - id, + await resetSavedLocalUserTokens(id, encryptedToken); + updateSavedLocalUser({ isTwoFactorEnabled: undefined, - encryptedToken, - token: undefined, + twoFactorSessionID: undefined, + passkeySessionID: undefined, }); saveKeyAttributes(keyAttributes); }; diff --git a/web/packages/accounts/utils/helpers.ts b/web/packages/accounts/utils/helpers.ts deleted file mode 100644 index 9a980587f4..0000000000 --- a/web/packages/accounts/utils/helpers.ts +++ /dev/null @@ -1,35 +0,0 @@ -// TODO: Audit this file, this can be better. e.g. do we need the Object.assign? - -import { getData, setLSUser } from "ente-accounts/services/accounts-db"; -import { type KeyAttributes } from "ente-accounts/services/user"; -import { boxSealOpenBytes, decryptBox, toB64URLSafe } from "ente-base/crypto"; - -export async function decryptAndStoreToken( - keyAttributes: KeyAttributes, - masterKey: string, -) { - const user = getData("user"); - const { encryptedToken } = user; - - if (encryptedToken && encryptedToken.length > 0) { - const { encryptedSecretKey, secretKeyDecryptionNonce, publicKey } = - keyAttributes; - const privateKey = await decryptBox( - { - encryptedData: encryptedSecretKey, - nonce: secretKeyDecryptionNonce, - }, - masterKey, - ); - - const decryptedToken = await toB64URLSafe( - await boxSealOpenBytes(encryptedToken, { publicKey, privateKey }), - ); - - await setLSUser({ - ...user, - token: decryptedToken, - encryptedToken: null, - }); - } -} diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index 1486c31952..0d94ae0472 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -154,13 +154,6 @@ export const updateSessionFromElectronSafeStorageIfNeeded = async () => { } }; -/** - * Return true if we both have a usable user's master key in session storage, - * and their auth token in KV DB. - */ -export const haveAuthenticatedSession = async () => - (await masterKeyFromSession()) && !!(await savedAuthToken()); - /** * Save the user's encypted key encryption key ("key") in session store * temporarily, until we get back here after completing the second factor. diff --git a/web/packages/base/token.ts b/web/packages/base/token.ts index 87e6626118..109322b339 100644 --- a/web/packages/base/token.ts +++ b/web/packages/base/token.ts @@ -1,4 +1,4 @@ -import { getKVS, setKV } from "./kv"; +import { getKVS, removeKV, setKV } from "./kv"; /** * Return the user's auth token, or throw an error. @@ -33,3 +33,10 @@ export const savedAuthToken = () => getKVS("token"); * This is the setter corresponding to {@link savedAuthToken}. */ export const saveAuthToken = (token: string) => setKV("token", token); + +/** + * Remove the user's auth token from KV DB. + * + * See {@link saveAuthToken}. + */ +export const removeAuthToken = () => removeKV("token"); From 7dabd9545eba4e81fd03c0ea0baed06b9c025af7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 09:21:19 +0530 Subject: [PATCH 032/109] Fin --- web/packages/accounts/pages/recover.tsx | 3 +- web/packages/accounts/services/accounts-db.ts | 94 +++---------------- web/packages/accounts/services/user.ts | 60 +++++------- web/packages/base/session.ts | 1 - 4 files changed, 36 insertions(+), 122 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 23c789d475..104709b91b 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -10,8 +10,7 @@ import { import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import type { KeyAttributes } from "ente-accounts/services/user"; -import { sendOTT } from "ente-accounts/services/user"; -import { decryptAndStoreToken } from "ente-accounts/utils/helpers"; +import { decryptAndStoreToken, sendOTT } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { SingleInputForm, diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index eda8fe69e8..d2af23aee8 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -17,8 +17,6 @@ * - "srpAttributes" */ -import { getKVS, removeKV, setKV } from "ente-base/kv"; -import log from "ente-base/log"; import { savedAuthToken } from "ente-base/token"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; @@ -123,7 +121,9 @@ const LocalUser = z.object({ export const savedPartialLocalUser = (): PartialLocalUser | undefined => { const jsonString = localStorage.getItem("user"); if (!jsonString) return undefined; - return PartialLocalUser.parse(JSON.parse(jsonString)); + const result = PartialLocalUser.parse(JSON.parse(jsonString)); + void ensureTokensMatch(result); + return result; }; /** @@ -133,10 +133,6 @@ export const savedPartialLocalUser = (): PartialLocalUser | undefined => { * * This method replaces the existing data. Use {@link updateSavedLocalUser} to * update selected fields while keeping the other fields as it is. - * - * TODO: WARNING: This does not update the KV token. The idea is to gradually - * move over uses of setLSUser to this while explicitly setting the KV token - * where needed. */ export const replaceSavedLocalUser = (partialLocalUser: PartialLocalUser) => localStorage.setItem("user", JSON.stringify(partialLocalUser)); @@ -150,10 +146,6 @@ export const replaceSavedLocalUser = (partialLocalUser: PartialLocalUser) => * * @param updates A subset of {@link PartialLocalUser} fields that we'd like to * update. The other fields, if present in local storage, remain unchanged. - * - * TODO: WARNING: This does not update the KV token. The idea is to gradually - * move over uses of setLSUser to this while explicitly setting the KV token - * where needed. */ export const updateSavedLocalUser = (updates: Partial) => replaceSavedLocalUser({ ...savedPartialLocalUser(), ...updates }); @@ -177,84 +169,20 @@ export const savedLocalUser = (): LocalUser | undefined => { if (!jsonString) return undefined; // We might have some data, but not all of it. So do a non-throwing parse. const { success, data } = LocalUser.safeParse(JSON.parse(jsonString)); + if (success) void ensureTokensMatch(data); return success ? data : undefined; }; -export type LocalStorageKey = "user"; - -export const getData = (key: LocalStorageKey) => { - try { - if ( - typeof localStorage == "undefined" || - typeof key == "undefined" || - typeof localStorage.getItem(key) == "undefined" || - localStorage.getItem(key) == "undefined" - ) { - return null; - } - const data = localStorage.getItem(key); - return data && JSON.parse(data); - } catch (e) { - log.error(`Failed to Parse JSON for key ${key}`, e); - } -}; - -export const setData = (key: LocalStorageKey, value: object) => - localStorage.setItem(key, JSON.stringify(value)); - -// TODO: Migrate this to `local-user.ts`, with (a) more precise optionality -// indication of the constituent fields, (b) moving any fields that need to be -// accessed from web workers to KV DB. -// -// Creating a new function here to act as a funnel point. -export const setLSUser = async (user: object) => { - await migrateKVToken(user); - setData("user", user); -}; - /** - * Update the "token" KV with the token (if any) for the given {@link user}. + * Sanity check to ensure that KV token and local storage token are the same. * - * This is an internal implementation details of {@link setLSUser} and doesn't - * need to exposed conceptually. For now though, we need to call this externally - * at an early point in the app startup to also copy over the token into KV DB - * for existing users. - * - * This was added 1 July 2024, can be removed after a while and this code - * inlined into `setLSUser` (tag: Migration). + * TODO: Added July 2025, can just be removed soon, there is already a sanity + * check `isLocalStorageAndIndexedDBMismatch` on app start (tag: Migration). */ -export const migrateKVToken = async (user: unknown) => { - // Throw an error if the data is in local storage but not in IndexedDB. This - // is a pre-cursor to inlining this code. - // TODO: Remove this sanity check eventually when this code is revisited. - const oldLSUser = getData("user"); - const wasMissing = - oldLSUser && - typeof oldLSUser == "object" && - "token" in oldLSUser && - typeof oldLSUser.token == "string" && - !(await getKVS("token")); - - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - user && - typeof user == "object" && - "id" in user && - typeof user.id == "number" - ? await setKV("userID", user.id) - : await removeKV("userID"); - - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - user && - typeof user == "object" && - "token" in user && - typeof user.token == "string" - ? await setKV("token", user.token) - : await removeKV("token"); - - if (wasMissing) - throw new Error( - "The user's token was present in local storage but not in IndexedDB", - ); +export const ensureTokensMatch = async (user: PartialLocalUser | undefined) => { + if (user?.token !== (await savedAuthToken())) { + throw new Error("Token mismatch"); + } }; /** diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 1729bce23b..8c00ddf100 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -1,12 +1,10 @@ import { - getData, replaceSavedLocalUser, savedKeyAttributes, savedLocalUser, savedPartialLocalUser, saveKeyAttributes, saveSRPAttributes, - setLSUser, updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; import { @@ -25,18 +23,19 @@ import { generateKeyPair, toB64URLSafe, } from "ente-base/crypto"; -import { isDevBuild } from "ente-base/env"; import { authenticatedRequestHeaders, ensureOk, publicRequestHeaders, } from "ente-base/http"; +import log from "ente-base/log"; import { apiURL } from "ente-base/origins"; +import { ensureMasterKeyFromSession } from "ente-base/session"; import { - ensureMasterKeyFromSession, - saveMasterKeyInSessionAndSafeStore, -} from "ente-base/session"; -import { removeAuthToken, savedAuthToken } from "ente-base/token"; + removeAuthToken, + saveAuthToken, + savedAuthToken, +} from "ente-base/token"; import { ensure } from "ente-utils/ensure"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; @@ -730,12 +729,6 @@ export const changePassword = async (password: string) => { { ...keyAttributes, ...updatedKeyAttr }, masterKey, ); - - // TODO(RE): This shouldn't be needed, remove me. As a soft remove, - // disabling it for dev builds. (tag: Migration) - if (!isDevBuild) { - await saveMasterKeyInSessionAndSafeStore(masterKey); - } }; /** @@ -780,30 +773,25 @@ export const decryptAndStoreToken = async ( keyAttributes: KeyAttributes, masterKey: string, ) => { - const user = getData("user"); - const { encryptedToken } = user; - - if (encryptedToken && encryptedToken.length > 0) { - const { encryptedSecretKey, secretKeyDecryptionNonce, publicKey } = - keyAttributes; - const privateKey = await decryptBox( - { - encryptedData: encryptedSecretKey, - nonce: secretKeyDecryptionNonce, - }, - masterKey, - ); - - const decryptedToken = await toB64URLSafe( - await boxSealOpenBytes(encryptedToken, { publicKey, privateKey }), - ); - - await setLSUser({ - ...user, - token: decryptedToken, - encryptedToken: null, - }); + const { encryptedToken } = savedPartialLocalUser() ?? {}; + if (!encryptedToken) { + log.info("Skipping token decryption (no encrypted token found)"); + return; } + + const { encryptedSecretKey, secretKeyDecryptionNonce, publicKey } = + keyAttributes; + const privateKey = await decryptBox( + { encryptedData: encryptedSecretKey, nonce: secretKeyDecryptionNonce }, + masterKey, + ); + + const token = await toB64URLSafe( + await boxSealOpenBytes(encryptedToken, { publicKey, privateKey }), + ); + + updateSavedLocalUser({ token, encryptedToken: undefined }); + return saveAuthToken(token); }; const TwoFactorSecret = z.object({ diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index 0d94ae0472..077e6d3271 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -1,7 +1,6 @@ import { z } from "zod/v4"; import { decryptBox, encryptBox, generateKey } from "./crypto"; import log from "./log"; -import { savedAuthToken } from "./token"; /** * Remove all data stored in session storage (data tied to the browser tab). From a2072c022c3ab1285aa5979656c3db93cfdf899d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 09:36:11 +0530 Subject: [PATCH 033/109] Don't add a history entry for automated client routing redirects --- web/packages/accounts/components/LoginContents.tsx | 10 +++++++--- web/packages/accounts/pages/change-password.tsx | 2 +- web/packages/accounts/pages/credentials.tsx | 8 ++++---- web/packages/accounts/pages/generate.tsx | 6 +++--- web/packages/accounts/pages/login.tsx | 2 +- web/packages/accounts/pages/passkeys/finish.tsx | 2 +- web/packages/accounts/pages/recover.tsx | 8 ++++---- web/packages/accounts/pages/signup.tsx | 2 +- web/packages/accounts/pages/two-factor/recover.tsx | 4 ++-- web/packages/accounts/pages/two-factor/verify.tsx | 4 ++-- web/packages/accounts/pages/verify.tsx | 2 +- 11 files changed, 27 insertions(+), 23 deletions(-) diff --git a/web/packages/accounts/components/LoginContents.tsx b/web/packages/accounts/components/LoginContents.tsx index f3107993f5..8b4b471d08 100644 --- a/web/packages/accounts/components/LoginContents.tsx +++ b/web/packages/accounts/components/LoginContents.tsx @@ -18,10 +18,14 @@ import { z } from "zod/v4"; import { AccountsPageTitleWithCaption } from "./LoginComponents"; interface LoginContentsProps { - /** Called when the user clicks the signup option instead. */ - onSignUp: () => void; - /** Reactive value of {@link customAPIHost}. */ + /** + * Reactive value of {@link customAPIHost}. + */ host: string | undefined; + /** + * Called when the user clicks the signup option instead. + */ + onSignUp: () => void; } /** diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index c05ed5f527..88b67b42a9 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -38,7 +38,7 @@ const Page: React.FC = () => { setUser(user); } else { stashRedirect("/change-password"); - void router.push("/"); + void router.replace("/"); } }, [router]); diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 66cbd2680a..17d4f6afd4 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -161,13 +161,13 @@ const Page: React.FC = () => { const user = savedPartialLocalUser(); const userEmail = user?.email; if (!userEmail) { - void router.push("/"); + await router.replace("/"); return; } await updateSessionFromElectronSafeStorageIfNeeded(); if ((await masterKeyFromSession()) && (await savedAuthToken())) { - void router.push(appHomeRoute); + await router.replace(appHomeRoute); return; } @@ -203,7 +203,7 @@ const Page: React.FC = () => { // TODO(RE): Why? For now, add a dev mode circuit breaker. if (isDevBuild) throw new Error("Unexpected case reached"); clearLocalStorage(); - void router.push("/"); + void router.replace("/"); return; } setKeyAttributes(keyAttributes); @@ -219,7 +219,7 @@ const Page: React.FC = () => { return; } - void router.push("/"); + void router.replace("/"); })(); }, [router, validateSession, postVerification]); diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index c80fa3db7f..d0b900dc45 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -55,15 +55,15 @@ const Page: React.FC = () => { useEffect(() => { const user = savedPartialLocalUser(); if (!user?.email || !user?.token) { - void router.push("/"); + void router.replace("/"); } else if (haveMasterKeyInSession()) { if (savedJustSignedUp()) { setOpenRecoveryKey(true); } else { - void router.push(appHomeRoute); + void router.replace(appHomeRoute); } } else if (savedOriginalKeyAttributes()) { - void router.push("/credentials"); + void router.replace("/credentials"); } else { setUserEmail(user.email); } diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index 9aab2d2105..bfa311a03a 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -143,7 +143,7 @@ const Page: React.FC = () => { useEffect(() => { void customAPIHost().then(setHost); - if (savedPartialLocalUser()?.email) void router.push("/verify"); + if (savedPartialLocalUser()?.email) void router.replace("/verify"); setLoading(false); }, [router]); diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index 6d25ba5ba5..f695012368 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -37,7 +37,7 @@ const Page: React.FC = () => { if (!passkeySessionID || !response) return; void saveQueryCredentialsAndNavigateTo(passkeySessionID, response).then( - (slug) => router.push(slug), + (slug) => router.replace(slug), ); }, [router]); diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 104709b91b..aaace99bc6 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -46,22 +46,22 @@ const Page: React.FC = () => { void (async () => { const user = savedPartialLocalUser(); if (!user?.email) { - await router.push("/"); + await router.replace("/"); return; } if (!user.encryptedToken && !user.token) { await sendOTT(user.email, undefined); stashRedirect("/recover"); - await router.push("/verify"); + await router.replace("/verify"); return; } const keyAttributes = savedKeyAttributes(); if (!keyAttributes) { - await router.push("/generate"); + await router.replace("/generate"); } else if (haveMasterKeyInSession()) { - await router.push(appHomeRoute); + await router.replace(appHomeRoute); } else { setKeyAttributes(keyAttributes); } diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index cff9575c76..5d06da4dad 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -19,7 +19,7 @@ const Page: React.FC = () => { useEffect(() => { void customAPIHost().then(setHost); - if (savedPartialLocalUser()?.email) void router.push("/verify"); + if (savedPartialLocalUser()?.email) void router.replace("/verify"); setLoading(false); }, [router]); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 6b17dfec6d..c92097ca16 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -71,9 +71,9 @@ const Page: React.FC = ({ twoFactorType }) => { ? user?.passkeySessionID : user?.twoFactorSessionID; if (!user?.email || !sessionID) { - await router.push("/"); + await router.replace("/"); } else if (user.encryptedToken || user.token) { - await router.push("/generate"); + await router.replace("/generate"); } else { setSessionID(sessionID); try { diff --git a/web/packages/accounts/pages/two-factor/verify.tsx b/web/packages/accounts/pages/two-factor/verify.tsx index f56c186f8f..e0038ef9ae 100644 --- a/web/packages/accounts/pages/two-factor/verify.tsx +++ b/web/packages/accounts/pages/two-factor/verify.tsx @@ -36,12 +36,12 @@ const Page: React.FC = () => { useEffect(() => { const user = savedPartialLocalUser(); if (!user?.email || !user.twoFactorSessionID) { - void router.push("/"); + void router.replace("/"); } else if ( !user.isTwoFactorEnabled && (user.encryptedToken || user.token) ) { - void router.push("/credentials"); + void router.replace("/credentials"); } else { setTwoFactorSessionID(user.twoFactorSessionID); } diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 3817b4d1fa..6a1806c41e 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -76,7 +76,7 @@ const Page: React.FC = () => { useEffect(() => { void redirectionIfNeededOrEmail().then((redirectOrEmail) => { if (typeof redirectOrEmail == "string") { - void router.push(redirectOrEmail); + void router.replace(redirectOrEmail); } else { setEmail(redirectOrEmail.email); } From 94b4c6b0bb1e517c261185123b1478fa726375ad Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 09:42:42 +0530 Subject: [PATCH 034/109] Touchups Writing the ID is necessary, the first time we get is when e.g. the pk is verified. --- web/apps/photos/src/pages/_app.tsx | 4 +- web/packages/accounts/pages/credentials.tsx | 4 +- .../accounts/pages/passkeys/finish.tsx | 3 +- web/packages/accounts/pages/recover.tsx | 7 ++- .../accounts/pages/two-factor/recover.tsx | 4 +- web/packages/accounts/services/passkey.ts | 12 ++++- web/packages/accounts/services/user.ts | 45 ++++++++++--------- 7 files changed, 50 insertions(+), 29 deletions(-) diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 9547992de3..5458061407 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -131,7 +131,9 @@ const App: React.FC = ({ Component, pageProps }) => { redirectToFamilyPortal(); router.events.on("routeChangeStart", (url) => { - if (process.env.NEXT_PUBLIC_ENTE_TRACE) console.log("route", url); + if (process.env.NEXT_PUBLIC_ENTE_TRACE_RT) { + log.debug(() => ["route", url]); + } if (needsFamilyRedirect && savedPartialLocalUser()?.token) { redirectToFamilyPortal(); diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 17d4f6afd4..6f5b710261 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -39,7 +39,7 @@ import { verifySRP, } from "ente-accounts/services/srp"; import { - decryptAndStoreToken, + decryptAndStoreTokenIfNeeded, generateAndSaveInteractiveKeyAttributes, type KeyAttributes, } from "ente-accounts/services/user"; @@ -137,7 +137,7 @@ const Page: React.FC = () => { keyAttributes: KeyAttributes, ) => { await saveMasterKeyInSessionAndSafeStore(masterKey); - await decryptAndStoreToken(keyAttributes, masterKey); + await decryptAndStoreTokenIfNeeded(keyAttributes, masterKey); try { let srpAttributes = savedSRPAttributes(); if (!srpAttributes) { diff --git a/web/packages/accounts/pages/passkeys/finish.tsx b/web/packages/accounts/pages/passkeys/finish.tsx index f695012368..7a1f6ddcf8 100644 --- a/web/packages/accounts/pages/passkeys/finish.tsx +++ b/web/packages/accounts/pages/passkeys/finish.tsx @@ -2,6 +2,7 @@ import { saveKeyAttributes, updateSavedLocalUser, } from "ente-accounts/services/accounts-db"; +import { clearInflightPasskeySessionID } from "ente-accounts/services/passkey"; import { unstashRedirect } from "ente-accounts/services/redirect"; import { resetSavedLocalUserTokens, @@ -85,7 +86,7 @@ const saveQueryCredentialsAndNavigateTo = async ( return "/"; } - sessionStorage.removeItem("inflightPasskeySessionID"); + clearInflightPasskeySessionID(); // Decode response string (inverse of the steps we perform in // `passkeyAuthenticationSuccessRedirectURL`). diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index aaace99bc6..ac4b791e7c 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -10,7 +10,10 @@ import { import { recoveryKeyFromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import type { KeyAttributes } from "ente-accounts/services/user"; -import { decryptAndStoreToken, sendOTT } from "ente-accounts/services/user"; +import { + decryptAndStoreTokenIfNeeded, + sendOTT, +} from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { SingleInputForm, @@ -81,7 +84,7 @@ const Page: React.FC = () => { await recoveryKeyFromMnemonic(recoveryKeyMnemonic), ); await saveMasterKeyInSessionAndSafeStore(masterKey); - await decryptAndStoreToken(keyAttr, masterKey); + await decryptAndStoreTokenIfNeeded(keyAttr, masterKey); void router.push("/change-password?op=reset"); } catch (e) { diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index c92097ca16..42e9b9f9df 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -6,7 +6,7 @@ import { } from "ente-accounts/components/layouts/centered-paper"; import { savedPartialLocalUser } from "ente-accounts/services/accounts-db"; import { - recoverTwoFactor, + getRecoverTwoFactor, recoverTwoFactorFinish, type TwoFactorRecoveryResponse, type TwoFactorType, @@ -78,7 +78,7 @@ const Page: React.FC = ({ twoFactorType }) => { setSessionID(sessionID); try { setRecoveryResponse( - await recoverTwoFactor(twoFactorType, sessionID), + await getRecoverTwoFactor(twoFactorType, sessionID), ); } catch (e) { log.error("Second factor recovery page setup failed", e); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index b3cef840a1..0dce3b22bf 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -258,7 +258,7 @@ export const saveCredentialsAndNavigateTo = async ( // goes through the passkey flow in the browser itself (when they are using // the web app). - sessionStorage.removeItem("inflightPasskeySessionID"); + clearInflightPasskeySessionID(); const { id, encryptedToken, keyAttributes } = response; @@ -268,3 +268,13 @@ export const saveCredentialsAndNavigateTo = async ( return unstashRedirect() ?? "/credentials"; }; + +/** + * Remove the inflight passkey session ID, if any, present in session storage. + * + * This should be called whenever we get back control from the passkey app to + * clean up after ourselves. + */ +export const clearInflightPasskeySessionID = () => { + sessionStorage.removeItem("inflightPasskeySessionID"); +}; diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 8c00ddf100..91b1f1ea5f 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -28,7 +28,6 @@ import { ensureOk, publicRequestHeaders, } from "ente-base/http"; -import log from "ente-base/log"; import { apiURL } from "ente-base/origins"; import { ensureMasterKeyFromSession } from "ente-base/session"; import { @@ -39,6 +38,7 @@ import { import { ensure } from "ente-utils/ensure"; import { nullToUndefined } from "ente-utils/transform"; import { z } from "zod/v4"; +import { clearInflightPasskeySessionID } from "./passkey"; import { getUserRecoveryKey, recoveryKeyFromMnemonic } from "./recovery-key"; /** @@ -732,19 +732,20 @@ export const changePassword = async (password: string) => { }; /** - * Update the {@link encryptedToken} present in the saved partial local user. + * Update the {@link id} and {@link encryptedToken} present in the saved partial + * local user. * * This function removes the {@link token}, if any, present in the saved partial * local user and sets the provided {@link encryptedToken}. * * It is expected that the code will subsequently redirect to "/credentials", - * which should call {@link decryptAndStoreToken} which will decrypt the newly - * set {@link encryptedToken} and write out the decrypted value as the + * which should call {@link decryptAndStoreTokenIfNeeded} which will decrypt the + * newly set {@link encryptedToken} and write out the decrypted value as the * {@link token} in the saved local user. * - * @param userID The ID of the user whose token this is. This is used as a - * sanity check to ensure that the we do not overwrite saved partial local user - * data for a different user. + * @param userID The ID of the user whose token this is. This is also saved to + * the partial local user (after doing a sanity check that we're not replacing + * partial data with a different userID). * * @param encryptedToken The newly obtained base64 encoded encrypted token from * remote (e.g. as a result of the user verifying their email). @@ -757,7 +758,12 @@ export const resetSavedLocalUserTokens = async ( if (user?.id && user.id != userID) { throw new Error(`User ID mismatch (${user.id}, ${userID})`); } - replaceSavedLocalUser({ ...user, token: undefined, encryptedToken }); + replaceSavedLocalUser({ + ...user, + id: userID, + token: undefined, + encryptedToken, + }); return removeAuthToken(); }; @@ -769,15 +775,12 @@ export const resetSavedLocalUserTokens = async ( * * @param masterKey The user's master key (base64 encoded). */ -export const decryptAndStoreToken = async ( +export const decryptAndStoreTokenIfNeeded = async ( keyAttributes: KeyAttributes, masterKey: string, ) => { const { encryptedToken } = savedPartialLocalUser() ?? {}; - if (!encryptedToken) { - log.info("Skipping token decryption (no encrypted token found)"); - return; - } + if (!encryptedToken) return; const { encryptedSecretKey, secretKeyDecryptionNonce, publicKey } = keyAttributes; @@ -954,7 +957,7 @@ export type TwoFactorRecoveryResponse = z.infer< * sends a encrypted recovery secret (see {@link configurePasskeyRecovery}). * * 3. When the user wishes to reset or bypass their second factor, the client - * asks remote for these encrypted secrets (using {@link recoverTwoFactor}). + * asks remote for these encrypted secrets (using {@link getRecoverTwoFactor}). * * 4. User then enters their recovery key, which the client uses to decrypt the * recovery secret and provide it back to remote for verification (using @@ -963,7 +966,7 @@ export type TwoFactorRecoveryResponse = z.infer< * 5. If the recovery secret matches, then remote resets (TOTP based) or bypass * (passkey based) the user's second factor. */ -export const recoverTwoFactor = async ( +export const getRecoverTwoFactor = async ( twoFactorType: TwoFactorType, sessionID: string, ): Promise => { @@ -977,22 +980,23 @@ export const recoverTwoFactor = async ( /** * Finish the second factor recovery / bypass initiated by - * {@link recoverTwoFactor} using the provided recovery key mnemonic entered by - * the user. + * {@link getRecoverTwoFactor} using the provided recovery key mnemonic entered + * by the user. * * See: [Note: Second factor recovery]. * * This completes the recovery process both locally, and on remote. * * @param twoFactorType The second factor type (same value as what would've been - * passed to {@link recoverTwoFactor} for obtaining {@link recoveryResponse}). + * passed to {@link getRecoverTwoFactor} for obtaining + * {@link recoveryResponse}). * * @param sessionID The second factor session ID (same value as what would've - * been passed to {@link recoverTwoFactor} for obtaining + * been passed to {@link getRecoverTwoFactor} for obtaining * {@link recoveryResponse}). * * @param recoveryResponse The response to a previous call to - * {@link recoverTwoFactor}. + * {@link getRecoverTwoFactor}. * * @param recoveryKeyMnemonic The 24-word BIP-39 recovery key mnemonic provided * by the user to complete recovery. @@ -1020,6 +1024,7 @@ export const recoverTwoFactorFinish = async ( twoFactorSessionID: undefined, passkeySessionID: undefined, }); + if (twoFactorType == "passkey") clearInflightPasskeySessionID(); saveKeyAttributes(keyAttributes); }; From 2ffce031de37d4e5ab4a1611e510e96319fb697b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 12:06:46 +0530 Subject: [PATCH 035/109] Ensure SRP attributes are also present after signup --- web/packages/accounts/pages/credentials.tsx | 2 ++ web/packages/accounts/pages/generate.tsx | 4 +++- web/packages/accounts/pages/verify.tsx | 7 ++++++- web/packages/accounts/services/session.ts | 1 - web/packages/accounts/services/srp.ts | 16 ++++++++++++++++ web/packages/accounts/services/user.ts | 3 +++ 6 files changed, 30 insertions(+), 3 deletions(-) diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 6f5b710261..f2fffce221 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -34,6 +34,7 @@ import { checkSessionValidity } from "ente-accounts/services/session"; import type { SRPAttributes } from "ente-accounts/services/srp"; import { generateSRPSetupAttributes, + getAndSaveSRPAttributes, getSRPAttributes, setupSRP, verifySRP, @@ -146,6 +147,7 @@ const Page: React.FC = () => { saveSRPAttributes(srpAttributes); } else { await setupSRP(await generateSRPSetupAttributes(kek)); + await getAndSaveSRPAttributes(userEmail); } } } catch (e) { diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index d0b900dc45..e60f9085cd 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -14,6 +14,7 @@ import { import { appHomeRoute } from "ente-accounts/services/redirect"; import { generateSRPSetupAttributes, + getAndSaveSRPAttributes, setupSRP, } from "ente-accounts/services/srp"; import { @@ -76,6 +77,7 @@ const Page: React.FC = () => { await generateKeysAndAttributes(password); await putUserKeyAttributes(keyAttributes); await setupSRP(await generateSRPSetupAttributes(kek)); + await getAndSaveSRPAttributes(userEmail); await generateAndSaveInteractiveKeyAttributes( password, keyAttributes, @@ -94,7 +96,7 @@ const Page: React.FC = () => { ); } }, - [], + [userEmail], ); return ( diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 6a1806c41e..7ffcffa231 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -28,7 +28,11 @@ import { stashedRedirect, unstashRedirect, } from "ente-accounts/services/redirect"; -import { getSRPAttributes, setupSRP } from "ente-accounts/services/srp"; +import { + getAndSaveSRPAttributes, + getSRPAttributes, + setupSRP, +} from "ente-accounts/services/srp"; import { putUserKeyAttributes, sendOTT, @@ -135,6 +139,7 @@ const Page: React.FC = () => { await putUserKeyAttributes(originalKeyAttributes); } await unstashAfterUseSRPSetupAttributes(setupSRP); + await getAndSaveSRPAttributes(email); } saveIsFirstLogin(); if (keyAttributes) { diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index 8bfbc856eb..c2e6f76f35 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -96,7 +96,6 @@ export const checkSessionValidity = async (): Promise => { // We should have these values locally if we reach here. const email = ensureLocalUser().email; - // TODO(RE): This is not being set until initial reauthentication. const localSRPAttributes = savedSRPAttributes()!; // Fetch the remote SRP attributes. diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index 7e3c1bf209..07930e5780 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -9,9 +9,11 @@ import { publicRequestHeaders, } from "ente-base/http"; import { apiURL } from "ente-base/origins"; +import { ensure } from "ente-utils/ensure"; import { SRP, SrpClient } from "fast-srp-hap"; import { v4 as uuidv4 } from "uuid"; import { z } from "zod/v4"; +import { saveSRPAttributes } from "./accounts-db"; import { RemoteSRPVerificationResponse, type EmailOrSRPVerificationResponse, @@ -440,6 +442,20 @@ const completeSRPSetup = async ( return CompleteSRPSetupResponse.parse(await res.json()); }; +/** + * Fetch the SRP attributes from remote and use them to update the SRP + * attributes we have saved locally. + * + * This function is intended to be called after {@link srpSetupOrReconfigure} to + * also update our local state to match remote. + * + * @param userEmail The email of the user whose SRP attributes we want to fetch. + * This should be the email address of the logged in user, or the user who is + * going through the login / signup sequence currently. + */ +export const getAndSaveSRPAttributes = async (userEmail: string) => + saveSRPAttributes(ensure(await getSRPAttributes(userEmail))); + /** * The subset of {@link KeyAttributes} that get updated when the user changes * their password. diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 91b1f1ea5f..9f66a92f6f 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -9,6 +9,7 @@ import { } from "ente-accounts/services/accounts-db"; import { generateSRPSetupAttributes, + getAndSaveSRPAttributes, getSRPAttributes, updateSRPAndKeyAttributes, type UpdatedKeyAttr, @@ -719,6 +720,8 @@ export const changePassword = async (password: string) => { ); // Update SRP attributes locally. + await getAndSaveSRPAttributes(user.email); + const srpAttributes = await getSRPAttributes(user.email); saveSRPAttributes(ensure(srpAttributes)); From 9d87560d5cc327917e6c538ff2871abcd2805504 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 12:16:56 +0530 Subject: [PATCH 036/109] Lint --- web/packages/accounts/eslint.config.mjs | 1 - web/packages/accounts/pages/credentials.tsx | 4 ++-- web/packages/accounts/pages/generate.tsx | 2 +- web/packages/accounts/pages/two-factor/setup.tsx | 6 +++--- web/packages/accounts/services/accounts-db.ts | 4 ++-- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/web/packages/accounts/eslint.config.mjs b/web/packages/accounts/eslint.config.mjs index f5408898a9..cc992f4f56 100644 --- a/web/packages/accounts/eslint.config.mjs +++ b/web/packages/accounts/eslint.config.mjs @@ -5,7 +5,6 @@ export default [ { rules: { /* TODO: */ - "@typescript-eslint/no-unnecessary-condition": "off", "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-member-access": "off", diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index f2fffce221..2fa37c73c4 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -201,7 +201,7 @@ const Page: React.FC = () => { // Reauthentication in a new tab on the web app. Use previously // generated interactive key attributes to verify password. if (keyAttributes) { - if (!user?.token && !user?.encryptedToken) { + if (!user.token && !user.encryptedToken) { // TODO(RE): Why? For now, add a dev mode circuit breaker. if (isDevBuild) throw new Error("Unexpected case reached"); clearLocalStorage(); @@ -339,7 +339,7 @@ const Page: React.FC = () => { return ( openPasskeyVerificationURL(passkeyVerificationData) } diff --git a/web/packages/accounts/pages/generate.tsx b/web/packages/accounts/pages/generate.tsx index e60f9085cd..6d8da16bb1 100644 --- a/web/packages/accounts/pages/generate.tsx +++ b/web/packages/accounts/pages/generate.tsx @@ -55,7 +55,7 @@ const Page: React.FC = () => { useEffect(() => { const user = savedPartialLocalUser(); - if (!user?.email || !user?.token) { + if (!user?.email || !user.token) { void router.replace("/"); } else if (haveMasterKeyInSession()) { if (savedJustSignedUp()) { diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index 135af20f68..0c20ce93c9 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -124,12 +124,12 @@ const SetupQRMode: React.FC = ({ {t("two_factor_qr_help")} - {!twoFactorSecret ? ( + {twoFactorSecret ? ( + + ) : ( - ) : ( - )} {t("two_factor_manual_entry_title")} diff --git a/web/packages/accounts/services/accounts-db.ts b/web/packages/accounts/services/accounts-db.ts index d2af23aee8..effb260d5d 100644 --- a/web/packages/accounts/services/accounts-db.ts +++ b/web/packages/accounts/services/accounts-db.ts @@ -340,7 +340,7 @@ export const savedIsFirstLogin = () => { const jsonString = localStorage.getItem("isFirstLogin"); if (!jsonString) return false; try { - return z.boolean().parse(JSON.parse(jsonString)) ?? false; + return z.boolean().parse(JSON.parse(jsonString)); } catch { return ( LocalLegacyBooleanFlag.parse(JSON.parse(jsonString)).status ?? false @@ -385,7 +385,7 @@ export const savedJustSignedUp = () => { const jsonString = localStorage.getItem("justSignedUp"); if (!jsonString) return false; try { - return z.boolean().parse(JSON.parse(jsonString)) ?? false; + return z.boolean().parse(JSON.parse(jsonString)); } catch { return ( LocalLegacyBooleanFlag.parse(JSON.parse(jsonString)).status ?? false From 093624a6ee4e6638364e30592ec17d76403fe359 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 12:28:08 +0530 Subject: [PATCH 037/109] Rest --- web/packages/accounts/eslint.config.mjs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/web/packages/accounts/eslint.config.mjs b/web/packages/accounts/eslint.config.mjs index cc992f4f56..1919c2b05d 100644 --- a/web/packages/accounts/eslint.config.mjs +++ b/web/packages/accounts/eslint.config.mjs @@ -1,14 +1 @@ -import config from "ente-build-config/eslintrc-react.mjs"; - -export default [ - ...config, - { - rules: { - /* TODO: */ - "@typescript-eslint/no-unsafe-assignment": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-argument": "off", - }, - }, -]; +export { default } from "ente-build-config/eslintrc-react.mjs"; From e64d66a32016f6ab147806c3cef77f40e30071ac Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 12:34:59 +0530 Subject: [PATCH 038/109] More lints --- web/apps/photos/eslint.config.mjs | 10 -------- .../src/components/Collections/AllAlbums.tsx | 2 +- .../Collections/CollectionShare.tsx | 2 +- web/apps/photos/src/components/FileList.tsx | 2 ++ web/apps/photos/src/components/Upload.tsx | 23 +++++++++++-------- .../photos/src/components/WatchFolder.tsx | 7 +++--- web/apps/photos/src/pages/gallery.tsx | 2 +- web/apps/photos/src/pages/shared-albums.tsx | 2 ++ .../photos/src/services/upload-manager.ts | 8 +++---- web/apps/photos/src/services/watch.ts | 7 +++--- web/apps/photos/tests/upload.test.ts | 7 ++++-- .../albums/components/UploaderNameInput.tsx | 2 +- 12 files changed, 36 insertions(+), 38 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index acc4236012..b8709f6cd3 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -19,18 +19,8 @@ export default [ "@typescript-eslint/no-unsafe-call": "off", /** TODO: Disabled as we migrate, try to prune these again */ "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-unsafe-enum-comparison": "off", "@typescript-eslint/no-unnecessary-type-assertion": "off", - "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/prefer-promise-reject-errors": "off", - "@typescript-eslint/no-useless-constructor": "off", - "@typescript-eslint/require-await": "off", - "@typescript-eslint/no-misused-promises": "off", - "@typescript-eslint/restrict-template-expressions": "off", - "@typescript-eslint/no-inferrable-types": "off", - "@typescript-eslint/no-base-to-string": "off", - "@typescript-eslint/restrict-plus-operands": "off", - "@typescript-eslint/no-unused-expressions": "off", "react-hooks/exhaustive-deps": "off", "react-refresh/only-export-components": "off", }, diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index ca2a032a35..11aa4d46e7 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -211,7 +211,7 @@ const AllAlbumsContent: React.FC = ({ if (!collectionSummaries) { return; } - const main = async () => { + const main = () => { if (refreshInProgress.current) { shouldRefresh.current = true; return; diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 0bd3871d59..9986932cb9 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -1673,7 +1673,7 @@ const ManageLinkPassword: React.FC = ({ const { show: showSetPassword, props: setPasswordVisibilityProps } = useModalVisibility(); - const handlePasswordChangeSetting = async () => { + const handlePasswordChangeSetting = () => { if (publicURL.passwordEnabled) { showMiniDialog({ title: t("disable_password"), diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 4a1c497f59..e946d9dab7 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -1,3 +1,5 @@ +// TODO: Audit this file +/* eslint-disable @typescript-eslint/restrict-plus-operands */ import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined"; import FavoriteRoundedIcon from "@mui/icons-material/FavoriteRounded"; import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined"; diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index fe9ec555f7..3343f26b01 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -567,6 +567,7 @@ export const Upload: React.FC = ({ } } + // eslint-disable-next-line @typescript-eslint/no-empty-function let showNextModal = () => {}; if (importSuggestion.hasNestedFolders) { showNextModal = () => setOpenCollectionMappingChoice(true); @@ -586,7 +587,7 @@ export const Upload: React.FC = ({ })(); }, [webFiles, desktopFiles, desktopFilePaths, desktopZipItems]); - const preCollectionCreationAction = async () => { + const preCollectionCreationAction = () => { onCloseCollectionSelector?.(); props.setShouldDisableDropzone(uploadManager.isUploadInProgress()); setUploadPhase("preparing"); @@ -597,7 +598,7 @@ export const Upload: React.FC = ({ collection: Collection, uploaderName?: string, ) => { - await preCollectionCreationAction(); + preCollectionCreationAction(); const uploadItemsWithCollection = uploadItemsAndPaths.current.map( ([uploadItem, path], index) => ({ uploadItem, @@ -618,7 +619,7 @@ export const Upload: React.FC = ({ mapping: CollectionMapping, collectionName?: string, ) => { - await preCollectionCreationAction(); + preCollectionCreationAction(); let uploadItemsWithCollection: UploadItemWithCollection[] = []; let collectionNameToUploadItems = new Map< string, @@ -821,7 +822,7 @@ export const Upload: React.FC = ({ } }; - const handlePublicUpload = async (uploaderName: string) => { + const handlePublicUpload = (uploaderName: string) => { savePublicCollectionUploaderName( publicCollectionGalleryContext.credentials.accessToken, uploaderName, @@ -1210,20 +1211,22 @@ const UploadOptions: React.FC = ({ onSelect("folders"); break; case "zips": - !showTakeoutOptions - ? setShowTakeoutOptions(true) - : onSelect("zips"); + if (!showTakeoutOptions) { + setShowTakeoutOptions(true); + } else { + onSelect("zips"); + } break; } }; - return !showTakeoutOptions ? ( + return showTakeoutOptions ? ( + + ) : ( - ) : ( - ); }; diff --git a/web/apps/photos/src/components/WatchFolder.tsx b/web/apps/photos/src/components/WatchFolder.tsx index c88e332776..d403997626 100644 --- a/web/apps/photos/src/components/WatchFolder.tsx +++ b/web/apps/photos/src/components/WatchFolder.tsx @@ -152,7 +152,7 @@ export const WatchFolder: React.FC = ({ interface WatchList { watches: FolderWatch[] | undefined; - removeWatch: (watch: FolderWatch) => void; + removeWatch: (watch: FolderWatch) => Promise; } const WatchList: React.FC = ({ watches, removeWatch }) => @@ -201,13 +201,13 @@ const Check: React.FC = () => ( interface WatchEntryProps { watch: FolderWatch; - removeWatch: (watch: FolderWatch) => void; + removeWatch: (watch: FolderWatch) => Promise; } const WatchEntry: React.FC = ({ watch, removeWatch }) => { const { showMiniDialog } = useBaseContext(); - const confirmStopWatching = () => { + const confirmStopWatching = () => showMiniDialog({ title: t("stop_watching_folder_title"), message: t("stop_watching_folder_message"), @@ -217,7 +217,6 @@ const WatchEntry: React.FC = ({ watch, removeWatch }) => { action: () => removeWatch(watch), }, }); - }; return ( diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index f29869493a..21173e9908 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1337,7 +1337,7 @@ export async function handleSubscriptionCompletionRedirectIfNeeded( ); } } else if (status == "fail") { - log.error(`Subscription purchase failed: ${reason}`); + log.error(`Subscription purchase failed`, reason); switch (reason) { case "canceled": showMiniDialog({ diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 92a49006ab..705ea034c9 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -1,3 +1,5 @@ +// TODO: Audit this file +/* eslint-disable @typescript-eslint/no-unused-expressions */ import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; import CloseIcon from "@mui/icons-material/Close"; import DownloadIcon from "@mui/icons-material/Download"; diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index 9c351f8df7..e5d61f6170 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -101,8 +101,8 @@ class UIService { // UPLOAD LEVEL STATES private uploadPhase: UploadPhase = "preparing"; private filenames = new Map(); - private hasLivePhoto: boolean = false; - private uploadProgressView: boolean = false; + private hasLivePhoto = false; + private uploadProgressView = false; // STAGE LEVEL STATES private perFileProgress: number; @@ -269,7 +269,7 @@ class UploadManager { private uiService = new UIService(); - public async init( + public init( progressUpdater: ProgressUpdater, onUploadFile: (file: EnteFile) => void, publicAlbumsCredentials: PublicAlbumsCredentials | undefined, @@ -571,7 +571,7 @@ class UploadManager { } if (isDesktop && watcher.isUploadRunning()) { - await watcher.onFileUpload(uploadableItem, uploadResult); + watcher.onFileUpload(uploadableItem, uploadResult); } return type == "addedSymlink" ? "uploaded" : type; diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index e9e6270281..38fcf838d1 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -74,6 +74,8 @@ class FolderWatcher { private debouncedRunNextEvent: () => void; constructor() { + // TODO: + // eslint-disable-next-line @typescript-eslint/no-misused-promises this.debouncedRunNextEvent = debounce(() => this.runNextEvent(), 1000); } @@ -319,10 +321,7 @@ class FolderWatcher { * Callback invoked by the uploader whenever a item we requested to * {@link upload} gets uploaded. */ - async onFileUpload( - item: UploadItemWithCollection, - uploadResult: UploadResult, - ) { + onFileUpload(item: UploadItemWithCollection, uploadResult: UploadResult) { // Re the usage of ensureString: For desktop watch, the only possibility // for a UploadItem is for it to be a string (the absolute path to a // file on disk). diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index 4605d65245..ec4812998c 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -1,3 +1,6 @@ +/* eslint-disable @typescript-eslint/no-base-to-string */ +// TODO: Audit this file +/* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/dot-notation */ import { parseDateFromDigitGroups, @@ -163,13 +166,13 @@ export async function testUpload() { await exifDataParsingCheck(expectedState); await fileDimensionExtractionCheck(expectedState); await googleMetadataReadingCheck(expectedState); - await totalFileCountCheck(expectedState); + totalFileCountCheck(expectedState); } catch (e) { console.log(e); } } -async function totalFileCountCheck(expectedState) { +function totalFileCountCheck(expectedState) { const userDetails = userDetailsSnapshot(); if (expectedState.total_file_count === userDetails.fileCount) { console.log("file count check passed ✅"); diff --git a/web/packages/new/albums/components/UploaderNameInput.tsx b/web/packages/new/albums/components/UploaderNameInput.tsx index 7aa31b7b0f..8d951bd0c3 100644 --- a/web/packages/new/albums/components/UploaderNameInput.tsx +++ b/web/packages/new/albums/components/UploaderNameInput.tsx @@ -29,7 +29,7 @@ type UploaderNameInput = ModalVisibilityProps & { /** * Callback invoked when the user presses submit after entering a name. */ - onSubmit: (name: string) => Promise; + onSubmit: (name: string) => void | Promise; }; /** From 6ae0516bd7770649cd0ec449566b914f418b5279 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 13:16:55 +0530 Subject: [PATCH 039/109] More lints and tsc --- web/apps/photos/eslint.config.mjs | 2 -- .../src/components/Collections/AllAlbums.tsx | 10 +++++++--- .../components/Collections/CollectionHeader.tsx | 8 ++++---- .../Collections/GalleryBarAndListHeader.tsx | 4 ++-- web/apps/photos/src/components/FileList.tsx | 6 +++--- .../photos/src/components/FileListWithViewer.tsx | 4 ++-- .../src/components/FilesDownloadProgress.tsx | 2 ++ web/apps/photos/src/components/Sidebar.tsx | 2 +- web/apps/photos/src/components/Upload.tsx | 4 +++- web/apps/photos/src/components/UploadProgress.tsx | 6 +++--- .../photos/src/components/pages/gallery/Avatar.tsx | 2 +- web/apps/photos/src/pages/_app.tsx | 4 ++-- web/apps/photos/src/pages/gallery.tsx | 14 +++++++------- web/apps/photos/src/pages/shared-albums.tsx | 8 ++++---- web/apps/photos/src/services/collectionService.ts | 8 +++++--- web/apps/photos/src/types/gallery/index.ts | 2 +- web/apps/photos/src/utils/collection.ts | 6 +++--- web/apps/photos/src/utils/file/index.ts | 4 ++-- web/apps/photos/src/utils/photoFrame/index.ts | 2 +- .../src/utils/publicCollectionGallery/index.ts | 2 +- web/apps/photos/tests/upload.test.ts | 4 +++- web/apps/photos/tsconfig.json | 8 +------- web/packages/gallery/components/FileInfo.tsx | 4 ++-- .../gallery/components/viewer/data-source.ts | 10 +++++----- web/packages/new/photos/components/Tiles.tsx | 2 +- .../new/photos/components/gallery/BarImpl.tsx | 2 +- 26 files changed, 67 insertions(+), 63 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index b8709f6cd3..76d9b9a84c 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -20,9 +20,7 @@ export default [ /** TODO: Disabled as we migrate, try to prune these again */ "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-unnecessary-type-assertion": "off", - "@typescript-eslint/prefer-promise-reject-errors": "off", "react-hooks/exhaustive-deps": "off", - "react-refresh/only-export-components": "off", }, }, ]; diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index 11aa4d46e7..80c589a0a8 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -18,15 +18,19 @@ import { LargeTileButton, LargeTileTextOverlay, } from "ente-new/photos/components/Tiles"; -import { +import type { CollectionsSortBy, - type CollectionSummary, + CollectionSummary, } from "ente-new/photos/services/collection-summary"; import { t } from "i18next"; import memoize from "memoize-one"; import React, { useEffect, useRef, useState } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; -import { areEqual, FixedSizeList, ListChildComponentProps } from "react-window"; +import { + areEqual, + FixedSizeList, + type ListChildComponentProps, +} from "react-window"; interface AllAlbums { open: boolean; diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index ba532a8c1d..25a44db6eb 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -51,7 +51,7 @@ import { usePhotosAppContext } from "ente-new/photos/types/context"; import { t } from "i18next"; import React, { useCallback, useRef } from "react"; import { Trans } from "react-i18next"; -import { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; +import type { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; import { downloadCollectionHelper, downloadDefaultHiddenCollectionHelper, @@ -221,15 +221,15 @@ const CollectionHeaderOptions: React.FC = ({ setActiveCollectionID(PseudoCollectionID.all); }); - const _downloadCollection = () => { + const _downloadCollection = async () => { if (isActiveCollectionDownloadInProgress()) return; if (collectionSummaryType == "hiddenItems") { - return downloadDefaultHiddenCollectionHelper( + await downloadDefaultHiddenCollectionHelper( setFilesDownloadProgressAttributesCreator, ); } else { - return downloadCollectionHelper( + await downloadCollectionHelper( activeCollection.id, setFilesDownloadProgressAttributesCreator( activeCollection.name, diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 57854c0ce2..d97b5ac30c 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -3,7 +3,7 @@ import { CollectionShare, type CollectionShareProps, } from "components/Collections/CollectionShare"; -import { TimeStampListItem } from "components/FileList"; +import type { TimeStampListItem } from "components/FileList"; import { useModalVisibility } from "ente-base/components/utils/modal"; import type { Collection } from "ente-media/collection"; import { @@ -22,9 +22,9 @@ import { includes } from "ente-utils/type-guards"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { sortCollectionSummaries } from "services/collectionService"; import { - FilesDownloadProgressAttributes, isFilesDownloadCancelled, isFilesDownloadCompleted, + type FilesDownloadProgressAttributes, } from "../FilesDownloadProgress"; import { AlbumCastDialog } from "./AlbumCastDialog"; import { diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index e946d9dab7..46c3375d01 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -12,7 +12,7 @@ import { isSameDay } from "ente-base/date"; import { formattedDateRelative } from "ente-base/i18n-date"; import log from "ente-base/log"; import { downloadManager } from "ente-gallery/services/download"; -import { EnteFile } from "ente-media/file"; +import type { EnteFile } from "ente-media/file"; import { fileCreationTime, fileDurationString } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { @@ -34,10 +34,10 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; import { Trans } from "react-i18next"; import { VariableSizeList as List, - ListChildComponentProps, + type ListChildComponentProps, areEqual, } from "react-window"; -import { SelectedState } from "types/gallery"; +import type { SelectedState } from "types/gallery"; import { shouldShowAvatar } from "utils/file"; import { handleSelectCreator, diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index b482a7ca72..07a1e42bb4 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -6,7 +6,7 @@ import { type FileViewerProps, } from "ente-gallery/components/viewer/FileViewer"; import type { Collection } from "ente-media/collection"; -import { EnteFile } from "ente-media/file"; +import type { EnteFile } from "ente-media/file"; import { fileCreationTime, fileFileName } from "ente-media/file-metadata"; import { moveToTrash } from "ente-new/photos/services/collection"; import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; @@ -14,7 +14,7 @@ import { t } from "i18next"; import { useCallback, useMemo, useState } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; import { uploadManager } from "services/upload-manager"; -import { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; +import type { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; import { downloadSingleFile } from "utils/file"; import { FileList, diff --git a/web/apps/photos/src/components/FilesDownloadProgress.tsx b/web/apps/photos/src/components/FilesDownloadProgress.tsx index b71c4f2521..da637938e3 100644 --- a/web/apps/photos/src/components/FilesDownloadProgress.tsx +++ b/web/apps/photos/src/components/FilesDownloadProgress.tsx @@ -1,3 +1,5 @@ +// TODO: Audit this file +/* eslint-disable react-refresh/only-export-components */ import { useBaseContext } from "ente-base/context"; import { Notification } from "ente-new/photos/components/Notification"; import { t } from "i18next"; diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 13899ebabf..5830d79014 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -112,11 +112,11 @@ import { initiateEmail, openURL } from "ente-new/photos/utils/web"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { - MouseEventHandler, useCallback, useEffect, useMemo, useState, + type MouseEventHandler, } from "react"; import { Trans } from "react-i18next"; import { testUpload } from "../../tests/upload.test"; diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 3343f26b01..1264eae2c4 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -1,3 +1,5 @@ +// TODO: Audit this file +/* eslint-disable @typescript-eslint/no-misused-promises */ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import DiscFullIcon from "@mui/icons-material/DiscFull"; import GoogleIcon from "@mui/icons-material/Google"; @@ -81,7 +83,7 @@ import type { } from "services/upload-manager"; import { uploadManager } from "services/upload-manager"; import watcher from "services/watch"; -import { SetLoading } from "types/gallery"; +import type { SetLoading } from "types/gallery"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; import { UploadProgress } from "./UploadProgress"; diff --git a/web/apps/photos/src/components/UploadProgress.tsx b/web/apps/photos/src/components/UploadProgress.tsx index 9b762c5189..1afa305ace 100644 --- a/web/apps/photos/src/components/UploadProgress.tsx +++ b/web/apps/photos/src/components/UploadProgress.tsx @@ -32,17 +32,17 @@ import { t } from "i18next"; import memoize from "memoize-one"; import React, { createContext, - ReactElement, useContext, useEffect, useState, + type ReactElement, } from "react"; import { Trans } from "react-i18next"; import { areEqual, FixedSizeList as List, - ListChildComponentProps, - ListItemKeySelector, + type ListChildComponentProps, + type ListItemKeySelector, } from "react-window"; import type { FinishedUploadType, diff --git a/web/apps/photos/src/components/pages/gallery/Avatar.tsx b/web/apps/photos/src/components/pages/gallery/Avatar.tsx index 31da162bb0..0ee92ff32b 100644 --- a/web/apps/photos/src/components/pages/gallery/Avatar.tsx +++ b/web/apps/photos/src/components/pages/gallery/Avatar.tsx @@ -1,7 +1,7 @@ import { styled } from "@mui/material"; import type { LocalUser } from "ente-accounts/services/user"; import log from "ente-base/log"; -import { EnteFile } from "ente-media/file"; +import { type EnteFile } from "ente-media/file"; import { avatarBackgroundColor, avatarBackgroundColorPublicCollectedFile, diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 5458061407..5b86b04ec0 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -26,7 +26,7 @@ import { photosTheme } from "ente-base/components/utils/theme"; import { BaseContext, deriveBaseContext } from "ente-base/context"; import log from "ente-base/log"; import { logStartupBanner } from "ente-base/log-web"; -import { AppUpdate } from "ente-base/types/ipc"; +import type { AppUpdate } from "ente-base/types/ipc"; import { initVideoProcessing, isHLSGenerationSupported, @@ -81,7 +81,7 @@ const App: React.FC = ({ Component, pageProps }) => { useEffect(() => { const electron = globalThis.electron; - if (!electron) return; + if (!electron) return undefined; // Attach various listeners for events sent to us by the Node.js layer. // This is for events that we should listen for always, not just when diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 21173e9908..3db254fa7e 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -4,11 +4,11 @@ import MenuIcon from "@mui/icons-material/Menu"; import { IconButton, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; -import { TimeStampListItem } from "components/FileList"; +import { type TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FilesDownloadProgress, - FilesDownloadProgressAttributes, + type FilesDownloadProgressAttributes, } from "components/FilesDownloadProgress"; import { FixCreationTime } from "components/FixCreationTime"; import { Sidebar } from "components/Sidebar"; @@ -120,10 +120,10 @@ import { PromiseQueue } from "ente-utils/promise"; import { t } from "i18next"; import { useRouter, type NextRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { FileWithPath } from "react-dropzone"; +import type { FileWithPath } from "react-dropzone"; import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; -import { +import type { SelectedState, SetFilesDownloadProgressAttributes, SetFilesDownloadProgressAttributesCreator, @@ -1309,11 +1309,11 @@ const HiddenSectionNavbarContents: React.FC< * * Check if these query parameters exist, and if so, act on them appropriately. */ -export async function handleSubscriptionCompletionRedirectIfNeeded( +const handleSubscriptionCompletionRedirectIfNeeded = async ( showMiniDialog: (attributes: MiniDialogAttributes) => void, showLoadingBar: () => void, router: NextRouter, -) { +) => { const { session_id: sessionID, status, reason } = router.query; if (status == "success") { @@ -1378,4 +1378,4 @@ export async function handleSubscriptionCompletionRedirectIfNeeded( ); } } -} +}; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 705ea034c9..79e85a12c9 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -6,11 +6,11 @@ import DownloadIcon from "@mui/icons-material/Download"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material"; import Typography from "@mui/material/Typography"; -import { TimeStampListItem } from "components/FileList"; +import type { TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FilesDownloadProgress, - FilesDownloadProgressAttributes, + type FilesDownloadProgressAttributes, } from "components/FilesDownloadProgress"; import { Upload } from "components/Upload"; import { @@ -46,7 +46,7 @@ import { useBaseContext } from "ente-base/context"; import { isHTTP401Error, isHTTPErrorWithStatus, - PublicAlbumsCredentials, + type PublicAlbumsCredentials, } from "ente-base/http"; import log from "ente-base/log"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; @@ -83,7 +83,7 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; import { uploadManager } from "services/upload-manager"; -import { +import type { SelectedState, SetFilesDownloadProgressAttributes, SetFilesDownloadProgressAttributesCreator, diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index 672ff93aa5..81bd297b57 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -1,7 +1,9 @@ import { sortFiles } from "ente-gallery/utils/file"; -import { EnteFile } from "ente-media/file"; -import type { CollectionSummary } from "ente-new/photos/services/collection-summary"; -import { CollectionsSortBy } from "ente-new/photos/services/collection-summary"; +import type { EnteFile } from "ente-media/file"; +import type { + CollectionsSortBy, + CollectionSummary, +} from "ente-new/photos/services/collection-summary"; export const sortCollectionSummaries = ( collectionSummaries: CollectionSummary[], diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts index 12bd92ec9b..83500edab5 100644 --- a/web/apps/photos/src/types/gallery/index.ts +++ b/web/apps/photos/src/types/gallery/index.ts @@ -1,4 +1,4 @@ -import { FilesDownloadProgressAttributes } from "components/FilesDownloadProgress"; +import { type FilesDownloadProgressAttributes } from "components/FilesDownloadProgress"; import { type SelectionContext } from "ente-new/photos/components/gallery"; export interface SelectedState { diff --git a/web/apps/photos/src/utils/collection.ts b/web/apps/photos/src/utils/collection.ts index 8be190369b..50c2697da6 100644 --- a/web/apps/photos/src/utils/collection.ts +++ b/web/apps/photos/src/utils/collection.ts @@ -2,7 +2,7 @@ import { ensureElectron } from "ente-base/electron"; import { joinPath } from "ente-base/file-name"; import log from "ente-base/log"; import { uniqueFilesByID } from "ente-gallery/utils/file"; -import { EnteFile } from "ente-media/file"; +import type { EnteFile } from "ente-media/file"; import { defaultHiddenCollectionUserFacingName, findDefaultHiddenCollectionIDs, @@ -13,9 +13,9 @@ import { savedCollections, } from "ente-new/photos/services/photos-fdb"; import { safeDirectoryName } from "ente-new/photos/utils/native-fs"; -import { +import type { SetFilesDownloadProgressAttributes, - type SetFilesDownloadProgressAttributesCreator, + SetFilesDownloadProgressAttributesCreator, } from "types/gallery"; import { downloadFilesWithProgress } from "utils/file"; diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 5558efaff0..df0c381204 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -6,7 +6,7 @@ import { saveAsFileAndRevokeObjectURL } from "ente-base/utils/web"; import { downloadManager } from "ente-gallery/services/download"; import { detectFileTypeInfo } from "ente-gallery/utils/detect-type"; import { writeStream } from "ente-gallery/utils/native-stream"; -import { EnteFile } from "ente-media/file"; +import type { EnteFile } from "ente-media/file"; import { ItemVisibility, fileFileName } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { decodeLivePhoto } from "ente-media/live-photo"; @@ -21,7 +21,7 @@ import { updateFilesVisibility } from "ente-new/photos/services/file"; import { safeFileName } from "ente-new/photos/utils/native-fs"; import { wait } from "ente-utils/promise"; import { t } from "i18next"; -import { +import type { SelectedState, SetFilesDownloadProgressAttributes, SetFilesDownloadProgressAttributesCreator, diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index 9f68a08658..33e902cc6f 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -1,6 +1,6 @@ import type { SelectionContext } from "ente-new/photos/components/gallery"; import type { GalleryBarMode } from "ente-new/photos/components/gallery/reducer"; -import { SetSelectedState, type SelectedState } from "types/gallery"; +import type { SelectedState, SetSelectedState } from "types/gallery"; // TODO: All this is unnecessarily complex, and needs reworking. export const handleSelectCreator = diff --git a/web/apps/photos/src/utils/publicCollectionGallery/index.ts b/web/apps/photos/src/utils/publicCollectionGallery/index.ts index 58a856e558..921c344ebc 100644 --- a/web/apps/photos/src/utils/publicCollectionGallery/index.ts +++ b/web/apps/photos/src/utils/publicCollectionGallery/index.ts @@ -1,4 +1,4 @@ -import { TimeStampListItem } from "components/FileList"; +import type { TimeStampListItem } from "components/FileList"; import type { PublicAlbumsCredentials } from "ente-base/http"; import { createContext } from "react"; diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index ec4812998c..7f1bbcfd7e 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -1,7 +1,9 @@ -/* eslint-disable @typescript-eslint/no-base-to-string */ // TODO: Audit this file +/* eslint-disable @typescript-eslint/ban-ts-comment */ +/* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ /* eslint-disable @typescript-eslint/dot-notation */ +// @ts-nocheck import { parseDateFromDigitGroups, tryParseEpochMicrosecondsFromFileName, diff --git a/web/apps/photos/tsconfig.json b/web/apps/photos/tsconfig.json index 9892dd4883..4b0f70e320 100644 --- a/web/apps/photos/tsconfig.json +++ b/web/apps/photos/tsconfig.json @@ -5,14 +5,8 @@ "baseUrl": "./src", /* Override tsconfig-next.json (TODO: Remove all of us) */ - "verbatimModuleSyntax": false, "noImplicitAny": false, - "noUnusedLocals": false, - "noUnusedParameters": false, - "noImplicitReturns": false, - "noUncheckedIndexedAccess": false, - "strictNullChecks": false, - "useUnknownInCatchVariables": false + "strictNullChecks": false }, "include": [ "next-env.d.ts", diff --git a/web/packages/gallery/components/FileInfo.tsx b/web/packages/gallery/components/FileInfo.tsx index 80a9014e01..92f06a085e 100644 --- a/web/packages/gallery/components/FileInfo.tsx +++ b/web/packages/gallery/components/FileInfo.tsx @@ -215,7 +215,7 @@ export const FileInfo: React.FC = ({ const annotatedExif = useMemo(() => annotateExif(exif), [exif]); useEffect(() => { - if (!isMLEnabled()) return; + if (!isMLEnabled()) return undefined; // Take a dependency on open so that we refresh the list of people by // calling `getAnnotatedFacesForFile` again when the file info dialog is @@ -230,7 +230,7 @@ export const FileInfo: React.FC = ({ // Since the `file` hasn't changed, this hook wouldn't rerun. So we also // take a dependency on the open state of the dialog, causing us to // rerun whenever reopened (even if for the same file). - if (!open) return; + if (!open) return undefined; let didCancel = false; diff --git a/web/packages/gallery/components/viewer/data-source.ts b/web/packages/gallery/components/viewer/data-source.ts index f13df294b4..2325584b27 100644 --- a/web/packages/gallery/components/viewer/data-source.ts +++ b/web/packages/gallery/components/viewer/data-source.ts @@ -723,15 +723,15 @@ export const updateFileInfoExifIfNeeded = async (itemData: ItemData) => { // We already have it available. if (_state.fileInfoExifByFileID.has(fileID)) return; - const updateNotifyAndReturn = (exifData: FileInfoExif) => { + const update = (exifData: FileInfoExif) => { _state.fileInfoExifByFileID.set(fileID, exifData); _state.exifObserverByFileID.get(fileID)?.(exifData); - return exifData; }; // For videos, insert a placeholder. if (fileType == FileType.video) { - return updateNotifyAndReturn(createPlaceholderFileInfoExif()); + update(createPlaceholderFileInfoExif()); + return; } // This is not a video, but the original image is not available yet. @@ -741,12 +741,12 @@ export const updateFileInfoExifIfNeeded = async (itemData: ItemData) => { const file = new File([originalImageBlob], ""); const tags = await extractRawExif(file); const parsed = parseExif(tags); - return updateNotifyAndReturn({ tags, parsed }); + update({ tags, parsed }); } catch (e) { log.error("Failed to extract exif", e); // Save the empty placeholder exif corresponding to the file, no point // in unnecessarily retrying this, it will deterministically fail again. - return updateNotifyAndReturn(createPlaceholderFileInfoExif()); + update(createPlaceholderFileInfoExif()); } }; diff --git a/web/packages/new/photos/components/Tiles.tsx b/web/packages/new/photos/components/Tiles.tsx index 2572257453..bf8318d832 100644 --- a/web/packages/new/photos/components/Tiles.tsx +++ b/web/packages/new/photos/components/Tiles.tsx @@ -66,7 +66,7 @@ export const ItemCard: React.FC> = ({ const [coverImageURL, setCoverImageURL] = useState(); useEffect(() => { - if (!coverFile) return; + if (!coverFile) return undefined; let didCancel = false; diff --git a/web/packages/new/photos/components/gallery/BarImpl.tsx b/web/packages/new/photos/components/gallery/BarImpl.tsx index 27198b86dc..757032f998 100644 --- a/web/packages/new/photos/components/gallery/BarImpl.tsx +++ b/web/packages/new/photos/components/gallery/BarImpl.tsx @@ -146,7 +146,7 @@ export const GalleryBarImpl: React.FC = ({ >( (ref) => { listContainerRef.current = ref; - if (!ref) return; + if (!ref) return undefined; // Listen for scrolls and resize. ref.addEventListener("scroll", updateScrollState); From 522704ccb86831b54f0b5961d92e2b66e1197e7e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 13:51:27 +0530 Subject: [PATCH 040/109] strictNullChecks prep - 1 --- web/apps/photos/src/components/AuthenticateUser.tsx | 2 +- .../src/components/Collections/AlbumCastDialog.tsx | 2 +- .../photos/src/components/Collections/AllAlbums.tsx | 10 ++++++---- .../src/components/Collections/CollectionHeader.tsx | 6 +++--- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/components/AuthenticateUser.tsx b/web/apps/photos/src/components/AuthenticateUser.tsx index a1559f7b83..8c3b813c4c 100644 --- a/web/apps/photos/src/components/AuthenticateUser.tsx +++ b/web/apps/photos/src/components/AuthenticateUser.tsx @@ -94,7 +94,7 @@ const AuthenticateUserDialogContents: React.FC = ({ }, [open, validateSession]); // They'll be read from disk shortly. - if (!user && !keyAttributes) return <>; + if (!user || !keyAttributes) return <>; return ( = ({ log.error("Error requesting session", e); return; } - const session = instance.getCurrentSession(); + const session = instance.getCurrentSession()!; session.addMessageListener( "urn:x-cast:pair-request", (_, message) => { diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index 80c589a0a8..e0b6bf710f 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -170,7 +170,7 @@ const AlbumsRow = React.memo( isScrolling, }: ListChildComponentProps) => { const { collectionRowList, onCollectionClick } = data; - const collectionRow = collectionRowList[index]; + const collectionRow = collectionRowList[index]!; return (
@@ -191,7 +191,7 @@ const AlbumsRow = React.memo( interface AllAlbumsContentProps { collectionSummaries: CollectionSummary[]; - onCollectionClick: (id?: number) => void; + onCollectionClick: (id: number) => void; } const AllAlbumsContent: React.FC = ({ @@ -203,7 +203,9 @@ const AllAlbumsContent: React.FC = ({ const refreshInProgress = useRef(false); const shouldRefresh = useRef(false); - const [collectionRowList, setCollectionRowList] = useState([]); + const [collectionRowList, setCollectionRowList] = useState< + CollectionSummary[][] + >([]); const columns = isTwoColumn ? 2 : 3; const maxListContentHeight = @@ -231,7 +233,7 @@ const AllAlbumsContent: React.FC = ({ i < columns && index < collectionSummaries.length; i++ ) { - collectionRow.push(collectionSummaries[index++]); + collectionRow.push(collectionSummaries[index++]!); } collectionRowList.push(collectionRow); } diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index 25a44db6eb..df35d1b372 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -124,7 +124,7 @@ const CollectionHeaderOptions: React.FC = ({ }) => { const { showMiniDialog, onGenericError } = useBaseContext(); const { showLoadingBar, hideLoadingBar } = usePhotosAppContext(); - const overflowMenuIconRef = useRef(null); + const overflowMenuIconRef = useRef(null); const { show: showSortOrderMenu, props: sortOrderMenuVisibilityProps } = useModalVisibility(); @@ -674,7 +674,7 @@ const DownloadOption: React.FC< interface CollectionSortOrderMenuProps { open: boolean; onClose: () => void; - overflowMenuIconRef: React.RefObject; + overflowMenuIconRef: React.RefObject; onAscClick: () => void; onDescClick: () => void; } @@ -699,7 +699,7 @@ const CollectionSortOrderMenu: React.FC = ({ return ( Date: Fri, 4 Jul 2025 15:18:00 +0530 Subject: [PATCH 041/109] Move --- .../Collections/CollectionHeader.tsx | 2 +- .../Collections/GalleryBarAndListHeader.tsx | 3 +- .../src/components/FileListWithViewer.tsx | 6 +- .../src/components/FilesDownloadProgress.tsx | 13 +- web/apps/photos/src/pages/gallery.tsx | 4 +- web/apps/photos/src/pages/shared-albums.tsx | 21 +- web/apps/photos/src/types/gallery/index.ts | 12 - web/apps/photos/src/utils/collection.ts | 121 ------ web/apps/photos/src/utils/file/index.ts | 256 +----------- web/packages/gallery/services/save.ts | 381 ++++++++++++++++++ 10 files changed, 407 insertions(+), 412 deletions(-) delete mode 100644 web/apps/photos/src/utils/collection.ts create mode 100644 web/packages/gallery/services/save.ts diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index df35d1b372..bbf99742bc 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -55,7 +55,7 @@ import type { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; import { downloadCollectionHelper, downloadDefaultHiddenCollectionHelper, -} from "utils/collection"; +} from "ente-gallery/services/save"; export interface CollectionHeaderProps { collectionSummary: CollectionSummary; diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index d97b5ac30c..6ef10876c5 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -5,6 +5,7 @@ import { } from "components/Collections/CollectionShare"; import type { TimeStampListItem } from "components/FileList"; import { useModalVisibility } from "ente-base/components/utils/modal"; +import { type FilesDownloadProgressAttributes } from "ente-gallery/services/save"; import type { Collection } from "ente-media/collection"; import { GalleryBarImpl, @@ -24,14 +25,12 @@ import { sortCollectionSummaries } from "services/collectionService"; import { isFilesDownloadCancelled, isFilesDownloadCompleted, - type FilesDownloadProgressAttributes, } from "../FilesDownloadProgress"; import { AlbumCastDialog } from "./AlbumCastDialog"; import { CollectionHeader, type CollectionHeaderProps, } from "./CollectionHeader"; - type GalleryBarAndListHeaderProps = Omit< GalleryBarImplProps, | "collectionSummaries" diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 07a1e42bb4..7881e2d2f2 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -5,6 +5,10 @@ import { FileViewer, type FileViewerProps, } from "ente-gallery/components/viewer/FileViewer"; +import { + downloadSingleFile, + type SetFilesDownloadProgressAttributesCreator, +} from "ente-gallery/services/save"; import type { Collection } from "ente-media/collection"; import type { EnteFile } from "ente-media/file"; import { fileCreationTime, fileFileName } from "ente-media/file-metadata"; @@ -14,8 +18,6 @@ import { t } from "i18next"; import { useCallback, useMemo, useState } from "react"; import AutoSizer from "react-virtualized-auto-sizer"; import { uploadManager } from "services/upload-manager"; -import type { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; -import { downloadSingleFile } from "utils/file"; import { FileList, type FileListAnnotatedFile, diff --git a/web/apps/photos/src/components/FilesDownloadProgress.tsx b/web/apps/photos/src/components/FilesDownloadProgress.tsx index da637938e3..87e628287c 100644 --- a/web/apps/photos/src/components/FilesDownloadProgress.tsx +++ b/web/apps/photos/src/components/FilesDownloadProgress.tsx @@ -1,21 +1,10 @@ // TODO: Audit this file /* eslint-disable react-refresh/only-export-components */ import { useBaseContext } from "ente-base/context"; +import type { FilesDownloadProgressAttributes } from "ente-gallery/services/save"; import { Notification } from "ente-new/photos/components/Notification"; import { t } from "i18next"; -export interface FilesDownloadProgressAttributes { - id: number; - success: number; - failed: number; - total: number; - folderName: string; - collectionID: number; - isHidden: boolean; - downloadDirPath: string; - canceller: AbortController; -} - interface FilesDownloadProgressProps { attributesList: FilesDownloadProgressAttributes[]; setAttributesList: (value: FilesDownloadProgressAttributes[]) => void; diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 3db254fa7e..565cef4e5f 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -125,9 +125,11 @@ import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; import type { SelectedState, +} from "types/gallery"; +import type { SetFilesDownloadProgressAttributes, SetFilesDownloadProgressAttributesCreator, -} from "types/gallery"; +} from "ente-gallery/services/save"; import { getSelectedFiles, performFileOp } from "utils/file"; /** diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 79e85a12c9..88b140a2ed 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -8,10 +8,7 @@ import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material"; import Typography from "@mui/material/Typography"; import type { TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; -import { - FilesDownloadProgress, - type FilesDownloadProgressAttributes, -} from "components/FilesDownloadProgress"; +import { FilesDownloadProgress } from "components/FilesDownloadProgress"; import { Upload } from "components/Upload"; import { AccountsPageContents, @@ -51,6 +48,13 @@ import { import log from "ente-base/log"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; import { downloadManager } from "ente-gallery/services/download"; +import { + downloadCollectionFiles, + downloadSelectedFiles, + type FilesDownloadProgressAttributes, + type SetFilesDownloadProgressAttributes, + type SetFilesDownloadProgressAttributesCreator, +} from "ente-gallery/services/save"; import { extractCollectionKeyFromShareURL } from "ente-gallery/services/share"; import { updateShouldDisableCFUploadProxy } from "ente-gallery/services/upload"; import { sortFiles } from "ente-gallery/utils/file"; @@ -83,13 +87,8 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; import { uploadManager } from "services/upload-manager"; -import type { - SelectedState, - SetFilesDownloadProgressAttributes, - SetFilesDownloadProgressAttributesCreator, -} from "types/gallery"; -import { downloadCollectionFiles } from "utils/collection"; -import { downloadSelectedFiles, getSelectedFiles } from "utils/file"; +import type { SelectedState } from "types/gallery"; +import { getSelectedFiles } from "utils/file"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; export default function PublicCollectionGallery() { diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts index 83500edab5..8a27adc307 100644 --- a/web/apps/photos/src/types/gallery/index.ts +++ b/web/apps/photos/src/types/gallery/index.ts @@ -17,19 +17,7 @@ export type SetSelectedState = React.Dispatch< React.SetStateAction >; export type SetLoading = React.Dispatch>; -export type SetFilesDownloadProgressAttributes = ( - value: - | Partial - | (( - prev: FilesDownloadProgressAttributes, - ) => FilesDownloadProgressAttributes), -) => void; -export type SetFilesDownloadProgressAttributesCreator = ( - folderName: string, - collectionID?: number, - isHidden?: boolean, -) => SetFilesDownloadProgressAttributes; export interface MergedSourceURL { original: string; diff --git a/web/apps/photos/src/utils/collection.ts b/web/apps/photos/src/utils/collection.ts deleted file mode 100644 index 50c2697da6..0000000000 --- a/web/apps/photos/src/utils/collection.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { ensureElectron } from "ente-base/electron"; -import { joinPath } from "ente-base/file-name"; -import log from "ente-base/log"; -import { uniqueFilesByID } from "ente-gallery/utils/file"; -import type { EnteFile } from "ente-media/file"; -import { - defaultHiddenCollectionUserFacingName, - findDefaultHiddenCollectionIDs, -} from "ente-new/photos/services/collection"; -import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; -import { - savedCollectionFiles, - savedCollections, -} from "ente-new/photos/services/photos-fdb"; -import { safeDirectoryName } from "ente-new/photos/utils/native-fs"; -import type { - SetFilesDownloadProgressAttributes, - SetFilesDownloadProgressAttributesCreator, -} from "types/gallery"; -import { downloadFilesWithProgress } from "utils/file"; - -export async function downloadCollectionHelper( - collectionID: number, - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - try { - const allFiles = await savedCollectionFiles(); - const collectionFiles = allFiles.filter( - (file) => file.collectionID == collectionID, - ); - const allCollections = await savedCollections(); - const collection = allCollections.find( - (collection) => collection.id == collectionID, - ); - if (!collection) { - throw Error("collection not found"); - } - await downloadCollectionFiles( - collection.name, - collectionFiles, - setFilesDownloadProgressAttributes, - ); - } catch (e) { - log.error("download collection failed ", e); - } -} - -export async function downloadDefaultHiddenCollectionHelper( - setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator, -) { - try { - const defaultHiddenCollectionsIDs = findDefaultHiddenCollectionIDs( - await savedCollections(), - ); - const collectionFiles = await savedCollectionFiles(); - const defaultHiddenCollectionFiles = uniqueFilesByID( - collectionFiles.filter((file) => - defaultHiddenCollectionsIDs.has(file.collectionID), - ), - ); - const setFilesDownloadProgressAttributes = - setFilesDownloadProgressAttributesCreator( - defaultHiddenCollectionUserFacingName, - PseudoCollectionID.hiddenItems, - true, - ); - - await downloadCollectionFiles( - defaultHiddenCollectionUserFacingName, - defaultHiddenCollectionFiles, - setFilesDownloadProgressAttributes, - ); - } catch (e) { - log.error("download hidden files failed ", e); - } -} - -export async function downloadCollectionFiles( - collectionName: string, - collectionFiles: EnteFile[], - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - if (!collectionFiles.length) { - return; - } - let downloadDirPath: string; - const electron = globalThis.electron; - if (electron) { - const selectedDir = await electron.selectDirectory(); - if (!selectedDir) { - return; - } - downloadDirPath = await createCollectionDownloadFolder( - selectedDir, - collectionName, - ); - } - await downloadFilesWithProgress( - collectionFiles, - downloadDirPath, - setFilesDownloadProgressAttributes, - ); -} - -async function createCollectionDownloadFolder( - downloadDirPath: string, - collectionName: string, -) { - const fs = ensureElectron().fs; - const collectionDownloadName = await safeDirectoryName( - downloadDirPath, - collectionName, - fs.exists, - ); - const collectionDownloadPath = joinPath( - downloadDirPath, - collectionDownloadName, - ); - await fs.mkdirIfNeeded(collectionDownloadPath); - return collectionDownloadPath; -} diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index df0c381204..d21dadf160 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,15 +1,10 @@ import type { LocalUser } from "ente-accounts/services/user"; -import { joinPath } from "ente-base/file-name"; -import log from "ente-base/log"; -import { type Electron } from "ente-base/types/ipc"; -import { saveAsFileAndRevokeObjectURL } from "ente-base/utils/web"; -import { downloadManager } from "ente-gallery/services/download"; -import { detectFileTypeInfo } from "ente-gallery/utils/detect-type"; -import { writeStream } from "ente-gallery/utils/native-stream"; +import { + type SetFilesDownloadProgressAttributesCreator, + downloadSelectedFiles, +} from "ente-gallery/services/save"; import type { EnteFile } from "ente-media/file"; -import { ItemVisibility, fileFileName } from "ente-media/file-metadata"; -import { FileType } from "ente-media/file-type"; -import { decodeLivePhoto } from "ente-media/live-photo"; +import { ItemVisibility } from "ente-media/file-metadata"; import { type FileOp } from "ente-new/photos/components/SelectedFileOptions"; import { addToFavoritesCollection, @@ -18,14 +13,8 @@ import { moveToTrash, } from "ente-new/photos/services/collection"; import { updateFilesVisibility } from "ente-new/photos/services/file"; -import { safeFileName } from "ente-new/photos/utils/native-fs"; -import { wait } from "ente-utils/promise"; import { t } from "i18next"; -import type { - SelectedState, - SetFilesDownloadProgressAttributes, - SetFilesDownloadProgressAttributesCreator, -} from "types/gallery"; +import type { SelectedState } from "types/gallery"; export function getSelectedFiles( selected: SelectedState, @@ -41,239 +30,6 @@ export function getSelectedFiles( return files.filter((file) => selectedFilesIDs.has(file.id)); } -export async function getFileFromURL(fileURL: string, name: string) { - const fileBlob = await (await fetch(fileURL)).blob(); - const fileFile = new File([fileBlob], name); - return fileFile; -} - -export async function downloadFilesWithProgress( - files: EnteFile[], - downloadDirPath: string, - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - if (!files.length) { - return; - } - const canceller = new AbortController(); - const increaseSuccess = () => { - if (canceller.signal.aborted) return; - setFilesDownloadProgressAttributes((prev) => ({ - ...prev, - success: prev.success + 1, - })); - }; - const increaseFailed = () => { - if (canceller.signal.aborted) return; - setFilesDownloadProgressAttributes((prev) => ({ - ...prev, - failed: prev.failed + 1, - })); - }; - const isCancelled = () => canceller.signal.aborted; - - setFilesDownloadProgressAttributes({ - downloadDirPath, - success: 0, - failed: 0, - total: files.length, - canceller, - }); - - const electron = globalThis.electron; - if (electron) { - await downloadFilesDesktop( - electron, - files, - { increaseSuccess, increaseFailed, isCancelled }, - downloadDirPath, - ); - } else { - await downloadFiles(files, { - increaseSuccess, - increaseFailed, - isCancelled, - }); - } -} - -export async function downloadSelectedFiles( - files: EnteFile[], - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - if (!files.length) { - return; - } - let downloadDirPath: string; - const electron = globalThis.electron; - if (electron) { - downloadDirPath = await electron.selectDirectory(); - if (!downloadDirPath) { - return; - } - } - await downloadFilesWithProgress( - files, - downloadDirPath, - setFilesDownloadProgressAttributes, - ); -} - -export async function downloadSingleFile( - file: EnteFile, - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - let downloadDirPath: string; - const electron = globalThis.electron; - if (electron) { - downloadDirPath = await electron.selectDirectory(); - if (!downloadDirPath) { - return; - } - } - await downloadFilesWithProgress( - [file], - downloadDirPath, - setFilesDownloadProgressAttributes, - ); -} - -export async function downloadFiles( - files: EnteFile[], - progressBarUpdater: { - increaseSuccess: () => void; - increaseFailed: () => void; - isCancelled: () => boolean; - }, -) { - for (const file of files) { - try { - if (progressBarUpdater?.isCancelled()) { - return; - } - await saveAsFile(file); - progressBarUpdater?.increaseSuccess(); - } catch (e) { - log.error("download fail for file", e); - progressBarUpdater?.increaseFailed(); - } - } -} - -/** - * Save the given {@link EnteFile} as a file in the user's download folder. - */ -const saveAsFile = async (file: EnteFile) => { - const fileBlob = await downloadManager.fileBlob(file); - const fileName = fileFileName(file); - if (file.metadata.fileType == FileType.livePhoto) { - const { imageFileName, imageData, videoFileName, videoData } = - await decodeLivePhoto(fileName, fileBlob); - - await saveBlobPartAsFile(imageData, imageFileName); - - // Downloading multiple works everywhere except, you guessed it, - // Safari. Make up for their incompetence by adding a setTimeout. - await wait(300) /* arbitrary constant, 300ms */; - await saveBlobPartAsFile(videoData, videoFileName); - } else { - await saveBlobPartAsFile(fileBlob, fileName); - } -}; - -/** - * Save the given {@link blob} as a file in the user's download folder. - */ -const saveBlobPartAsFile = async (blobPart: BlobPart, fileName: string) => - createTypedObjectURL(blobPart, fileName).then((url) => - saveAsFileAndRevokeObjectURL(url, fileName), - ); - -const createTypedObjectURL = async (blobPart: BlobPart, fileName: string) => { - const blob = blobPart instanceof Blob ? blobPart : new Blob([blobPart]); - const { mimeType } = await detectFileTypeInfo(new File([blob], fileName)); - return URL.createObjectURL(new Blob([blob], { type: mimeType })); -}; - -async function downloadFilesDesktop( - electron: Electron, - files: EnteFile[], - progressBarUpdater: { - increaseSuccess: () => void; - increaseFailed: () => void; - isCancelled: () => boolean; - }, - downloadPath: string, -) { - for (const file of files) { - try { - if (progressBarUpdater?.isCancelled()) { - return; - } - await downloadFileDesktop(electron, file, downloadPath); - progressBarUpdater?.increaseSuccess(); - } catch (e) { - log.error("download fail for file", e); - progressBarUpdater?.increaseFailed(); - } - } -} - -async function downloadFileDesktop( - electron: Electron, - file: EnteFile, - downloadDir: string, -) { - const fs = electron.fs; - - const stream = await downloadManager.fileStream(file); - const fileName = fileFileName(file); - - if (file.metadata.fileType == FileType.livePhoto) { - const fileBlob = await new Response(stream).blob(); - const { imageFileName, imageData, videoFileName, videoData } = - await decodeLivePhoto(fileName, fileBlob); - const imageExportName = await safeFileName( - downloadDir, - imageFileName, - fs.exists, - ); - const imageStream = new Response(imageData).body; - await writeStream( - electron, - joinPath(downloadDir, imageExportName), - imageStream, - ); - try { - const videoExportName = await safeFileName( - downloadDir, - videoFileName, - fs.exists, - ); - const videoStream = new Response(videoData).body; - await writeStream( - electron, - joinPath(downloadDir, videoExportName), - videoStream, - ); - } catch (e) { - await fs.rm(joinPath(downloadDir, imageExportName)); - throw e; - } - } else { - const fileExportName = await safeFileName( - downloadDir, - fileName, - fs.exists, - ); - await writeStream( - electron, - joinPath(downloadDir, fileExportName), - stream, - ); - } -} - export const shouldShowAvatar = ( file: EnteFile, user: LocalUser | undefined, diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts new file mode 100644 index 0000000000..f081a870af --- /dev/null +++ b/web/packages/gallery/services/save.ts @@ -0,0 +1,381 @@ +import { ensureElectron } from "ente-base/electron"; +import { joinPath } from "ente-base/file-name"; +import log from "ente-base/log"; +import { type Electron } from "ente-base/types/ipc"; +import { saveAsFileAndRevokeObjectURL } from "ente-base/utils/web"; +import { downloadManager } from "ente-gallery/services/download"; +import { detectFileTypeInfo } from "ente-gallery/utils/detect-type"; +import { uniqueFilesByID } from "ente-gallery/utils/file"; +import { writeStream } from "ente-gallery/utils/native-stream"; +import type { EnteFile } from "ente-media/file"; +import { fileFileName } from "ente-media/file-metadata"; +import { FileType } from "ente-media/file-type"; +import { decodeLivePhoto } from "ente-media/live-photo"; +import { + defaultHiddenCollectionUserFacingName, + findDefaultHiddenCollectionIDs, +} from "ente-new/photos/services/collection"; +import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; +import { + savedCollectionFiles, + savedCollections, +} from "ente-new/photos/services/photos-fdb"; +import { + safeDirectoryName, + safeFileName, +} from "ente-new/photos/utils/native-fs"; +import { wait } from "ente-utils/promise"; + +export interface FilesDownloadProgressAttributes { + id: number; + success: number; + failed: number; + total: number; + folderName: string; + collectionID: number; + isHidden: boolean; + downloadDirPath: string; + canceller: AbortController; +} + +export type SetFilesDownloadProgressAttributes = ( + value: + | Partial + | (( + prev: FilesDownloadProgressAttributes, + ) => FilesDownloadProgressAttributes), +) => void; + +export type SetFilesDownloadProgressAttributesCreator = ( + folderName: string, + collectionID?: number, + isHidden?: boolean, +) => SetFilesDownloadProgressAttributes; + +export async function downloadFilesWithProgress( + files: EnteFile[], + downloadDirPath: string, + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, +) { + if (!files.length) { + return; + } + const canceller = new AbortController(); + const increaseSuccess = () => { + if (canceller.signal.aborted) return; + setFilesDownloadProgressAttributes((prev) => ({ + ...prev, + success: prev.success + 1, + })); + }; + const increaseFailed = () => { + if (canceller.signal.aborted) return; + setFilesDownloadProgressAttributes((prev) => ({ + ...prev, + failed: prev.failed + 1, + })); + }; + const isCancelled = () => canceller.signal.aborted; + + setFilesDownloadProgressAttributes({ + downloadDirPath, + success: 0, + failed: 0, + total: files.length, + canceller, + }); + + const electron = globalThis.electron; + if (electron) { + await downloadFilesDesktop( + electron, + files, + { increaseSuccess, increaseFailed, isCancelled }, + downloadDirPath, + ); + } else { + await downloadFiles(files, { + increaseSuccess, + increaseFailed, + isCancelled, + }); + } +} + +export async function downloadSelectedFiles( + files: EnteFile[], + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, +) { + if (!files.length) { + return; + } + let downloadDirPath: string; + const electron = globalThis.electron; + if (electron) { + downloadDirPath = await electron.selectDirectory(); + if (!downloadDirPath) { + return; + } + } + await downloadFilesWithProgress( + files, + downloadDirPath, + setFilesDownloadProgressAttributes, + ); +} + +export async function downloadSingleFile( + file: EnteFile, + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, +) { + let downloadDirPath: string; + const electron = globalThis.electron; + if (electron) { + downloadDirPath = await electron.selectDirectory(); + if (!downloadDirPath) { + return; + } + } + await downloadFilesWithProgress( + [file], + downloadDirPath, + setFilesDownloadProgressAttributes, + ); +} + +export async function downloadFiles( + files: EnteFile[], + progressBarUpdater: { + increaseSuccess: () => void; + increaseFailed: () => void; + isCancelled: () => boolean; + }, +) { + for (const file of files) { + try { + if (progressBarUpdater?.isCancelled()) { + return; + } + await saveAsFile(file); + progressBarUpdater?.increaseSuccess(); + } catch (e) { + log.error("download fail for file", e); + progressBarUpdater?.increaseFailed(); + } + } +} + +/** + * Save the given {@link EnteFile} as a file in the user's download folder. + */ +const saveAsFile = async (file: EnteFile) => { + const fileBlob = await downloadManager.fileBlob(file); + const fileName = fileFileName(file); + if (file.metadata.fileType == FileType.livePhoto) { + const { imageFileName, imageData, videoFileName, videoData } = + await decodeLivePhoto(fileName, fileBlob); + + await saveBlobPartAsFile(imageData, imageFileName); + + // Downloading multiple works everywhere except, you guessed it, + // Safari. Make up for their incompetence by adding a setTimeout. + await wait(300) /* arbitrary constant, 300ms */; + await saveBlobPartAsFile(videoData, videoFileName); + } else { + await saveBlobPartAsFile(fileBlob, fileName); + } +}; + +/** + * Save the given {@link blob} as a file in the user's download folder. + */ +const saveBlobPartAsFile = async (blobPart: BlobPart, fileName: string) => + createTypedObjectURL(blobPart, fileName).then((url) => + saveAsFileAndRevokeObjectURL(url, fileName), + ); + +const createTypedObjectURL = async (blobPart: BlobPart, fileName: string) => { + const blob = blobPart instanceof Blob ? blobPart : new Blob([blobPart]); + const { mimeType } = await detectFileTypeInfo(new File([blob], fileName)); + return URL.createObjectURL(new Blob([blob], { type: mimeType })); +}; + +async function downloadFilesDesktop( + electron: Electron, + files: EnteFile[], + progressBarUpdater: { + increaseSuccess: () => void; + increaseFailed: () => void; + isCancelled: () => boolean; + }, + downloadPath: string, +) { + for (const file of files) { + try { + if (progressBarUpdater?.isCancelled()) { + return; + } + await downloadFileDesktop(electron, file, downloadPath); + progressBarUpdater?.increaseSuccess(); + } catch (e) { + log.error("download fail for file", e); + progressBarUpdater?.increaseFailed(); + } + } +} + +async function downloadFileDesktop( + electron: Electron, + file: EnteFile, + downloadDir: string, +) { + const fs = electron.fs; + + const stream = await downloadManager.fileStream(file); + const fileName = fileFileName(file); + + if (file.metadata.fileType == FileType.livePhoto) { + const fileBlob = await new Response(stream).blob(); + const { imageFileName, imageData, videoFileName, videoData } = + await decodeLivePhoto(fileName, fileBlob); + const imageExportName = await safeFileName( + downloadDir, + imageFileName, + fs.exists, + ); + const imageStream = new Response(imageData).body; + await writeStream( + electron, + joinPath(downloadDir, imageExportName), + imageStream, + ); + try { + const videoExportName = await safeFileName( + downloadDir, + videoFileName, + fs.exists, + ); + const videoStream = new Response(videoData).body; + await writeStream( + electron, + joinPath(downloadDir, videoExportName), + videoStream, + ); + } catch (e) { + await fs.rm(joinPath(downloadDir, imageExportName)); + throw e; + } + } else { + const fileExportName = await safeFileName( + downloadDir, + fileName, + fs.exists, + ); + await writeStream( + electron, + joinPath(downloadDir, fileExportName), + stream, + ); + } +} + +export async function downloadCollectionHelper( + collectionID: number, + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, +) { + try { + const allFiles = await savedCollectionFiles(); + const collectionFiles = allFiles.filter( + (file) => file.collectionID == collectionID, + ); + const allCollections = await savedCollections(); + const collection = allCollections.find( + (collection) => collection.id == collectionID, + ); + if (!collection) { + throw Error("collection not found"); + } + await downloadCollectionFiles( + collection.name, + collectionFiles, + setFilesDownloadProgressAttributes, + ); + } catch (e) { + log.error("download collection failed ", e); + } +} + +export async function downloadDefaultHiddenCollectionHelper( + setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator, +) { + try { + const defaultHiddenCollectionsIDs = findDefaultHiddenCollectionIDs( + await savedCollections(), + ); + const collectionFiles = await savedCollectionFiles(); + const defaultHiddenCollectionFiles = uniqueFilesByID( + collectionFiles.filter((file) => + defaultHiddenCollectionsIDs.has(file.collectionID), + ), + ); + const setFilesDownloadProgressAttributes = + setFilesDownloadProgressAttributesCreator( + defaultHiddenCollectionUserFacingName, + PseudoCollectionID.hiddenItems, + true, + ); + + await downloadCollectionFiles( + defaultHiddenCollectionUserFacingName, + defaultHiddenCollectionFiles, + setFilesDownloadProgressAttributes, + ); + } catch (e) { + log.error("download hidden files failed ", e); + } +} + +export async function downloadCollectionFiles( + collectionName: string, + collectionFiles: EnteFile[], + setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, +) { + if (!collectionFiles.length) { + return; + } + let downloadDirPath: string; + const electron = globalThis.electron; + if (electron) { + const selectedDir = await electron.selectDirectory(); + if (!selectedDir) { + return; + } + downloadDirPath = await createCollectionDownloadFolder( + selectedDir, + collectionName, + ); + } + await downloadFilesWithProgress( + collectionFiles, + downloadDirPath, + setFilesDownloadProgressAttributes, + ); +} + +async function createCollectionDownloadFolder( + downloadDirPath: string, + collectionName: string, +) { + const fs = ensureElectron().fs; + const collectionDownloadName = await safeDirectoryName( + downloadDirPath, + collectionName, + fs.exists, + ); + const collectionDownloadPath = joinPath( + downloadDirPath, + collectionDownloadName, + ); + await fs.mkdirIfNeeded(collectionDownloadPath); + return collectionDownloadPath; +} From 85a3a2f2ea49fb920b55b342261f8b026f6c681e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 15:40:06 +0530 Subject: [PATCH 042/109] Update --- web/packages/gallery/services/save.ts | 85 +++++++++++---------- web/packages/gallery/utils/native-stream.ts | 2 +- 2 files changed, 47 insertions(+), 40 deletions(-) diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index f081a870af..a3c59e4635 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -26,6 +26,15 @@ import { } from "ente-new/photos/utils/native-fs"; import { wait } from "ente-utils/promise"; +/** + * An object that keeps track of progress of the download of a set of files. + * + * This "download" is distinct from the downloads the app does from remote. What + * we're doing here is perhaps more accurately but too verbosely described as "a + * user initiated download of files to the user's device", aka "saving them". In + * contrast, the app does the "download the file from remote" internal action, + * e.g. for showing to the user or performing some indexing etc. + */ export interface FilesDownloadProgressAttributes { id: number; success: number; @@ -215,7 +224,7 @@ async function downloadFilesDesktop( if (progressBarUpdater?.isCancelled()) { return; } - await downloadFileDesktop(electron, file, downloadPath); + await saveFileDesktop(electron, file, downloadPath); progressBarUpdater?.increaseSuccess(); } catch (e) { log.error("download fail for file", e); @@ -224,60 +233,58 @@ async function downloadFilesDesktop( } } -async function downloadFileDesktop( +/** + * Save a file to the given {@link directoryPath} using native filesystem APIs. + * + * This is a sibling of {@link saveAsFile} for use when we are running in the + * context of our desktop app. Unlike the browser, the desktop app can use + * native file system APIs to efficiently write the files on disk without + * needing to prompt the user for each write. + * + * @param electron An {@link Electron} instance, a witness to the fact that + * we're running in the desktop app. + * + * @param file The {@link EnteFile} whose contents we want to save to the user's + * file system. + * + * @param directoryPath The file system directory in which to save the file. + */ +const saveFileDesktop = async ( electron: Electron, file: EnteFile, - downloadDir: string, -) { + directoryPath: string, +) => { const fs = electron.fs; + const createSafeName = (fileName: string) => + safeFileName(directoryPath, fileName, fs.exists); + + const writeStreamToFile = ( + fileName: string, + stream: ReadableStream | null, + ) => writeStream(electron, joinPath(directoryPath, fileName), stream); + const stream = await downloadManager.fileStream(file); const fileName = fileFileName(file); if (file.metadata.fileType == FileType.livePhoto) { - const fileBlob = await new Response(stream).blob(); const { imageFileName, imageData, videoFileName, videoData } = - await decodeLivePhoto(fileName, fileBlob); - const imageExportName = await safeFileName( - downloadDir, - imageFileName, - fs.exists, - ); - const imageStream = new Response(imageData).body; - await writeStream( - electron, - joinPath(downloadDir, imageExportName), - imageStream, - ); + await decodeLivePhoto(fileName, await new Response(stream).blob()); + const imageExportName = await createSafeName(imageFileName); + await writeStreamToFile(imageExportName, new Response(imageData).body); try { - const videoExportName = await safeFileName( - downloadDir, - videoFileName, - fs.exists, - ); - const videoStream = new Response(videoData).body; - await writeStream( - electron, - joinPath(downloadDir, videoExportName), - videoStream, + await writeStreamToFile( + await createSafeName(videoFileName), + new Response(videoData).body, ); } catch (e) { - await fs.rm(joinPath(downloadDir, imageExportName)); + await fs.rm(joinPath(directoryPath, imageExportName)); throw e; } } else { - const fileExportName = await safeFileName( - downloadDir, - fileName, - fs.exists, - ); - await writeStream( - electron, - joinPath(downloadDir, fileExportName), - stream, - ); + await writeStreamToFile(await createSafeName(fileName), stream); } -} +}; export async function downloadCollectionHelper( collectionID: number, diff --git a/web/packages/gallery/utils/native-stream.ts b/web/packages/gallery/utils/native-stream.ts index e5a7160595..f52aa7319d 100644 --- a/web/packages/gallery/utils/native-stream.ts +++ b/web/packages/gallery/utils/native-stream.ts @@ -91,7 +91,7 @@ const readNumericHeader = (res: Response, key: string) => { export const writeStream = async ( _: Electron, path: string, - stream: ReadableStream, + stream: ReadableStream | null, ) => { const params = new URLSearchParams({ path }); const url = new URL(`stream://write?${params.toString()}`); From d1951a1f1082a2413d6b97d893c14283f20eb88f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 15:58:24 +0530 Subject: [PATCH 043/109] Rename --- .../Collections/GalleryBarAndListHeader.tsx | 2 +- ...wnloadProgress.tsx => DownloadProgress.tsx} | 0 web/apps/photos/src/pages/gallery.tsx | 18 ++++++++---------- web/apps/photos/src/pages/shared-albums.tsx | 2 +- web/apps/photos/src/types/gallery/index.ts | 2 -- web/packages/gallery/services/save.ts | 12 ++++++------ 6 files changed, 16 insertions(+), 20 deletions(-) rename web/apps/photos/src/components/{FilesDownloadProgress.tsx => DownloadProgress.tsx} (100%) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 6ef10876c5..ea797f5045 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -25,7 +25,7 @@ import { sortCollectionSummaries } from "services/collectionService"; import { isFilesDownloadCancelled, isFilesDownloadCompleted, -} from "../FilesDownloadProgress"; +} from "../DownloadProgress"; import { AlbumCastDialog } from "./AlbumCastDialog"; import { CollectionHeader, diff --git a/web/apps/photos/src/components/FilesDownloadProgress.tsx b/web/apps/photos/src/components/DownloadProgress.tsx similarity index 100% rename from web/apps/photos/src/components/FilesDownloadProgress.tsx rename to web/apps/photos/src/components/DownloadProgress.tsx diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 565cef4e5f..334910b2d8 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -4,12 +4,12 @@ import MenuIcon from "@mui/icons-material/Menu"; import { IconButton, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; -import { type TimeStampListItem } from "components/FileList"; -import { FileListWithViewer } from "components/FileListWithViewer"; import { FilesDownloadProgress, type FilesDownloadProgressAttributes, -} from "components/FilesDownloadProgress"; +} from "components/DownloadProgress"; +import { type TimeStampListItem } from "components/FileList"; +import { FileListWithViewer } from "components/FileListWithViewer"; import { FixCreationTime } from "components/FixCreationTime"; import { Sidebar } from "components/Sidebar"; import { Upload } from "components/Upload"; @@ -41,6 +41,10 @@ import { import { savedAuthToken } from "ente-base/token"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; +import type { + SetFilesDownloadProgressAttributes, + SetFilesDownloadProgressAttributesCreator, +} from "ente-gallery/services/save"; import { type Collection } from "ente-media/collection"; import { type EnteFile } from "ente-media/file"; import { type ItemVisibility } from "ente-media/file-metadata"; @@ -123,13 +127,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { FileWithPath } from "react-dropzone"; import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; -import type { - SelectedState, -} from "types/gallery"; -import type { - SetFilesDownloadProgressAttributes, - SetFilesDownloadProgressAttributesCreator, -} from "ente-gallery/services/save"; +import type { SelectedState } from "types/gallery"; import { getSelectedFiles, performFileOp } from "utils/file"; /** diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 88b140a2ed..85865797df 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -6,9 +6,9 @@ import DownloadIcon from "@mui/icons-material/Download"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material"; import Typography from "@mui/material/Typography"; +import { FilesDownloadProgress } from "components/DownloadProgress"; import type { TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; -import { FilesDownloadProgress } from "components/FilesDownloadProgress"; import { Upload } from "components/Upload"; import { AccountsPageContents, diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts index 8a27adc307..47f1f1c1b7 100644 --- a/web/apps/photos/src/types/gallery/index.ts +++ b/web/apps/photos/src/types/gallery/index.ts @@ -1,4 +1,3 @@ -import { type FilesDownloadProgressAttributes } from "components/FilesDownloadProgress"; import { type SelectionContext } from "ente-new/photos/components/gallery"; export interface SelectedState { @@ -18,7 +17,6 @@ export type SetSelectedState = React.Dispatch< >; export type SetLoading = React.Dispatch>; - export interface MergedSourceURL { original: string; converted: string; diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index a3c59e4635..627f76e8a8 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -256,13 +256,13 @@ const saveFileDesktop = async ( ) => { const fs = electron.fs; - const createSafeName = (fileName: string) => + const createExportName = (fileName: string) => safeFileName(directoryPath, fileName, fs.exists); const writeStreamToFile = ( - fileName: string, + exportName: string, stream: ReadableStream | null, - ) => writeStream(electron, joinPath(directoryPath, fileName), stream); + ) => writeStream(electron, joinPath(directoryPath, exportName), stream); const stream = await downloadManager.fileStream(file); const fileName = fileFileName(file); @@ -270,11 +270,11 @@ const saveFileDesktop = async ( if (file.metadata.fileType == FileType.livePhoto) { const { imageFileName, imageData, videoFileName, videoData } = await decodeLivePhoto(fileName, await new Response(stream).blob()); - const imageExportName = await createSafeName(imageFileName); + const imageExportName = await createExportName(imageFileName); await writeStreamToFile(imageExportName, new Response(imageData).body); try { await writeStreamToFile( - await createSafeName(videoFileName), + await createExportName(videoFileName), new Response(videoData).body, ); } catch (e) { @@ -282,7 +282,7 @@ const saveFileDesktop = async ( throw e; } } else { - await writeStreamToFile(await createSafeName(fileName), stream); + await writeStreamToFile(await createExportName(fileName), stream); } }; From 9f12229b28a034bc5d7389214dddebe47098519b Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:12:46 +0530 Subject: [PATCH 044/109] Move mobile/ -> mobile/app/photos --- mobile/{ => apps/photos}/.gitattributes | 0 mobile/{ => apps/photos}/.gitignore | 0 .../.gradle/6.5.1/fileHashes/fileHashes.lock | Bin .../{ => apps/photos}/.gradle/6.5.1/gc.properties | 0 .../photos}/.gradle/checksums/checksums.lock | Bin .../{ => apps/photos}/.gradle/vcs-1/gc.properties | 0 mobile/{ => apps/photos}/.metadata | 0 mobile/{ => apps/photos}/CHANGELOG.md | 0 mobile/{ => apps/photos}/Gemfile | 0 mobile/{ => apps/photos}/Gemfile.lock | 0 mobile/{ => apps/photos}/README.md | 0 mobile/{ => apps/photos}/analysis_options.yaml | 0 mobile/{ => apps/photos}/android/.gitignore | 0 mobile/{ => apps/photos}/android/.project | 0 mobile/{ => apps/photos}/android/app/.classpath | 0 mobile/{ => apps/photos}/android/app/.project | 0 mobile/{ => apps/photos}/android/app/build.gradle | 0 .../photos}/android/app/proguard-rules.pro | 0 .../android/app/src/debug/AndroidManifest.xml | 0 .../android/app/src/debug/res/values/strings.xml | 0 .../photos}/android/app/src/dev/AndroidManifest.xml | 0 .../app/src/dev/res/mipmap-hdpi/launcher_icon.png | Bin .../app/src/dev/res/mipmap-mdpi/launcher_icon.png | Bin .../app/src/dev/res/mipmap-xhdpi/launcher_icon.png | Bin .../app/src/dev/res/mipmap-xxhdpi/launcher_icon.png | Bin .../src/dev/res/mipmap-xxxhdpi/launcher_icon.png | Bin .../android/app/src/dev/res/values/strings.xml | 0 .../android/app/src/fdroid/AndroidManifest.xml | 0 .../android/app/src/independent/AndroidManifest.xml | 0 .../android/app/src/main/AndroidManifest.xml | 0 .../io/ente/photos/EnteAlbumsWidgetProvider.kt | 0 .../io/ente/photos/EnteMemoryWidgetProvider.kt | 0 .../io/ente/photos/EntePeopleWidgetProvider.kt | 0 .../src/main/kotlin/io/ente/photos/MainActivity.kt | 0 .../src/main/play/listings/de/full-description.txt | 0 .../src/main/play/listings/de/short-description.txt | 0 .../android/app/src/main/play/listings/de/title.txt | 0 .../main/play/listings/en-US/full-description.txt | 0 .../main/play/listings/en-US/graphics/icon/icon.png | Bin .../listings/en-US/graphics/phone-screenshots/1.png | Bin .../listings/en-US/graphics/phone-screenshots/2.png | Bin .../listings/en-US/graphics/phone-screenshots/3.png | Bin .../listings/en-US/graphics/phone-screenshots/4.png | Bin .../listings/en-US/graphics/phone-screenshots/5.png | Bin .../listings/en-US/graphics/phone-screenshots/6.png | Bin .../listings/en-US/graphics/phone-screenshots/7.png | Bin .../main/play/listings/en-US/short-description.txt | 0 .../app/src/main/play/listings/en-US/title.txt | 0 .../src/main/play/listings/es/full-description.txt | 0 .../src/main/play/listings/es/short-description.txt | 0 .../android/app/src/main/play/listings/es/title.txt | 0 .../src/main/play/listings/fr/full-description.txt | 0 .../src/main/play/listings/fr/short-description.txt | 0 .../android/app/src/main/play/listings/fr/title.txt | 0 .../src/main/play/listings/he/full-description.txt | 0 .../src/main/play/listings/he/short-description.txt | 0 .../android/app/src/main/play/listings/he/title.txt | 0 .../src/main/play/listings/it/full-description.txt | 0 .../src/main/play/listings/it/short-description.txt | 0 .../android/app/src/main/play/listings/it/title.txt | 0 .../src/main/play/listings/nl/full-description.txt | 0 .../src/main/play/listings/nl/short-description.txt | 0 .../android/app/src/main/play/listings/nl/title.txt | 0 .../src/main/play/listings/pl/short-description.txt | 0 .../android/app/src/main/play/listings/pl/title.txt | 0 .../src/main/play/listings/pt/full-description.txt | 0 .../src/main/play/listings/pt/short-description.txt | 0 .../android/app/src/main/play/listings/pt/title.txt | 0 .../src/main/play/listings/ru/full-description.txt | 0 .../src/main/play/listings/ru/short-description.txt | 0 .../android/app/src/main/play/listings/ru/title.txt | 0 .../src/main/play/listings/zh/full-description.txt | 0 .../src/main/play/listings/zh/short-description.txt | 0 .../android/app/src/main/play/listings/zh/title.txt | 0 .../src/main/res/drawable-hdpi/android12splash.png | Bin .../src/main/res/drawable-hdpi/ic_albums_widget.png | Bin .../res/drawable-hdpi/ic_launcher_foreground.png | Bin .../main/res/drawable-hdpi/ic_memories_widget.png | Bin .../ic_monochrome_launcher_foreground.png | Bin .../res/drawable-hdpi/ic_og_launcher_foreground.png | Bin .../src/main/res/drawable-hdpi/ic_people_widget.png | Bin .../app/src/main/res/drawable-hdpi/splash.png | Bin .../res/drawable-ldpi/ic_home_widget_default.png | Bin .../src/main/res/drawable-mdpi/android12splash.png | Bin .../src/main/res/drawable-mdpi/ic_albums_widget.png | Bin .../res/drawable-mdpi/ic_launcher_foreground.png | Bin .../main/res/drawable-mdpi/ic_memories_widget.png | Bin .../ic_monochrome_launcher_foreground.png | Bin .../res/drawable-mdpi/ic_og_launcher_foreground.png | Bin .../src/main/res/drawable-mdpi/ic_people_widget.png | Bin .../app/src/main/res/drawable-mdpi/splash.png | Bin .../res/drawable-night-hdpi/android12splash.png | Bin .../res/drawable-night-mdpi/android12splash.png | Bin .../src/main/res/drawable-night-v21/background.png | Bin .../res/drawable-night-v21/launch_background.xml | 0 .../res/drawable-night-xhdpi/android12splash.png | Bin .../res/drawable-night-xxhdpi/android12splash.png | Bin .../res/drawable-night-xxxhdpi/android12splash.png | Bin .../app/src/main/res/drawable-night/background.png | Bin .../main/res/drawable-night/launch_background.xml | 0 .../app/src/main/res/drawable-v21/background.png | Bin .../src/main/res/drawable-v21/launch_background.xml | 0 .../src/main/res/drawable-xhdpi/android12splash.png | Bin .../main/res/drawable-xhdpi/ic_albums_widget.png | Bin .../res/drawable-xhdpi/ic_launcher_foreground.png | Bin .../main/res/drawable-xhdpi/ic_memories_widget.png | Bin .../ic_monochrome_launcher_foreground.png | Bin .../drawable-xhdpi/ic_og_launcher_foreground.png | Bin .../main/res/drawable-xhdpi/ic_people_widget.png | Bin .../app/src/main/res/drawable-xhdpi/splash.png | Bin .../main/res/drawable-xxhdpi/android12splash.png | Bin .../main/res/drawable-xxhdpi/ic_albums_widget.png | Bin .../res/drawable-xxhdpi/ic_launcher_foreground.png | Bin .../main/res/drawable-xxhdpi/ic_memories_widget.png | Bin .../ic_monochrome_launcher_foreground.png | Bin .../drawable-xxhdpi/ic_og_launcher_foreground.png | Bin .../main/res/drawable-xxhdpi/ic_people_widget.png | Bin .../app/src/main/res/drawable-xxhdpi/splash.png | Bin .../main/res/drawable-xxxhdpi/android12splash.png | Bin .../main/res/drawable-xxxhdpi/ic_albums_widget.png | Bin .../res/drawable-xxxhdpi/ic_launcher_foreground.png | Bin .../res/drawable-xxxhdpi/ic_memories_widget.png | Bin .../ic_monochrome_launcher_foreground.png | Bin .../drawable-xxxhdpi/ic_og_launcher_foreground.png | Bin .../main/res/drawable-xxxhdpi/ic_people_widget.png | Bin .../app/src/main/res/drawable-xxxhdpi/splash.png | Bin .../src/main/res/drawable/albums_widget_preview.png | Bin .../app/src/main/res/drawable/background.png | Bin .../app/src/main/res/drawable/launch_background.xml | 0 .../src/main/res/drawable/memory_widget_preview.png | Bin .../app/src/main/res/drawable/notification_icon.png | Bin .../src/main/res/drawable/people_widget_preview.png | Bin .../app/src/main/res/drawable/widget_background.xml | 0 .../src/main/res/layout/albums_widget_layout.xml | 0 .../app/src/main/res/layout/gradient_overlay.xml | 0 .../src/main/res/layout/memory_widget_layout.xml | 0 .../src/main/res/layout/people_widget_layout.xml | 0 .../src/main/res/mipmap-anydpi-v26/icon_dark.xml | 0 .../src/main/res/mipmap-anydpi-v26/icon_green.xml | 0 .../src/main/res/mipmap-anydpi-v26/icon_light.xml | 0 .../app/src/main/res/mipmap-anydpi-v26/icon_og.xml | 0 .../main/res/mipmap-anydpi-v26/launcher_icon.xml | 0 .../app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-hdpi/icon_dark.png | Bin .../app/src/main/res/mipmap-hdpi/icon_green.png | Bin .../app/src/main/res/mipmap-hdpi/icon_light.png | Bin .../src/main/res/mipmap-hdpi/icon_monochrome.png | Bin .../app/src/main/res/mipmap-hdpi/icon_og.png | Bin .../app/src/main/res/mipmap-hdpi/launcher_icon.png | Bin .../app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-mdpi/icon_dark.png | Bin .../app/src/main/res/mipmap-mdpi/icon_green.png | Bin .../app/src/main/res/mipmap-mdpi/icon_light.png | Bin .../src/main/res/mipmap-mdpi/icon_monochrome.png | Bin .../app/src/main/res/mipmap-mdpi/icon_og.png | Bin .../app/src/main/res/mipmap-mdpi/launcher_icon.png | Bin .../app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xhdpi/icon_dark.png | Bin .../app/src/main/res/mipmap-xhdpi/icon_green.png | Bin .../app/src/main/res/mipmap-xhdpi/icon_light.png | Bin .../src/main/res/mipmap-xhdpi/icon_monochrome.png | Bin .../app/src/main/res/mipmap-xhdpi/icon_og.png | Bin .../app/src/main/res/mipmap-xhdpi/launcher_icon.png | Bin .../app/src/main/res/mipmap-xxhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxhdpi/icon_dark.png | Bin .../app/src/main/res/mipmap-xxhdpi/icon_green.png | Bin .../app/src/main/res/mipmap-xxhdpi/icon_light.png | Bin .../src/main/res/mipmap-xxhdpi/icon_monochrome.png | Bin .../app/src/main/res/mipmap-xxhdpi/icon_og.png | Bin .../src/main/res/mipmap-xxhdpi/launcher_icon.png | Bin .../app/src/main/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../app/src/main/res/mipmap-xxxhdpi/icon_dark.png | Bin .../app/src/main/res/mipmap-xxxhdpi/icon_green.png | Bin .../app/src/main/res/mipmap-xxxhdpi/icon_light.png | Bin .../src/main/res/mipmap-xxxhdpi/icon_monochrome.png | Bin .../app/src/main/res/mipmap-xxxhdpi/icon_og.png | Bin .../src/main/res/mipmap-xxxhdpi/launcher_icon.png | Bin .../app/src/main/res/values-night-v31/styles.xml | 0 .../app/src/main/res/values-night/colors.xml | 0 .../app/src/main/res/values-night/styles.xml | 0 .../android/app/src/main/res/values-v31/styles.xml | 0 .../android/app/src/main/res/values/colors.xml | 0 .../android/app/src/main/res/values/strings.xml | 0 .../android/app/src/main/res/values/styles.xml | 0 .../android/app/src/main/res/xml/albums_widget.xml | 0 .../app/src/main/res/xml/data_extraction_rules.xml | 0 .../android/app/src/main/res/xml/memory_widget.xml | 0 .../src/main/res/xml/network_security_config.xml | 0 .../android/app/src/main/res/xml/people_widget.xml | 0 .../android/app/src/playstore/AndroidManifest.xml | 0 .../android/app/src/profile/AndroidManifest.xml | 0 mobile/{ => apps/photos}/android/build.gradle | 0 mobile/{ => apps/photos}/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 mobile/{ => apps/photos}/android/permissions.md | 0 mobile/{ => apps/photos}/android/settings.gradle | 0 .../{ => apps/photos}/android/settings_aar.gradle | 0 .../photos}/assets/2.0x/active_subscription.png | Bin .../photos}/assets/2.0x/albums-widget-static.png | Bin .../photos}/assets/2.0x/create_new_album.png | Bin .../photos}/assets/2.0x/family_plan_leave.png | Bin .../photos}/assets/2.0x/gallery_locked.png | Bin .../assets/2.0x/loading_photos_background.png | Bin .../assets/2.0x/loading_photos_background_dark.png | Bin .../photos}/assets/2.0x/lock_screen_background.png | Bin mobile/{ => apps/photos}/assets/2.0x/map_world.png | Bin .../photos}/assets/2.0x/memories-widget-static.png | Bin .../photos}/assets/2.0x/new_empty_album.png | Bin .../photos}/assets/2.0x/new_empty_album_dark.png | Bin .../photos}/assets/2.0x/onboarding_lock.png | Bin .../photos}/assets/2.0x/onboarding_safe.png | Bin .../photos}/assets/2.0x/onboarding_sync.png | Bin .../photos}/assets/2.0x/people-widget-static.png | Bin .../photos}/assets/2.0x/popular_subscription.png | Bin .../photos}/assets/2.0x/processing-video-failed.png | Bin .../assets/2.0x/processing-video-success.png | Bin .../photos}/assets/2.0x/processing-video.png | Bin .../photos}/assets/2.0x/storage_card_background.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_AVI.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_GIF.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_HEIC.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_JPEG.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_JPG.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_MKV.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_MP4.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_PNG.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_WEBP.png | Bin mobile/{ => apps/photos}/assets/2.0x/type_live.png | Bin .../{ => apps/photos}/assets/2.0x/type_photos.png | Bin .../{ => apps/photos}/assets/2.0x/type_unknown.png | Bin .../{ => apps/photos}/assets/2.0x/type_videos.png | Bin .../photos}/assets/2.0x/video-processing-queued.png | Bin .../photos}/assets/3.0x/active_subscription.png | Bin .../photos}/assets/3.0x/albums-widget-static.png | Bin .../photos}/assets/3.0x/create_new_album.png | Bin .../photos}/assets/3.0x/family_plan_leave.png | Bin .../photos}/assets/3.0x/gallery_locked.png | Bin .../assets/3.0x/loading_photos_background.png | Bin .../assets/3.0x/loading_photos_background_dark.png | Bin .../photos}/assets/3.0x/lock_screen_background.png | Bin mobile/{ => apps/photos}/assets/3.0x/map_world.png | Bin .../photos}/assets/3.0x/memories-widget-static.png | Bin .../photos}/assets/3.0x/new_empty_album.png | Bin .../photos}/assets/3.0x/new_empty_album_dark.png | Bin .../photos}/assets/3.0x/onboarding_lock.png | Bin .../photos}/assets/3.0x/onboarding_safe.png | Bin .../photos}/assets/3.0x/onboarding_sync.png | Bin .../photos}/assets/3.0x/people-widget-static.png | Bin .../photos}/assets/3.0x/popular_subscription.png | Bin .../photos}/assets/3.0x/processing-video-failed.png | Bin .../assets/3.0x/processing-video-success.png | Bin .../photos}/assets/3.0x/processing-video.png | Bin .../photos}/assets/3.0x/storage_card_background.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_AVI.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_GIF.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_HEIC.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_JPEG.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_JPG.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_MKV.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_MP4.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_PNG.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_WEBP.png | Bin mobile/{ => apps/photos}/assets/3.0x/type_live.png | Bin .../{ => apps/photos}/assets/3.0x/type_photos.png | Bin .../{ => apps/photos}/assets/3.0x/type_unknown.png | Bin .../{ => apps/photos}/assets/3.0x/type_videos.png | Bin .../photos}/assets/3.0x/video-processing-queued.png | Bin .../photos}/assets/active_subscription.png | Bin .../photos}/assets/albums-widget-static.png | Bin .../{ => apps/photos}/assets/create_new_album.png | Bin mobile/{ => apps/photos}/assets/earth_blurred.png | Bin .../{ => apps/photos}/assets/family_plan_leave.png | Bin mobile/{ => apps/photos}/assets/gallery_locked.png | Bin mobile/{ => apps/photos}/assets/icon-light.png | Bin .../photos}/assets/icons/albums-widget-icon.svg | 0 .../photos}/assets/icons/guest_view_icon.svg | 0 .../{ => apps/photos}/assets/icons/legacy-dark.svg | 0 .../{ => apps/photos}/assets/icons/legacy-light.svg | 0 .../photos}/assets/icons/list_view_icon_dark.svg | 0 .../photos}/assets/icons/list_view_icon_light.svg | 0 .../photos}/assets/icons/memories-widget-icon.svg | 0 .../photos}/assets/icons/past-year-memory-icon.svg | 0 .../photos}/assets/icons/people-widget-icon.svg | 0 .../photos}/assets/icons/search_icon_dark.svg | 0 .../photos}/assets/icons/search_icon_light.svg | 0 .../photos}/assets/icons/smart-memory-icon.svg | 0 .../photos}/assets/launcher_icon/icon-dark.png | Bin .../photos}/assets/launcher_icon/icon-dev.png | Bin .../assets/launcher_icon/icon-foreground.png | Bin .../photos}/assets/launcher_icon/icon-green.png | Bin .../photos}/assets/launcher_icon/icon-light.png | Bin .../launcher_icon/icon-monochrome-foreground.png | Bin .../assets/launcher_icon/icon-og-foreground.png | Bin .../photos}/assets/launcher_icon/icon-og.png | Bin .../photos}/assets/loadingGalleryLottie.json | 0 .../photos}/assets/loading_photos_background.png | Bin .../assets/loading_photos_background_dark.png | Bin .../photos}/assets/lock_screen_background.png | Bin mobile/{ => apps/photos}/assets/map.png | Bin mobile/{ => apps/photos}/assets/map_world.png | Bin .../photos}/assets/memories-widget-static.png | Bin mobile/{ => apps/photos}/assets/new_empty_album.png | Bin .../photos}/assets/new_empty_album_dark.png | Bin mobile/{ => apps/photos}/assets/onboarding_lock.png | Bin mobile/{ => apps/photos}/assets/onboarding_safe.png | Bin mobile/{ => apps/photos}/assets/onboarding_sync.png | Bin .../photos}/assets/people-widget-static.png | Bin .../photos}/assets/popular_subscription.png | Bin mobile/{ => apps/photos}/assets/preserved_green.png | Bin .../photos}/assets/processing-video-failed.png | Bin .../photos}/assets/processing-video-success.png | Bin .../{ => apps/photos}/assets/processing-video.png | Bin .../{ => apps/photos}/assets/splash-screen-icon.png | Bin .../photos}/assets/storage_card_background.png | Bin mobile/{ => apps/photos}/assets/type_AVI.png | Bin mobile/{ => apps/photos}/assets/type_GIF.png | Bin mobile/{ => apps/photos}/assets/type_HEIC.png | Bin mobile/{ => apps/photos}/assets/type_JPEG.png | Bin mobile/{ => apps/photos}/assets/type_JPG.png | Bin mobile/{ => apps/photos}/assets/type_MKV.png | Bin mobile/{ => apps/photos}/assets/type_MP4.png | Bin mobile/{ => apps/photos}/assets/type_PNG.png | Bin mobile/{ => apps/photos}/assets/type_WEBP.png | Bin mobile/{ => apps/photos}/assets/type_live.png | Bin mobile/{ => apps/photos}/assets/type_photos.png | Bin mobile/{ => apps/photos}/assets/type_unknown.png | Bin mobile/{ => apps/photos}/assets/type_videos.png | Bin .../assets/video-editor/video-crop-free-action.svg | 0 .../video-editor/video-crop-original-action.svg | 0 .../video-editor/video-crop-ratio_16_9-action.svg | 0 .../video-editor/video-crop-ratio_1_1-action.svg | 0 .../video-editor/video-crop-ratio_3_4-action.svg | 0 .../video-editor/video-crop-ratio_4_3-action.svg | 0 .../video-editor/video-crop-ratio_9_16-action.svg | 0 .../video-editor/video-editor-crop-action.svg | 0 .../video-editor/video-editor-rotate-action.svg | 0 .../video-editor/video-editor-trim-action.svg | 0 .../photos}/assets/video-processing-queued.png | Bin mobile/{ => apps/photos}/build-apk.sh | 0 mobile/{ => apps/photos}/crowdin.yml | 0 mobile/{ => apps/photos}/devtools_options.yaml | 0 mobile/{ => apps/photos}/docs/README.md | 0 .../photos}/docs/assets/translations_1.png | Bin .../photos}/docs/assets/translations_2.png | Bin .../photos}/docs/assets/translations_3.png | Bin .../photos}/docs/assets/translations_4.png | Bin mobile/{ => apps/photos}/docs/dev.md | 0 mobile/{ => apps/photos}/docs/release.md | 0 mobile/{ => apps/photos}/docs/translations.md | 0 mobile/{ => apps/photos}/docs/vscode/launch.json | 0 mobile/{ => apps/photos}/fastlane/Appfile | 0 mobile/{ => apps/photos}/fastlane/Fastfile | 0 mobile/{ => apps/photos}/fastlane/Pluginfile | 0 mobile/{ => apps/photos}/fastlane/README.md | 0 .../metadata/android/ar/full_description.txt | 0 .../metadata/android/ar/short_description.txt | 0 .../photos}/fastlane/metadata/android/ar/title.txt | 0 .../metadata/android/be/full_description.txt | 0 .../metadata/android/be/short_description.txt | 0 .../photos}/fastlane/metadata/android/be/title.txt | 0 .../metadata/android/bg/full_description.txt | 0 .../metadata/android/bg/short_description.txt | 0 .../photos}/fastlane/metadata/android/bg/title.txt | 0 .../metadata/android/ca/full_description.txt | 0 .../metadata/android/ca/short_description.txt | 0 .../photos}/fastlane/metadata/android/ca/title.txt | 0 .../metadata/android/cs/full_description.txt | 0 .../metadata/android/cs/short_description.txt | 0 .../photos}/fastlane/metadata/android/cs/title.txt | 0 .../metadata/android/da/full_description.txt | 0 .../metadata/android/da/short_description.txt | 0 .../photos}/fastlane/metadata/android/da/title.txt | 0 .../metadata/android/de/full_description.txt | 0 .../metadata/android/de/short_description.txt | 0 .../photos}/fastlane/metadata/android/de/title.txt | 0 .../metadata/android/el/full_description.txt | 0 .../metadata/android/el/short_description.txt | 0 .../photos}/fastlane/metadata/android/el/title.txt | 0 .../metadata/android/en-US/changelogs/169.txt | 0 .../metadata/android/en-US/changelogs/293.txt | 0 .../metadata/android/en-US/changelogs/317.txt | 0 .../metadata/android/en-US/changelogs/330.txt | 0 .../metadata/android/en-US/changelogs/331.txt | 0 .../metadata/android/en-US/changelogs/333.txt | 0 .../metadata/android/en-US/changelogs/420.txt | 0 .../metadata/android/en-US/full_description.txt | 0 .../fastlane/metadata/android/en-US/images/icon.png | Bin .../android/en-US/images/phoneScreenshots/1.png | Bin .../android/en-US/images/phoneScreenshots/2.png | Bin .../android/en-US/images/phoneScreenshots/3.png | Bin .../android/en-US/images/phoneScreenshots/4.png | Bin .../android/en-US/images/phoneScreenshots/5.png | Bin .../android/en-US/images/phoneScreenshots/6.png | Bin .../android/en-US/images/phoneScreenshots/7.png | Bin .../metadata/android/en-US/short_description.txt | 0 .../fastlane/metadata/android/en-US/title.txt | 0 .../metadata/android/es/full_description.txt | 0 .../metadata/android/es/short_description.txt | 0 .../photos}/fastlane/metadata/android/es/title.txt | 0 .../metadata/android/et/full_description.txt | 0 .../metadata/android/et/short_description.txt | 0 .../photos}/fastlane/metadata/android/et/title.txt | 0 .../metadata/android/eu/full_description.txt | 0 .../metadata/android/eu/short_description.txt | 0 .../photos}/fastlane/metadata/android/eu/title.txt | 0 .../metadata/android/fa/full_description.txt | 0 .../metadata/android/fa/short_description.txt | 0 .../photos}/fastlane/metadata/android/fa/title.txt | 0 .../metadata/android/fr/full_description.txt | 0 .../metadata/android/fr/short_description.txt | 0 .../photos}/fastlane/metadata/android/fr/title.txt | 0 .../metadata/android/gu/full_description.txt | 0 .../metadata/android/gu/short_description.txt | 0 .../photos}/fastlane/metadata/android/gu/title.txt | 0 .../metadata/android/he/full_description.txt | 0 .../metadata/android/he/short_description.txt | 0 .../photos}/fastlane/metadata/android/he/title.txt | 0 .../metadata/android/hi/full_description.txt | 0 .../metadata/android/hi/short_description.txt | 0 .../photos}/fastlane/metadata/android/hi/title.txt | 0 .../metadata/android/hu/full_description.txt | 0 .../metadata/android/hu/short_description.txt | 0 .../photos}/fastlane/metadata/android/hu/title.txt | 0 .../metadata/android/id/full_description.txt | 0 .../metadata/android/id/short_description.txt | 0 .../photos}/fastlane/metadata/android/id/title.txt | 0 .../metadata/android/it/full_description.txt | 0 .../metadata/android/it/short_description.txt | 0 .../photos}/fastlane/metadata/android/it/title.txt | 0 .../metadata/android/ja/full_description.txt | 0 .../metadata/android/ja/short_description.txt | 0 .../photos}/fastlane/metadata/android/ja/title.txt | 0 .../metadata/android/km/full_description.txt | 0 .../metadata/android/km/short_description.txt | 0 .../photos}/fastlane/metadata/android/km/title.txt | 0 .../metadata/android/ko/full_description.txt | 0 .../metadata/android/ko/short_description.txt | 0 .../photos}/fastlane/metadata/android/ko/title.txt | 0 .../metadata/android/ku/full_description.txt | 0 .../metadata/android/ku/short_description.txt | 0 .../photos}/fastlane/metadata/android/ku/title.txt | 0 .../metadata/android/lt/full_description.txt | 0 .../metadata/android/lt/short_description.txt | 0 .../photos}/fastlane/metadata/android/lt/title.txt | 0 .../metadata/android/lv/full_description.txt | 0 .../metadata/android/lv/short_description.txt | 0 .../photos}/fastlane/metadata/android/lv/title.txt | 0 .../metadata/android/ml/full_description.txt | 0 .../metadata/android/ml/short_description.txt | 0 .../photos}/fastlane/metadata/android/ml/title.txt | 0 .../metadata/android/nl/full_description.txt | 0 .../metadata/android/nl/short_description.txt | 0 .../photos}/fastlane/metadata/android/nl/title.txt | 0 .../metadata/android/no/full_description.txt | 0 .../metadata/android/no/short_description.txt | 0 .../photos}/fastlane/metadata/android/no/title.txt | 0 .../metadata/android/or/full_description.txt | 0 .../metadata/android/or/short_description.txt | 0 .../photos}/fastlane/metadata/android/or/title.txt | 0 .../metadata/android/pl/full_description.txt | 0 .../metadata/android/pl/short_description.txt | 0 .../photos}/fastlane/metadata/android/pl/title.txt | 0 .../metadata/android/pt/full_description.txt | 0 .../metadata/android/pt/short_description.txt | 0 .../photos}/fastlane/metadata/android/pt/title.txt | 0 .../metadata/android/pt_BR/full_description.txt | 0 .../metadata/android/pt_BR/short_description.txt | 0 .../fastlane/metadata/android/pt_BR/title.txt | 0 .../metadata/android/pt_PT/full_description.txt | 0 .../metadata/android/pt_PT/short_description.txt | 0 .../fastlane/metadata/android/pt_PT/title.txt | 0 .../metadata/android/ro/full_description.txt | 0 .../metadata/android/ro/short_description.txt | 0 .../photos}/fastlane/metadata/android/ro/title.txt | 0 .../metadata/android/ru/full_description.txt | 0 .../metadata/android/ru/short_description.txt | 0 .../photos}/fastlane/metadata/android/ru/title.txt | 0 .../metadata/android/sl/full_description.txt | 0 .../metadata/android/sl/short_description.txt | 0 .../photos}/fastlane/metadata/android/sl/title.txt | 0 .../metadata/android/sr/full_description.txt | 0 .../metadata/android/sr/short_description.txt | 0 .../photos}/fastlane/metadata/android/sr/title.txt | 0 .../metadata/android/sv/full_description.txt | 0 .../metadata/android/sv/short_description.txt | 0 .../photos}/fastlane/metadata/android/sv/title.txt | 0 .../metadata/android/ta/full_description.txt | 0 .../metadata/android/ta/short_description.txt | 0 .../photos}/fastlane/metadata/android/ta/title.txt | 0 .../metadata/android/te/full_description.txt | 0 .../metadata/android/te/short_description.txt | 0 .../photos}/fastlane/metadata/android/te/title.txt | 0 .../metadata/android/th/full_description.txt | 0 .../metadata/android/th/short_description.txt | 0 .../photos}/fastlane/metadata/android/th/title.txt | 0 .../metadata/android/ti/full_description.txt | 0 .../metadata/android/ti/short_description.txt | 0 .../photos}/fastlane/metadata/android/ti/title.txt | 0 .../metadata/android/tr/full_description.txt | 0 .../metadata/android/tr/short_description.txt | 0 .../photos}/fastlane/metadata/android/tr/title.txt | 0 .../metadata/android/uk/full_description.txt | 0 .../metadata/android/uk/short_description.txt | 0 .../photos}/fastlane/metadata/android/uk/title.txt | 0 .../metadata/android/vi/full_description.txt | 0 .../metadata/android/vi/short_description.txt | 0 .../photos}/fastlane/metadata/android/vi/title.txt | 0 .../metadata/android/zh/full_description.txt | 0 .../metadata/android/zh/short_description.txt | 0 .../photos}/fastlane/metadata/android/zh/title.txt | 0 .../ios/Screenshots/en-US/0_APP_IPAD_PRO_129_0.png | Bin .../Screenshots/en-US/0_APP_IPAD_PRO_3GEN_129_0.png | Bin .../ios/Screenshots/en-US/0_APP_IPHONE_55_0.png | Bin .../ios/Screenshots/en-US/0_APP_IPHONE_65_0.png | Bin .../ios/Screenshots/en-US/1_APP_IPHONE_55_1.png | Bin .../ios/Screenshots/en-US/1_APP_IPHONE_65_1.png | Bin .../ios/Screenshots/en-US/2_APP_IPHONE_55_2.png | Bin .../ios/Screenshots/en-US/2_APP_IPHONE_65_2.png | Bin .../ios/Screenshots/en-US/3_APP_IPHONE_55_3.png | Bin .../ios/Screenshots/en-US/3_APP_IPHONE_65_3.png | Bin .../ios/Screenshots/en-US/4_APP_IPHONE_55_4.png | Bin .../ios/Screenshots/en-US/4_APP_IPHONE_65_4.png | Bin .../ios/Screenshots/en-US/5_APP_IPHONE_55_5.png | Bin .../ios/Screenshots/en-US/5_APP_IPHONE_65_5.png | Bin .../ios/Screenshots/en-US/6_APP_IPHONE_55_6.png | Bin .../ios/Screenshots/en-US/6_APP_IPHONE_65_6.png | Bin .../fastlane/metadata/ios/ar/description.txt | 0 .../photos}/fastlane/metadata/ios/ar/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ar/name.txt | 0 .../photos}/fastlane/metadata/ios/ar/subtitle.txt | 0 .../fastlane/metadata/ios/be/description.txt | 0 .../photos}/fastlane/metadata/ios/be/keywords.txt | 0 .../photos}/fastlane/metadata/ios/be/name.txt | 0 .../photos}/fastlane/metadata/ios/be/subtitle.txt | 0 .../fastlane/metadata/ios/bg/description.txt | 0 .../photos}/fastlane/metadata/ios/bg/keywords.txt | 0 .../photos}/fastlane/metadata/ios/bg/name.txt | 0 .../photos}/fastlane/metadata/ios/bg/subtitle.txt | 0 .../fastlane/metadata/ios/ca/description.txt | 0 .../photos}/fastlane/metadata/ios/ca/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ca/name.txt | 0 .../photos}/fastlane/metadata/ios/ca/subtitle.txt | 0 .../fastlane/metadata/ios/cs/description.txt | 0 .../photos}/fastlane/metadata/ios/cs/keywords.txt | 0 .../photos}/fastlane/metadata/ios/cs/name.txt | 0 .../photos}/fastlane/metadata/ios/cs/subtitle.txt | 0 .../fastlane/metadata/ios/da/description.txt | 0 .../photos}/fastlane/metadata/ios/da/keywords.txt | 0 .../photos}/fastlane/metadata/ios/da/name.txt | 0 .../photos}/fastlane/metadata/ios/da/subtitle.txt | 0 .../fastlane/metadata/ios/de/description.txt | 0 .../photos}/fastlane/metadata/ios/de/keywords.txt | 0 .../photos}/fastlane/metadata/ios/de/name.txt | 0 .../photos}/fastlane/metadata/ios/de/subtitle.txt | 0 .../fastlane/metadata/ios/el/description.txt | 0 .../photos}/fastlane/metadata/ios/el/keywords.txt | 0 .../photos}/fastlane/metadata/ios/el/name.txt | 0 .../photos}/fastlane/metadata/ios/el/subtitle.txt | 0 .../fastlane/metadata/ios/en-US/description.txt | 0 .../fastlane/metadata/ios/en-US/keywords.txt | 0 .../photos}/fastlane/metadata/ios/en-US/name.txt | 0 .../fastlane/metadata/ios/en-US/subtitle.txt | 0 .../fastlane/metadata/ios/es/description.txt | 0 .../photos}/fastlane/metadata/ios/es/keywords.txt | 0 .../photos}/fastlane/metadata/ios/es/name.txt | 0 .../photos}/fastlane/metadata/ios/es/subtitle.txt | 0 .../fastlane/metadata/ios/et/description.txt | 0 .../photos}/fastlane/metadata/ios/et/keywords.txt | 0 .../photos}/fastlane/metadata/ios/et/name.txt | 0 .../photos}/fastlane/metadata/ios/et/subtitle.txt | 0 .../fastlane/metadata/ios/eu/description.txt | 0 .../photos}/fastlane/metadata/ios/eu/keywords.txt | 0 .../photos}/fastlane/metadata/ios/eu/name.txt | 0 .../photos}/fastlane/metadata/ios/eu/subtitle.txt | 0 .../fastlane/metadata/ios/fa/description.txt | 0 .../photos}/fastlane/metadata/ios/fa/keywords.txt | 0 .../photos}/fastlane/metadata/ios/fa/name.txt | 0 .../photos}/fastlane/metadata/ios/fa/subtitle.txt | 0 .../fastlane/metadata/ios/fr/description.txt | 0 .../photos}/fastlane/metadata/ios/fr/keywords.txt | 0 .../photos}/fastlane/metadata/ios/fr/name.txt | 0 .../photos}/fastlane/metadata/ios/fr/subtitle.txt | 0 .../fastlane/metadata/ios/gu/description.txt | 0 .../photos}/fastlane/metadata/ios/gu/keywords.txt | 0 .../photos}/fastlane/metadata/ios/gu/name.txt | 0 .../photos}/fastlane/metadata/ios/gu/subtitle.txt | 0 .../fastlane/metadata/ios/he/description.txt | 0 .../photos}/fastlane/metadata/ios/he/keywords.txt | 0 .../photos}/fastlane/metadata/ios/he/name.txt | 0 .../photos}/fastlane/metadata/ios/he/subtitle.txt | 0 .../fastlane/metadata/ios/hi/description.txt | 0 .../photos}/fastlane/metadata/ios/hi/keywords.txt | 0 .../photos}/fastlane/metadata/ios/hi/name.txt | 0 .../photos}/fastlane/metadata/ios/hi/subtitle.txt | 0 .../fastlane/metadata/ios/hu/description.txt | 0 .../photos}/fastlane/metadata/ios/hu/keywords.txt | 0 .../photos}/fastlane/metadata/ios/hu/name.txt | 0 .../photos}/fastlane/metadata/ios/hu/subtitle.txt | 0 .../fastlane/metadata/ios/id/description.txt | 0 .../photos}/fastlane/metadata/ios/id/keywords.txt | 0 .../photos}/fastlane/metadata/ios/id/name.txt | 0 .../photos}/fastlane/metadata/ios/id/subtitle.txt | 0 .../fastlane/metadata/ios/it/description.txt | 0 .../photos}/fastlane/metadata/ios/it/keywords.txt | 0 .../photos}/fastlane/metadata/ios/it/name.txt | 0 .../photos}/fastlane/metadata/ios/it/subtitle.txt | 0 .../fastlane/metadata/ios/ja/description.txt | 0 .../photos}/fastlane/metadata/ios/ja/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ja/name.txt | 0 .../photos}/fastlane/metadata/ios/ja/subtitle.txt | 0 .../fastlane/metadata/ios/km/description.txt | 0 .../photos}/fastlane/metadata/ios/km/keywords.txt | 0 .../photos}/fastlane/metadata/ios/km/name.txt | 0 .../photos}/fastlane/metadata/ios/km/subtitle.txt | 0 .../fastlane/metadata/ios/ko/description.txt | 0 .../photos}/fastlane/metadata/ios/ko/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ko/name.txt | 0 .../photos}/fastlane/metadata/ios/ko/subtitle.txt | 0 .../fastlane/metadata/ios/ku/description.txt | 0 .../photos}/fastlane/metadata/ios/ku/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ku/name.txt | 0 .../photos}/fastlane/metadata/ios/ku/subtitle.txt | 0 .../fastlane/metadata/ios/lt/description.txt | 0 .../photos}/fastlane/metadata/ios/lt/keywords.txt | 0 .../photos}/fastlane/metadata/ios/lt/name.txt | 0 .../photos}/fastlane/metadata/ios/lt/subtitle.txt | 0 .../fastlane/metadata/ios/lv/description.txt | 0 .../photos}/fastlane/metadata/ios/lv/keywords.txt | 0 .../photos}/fastlane/metadata/ios/lv/name.txt | 0 .../photos}/fastlane/metadata/ios/lv/subtitle.txt | 0 .../fastlane/metadata/ios/ml/description.txt | 0 .../photos}/fastlane/metadata/ios/ml/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ml/name.txt | 0 .../photos}/fastlane/metadata/ios/ml/subtitle.txt | 0 .../fastlane/metadata/ios/nl/description.txt | 0 .../photos}/fastlane/metadata/ios/nl/keywords.txt | 0 .../photos}/fastlane/metadata/ios/nl/name.txt | 0 .../photos}/fastlane/metadata/ios/nl/subtitle.txt | 0 .../fastlane/metadata/ios/no/description.txt | 0 .../photos}/fastlane/metadata/ios/no/keywords.txt | 0 .../photos}/fastlane/metadata/ios/no/name.txt | 0 .../photos}/fastlane/metadata/ios/no/subtitle.txt | 0 .../fastlane/metadata/ios/or/description.txt | 0 .../photos}/fastlane/metadata/ios/or/keywords.txt | 0 .../photos}/fastlane/metadata/ios/or/name.txt | 0 .../photos}/fastlane/metadata/ios/or/subtitle.txt | 0 .../fastlane/metadata/ios/pl/description.txt | 0 .../photos}/fastlane/metadata/ios/pl/keywords.txt | 0 .../photos}/fastlane/metadata/ios/pl/name.txt | 0 .../photos}/fastlane/metadata/ios/pl/subtitle.txt | 0 .../fastlane/metadata/ios/pt/description.txt | 0 .../photos}/fastlane/metadata/ios/pt/keywords.txt | 0 .../photos}/fastlane/metadata/ios/pt/name.txt | 0 .../photos}/fastlane/metadata/ios/pt/subtitle.txt | 0 .../fastlane/metadata/ios/pt_BR/description.txt | 0 .../fastlane/metadata/ios/pt_BR/keywords.txt | 0 .../photos}/fastlane/metadata/ios/pt_BR/name.txt | 0 .../fastlane/metadata/ios/pt_BR/subtitle.txt | 0 .../fastlane/metadata/ios/pt_PT/description.txt | 0 .../fastlane/metadata/ios/pt_PT/keywords.txt | 0 .../photos}/fastlane/metadata/ios/pt_PT/name.txt | 0 .../fastlane/metadata/ios/pt_PT/subtitle.txt | 0 .../fastlane/metadata/ios/ro/description.txt | 0 .../photos}/fastlane/metadata/ios/ro/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ro/name.txt | 0 .../photos}/fastlane/metadata/ios/ro/subtitle.txt | 0 .../fastlane/metadata/ios/ru/description.txt | 0 .../photos}/fastlane/metadata/ios/ru/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ru/name.txt | 0 .../photos}/fastlane/metadata/ios/ru/subtitle.txt | 0 .../fastlane/metadata/ios/sl/description.txt | 0 .../photos}/fastlane/metadata/ios/sl/keywords.txt | 0 .../photos}/fastlane/metadata/ios/sl/name.txt | 0 .../photos}/fastlane/metadata/ios/sl/subtitle.txt | 0 .../fastlane/metadata/ios/sr/description.txt | 0 .../photos}/fastlane/metadata/ios/sr/keywords.txt | 0 .../photos}/fastlane/metadata/ios/sr/name.txt | 0 .../photos}/fastlane/metadata/ios/sr/subtitle.txt | 0 .../fastlane/metadata/ios/sv/description.txt | 0 .../photos}/fastlane/metadata/ios/sv/keywords.txt | 0 .../photos}/fastlane/metadata/ios/sv/name.txt | 0 .../photos}/fastlane/metadata/ios/sv/subtitle.txt | 0 .../fastlane/metadata/ios/ta/description.txt | 0 .../photos}/fastlane/metadata/ios/ta/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ta/name.txt | 0 .../photos}/fastlane/metadata/ios/ta/subtitle.txt | 0 .../fastlane/metadata/ios/te/description.txt | 0 .../photos}/fastlane/metadata/ios/te/keywords.txt | 0 .../photos}/fastlane/metadata/ios/te/name.txt | 0 .../photos}/fastlane/metadata/ios/te/subtitle.txt | 0 .../fastlane/metadata/ios/th/description.txt | 0 .../photos}/fastlane/metadata/ios/th/keywords.txt | 0 .../photos}/fastlane/metadata/ios/th/name.txt | 0 .../photos}/fastlane/metadata/ios/th/subtitle.txt | 0 .../fastlane/metadata/ios/ti/description.txt | 0 .../photos}/fastlane/metadata/ios/ti/keywords.txt | 0 .../photos}/fastlane/metadata/ios/ti/name.txt | 0 .../photos}/fastlane/metadata/ios/ti/subtitle.txt | 0 .../fastlane/metadata/ios/tr/description.txt | 0 .../photos}/fastlane/metadata/ios/tr/keywords.txt | 0 .../photos}/fastlane/metadata/ios/tr/name.txt | 0 .../photos}/fastlane/metadata/ios/tr/subtitle.txt | 0 .../fastlane/metadata/ios/uk/description.txt | 0 .../photos}/fastlane/metadata/ios/uk/keywords.txt | 0 .../photos}/fastlane/metadata/ios/uk/name.txt | 0 .../photos}/fastlane/metadata/ios/uk/subtitle.txt | 0 .../fastlane/metadata/ios/vi/description.txt | 0 .../photos}/fastlane/metadata/ios/vi/keywords.txt | 0 .../photos}/fastlane/metadata/ios/vi/name.txt | 0 .../photos}/fastlane/metadata/ios/vi/subtitle.txt | 0 .../fastlane/metadata/ios/zh/description.txt | 0 .../photos}/fastlane/metadata/ios/zh/keywords.txt | 0 .../photos}/fastlane/metadata/ios/zh/name.txt | 0 .../photos}/fastlane/metadata/ios/zh/subtitle.txt | 0 .../metadata/playstore/ar/full_description.txt | 0 .../metadata/playstore/ar/short_description.txt | 0 .../fastlane/metadata/playstore/ar/title.txt | 0 .../metadata/playstore/be/full_description.txt | 0 .../metadata/playstore/be/short_description.txt | 0 .../fastlane/metadata/playstore/be/title.txt | 0 .../metadata/playstore/bg/full_description.txt | 0 .../metadata/playstore/bg/short_description.txt | 0 .../fastlane/metadata/playstore/bg/title.txt | 0 .../metadata/playstore/ca/full_description.txt | 0 .../metadata/playstore/ca/short_description.txt | 0 .../fastlane/metadata/playstore/ca/title.txt | 0 .../metadata/playstore/cs/full_description.txt | 0 .../metadata/playstore/cs/short_description.txt | 0 .../fastlane/metadata/playstore/cs/title.txt | 0 .../metadata/playstore/da/full_description.txt | 0 .../metadata/playstore/da/short_description.txt | 0 .../fastlane/metadata/playstore/da/title.txt | 0 .../metadata/playstore/de/full_description.txt | 0 .../metadata/playstore/de/short_description.txt | 0 .../fastlane/metadata/playstore/de/title.txt | 0 .../metadata/playstore/el/full_description.txt | 0 .../metadata/playstore/el/short_description.txt | 0 .../fastlane/metadata/playstore/el/title.txt | 0 .../metadata/playstore/en-US/changelogs/443.txt | 0 .../metadata/playstore/en-US/full_description.txt | 0 .../playstore/en-US/images/featureGraphic.png | Bin .../metadata/playstore/en-US/images/icon.png | Bin .../en-US/images/phoneScreenshots/1_en-US.png | Bin .../en-US/images/phoneScreenshots/2_en-US.png | Bin .../en-US/images/phoneScreenshots/3_en-US.png | Bin .../en-US/images/phoneScreenshots/4_en-US.png | Bin .../en-US/images/phoneScreenshots/5_en-US.png | Bin .../en-US/images/phoneScreenshots/6_en-US.png | Bin .../en-US/images/phoneScreenshots/7_en-US.png | Bin .../en-US/images/sevenInchScreenshots/1_en-US.png | Bin .../en-US/images/tenInchScreenshots/1_en-US.png | Bin .../metadata/playstore/en-US/short_description.txt | 0 .../fastlane/metadata/playstore/en-US/title.txt | 0 .../metadata/playstore/es/full_description.txt | 0 .../metadata/playstore/es/short_description.txt | 0 .../fastlane/metadata/playstore/es/title.txt | 0 .../metadata/playstore/et/full_description.txt | 0 .../metadata/playstore/et/short_description.txt | 0 .../fastlane/metadata/playstore/et/title.txt | 0 .../metadata/playstore/eu/full_description.txt | 0 .../metadata/playstore/eu/short_description.txt | 0 .../fastlane/metadata/playstore/eu/title.txt | 0 .../metadata/playstore/fa/full_description.txt | 0 .../metadata/playstore/fa/short_description.txt | 0 .../fastlane/metadata/playstore/fa/title.txt | 0 .../metadata/playstore/fr/full_description.txt | 0 .../metadata/playstore/fr/short_description.txt | 0 .../fastlane/metadata/playstore/fr/title.txt | 0 .../metadata/playstore/gu/full_description.txt | 0 .../metadata/playstore/gu/short_description.txt | 0 .../fastlane/metadata/playstore/gu/title.txt | 0 .../metadata/playstore/he/full_description.txt | 0 .../metadata/playstore/he/short_description.txt | 0 .../fastlane/metadata/playstore/he/title.txt | 0 .../metadata/playstore/hi/full_description.txt | 0 .../metadata/playstore/hi/short_description.txt | 0 .../fastlane/metadata/playstore/hi/title.txt | 0 .../metadata/playstore/hu/full_description.txt | 0 .../metadata/playstore/hu/short_description.txt | 0 .../fastlane/metadata/playstore/hu/title.txt | 0 .../metadata/playstore/id/full_description.txt | 0 .../metadata/playstore/id/short_description.txt | 0 .../fastlane/metadata/playstore/id/title.txt | 0 .../metadata/playstore/it/full_description.txt | 0 .../metadata/playstore/it/short_description.txt | 0 .../fastlane/metadata/playstore/it/title.txt | 0 .../metadata/playstore/ja/full_description.txt | 0 .../metadata/playstore/ja/short_description.txt | 0 .../fastlane/metadata/playstore/ja/title.txt | 0 .../metadata/playstore/km/full_description.txt | 0 .../metadata/playstore/km/short_description.txt | 0 .../fastlane/metadata/playstore/km/title.txt | 0 .../metadata/playstore/ko/full_description.txt | 0 .../metadata/playstore/ko/short_description.txt | 0 .../fastlane/metadata/playstore/ko/title.txt | 0 .../metadata/playstore/ku/full_description.txt | 0 .../metadata/playstore/ku/short_description.txt | 0 .../fastlane/metadata/playstore/ku/title.txt | 0 .../metadata/playstore/lt/full_description.txt | 0 .../metadata/playstore/lt/short_description.txt | 0 .../fastlane/metadata/playstore/lt/title.txt | 0 .../metadata/playstore/lv/full_description.txt | 0 .../metadata/playstore/lv/short_description.txt | 0 .../fastlane/metadata/playstore/lv/title.txt | 0 .../metadata/playstore/ml/full_description.txt | 0 .../metadata/playstore/ml/short_description.txt | 0 .../fastlane/metadata/playstore/ml/title.txt | 0 .../metadata/playstore/nl/full_description.txt | 0 .../metadata/playstore/nl/short_description.txt | 0 .../fastlane/metadata/playstore/nl/title.txt | 0 .../metadata/playstore/no/full_description.txt | 0 .../metadata/playstore/no/short_description.txt | 0 .../fastlane/metadata/playstore/no/title.txt | 0 .../metadata/playstore/or/full_description.txt | 0 .../metadata/playstore/or/short_description.txt | 0 .../fastlane/metadata/playstore/or/title.txt | 0 .../metadata/playstore/pl/full_description.txt | 0 .../metadata/playstore/pl/short_description.txt | 0 .../fastlane/metadata/playstore/pl/title.txt | 0 .../metadata/playstore/pt/full_description.txt | 0 .../metadata/playstore/pt/short_description.txt | 0 .../fastlane/metadata/playstore/pt/title.txt | 0 .../metadata/playstore/pt_BR/full_description.txt | 0 .../metadata/playstore/pt_BR/short_description.txt | 0 .../fastlane/metadata/playstore/pt_BR/title.txt | 0 .../metadata/playstore/pt_PT/full_description.txt | 0 .../metadata/playstore/pt_PT/short_description.txt | 0 .../fastlane/metadata/playstore/pt_PT/title.txt | 0 .../metadata/playstore/ro/full_description.txt | 0 .../metadata/playstore/ro/short_description.txt | 0 .../fastlane/metadata/playstore/ro/title.txt | 0 .../metadata/playstore/ru/full_description.txt | 0 .../metadata/playstore/ru/short_description.txt | 0 .../fastlane/metadata/playstore/ru/title.txt | 0 .../metadata/playstore/sl/full_description.txt | 0 .../metadata/playstore/sl/short_description.txt | 0 .../fastlane/metadata/playstore/sl/title.txt | 0 .../metadata/playstore/sr/full_description.txt | 0 .../metadata/playstore/sr/short_description.txt | 0 .../fastlane/metadata/playstore/sr/title.txt | 0 .../metadata/playstore/sv/full_description.txt | 0 .../metadata/playstore/sv/short_description.txt | 0 .../fastlane/metadata/playstore/sv/title.txt | 0 .../metadata/playstore/ta/full_description.txt | 0 .../metadata/playstore/ta/short_description.txt | 0 .../fastlane/metadata/playstore/ta/title.txt | 0 .../metadata/playstore/te/full_description.txt | 0 .../metadata/playstore/te/short_description.txt | 0 .../fastlane/metadata/playstore/te/title.txt | 0 .../metadata/playstore/th/full_description.txt | 0 .../metadata/playstore/th/short_description.txt | 0 .../fastlane/metadata/playstore/th/title.txt | 0 .../metadata/playstore/ti/full_description.txt | 0 .../metadata/playstore/ti/short_description.txt | 0 .../fastlane/metadata/playstore/ti/title.txt | 0 .../metadata/playstore/tr/full_description.txt | 0 .../metadata/playstore/tr/short_description.txt | 0 .../fastlane/metadata/playstore/tr/title.txt | 0 .../metadata/playstore/uk/full_description.txt | 0 .../metadata/playstore/uk/short_description.txt | 0 .../fastlane/metadata/playstore/uk/title.txt | 0 .../metadata/playstore/vi/full_description.txt | 0 .../metadata/playstore/vi/short_description.txt | 0 .../fastlane/metadata/playstore/vi/title.txt | 0 .../metadata/playstore/zh/full_description.txt | 0 .../metadata/playstore/zh/short_description.txt | 0 .../fastlane/metadata/playstore/zh/title.txt | 0 mobile/{ => apps/photos}/fonts/Inter-Bold.ttf | Bin mobile/{ => apps/photos}/fonts/Inter-Light.ttf | Bin mobile/{ => apps/photos}/fonts/Inter-Medium.ttf | Bin mobile/{ => apps/photos}/fonts/Inter-Regular.ttf | Bin mobile/{ => apps/photos}/fonts/Inter-SemiBold.ttf | Bin mobile/{ => apps/photos}/fonts/Montserrat-Bold.ttf | Bin mobile/{ => apps/photos}/hooks/pre-commit | 0 mobile/{ => apps/photos}/hooks/pre-commit-fdroid | 0 .../photos}/integration_test/app_init_test.dart | 0 .../integration_test/home_gallery_scroll_test.dart | 0 mobile/{ => apps/photos}/ios/.gitignore | 0 .../AccentColor.colorset/Contents.json | 0 .../AlbumsWidgetDefault.png | Bin .../AlbumsWidgetDefault.imageset/Contents.json | 0 .../AlbumsWidgetPreview.png | Bin .../AlbumsWidgetPreview.imageset/Contents.json | 0 .../EnteAlbumWidget/Assets.xcassets/Contents.json | 0 .../IconGreen.appiconset/Contents.json | 0 .../WidgetBackground.colorset/Contents.json | 0 .../ios/EnteAlbumWidget/EnteAlbumWidget.swift | 0 .../ios/EnteAlbumWidget/EnteAlbumWidgetBundle.swift | 0 .../photos}/ios/EnteAlbumWidget/Info.plist | 0 .../ios/EnteAlbumWidgetExtension.entitlements | 0 .../AccentColor.colorset/Contents.json | 0 .../EnteMemoryWidget/Assets.xcassets/Contents.json | 0 .../IconGreen.appiconset/Contents.json | 0 .../MemoriesWidgetDefault.imageset/Contents.json | 0 .../MemoriesWidgetDefault.png | Bin .../MemoriesWidgetPreview.imageset/Contents.json | 0 .../MemoriesWidgetPreview.png | Bin .../WidgetBackground.colorset/Contents.json | 0 .../ios/EnteMemoryWidget/EnteMemoryWidget.swift | 0 .../EnteMemoryWidget/EnteMemoryWidgetBundle.swift | 0 .../photos}/ios/EnteMemoryWidget/Info.plist | 0 .../ios/EnteMemoryWidgetExtension.entitlements | 0 .../ios/EnteMemoryWidgetExtensionDebug.entitlements | 0 .../AccentColor.colorset/Contents.json | 0 .../EntePeopleWidget/Assets.xcassets/Contents.json | 0 .../IconGreen.appiconset/Contents.json | 0 .../PeopleWidgetDefault.imageset/Contents.json | 0 .../PeopleWidgetDefault.png | Bin .../PeopleWidgetPreview.imageset/Contents.json | 0 .../PeopleWidgetPreview.png | Bin .../WidgetBackground.colorset/Contents.json | 0 .../ios/EntePeopleWidget/EntePeopleWidget.swift | 0 .../EntePeopleWidget/EntePeopleWidgetBundle.swift | 0 .../photos}/ios/EntePeopleWidget/Info.plist | 0 .../ios/EntePeopleWidgetExtension.entitlements | 0 .../photos}/ios/Flutter/AppFrameworkInfo.plist | 0 mobile/{ => apps/photos}/ios/Flutter/Debug.xcconfig | 0 .../{ => apps/photos}/ios/Flutter/Release.xcconfig | 0 .../{ => apps/photos}/ios/GoogleService-Info.plist | 0 mobile/{ => apps/photos}/ios/Podfile | 0 mobile/{ => apps/photos}/ios/Podfile.lock | 0 .../photos}/ios/Runner.xcodeproj/project.pbxproj | 0 .../project.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../ios/Runner.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../{ => apps/photos}/ios/Runner/AppDelegate.swift | 0 .../ios/Runner/Assets.xcassets/Contents.json | 0 .../IconDark.appiconset/Contents.json | 0 .../IconDark.appiconset/IconDarkAny.png | Bin .../IconDark.appiconset/IconDarkDark.png | Bin .../IconDark.appiconset/IconDarkTinted.png | Bin .../IconGreen.appiconset/Contents.json | 0 .../IconGreen.appiconset/IconGreenAny.png | Bin .../IconGreen.appiconset/IconGreenDark.png | Bin .../IconGreen.appiconset/IconGreenTinted.png | Bin .../IconLight.appiconset/Contents.json | 0 .../IconLight.appiconset/IconLightAny.png | Bin .../IconLight.appiconset/IconLightDark.png | Bin .../IconLight.appiconset/IconLightTinted.png | Bin .../Assets.xcassets/IconOG.appiconset/Contents.json | 0 .../Assets.xcassets/IconOG.appiconset/IconOGAny.png | Bin .../IconOG.appiconset/IconOGDark.png | Bin .../IconOG.appiconset/IconOGTinted.png | Bin .../LaunchBackground.imageset/Contents.json | 0 .../LaunchBackground.imageset/background.png | Bin .../LaunchBackground.imageset/darkbackground.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../Assets.xcassets/LaunchImage.imageset/README.md | 0 .../ios/Runner/Base.lproj/LaunchScreen.storyboard | 0 .../photos}/ios/Runner/Base.lproj/Main.storyboard | 0 mobile/{ => apps/photos}/ios/Runner/Info.plist | 0 .../photos}/ios/Runner/Runner-Bridging-Header.h | 0 .../photos}/ios/Runner/Runner.entitlements | 0 mobile/{ => apps/photos}/l10n.yaml | 0 mobile/{ => apps/photos}/lib/app.dart | 0 .../photos}/lib/core/cache/image_cache.dart | 0 .../{ => apps/photos}/lib/core/cache/lru_map.dart | 0 .../lib/core/cache/thumbnail_in_memory_cache.dart | 0 .../photos}/lib/core/cache/video_cache_manager.dart | 0 .../{ => apps/photos}/lib/core/configuration.dart | 0 mobile/{ => apps/photos}/lib/core/constants.dart | 0 .../lib/core/error-reporting/isolate_logging.dart | 0 .../lib/core/error-reporting/super_logging.dart | 0 .../core/error-reporting/tunneled_transport.dart | 0 mobile/{ => apps/photos}/lib/core/errors.dart | 0 mobile/{ => apps/photos}/lib/core/event_bus.dart | 0 .../photos}/lib/core/network/ente_interceptor.dart | 0 .../{ => apps/photos}/lib/core/network/network.dart | 0 mobile/{ => apps/photos}/lib/data/holidays.dart | 0 mobile/{ => apps/photos}/lib/data/months.dart | 0 mobile/{ => apps/photos}/lib/data/years.dart | 0 mobile/{ => apps/photos}/lib/db/collections_db.dart | 0 mobile/{ => apps/photos}/lib/db/common/base.dart | 0 .../photos}/lib/db/common/conflict_algo.dart | 0 .../{ => apps/photos}/lib/db/device_files_db.dart | 0 mobile/{ => apps/photos}/lib/db/entities_db.dart | 0 .../{ => apps/photos}/lib/db/file_updation_db.dart | 0 mobile/{ => apps/photos}/lib/db/files_db.dart | 0 .../{ => apps/photos}/lib/db/ignored_files_db.dart | 0 mobile/{ => apps/photos}/lib/db/memories_db.dart | 0 mobile/{ => apps/photos}/lib/db/ml/base.dart | 0 mobile/{ => apps/photos}/lib/db/ml/db.dart | 0 .../photos}/lib/db/ml/db_model_mappers.dart | 0 mobile/{ => apps/photos}/lib/db/ml/filedata.dart | 0 mobile/{ => apps/photos}/lib/db/ml/schema.dart | 0 mobile/{ => apps/photos}/lib/db/trash_db.dart | 0 .../{ => apps/photos}/lib/db/upload_locks_db.dart | 0 .../photos}/lib/emergency/emergency_page.dart | 0 .../photos}/lib/emergency/emergency_service.dart | 0 mobile/{ => apps/photos}/lib/emergency/model.dart | 0 .../photos}/lib/emergency/other_contact_page.dart | 0 .../lib/emergency/recover_others_account.dart | 0 .../photos}/lib/emergency/select_contact_page.dart | 0 mobile/{ => apps/photos}/lib/ente_theme_data.dart | 0 .../lib/events/account_configured_event.dart | 0 .../lib/events/album_sort_order_change_event.dart | 0 .../lib/events/backup_folders_updated_event.dart | 0 .../photos}/lib/events/backup_updated_event.dart | 0 .../lib/events/clear_album_selections_event.dart | 0 .../events/clear_and_unfocus_search_bar_event.dart | 0 .../photos}/lib/events/clear_selections_event.dart | 0 .../photos}/lib/events/collection_meta_event.dart | 0 .../lib/events/collection_updated_event.dart | 0 .../photos}/lib/events/compute_control_event.dart | 0 .../photos}/lib/events/create_new_album_event.dart | 0 .../photos}/lib/events/details_sheet_event.dart | 0 .../lib/events/diff_sync_complete_event.dart | 0 .../photos}/lib/events/embedding_updated_event.dart | 0 .../photos}/lib/events/endpoint_updated_event.dart | 0 mobile/{ => apps/photos}/lib/events/event.dart | 0 .../favorites_service_init_complete_event.dart | 0 .../lib/events/file_caption_updated_event.dart | 0 .../photos}/lib/events/file_uploaded_event.dart | 0 .../photos}/lib/events/files_updated_event.dart | 0 .../lib/events/force_reload_home_gallery_event.dart | 0 .../lib/events/force_reload_trash_page_event.dart | 0 .../photos}/lib/events/guest_view_event.dart | 0 .../hide_shared_items_from_home_gallery_event.dart | 0 .../photos}/lib/events/local_import_progress.dart | 0 .../lib/events/local_photos_updated_event.dart | 0 .../lib/events/location_tag_updated_event.dart | 0 .../lib/events/magic_cache_updated_event.dart | 0 .../photos}/lib/events/magic_sort_change_event.dart | 0 .../photos}/lib/events/memories_changed_event.dart | 0 .../lib/events/memories_setting_changed.dart | 0 .../photos}/lib/events/memory_seen_event.dart | 0 .../photos}/lib/events/notification_event.dart | 0 .../photos}/lib/events/opened_settings_event.dart | 0 .../photos}/lib/events/pause_video_event.dart | 0 .../photos}/lib/events/people_changed_event.dart | 0 .../lib/events/permission_granted_event.dart | 0 .../lib/events/reset_zoom_of_photo_view_event.dart | 0 .../photos}/lib/events/seekbar_triggered_event.dart | 0 .../photos}/lib/events/signed_in_event.dart | 0 .../photos}/lib/events/stream_switched_event.dart | 0 .../lib/events/subscription_purchased_event.dart | 0 .../lib/events/sync_status_update_event.dart | 0 .../photos}/lib/events/tab_changed_event.dart | 0 .../photos}/lib/events/trash_updated_event.dart | 0 .../photos}/lib/events/trigger_logout_event.dart | 0 .../lib/events/two_factor_status_change_event.dart | 0 .../photos}/lib/events/use_media_kit_for_video.dart | 0 .../lib/events/user_details_changed_event.dart | 0 .../photos}/lib/events/user_logged_out_event.dart | 0 .../photos}/lib/events/video_streaming_changed.dart | 0 .../photos}/lib/extensions/input_formatter.dart | 0 mobile/{ => apps/photos}/lib/extensions/list.dart | 0 .../lib/extensions/ml_linalg_extensions.dart | 0 .../photos}/lib/extensions/stop_watch.dart | 0 .../photos}/lib/extensions/user_extension.dart | 0 mobile/{ => apps/photos}/lib/gateways/cast_gw.dart | 0 .../{ => apps/photos}/lib/gateways/entity_gw.dart | 0 .../photos}/lib/gateways/storage_bonus_gw.dart | 0 .../photos}/lib/generated/intl/messages_all.dart | 0 .../photos}/lib/generated/intl/messages_ar.dart | 0 .../photos}/lib/generated/intl/messages_be.dart | 0 .../photos}/lib/generated/intl/messages_bg.dart | 0 .../photos}/lib/generated/intl/messages_ca.dart | 0 .../photos}/lib/generated/intl/messages_cs.dart | 0 .../photos}/lib/generated/intl/messages_da.dart | 0 .../photos}/lib/generated/intl/messages_de.dart | 0 .../photos}/lib/generated/intl/messages_el.dart | 0 .../photos}/lib/generated/intl/messages_en.dart | 0 .../photos}/lib/generated/intl/messages_es.dart | 0 .../photos}/lib/generated/intl/messages_et.dart | 0 .../photos}/lib/generated/intl/messages_eu.dart | 0 .../photos}/lib/generated/intl/messages_fa.dart | 0 .../photos}/lib/generated/intl/messages_fr.dart | 0 .../photos}/lib/generated/intl/messages_gu.dart | 0 .../photos}/lib/generated/intl/messages_he.dart | 0 .../photos}/lib/generated/intl/messages_hi.dart | 0 .../photos}/lib/generated/intl/messages_hu.dart | 0 .../photos}/lib/generated/intl/messages_id.dart | 0 .../photos}/lib/generated/intl/messages_it.dart | 0 .../photos}/lib/generated/intl/messages_ja.dart | 0 .../photos}/lib/generated/intl/messages_km.dart | 0 .../photos}/lib/generated/intl/messages_ko.dart | 0 .../photos}/lib/generated/intl/messages_ku.dart | 0 .../photos}/lib/generated/intl/messages_lt.dart | 0 .../photos}/lib/generated/intl/messages_lv.dart | 0 .../photos}/lib/generated/intl/messages_ml.dart | 0 .../photos}/lib/generated/intl/messages_nl.dart | 0 .../photos}/lib/generated/intl/messages_no.dart | 0 .../photos}/lib/generated/intl/messages_or.dart | 0 .../photos}/lib/generated/intl/messages_pl.dart | 0 .../photos}/lib/generated/intl/messages_pt.dart | 0 .../photos}/lib/generated/intl/messages_pt_BR.dart | 0 .../photos}/lib/generated/intl/messages_pt_PT.dart | 0 .../photos}/lib/generated/intl/messages_ro.dart | 0 .../photos}/lib/generated/intl/messages_ru.dart | 0 .../photos}/lib/generated/intl/messages_sl.dart | 0 .../photos}/lib/generated/intl/messages_sr.dart | 0 .../photos}/lib/generated/intl/messages_sv.dart | 0 .../photos}/lib/generated/intl/messages_ta.dart | 0 .../photos}/lib/generated/intl/messages_te.dart | 0 .../photos}/lib/generated/intl/messages_th.dart | 0 .../photos}/lib/generated/intl/messages_ti.dart | 0 .../photos}/lib/generated/intl/messages_tr.dart | 0 .../photos}/lib/generated/intl/messages_uk.dart | 0 .../photos}/lib/generated/intl/messages_vi.dart | 0 .../photos}/lib/generated/intl/messages_zh.dart | 0 mobile/{ => apps/photos}/lib/generated/l10n.dart | 0 .../lib/generated/protos/ente/common/box.pb.dart | 0 .../generated/protos/ente/common/box.pbenum.dart | 0 .../generated/protos/ente/common/box.pbjson.dart | 0 .../generated/protos/ente/common/box.pbserver.dart | 0 .../lib/generated/protos/ente/common/point.pb.dart | 0 .../generated/protos/ente/common/point.pbenum.dart | 0 .../generated/protos/ente/common/point.pbjson.dart | 0 .../protos/ente/common/point.pbserver.dart | 0 .../lib/generated/protos/ente/common/vector.pb.dart | 0 .../generated/protos/ente/common/vector.pbenum.dart | 0 .../generated/protos/ente/common/vector.pbjson.dart | 0 .../protos/ente/common/vector.pbserver.dart | 0 .../lib/generated/protos/ente/ml/face.pb.dart | 0 .../lib/generated/protos/ente/ml/face.pbenum.dart | 0 .../lib/generated/protos/ente/ml/face.pbjson.dart | 0 .../lib/generated/protos/ente/ml/face.pbserver.dart | 0 .../lib/generated/protos/ente/ml/fileml.pb.dart | 0 .../lib/generated/protos/ente/ml/fileml.pbenum.dart | 0 .../lib/generated/protos/ente/ml/fileml.pbjson.dart | 0 .../generated/protos/ente/ml/fileml.pbserver.dart | 0 mobile/{ => apps/photos}/lib/l10n/intl_ar.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_be.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_bg.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ca.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_cs.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_da.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_de.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_el.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_en.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_es.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_et.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_eu.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_fa.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_fr.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_gu.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_he.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_hi.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_hu.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_id.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_it.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ja.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_km.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ko.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ku.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_lt.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_lv.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ml.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_nl.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_no.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_or.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_pl.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_pt.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_pt_BR.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_pt_PT.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ro.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ru.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_sl.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_sr.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_sv.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ta.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_te.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_th.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_ti.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_tr.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_uk.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_vi.arb | 0 mobile/{ => apps/photos}/lib/l10n/intl_zh.arb | 0 mobile/{ => apps/photos}/lib/l10n/l10n.dart | 0 mobile/{ => apps/photos}/lib/main.dart | 0 .../photos}/lib/models/account/two_factor.dart | 0 .../lib/models/api/billing/billing_plan.dart | 0 .../lib/models/api/billing/subscription.dart | 0 .../models/api/collection/collection_file_item.dart | 0 .../lib/models/api/collection/create_request.dart | 0 .../lib/models/api/collection/public_url.dart | 0 .../models/api/collection/trash_item_request.dart | 0 .../photos}/lib/models/api/collection/user.dart | 0 .../photos}/lib/models/api/entity/data.dart | 0 .../photos}/lib/models/api/entity/key.dart | 0 .../photos}/lib/models/api/entity/type.dart | 0 .../{ => apps/photos}/lib/models/api/metadata.dart | 0 .../photos}/lib/models/api/storage_bonus/bonus.dart | 0 .../lib/models/api/storage_bonus/storage_bonus.dart | 0 .../photos}/lib/models/api/user/delete_account.dart | 0 .../photos}/lib/models/api/user/key_attributes.dart | 0 .../photos}/lib/models/api/user/key_gen_result.dart | 0 .../lib/models/api/user/private_key_attributes.dart | 0 .../photos}/lib/models/api/user/sessions.dart | 0 .../lib/models/api/user/set_keys_request.dart | 0 .../models/api/user/set_recovery_key_request.dart | 0 .../{ => apps/photos}/lib/models/api/user/srp.dart | 0 .../photos}/lib/models/backup/backup_item.dart | 0 .../lib/models/backup/backup_item_status.dart | 0 .../{ => apps/photos}/lib/models/backup_status.dart | 0 mobile/{ => apps/photos}/lib/models/base/id.dart | 0 .../{ => apps/photos}/lib/models/base_location.dart | 0 .../{ => apps/photos}/lib/models/button_result.dart | 0 .../photos}/lib/models/collection/collection.dart | 0 .../lib/models/collection/collection_items.dart | 0 .../photos}/lib/models/device_collection.dart | 0 .../photos}/lib/models/duplicate_files.dart | 0 .../photos}/lib/models/execution_states.dart | 0 .../photos}/lib/models/ffmpeg/channel_layouts.dart | 0 .../{ => apps/photos}/lib/models/ffmpeg/codecs.dart | 0 .../photos}/lib/models/ffmpeg/ffprobe_keys.dart | 0 .../photos}/lib/models/ffmpeg/ffprobe_props.dart | 0 .../photos}/lib/models/ffmpeg/language.dart | 0 mobile/{ => apps/photos}/lib/models/ffmpeg/mp4.dart | 0 .../lib/models/file/extensions/file_props.dart | 0 mobile/{ => apps/photos}/lib/models/file/file.dart | 0 .../photos}/lib/models/file/file_type.dart | 0 .../photos}/lib/models/file/trash_file.dart | 0 .../photos}/lib/models/file_load_result.dart | 0 .../{ => apps/photos}/lib/models/files_split.dart | 0 .../{ => apps/photos}/lib/models/gallery_type.dart | 0 .../{ => apps/photos}/lib/models/ignored_file.dart | 0 .../photos}/lib/models/local_entity_data.dart | 0 .../photos}/lib/models/location/location.dart | 0 .../lib/models/location/location.freezed.dart | 0 .../photos}/lib/models/location/location.g.dart | 0 .../lib/models/location_tag/location_tag.dart | 0 .../models/location_tag/location_tag.freezed.dart | 0 .../lib/models/location_tag/location_tag.g.dart | 0 .../photos}/lib/models/memories/clip_memory.dart | 0 .../photos}/lib/models/memories/filler_memory.dart | 0 .../photos}/lib/models/memories/memories_cache.dart | 0 .../photos}/lib/models/memories/memory.dart | 0 .../lib/models/memories/on_this_day_memory.dart | 0 .../photos}/lib/models/memories/people_memory.dart | 0 .../photos}/lib/models/memories/smart_memory.dart | 0 .../lib/models/memories/smart_memory_constants.dart | 0 .../photos}/lib/models/memories/time_memory.dart | 0 .../photos}/lib/models/memories/trip_memory.dart | 0 .../lib/models/metadata/collection_magic.dart | 0 .../photos}/lib/models/metadata/common_keys.dart | 0 .../photos}/lib/models/metadata/file_magic.dart | 0 mobile/{ => apps/photos}/lib/models/ml/clip.dart | 0 .../photos}/lib/models/ml/discover/prompt.dart | 0 .../{ => apps/photos}/lib/models/ml/face/box.dart | 0 .../photos}/lib/models/ml/face/detection.dart | 0 .../photos}/lib/models/ml/face/dimension.dart | 0 .../{ => apps/photos}/lib/models/ml/face/face.dart | 0 .../lib/models/ml/face/face_with_embedding.dart | 0 .../photos}/lib/models/ml/face/landmark.dart | 0 .../photos}/lib/models/ml/face/person.dart | 0 .../photos}/lib/models/ml/ml_typedefs.dart | 0 .../photos}/lib/models/ml/ml_versions.dart | 0 mobile/{ => apps/photos}/lib/models/ml/vector.dart | 0 .../photos}/lib/models/preview/playlist_data.dart | 0 .../photos}/lib/models/preview/preview_item.dart | 0 .../lib/models/preview/preview_item_status.dart | 0 .../lib/models/search/album_search_result.dart | 0 .../lib/models/search/generic_search_result.dart | 0 .../models/search/hierarchical/album_filter.dart | 0 .../models/search/hierarchical/contacts_filter.dart | 0 .../lib/models/search/hierarchical/face_filter.dart | 0 .../search/hierarchical/file_type_filter.dart | 0 .../hierarchical/hierarchical_search_filter.dart | 0 .../models/search/hierarchical/location_filter.dart | 0 .../models/search/hierarchical/magic_filter.dart | 0 .../search/hierarchical/only_them_filter.dart | 0 .../hierarchical/top_level_generic_filter.dart | 0 .../models/search/hierarchical/uploader_filter.dart | 0 .../lib/models/search/index_of_indexed_stack.dart | 0 .../photos}/lib/models/search/recent_searches.dart | 0 .../photos}/lib/models/search/search_constants.dart | 0 .../photos}/lib/models/search/search_result.dart | 0 .../photos}/lib/models/search/search_types.dart | 0 .../photos}/lib/models/selected_albums.dart | 0 .../photos}/lib/models/selected_files.dart | 0 .../photos}/lib/models/selected_people.dart | 0 mobile/{ => apps/photos}/lib/models/typedefs.dart | 0 .../photos}/lib/models/upload_strategy.dart | 0 .../{ => apps/photos}/lib/models/user_details.dart | 0 .../photos}/lib/module/download/file_url.dart | 0 .../photos}/lib/module/download/manager.dart | 0 .../{ => apps/photos}/lib/module/download/task.dart | 0 .../photos}/lib/module/upload/model/multipart.dart | 0 .../photos}/lib/module/upload/model/upload_url.dart | 0 .../photos}/lib/module/upload/model/xml.dart | 0 .../lib/module/upload/service/multipart.dart | 0 mobile/{ => apps/photos}/lib/service_locator.dart | 0 .../lib/services/account/billing_service.dart | 0 .../lib/services/account/passkey_service.dart | 0 .../photos}/lib/services/account/user_service.dart | 0 .../lib/services/album_home_widget_service.dart | 0 .../photos}/lib/services/app_lifecycle_service.dart | 0 .../photos}/lib/services/collections_service.dart | 0 .../photos}/lib/services/deduplication_service.dart | 0 .../photos}/lib/services/entity_service.dart | 0 .../photos}/lib/services/favorites_service.dart | 0 .../photos}/lib/services/file_magic_service.dart | 0 .../lib/services/filedata/filedata_service.dart | 0 .../lib/services/filedata/model/enc_file_data.dart | 0 .../lib/services/filedata/model/file_data.dart | 0 .../lib/services/filedata/model/response.dart | 0 .../photos}/lib/services/files_service.dart | 0 .../lib/services/filter/collection_ignore.dart | 0 .../photos}/lib/services/filter/db_filters.dart | 0 .../lib/services/filter/dedupe_by_upload_id.dart | 0 .../photos}/lib/services/filter/filter.dart | 0 .../services/filter/only_uploaded_files_filter.dart | 0 .../photos}/lib/services/filter/shared.dart | 0 .../photos}/lib/services/filter/type_filter.dart | 0 .../photos}/lib/services/filter/upload_ignore.dart | 0 .../photos}/lib/services/hidden_service.dart | 0 .../photos}/lib/services/home_widget_service.dart | 0 .../photos}/lib/services/ignored_files_service.dart | 0 .../photos}/lib/services/isolate_functions.dart | 0 .../photos}/lib/services/isolate_service.dart | 0 .../photos}/lib/services/language_service.dart | 0 .../lib/services/local_authentication_service.dart | 0 .../lib/services/local_file_update_service.dart | 0 .../photos}/lib/services/location_service.dart | 0 .../machine_learning/compute_controller.dart | 0 .../face_ml/face_alignment/alignment_result.dart | 0 .../face_alignment/similarity_transform.dart | 0 .../face_clustering/face_clustering_service.dart | 0 .../face_db_info_for_clustering.dart | 0 .../face_ml/face_detection/detection.dart | 0 .../face_detection_postprocessing.dart | 0 .../face_detection/face_detection_service.dart | 0 .../face_embedding/face_embedding_service.dart | 0 .../face_filtering/blur_detection_service.dart | 0 .../face_filtering/face_filtering_constants.dart | 0 .../face_ml/face_recognition_service.dart | 0 .../face_ml/feedback/cluster_feedback.dart | 0 .../face_ml/person/person_service.dart | 0 .../machine_learning/face_thumbnail_generator.dart | 0 .../lib/services/machine_learning/ml_computer.dart | 0 .../lib/services/machine_learning/ml_constants.dart | 0 .../services/machine_learning/ml_exceptions.dart | 0 .../machine_learning/ml_indexing_isolate.dart | 0 .../lib/services/machine_learning/ml_model.dart | 0 .../machine_learning/ml_models_overview.dart | 0 .../lib/services/machine_learning/ml_result.dart | 0 .../lib/services/machine_learning/ml_service.dart | 0 .../lib/services/machine_learning/onnx_env.dart | 0 .../semantic_search/clip/clip_image_encoder.dart | 0 .../semantic_search/clip/clip_text_encoder.dart | 0 .../semantic_search/clip/clip_text_tokenizer.dart | 0 .../semantic_search/query_result.dart | 0 .../semantic_search/semantic_search_service.dart | 0 .../photos}/lib/services/magic_cache_service.dart | 0 .../lib/services/memories_cache_service.dart | 0 .../lib/services/memory_home_widget_service.dart | 0 .../photos}/lib/services/notification_service.dart | 0 .../lib/services/people_home_widget_service.dart | 0 .../photos}/lib/services/permission/service.dart | 0 .../photos}/lib/services/push_service.dart | 0 .../photos}/lib/services/remote_assets_service.dart | 0 .../photos}/lib/services/search_service.dart | 0 .../lib/services/smart_memories_service.dart | 0 .../photos}/lib/services/storage_bonus_service.dart | 0 .../photos}/lib/services/sync/diff_fetcher.dart | 0 .../photos}/lib/services/sync/import/diff.dart | 0 .../lib/services/sync/import/local_assets.dart | 0 .../photos}/lib/services/sync/import/model.dart | 0 .../lib/services/sync/local_sync_service.dart | 0 .../lib/services/sync/remote_sync_service.dart | 0 .../photos}/lib/services/sync/sync_service.dart | 0 .../lib/services/sync/trash_sync_service.dart | 0 .../photos}/lib/services/update_service.dart | 0 .../photos}/lib/services/video_memory_service.dart | 0 .../photos}/lib/services/video_preview_service.dart | 0 .../photos}/lib/services/wake_lock_service.dart | 0 .../lib/states/all_sections_examples_state.dart | 0 .../photos}/lib/states/detail_page_state.dart | 0 .../photos}/lib/states/location_screen_state.dart | 0 .../photos}/lib/states/location_state.dart | 0 .../photos}/lib/states/user_details_state.dart | 0 mobile/{ => apps/photos}/lib/theme/colors.dart | 0 mobile/{ => apps/photos}/lib/theme/effects.dart | 0 mobile/{ => apps/photos}/lib/theme/ente_theme.dart | 0 mobile/{ => apps/photos}/lib/theme/text_style.dart | 0 .../photos}/lib/ui/account/change_email_dialog.dart | 0 .../photos}/lib/ui/account/delete_account_page.dart | 0 .../photos}/lib/ui/account/email_entry_page.dart | 0 .../photos}/lib/ui/account/login_page.dart | 0 .../lib/ui/account/login_pwd_verification_page.dart | 0 .../lib/ui/account/ott_verification_page.dart | 0 .../photos}/lib/ui/account/passkey_page.dart | 0 .../photos}/lib/ui/account/password_entry_page.dart | 0 .../lib/ui/account/password_reentry_page.dart | 0 .../photos}/lib/ui/account/recovery_key_page.dart | 0 .../photos}/lib/ui/account/recovery_page.dart | 0 .../ui/account/request_pwd_verification_page.dart | 0 .../photos}/lib/ui/account/sessions_page.dart | 0 .../ui/account/two_factor_authentication_page.dart | 0 .../lib/ui/account/two_factor_recovery_page.dart | 0 .../lib/ui/account/two_factor_setup_page.dart | 0 .../lib/ui/account/verify_recovery_page.dart | 0 .../actions/collection/collection_file_actions.dart | 0 .../collection/collection_sharing_actions.dart | 0 .../photos}/lib/ui/actions/file/file_actions.dart | 0 mobile/{ => apps/photos}/lib/ui/cast/auto.dart | 0 mobile/{ => apps/photos}/lib/ui/cast/choose.dart | 0 .../lib/ui/collections/album/column_item.dart | 0 .../lib/ui/collections/album/horizontal_list.dart | 0 .../photos}/lib/ui/collections/album/list_item.dart | 0 .../lib/ui/collections/album/new_list_item.dart | 0 .../lib/ui/collections/album/new_row_item.dart | 0 .../photos}/lib/ui/collections/album/row_item.dart | 0 .../lib/ui/collections/album/vertical_list.dart | 0 .../lib/ui/collections/button/archived_button.dart | 0 .../lib/ui/collections/button/hidden_button.dart | 0 .../lib/ui/collections/button/trash_button.dart | 0 .../ui/collections/button/uncategorized_button.dart | 0 .../lib/ui/collections/collection_action_sheet.dart | 0 .../lib/ui/collections/collection_list_page.dart | 0 .../ui/collections/device/device_folder_item.dart | 0 .../device/device_folders_grid_view.dart | 0 .../device/device_folders_vertical_grid_view.dart | 0 .../photos}/lib/ui/collections/flex_grid_view.dart | 0 .../photos}/lib/ui/common/bottom_shadow.dart | 0 .../{ => apps/photos}/lib/ui/common/date_input.dart | 0 .../photos}/lib/ui/common/dynamic_fab.dart | 0 .../photos}/lib/ui/common/fast_scroll_physics.dart | 0 .../photos}/lib/ui/common/gradient_button.dart | 0 .../lib/ui/common/linear_progress_dialog.dart | 0 .../photos}/lib/ui/common/loading_widget.dart | 0 .../{ => apps/photos}/lib/ui/common/popup_item.dart | 0 .../photos}/lib/ui/common/progress_dialog.dart | 0 .../photos}/lib/ui/common/user_dialogs.dart | 0 .../{ => apps/photos}/lib/ui/common/web_page.dart | 0 .../lib/ui/components/action_sheet_widget.dart | 0 .../lib/ui/components/blur_menu_item_widget.dart | 0 .../bottom_action_bar/action_bar_widget.dart | 0 .../bottom_action_bar/album_action_bar_widget.dart | 0 .../album_bottom_action_bar_widget.dart | 0 .../bottom_action_bar/bottom_action_bar_widget.dart | 0 .../bottom_action_bar/expanded_menu_widget.dart | 0 .../bottom_action_bar/people_action_bar_widget.dart | 0 .../people_bottom_action_bar_widget.dart | 0 .../selection_action_button_widget.dart | 0 .../ui/components/bottom_of_title_bar_widget.dart | 0 .../lib/ui/components/buttons/button_widget.dart | 0 .../ui/components/buttons/chip_button_widget.dart | 0 .../ui/components/buttons/icon_button_widget.dart | 0 .../ui/components/buttons/inline_button_widget.dart | 0 .../lib/ui/components/captioned_text_widget.dart | 0 .../photos}/lib/ui/components/dialog_widget.dart | 0 .../photos}/lib/ui/components/divider_widget.dart | 0 .../lib/ui/components/empty_state_item_widget.dart | 0 .../lib/ui/components/end_to_end_banner.dart | 0 .../ui/components/expandable_menu_item_widget.dart | 0 .../lib/ui/components/home_header_widget.dart | 0 .../photos}/lib/ui/components/info_item_widget.dart | 0 .../ui/components/keyboard/keyboard_oveylay.dart | 0 .../ui/components/keyboard/keyboard_top_button.dart | 0 .../menu_item_widget/menu_item_child_widgets.dart | 0 .../menu_item_widget/menu_item_widget.dart | 0 .../components/menu_section_description_widget.dart | 0 .../lib/ui/components/menu_section_title.dart | 0 .../lib/ui/components/models/button_type.dart | 0 .../ui/components/models/custom_button_style.dart | 0 .../lib/ui/components/notification_widget.dart | 0 .../lib/ui/components/searchable_appbar.dart | 0 .../lib/ui/components/text_input_widget.dart | 0 .../lib/ui/components/title_bar_title_widget.dart | 0 .../photos}/lib/ui/components/title_bar_widget.dart | 0 .../lib/ui/components/toggle_switch_widget.dart | 0 .../{ => apps/photos}/lib/ui/extents_page_view.dart | 0 .../photos}/lib/ui/growth/apply_code_screen.dart | 0 .../photos}/lib/ui/growth/code_success_screen.dart | 0 .../photos}/lib/ui/growth/referral_code_widget.dart | 0 .../photos}/lib/ui/growth/referral_screen.dart | 0 .../lib/ui/growth/storage_details_screen.dart | 0 .../lib/ui/home/grant_permissions_widget.dart | 0 .../photos}/lib/ui/home/header_error_widget.dart | 0 .../photos}/lib/ui/home/header_widget.dart | 0 .../photos}/lib/ui/home/home_bottom_nav_bar.dart | 0 .../photos}/lib/ui/home/home_gallery_widget.dart | 0 .../photos}/lib/ui/home/landing_page_widget.dart | 0 .../photos}/lib/ui/home/loading_photos_widget.dart | 0 .../lib/ui/home/memories/all_memories_page.dart | 0 .../lib/ui/home/memories/custom_listener.dart | 0 .../lib/ui/home/memories/full_screen_memory.dart | 0 .../lib/ui/home/memories/memories_widget.dart | 0 .../lib/ui/home/memories/memory_cover_widget.dart | 0 .../ui/home/memories/memory_progress_indicator.dart | 0 .../lib/ui/home/start_backup_hook_widget.dart | 0 .../photos}/lib/ui/home/status_bar_widget.dart | 0 .../lib/ui/huge_listview/draggable_scrollbar.dart | 0 .../photos}/lib/ui/huge_listview/huge_listview.dart | 0 .../lib/ui/huge_listview/scroll_bar_thumb.dart | 0 .../photos}/lib/ui/lifecycle_event_handler.dart | 0 mobile/{ => apps/photos}/lib/ui/map/enable_map.dart | 0 .../{ => apps/photos}/lib/ui/map/image_marker.dart | 0 mobile/{ => apps/photos}/lib/ui/map/map_button.dart | 0 .../photos}/lib/ui/map/map_gallery_tile.dart | 0 .../photos}/lib/ui/map/map_gallery_tile_badge.dart | 0 .../{ => apps/photos}/lib/ui/map/map_isolate.dart | 0 mobile/{ => apps/photos}/lib/ui/map/map_marker.dart | 0 .../photos}/lib/ui/map/map_pull_up_gallery.dart | 0 mobile/{ => apps/photos}/lib/ui/map/map_screen.dart | 0 mobile/{ => apps/photos}/lib/ui/map/map_view.dart | 0 .../{ => apps/photos}/lib/ui/map/marker_image.dart | 0 .../ui/map/tile/attribution/map_attribution.dart | 0 mobile/{ => apps/photos}/lib/ui/map/tile/cache.dart | 0 .../{ => apps/photos}/lib/ui/map/tile/layers.dart | 0 .../photos}/lib/ui/notification/toast.dart | 0 .../ui/notification/update/change_log_entry.dart | 0 .../lib/ui/notification/update/change_log_page.dart | 0 .../photos}/lib/ui/payment/add_on_page.dart | 0 .../lib/ui/payment/billing_questions_widget.dart | 0 .../lib/ui/payment/child_subscription_widget.dart | 0 .../photos}/lib/ui/payment/payment_web_page.dart | 0 .../lib/ui/payment/store_subscription_page.dart | 0 .../lib/ui/payment/stripe_subscription_page.dart | 0 .../photos}/lib/ui/payment/subscription.dart | 0 .../lib/ui/payment/subscription_common_widgets.dart | 0 .../lib/ui/payment/subscription_plan_widget.dart | 0 .../photos}/lib/ui/payment/view_add_on_widget.dart | 0 .../lib/ui/settings/about_section_widget.dart | 0 .../lib/ui/settings/account_section_widget.dart | 0 .../lib/ui/settings/advanced_settings_screen.dart | 0 .../lib/ui/settings/app_icon_selection_screen.dart | 0 .../photos}/lib/ui/settings/app_update_dialog.dart | 0 .../photos}/lib/ui/settings/app_version_widget.dart | 0 .../backup/backup_folder_selection_page.dart | 0 .../lib/ui/settings/backup/backup_item_card.dart | 0 .../ui/settings/backup/backup_section_widget.dart | 0 .../ui/settings/backup/backup_settings_screen.dart | 0 .../ui/settings/backup/backup_status_screen.dart | 0 .../lib/ui/settings/backup/free_space_options.dart | 0 .../photos}/lib/ui/settings/common_settings.dart | 0 .../lib/ui/settings/debug/debug_section_widget.dart | 0 .../ui/settings/debug/ml_debug_section_widget.dart | 0 .../lib/ui/settings/developer_settings_page.dart | 0 .../lib/ui/settings/developer_settings_widget.dart | 0 .../lib/ui/settings/gallery_settings_screen.dart | 0 .../lib/ui/settings/general_section_widget.dart | 0 .../lib/ui/settings/inherited_settings_state.dart | 0 .../photos}/lib/ui/settings/language_picker.dart | 0 .../ui/settings/lock_screen/custom_pin_keypad.dart | 0 .../settings/lock_screen/lock_screen_auto_lock.dart | 0 .../lock_screen/lock_screen_confirm_password.dart | 0 .../lock_screen/lock_screen_confirm_pin.dart | 0 .../settings/lock_screen/lock_screen_options.dart | 0 .../settings/lock_screen/lock_screen_password.dart | 0 .../ui/settings/lock_screen/lock_screen_pin.dart | 0 .../lib/ui/settings/ml/enable_ml_consent.dart | 0 .../settings/ml/machine_learning_settings_page.dart | 0 .../lib/ui/settings/ml/ml_user_dev_screen.dart | 0 .../ui/settings/notification_settings_screen.dart | 0 .../pending_sync/path_info_storage_viewer.dart | 0 .../pending_sync/pending_sync_info_screen.dart | 0 .../lib/ui/settings/security_section_widget.dart | 0 .../lib/ui/settings/settings_title_bar_widget.dart | 0 .../lib/ui/settings/social_section_widget.dart | 0 .../lib/ui/settings/storage_card_widget.dart | 0 .../lib/ui/settings/storage_progress_widget.dart | 0 .../lib/ui/settings/support_section_widget.dart | 0 .../lib/ui/settings/theme_switch_widget.dart | 0 .../lib/ui/settings/widget_settings_screen.dart | 0 .../ui/settings/widgets/albums_widget_settings.dart | 0 .../settings/widgets/memories_widget_settings.dart | 0 .../ui/settings/widgets/people_widget_settings.dart | 0 mobile/{ => apps/photos}/lib/ui/settings_page.dart | 0 .../lib/ui/sharing/add_participant_page.dart | 0 .../lib/ui/sharing/album_participants_page.dart | 0 .../lib/ui/sharing/album_share_info_widget.dart | 0 .../lib/ui/sharing/manage_album_participant.dart | 0 .../photos}/lib/ui/sharing/manage_links_widget.dart | 0 .../photos}/lib/ui/sharing/more_count_badge.dart | 0 .../sharing/pickers/device_limit_picker_page.dart | 0 .../ui/sharing/pickers/link_expiry_picker_page.dart | 0 .../lib/ui/sharing/share_collection_page.dart | 0 .../photos}/lib/ui/sharing/show_images_prevew.dart | 0 .../photos}/lib/ui/sharing/user_avator_widget.dart | 0 .../lib/ui/sharing/verify_identity_dialog.dart | 0 .../{ => apps/photos}/lib/ui/tabs/home_widget.dart | 0 mobile/{ => apps/photos}/lib/ui/tabs/nav_bar.dart | 0 .../photos}/lib/ui/tabs/section_title.dart | 0 .../lib/ui/tabs/shared/all_quick_links_page.dart | 0 .../photos}/lib/ui/tabs/shared/empty_state.dart | 0 .../lib/ui/tabs/shared/quick_link_album_item.dart | 0 .../photos}/lib/ui/tabs/shared_collections_tab.dart | 0 .../photos}/lib/ui/tabs/user_collections_tab.dart | 0 mobile/{ => apps/photos}/lib/ui/tools/app_lock.dart | 0 .../ui/tools/collage/collage_common_widgets.dart | 0 .../lib/ui/tools/collage/collage_creator_page.dart | 0 .../lib/ui/tools/collage/collage_item_icon.dart | 0 .../lib/ui/tools/collage/collage_item_widget.dart | 0 .../lib/ui/tools/collage/collage_save_button.dart | 0 .../lib/ui/tools/collage/collage_test_grid.dart | 0 .../ui/tools/collage/collage_with_five_items.dart | 0 .../ui/tools/collage/collage_with_four_items.dart | 0 .../ui/tools/collage/collage_with_six_items.dart | 0 .../ui/tools/collage/collage_with_three_items.dart | 0 .../ui/tools/collage/collage_with_two_items.dart | 0 .../lib/ui/tools/debug/app_storage_viewer.dart | 0 .../photos}/lib/ui/tools/debug/log_file_viewer.dart | 0 .../lib/ui/tools/debug/path_storage_viewer.dart | 0 .../photos}/lib/ui/tools/deduplicate_page.dart | 0 .../lib/ui/tools/editor/export_video_result.dart | 0 .../lib/ui/tools/editor/export_video_service.dart | 0 .../photos}/lib/ui/tools/editor/filtered_image.dart | 0 .../lib/ui/tools/editor/image_editor_page.dart | 0 .../lib/ui/tools/editor/video_crop_page.dart | 0 .../ui/tools/editor/video_editor/crop_value.dart | 0 .../video_editor/video_editor_bottom_action.dart | 0 .../video_editor/video_editor_main_actions.dart | 0 .../video_editor_navigation_options.dart | 0 .../video_editor/video_editor_player_control.dart | 0 .../lib/ui/tools/editor/video_editor_page.dart | 0 .../lib/ui/tools/editor/video_rotate_page.dart | 0 .../lib/ui/tools/editor/video_trim_page.dart | 0 .../photos}/lib/ui/tools/free_space_page.dart | 0 .../{ => apps/photos}/lib/ui/tools/lock_screen.dart | 0 .../actions/album_selection_action_widget.dart | 0 .../viewer/actions/album_selection_overlay_bar.dart | 0 .../lib/ui/viewer/actions/delete_empty_albums.dart | 0 .../actions/file_selection_actions_widget.dart | 0 .../viewer/actions/file_selection_overlay_bar.dart | 0 .../photos}/lib/ui/viewer/actions/file_viewer.dart | 0 .../actions/people_selection_action_widget.dart | 0 .../lib/ui/viewer/date/date_time_picker.dart | 0 .../photos}/lib/ui/viewer/date/edit_date_sheet.dart | 0 .../photos}/lib/ui/viewer/file/detail_page.dart | 0 .../lib/ui/viewer/file/exif_info_dialog.dart | 0 .../photos}/lib/ui/viewer/file/file_app_bar.dart | 0 .../photos}/lib/ui/viewer/file/file_bottom_bar.dart | 0 .../lib/ui/viewer/file/file_caption_widget.dart | 0 .../lib/ui/viewer/file/file_details_widget.dart | 0 .../lib/ui/viewer/file/file_icons_widget.dart | 0 .../photos}/lib/ui/viewer/file/file_widget.dart | 0 .../play_pause_button.dart | 0 .../file/native_video_player_controls/seek_bar.dart | 0 .../lib/ui/viewer/file/no_thumbnail_widget.dart | 0 .../lib/ui/viewer/file/panorama_viewer_screen.dart | 0 .../lib/ui/viewer/file/thumbnail_widget.dart | 0 .../file/video_control/custom_progress_bar.dart | 0 .../lib/ui/viewer/file/video_exif_dialog.dart | 0 .../lib/ui/viewer/file/video_stream_change.dart | 0 .../photos}/lib/ui/viewer/file/video_widget.dart | 0 .../lib/ui/viewer/file/video_widget_media_kit.dart | 0 .../viewer/file/video_widget_media_kit_common.dart | 0 .../lib/ui/viewer/file/video_widget_native.dart | 0 .../photos}/lib/ui/viewer/file/zoomable_image.dart | 0 .../lib/ui/viewer/file/zoomable_live_image_new.dart | 0 .../lib/ui/viewer/file_details/added_by_widget.dart | 0 .../ui/viewer/file_details/albums_item_widget.dart | 0 .../file_details/backed_up_time_item_widget.dart | 0 .../file_details/creation_time_item_widget.dart | 0 .../ui/viewer/file_details/exif_item_widgets.dart | 0 .../lib/ui/viewer/file_details/favorite_widget.dart | 0 .../viewer/file_details/file_info_face_widget.dart | 0 .../file_details/file_info_faces_item_widget.dart | 0 .../file_details/file_properties_item_widget.dart | 0 .../viewer/file_details/location_tags_widget.dart | 0 .../preview_properties_item_widget.dart | 0 .../ui/viewer/file_details/upload_icon_widget.dart | 0 .../lib/ui/viewer/file_details/video_exif_item.dart | 0 .../photos}/lib/ui/viewer/gallery/archive_page.dart | 0 .../gallery/collect_photos_bottom_buttons.dart | 0 .../viewer/gallery/collect_photos_card_widget.dart | 0 .../lib/ui/viewer/gallery/collection_page.dart | 0 .../gallery/component/gallery_file_widget.dart | 0 .../component/grid/gallery_grid_view_widget.dart | 0 .../gallery/component/grid/lazy_grid_view.dart | 0 .../grid/non_recyclable_grid_view_widget.dart | 0 .../grid/place_holder_grid_view_widget.dart | 0 .../component/grid/recyclable_grid_view_widget.dart | 0 .../gallery/component/group/group_gallery.dart | 0 .../component/group/group_header_widget.dart | 0 .../gallery/component/group/lazy_group_gallery.dart | 0 .../lib/ui/viewer/gallery/component/group/type.dart | 0 .../component/multiple_groups_gallery_view.dart | 0 .../lib/ui/viewer/gallery/device_folder_page.dart | 0 .../lib/ui/viewer/gallery/empty_album_state.dart | 0 .../lib/ui/viewer/gallery/empty_hidden_widget.dart | 0 .../photos}/lib/ui/viewer/gallery/empty_state.dart | 0 .../photos}/lib/ui/viewer/gallery/gallery.dart | 0 .../ui/viewer/gallery/gallery_app_bar_widget.dart | 0 .../photos}/lib/ui/viewer/gallery/hidden_page.dart | 0 .../viewer/gallery/hierarchical_search_gallery.dart | 0 .../ui/viewer/gallery/hooks/add_photos_sheet.dart | 0 .../ui/viewer/gallery/hooks/pick_cover_photo.dart | 0 .../ui/viewer/gallery/hooks/pick_person_avatar.dart | 0 .../lib/ui/viewer/gallery/large_files_page.dart | 0 .../viewer/gallery/photo_grid_size_picker_page.dart | 0 .../gallery/shared_public_collection_page.dart | 0 .../viewer/gallery/state/gallery_context_state.dart | 0 .../state/gallery_files_inherited_widget.dart | 0 .../gallery/state/inherited_search_filter_data.dart | 0 .../gallery/state/search_filter_data_provider.dart | 0 .../ui/viewer/gallery/state/selection_state.dart | 0 .../photos}/lib/ui/viewer/gallery/trash_page.dart | 0 .../lib/ui/viewer/gallery/uncategorized_page.dart | 0 .../applied_filters_for_appbar.dart | 0 .../chip_widgets/face_filter_chip.dart | 0 .../chip_widgets/generic_filter_chip.dart | 0 .../chip_widgets/only_them_filter_chip.dart | 0 .../filter_options_bottom_sheet.dart | 0 .../recommended_filters_for_appbar.dart | 0 .../lib/ui/viewer/location/add_location_sheet.dart | 0 .../location/dynamic_location_gallery_widget.dart | 0 .../location/edit_center_point_tile_widget.dart | 0 .../lib/ui/viewer/location/edit_location_sheet.dart | 0 .../lib/ui/viewer/location/location_screen.dart | 0 .../viewer/location/pick_center_point_widget.dart | 0 .../ui/viewer/location/radius_picker_widget.dart | 0 .../location/update_location_data_widget.dart | 0 .../ui/viewer/people/add_person_action_sheet.dart | 0 .../lib/ui/viewer/people/cluster_app_bar.dart | 0 .../lib/ui/viewer/people/cluster_breakup_page.dart | 0 .../photos}/lib/ui/viewer/people/cluster_page.dart | 0 .../lib/ui/viewer/people/file_face_widget.dart | 0 .../lib/ui/viewer/people/link_email_screen.dart | 0 .../lib/ui/viewer/people/people_app_bar.dart | 0 .../photos}/lib/ui/viewer/people/people_banner.dart | 0 .../photos}/lib/ui/viewer/people/people_page.dart | 0 .../photos}/lib/ui/viewer/people/people_util.dart | 0 .../ui/viewer/people/person_cluster_suggestion.dart | 0 .../lib/ui/viewer/people/person_clusters_page.dart | 0 .../lib/ui/viewer/people/person_face_widget.dart | 0 .../ui/viewer/people/person_gallery_suggestion.dart | 0 .../lib/ui/viewer/people/person_row_item.dart | 0 .../people/person_selection_action_widgets.dart | 0 .../lib/ui/viewer/people/save_or_edit_person.dart | 0 .../viewer/search/result/contact_result_page.dart | 0 .../ui/viewer/search/result/go_to_map_widget.dart | 0 .../viewer/search/result/magic_result_screen.dart | 0 .../ui/viewer/search/result/no_result_widget.dart | 0 .../search/result/people_section_all_page.dart | 0 .../ui/viewer/search/result/search_result_page.dart | 0 .../viewer/search/result/search_result_widget.dart | 0 .../search/result/search_section_all_page.dart | 0 .../search/result/search_thumbnail_widget.dart | 0 .../ui/viewer/search/result/searchable_item.dart | 0 .../lib/ui/viewer/search/search_section_cta.dart | 0 .../ui/viewer/search/search_suffix_icon_widget.dart | 0 .../lib/ui/viewer/search/search_suggestions.dart | 0 .../photos}/lib/ui/viewer/search/search_widget.dart | 0 .../lib/ui/viewer/search/tab_empty_state.dart | 0 .../lib/ui/viewer/search_tab/albums_section.dart | 0 .../lib/ui/viewer/search_tab/contacts_section.dart | 0 .../lib/ui/viewer/search_tab/file_type_section.dart | 0 .../lib/ui/viewer/search_tab/locations_section.dart | 0 .../lib/ui/viewer/search_tab/magic_section.dart | 0 .../lib/ui/viewer/search_tab/moments_section.dart | 0 .../lib/ui/viewer/search_tab/people_section.dart | 0 .../lib/ui/viewer/search_tab/search_tab.dart | 0 .../lib/ui/viewer/search_tab/section_header.dart | 0 mobile/{ => apps/photos}/lib/utils/auth_util.dart | 0 .../{ => apps/photos}/lib/utils/bg_task_utils.dart | 0 mobile/{ => apps/photos}/lib/utils/cache_util.dart | 0 .../photos}/lib/utils/collection_util.dart | 0 .../photos}/lib/utils/debug_ml_export_data.dart | 0 .../photos}/lib/utils/delete_file_util.dart | 0 mobile/{ => apps/photos}/lib/utils/device_info.dart | 0 mobile/{ => apps/photos}/lib/utils/dialog_util.dart | 0 mobile/{ => apps/photos}/lib/utils/email_util.dart | 0 mobile/{ => apps/photos}/lib/utils/exif_util.dart | 0 .../lib/utils/face/face_thumbnail_cache.dart | 0 .../{ => apps/photos}/lib/utils/ffprobe_util.dart | 0 .../photos}/lib/utils/file_download_util.dart | 0 mobile/{ => apps/photos}/lib/utils/file_key.dart | 0 .../{ => apps/photos}/lib/utils/file_uploader.dart | 0 .../photos}/lib/utils/file_uploader_util.dart | 0 mobile/{ => apps/photos}/lib/utils/file_util.dart | 0 mobile/{ => apps/photos}/lib/utils/gzip.dart | 0 .../photos}/lib/utils/hierarchical_search_util.dart | 0 .../{ => apps/photos}/lib/utils/image_ml_util.dart | 0 mobile/{ => apps/photos}/lib/utils/image_util.dart | 0 mobile/{ => apps/photos}/lib/utils/intent_util.dart | 0 .../{ => apps/photos}/lib/utils/local_settings.dart | 0 .../photos}/lib/utils/lock_screen_settings.dart | 0 mobile/{ => apps/photos}/lib/utils/magic_util.dart | 0 mobile/{ => apps/photos}/lib/utils/ml_util.dart | 0 .../photos}/lib/utils/navigation_util.dart | 0 .../{ => apps/photos}/lib/utils/network_util.dart | 0 .../{ => apps/photos}/lib/utils/panorama_util.dart | 0 .../lib/utils/person_contact_linking_util.dart | 0 .../{ => apps/photos}/lib/utils/ram_check_util.dart | 0 .../photos}/lib/utils/separators_util.dart | 0 mobile/{ => apps/photos}/lib/utils/share_util.dart | 0 .../photos}/lib/utils/standalone/README.md | 0 .../photos}/lib/utils/standalone/data.dart | 0 .../photos}/lib/utils/standalone/date_time.dart | 0 .../photos}/lib/utils/standalone/debouncer.dart | 0 .../lib/utils/standalone/directory_content.dart | 0 .../photos}/lib/utils/standalone/fake_progress.dart | 0 .../photos}/lib/utils/standalone/parse.dart | 0 .../lib/utils/standalone/simple_task_queue.dart | 0 .../photos}/lib/utils/standalone/task_queue.dart | 0 .../{ => apps/photos}/lib/utils/thumbnail_util.dart | 0 .../{ => apps/photos}/lib/utils/validator_util.dart | 0 .../{ => apps/photos}/plugins/ente_cast/.metadata | 0 .../photos}/plugins/ente_cast/analysis_options.yaml | 0 .../photos}/plugins/ente_cast/lib/ente_cast.dart | 0 .../photos}/plugins/ente_cast/lib/src/model.dart | 0 .../photos}/plugins/ente_cast/lib/src/service.dart | 0 .../photos}/plugins/ente_cast/pubspec.lock | 0 .../photos}/plugins/ente_cast/pubspec.yaml | 0 .../photos}/plugins/ente_cast_none/.metadata | 0 .../plugins/ente_cast_none/analysis_options.yaml | 0 .../plugins/ente_cast_none/lib/ente_cast_none.dart | 0 .../plugins/ente_cast_none/lib/src/service.dart | 0 .../photos}/plugins/ente_cast_none/pubspec.lock | 0 .../photos}/plugins/ente_cast_none/pubspec.yaml | 0 .../photos}/plugins/ente_cast_normal/.metadata | 0 .../plugins/ente_cast_normal/analysis_options.yaml | 0 .../ente_cast_normal/lib/ente_cast_normal.dart | 0 .../plugins/ente_cast_normal/lib/src/service.dart | 0 .../photos}/plugins/ente_cast_normal/pubspec.lock | 0 .../photos}/plugins/ente_cast_normal/pubspec.yaml | 0 .../{ => apps/photos}/plugins/ente_crypto/.metadata | 0 .../plugins/ente_crypto/analysis_options.yaml | 0 .../plugins/ente_crypto/lib/ente_crypto.dart | 0 .../photos}/plugins/ente_crypto/lib/src/crypto.dart | 0 .../lib/src/models/derived_key_result.dart | 0 .../lib/src/models/encryption_result.dart | 0 .../plugins/ente_crypto/lib/src/models/errors.dart | 0 .../photos}/plugins/ente_crypto/pubspec.lock | 0 .../photos}/plugins/ente_crypto/pubspec.yaml | 0 .../photos}/plugins/ente_feature_flag/.metadata | 0 .../plugins/ente_feature_flag/analysis_options.yaml | 0 .../ente_feature_flag/lib/ente_feature_flag.dart | 0 .../plugins/ente_feature_flag/lib/src/model.dart | 0 .../plugins/ente_feature_flag/lib/src/service.dart | 0 .../photos}/plugins/ente_feature_flag/pubspec.lock | 0 .../photos}/plugins/ente_feature_flag/pubspec.yaml | 0 .../{ => apps/photos}/plugins/onnx_dart/.metadata | 0 .../photos}/plugins/onnx_dart/analysis_options.yaml | 0 .../android/.gradle/8.5/checksums/checksums.lock | Bin .../dependencies-accessors.lock | Bin .../8.5/dependencies-accessors/gc.properties | 0 .../8.5/executionHistory/executionHistory.bin | Bin .../8.5/executionHistory/executionHistory.lock | Bin .../android/.gradle/8.5/fileChanges/last-build.bin | Bin .../android/.gradle/8.5/fileHashes/fileHashes.bin | Bin .../android/.gradle/8.5/fileHashes/fileHashes.lock | Bin .../onnx_dart/android/.gradle/8.5/gc.properties | 0 .../buildOutputCleanup/buildOutputCleanup.lock | Bin .../.gradle/buildOutputCleanup/cache.properties | 0 .../.gradle/buildOutputCleanup/outputFiles.bin | Bin .../onnx_dart/android/.gradle/config.properties | 0 .../onnx_dart/android/.gradle/file-system.probe | Bin .../onnx_dart/android/.gradle/vcs-1/gc.properties | 0 .../photos}/plugins/onnx_dart/android/build.gradle | 0 .../android/gradle/wrapper/gradle-wrapper.jar | Bin .../gradle/wrapper/gradle-wrapper.properties | 0 .../photos}/plugins/onnx_dart/android/gradlew | 0 .../photos}/plugins/onnx_dart/android/gradlew.bat | 0 .../plugins/onnx_dart/android/local.properties | 0 .../plugins/onnx_dart/android/settings.gradle | 0 .../onnx_dart/android/src/main/AndroidManifest.xml | 0 .../io/ente/photos/onnx_dart/OnnxDartPlugin.kt | 0 .../photos}/plugins/onnx_dart/lib/onnx_dart.dart | 0 .../onnx_dart/lib/onnx_dart_method_channel.dart | 0 .../onnx_dart/lib/onnx_dart_platform_interface.dart | 0 .../photos}/plugins/onnx_dart/pubspec.lock | 0 .../photos}/plugins/onnx_dart/pubspec.yaml | 0 mobile/{ => apps/photos}/pubspec.lock | 0 mobile/{ => apps/photos}/pubspec.yaml | 0 mobile/{ => apps/photos}/run.sh | 0 .../{ => apps/photos}/scripts/app_init_perf_test.sh | 0 mobile/{ => apps/photos}/scripts/bump_version.sh | 0 mobile/{ => apps/photos}/scripts/create_tag.sh | 0 .../photos}/scripts/gallery_scroll_perf_test.sh | 0 .../photos}/test/utils/date_time_util_test.dart | 0 .../test/utils/parsing_loc_from_exif_test.dart | 0 .../{ => apps/photos}/test_driver/perf_driver.dart | 0 1873 files changed, 0 insertions(+), 0 deletions(-) rename mobile/{ => apps/photos}/.gitattributes (100%) rename mobile/{ => apps/photos}/.gitignore (100%) rename mobile/{ => apps/photos}/.gradle/6.5.1/fileHashes/fileHashes.lock (100%) rename mobile/{ => apps/photos}/.gradle/6.5.1/gc.properties (100%) rename mobile/{ => apps/photos}/.gradle/checksums/checksums.lock (100%) rename mobile/{ => apps/photos}/.gradle/vcs-1/gc.properties (100%) rename mobile/{ => apps/photos}/.metadata (100%) rename mobile/{ => apps/photos}/CHANGELOG.md (100%) rename mobile/{ => apps/photos}/Gemfile (100%) rename mobile/{ => apps/photos}/Gemfile.lock (100%) rename mobile/{ => apps/photos}/README.md (100%) rename mobile/{ => apps/photos}/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/android/.gitignore (100%) rename mobile/{ => apps/photos}/android/.project (100%) rename mobile/{ => apps/photos}/android/app/.classpath (100%) rename mobile/{ => apps/photos}/android/app/.project (100%) rename mobile/{ => apps/photos}/android/app/build.gradle (100%) rename mobile/{ => apps/photos}/android/app/proguard-rules.pro (100%) rename mobile/{ => apps/photos}/android/app/src/debug/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/app/src/debug/res/values/strings.xml (100%) rename mobile/{ => apps/photos}/android/app/src/dev/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/app/src/dev/res/mipmap-hdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/dev/res/mipmap-mdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/dev/res/mipmap-xhdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/dev/res/mipmap-xxhdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/dev/res/mipmap-xxxhdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/dev/res/values/strings.xml (100%) rename mobile/{ => apps/photos}/android/app/src/fdroid/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/app/src/independent/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/kotlin/io/ente/photos/EnteAlbumsWidgetProvider.kt (100%) rename mobile/{ => apps/photos}/android/app/src/main/kotlin/io/ente/photos/EnteMemoryWidgetProvider.kt (100%) rename mobile/{ => apps/photos}/android/app/src/main/kotlin/io/ente/photos/EntePeopleWidgetProvider.kt (100%) rename mobile/{ => apps/photos}/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/de/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/de/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/de/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/icon/icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/en-US/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/es/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/es/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/es/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/fr/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/fr/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/fr/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/he/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/he/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/he/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/it/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/it/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/it/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/nl/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/nl/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/nl/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/pl/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/pl/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/pt/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/pt/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/pt/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/ru/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/ru/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/ru/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/zh/full-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/zh/short-description.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/play/listings/zh/title.txt (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/ic_albums_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/ic_memories_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/ic_monochrome_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/ic_og_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/ic_people_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-hdpi/splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-ldpi/ic_home_widget_default.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/ic_albums_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/ic_memories_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/ic_monochrome_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/ic_og_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/ic_people_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-mdpi/splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-hdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-mdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-v21/background.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-v21/launch_background.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-xhdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night/background.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-night/launch_background.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-v21/background.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/ic_albums_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/ic_memories_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/ic_monochrome_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/ic_og_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/ic_people_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xhdpi/splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/ic_albums_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/ic_memories_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/ic_monochrome_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/ic_og_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/ic_people_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxhdpi/splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/android12splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/ic_albums_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/ic_memories_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/ic_monochrome_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/ic_og_launcher_foreground.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/ic_people_widget.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable-xxxhdpi/splash.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/albums_widget_preview.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/background.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/launch_background.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/memory_widget_preview.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/notification_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/people_widget_preview.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/drawable/widget_background.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/layout/albums_widget_layout.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/layout/gradient_overlay.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/layout/memory_widget_layout.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/layout/people_widget_layout.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-anydpi-v26/icon_dark.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-anydpi-v26/icon_green.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-anydpi-v26/icon_light.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-anydpi-v26/icon_og.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/ic_launcher.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/icon_dark.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/icon_green.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/icon_light.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/icon_monochrome.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/icon_og.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-hdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/ic_launcher.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/icon_dark.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/icon_green.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/icon_light.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/icon_monochrome.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/icon_og.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-mdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/icon_dark.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/icon_green.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/icon_light.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/icon_monochrome.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/icon_og.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/icon_dark.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/icon_green.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/icon_light.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/icon_monochrome.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/icon_og.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/icon_dark.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/icon_green.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/icon_light.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/icon_monochrome.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/icon_og.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values-night-v31/styles.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values-night/colors.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values-night/styles.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values-v31/styles.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values/colors.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values/strings.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/values/styles.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/xml/albums_widget.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/xml/data_extraction_rules.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/xml/memory_widget.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/xml/network_security_config.xml (100%) rename mobile/{ => apps/photos}/android/app/src/main/res/xml/people_widget.xml (100%) rename mobile/{ => apps/photos}/android/app/src/playstore/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/app/src/profile/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/android/build.gradle (100%) rename mobile/{ => apps/photos}/android/gradle.properties (100%) rename mobile/{ => apps/photos}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename mobile/{ => apps/photos}/android/permissions.md (100%) rename mobile/{ => apps/photos}/android/settings.gradle (100%) rename mobile/{ => apps/photos}/android/settings_aar.gradle (100%) rename mobile/{ => apps/photos}/assets/2.0x/active_subscription.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/albums-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/create_new_album.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/family_plan_leave.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/gallery_locked.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/loading_photos_background.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/loading_photos_background_dark.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/lock_screen_background.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/map_world.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/memories-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/new_empty_album.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/new_empty_album_dark.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/onboarding_lock.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/onboarding_safe.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/onboarding_sync.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/people-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/popular_subscription.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/processing-video-failed.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/processing-video-success.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/processing-video.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/storage_card_background.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_AVI.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_GIF.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_HEIC.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_JPEG.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_JPG.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_MKV.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_MP4.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_PNG.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_WEBP.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_live.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_photos.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_unknown.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/type_videos.png (100%) rename mobile/{ => apps/photos}/assets/2.0x/video-processing-queued.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/active_subscription.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/albums-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/create_new_album.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/family_plan_leave.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/gallery_locked.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/loading_photos_background.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/loading_photos_background_dark.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/lock_screen_background.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/map_world.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/memories-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/new_empty_album.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/new_empty_album_dark.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/onboarding_lock.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/onboarding_safe.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/onboarding_sync.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/people-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/popular_subscription.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/processing-video-failed.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/processing-video-success.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/processing-video.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/storage_card_background.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_AVI.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_GIF.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_HEIC.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_JPEG.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_JPG.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_MKV.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_MP4.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_PNG.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_WEBP.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_live.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_photos.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_unknown.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/type_videos.png (100%) rename mobile/{ => apps/photos}/assets/3.0x/video-processing-queued.png (100%) rename mobile/{ => apps/photos}/assets/active_subscription.png (100%) rename mobile/{ => apps/photos}/assets/albums-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/create_new_album.png (100%) rename mobile/{ => apps/photos}/assets/earth_blurred.png (100%) rename mobile/{ => apps/photos}/assets/family_plan_leave.png (100%) rename mobile/{ => apps/photos}/assets/gallery_locked.png (100%) rename mobile/{ => apps/photos}/assets/icon-light.png (100%) rename mobile/{ => apps/photos}/assets/icons/albums-widget-icon.svg (100%) rename mobile/{ => apps/photos}/assets/icons/guest_view_icon.svg (100%) rename mobile/{ => apps/photos}/assets/icons/legacy-dark.svg (100%) rename mobile/{ => apps/photos}/assets/icons/legacy-light.svg (100%) rename mobile/{ => apps/photos}/assets/icons/list_view_icon_dark.svg (100%) rename mobile/{ => apps/photos}/assets/icons/list_view_icon_light.svg (100%) rename mobile/{ => apps/photos}/assets/icons/memories-widget-icon.svg (100%) rename mobile/{ => apps/photos}/assets/icons/past-year-memory-icon.svg (100%) rename mobile/{ => apps/photos}/assets/icons/people-widget-icon.svg (100%) rename mobile/{ => apps/photos}/assets/icons/search_icon_dark.svg (100%) rename mobile/{ => apps/photos}/assets/icons/search_icon_light.svg (100%) rename mobile/{ => apps/photos}/assets/icons/smart-memory-icon.svg (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-dark.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-dev.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-foreground.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-green.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-light.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-monochrome-foreground.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-og-foreground.png (100%) rename mobile/{ => apps/photos}/assets/launcher_icon/icon-og.png (100%) rename mobile/{ => apps/photos}/assets/loadingGalleryLottie.json (100%) rename mobile/{ => apps/photos}/assets/loading_photos_background.png (100%) rename mobile/{ => apps/photos}/assets/loading_photos_background_dark.png (100%) rename mobile/{ => apps/photos}/assets/lock_screen_background.png (100%) rename mobile/{ => apps/photos}/assets/map.png (100%) rename mobile/{ => apps/photos}/assets/map_world.png (100%) rename mobile/{ => apps/photos}/assets/memories-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/new_empty_album.png (100%) rename mobile/{ => apps/photos}/assets/new_empty_album_dark.png (100%) rename mobile/{ => apps/photos}/assets/onboarding_lock.png (100%) rename mobile/{ => apps/photos}/assets/onboarding_safe.png (100%) rename mobile/{ => apps/photos}/assets/onboarding_sync.png (100%) rename mobile/{ => apps/photos}/assets/people-widget-static.png (100%) rename mobile/{ => apps/photos}/assets/popular_subscription.png (100%) rename mobile/{ => apps/photos}/assets/preserved_green.png (100%) rename mobile/{ => apps/photos}/assets/processing-video-failed.png (100%) rename mobile/{ => apps/photos}/assets/processing-video-success.png (100%) rename mobile/{ => apps/photos}/assets/processing-video.png (100%) rename mobile/{ => apps/photos}/assets/splash-screen-icon.png (100%) rename mobile/{ => apps/photos}/assets/storage_card_background.png (100%) rename mobile/{ => apps/photos}/assets/type_AVI.png (100%) rename mobile/{ => apps/photos}/assets/type_GIF.png (100%) rename mobile/{ => apps/photos}/assets/type_HEIC.png (100%) rename mobile/{ => apps/photos}/assets/type_JPEG.png (100%) rename mobile/{ => apps/photos}/assets/type_JPG.png (100%) rename mobile/{ => apps/photos}/assets/type_MKV.png (100%) rename mobile/{ => apps/photos}/assets/type_MP4.png (100%) rename mobile/{ => apps/photos}/assets/type_PNG.png (100%) rename mobile/{ => apps/photos}/assets/type_WEBP.png (100%) rename mobile/{ => apps/photos}/assets/type_live.png (100%) rename mobile/{ => apps/photos}/assets/type_photos.png (100%) rename mobile/{ => apps/photos}/assets/type_unknown.png (100%) rename mobile/{ => apps/photos}/assets/type_videos.png (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-free-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-original-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-ratio_16_9-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-ratio_1_1-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-ratio_3_4-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-ratio_4_3-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-crop-ratio_9_16-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-editor-crop-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-editor-rotate-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-editor/video-editor-trim-action.svg (100%) rename mobile/{ => apps/photos}/assets/video-processing-queued.png (100%) rename mobile/{ => apps/photos}/build-apk.sh (100%) rename mobile/{ => apps/photos}/crowdin.yml (100%) rename mobile/{ => apps/photos}/devtools_options.yaml (100%) rename mobile/{ => apps/photos}/docs/README.md (100%) rename mobile/{ => apps/photos}/docs/assets/translations_1.png (100%) rename mobile/{ => apps/photos}/docs/assets/translations_2.png (100%) rename mobile/{ => apps/photos}/docs/assets/translations_3.png (100%) rename mobile/{ => apps/photos}/docs/assets/translations_4.png (100%) rename mobile/{ => apps/photos}/docs/dev.md (100%) rename mobile/{ => apps/photos}/docs/release.md (100%) rename mobile/{ => apps/photos}/docs/translations.md (100%) rename mobile/{ => apps/photos}/docs/vscode/launch.json (100%) rename mobile/{ => apps/photos}/fastlane/Appfile (100%) rename mobile/{ => apps/photos}/fastlane/Fastfile (100%) rename mobile/{ => apps/photos}/fastlane/Pluginfile (100%) rename mobile/{ => apps/photos}/fastlane/README.md (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ar/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ar/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ar/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/be/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/be/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/be/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/bg/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/bg/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/bg/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ca/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ca/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ca/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/cs/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/cs/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/cs/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/da/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/da/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/da/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/de/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/de/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/de/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/el/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/el/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/el/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/169.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/293.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/317.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/330.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/331.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/333.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/changelogs/420.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/icon.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/en-US/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/es/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/es/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/es/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/et/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/et/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/et/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/eu/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/eu/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/eu/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/fa/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/fa/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/fa/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/fr/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/fr/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/fr/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/gu/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/gu/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/gu/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/he/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/he/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/he/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/hi/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/hi/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/hi/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/hu/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/hu/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/hu/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/id/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/id/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/id/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/it/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/it/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/it/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ja/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ja/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ja/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/km/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/km/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/km/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ko/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ko/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ko/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ku/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ku/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ku/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/lt/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/lt/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/lt/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/lv/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/lv/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/lv/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ml/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ml/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ml/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/nl/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/nl/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/nl/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/no/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/no/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/no/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/or/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/or/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/or/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pl/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pl/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pl/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt_BR/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt_BR/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt_BR/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt_PT/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt_PT/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/pt_PT/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ro/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ro/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ro/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ru/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ru/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ru/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sl/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sl/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sl/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sr/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sr/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sr/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sv/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sv/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/sv/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ta/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ta/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ta/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/te/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/te/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/te/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/th/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/th/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/th/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ti/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ti/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/ti/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/tr/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/tr/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/tr/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/uk/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/uk/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/uk/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/vi/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/vi/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/vi/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/zh/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/zh/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/android/zh/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_129_0.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_3GEN_129_0.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_55_0.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_65_0.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_55_1.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_65_1.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_55_2.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_65_2.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_55_3.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_65_3.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_55_4.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_65_4.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_55_5.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_65_5.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_55_6.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_65_6.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ar/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ar/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ar/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ar/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/be/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/be/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/be/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/be/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/bg/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/bg/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/bg/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/bg/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ca/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ca/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ca/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ca/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/cs/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/cs/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/cs/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/cs/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/da/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/da/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/da/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/da/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/de/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/de/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/de/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/de/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/el/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/el/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/el/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/el/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/en-US/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/en-US/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/en-US/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/en-US/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/es/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/es/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/es/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/es/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/et/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/et/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/et/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/et/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/eu/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/eu/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/eu/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/eu/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fa/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fa/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fa/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fa/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fr/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fr/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fr/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/fr/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/gu/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/gu/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/gu/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/gu/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/he/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/he/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/he/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/he/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hi/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hi/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hi/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hi/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hu/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hu/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hu/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/hu/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/id/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/id/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/id/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/id/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/it/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/it/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/it/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/it/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ja/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ja/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ja/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ja/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/km/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/km/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/km/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/km/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ko/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ko/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ko/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ko/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ku/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ku/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ku/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ku/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lt/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lt/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lt/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lt/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lv/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lv/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lv/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/lv/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ml/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ml/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ml/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ml/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/nl/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/nl/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/nl/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/nl/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/no/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/no/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/no/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/no/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/or/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/or/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/or/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/or/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pl/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pl/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pl/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pl/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_BR/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_BR/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_BR/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_BR/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_PT/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_PT/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_PT/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/pt_PT/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ro/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ro/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ro/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ro/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ru/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ru/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ru/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ru/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sl/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sl/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sl/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sl/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sr/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sr/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sr/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sr/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sv/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sv/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sv/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/sv/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ta/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ta/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ta/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ta/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/te/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/te/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/te/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/te/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/th/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/th/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/th/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/th/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ti/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ti/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ti/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/ti/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/tr/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/tr/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/tr/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/tr/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/uk/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/uk/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/uk/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/uk/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/vi/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/vi/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/vi/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/vi/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/zh/description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/zh/keywords.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/zh/name.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/ios/zh/subtitle.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ar/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ar/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ar/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/be/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/be/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/be/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/bg/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/bg/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/bg/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ca/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ca/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ca/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/cs/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/cs/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/cs/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/da/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/da/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/da/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/de/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/de/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/de/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/el/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/el/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/el/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/changelogs/443.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/featureGraphic.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/icon.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/1_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/2_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/3_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/4_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/5_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/6_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/phoneScreenshots/7_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/sevenInchScreenshots/1_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/images/tenInchScreenshots/1_en-US.png (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/en-US/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/es/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/es/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/es/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/et/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/et/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/et/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/eu/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/eu/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/eu/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/fa/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/fa/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/fa/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/fr/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/fr/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/fr/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/gu/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/gu/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/gu/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/he/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/he/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/he/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/hi/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/hi/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/hi/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/hu/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/hu/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/hu/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/id/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/id/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/id/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/it/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/it/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/it/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ja/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ja/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ja/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/km/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/km/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/km/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ko/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ko/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ko/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ku/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ku/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ku/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/lt/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/lt/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/lt/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/lv/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/lv/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/lv/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ml/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ml/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ml/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/nl/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/nl/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/nl/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/no/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/no/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/no/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/or/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/or/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/or/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pl/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pl/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pl/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt_BR/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt_BR/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt_BR/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt_PT/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt_PT/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/pt_PT/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ro/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ro/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ro/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ru/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ru/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ru/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sl/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sl/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sl/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sr/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sr/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sr/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sv/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sv/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/sv/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ta/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ta/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ta/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/te/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/te/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/te/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/th/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/th/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/th/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ti/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ti/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/ti/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/tr/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/tr/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/tr/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/uk/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/uk/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/uk/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/vi/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/vi/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/vi/title.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/zh/full_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/zh/short_description.txt (100%) rename mobile/{ => apps/photos}/fastlane/metadata/playstore/zh/title.txt (100%) rename mobile/{ => apps/photos}/fonts/Inter-Bold.ttf (100%) rename mobile/{ => apps/photos}/fonts/Inter-Light.ttf (100%) rename mobile/{ => apps/photos}/fonts/Inter-Medium.ttf (100%) rename mobile/{ => apps/photos}/fonts/Inter-Regular.ttf (100%) rename mobile/{ => apps/photos}/fonts/Inter-SemiBold.ttf (100%) rename mobile/{ => apps/photos}/fonts/Montserrat-Bold.ttf (100%) rename mobile/{ => apps/photos}/hooks/pre-commit (100%) rename mobile/{ => apps/photos}/hooks/pre-commit-fdroid (100%) rename mobile/{ => apps/photos}/integration_test/app_init_test.dart (100%) rename mobile/{ => apps/photos}/integration_test/home_gallery_scroll_test.dart (100%) rename mobile/{ => apps/photos}/ios/.gitignore (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/AlbumsWidgetDefault.png (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/AlbumsWidgetPreview.png (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/IconGreen.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/EnteAlbumWidget.swift (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/EnteAlbumWidgetBundle.swift (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidget/Info.plist (100%) rename mobile/{ => apps/photos}/ios/EnteAlbumWidgetExtension.entitlements (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/IconGreen.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/MemoriesWidgetDefault.png (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/MemoriesWidgetPreview.png (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/EnteMemoryWidget.swift (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/EnteMemoryWidgetBundle.swift (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidget/Info.plist (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidgetExtension.entitlements (100%) rename mobile/{ => apps/photos}/ios/EnteMemoryWidgetExtensionDebug.entitlements (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/AccentColor.colorset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/IconGreen.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/PeopleWidgetDefault.png (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/PeopleWidgetPreview.png (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/EntePeopleWidget.swift (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/EntePeopleWidgetBundle.swift (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidget/Info.plist (100%) rename mobile/{ => apps/photos}/ios/EntePeopleWidgetExtension.entitlements (100%) rename mobile/{ => apps/photos}/ios/Flutter/AppFrameworkInfo.plist (100%) rename mobile/{ => apps/photos}/ios/Flutter/Debug.xcconfig (100%) rename mobile/{ => apps/photos}/ios/Flutter/Release.xcconfig (100%) rename mobile/{ => apps/photos}/ios/GoogleService-Info.plist (100%) rename mobile/{ => apps/photos}/ios/Podfile (100%) rename mobile/{ => apps/photos}/ios/Podfile.lock (100%) rename mobile/{ => apps/photos}/ios/Runner.xcodeproj/project.pbxproj (100%) rename mobile/{ => apps/photos}/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename mobile/{ => apps/photos}/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename mobile/{ => apps/photos}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename mobile/{ => apps/photos}/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename mobile/{ => apps/photos}/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename mobile/{ => apps/photos}/ios/Runner/AppDelegate.swift (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGTinted.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename mobile/{ => apps/photos}/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename mobile/{ => apps/photos}/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename mobile/{ => apps/photos}/ios/Runner/Base.lproj/Main.storyboard (100%) rename mobile/{ => apps/photos}/ios/Runner/Info.plist (100%) rename mobile/{ => apps/photos}/ios/Runner/Runner-Bridging-Header.h (100%) rename mobile/{ => apps/photos}/ios/Runner/Runner.entitlements (100%) rename mobile/{ => apps/photos}/l10n.yaml (100%) rename mobile/{ => apps/photos}/lib/app.dart (100%) rename mobile/{ => apps/photos}/lib/core/cache/image_cache.dart (100%) rename mobile/{ => apps/photos}/lib/core/cache/lru_map.dart (100%) rename mobile/{ => apps/photos}/lib/core/cache/thumbnail_in_memory_cache.dart (100%) rename mobile/{ => apps/photos}/lib/core/cache/video_cache_manager.dart (100%) rename mobile/{ => apps/photos}/lib/core/configuration.dart (100%) rename mobile/{ => apps/photos}/lib/core/constants.dart (100%) rename mobile/{ => apps/photos}/lib/core/error-reporting/isolate_logging.dart (100%) rename mobile/{ => apps/photos}/lib/core/error-reporting/super_logging.dart (100%) rename mobile/{ => apps/photos}/lib/core/error-reporting/tunneled_transport.dart (100%) rename mobile/{ => apps/photos}/lib/core/errors.dart (100%) rename mobile/{ => apps/photos}/lib/core/event_bus.dart (100%) rename mobile/{ => apps/photos}/lib/core/network/ente_interceptor.dart (100%) rename mobile/{ => apps/photos}/lib/core/network/network.dart (100%) rename mobile/{ => apps/photos}/lib/data/holidays.dart (100%) rename mobile/{ => apps/photos}/lib/data/months.dart (100%) rename mobile/{ => apps/photos}/lib/data/years.dart (100%) rename mobile/{ => apps/photos}/lib/db/collections_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/common/base.dart (100%) rename mobile/{ => apps/photos}/lib/db/common/conflict_algo.dart (100%) rename mobile/{ => apps/photos}/lib/db/device_files_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/entities_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/file_updation_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/files_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/ignored_files_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/memories_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/ml/base.dart (100%) rename mobile/{ => apps/photos}/lib/db/ml/db.dart (100%) rename mobile/{ => apps/photos}/lib/db/ml/db_model_mappers.dart (100%) rename mobile/{ => apps/photos}/lib/db/ml/filedata.dart (100%) rename mobile/{ => apps/photos}/lib/db/ml/schema.dart (100%) rename mobile/{ => apps/photos}/lib/db/trash_db.dart (100%) rename mobile/{ => apps/photos}/lib/db/upload_locks_db.dart (100%) rename mobile/{ => apps/photos}/lib/emergency/emergency_page.dart (100%) rename mobile/{ => apps/photos}/lib/emergency/emergency_service.dart (100%) rename mobile/{ => apps/photos}/lib/emergency/model.dart (100%) rename mobile/{ => apps/photos}/lib/emergency/other_contact_page.dart (100%) rename mobile/{ => apps/photos}/lib/emergency/recover_others_account.dart (100%) rename mobile/{ => apps/photos}/lib/emergency/select_contact_page.dart (100%) rename mobile/{ => apps/photos}/lib/ente_theme_data.dart (100%) rename mobile/{ => apps/photos}/lib/events/account_configured_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/album_sort_order_change_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/backup_folders_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/backup_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/clear_album_selections_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/clear_and_unfocus_search_bar_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/clear_selections_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/collection_meta_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/collection_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/compute_control_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/create_new_album_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/details_sheet_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/diff_sync_complete_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/embedding_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/endpoint_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/event.dart (100%) rename mobile/{ => apps/photos}/lib/events/favorites_service_init_complete_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/file_caption_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/file_uploaded_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/files_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/force_reload_home_gallery_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/force_reload_trash_page_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/guest_view_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/hide_shared_items_from_home_gallery_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/local_import_progress.dart (100%) rename mobile/{ => apps/photos}/lib/events/local_photos_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/location_tag_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/magic_cache_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/magic_sort_change_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/memories_changed_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/memories_setting_changed.dart (100%) rename mobile/{ => apps/photos}/lib/events/memory_seen_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/notification_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/opened_settings_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/pause_video_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/people_changed_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/permission_granted_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/reset_zoom_of_photo_view_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/seekbar_triggered_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/signed_in_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/stream_switched_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/subscription_purchased_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/sync_status_update_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/tab_changed_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/trash_updated_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/trigger_logout_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/two_factor_status_change_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/use_media_kit_for_video.dart (100%) rename mobile/{ => apps/photos}/lib/events/user_details_changed_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/user_logged_out_event.dart (100%) rename mobile/{ => apps/photos}/lib/events/video_streaming_changed.dart (100%) rename mobile/{ => apps/photos}/lib/extensions/input_formatter.dart (100%) rename mobile/{ => apps/photos}/lib/extensions/list.dart (100%) rename mobile/{ => apps/photos}/lib/extensions/ml_linalg_extensions.dart (100%) rename mobile/{ => apps/photos}/lib/extensions/stop_watch.dart (100%) rename mobile/{ => apps/photos}/lib/extensions/user_extension.dart (100%) rename mobile/{ => apps/photos}/lib/gateways/cast_gw.dart (100%) rename mobile/{ => apps/photos}/lib/gateways/entity_gw.dart (100%) rename mobile/{ => apps/photos}/lib/gateways/storage_bonus_gw.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_all.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ar.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_be.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_bg.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ca.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_cs.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_da.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_de.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_el.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_en.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_es.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_et.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_eu.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_fa.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_fr.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_gu.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_he.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_hi.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_hu.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_id.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_it.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ja.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_km.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ko.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ku.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_lt.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_lv.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ml.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_nl.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_no.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_or.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_pl.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_pt.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_pt_BR.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_pt_PT.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ro.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ru.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_sl.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_sr.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_sv.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ta.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_te.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_th.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_ti.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_tr.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_uk.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_vi.dart (100%) rename mobile/{ => apps/photos}/lib/generated/intl/messages_zh.dart (100%) rename mobile/{ => apps/photos}/lib/generated/l10n.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/box.pb.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/box.pbenum.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/box.pbjson.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/box.pbserver.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/point.pb.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/point.pbenum.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/point.pbjson.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/point.pbserver.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/vector.pb.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/vector.pbenum.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/vector.pbjson.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/common/vector.pbserver.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/face.pb.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/face.pbenum.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/face.pbjson.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/face.pbserver.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/fileml.pb.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/fileml.pbenum.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/fileml.pbjson.dart (100%) rename mobile/{ => apps/photos}/lib/generated/protos/ente/ml/fileml.pbserver.dart (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ar.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_be.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_bg.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ca.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_cs.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_da.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_de.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_el.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_en.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_es.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_et.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_eu.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_fa.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_fr.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_gu.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_he.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_hi.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_hu.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_id.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_it.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ja.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_km.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ko.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ku.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_lt.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_lv.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ml.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_nl.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_no.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_or.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_pl.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_pt.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_pt_BR.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_pt_PT.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ro.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ru.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_sl.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_sr.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_sv.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ta.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_te.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_th.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_ti.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_tr.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_uk.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_vi.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/intl_zh.arb (100%) rename mobile/{ => apps/photos}/lib/l10n/l10n.dart (100%) rename mobile/{ => apps/photos}/lib/main.dart (100%) rename mobile/{ => apps/photos}/lib/models/account/two_factor.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/billing/billing_plan.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/billing/subscription.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/collection/collection_file_item.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/collection/create_request.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/collection/public_url.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/collection/trash_item_request.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/collection/user.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/entity/data.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/entity/key.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/entity/type.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/metadata.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/storage_bonus/bonus.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/storage_bonus/storage_bonus.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/delete_account.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/key_attributes.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/key_gen_result.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/private_key_attributes.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/sessions.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/set_keys_request.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/set_recovery_key_request.dart (100%) rename mobile/{ => apps/photos}/lib/models/api/user/srp.dart (100%) rename mobile/{ => apps/photos}/lib/models/backup/backup_item.dart (100%) rename mobile/{ => apps/photos}/lib/models/backup/backup_item_status.dart (100%) rename mobile/{ => apps/photos}/lib/models/backup_status.dart (100%) rename mobile/{ => apps/photos}/lib/models/base/id.dart (100%) rename mobile/{ => apps/photos}/lib/models/base_location.dart (100%) rename mobile/{ => apps/photos}/lib/models/button_result.dart (100%) rename mobile/{ => apps/photos}/lib/models/collection/collection.dart (100%) rename mobile/{ => apps/photos}/lib/models/collection/collection_items.dart (100%) rename mobile/{ => apps/photos}/lib/models/device_collection.dart (100%) rename mobile/{ => apps/photos}/lib/models/duplicate_files.dart (100%) rename mobile/{ => apps/photos}/lib/models/execution_states.dart (100%) rename mobile/{ => apps/photos}/lib/models/ffmpeg/channel_layouts.dart (100%) rename mobile/{ => apps/photos}/lib/models/ffmpeg/codecs.dart (100%) rename mobile/{ => apps/photos}/lib/models/ffmpeg/ffprobe_keys.dart (100%) rename mobile/{ => apps/photos}/lib/models/ffmpeg/ffprobe_props.dart (100%) rename mobile/{ => apps/photos}/lib/models/ffmpeg/language.dart (100%) rename mobile/{ => apps/photos}/lib/models/ffmpeg/mp4.dart (100%) rename mobile/{ => apps/photos}/lib/models/file/extensions/file_props.dart (100%) rename mobile/{ => apps/photos}/lib/models/file/file.dart (100%) rename mobile/{ => apps/photos}/lib/models/file/file_type.dart (100%) rename mobile/{ => apps/photos}/lib/models/file/trash_file.dart (100%) rename mobile/{ => apps/photos}/lib/models/file_load_result.dart (100%) rename mobile/{ => apps/photos}/lib/models/files_split.dart (100%) rename mobile/{ => apps/photos}/lib/models/gallery_type.dart (100%) rename mobile/{ => apps/photos}/lib/models/ignored_file.dart (100%) rename mobile/{ => apps/photos}/lib/models/local_entity_data.dart (100%) rename mobile/{ => apps/photos}/lib/models/location/location.dart (100%) rename mobile/{ => apps/photos}/lib/models/location/location.freezed.dart (100%) rename mobile/{ => apps/photos}/lib/models/location/location.g.dart (100%) rename mobile/{ => apps/photos}/lib/models/location_tag/location_tag.dart (100%) rename mobile/{ => apps/photos}/lib/models/location_tag/location_tag.freezed.dart (100%) rename mobile/{ => apps/photos}/lib/models/location_tag/location_tag.g.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/clip_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/filler_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/memories_cache.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/on_this_day_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/people_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/smart_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/smart_memory_constants.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/time_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/memories/trip_memory.dart (100%) rename mobile/{ => apps/photos}/lib/models/metadata/collection_magic.dart (100%) rename mobile/{ => apps/photos}/lib/models/metadata/common_keys.dart (100%) rename mobile/{ => apps/photos}/lib/models/metadata/file_magic.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/clip.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/discover/prompt.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/box.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/detection.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/dimension.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/face.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/face_with_embedding.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/landmark.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/face/person.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/ml_typedefs.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/ml_versions.dart (100%) rename mobile/{ => apps/photos}/lib/models/ml/vector.dart (100%) rename mobile/{ => apps/photos}/lib/models/preview/playlist_data.dart (100%) rename mobile/{ => apps/photos}/lib/models/preview/preview_item.dart (100%) rename mobile/{ => apps/photos}/lib/models/preview/preview_item_status.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/album_search_result.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/generic_search_result.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/album_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/contacts_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/face_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/file_type_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/hierarchical_search_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/location_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/magic_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/only_them_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/top_level_generic_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/hierarchical/uploader_filter.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/index_of_indexed_stack.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/recent_searches.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/search_constants.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/search_result.dart (100%) rename mobile/{ => apps/photos}/lib/models/search/search_types.dart (100%) rename mobile/{ => apps/photos}/lib/models/selected_albums.dart (100%) rename mobile/{ => apps/photos}/lib/models/selected_files.dart (100%) rename mobile/{ => apps/photos}/lib/models/selected_people.dart (100%) rename mobile/{ => apps/photos}/lib/models/typedefs.dart (100%) rename mobile/{ => apps/photos}/lib/models/upload_strategy.dart (100%) rename mobile/{ => apps/photos}/lib/models/user_details.dart (100%) rename mobile/{ => apps/photos}/lib/module/download/file_url.dart (100%) rename mobile/{ => apps/photos}/lib/module/download/manager.dart (100%) rename mobile/{ => apps/photos}/lib/module/download/task.dart (100%) rename mobile/{ => apps/photos}/lib/module/upload/model/multipart.dart (100%) rename mobile/{ => apps/photos}/lib/module/upload/model/upload_url.dart (100%) rename mobile/{ => apps/photos}/lib/module/upload/model/xml.dart (100%) rename mobile/{ => apps/photos}/lib/module/upload/service/multipart.dart (100%) rename mobile/{ => apps/photos}/lib/service_locator.dart (100%) rename mobile/{ => apps/photos}/lib/services/account/billing_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/account/passkey_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/account/user_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/album_home_widget_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/app_lifecycle_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/collections_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/deduplication_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/entity_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/favorites_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/file_magic_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/filedata/filedata_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/filedata/model/enc_file_data.dart (100%) rename mobile/{ => apps/photos}/lib/services/filedata/model/file_data.dart (100%) rename mobile/{ => apps/photos}/lib/services/filedata/model/response.dart (100%) rename mobile/{ => apps/photos}/lib/services/files_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/collection_ignore.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/db_filters.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/dedupe_by_upload_id.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/filter.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/only_uploaded_files_filter.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/shared.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/type_filter.dart (100%) rename mobile/{ => apps/photos}/lib/services/filter/upload_ignore.dart (100%) rename mobile/{ => apps/photos}/lib/services/hidden_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/home_widget_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/ignored_files_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/isolate_functions.dart (100%) rename mobile/{ => apps/photos}/lib/services/isolate_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/language_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/local_authentication_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/local_file_update_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/location_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/compute_controller.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_alignment/alignment_result.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_alignment/similarity_transform.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_detection/detection.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_detection/face_detection_postprocessing.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_detection/face_detection_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_filtering/blur_detection_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/face_recognition_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_ml/person/person_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/face_thumbnail_generator.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_computer.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_constants.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_exceptions.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_indexing_isolate.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_model.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_models_overview.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_result.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/ml_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/onnx_env.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/semantic_search/clip/clip_image_encoder.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/semantic_search/clip/clip_text_encoder.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/semantic_search/clip/clip_text_tokenizer.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/semantic_search/query_result.dart (100%) rename mobile/{ => apps/photos}/lib/services/machine_learning/semantic_search/semantic_search_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/magic_cache_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/memories_cache_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/memory_home_widget_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/notification_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/people_home_widget_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/permission/service.dart (100%) rename mobile/{ => apps/photos}/lib/services/push_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/remote_assets_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/search_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/smart_memories_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/storage_bonus_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/diff_fetcher.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/import/diff.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/import/local_assets.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/import/model.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/local_sync_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/remote_sync_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/sync_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/sync/trash_sync_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/update_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/video_memory_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/video_preview_service.dart (100%) rename mobile/{ => apps/photos}/lib/services/wake_lock_service.dart (100%) rename mobile/{ => apps/photos}/lib/states/all_sections_examples_state.dart (100%) rename mobile/{ => apps/photos}/lib/states/detail_page_state.dart (100%) rename mobile/{ => apps/photos}/lib/states/location_screen_state.dart (100%) rename mobile/{ => apps/photos}/lib/states/location_state.dart (100%) rename mobile/{ => apps/photos}/lib/states/user_details_state.dart (100%) rename mobile/{ => apps/photos}/lib/theme/colors.dart (100%) rename mobile/{ => apps/photos}/lib/theme/effects.dart (100%) rename mobile/{ => apps/photos}/lib/theme/ente_theme.dart (100%) rename mobile/{ => apps/photos}/lib/theme/text_style.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/change_email_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/delete_account_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/email_entry_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/login_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/login_pwd_verification_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/ott_verification_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/passkey_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/password_entry_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/password_reentry_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/recovery_key_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/recovery_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/request_pwd_verification_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/sessions_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/two_factor_authentication_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/two_factor_recovery_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/two_factor_setup_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/account/verify_recovery_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/actions/collection/collection_file_actions.dart (100%) rename mobile/{ => apps/photos}/lib/ui/actions/collection/collection_sharing_actions.dart (100%) rename mobile/{ => apps/photos}/lib/ui/actions/file/file_actions.dart (100%) rename mobile/{ => apps/photos}/lib/ui/cast/auto.dart (100%) rename mobile/{ => apps/photos}/lib/ui/cast/choose.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/column_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/horizontal_list.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/list_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/new_list_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/new_row_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/row_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/album/vertical_list.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/button/archived_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/button/hidden_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/button/trash_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/button/uncategorized_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/collection_action_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/collection_list_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/device/device_folder_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/device/device_folders_grid_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/device/device_folders_vertical_grid_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/collections/flex_grid_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/bottom_shadow.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/date_input.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/dynamic_fab.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/fast_scroll_physics.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/gradient_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/linear_progress_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/loading_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/popup_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/progress_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/user_dialogs.dart (100%) rename mobile/{ => apps/photos}/lib/ui/common/web_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/action_sheet_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/blur_menu_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/action_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/album_action_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/album_bottom_action_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/expanded_menu_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/people_action_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/people_bottom_action_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/bottom_of_title_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/buttons/button_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/buttons/chip_button_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/buttons/icon_button_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/buttons/inline_button_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/captioned_text_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/dialog_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/divider_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/empty_state_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/end_to_end_banner.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/expandable_menu_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/home_header_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/info_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/keyboard/keyboard_oveylay.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/keyboard/keyboard_top_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/menu_item_widget/menu_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/menu_section_description_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/menu_section_title.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/models/button_type.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/models/custom_button_style.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/notification_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/searchable_appbar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/text_input_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/title_bar_title_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/title_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/components/toggle_switch_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/extents_page_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/growth/apply_code_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/growth/code_success_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/growth/referral_code_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/growth/referral_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/growth/storage_details_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/grant_permissions_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/header_error_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/header_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/home_bottom_nav_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/home_gallery_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/landing_page_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/loading_photos_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/memories/all_memories_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/memories/custom_listener.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/memories/full_screen_memory.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/memories/memories_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/memories/memory_cover_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/memories/memory_progress_indicator.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/start_backup_hook_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/home/status_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/huge_listview/draggable_scrollbar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/huge_listview/huge_listview.dart (100%) rename mobile/{ => apps/photos}/lib/ui/huge_listview/scroll_bar_thumb.dart (100%) rename mobile/{ => apps/photos}/lib/ui/lifecycle_event_handler.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/enable_map.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/image_marker.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_gallery_tile.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_gallery_tile_badge.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_isolate.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_marker.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_pull_up_gallery.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/map_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/marker_image.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/tile/attribution/map_attribution.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/tile/cache.dart (100%) rename mobile/{ => apps/photos}/lib/ui/map/tile/layers.dart (100%) rename mobile/{ => apps/photos}/lib/ui/notification/toast.dart (100%) rename mobile/{ => apps/photos}/lib/ui/notification/update/change_log_entry.dart (100%) rename mobile/{ => apps/photos}/lib/ui/notification/update/change_log_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/add_on_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/billing_questions_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/child_subscription_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/payment_web_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/store_subscription_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/stripe_subscription_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/subscription.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/subscription_common_widgets.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/subscription_plan_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/payment/view_add_on_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/about_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/account_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/advanced_settings_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/app_icon_selection_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/app_update_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/app_version_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/backup/backup_folder_selection_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/backup/backup_item_card.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/backup/backup_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/backup/backup_settings_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/backup/backup_status_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/backup/free_space_options.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/common_settings.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/debug/debug_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/debug/ml_debug_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/developer_settings_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/developer_settings_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/gallery_settings_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/general_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/inherited_settings_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/language_picker.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/custom_pin_keypad.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/lock_screen_options.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/lock_screen_password.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/lock_screen/lock_screen_pin.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/ml/enable_ml_consent.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/ml/machine_learning_settings_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/ml/ml_user_dev_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/notification_settings_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/pending_sync/path_info_storage_viewer.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/pending_sync/pending_sync_info_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/security_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/settings_title_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/social_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/storage_card_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/storage_progress_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/support_section_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/theme_switch_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/widget_settings_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/widgets/albums_widget_settings.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/widgets/memories_widget_settings.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings/widgets/people_widget_settings.dart (100%) rename mobile/{ => apps/photos}/lib/ui/settings_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/add_participant_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/album_participants_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/album_share_info_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/manage_album_participant.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/manage_links_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/more_count_badge.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/pickers/device_limit_picker_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/pickers/link_expiry_picker_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/share_collection_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/show_images_prevew.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/user_avator_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/sharing/verify_identity_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/home_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/nav_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/section_title.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/shared/all_quick_links_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/shared/empty_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/shared/quick_link_album_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/shared_collections_tab.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tabs/user_collections_tab.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/app_lock.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_common_widgets.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_creator_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_item_icon.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_save_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_test_grid.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_with_five_items.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_with_four_items.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_with_six_items.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_with_three_items.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/collage/collage_with_two_items.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/debug/app_storage_viewer.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/debug/log_file_viewer.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/debug/path_storage_viewer.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/deduplicate_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/export_video_result.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/export_video_service.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/filtered_image.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/image_editor_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_crop_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_editor/crop_value.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_editor/video_editor_main_actions.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_editor/video_editor_navigation_options.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_editor/video_editor_player_control.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_editor_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_rotate_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/editor/video_trim_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/free_space_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/tools/lock_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/album_selection_action_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/album_selection_overlay_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/delete_empty_albums.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/file_selection_actions_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/file_selection_overlay_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/file_viewer.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/actions/people_selection_action_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/date/date_time_picker.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/date/edit_date_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/detail_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/exif_info_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/file_app_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/file_bottom_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/file_caption_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/file_details_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/file_icons_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/file_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/native_video_player_controls/play_pause_button.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/native_video_player_controls/seek_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/no_thumbnail_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/panorama_viewer_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/thumbnail_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_control/custom_progress_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_exif_dialog.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_stream_change.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_widget_media_kit.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_widget_media_kit_common.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/video_widget_native.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/zoomable_image.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file/zoomable_live_image_new.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/added_by_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/albums_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/backed_up_time_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/creation_time_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/exif_item_widgets.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/favorite_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/file_info_face_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/file_info_faces_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/file_properties_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/location_tags_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/preview_properties_item_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/upload_icon_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/file_details/video_exif_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/archive_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/collect_photos_bottom_buttons.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/collect_photos_card_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/collection_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/gallery_file_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/grid/lazy_grid_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/grid/non_recyclable_grid_view_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/grid/recyclable_grid_view_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/group/group_gallery.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/group/group_header_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/group/lazy_group_gallery.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/group/type.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/component/multiple_groups_gallery_view.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/device_folder_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/empty_album_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/empty_hidden_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/empty_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/gallery.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/gallery_app_bar_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/hidden_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/hierarchical_search_gallery.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/hooks/add_photos_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/hooks/pick_cover_photo.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/large_files_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/shared_public_collection_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/state/gallery_context_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/state/gallery_files_inherited_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/state/search_filter_data_provider.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/state/selection_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/trash_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/gallery/uncategorized_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/hierarchicial_search/applied_filters_for_appbar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/hierarchicial_search/chip_widgets/face_filter_chip.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/hierarchicial_search/chip_widgets/generic_filter_chip.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/hierarchicial_search/chip_widgets/only_them_filter_chip.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/hierarchicial_search/filter_options_bottom_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/hierarchicial_search/recommended_filters_for_appbar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/add_location_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/dynamic_location_gallery_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/edit_center_point_tile_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/edit_location_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/location_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/pick_center_point_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/radius_picker_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/location/update_location_data_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/add_person_action_sheet.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/cluster_app_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/cluster_breakup_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/cluster_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/file_face_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/link_email_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/people_app_bar.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/people_banner.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/people_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/people_util.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/person_cluster_suggestion.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/person_clusters_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/person_face_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/person_gallery_suggestion.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/person_row_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/person_selection_action_widgets.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/people/save_or_edit_person.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/contact_result_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/go_to_map_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/magic_result_screen.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/no_result_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/people_section_all_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/search_result_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/search_result_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/search_section_all_page.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/search_thumbnail_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/result/searchable_item.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/search_section_cta.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/search_suffix_icon_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/search_suggestions.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/search_widget.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search/tab_empty_state.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/albums_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/contacts_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/file_type_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/locations_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/magic_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/moments_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/people_section.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/search_tab.dart (100%) rename mobile/{ => apps/photos}/lib/ui/viewer/search_tab/section_header.dart (100%) rename mobile/{ => apps/photos}/lib/utils/auth_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/bg_task_utils.dart (100%) rename mobile/{ => apps/photos}/lib/utils/cache_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/collection_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/debug_ml_export_data.dart (100%) rename mobile/{ => apps/photos}/lib/utils/delete_file_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/device_info.dart (100%) rename mobile/{ => apps/photos}/lib/utils/dialog_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/email_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/exif_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/face/face_thumbnail_cache.dart (100%) rename mobile/{ => apps/photos}/lib/utils/ffprobe_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/file_download_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/file_key.dart (100%) rename mobile/{ => apps/photos}/lib/utils/file_uploader.dart (100%) rename mobile/{ => apps/photos}/lib/utils/file_uploader_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/file_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/gzip.dart (100%) rename mobile/{ => apps/photos}/lib/utils/hierarchical_search_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/image_ml_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/image_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/intent_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/local_settings.dart (100%) rename mobile/{ => apps/photos}/lib/utils/lock_screen_settings.dart (100%) rename mobile/{ => apps/photos}/lib/utils/magic_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/ml_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/navigation_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/network_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/panorama_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/person_contact_linking_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/ram_check_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/separators_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/share_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/README.md (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/data.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/date_time.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/debouncer.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/directory_content.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/fake_progress.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/parse.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/simple_task_queue.dart (100%) rename mobile/{ => apps/photos}/lib/utils/standalone/task_queue.dart (100%) rename mobile/{ => apps/photos}/lib/utils/thumbnail_util.dart (100%) rename mobile/{ => apps/photos}/lib/utils/validator_util.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/.metadata (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/lib/ente_cast.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/lib/src/model.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/lib/src/service.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/pubspec.lock (100%) rename mobile/{ => apps/photos}/plugins/ente_cast/pubspec.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_none/.metadata (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_none/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_none/lib/ente_cast_none.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_none/lib/src/service.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_none/pubspec.lock (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_none/pubspec.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_normal/.metadata (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_normal/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_normal/lib/ente_cast_normal.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_normal/lib/src/service.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_normal/pubspec.lock (100%) rename mobile/{ => apps/photos}/plugins/ente_cast_normal/pubspec.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/.metadata (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/lib/ente_crypto.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/lib/src/crypto.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/lib/src/models/derived_key_result.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/lib/src/models/encryption_result.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/lib/src/models/errors.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/pubspec.lock (100%) rename mobile/{ => apps/photos}/plugins/ente_crypto/pubspec.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/.metadata (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/lib/ente_feature_flag.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/lib/src/model.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/lib/src/service.dart (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/pubspec.lock (100%) rename mobile/{ => apps/photos}/plugins/ente_feature_flag/pubspec.yaml (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/.metadata (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/analysis_options.yaml (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/checksums/checksums.lock (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/gc.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.bin (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.lock (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/fileChanges/last-build.bin (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.bin (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.lock (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/8.5/gc.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/buildOutputCleanup/cache.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/buildOutputCleanup/outputFiles.bin (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/config.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/file-system.probe (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/.gradle/vcs-1/gc.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/build.gradle (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.jar (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/gradlew (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/gradlew.bat (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/local.properties (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/settings.gradle (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/src/main/AndroidManifest.xml (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/android/src/main/kotlin/io/ente/photos/onnx_dart/OnnxDartPlugin.kt (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/lib/onnx_dart.dart (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/lib/onnx_dart_method_channel.dart (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/lib/onnx_dart_platform_interface.dart (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/pubspec.lock (100%) rename mobile/{ => apps/photos}/plugins/onnx_dart/pubspec.yaml (100%) rename mobile/{ => apps/photos}/pubspec.lock (100%) rename mobile/{ => apps/photos}/pubspec.yaml (100%) rename mobile/{ => apps/photos}/run.sh (100%) rename mobile/{ => apps/photos}/scripts/app_init_perf_test.sh (100%) rename mobile/{ => apps/photos}/scripts/bump_version.sh (100%) rename mobile/{ => apps/photos}/scripts/create_tag.sh (100%) rename mobile/{ => apps/photos}/scripts/gallery_scroll_perf_test.sh (100%) rename mobile/{ => apps/photos}/test/utils/date_time_util_test.dart (100%) rename mobile/{ => apps/photos}/test/utils/parsing_loc_from_exif_test.dart (100%) rename mobile/{ => apps/photos}/test_driver/perf_driver.dart (100%) diff --git a/mobile/.gitattributes b/mobile/apps/photos/.gitattributes similarity index 100% rename from mobile/.gitattributes rename to mobile/apps/photos/.gitattributes diff --git a/mobile/.gitignore b/mobile/apps/photos/.gitignore similarity index 100% rename from mobile/.gitignore rename to mobile/apps/photos/.gitignore diff --git a/mobile/.gradle/6.5.1/fileHashes/fileHashes.lock b/mobile/apps/photos/.gradle/6.5.1/fileHashes/fileHashes.lock similarity index 100% rename from mobile/.gradle/6.5.1/fileHashes/fileHashes.lock rename to mobile/apps/photos/.gradle/6.5.1/fileHashes/fileHashes.lock diff --git a/mobile/.gradle/6.5.1/gc.properties b/mobile/apps/photos/.gradle/6.5.1/gc.properties similarity index 100% rename from mobile/.gradle/6.5.1/gc.properties rename to mobile/apps/photos/.gradle/6.5.1/gc.properties diff --git a/mobile/.gradle/checksums/checksums.lock b/mobile/apps/photos/.gradle/checksums/checksums.lock similarity index 100% rename from mobile/.gradle/checksums/checksums.lock rename to mobile/apps/photos/.gradle/checksums/checksums.lock diff --git a/mobile/.gradle/vcs-1/gc.properties b/mobile/apps/photos/.gradle/vcs-1/gc.properties similarity index 100% rename from mobile/.gradle/vcs-1/gc.properties rename to mobile/apps/photos/.gradle/vcs-1/gc.properties diff --git a/mobile/.metadata b/mobile/apps/photos/.metadata similarity index 100% rename from mobile/.metadata rename to mobile/apps/photos/.metadata diff --git a/mobile/CHANGELOG.md b/mobile/apps/photos/CHANGELOG.md similarity index 100% rename from mobile/CHANGELOG.md rename to mobile/apps/photos/CHANGELOG.md diff --git a/mobile/Gemfile b/mobile/apps/photos/Gemfile similarity index 100% rename from mobile/Gemfile rename to mobile/apps/photos/Gemfile diff --git a/mobile/Gemfile.lock b/mobile/apps/photos/Gemfile.lock similarity index 100% rename from mobile/Gemfile.lock rename to mobile/apps/photos/Gemfile.lock diff --git a/mobile/README.md b/mobile/apps/photos/README.md similarity index 100% rename from mobile/README.md rename to mobile/apps/photos/README.md diff --git a/mobile/analysis_options.yaml b/mobile/apps/photos/analysis_options.yaml similarity index 100% rename from mobile/analysis_options.yaml rename to mobile/apps/photos/analysis_options.yaml diff --git a/mobile/android/.gitignore b/mobile/apps/photos/android/.gitignore similarity index 100% rename from mobile/android/.gitignore rename to mobile/apps/photos/android/.gitignore diff --git a/mobile/android/.project b/mobile/apps/photos/android/.project similarity index 100% rename from mobile/android/.project rename to mobile/apps/photos/android/.project diff --git a/mobile/android/app/.classpath b/mobile/apps/photos/android/app/.classpath similarity index 100% rename from mobile/android/app/.classpath rename to mobile/apps/photos/android/app/.classpath diff --git a/mobile/android/app/.project b/mobile/apps/photos/android/app/.project similarity index 100% rename from mobile/android/app/.project rename to mobile/apps/photos/android/app/.project diff --git a/mobile/android/app/build.gradle b/mobile/apps/photos/android/app/build.gradle similarity index 100% rename from mobile/android/app/build.gradle rename to mobile/apps/photos/android/app/build.gradle diff --git a/mobile/android/app/proguard-rules.pro b/mobile/apps/photos/android/app/proguard-rules.pro similarity index 100% rename from mobile/android/app/proguard-rules.pro rename to mobile/apps/photos/android/app/proguard-rules.pro diff --git a/mobile/android/app/src/debug/AndroidManifest.xml b/mobile/apps/photos/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/debug/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/debug/AndroidManifest.xml diff --git a/mobile/android/app/src/debug/res/values/strings.xml b/mobile/apps/photos/android/app/src/debug/res/values/strings.xml similarity index 100% rename from mobile/android/app/src/debug/res/values/strings.xml rename to mobile/apps/photos/android/app/src/debug/res/values/strings.xml diff --git a/mobile/android/app/src/dev/AndroidManifest.xml b/mobile/apps/photos/android/app/src/dev/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/dev/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/dev/AndroidManifest.xml diff --git a/mobile/android/app/src/dev/res/mipmap-hdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/dev/res/mipmap-hdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/dev/res/mipmap-hdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/dev/res/mipmap-hdpi/launcher_icon.png diff --git a/mobile/android/app/src/dev/res/mipmap-mdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/dev/res/mipmap-mdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/dev/res/mipmap-mdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/dev/res/mipmap-mdpi/launcher_icon.png diff --git a/mobile/android/app/src/dev/res/mipmap-xhdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/dev/res/mipmap-xhdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/dev/res/mipmap-xhdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/dev/res/mipmap-xhdpi/launcher_icon.png diff --git a/mobile/android/app/src/dev/res/mipmap-xxhdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/dev/res/mipmap-xxhdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/dev/res/mipmap-xxhdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/dev/res/mipmap-xxhdpi/launcher_icon.png diff --git a/mobile/android/app/src/dev/res/mipmap-xxxhdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/dev/res/mipmap-xxxhdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/dev/res/mipmap-xxxhdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/dev/res/mipmap-xxxhdpi/launcher_icon.png diff --git a/mobile/android/app/src/dev/res/values/strings.xml b/mobile/apps/photos/android/app/src/dev/res/values/strings.xml similarity index 100% rename from mobile/android/app/src/dev/res/values/strings.xml rename to mobile/apps/photos/android/app/src/dev/res/values/strings.xml diff --git a/mobile/android/app/src/fdroid/AndroidManifest.xml b/mobile/apps/photos/android/app/src/fdroid/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/fdroid/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/fdroid/AndroidManifest.xml diff --git a/mobile/android/app/src/independent/AndroidManifest.xml b/mobile/apps/photos/android/app/src/independent/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/independent/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/independent/AndroidManifest.xml diff --git a/mobile/android/app/src/main/AndroidManifest.xml b/mobile/apps/photos/android/app/src/main/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/main/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/main/AndroidManifest.xml diff --git a/mobile/android/app/src/main/kotlin/io/ente/photos/EnteAlbumsWidgetProvider.kt b/mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/EnteAlbumsWidgetProvider.kt similarity index 100% rename from mobile/android/app/src/main/kotlin/io/ente/photos/EnteAlbumsWidgetProvider.kt rename to mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/EnteAlbumsWidgetProvider.kt diff --git a/mobile/android/app/src/main/kotlin/io/ente/photos/EnteMemoryWidgetProvider.kt b/mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/EnteMemoryWidgetProvider.kt similarity index 100% rename from mobile/android/app/src/main/kotlin/io/ente/photos/EnteMemoryWidgetProvider.kt rename to mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/EnteMemoryWidgetProvider.kt diff --git a/mobile/android/app/src/main/kotlin/io/ente/photos/EntePeopleWidgetProvider.kt b/mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/EntePeopleWidgetProvider.kt similarity index 100% rename from mobile/android/app/src/main/kotlin/io/ente/photos/EntePeopleWidgetProvider.kt rename to mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/EntePeopleWidgetProvider.kt diff --git a/mobile/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt b/mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt similarity index 100% rename from mobile/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt rename to mobile/apps/photos/android/app/src/main/kotlin/io/ente/photos/MainActivity.kt diff --git a/mobile/android/app/src/main/play/listings/de/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/de/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/de/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/de/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/de/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/de/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/de/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/de/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/de/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/de/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/de/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/de/title.txt diff --git a/mobile/android/app/src/main/play/listings/en-US/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/en-US/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/icon/icon.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/icon/icon.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/icon/icon.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/icon/icon.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/6.png diff --git a/mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png b/mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/7.png diff --git a/mobile/android/app/src/main/play/listings/en-US/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/en-US/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/en-US/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/en-US/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/en-US/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/en-US/title.txt diff --git a/mobile/android/app/src/main/play/listings/es/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/es/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/es/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/es/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/es/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/es/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/es/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/es/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/es/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/es/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/es/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/es/title.txt diff --git a/mobile/android/app/src/main/play/listings/fr/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/fr/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/fr/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/fr/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/fr/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/fr/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/fr/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/fr/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/fr/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/fr/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/fr/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/fr/title.txt diff --git a/mobile/android/app/src/main/play/listings/he/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/he/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/he/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/he/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/he/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/he/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/he/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/he/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/he/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/he/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/he/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/he/title.txt diff --git a/mobile/android/app/src/main/play/listings/it/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/it/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/it/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/it/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/it/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/it/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/it/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/it/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/it/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/it/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/it/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/it/title.txt diff --git a/mobile/android/app/src/main/play/listings/nl/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/nl/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/nl/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/nl/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/nl/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/nl/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/nl/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/nl/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/nl/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/nl/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/nl/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/nl/title.txt diff --git a/mobile/android/app/src/main/play/listings/pl/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/pl/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/pl/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/pl/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/pl/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/pl/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/pl/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/pl/title.txt diff --git a/mobile/android/app/src/main/play/listings/pt/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/pt/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/pt/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/pt/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/pt/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/pt/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/pt/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/pt/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/pt/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/pt/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/pt/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/pt/title.txt diff --git a/mobile/android/app/src/main/play/listings/ru/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/ru/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/ru/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/ru/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/ru/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/ru/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/ru/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/ru/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/ru/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/ru/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/ru/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/ru/title.txt diff --git a/mobile/android/app/src/main/play/listings/zh/full-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/zh/full-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/zh/full-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/zh/full-description.txt diff --git a/mobile/android/app/src/main/play/listings/zh/short-description.txt b/mobile/apps/photos/android/app/src/main/play/listings/zh/short-description.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/zh/short-description.txt rename to mobile/apps/photos/android/app/src/main/play/listings/zh/short-description.txt diff --git a/mobile/android/app/src/main/play/listings/zh/title.txt b/mobile/apps/photos/android/app/src/main/play/listings/zh/title.txt similarity index 100% rename from mobile/android/app/src/main/play/listings/zh/title.txt rename to mobile/apps/photos/android/app/src/main/play/listings/zh/title.txt diff --git a/mobile/android/app/src/main/res/drawable-hdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_albums_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_albums_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/ic_albums_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_albums_widget.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_memories_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_memories_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/ic_memories_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_memories_widget.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_monochrome_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_monochrome_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/ic_monochrome_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_monochrome_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_og_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_og_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/ic_og_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_og_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/ic_people_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_people_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/ic_people_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/ic_people_widget.png diff --git a/mobile/android/app/src/main/res/drawable-hdpi/splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-hdpi/splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-hdpi/splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-hdpi/splash.png diff --git a/mobile/android/app/src/main/res/drawable-ldpi/ic_home_widget_default.png b/mobile/apps/photos/android/app/src/main/res/drawable-ldpi/ic_home_widget_default.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-ldpi/ic_home_widget_default.png rename to mobile/apps/photos/android/app/src/main/res/drawable-ldpi/ic_home_widget_default.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_albums_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_albums_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/ic_albums_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_albums_widget.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_memories_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_memories_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/ic_memories_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_memories_widget.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_monochrome_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_monochrome_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/ic_monochrome_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_monochrome_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_og_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_og_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/ic_og_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_og_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/ic_people_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_people_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/ic_people_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/ic_people_widget.png diff --git a/mobile/android/app/src/main/res/drawable-mdpi/splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-mdpi/splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-mdpi/splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-mdpi/splash.png diff --git a/mobile/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-night-hdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-hdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night-hdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-night-mdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-mdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night-mdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-night-v21/background.png b/mobile/apps/photos/android/app/src/main/res/drawable-night-v21/background.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-v21/background.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night-v21/background.png diff --git a/mobile/android/app/src/main/res/drawable-night-v21/launch_background.xml b/mobile/apps/photos/android/app/src/main/res/drawable-night-v21/launch_background.xml similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-v21/launch_background.xml rename to mobile/apps/photos/android/app/src/main/res/drawable-night-v21/launch_background.xml diff --git a/mobile/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-night-xhdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-xhdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night-xhdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-night/background.png b/mobile/apps/photos/android/app/src/main/res/drawable-night/background.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-night/background.png rename to mobile/apps/photos/android/app/src/main/res/drawable-night/background.png diff --git a/mobile/android/app/src/main/res/drawable-night/launch_background.xml b/mobile/apps/photos/android/app/src/main/res/drawable-night/launch_background.xml similarity index 100% rename from mobile/android/app/src/main/res/drawable-night/launch_background.xml rename to mobile/apps/photos/android/app/src/main/res/drawable-night/launch_background.xml diff --git a/mobile/android/app/src/main/res/drawable-v21/background.png b/mobile/apps/photos/android/app/src/main/res/drawable-v21/background.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-v21/background.png rename to mobile/apps/photos/android/app/src/main/res/drawable-v21/background.png diff --git a/mobile/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/apps/photos/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from mobile/android/app/src/main/res/drawable-v21/launch_background.xml rename to mobile/apps/photos/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_albums_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_albums_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/ic_albums_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_albums_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_memories_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_memories_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/ic_memories_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_memories_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_monochrome_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_monochrome_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/ic_monochrome_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_monochrome_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_og_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_og_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/ic_og_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_og_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/ic_people_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_people_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/ic_people_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/ic_people_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xhdpi/splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xhdpi/splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xhdpi/splash.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_albums_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_albums_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/ic_albums_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_albums_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_memories_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_memories_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/ic_memories_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_memories_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_monochrome_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_monochrome_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/ic_monochrome_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_monochrome_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_og_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_og_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/ic_og_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_og_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/ic_people_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_people_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/ic_people_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/ic_people_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xxhdpi/splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxhdpi/splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxhdpi/splash.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/android12splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/android12splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/android12splash.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_albums_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_albums_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/ic_albums_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_albums_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_memories_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_memories_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/ic_memories_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_memories_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_monochrome_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_monochrome_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/ic_monochrome_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_monochrome_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_og_launcher_foreground.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_og_launcher_foreground.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/ic_og_launcher_foreground.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_og_launcher_foreground.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/ic_people_widget.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_people_widget.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/ic_people_widget.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/ic_people_widget.png diff --git a/mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png b/mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/splash.png similarity index 100% rename from mobile/android/app/src/main/res/drawable-xxxhdpi/splash.png rename to mobile/apps/photos/android/app/src/main/res/drawable-xxxhdpi/splash.png diff --git a/mobile/android/app/src/main/res/drawable/albums_widget_preview.png b/mobile/apps/photos/android/app/src/main/res/drawable/albums_widget_preview.png similarity index 100% rename from mobile/android/app/src/main/res/drawable/albums_widget_preview.png rename to mobile/apps/photos/android/app/src/main/res/drawable/albums_widget_preview.png diff --git a/mobile/android/app/src/main/res/drawable/background.png b/mobile/apps/photos/android/app/src/main/res/drawable/background.png similarity index 100% rename from mobile/android/app/src/main/res/drawable/background.png rename to mobile/apps/photos/android/app/src/main/res/drawable/background.png diff --git a/mobile/android/app/src/main/res/drawable/launch_background.xml b/mobile/apps/photos/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from mobile/android/app/src/main/res/drawable/launch_background.xml rename to mobile/apps/photos/android/app/src/main/res/drawable/launch_background.xml diff --git a/mobile/android/app/src/main/res/drawable/memory_widget_preview.png b/mobile/apps/photos/android/app/src/main/res/drawable/memory_widget_preview.png similarity index 100% rename from mobile/android/app/src/main/res/drawable/memory_widget_preview.png rename to mobile/apps/photos/android/app/src/main/res/drawable/memory_widget_preview.png diff --git a/mobile/android/app/src/main/res/drawable/notification_icon.png b/mobile/apps/photos/android/app/src/main/res/drawable/notification_icon.png similarity index 100% rename from mobile/android/app/src/main/res/drawable/notification_icon.png rename to mobile/apps/photos/android/app/src/main/res/drawable/notification_icon.png diff --git a/mobile/android/app/src/main/res/drawable/people_widget_preview.png b/mobile/apps/photos/android/app/src/main/res/drawable/people_widget_preview.png similarity index 100% rename from mobile/android/app/src/main/res/drawable/people_widget_preview.png rename to mobile/apps/photos/android/app/src/main/res/drawable/people_widget_preview.png diff --git a/mobile/android/app/src/main/res/drawable/widget_background.xml b/mobile/apps/photos/android/app/src/main/res/drawable/widget_background.xml similarity index 100% rename from mobile/android/app/src/main/res/drawable/widget_background.xml rename to mobile/apps/photos/android/app/src/main/res/drawable/widget_background.xml diff --git a/mobile/android/app/src/main/res/layout/albums_widget_layout.xml b/mobile/apps/photos/android/app/src/main/res/layout/albums_widget_layout.xml similarity index 100% rename from mobile/android/app/src/main/res/layout/albums_widget_layout.xml rename to mobile/apps/photos/android/app/src/main/res/layout/albums_widget_layout.xml diff --git a/mobile/android/app/src/main/res/layout/gradient_overlay.xml b/mobile/apps/photos/android/app/src/main/res/layout/gradient_overlay.xml similarity index 100% rename from mobile/android/app/src/main/res/layout/gradient_overlay.xml rename to mobile/apps/photos/android/app/src/main/res/layout/gradient_overlay.xml diff --git a/mobile/android/app/src/main/res/layout/memory_widget_layout.xml b/mobile/apps/photos/android/app/src/main/res/layout/memory_widget_layout.xml similarity index 100% rename from mobile/android/app/src/main/res/layout/memory_widget_layout.xml rename to mobile/apps/photos/android/app/src/main/res/layout/memory_widget_layout.xml diff --git a/mobile/android/app/src/main/res/layout/people_widget_layout.xml b/mobile/apps/photos/android/app/src/main/res/layout/people_widget_layout.xml similarity index 100% rename from mobile/android/app/src/main/res/layout/people_widget_layout.xml rename to mobile/apps/photos/android/app/src/main/res/layout/people_widget_layout.xml diff --git a/mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_dark.xml b/mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_dark.xml similarity index 100% rename from mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_dark.xml rename to mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_dark.xml diff --git a/mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_green.xml b/mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_green.xml similarity index 100% rename from mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_green.xml rename to mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_green.xml diff --git a/mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_light.xml b/mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_light.xml similarity index 100% rename from mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_light.xml rename to mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_light.xml diff --git a/mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_og.xml b/mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_og.xml similarity index 100% rename from mobile/android/app/src/main/res/mipmap-anydpi-v26/icon_og.xml rename to mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/icon_og.xml diff --git a/mobile/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml similarity index 100% rename from mobile/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml rename to mobile/apps/photos/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/icon_dark.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_dark.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/icon_dark.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_dark.png diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/icon_green.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_green.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/icon_green.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_green.png diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/icon_light.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_light.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/icon_light.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_light.png diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/icon_monochrome.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_monochrome.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/icon_monochrome.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_monochrome.png diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/icon_og.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_og.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/icon_og.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/icon_og.png diff --git a/mobile/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-hdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-hdpi/launcher_icon.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/icon_dark.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_dark.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/icon_dark.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_dark.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/icon_green.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_green.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/icon_green.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_green.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/icon_light.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_light.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/icon_light.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_light.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/icon_monochrome.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_monochrome.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/icon_monochrome.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_monochrome.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/icon_og.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_og.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/icon_og.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/icon_og.png diff --git a/mobile/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-mdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-mdpi/launcher_icon.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/icon_dark.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_dark.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/icon_dark.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_dark.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/icon_green.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_green.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/icon_green.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_green.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/icon_light.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_light.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/icon_light.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_light.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/icon_monochrome.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_monochrome.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/icon_monochrome.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_monochrome.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/icon_og.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_og.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/icon_og.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/icon_og.png diff --git a/mobile/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/icon_dark.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_dark.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/icon_dark.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_dark.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/icon_green.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_green.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/icon_green.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_green.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/icon_light.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_light.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/icon_light.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_light.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/icon_monochrome.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_monochrome.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/icon_monochrome.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_monochrome.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/icon_og.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_og.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/icon_og.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/icon_og.png diff --git a/mobile/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_dark.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_dark.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_dark.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_dark.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_green.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_green.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_green.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_green.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_light.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_light.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_light.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_light.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_monochrome.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_monochrome.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_monochrome.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_monochrome.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_og.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_og.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/icon_og.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/icon_og.png diff --git a/mobile/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png similarity index 100% rename from mobile/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png rename to mobile/apps/photos/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png diff --git a/mobile/android/app/src/main/res/values-night-v31/styles.xml b/mobile/apps/photos/android/app/src/main/res/values-night-v31/styles.xml similarity index 100% rename from mobile/android/app/src/main/res/values-night-v31/styles.xml rename to mobile/apps/photos/android/app/src/main/res/values-night-v31/styles.xml diff --git a/mobile/android/app/src/main/res/values-night/colors.xml b/mobile/apps/photos/android/app/src/main/res/values-night/colors.xml similarity index 100% rename from mobile/android/app/src/main/res/values-night/colors.xml rename to mobile/apps/photos/android/app/src/main/res/values-night/colors.xml diff --git a/mobile/android/app/src/main/res/values-night/styles.xml b/mobile/apps/photos/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from mobile/android/app/src/main/res/values-night/styles.xml rename to mobile/apps/photos/android/app/src/main/res/values-night/styles.xml diff --git a/mobile/android/app/src/main/res/values-v31/styles.xml b/mobile/apps/photos/android/app/src/main/res/values-v31/styles.xml similarity index 100% rename from mobile/android/app/src/main/res/values-v31/styles.xml rename to mobile/apps/photos/android/app/src/main/res/values-v31/styles.xml diff --git a/mobile/android/app/src/main/res/values/colors.xml b/mobile/apps/photos/android/app/src/main/res/values/colors.xml similarity index 100% rename from mobile/android/app/src/main/res/values/colors.xml rename to mobile/apps/photos/android/app/src/main/res/values/colors.xml diff --git a/mobile/android/app/src/main/res/values/strings.xml b/mobile/apps/photos/android/app/src/main/res/values/strings.xml similarity index 100% rename from mobile/android/app/src/main/res/values/strings.xml rename to mobile/apps/photos/android/app/src/main/res/values/strings.xml diff --git a/mobile/android/app/src/main/res/values/styles.xml b/mobile/apps/photos/android/app/src/main/res/values/styles.xml similarity index 100% rename from mobile/android/app/src/main/res/values/styles.xml rename to mobile/apps/photos/android/app/src/main/res/values/styles.xml diff --git a/mobile/android/app/src/main/res/xml/albums_widget.xml b/mobile/apps/photos/android/app/src/main/res/xml/albums_widget.xml similarity index 100% rename from mobile/android/app/src/main/res/xml/albums_widget.xml rename to mobile/apps/photos/android/app/src/main/res/xml/albums_widget.xml diff --git a/mobile/android/app/src/main/res/xml/data_extraction_rules.xml b/mobile/apps/photos/android/app/src/main/res/xml/data_extraction_rules.xml similarity index 100% rename from mobile/android/app/src/main/res/xml/data_extraction_rules.xml rename to mobile/apps/photos/android/app/src/main/res/xml/data_extraction_rules.xml diff --git a/mobile/android/app/src/main/res/xml/memory_widget.xml b/mobile/apps/photos/android/app/src/main/res/xml/memory_widget.xml similarity index 100% rename from mobile/android/app/src/main/res/xml/memory_widget.xml rename to mobile/apps/photos/android/app/src/main/res/xml/memory_widget.xml diff --git a/mobile/android/app/src/main/res/xml/network_security_config.xml b/mobile/apps/photos/android/app/src/main/res/xml/network_security_config.xml similarity index 100% rename from mobile/android/app/src/main/res/xml/network_security_config.xml rename to mobile/apps/photos/android/app/src/main/res/xml/network_security_config.xml diff --git a/mobile/android/app/src/main/res/xml/people_widget.xml b/mobile/apps/photos/android/app/src/main/res/xml/people_widget.xml similarity index 100% rename from mobile/android/app/src/main/res/xml/people_widget.xml rename to mobile/apps/photos/android/app/src/main/res/xml/people_widget.xml diff --git a/mobile/android/app/src/playstore/AndroidManifest.xml b/mobile/apps/photos/android/app/src/playstore/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/playstore/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/playstore/AndroidManifest.xml diff --git a/mobile/android/app/src/profile/AndroidManifest.xml b/mobile/apps/photos/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from mobile/android/app/src/profile/AndroidManifest.xml rename to mobile/apps/photos/android/app/src/profile/AndroidManifest.xml diff --git a/mobile/android/build.gradle b/mobile/apps/photos/android/build.gradle similarity index 100% rename from mobile/android/build.gradle rename to mobile/apps/photos/android/build.gradle diff --git a/mobile/android/gradle.properties b/mobile/apps/photos/android/gradle.properties similarity index 100% rename from mobile/android/gradle.properties rename to mobile/apps/photos/android/gradle.properties diff --git a/mobile/android/gradle/wrapper/gradle-wrapper.properties b/mobile/apps/photos/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from mobile/android/gradle/wrapper/gradle-wrapper.properties rename to mobile/apps/photos/android/gradle/wrapper/gradle-wrapper.properties diff --git a/mobile/android/permissions.md b/mobile/apps/photos/android/permissions.md similarity index 100% rename from mobile/android/permissions.md rename to mobile/apps/photos/android/permissions.md diff --git a/mobile/android/settings.gradle b/mobile/apps/photos/android/settings.gradle similarity index 100% rename from mobile/android/settings.gradle rename to mobile/apps/photos/android/settings.gradle diff --git a/mobile/android/settings_aar.gradle b/mobile/apps/photos/android/settings_aar.gradle similarity index 100% rename from mobile/android/settings_aar.gradle rename to mobile/apps/photos/android/settings_aar.gradle diff --git a/mobile/assets/2.0x/active_subscription.png b/mobile/apps/photos/assets/2.0x/active_subscription.png similarity index 100% rename from mobile/assets/2.0x/active_subscription.png rename to mobile/apps/photos/assets/2.0x/active_subscription.png diff --git a/mobile/assets/2.0x/albums-widget-static.png b/mobile/apps/photos/assets/2.0x/albums-widget-static.png similarity index 100% rename from mobile/assets/2.0x/albums-widget-static.png rename to mobile/apps/photos/assets/2.0x/albums-widget-static.png diff --git a/mobile/assets/2.0x/create_new_album.png b/mobile/apps/photos/assets/2.0x/create_new_album.png similarity index 100% rename from mobile/assets/2.0x/create_new_album.png rename to mobile/apps/photos/assets/2.0x/create_new_album.png diff --git a/mobile/assets/2.0x/family_plan_leave.png b/mobile/apps/photos/assets/2.0x/family_plan_leave.png similarity index 100% rename from mobile/assets/2.0x/family_plan_leave.png rename to mobile/apps/photos/assets/2.0x/family_plan_leave.png diff --git a/mobile/assets/2.0x/gallery_locked.png b/mobile/apps/photos/assets/2.0x/gallery_locked.png similarity index 100% rename from mobile/assets/2.0x/gallery_locked.png rename to mobile/apps/photos/assets/2.0x/gallery_locked.png diff --git a/mobile/assets/2.0x/loading_photos_background.png b/mobile/apps/photos/assets/2.0x/loading_photos_background.png similarity index 100% rename from mobile/assets/2.0x/loading_photos_background.png rename to mobile/apps/photos/assets/2.0x/loading_photos_background.png diff --git a/mobile/assets/2.0x/loading_photos_background_dark.png b/mobile/apps/photos/assets/2.0x/loading_photos_background_dark.png similarity index 100% rename from mobile/assets/2.0x/loading_photos_background_dark.png rename to mobile/apps/photos/assets/2.0x/loading_photos_background_dark.png diff --git a/mobile/assets/2.0x/lock_screen_background.png b/mobile/apps/photos/assets/2.0x/lock_screen_background.png similarity index 100% rename from mobile/assets/2.0x/lock_screen_background.png rename to mobile/apps/photos/assets/2.0x/lock_screen_background.png diff --git a/mobile/assets/2.0x/map_world.png b/mobile/apps/photos/assets/2.0x/map_world.png similarity index 100% rename from mobile/assets/2.0x/map_world.png rename to mobile/apps/photos/assets/2.0x/map_world.png diff --git a/mobile/assets/2.0x/memories-widget-static.png b/mobile/apps/photos/assets/2.0x/memories-widget-static.png similarity index 100% rename from mobile/assets/2.0x/memories-widget-static.png rename to mobile/apps/photos/assets/2.0x/memories-widget-static.png diff --git a/mobile/assets/2.0x/new_empty_album.png b/mobile/apps/photos/assets/2.0x/new_empty_album.png similarity index 100% rename from mobile/assets/2.0x/new_empty_album.png rename to mobile/apps/photos/assets/2.0x/new_empty_album.png diff --git a/mobile/assets/2.0x/new_empty_album_dark.png b/mobile/apps/photos/assets/2.0x/new_empty_album_dark.png similarity index 100% rename from mobile/assets/2.0x/new_empty_album_dark.png rename to mobile/apps/photos/assets/2.0x/new_empty_album_dark.png diff --git a/mobile/assets/2.0x/onboarding_lock.png b/mobile/apps/photos/assets/2.0x/onboarding_lock.png similarity index 100% rename from mobile/assets/2.0x/onboarding_lock.png rename to mobile/apps/photos/assets/2.0x/onboarding_lock.png diff --git a/mobile/assets/2.0x/onboarding_safe.png b/mobile/apps/photos/assets/2.0x/onboarding_safe.png similarity index 100% rename from mobile/assets/2.0x/onboarding_safe.png rename to mobile/apps/photos/assets/2.0x/onboarding_safe.png diff --git a/mobile/assets/2.0x/onboarding_sync.png b/mobile/apps/photos/assets/2.0x/onboarding_sync.png similarity index 100% rename from mobile/assets/2.0x/onboarding_sync.png rename to mobile/apps/photos/assets/2.0x/onboarding_sync.png diff --git a/mobile/assets/2.0x/people-widget-static.png b/mobile/apps/photos/assets/2.0x/people-widget-static.png similarity index 100% rename from mobile/assets/2.0x/people-widget-static.png rename to mobile/apps/photos/assets/2.0x/people-widget-static.png diff --git a/mobile/assets/2.0x/popular_subscription.png b/mobile/apps/photos/assets/2.0x/popular_subscription.png similarity index 100% rename from mobile/assets/2.0x/popular_subscription.png rename to mobile/apps/photos/assets/2.0x/popular_subscription.png diff --git a/mobile/assets/2.0x/processing-video-failed.png b/mobile/apps/photos/assets/2.0x/processing-video-failed.png similarity index 100% rename from mobile/assets/2.0x/processing-video-failed.png rename to mobile/apps/photos/assets/2.0x/processing-video-failed.png diff --git a/mobile/assets/2.0x/processing-video-success.png b/mobile/apps/photos/assets/2.0x/processing-video-success.png similarity index 100% rename from mobile/assets/2.0x/processing-video-success.png rename to mobile/apps/photos/assets/2.0x/processing-video-success.png diff --git a/mobile/assets/2.0x/processing-video.png b/mobile/apps/photos/assets/2.0x/processing-video.png similarity index 100% rename from mobile/assets/2.0x/processing-video.png rename to mobile/apps/photos/assets/2.0x/processing-video.png diff --git a/mobile/assets/2.0x/storage_card_background.png b/mobile/apps/photos/assets/2.0x/storage_card_background.png similarity index 100% rename from mobile/assets/2.0x/storage_card_background.png rename to mobile/apps/photos/assets/2.0x/storage_card_background.png diff --git a/mobile/assets/2.0x/type_AVI.png b/mobile/apps/photos/assets/2.0x/type_AVI.png similarity index 100% rename from mobile/assets/2.0x/type_AVI.png rename to mobile/apps/photos/assets/2.0x/type_AVI.png diff --git a/mobile/assets/2.0x/type_GIF.png b/mobile/apps/photos/assets/2.0x/type_GIF.png similarity index 100% rename from mobile/assets/2.0x/type_GIF.png rename to mobile/apps/photos/assets/2.0x/type_GIF.png diff --git a/mobile/assets/2.0x/type_HEIC.png b/mobile/apps/photos/assets/2.0x/type_HEIC.png similarity index 100% rename from mobile/assets/2.0x/type_HEIC.png rename to mobile/apps/photos/assets/2.0x/type_HEIC.png diff --git a/mobile/assets/2.0x/type_JPEG.png b/mobile/apps/photos/assets/2.0x/type_JPEG.png similarity index 100% rename from mobile/assets/2.0x/type_JPEG.png rename to mobile/apps/photos/assets/2.0x/type_JPEG.png diff --git a/mobile/assets/2.0x/type_JPG.png b/mobile/apps/photos/assets/2.0x/type_JPG.png similarity index 100% rename from mobile/assets/2.0x/type_JPG.png rename to mobile/apps/photos/assets/2.0x/type_JPG.png diff --git a/mobile/assets/2.0x/type_MKV.png b/mobile/apps/photos/assets/2.0x/type_MKV.png similarity index 100% rename from mobile/assets/2.0x/type_MKV.png rename to mobile/apps/photos/assets/2.0x/type_MKV.png diff --git a/mobile/assets/2.0x/type_MP4.png b/mobile/apps/photos/assets/2.0x/type_MP4.png similarity index 100% rename from mobile/assets/2.0x/type_MP4.png rename to mobile/apps/photos/assets/2.0x/type_MP4.png diff --git a/mobile/assets/2.0x/type_PNG.png b/mobile/apps/photos/assets/2.0x/type_PNG.png similarity index 100% rename from mobile/assets/2.0x/type_PNG.png rename to mobile/apps/photos/assets/2.0x/type_PNG.png diff --git a/mobile/assets/2.0x/type_WEBP.png b/mobile/apps/photos/assets/2.0x/type_WEBP.png similarity index 100% rename from mobile/assets/2.0x/type_WEBP.png rename to mobile/apps/photos/assets/2.0x/type_WEBP.png diff --git a/mobile/assets/2.0x/type_live.png b/mobile/apps/photos/assets/2.0x/type_live.png similarity index 100% rename from mobile/assets/2.0x/type_live.png rename to mobile/apps/photos/assets/2.0x/type_live.png diff --git a/mobile/assets/2.0x/type_photos.png b/mobile/apps/photos/assets/2.0x/type_photos.png similarity index 100% rename from mobile/assets/2.0x/type_photos.png rename to mobile/apps/photos/assets/2.0x/type_photos.png diff --git a/mobile/assets/2.0x/type_unknown.png b/mobile/apps/photos/assets/2.0x/type_unknown.png similarity index 100% rename from mobile/assets/2.0x/type_unknown.png rename to mobile/apps/photos/assets/2.0x/type_unknown.png diff --git a/mobile/assets/2.0x/type_videos.png b/mobile/apps/photos/assets/2.0x/type_videos.png similarity index 100% rename from mobile/assets/2.0x/type_videos.png rename to mobile/apps/photos/assets/2.0x/type_videos.png diff --git a/mobile/assets/2.0x/video-processing-queued.png b/mobile/apps/photos/assets/2.0x/video-processing-queued.png similarity index 100% rename from mobile/assets/2.0x/video-processing-queued.png rename to mobile/apps/photos/assets/2.0x/video-processing-queued.png diff --git a/mobile/assets/3.0x/active_subscription.png b/mobile/apps/photos/assets/3.0x/active_subscription.png similarity index 100% rename from mobile/assets/3.0x/active_subscription.png rename to mobile/apps/photos/assets/3.0x/active_subscription.png diff --git a/mobile/assets/3.0x/albums-widget-static.png b/mobile/apps/photos/assets/3.0x/albums-widget-static.png similarity index 100% rename from mobile/assets/3.0x/albums-widget-static.png rename to mobile/apps/photos/assets/3.0x/albums-widget-static.png diff --git a/mobile/assets/3.0x/create_new_album.png b/mobile/apps/photos/assets/3.0x/create_new_album.png similarity index 100% rename from mobile/assets/3.0x/create_new_album.png rename to mobile/apps/photos/assets/3.0x/create_new_album.png diff --git a/mobile/assets/3.0x/family_plan_leave.png b/mobile/apps/photos/assets/3.0x/family_plan_leave.png similarity index 100% rename from mobile/assets/3.0x/family_plan_leave.png rename to mobile/apps/photos/assets/3.0x/family_plan_leave.png diff --git a/mobile/assets/3.0x/gallery_locked.png b/mobile/apps/photos/assets/3.0x/gallery_locked.png similarity index 100% rename from mobile/assets/3.0x/gallery_locked.png rename to mobile/apps/photos/assets/3.0x/gallery_locked.png diff --git a/mobile/assets/3.0x/loading_photos_background.png b/mobile/apps/photos/assets/3.0x/loading_photos_background.png similarity index 100% rename from mobile/assets/3.0x/loading_photos_background.png rename to mobile/apps/photos/assets/3.0x/loading_photos_background.png diff --git a/mobile/assets/3.0x/loading_photos_background_dark.png b/mobile/apps/photos/assets/3.0x/loading_photos_background_dark.png similarity index 100% rename from mobile/assets/3.0x/loading_photos_background_dark.png rename to mobile/apps/photos/assets/3.0x/loading_photos_background_dark.png diff --git a/mobile/assets/3.0x/lock_screen_background.png b/mobile/apps/photos/assets/3.0x/lock_screen_background.png similarity index 100% rename from mobile/assets/3.0x/lock_screen_background.png rename to mobile/apps/photos/assets/3.0x/lock_screen_background.png diff --git a/mobile/assets/3.0x/map_world.png b/mobile/apps/photos/assets/3.0x/map_world.png similarity index 100% rename from mobile/assets/3.0x/map_world.png rename to mobile/apps/photos/assets/3.0x/map_world.png diff --git a/mobile/assets/3.0x/memories-widget-static.png b/mobile/apps/photos/assets/3.0x/memories-widget-static.png similarity index 100% rename from mobile/assets/3.0x/memories-widget-static.png rename to mobile/apps/photos/assets/3.0x/memories-widget-static.png diff --git a/mobile/assets/3.0x/new_empty_album.png b/mobile/apps/photos/assets/3.0x/new_empty_album.png similarity index 100% rename from mobile/assets/3.0x/new_empty_album.png rename to mobile/apps/photos/assets/3.0x/new_empty_album.png diff --git a/mobile/assets/3.0x/new_empty_album_dark.png b/mobile/apps/photos/assets/3.0x/new_empty_album_dark.png similarity index 100% rename from mobile/assets/3.0x/new_empty_album_dark.png rename to mobile/apps/photos/assets/3.0x/new_empty_album_dark.png diff --git a/mobile/assets/3.0x/onboarding_lock.png b/mobile/apps/photos/assets/3.0x/onboarding_lock.png similarity index 100% rename from mobile/assets/3.0x/onboarding_lock.png rename to mobile/apps/photos/assets/3.0x/onboarding_lock.png diff --git a/mobile/assets/3.0x/onboarding_safe.png b/mobile/apps/photos/assets/3.0x/onboarding_safe.png similarity index 100% rename from mobile/assets/3.0x/onboarding_safe.png rename to mobile/apps/photos/assets/3.0x/onboarding_safe.png diff --git a/mobile/assets/3.0x/onboarding_sync.png b/mobile/apps/photos/assets/3.0x/onboarding_sync.png similarity index 100% rename from mobile/assets/3.0x/onboarding_sync.png rename to mobile/apps/photos/assets/3.0x/onboarding_sync.png diff --git a/mobile/assets/3.0x/people-widget-static.png b/mobile/apps/photos/assets/3.0x/people-widget-static.png similarity index 100% rename from mobile/assets/3.0x/people-widget-static.png rename to mobile/apps/photos/assets/3.0x/people-widget-static.png diff --git a/mobile/assets/3.0x/popular_subscription.png b/mobile/apps/photos/assets/3.0x/popular_subscription.png similarity index 100% rename from mobile/assets/3.0x/popular_subscription.png rename to mobile/apps/photos/assets/3.0x/popular_subscription.png diff --git a/mobile/assets/3.0x/processing-video-failed.png b/mobile/apps/photos/assets/3.0x/processing-video-failed.png similarity index 100% rename from mobile/assets/3.0x/processing-video-failed.png rename to mobile/apps/photos/assets/3.0x/processing-video-failed.png diff --git a/mobile/assets/3.0x/processing-video-success.png b/mobile/apps/photos/assets/3.0x/processing-video-success.png similarity index 100% rename from mobile/assets/3.0x/processing-video-success.png rename to mobile/apps/photos/assets/3.0x/processing-video-success.png diff --git a/mobile/assets/3.0x/processing-video.png b/mobile/apps/photos/assets/3.0x/processing-video.png similarity index 100% rename from mobile/assets/3.0x/processing-video.png rename to mobile/apps/photos/assets/3.0x/processing-video.png diff --git a/mobile/assets/3.0x/storage_card_background.png b/mobile/apps/photos/assets/3.0x/storage_card_background.png similarity index 100% rename from mobile/assets/3.0x/storage_card_background.png rename to mobile/apps/photos/assets/3.0x/storage_card_background.png diff --git a/mobile/assets/3.0x/type_AVI.png b/mobile/apps/photos/assets/3.0x/type_AVI.png similarity index 100% rename from mobile/assets/3.0x/type_AVI.png rename to mobile/apps/photos/assets/3.0x/type_AVI.png diff --git a/mobile/assets/3.0x/type_GIF.png b/mobile/apps/photos/assets/3.0x/type_GIF.png similarity index 100% rename from mobile/assets/3.0x/type_GIF.png rename to mobile/apps/photos/assets/3.0x/type_GIF.png diff --git a/mobile/assets/3.0x/type_HEIC.png b/mobile/apps/photos/assets/3.0x/type_HEIC.png similarity index 100% rename from mobile/assets/3.0x/type_HEIC.png rename to mobile/apps/photos/assets/3.0x/type_HEIC.png diff --git a/mobile/assets/3.0x/type_JPEG.png b/mobile/apps/photos/assets/3.0x/type_JPEG.png similarity index 100% rename from mobile/assets/3.0x/type_JPEG.png rename to mobile/apps/photos/assets/3.0x/type_JPEG.png diff --git a/mobile/assets/3.0x/type_JPG.png b/mobile/apps/photos/assets/3.0x/type_JPG.png similarity index 100% rename from mobile/assets/3.0x/type_JPG.png rename to mobile/apps/photos/assets/3.0x/type_JPG.png diff --git a/mobile/assets/3.0x/type_MKV.png b/mobile/apps/photos/assets/3.0x/type_MKV.png similarity index 100% rename from mobile/assets/3.0x/type_MKV.png rename to mobile/apps/photos/assets/3.0x/type_MKV.png diff --git a/mobile/assets/3.0x/type_MP4.png b/mobile/apps/photos/assets/3.0x/type_MP4.png similarity index 100% rename from mobile/assets/3.0x/type_MP4.png rename to mobile/apps/photos/assets/3.0x/type_MP4.png diff --git a/mobile/assets/3.0x/type_PNG.png b/mobile/apps/photos/assets/3.0x/type_PNG.png similarity index 100% rename from mobile/assets/3.0x/type_PNG.png rename to mobile/apps/photos/assets/3.0x/type_PNG.png diff --git a/mobile/assets/3.0x/type_WEBP.png b/mobile/apps/photos/assets/3.0x/type_WEBP.png similarity index 100% rename from mobile/assets/3.0x/type_WEBP.png rename to mobile/apps/photos/assets/3.0x/type_WEBP.png diff --git a/mobile/assets/3.0x/type_live.png b/mobile/apps/photos/assets/3.0x/type_live.png similarity index 100% rename from mobile/assets/3.0x/type_live.png rename to mobile/apps/photos/assets/3.0x/type_live.png diff --git a/mobile/assets/3.0x/type_photos.png b/mobile/apps/photos/assets/3.0x/type_photos.png similarity index 100% rename from mobile/assets/3.0x/type_photos.png rename to mobile/apps/photos/assets/3.0x/type_photos.png diff --git a/mobile/assets/3.0x/type_unknown.png b/mobile/apps/photos/assets/3.0x/type_unknown.png similarity index 100% rename from mobile/assets/3.0x/type_unknown.png rename to mobile/apps/photos/assets/3.0x/type_unknown.png diff --git a/mobile/assets/3.0x/type_videos.png b/mobile/apps/photos/assets/3.0x/type_videos.png similarity index 100% rename from mobile/assets/3.0x/type_videos.png rename to mobile/apps/photos/assets/3.0x/type_videos.png diff --git a/mobile/assets/3.0x/video-processing-queued.png b/mobile/apps/photos/assets/3.0x/video-processing-queued.png similarity index 100% rename from mobile/assets/3.0x/video-processing-queued.png rename to mobile/apps/photos/assets/3.0x/video-processing-queued.png diff --git a/mobile/assets/active_subscription.png b/mobile/apps/photos/assets/active_subscription.png similarity index 100% rename from mobile/assets/active_subscription.png rename to mobile/apps/photos/assets/active_subscription.png diff --git a/mobile/assets/albums-widget-static.png b/mobile/apps/photos/assets/albums-widget-static.png similarity index 100% rename from mobile/assets/albums-widget-static.png rename to mobile/apps/photos/assets/albums-widget-static.png diff --git a/mobile/assets/create_new_album.png b/mobile/apps/photos/assets/create_new_album.png similarity index 100% rename from mobile/assets/create_new_album.png rename to mobile/apps/photos/assets/create_new_album.png diff --git a/mobile/assets/earth_blurred.png b/mobile/apps/photos/assets/earth_blurred.png similarity index 100% rename from mobile/assets/earth_blurred.png rename to mobile/apps/photos/assets/earth_blurred.png diff --git a/mobile/assets/family_plan_leave.png b/mobile/apps/photos/assets/family_plan_leave.png similarity index 100% rename from mobile/assets/family_plan_leave.png rename to mobile/apps/photos/assets/family_plan_leave.png diff --git a/mobile/assets/gallery_locked.png b/mobile/apps/photos/assets/gallery_locked.png similarity index 100% rename from mobile/assets/gallery_locked.png rename to mobile/apps/photos/assets/gallery_locked.png diff --git a/mobile/assets/icon-light.png b/mobile/apps/photos/assets/icon-light.png similarity index 100% rename from mobile/assets/icon-light.png rename to mobile/apps/photos/assets/icon-light.png diff --git a/mobile/assets/icons/albums-widget-icon.svg b/mobile/apps/photos/assets/icons/albums-widget-icon.svg similarity index 100% rename from mobile/assets/icons/albums-widget-icon.svg rename to mobile/apps/photos/assets/icons/albums-widget-icon.svg diff --git a/mobile/assets/icons/guest_view_icon.svg b/mobile/apps/photos/assets/icons/guest_view_icon.svg similarity index 100% rename from mobile/assets/icons/guest_view_icon.svg rename to mobile/apps/photos/assets/icons/guest_view_icon.svg diff --git a/mobile/assets/icons/legacy-dark.svg b/mobile/apps/photos/assets/icons/legacy-dark.svg similarity index 100% rename from mobile/assets/icons/legacy-dark.svg rename to mobile/apps/photos/assets/icons/legacy-dark.svg diff --git a/mobile/assets/icons/legacy-light.svg b/mobile/apps/photos/assets/icons/legacy-light.svg similarity index 100% rename from mobile/assets/icons/legacy-light.svg rename to mobile/apps/photos/assets/icons/legacy-light.svg diff --git a/mobile/assets/icons/list_view_icon_dark.svg b/mobile/apps/photos/assets/icons/list_view_icon_dark.svg similarity index 100% rename from mobile/assets/icons/list_view_icon_dark.svg rename to mobile/apps/photos/assets/icons/list_view_icon_dark.svg diff --git a/mobile/assets/icons/list_view_icon_light.svg b/mobile/apps/photos/assets/icons/list_view_icon_light.svg similarity index 100% rename from mobile/assets/icons/list_view_icon_light.svg rename to mobile/apps/photos/assets/icons/list_view_icon_light.svg diff --git a/mobile/assets/icons/memories-widget-icon.svg b/mobile/apps/photos/assets/icons/memories-widget-icon.svg similarity index 100% rename from mobile/assets/icons/memories-widget-icon.svg rename to mobile/apps/photos/assets/icons/memories-widget-icon.svg diff --git a/mobile/assets/icons/past-year-memory-icon.svg b/mobile/apps/photos/assets/icons/past-year-memory-icon.svg similarity index 100% rename from mobile/assets/icons/past-year-memory-icon.svg rename to mobile/apps/photos/assets/icons/past-year-memory-icon.svg diff --git a/mobile/assets/icons/people-widget-icon.svg b/mobile/apps/photos/assets/icons/people-widget-icon.svg similarity index 100% rename from mobile/assets/icons/people-widget-icon.svg rename to mobile/apps/photos/assets/icons/people-widget-icon.svg diff --git a/mobile/assets/icons/search_icon_dark.svg b/mobile/apps/photos/assets/icons/search_icon_dark.svg similarity index 100% rename from mobile/assets/icons/search_icon_dark.svg rename to mobile/apps/photos/assets/icons/search_icon_dark.svg diff --git a/mobile/assets/icons/search_icon_light.svg b/mobile/apps/photos/assets/icons/search_icon_light.svg similarity index 100% rename from mobile/assets/icons/search_icon_light.svg rename to mobile/apps/photos/assets/icons/search_icon_light.svg diff --git a/mobile/assets/icons/smart-memory-icon.svg b/mobile/apps/photos/assets/icons/smart-memory-icon.svg similarity index 100% rename from mobile/assets/icons/smart-memory-icon.svg rename to mobile/apps/photos/assets/icons/smart-memory-icon.svg diff --git a/mobile/assets/launcher_icon/icon-dark.png b/mobile/apps/photos/assets/launcher_icon/icon-dark.png similarity index 100% rename from mobile/assets/launcher_icon/icon-dark.png rename to mobile/apps/photos/assets/launcher_icon/icon-dark.png diff --git a/mobile/assets/launcher_icon/icon-dev.png b/mobile/apps/photos/assets/launcher_icon/icon-dev.png similarity index 100% rename from mobile/assets/launcher_icon/icon-dev.png rename to mobile/apps/photos/assets/launcher_icon/icon-dev.png diff --git a/mobile/assets/launcher_icon/icon-foreground.png b/mobile/apps/photos/assets/launcher_icon/icon-foreground.png similarity index 100% rename from mobile/assets/launcher_icon/icon-foreground.png rename to mobile/apps/photos/assets/launcher_icon/icon-foreground.png diff --git a/mobile/assets/launcher_icon/icon-green.png b/mobile/apps/photos/assets/launcher_icon/icon-green.png similarity index 100% rename from mobile/assets/launcher_icon/icon-green.png rename to mobile/apps/photos/assets/launcher_icon/icon-green.png diff --git a/mobile/assets/launcher_icon/icon-light.png b/mobile/apps/photos/assets/launcher_icon/icon-light.png similarity index 100% rename from mobile/assets/launcher_icon/icon-light.png rename to mobile/apps/photos/assets/launcher_icon/icon-light.png diff --git a/mobile/assets/launcher_icon/icon-monochrome-foreground.png b/mobile/apps/photos/assets/launcher_icon/icon-monochrome-foreground.png similarity index 100% rename from mobile/assets/launcher_icon/icon-monochrome-foreground.png rename to mobile/apps/photos/assets/launcher_icon/icon-monochrome-foreground.png diff --git a/mobile/assets/launcher_icon/icon-og-foreground.png b/mobile/apps/photos/assets/launcher_icon/icon-og-foreground.png similarity index 100% rename from mobile/assets/launcher_icon/icon-og-foreground.png rename to mobile/apps/photos/assets/launcher_icon/icon-og-foreground.png diff --git a/mobile/assets/launcher_icon/icon-og.png b/mobile/apps/photos/assets/launcher_icon/icon-og.png similarity index 100% rename from mobile/assets/launcher_icon/icon-og.png rename to mobile/apps/photos/assets/launcher_icon/icon-og.png diff --git a/mobile/assets/loadingGalleryLottie.json b/mobile/apps/photos/assets/loadingGalleryLottie.json similarity index 100% rename from mobile/assets/loadingGalleryLottie.json rename to mobile/apps/photos/assets/loadingGalleryLottie.json diff --git a/mobile/assets/loading_photos_background.png b/mobile/apps/photos/assets/loading_photos_background.png similarity index 100% rename from mobile/assets/loading_photos_background.png rename to mobile/apps/photos/assets/loading_photos_background.png diff --git a/mobile/assets/loading_photos_background_dark.png b/mobile/apps/photos/assets/loading_photos_background_dark.png similarity index 100% rename from mobile/assets/loading_photos_background_dark.png rename to mobile/apps/photos/assets/loading_photos_background_dark.png diff --git a/mobile/assets/lock_screen_background.png b/mobile/apps/photos/assets/lock_screen_background.png similarity index 100% rename from mobile/assets/lock_screen_background.png rename to mobile/apps/photos/assets/lock_screen_background.png diff --git a/mobile/assets/map.png b/mobile/apps/photos/assets/map.png similarity index 100% rename from mobile/assets/map.png rename to mobile/apps/photos/assets/map.png diff --git a/mobile/assets/map_world.png b/mobile/apps/photos/assets/map_world.png similarity index 100% rename from mobile/assets/map_world.png rename to mobile/apps/photos/assets/map_world.png diff --git a/mobile/assets/memories-widget-static.png b/mobile/apps/photos/assets/memories-widget-static.png similarity index 100% rename from mobile/assets/memories-widget-static.png rename to mobile/apps/photos/assets/memories-widget-static.png diff --git a/mobile/assets/new_empty_album.png b/mobile/apps/photos/assets/new_empty_album.png similarity index 100% rename from mobile/assets/new_empty_album.png rename to mobile/apps/photos/assets/new_empty_album.png diff --git a/mobile/assets/new_empty_album_dark.png b/mobile/apps/photos/assets/new_empty_album_dark.png similarity index 100% rename from mobile/assets/new_empty_album_dark.png rename to mobile/apps/photos/assets/new_empty_album_dark.png diff --git a/mobile/assets/onboarding_lock.png b/mobile/apps/photos/assets/onboarding_lock.png similarity index 100% rename from mobile/assets/onboarding_lock.png rename to mobile/apps/photos/assets/onboarding_lock.png diff --git a/mobile/assets/onboarding_safe.png b/mobile/apps/photos/assets/onboarding_safe.png similarity index 100% rename from mobile/assets/onboarding_safe.png rename to mobile/apps/photos/assets/onboarding_safe.png diff --git a/mobile/assets/onboarding_sync.png b/mobile/apps/photos/assets/onboarding_sync.png similarity index 100% rename from mobile/assets/onboarding_sync.png rename to mobile/apps/photos/assets/onboarding_sync.png diff --git a/mobile/assets/people-widget-static.png b/mobile/apps/photos/assets/people-widget-static.png similarity index 100% rename from mobile/assets/people-widget-static.png rename to mobile/apps/photos/assets/people-widget-static.png diff --git a/mobile/assets/popular_subscription.png b/mobile/apps/photos/assets/popular_subscription.png similarity index 100% rename from mobile/assets/popular_subscription.png rename to mobile/apps/photos/assets/popular_subscription.png diff --git a/mobile/assets/preserved_green.png b/mobile/apps/photos/assets/preserved_green.png similarity index 100% rename from mobile/assets/preserved_green.png rename to mobile/apps/photos/assets/preserved_green.png diff --git a/mobile/assets/processing-video-failed.png b/mobile/apps/photos/assets/processing-video-failed.png similarity index 100% rename from mobile/assets/processing-video-failed.png rename to mobile/apps/photos/assets/processing-video-failed.png diff --git a/mobile/assets/processing-video-success.png b/mobile/apps/photos/assets/processing-video-success.png similarity index 100% rename from mobile/assets/processing-video-success.png rename to mobile/apps/photos/assets/processing-video-success.png diff --git a/mobile/assets/processing-video.png b/mobile/apps/photos/assets/processing-video.png similarity index 100% rename from mobile/assets/processing-video.png rename to mobile/apps/photos/assets/processing-video.png diff --git a/mobile/assets/splash-screen-icon.png b/mobile/apps/photos/assets/splash-screen-icon.png similarity index 100% rename from mobile/assets/splash-screen-icon.png rename to mobile/apps/photos/assets/splash-screen-icon.png diff --git a/mobile/assets/storage_card_background.png b/mobile/apps/photos/assets/storage_card_background.png similarity index 100% rename from mobile/assets/storage_card_background.png rename to mobile/apps/photos/assets/storage_card_background.png diff --git a/mobile/assets/type_AVI.png b/mobile/apps/photos/assets/type_AVI.png similarity index 100% rename from mobile/assets/type_AVI.png rename to mobile/apps/photos/assets/type_AVI.png diff --git a/mobile/assets/type_GIF.png b/mobile/apps/photos/assets/type_GIF.png similarity index 100% rename from mobile/assets/type_GIF.png rename to mobile/apps/photos/assets/type_GIF.png diff --git a/mobile/assets/type_HEIC.png b/mobile/apps/photos/assets/type_HEIC.png similarity index 100% rename from mobile/assets/type_HEIC.png rename to mobile/apps/photos/assets/type_HEIC.png diff --git a/mobile/assets/type_JPEG.png b/mobile/apps/photos/assets/type_JPEG.png similarity index 100% rename from mobile/assets/type_JPEG.png rename to mobile/apps/photos/assets/type_JPEG.png diff --git a/mobile/assets/type_JPG.png b/mobile/apps/photos/assets/type_JPG.png similarity index 100% rename from mobile/assets/type_JPG.png rename to mobile/apps/photos/assets/type_JPG.png diff --git a/mobile/assets/type_MKV.png b/mobile/apps/photos/assets/type_MKV.png similarity index 100% rename from mobile/assets/type_MKV.png rename to mobile/apps/photos/assets/type_MKV.png diff --git a/mobile/assets/type_MP4.png b/mobile/apps/photos/assets/type_MP4.png similarity index 100% rename from mobile/assets/type_MP4.png rename to mobile/apps/photos/assets/type_MP4.png diff --git a/mobile/assets/type_PNG.png b/mobile/apps/photos/assets/type_PNG.png similarity index 100% rename from mobile/assets/type_PNG.png rename to mobile/apps/photos/assets/type_PNG.png diff --git a/mobile/assets/type_WEBP.png b/mobile/apps/photos/assets/type_WEBP.png similarity index 100% rename from mobile/assets/type_WEBP.png rename to mobile/apps/photos/assets/type_WEBP.png diff --git a/mobile/assets/type_live.png b/mobile/apps/photos/assets/type_live.png similarity index 100% rename from mobile/assets/type_live.png rename to mobile/apps/photos/assets/type_live.png diff --git a/mobile/assets/type_photos.png b/mobile/apps/photos/assets/type_photos.png similarity index 100% rename from mobile/assets/type_photos.png rename to mobile/apps/photos/assets/type_photos.png diff --git a/mobile/assets/type_unknown.png b/mobile/apps/photos/assets/type_unknown.png similarity index 100% rename from mobile/assets/type_unknown.png rename to mobile/apps/photos/assets/type_unknown.png diff --git a/mobile/assets/type_videos.png b/mobile/apps/photos/assets/type_videos.png similarity index 100% rename from mobile/assets/type_videos.png rename to mobile/apps/photos/assets/type_videos.png diff --git a/mobile/assets/video-editor/video-crop-free-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-free-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-free-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-free-action.svg diff --git a/mobile/assets/video-editor/video-crop-original-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-original-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-original-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-original-action.svg diff --git a/mobile/assets/video-editor/video-crop-ratio_16_9-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-ratio_16_9-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-ratio_16_9-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-ratio_16_9-action.svg diff --git a/mobile/assets/video-editor/video-crop-ratio_1_1-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-ratio_1_1-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-ratio_1_1-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-ratio_1_1-action.svg diff --git a/mobile/assets/video-editor/video-crop-ratio_3_4-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-ratio_3_4-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-ratio_3_4-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-ratio_3_4-action.svg diff --git a/mobile/assets/video-editor/video-crop-ratio_4_3-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-ratio_4_3-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-ratio_4_3-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-ratio_4_3-action.svg diff --git a/mobile/assets/video-editor/video-crop-ratio_9_16-action.svg b/mobile/apps/photos/assets/video-editor/video-crop-ratio_9_16-action.svg similarity index 100% rename from mobile/assets/video-editor/video-crop-ratio_9_16-action.svg rename to mobile/apps/photos/assets/video-editor/video-crop-ratio_9_16-action.svg diff --git a/mobile/assets/video-editor/video-editor-crop-action.svg b/mobile/apps/photos/assets/video-editor/video-editor-crop-action.svg similarity index 100% rename from mobile/assets/video-editor/video-editor-crop-action.svg rename to mobile/apps/photos/assets/video-editor/video-editor-crop-action.svg diff --git a/mobile/assets/video-editor/video-editor-rotate-action.svg b/mobile/apps/photos/assets/video-editor/video-editor-rotate-action.svg similarity index 100% rename from mobile/assets/video-editor/video-editor-rotate-action.svg rename to mobile/apps/photos/assets/video-editor/video-editor-rotate-action.svg diff --git a/mobile/assets/video-editor/video-editor-trim-action.svg b/mobile/apps/photos/assets/video-editor/video-editor-trim-action.svg similarity index 100% rename from mobile/assets/video-editor/video-editor-trim-action.svg rename to mobile/apps/photos/assets/video-editor/video-editor-trim-action.svg diff --git a/mobile/assets/video-processing-queued.png b/mobile/apps/photos/assets/video-processing-queued.png similarity index 100% rename from mobile/assets/video-processing-queued.png rename to mobile/apps/photos/assets/video-processing-queued.png diff --git a/mobile/build-apk.sh b/mobile/apps/photos/build-apk.sh similarity index 100% rename from mobile/build-apk.sh rename to mobile/apps/photos/build-apk.sh diff --git a/mobile/crowdin.yml b/mobile/apps/photos/crowdin.yml similarity index 100% rename from mobile/crowdin.yml rename to mobile/apps/photos/crowdin.yml diff --git a/mobile/devtools_options.yaml b/mobile/apps/photos/devtools_options.yaml similarity index 100% rename from mobile/devtools_options.yaml rename to mobile/apps/photos/devtools_options.yaml diff --git a/mobile/docs/README.md b/mobile/apps/photos/docs/README.md similarity index 100% rename from mobile/docs/README.md rename to mobile/apps/photos/docs/README.md diff --git a/mobile/docs/assets/translations_1.png b/mobile/apps/photos/docs/assets/translations_1.png similarity index 100% rename from mobile/docs/assets/translations_1.png rename to mobile/apps/photos/docs/assets/translations_1.png diff --git a/mobile/docs/assets/translations_2.png b/mobile/apps/photos/docs/assets/translations_2.png similarity index 100% rename from mobile/docs/assets/translations_2.png rename to mobile/apps/photos/docs/assets/translations_2.png diff --git a/mobile/docs/assets/translations_3.png b/mobile/apps/photos/docs/assets/translations_3.png similarity index 100% rename from mobile/docs/assets/translations_3.png rename to mobile/apps/photos/docs/assets/translations_3.png diff --git a/mobile/docs/assets/translations_4.png b/mobile/apps/photos/docs/assets/translations_4.png similarity index 100% rename from mobile/docs/assets/translations_4.png rename to mobile/apps/photos/docs/assets/translations_4.png diff --git a/mobile/docs/dev.md b/mobile/apps/photos/docs/dev.md similarity index 100% rename from mobile/docs/dev.md rename to mobile/apps/photos/docs/dev.md diff --git a/mobile/docs/release.md b/mobile/apps/photos/docs/release.md similarity index 100% rename from mobile/docs/release.md rename to mobile/apps/photos/docs/release.md diff --git a/mobile/docs/translations.md b/mobile/apps/photos/docs/translations.md similarity index 100% rename from mobile/docs/translations.md rename to mobile/apps/photos/docs/translations.md diff --git a/mobile/docs/vscode/launch.json b/mobile/apps/photos/docs/vscode/launch.json similarity index 100% rename from mobile/docs/vscode/launch.json rename to mobile/apps/photos/docs/vscode/launch.json diff --git a/mobile/fastlane/Appfile b/mobile/apps/photos/fastlane/Appfile similarity index 100% rename from mobile/fastlane/Appfile rename to mobile/apps/photos/fastlane/Appfile diff --git a/mobile/fastlane/Fastfile b/mobile/apps/photos/fastlane/Fastfile similarity index 100% rename from mobile/fastlane/Fastfile rename to mobile/apps/photos/fastlane/Fastfile diff --git a/mobile/fastlane/Pluginfile b/mobile/apps/photos/fastlane/Pluginfile similarity index 100% rename from mobile/fastlane/Pluginfile rename to mobile/apps/photos/fastlane/Pluginfile diff --git a/mobile/fastlane/README.md b/mobile/apps/photos/fastlane/README.md similarity index 100% rename from mobile/fastlane/README.md rename to mobile/apps/photos/fastlane/README.md diff --git a/mobile/fastlane/metadata/android/ar/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ar/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ar/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ar/full_description.txt diff --git a/mobile/fastlane/metadata/android/ar/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ar/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ar/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ar/short_description.txt diff --git a/mobile/fastlane/metadata/android/ar/title.txt b/mobile/apps/photos/fastlane/metadata/android/ar/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ar/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ar/title.txt diff --git a/mobile/fastlane/metadata/android/be/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/be/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/be/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/be/full_description.txt diff --git a/mobile/fastlane/metadata/android/be/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/be/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/be/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/be/short_description.txt diff --git a/mobile/fastlane/metadata/android/be/title.txt b/mobile/apps/photos/fastlane/metadata/android/be/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/be/title.txt rename to mobile/apps/photos/fastlane/metadata/android/be/title.txt diff --git a/mobile/fastlane/metadata/android/bg/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/bg/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/bg/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/bg/full_description.txt diff --git a/mobile/fastlane/metadata/android/bg/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/bg/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/bg/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/bg/short_description.txt diff --git a/mobile/fastlane/metadata/android/bg/title.txt b/mobile/apps/photos/fastlane/metadata/android/bg/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/bg/title.txt rename to mobile/apps/photos/fastlane/metadata/android/bg/title.txt diff --git a/mobile/fastlane/metadata/android/ca/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ca/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ca/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ca/full_description.txt diff --git a/mobile/fastlane/metadata/android/ca/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ca/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ca/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ca/short_description.txt diff --git a/mobile/fastlane/metadata/android/ca/title.txt b/mobile/apps/photos/fastlane/metadata/android/ca/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ca/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ca/title.txt diff --git a/mobile/fastlane/metadata/android/cs/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/cs/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/cs/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/cs/full_description.txt diff --git a/mobile/fastlane/metadata/android/cs/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/cs/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/cs/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/cs/short_description.txt diff --git a/mobile/fastlane/metadata/android/cs/title.txt b/mobile/apps/photos/fastlane/metadata/android/cs/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/cs/title.txt rename to mobile/apps/photos/fastlane/metadata/android/cs/title.txt diff --git a/mobile/fastlane/metadata/android/da/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/da/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/da/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/da/full_description.txt diff --git a/mobile/fastlane/metadata/android/da/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/da/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/da/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/da/short_description.txt diff --git a/mobile/fastlane/metadata/android/da/title.txt b/mobile/apps/photos/fastlane/metadata/android/da/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/da/title.txt rename to mobile/apps/photos/fastlane/metadata/android/da/title.txt diff --git a/mobile/fastlane/metadata/android/de/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/de/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/de/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/de/full_description.txt diff --git a/mobile/fastlane/metadata/android/de/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/de/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/de/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/de/short_description.txt diff --git a/mobile/fastlane/metadata/android/de/title.txt b/mobile/apps/photos/fastlane/metadata/android/de/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/de/title.txt rename to mobile/apps/photos/fastlane/metadata/android/de/title.txt diff --git a/mobile/fastlane/metadata/android/el/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/el/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/el/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/el/full_description.txt diff --git a/mobile/fastlane/metadata/android/el/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/el/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/el/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/el/short_description.txt diff --git a/mobile/fastlane/metadata/android/el/title.txt b/mobile/apps/photos/fastlane/metadata/android/el/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/el/title.txt rename to mobile/apps/photos/fastlane/metadata/android/el/title.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/169.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/169.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/169.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/169.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/293.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/293.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/293.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/293.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/317.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/317.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/317.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/317.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/330.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/330.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/330.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/330.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/331.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/331.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/331.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/331.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/333.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/333.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/333.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/333.txt diff --git a/mobile/fastlane/metadata/android/en-US/changelogs/420.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/420.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/changelogs/420.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/changelogs/420.txt diff --git a/mobile/fastlane/metadata/android/en-US/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/full_description.txt diff --git a/mobile/fastlane/metadata/android/en-US/images/icon.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/icon.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/icon.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/icon.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png diff --git a/mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png similarity index 100% rename from mobile/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png rename to mobile/apps/photos/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png diff --git a/mobile/fastlane/metadata/android/en-US/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/short_description.txt diff --git a/mobile/fastlane/metadata/android/en-US/title.txt b/mobile/apps/photos/fastlane/metadata/android/en-US/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/en-US/title.txt rename to mobile/apps/photos/fastlane/metadata/android/en-US/title.txt diff --git a/mobile/fastlane/metadata/android/es/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/es/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/es/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/es/full_description.txt diff --git a/mobile/fastlane/metadata/android/es/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/es/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/es/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/es/short_description.txt diff --git a/mobile/fastlane/metadata/android/es/title.txt b/mobile/apps/photos/fastlane/metadata/android/es/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/es/title.txt rename to mobile/apps/photos/fastlane/metadata/android/es/title.txt diff --git a/mobile/fastlane/metadata/android/et/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/et/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/et/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/et/full_description.txt diff --git a/mobile/fastlane/metadata/android/et/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/et/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/et/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/et/short_description.txt diff --git a/mobile/fastlane/metadata/android/et/title.txt b/mobile/apps/photos/fastlane/metadata/android/et/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/et/title.txt rename to mobile/apps/photos/fastlane/metadata/android/et/title.txt diff --git a/mobile/fastlane/metadata/android/eu/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/eu/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/eu/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/eu/full_description.txt diff --git a/mobile/fastlane/metadata/android/eu/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/eu/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/eu/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/eu/short_description.txt diff --git a/mobile/fastlane/metadata/android/eu/title.txt b/mobile/apps/photos/fastlane/metadata/android/eu/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/eu/title.txt rename to mobile/apps/photos/fastlane/metadata/android/eu/title.txt diff --git a/mobile/fastlane/metadata/android/fa/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/fa/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/fa/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/fa/full_description.txt diff --git a/mobile/fastlane/metadata/android/fa/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/fa/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/fa/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/fa/short_description.txt diff --git a/mobile/fastlane/metadata/android/fa/title.txt b/mobile/apps/photos/fastlane/metadata/android/fa/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/fa/title.txt rename to mobile/apps/photos/fastlane/metadata/android/fa/title.txt diff --git a/mobile/fastlane/metadata/android/fr/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/fr/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/fr/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/fr/full_description.txt diff --git a/mobile/fastlane/metadata/android/fr/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/fr/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/fr/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/fr/short_description.txt diff --git a/mobile/fastlane/metadata/android/fr/title.txt b/mobile/apps/photos/fastlane/metadata/android/fr/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/fr/title.txt rename to mobile/apps/photos/fastlane/metadata/android/fr/title.txt diff --git a/mobile/fastlane/metadata/android/gu/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/gu/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/gu/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/gu/full_description.txt diff --git a/mobile/fastlane/metadata/android/gu/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/gu/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/gu/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/gu/short_description.txt diff --git a/mobile/fastlane/metadata/android/gu/title.txt b/mobile/apps/photos/fastlane/metadata/android/gu/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/gu/title.txt rename to mobile/apps/photos/fastlane/metadata/android/gu/title.txt diff --git a/mobile/fastlane/metadata/android/he/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/he/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/he/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/he/full_description.txt diff --git a/mobile/fastlane/metadata/android/he/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/he/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/he/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/he/short_description.txt diff --git a/mobile/fastlane/metadata/android/he/title.txt b/mobile/apps/photos/fastlane/metadata/android/he/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/he/title.txt rename to mobile/apps/photos/fastlane/metadata/android/he/title.txt diff --git a/mobile/fastlane/metadata/android/hi/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/hi/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/hi/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/hi/full_description.txt diff --git a/mobile/fastlane/metadata/android/hi/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/hi/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/hi/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/hi/short_description.txt diff --git a/mobile/fastlane/metadata/android/hi/title.txt b/mobile/apps/photos/fastlane/metadata/android/hi/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/hi/title.txt rename to mobile/apps/photos/fastlane/metadata/android/hi/title.txt diff --git a/mobile/fastlane/metadata/android/hu/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/hu/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/hu/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/hu/full_description.txt diff --git a/mobile/fastlane/metadata/android/hu/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/hu/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/hu/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/hu/short_description.txt diff --git a/mobile/fastlane/metadata/android/hu/title.txt b/mobile/apps/photos/fastlane/metadata/android/hu/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/hu/title.txt rename to mobile/apps/photos/fastlane/metadata/android/hu/title.txt diff --git a/mobile/fastlane/metadata/android/id/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/id/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/id/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/id/full_description.txt diff --git a/mobile/fastlane/metadata/android/id/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/id/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/id/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/id/short_description.txt diff --git a/mobile/fastlane/metadata/android/id/title.txt b/mobile/apps/photos/fastlane/metadata/android/id/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/id/title.txt rename to mobile/apps/photos/fastlane/metadata/android/id/title.txt diff --git a/mobile/fastlane/metadata/android/it/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/it/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/it/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/it/full_description.txt diff --git a/mobile/fastlane/metadata/android/it/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/it/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/it/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/it/short_description.txt diff --git a/mobile/fastlane/metadata/android/it/title.txt b/mobile/apps/photos/fastlane/metadata/android/it/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/it/title.txt rename to mobile/apps/photos/fastlane/metadata/android/it/title.txt diff --git a/mobile/fastlane/metadata/android/ja/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ja/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ja/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ja/full_description.txt diff --git a/mobile/fastlane/metadata/android/ja/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ja/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ja/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ja/short_description.txt diff --git a/mobile/fastlane/metadata/android/ja/title.txt b/mobile/apps/photos/fastlane/metadata/android/ja/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ja/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ja/title.txt diff --git a/mobile/fastlane/metadata/android/km/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/km/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/km/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/km/full_description.txt diff --git a/mobile/fastlane/metadata/android/km/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/km/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/km/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/km/short_description.txt diff --git a/mobile/fastlane/metadata/android/km/title.txt b/mobile/apps/photos/fastlane/metadata/android/km/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/km/title.txt rename to mobile/apps/photos/fastlane/metadata/android/km/title.txt diff --git a/mobile/fastlane/metadata/android/ko/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ko/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ko/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ko/full_description.txt diff --git a/mobile/fastlane/metadata/android/ko/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ko/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ko/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ko/short_description.txt diff --git a/mobile/fastlane/metadata/android/ko/title.txt b/mobile/apps/photos/fastlane/metadata/android/ko/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ko/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ko/title.txt diff --git a/mobile/fastlane/metadata/android/ku/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ku/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ku/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ku/full_description.txt diff --git a/mobile/fastlane/metadata/android/ku/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ku/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ku/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ku/short_description.txt diff --git a/mobile/fastlane/metadata/android/ku/title.txt b/mobile/apps/photos/fastlane/metadata/android/ku/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ku/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ku/title.txt diff --git a/mobile/fastlane/metadata/android/lt/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/lt/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/lt/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/lt/full_description.txt diff --git a/mobile/fastlane/metadata/android/lt/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/lt/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/lt/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/lt/short_description.txt diff --git a/mobile/fastlane/metadata/android/lt/title.txt b/mobile/apps/photos/fastlane/metadata/android/lt/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/lt/title.txt rename to mobile/apps/photos/fastlane/metadata/android/lt/title.txt diff --git a/mobile/fastlane/metadata/android/lv/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/lv/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/lv/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/lv/full_description.txt diff --git a/mobile/fastlane/metadata/android/lv/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/lv/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/lv/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/lv/short_description.txt diff --git a/mobile/fastlane/metadata/android/lv/title.txt b/mobile/apps/photos/fastlane/metadata/android/lv/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/lv/title.txt rename to mobile/apps/photos/fastlane/metadata/android/lv/title.txt diff --git a/mobile/fastlane/metadata/android/ml/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ml/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ml/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ml/full_description.txt diff --git a/mobile/fastlane/metadata/android/ml/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ml/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ml/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ml/short_description.txt diff --git a/mobile/fastlane/metadata/android/ml/title.txt b/mobile/apps/photos/fastlane/metadata/android/ml/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ml/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ml/title.txt diff --git a/mobile/fastlane/metadata/android/nl/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/nl/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/nl/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/nl/full_description.txt diff --git a/mobile/fastlane/metadata/android/nl/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/nl/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/nl/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/nl/short_description.txt diff --git a/mobile/fastlane/metadata/android/nl/title.txt b/mobile/apps/photos/fastlane/metadata/android/nl/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/nl/title.txt rename to mobile/apps/photos/fastlane/metadata/android/nl/title.txt diff --git a/mobile/fastlane/metadata/android/no/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/no/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/no/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/no/full_description.txt diff --git a/mobile/fastlane/metadata/android/no/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/no/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/no/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/no/short_description.txt diff --git a/mobile/fastlane/metadata/android/no/title.txt b/mobile/apps/photos/fastlane/metadata/android/no/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/no/title.txt rename to mobile/apps/photos/fastlane/metadata/android/no/title.txt diff --git a/mobile/fastlane/metadata/android/or/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/or/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/or/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/or/full_description.txt diff --git a/mobile/fastlane/metadata/android/or/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/or/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/or/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/or/short_description.txt diff --git a/mobile/fastlane/metadata/android/or/title.txt b/mobile/apps/photos/fastlane/metadata/android/or/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/or/title.txt rename to mobile/apps/photos/fastlane/metadata/android/or/title.txt diff --git a/mobile/fastlane/metadata/android/pl/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/pl/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pl/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pl/full_description.txt diff --git a/mobile/fastlane/metadata/android/pl/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/pl/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pl/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pl/short_description.txt diff --git a/mobile/fastlane/metadata/android/pl/title.txt b/mobile/apps/photos/fastlane/metadata/android/pl/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/pl/title.txt rename to mobile/apps/photos/fastlane/metadata/android/pl/title.txt diff --git a/mobile/fastlane/metadata/android/pt/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/pt/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pt/full_description.txt diff --git a/mobile/fastlane/metadata/android/pt/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/pt/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pt/short_description.txt diff --git a/mobile/fastlane/metadata/android/pt/title.txt b/mobile/apps/photos/fastlane/metadata/android/pt/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt/title.txt rename to mobile/apps/photos/fastlane/metadata/android/pt/title.txt diff --git a/mobile/fastlane/metadata/android/pt_BR/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/pt_BR/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt_BR/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pt_BR/full_description.txt diff --git a/mobile/fastlane/metadata/android/pt_BR/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/pt_BR/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt_BR/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pt_BR/short_description.txt diff --git a/mobile/fastlane/metadata/android/pt_BR/title.txt b/mobile/apps/photos/fastlane/metadata/android/pt_BR/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt_BR/title.txt rename to mobile/apps/photos/fastlane/metadata/android/pt_BR/title.txt diff --git a/mobile/fastlane/metadata/android/pt_PT/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/pt_PT/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt_PT/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pt_PT/full_description.txt diff --git a/mobile/fastlane/metadata/android/pt_PT/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/pt_PT/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt_PT/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/pt_PT/short_description.txt diff --git a/mobile/fastlane/metadata/android/pt_PT/title.txt b/mobile/apps/photos/fastlane/metadata/android/pt_PT/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/pt_PT/title.txt rename to mobile/apps/photos/fastlane/metadata/android/pt_PT/title.txt diff --git a/mobile/fastlane/metadata/android/ro/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ro/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ro/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ro/full_description.txt diff --git a/mobile/fastlane/metadata/android/ro/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ro/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ro/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ro/short_description.txt diff --git a/mobile/fastlane/metadata/android/ro/title.txt b/mobile/apps/photos/fastlane/metadata/android/ro/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ro/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ro/title.txt diff --git a/mobile/fastlane/metadata/android/ru/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ru/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ru/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ru/full_description.txt diff --git a/mobile/fastlane/metadata/android/ru/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ru/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ru/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ru/short_description.txt diff --git a/mobile/fastlane/metadata/android/ru/title.txt b/mobile/apps/photos/fastlane/metadata/android/ru/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ru/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ru/title.txt diff --git a/mobile/fastlane/metadata/android/sl/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/sl/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/sl/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/sl/full_description.txt diff --git a/mobile/fastlane/metadata/android/sl/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/sl/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/sl/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/sl/short_description.txt diff --git a/mobile/fastlane/metadata/android/sl/title.txt b/mobile/apps/photos/fastlane/metadata/android/sl/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/sl/title.txt rename to mobile/apps/photos/fastlane/metadata/android/sl/title.txt diff --git a/mobile/fastlane/metadata/android/sr/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/sr/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/sr/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/sr/full_description.txt diff --git a/mobile/fastlane/metadata/android/sr/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/sr/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/sr/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/sr/short_description.txt diff --git a/mobile/fastlane/metadata/android/sr/title.txt b/mobile/apps/photos/fastlane/metadata/android/sr/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/sr/title.txt rename to mobile/apps/photos/fastlane/metadata/android/sr/title.txt diff --git a/mobile/fastlane/metadata/android/sv/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/sv/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/sv/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/sv/full_description.txt diff --git a/mobile/fastlane/metadata/android/sv/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/sv/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/sv/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/sv/short_description.txt diff --git a/mobile/fastlane/metadata/android/sv/title.txt b/mobile/apps/photos/fastlane/metadata/android/sv/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/sv/title.txt rename to mobile/apps/photos/fastlane/metadata/android/sv/title.txt diff --git a/mobile/fastlane/metadata/android/ta/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ta/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ta/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ta/full_description.txt diff --git a/mobile/fastlane/metadata/android/ta/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ta/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ta/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ta/short_description.txt diff --git a/mobile/fastlane/metadata/android/ta/title.txt b/mobile/apps/photos/fastlane/metadata/android/ta/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ta/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ta/title.txt diff --git a/mobile/fastlane/metadata/android/te/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/te/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/te/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/te/full_description.txt diff --git a/mobile/fastlane/metadata/android/te/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/te/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/te/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/te/short_description.txt diff --git a/mobile/fastlane/metadata/android/te/title.txt b/mobile/apps/photos/fastlane/metadata/android/te/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/te/title.txt rename to mobile/apps/photos/fastlane/metadata/android/te/title.txt diff --git a/mobile/fastlane/metadata/android/th/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/th/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/th/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/th/full_description.txt diff --git a/mobile/fastlane/metadata/android/th/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/th/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/th/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/th/short_description.txt diff --git a/mobile/fastlane/metadata/android/th/title.txt b/mobile/apps/photos/fastlane/metadata/android/th/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/th/title.txt rename to mobile/apps/photos/fastlane/metadata/android/th/title.txt diff --git a/mobile/fastlane/metadata/android/ti/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/ti/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ti/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ti/full_description.txt diff --git a/mobile/fastlane/metadata/android/ti/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/ti/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/ti/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/ti/short_description.txt diff --git a/mobile/fastlane/metadata/android/ti/title.txt b/mobile/apps/photos/fastlane/metadata/android/ti/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/ti/title.txt rename to mobile/apps/photos/fastlane/metadata/android/ti/title.txt diff --git a/mobile/fastlane/metadata/android/tr/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/tr/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/tr/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/tr/full_description.txt diff --git a/mobile/fastlane/metadata/android/tr/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/tr/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/tr/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/tr/short_description.txt diff --git a/mobile/fastlane/metadata/android/tr/title.txt b/mobile/apps/photos/fastlane/metadata/android/tr/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/tr/title.txt rename to mobile/apps/photos/fastlane/metadata/android/tr/title.txt diff --git a/mobile/fastlane/metadata/android/uk/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/uk/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/uk/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/uk/full_description.txt diff --git a/mobile/fastlane/metadata/android/uk/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/uk/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/uk/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/uk/short_description.txt diff --git a/mobile/fastlane/metadata/android/uk/title.txt b/mobile/apps/photos/fastlane/metadata/android/uk/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/uk/title.txt rename to mobile/apps/photos/fastlane/metadata/android/uk/title.txt diff --git a/mobile/fastlane/metadata/android/vi/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/vi/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/vi/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/vi/full_description.txt diff --git a/mobile/fastlane/metadata/android/vi/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/vi/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/vi/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/vi/short_description.txt diff --git a/mobile/fastlane/metadata/android/vi/title.txt b/mobile/apps/photos/fastlane/metadata/android/vi/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/vi/title.txt rename to mobile/apps/photos/fastlane/metadata/android/vi/title.txt diff --git a/mobile/fastlane/metadata/android/zh/full_description.txt b/mobile/apps/photos/fastlane/metadata/android/zh/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/zh/full_description.txt rename to mobile/apps/photos/fastlane/metadata/android/zh/full_description.txt diff --git a/mobile/fastlane/metadata/android/zh/short_description.txt b/mobile/apps/photos/fastlane/metadata/android/zh/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/android/zh/short_description.txt rename to mobile/apps/photos/fastlane/metadata/android/zh/short_description.txt diff --git a/mobile/fastlane/metadata/android/zh/title.txt b/mobile/apps/photos/fastlane/metadata/android/zh/title.txt similarity index 100% rename from mobile/fastlane/metadata/android/zh/title.txt rename to mobile/apps/photos/fastlane/metadata/android/zh/title.txt diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_129_0.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_129_0.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_129_0.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_129_0.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_3GEN_129_0.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_3GEN_129_0.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_3GEN_129_0.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPAD_PRO_3GEN_129_0.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_55_0.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_55_0.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_55_0.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_55_0.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_65_0.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_65_0.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_65_0.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/0_APP_IPHONE_65_0.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_55_1.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_55_1.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_55_1.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_55_1.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_65_1.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_65_1.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_65_1.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/1_APP_IPHONE_65_1.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_55_2.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_55_2.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_55_2.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_55_2.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_65_2.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_65_2.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_65_2.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/2_APP_IPHONE_65_2.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_55_3.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_55_3.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_55_3.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_55_3.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_65_3.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_65_3.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_65_3.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/3_APP_IPHONE_65_3.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_55_4.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_55_4.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_55_4.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_55_4.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_65_4.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_65_4.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_65_4.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/4_APP_IPHONE_65_4.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_55_5.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_55_5.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_55_5.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_55_5.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_65_5.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_65_5.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_65_5.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/5_APP_IPHONE_65_5.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_55_6.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_55_6.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_55_6.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_55_6.png diff --git a/mobile/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_65_6.png b/mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_65_6.png similarity index 100% rename from mobile/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_65_6.png rename to mobile/apps/photos/fastlane/metadata/ios/Screenshots/en-US/6_APP_IPHONE_65_6.png diff --git a/mobile/fastlane/metadata/ios/ar/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ar/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ar/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ar/description.txt diff --git a/mobile/fastlane/metadata/ios/ar/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ar/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ar/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ar/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ar/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ar/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ar/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ar/name.txt diff --git a/mobile/fastlane/metadata/ios/ar/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ar/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ar/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ar/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/be/description.txt b/mobile/apps/photos/fastlane/metadata/ios/be/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/be/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/be/description.txt diff --git a/mobile/fastlane/metadata/ios/be/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/be/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/be/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/be/keywords.txt diff --git a/mobile/fastlane/metadata/ios/be/name.txt b/mobile/apps/photos/fastlane/metadata/ios/be/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/be/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/be/name.txt diff --git a/mobile/fastlane/metadata/ios/be/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/be/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/be/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/be/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/bg/description.txt b/mobile/apps/photos/fastlane/metadata/ios/bg/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/bg/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/bg/description.txt diff --git a/mobile/fastlane/metadata/ios/bg/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/bg/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/bg/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/bg/keywords.txt diff --git a/mobile/fastlane/metadata/ios/bg/name.txt b/mobile/apps/photos/fastlane/metadata/ios/bg/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/bg/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/bg/name.txt diff --git a/mobile/fastlane/metadata/ios/bg/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/bg/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/bg/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/bg/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ca/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ca/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ca/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ca/description.txt diff --git a/mobile/fastlane/metadata/ios/ca/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ca/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ca/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ca/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ca/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ca/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ca/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ca/name.txt diff --git a/mobile/fastlane/metadata/ios/ca/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ca/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ca/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ca/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/cs/description.txt b/mobile/apps/photos/fastlane/metadata/ios/cs/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/cs/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/cs/description.txt diff --git a/mobile/fastlane/metadata/ios/cs/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/cs/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/cs/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/cs/keywords.txt diff --git a/mobile/fastlane/metadata/ios/cs/name.txt b/mobile/apps/photos/fastlane/metadata/ios/cs/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/cs/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/cs/name.txt diff --git a/mobile/fastlane/metadata/ios/cs/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/cs/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/cs/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/cs/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/da/description.txt b/mobile/apps/photos/fastlane/metadata/ios/da/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/da/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/da/description.txt diff --git a/mobile/fastlane/metadata/ios/da/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/da/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/da/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/da/keywords.txt diff --git a/mobile/fastlane/metadata/ios/da/name.txt b/mobile/apps/photos/fastlane/metadata/ios/da/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/da/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/da/name.txt diff --git a/mobile/fastlane/metadata/ios/da/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/da/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/da/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/da/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/de/description.txt b/mobile/apps/photos/fastlane/metadata/ios/de/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/de/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/de/description.txt diff --git a/mobile/fastlane/metadata/ios/de/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/de/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/de/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/de/keywords.txt diff --git a/mobile/fastlane/metadata/ios/de/name.txt b/mobile/apps/photos/fastlane/metadata/ios/de/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/de/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/de/name.txt diff --git a/mobile/fastlane/metadata/ios/de/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/de/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/de/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/de/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/el/description.txt b/mobile/apps/photos/fastlane/metadata/ios/el/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/el/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/el/description.txt diff --git a/mobile/fastlane/metadata/ios/el/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/el/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/el/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/el/keywords.txt diff --git a/mobile/fastlane/metadata/ios/el/name.txt b/mobile/apps/photos/fastlane/metadata/ios/el/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/el/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/el/name.txt diff --git a/mobile/fastlane/metadata/ios/el/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/el/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/el/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/el/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/en-US/description.txt b/mobile/apps/photos/fastlane/metadata/ios/en-US/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/en-US/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/en-US/description.txt diff --git a/mobile/fastlane/metadata/ios/en-US/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/en-US/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/en-US/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/en-US/keywords.txt diff --git a/mobile/fastlane/metadata/ios/en-US/name.txt b/mobile/apps/photos/fastlane/metadata/ios/en-US/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/en-US/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/en-US/name.txt diff --git a/mobile/fastlane/metadata/ios/en-US/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/en-US/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/en-US/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/en-US/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/es/description.txt b/mobile/apps/photos/fastlane/metadata/ios/es/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/es/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/es/description.txt diff --git a/mobile/fastlane/metadata/ios/es/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/es/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/es/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/es/keywords.txt diff --git a/mobile/fastlane/metadata/ios/es/name.txt b/mobile/apps/photos/fastlane/metadata/ios/es/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/es/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/es/name.txt diff --git a/mobile/fastlane/metadata/ios/es/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/es/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/es/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/es/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/et/description.txt b/mobile/apps/photos/fastlane/metadata/ios/et/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/et/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/et/description.txt diff --git a/mobile/fastlane/metadata/ios/et/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/et/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/et/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/et/keywords.txt diff --git a/mobile/fastlane/metadata/ios/et/name.txt b/mobile/apps/photos/fastlane/metadata/ios/et/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/et/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/et/name.txt diff --git a/mobile/fastlane/metadata/ios/et/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/et/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/et/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/et/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/eu/description.txt b/mobile/apps/photos/fastlane/metadata/ios/eu/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/eu/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/eu/description.txt diff --git a/mobile/fastlane/metadata/ios/eu/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/eu/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/eu/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/eu/keywords.txt diff --git a/mobile/fastlane/metadata/ios/eu/name.txt b/mobile/apps/photos/fastlane/metadata/ios/eu/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/eu/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/eu/name.txt diff --git a/mobile/fastlane/metadata/ios/eu/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/eu/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/eu/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/eu/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/fa/description.txt b/mobile/apps/photos/fastlane/metadata/ios/fa/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fa/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/fa/description.txt diff --git a/mobile/fastlane/metadata/ios/fa/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/fa/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fa/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/fa/keywords.txt diff --git a/mobile/fastlane/metadata/ios/fa/name.txt b/mobile/apps/photos/fastlane/metadata/ios/fa/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fa/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/fa/name.txt diff --git a/mobile/fastlane/metadata/ios/fa/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/fa/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fa/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/fa/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/fr/description.txt b/mobile/apps/photos/fastlane/metadata/ios/fr/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fr/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/fr/description.txt diff --git a/mobile/fastlane/metadata/ios/fr/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/fr/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fr/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/fr/keywords.txt diff --git a/mobile/fastlane/metadata/ios/fr/name.txt b/mobile/apps/photos/fastlane/metadata/ios/fr/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fr/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/fr/name.txt diff --git a/mobile/fastlane/metadata/ios/fr/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/fr/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/fr/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/fr/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/gu/description.txt b/mobile/apps/photos/fastlane/metadata/ios/gu/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/gu/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/gu/description.txt diff --git a/mobile/fastlane/metadata/ios/gu/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/gu/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/gu/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/gu/keywords.txt diff --git a/mobile/fastlane/metadata/ios/gu/name.txt b/mobile/apps/photos/fastlane/metadata/ios/gu/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/gu/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/gu/name.txt diff --git a/mobile/fastlane/metadata/ios/gu/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/gu/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/gu/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/gu/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/he/description.txt b/mobile/apps/photos/fastlane/metadata/ios/he/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/he/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/he/description.txt diff --git a/mobile/fastlane/metadata/ios/he/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/he/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/he/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/he/keywords.txt diff --git a/mobile/fastlane/metadata/ios/he/name.txt b/mobile/apps/photos/fastlane/metadata/ios/he/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/he/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/he/name.txt diff --git a/mobile/fastlane/metadata/ios/he/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/he/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/he/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/he/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/hi/description.txt b/mobile/apps/photos/fastlane/metadata/ios/hi/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hi/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/hi/description.txt diff --git a/mobile/fastlane/metadata/ios/hi/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/hi/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hi/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/hi/keywords.txt diff --git a/mobile/fastlane/metadata/ios/hi/name.txt b/mobile/apps/photos/fastlane/metadata/ios/hi/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hi/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/hi/name.txt diff --git a/mobile/fastlane/metadata/ios/hi/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/hi/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hi/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/hi/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/hu/description.txt b/mobile/apps/photos/fastlane/metadata/ios/hu/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hu/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/hu/description.txt diff --git a/mobile/fastlane/metadata/ios/hu/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/hu/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hu/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/hu/keywords.txt diff --git a/mobile/fastlane/metadata/ios/hu/name.txt b/mobile/apps/photos/fastlane/metadata/ios/hu/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hu/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/hu/name.txt diff --git a/mobile/fastlane/metadata/ios/hu/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/hu/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/hu/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/hu/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/id/description.txt b/mobile/apps/photos/fastlane/metadata/ios/id/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/id/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/id/description.txt diff --git a/mobile/fastlane/metadata/ios/id/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/id/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/id/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/id/keywords.txt diff --git a/mobile/fastlane/metadata/ios/id/name.txt b/mobile/apps/photos/fastlane/metadata/ios/id/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/id/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/id/name.txt diff --git a/mobile/fastlane/metadata/ios/id/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/id/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/id/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/id/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/it/description.txt b/mobile/apps/photos/fastlane/metadata/ios/it/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/it/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/it/description.txt diff --git a/mobile/fastlane/metadata/ios/it/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/it/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/it/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/it/keywords.txt diff --git a/mobile/fastlane/metadata/ios/it/name.txt b/mobile/apps/photos/fastlane/metadata/ios/it/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/it/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/it/name.txt diff --git a/mobile/fastlane/metadata/ios/it/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/it/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/it/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/it/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ja/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ja/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ja/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ja/description.txt diff --git a/mobile/fastlane/metadata/ios/ja/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ja/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ja/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ja/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ja/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ja/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ja/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ja/name.txt diff --git a/mobile/fastlane/metadata/ios/ja/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ja/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ja/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ja/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/km/description.txt b/mobile/apps/photos/fastlane/metadata/ios/km/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/km/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/km/description.txt diff --git a/mobile/fastlane/metadata/ios/km/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/km/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/km/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/km/keywords.txt diff --git a/mobile/fastlane/metadata/ios/km/name.txt b/mobile/apps/photos/fastlane/metadata/ios/km/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/km/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/km/name.txt diff --git a/mobile/fastlane/metadata/ios/km/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/km/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/km/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/km/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ko/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ko/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ko/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ko/description.txt diff --git a/mobile/fastlane/metadata/ios/ko/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ko/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ko/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ko/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ko/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ko/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ko/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ko/name.txt diff --git a/mobile/fastlane/metadata/ios/ko/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ko/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ko/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ko/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ku/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ku/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ku/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ku/description.txt diff --git a/mobile/fastlane/metadata/ios/ku/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ku/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ku/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ku/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ku/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ku/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ku/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ku/name.txt diff --git a/mobile/fastlane/metadata/ios/ku/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ku/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ku/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ku/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/lt/description.txt b/mobile/apps/photos/fastlane/metadata/ios/lt/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lt/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/lt/description.txt diff --git a/mobile/fastlane/metadata/ios/lt/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/lt/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lt/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/lt/keywords.txt diff --git a/mobile/fastlane/metadata/ios/lt/name.txt b/mobile/apps/photos/fastlane/metadata/ios/lt/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lt/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/lt/name.txt diff --git a/mobile/fastlane/metadata/ios/lt/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/lt/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lt/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/lt/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/lv/description.txt b/mobile/apps/photos/fastlane/metadata/ios/lv/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lv/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/lv/description.txt diff --git a/mobile/fastlane/metadata/ios/lv/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/lv/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lv/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/lv/keywords.txt diff --git a/mobile/fastlane/metadata/ios/lv/name.txt b/mobile/apps/photos/fastlane/metadata/ios/lv/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lv/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/lv/name.txt diff --git a/mobile/fastlane/metadata/ios/lv/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/lv/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/lv/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/lv/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ml/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ml/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ml/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ml/description.txt diff --git a/mobile/fastlane/metadata/ios/ml/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ml/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ml/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ml/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ml/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ml/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ml/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ml/name.txt diff --git a/mobile/fastlane/metadata/ios/ml/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ml/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ml/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ml/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/nl/description.txt b/mobile/apps/photos/fastlane/metadata/ios/nl/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/nl/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/nl/description.txt diff --git a/mobile/fastlane/metadata/ios/nl/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/nl/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/nl/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/nl/keywords.txt diff --git a/mobile/fastlane/metadata/ios/nl/name.txt b/mobile/apps/photos/fastlane/metadata/ios/nl/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/nl/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/nl/name.txt diff --git a/mobile/fastlane/metadata/ios/nl/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/nl/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/nl/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/nl/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/no/description.txt b/mobile/apps/photos/fastlane/metadata/ios/no/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/no/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/no/description.txt diff --git a/mobile/fastlane/metadata/ios/no/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/no/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/no/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/no/keywords.txt diff --git a/mobile/fastlane/metadata/ios/no/name.txt b/mobile/apps/photos/fastlane/metadata/ios/no/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/no/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/no/name.txt diff --git a/mobile/fastlane/metadata/ios/no/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/no/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/no/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/no/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/or/description.txt b/mobile/apps/photos/fastlane/metadata/ios/or/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/or/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/or/description.txt diff --git a/mobile/fastlane/metadata/ios/or/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/or/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/or/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/or/keywords.txt diff --git a/mobile/fastlane/metadata/ios/or/name.txt b/mobile/apps/photos/fastlane/metadata/ios/or/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/or/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/or/name.txt diff --git a/mobile/fastlane/metadata/ios/or/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/or/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/or/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/or/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/pl/description.txt b/mobile/apps/photos/fastlane/metadata/ios/pl/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pl/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/pl/description.txt diff --git a/mobile/fastlane/metadata/ios/pl/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/pl/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pl/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/pl/keywords.txt diff --git a/mobile/fastlane/metadata/ios/pl/name.txt b/mobile/apps/photos/fastlane/metadata/ios/pl/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pl/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/pl/name.txt diff --git a/mobile/fastlane/metadata/ios/pl/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/pl/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pl/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/pl/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/pt/description.txt b/mobile/apps/photos/fastlane/metadata/ios/pt/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt/description.txt diff --git a/mobile/fastlane/metadata/ios/pt/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/pt/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt/keywords.txt diff --git a/mobile/fastlane/metadata/ios/pt/name.txt b/mobile/apps/photos/fastlane/metadata/ios/pt/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt/name.txt diff --git a/mobile/fastlane/metadata/ios/pt/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/pt/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/pt_BR/description.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_BR/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_BR/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_BR/description.txt diff --git a/mobile/fastlane/metadata/ios/pt_BR/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_BR/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_BR/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_BR/keywords.txt diff --git a/mobile/fastlane/metadata/ios/pt_BR/name.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_BR/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_BR/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_BR/name.txt diff --git a/mobile/fastlane/metadata/ios/pt_BR/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_BR/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_BR/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_BR/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/pt_PT/description.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_PT/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_PT/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_PT/description.txt diff --git a/mobile/fastlane/metadata/ios/pt_PT/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_PT/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_PT/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_PT/keywords.txt diff --git a/mobile/fastlane/metadata/ios/pt_PT/name.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_PT/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_PT/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_PT/name.txt diff --git a/mobile/fastlane/metadata/ios/pt_PT/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/pt_PT/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/pt_PT/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/pt_PT/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ro/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ro/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ro/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ro/description.txt diff --git a/mobile/fastlane/metadata/ios/ro/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ro/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ro/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ro/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ro/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ro/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ro/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ro/name.txt diff --git a/mobile/fastlane/metadata/ios/ro/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ro/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ro/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ro/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ru/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ru/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ru/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ru/description.txt diff --git a/mobile/fastlane/metadata/ios/ru/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ru/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ru/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ru/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ru/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ru/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ru/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ru/name.txt diff --git a/mobile/fastlane/metadata/ios/ru/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ru/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ru/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ru/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/sl/description.txt b/mobile/apps/photos/fastlane/metadata/ios/sl/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sl/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/sl/description.txt diff --git a/mobile/fastlane/metadata/ios/sl/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/sl/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sl/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/sl/keywords.txt diff --git a/mobile/fastlane/metadata/ios/sl/name.txt b/mobile/apps/photos/fastlane/metadata/ios/sl/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sl/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/sl/name.txt diff --git a/mobile/fastlane/metadata/ios/sl/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/sl/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sl/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/sl/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/sr/description.txt b/mobile/apps/photos/fastlane/metadata/ios/sr/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sr/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/sr/description.txt diff --git a/mobile/fastlane/metadata/ios/sr/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/sr/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sr/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/sr/keywords.txt diff --git a/mobile/fastlane/metadata/ios/sr/name.txt b/mobile/apps/photos/fastlane/metadata/ios/sr/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sr/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/sr/name.txt diff --git a/mobile/fastlane/metadata/ios/sr/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/sr/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sr/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/sr/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/sv/description.txt b/mobile/apps/photos/fastlane/metadata/ios/sv/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sv/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/sv/description.txt diff --git a/mobile/fastlane/metadata/ios/sv/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/sv/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sv/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/sv/keywords.txt diff --git a/mobile/fastlane/metadata/ios/sv/name.txt b/mobile/apps/photos/fastlane/metadata/ios/sv/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sv/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/sv/name.txt diff --git a/mobile/fastlane/metadata/ios/sv/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/sv/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/sv/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/sv/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ta/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ta/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ta/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ta/description.txt diff --git a/mobile/fastlane/metadata/ios/ta/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ta/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ta/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ta/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ta/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ta/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ta/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ta/name.txt diff --git a/mobile/fastlane/metadata/ios/ta/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ta/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ta/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ta/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/te/description.txt b/mobile/apps/photos/fastlane/metadata/ios/te/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/te/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/te/description.txt diff --git a/mobile/fastlane/metadata/ios/te/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/te/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/te/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/te/keywords.txt diff --git a/mobile/fastlane/metadata/ios/te/name.txt b/mobile/apps/photos/fastlane/metadata/ios/te/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/te/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/te/name.txt diff --git a/mobile/fastlane/metadata/ios/te/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/te/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/te/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/te/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/th/description.txt b/mobile/apps/photos/fastlane/metadata/ios/th/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/th/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/th/description.txt diff --git a/mobile/fastlane/metadata/ios/th/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/th/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/th/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/th/keywords.txt diff --git a/mobile/fastlane/metadata/ios/th/name.txt b/mobile/apps/photos/fastlane/metadata/ios/th/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/th/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/th/name.txt diff --git a/mobile/fastlane/metadata/ios/th/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/th/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/th/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/th/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/ti/description.txt b/mobile/apps/photos/fastlane/metadata/ios/ti/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ti/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/ti/description.txt diff --git a/mobile/fastlane/metadata/ios/ti/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/ti/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ti/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/ti/keywords.txt diff --git a/mobile/fastlane/metadata/ios/ti/name.txt b/mobile/apps/photos/fastlane/metadata/ios/ti/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ti/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/ti/name.txt diff --git a/mobile/fastlane/metadata/ios/ti/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/ti/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/ti/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/ti/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/tr/description.txt b/mobile/apps/photos/fastlane/metadata/ios/tr/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/tr/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/tr/description.txt diff --git a/mobile/fastlane/metadata/ios/tr/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/tr/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/tr/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/tr/keywords.txt diff --git a/mobile/fastlane/metadata/ios/tr/name.txt b/mobile/apps/photos/fastlane/metadata/ios/tr/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/tr/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/tr/name.txt diff --git a/mobile/fastlane/metadata/ios/tr/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/tr/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/tr/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/tr/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/uk/description.txt b/mobile/apps/photos/fastlane/metadata/ios/uk/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/uk/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/uk/description.txt diff --git a/mobile/fastlane/metadata/ios/uk/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/uk/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/uk/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/uk/keywords.txt diff --git a/mobile/fastlane/metadata/ios/uk/name.txt b/mobile/apps/photos/fastlane/metadata/ios/uk/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/uk/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/uk/name.txt diff --git a/mobile/fastlane/metadata/ios/uk/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/uk/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/uk/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/uk/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/vi/description.txt b/mobile/apps/photos/fastlane/metadata/ios/vi/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/vi/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/vi/description.txt diff --git a/mobile/fastlane/metadata/ios/vi/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/vi/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/vi/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/vi/keywords.txt diff --git a/mobile/fastlane/metadata/ios/vi/name.txt b/mobile/apps/photos/fastlane/metadata/ios/vi/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/vi/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/vi/name.txt diff --git a/mobile/fastlane/metadata/ios/vi/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/vi/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/vi/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/vi/subtitle.txt diff --git a/mobile/fastlane/metadata/ios/zh/description.txt b/mobile/apps/photos/fastlane/metadata/ios/zh/description.txt similarity index 100% rename from mobile/fastlane/metadata/ios/zh/description.txt rename to mobile/apps/photos/fastlane/metadata/ios/zh/description.txt diff --git a/mobile/fastlane/metadata/ios/zh/keywords.txt b/mobile/apps/photos/fastlane/metadata/ios/zh/keywords.txt similarity index 100% rename from mobile/fastlane/metadata/ios/zh/keywords.txt rename to mobile/apps/photos/fastlane/metadata/ios/zh/keywords.txt diff --git a/mobile/fastlane/metadata/ios/zh/name.txt b/mobile/apps/photos/fastlane/metadata/ios/zh/name.txt similarity index 100% rename from mobile/fastlane/metadata/ios/zh/name.txt rename to mobile/apps/photos/fastlane/metadata/ios/zh/name.txt diff --git a/mobile/fastlane/metadata/ios/zh/subtitle.txt b/mobile/apps/photos/fastlane/metadata/ios/zh/subtitle.txt similarity index 100% rename from mobile/fastlane/metadata/ios/zh/subtitle.txt rename to mobile/apps/photos/fastlane/metadata/ios/zh/subtitle.txt diff --git a/mobile/fastlane/metadata/playstore/ar/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ar/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ar/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ar/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ar/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ar/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ar/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ar/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ar/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ar/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ar/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ar/title.txt diff --git a/mobile/fastlane/metadata/playstore/be/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/be/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/be/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/be/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/be/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/be/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/be/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/be/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/be/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/be/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/be/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/be/title.txt diff --git a/mobile/fastlane/metadata/playstore/bg/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/bg/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/bg/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/bg/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/bg/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/bg/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/bg/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/bg/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/bg/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/bg/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/bg/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/bg/title.txt diff --git a/mobile/fastlane/metadata/playstore/ca/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ca/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ca/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ca/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ca/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ca/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ca/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ca/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ca/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ca/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ca/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ca/title.txt diff --git a/mobile/fastlane/metadata/playstore/cs/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/cs/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/cs/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/cs/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/cs/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/cs/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/cs/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/cs/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/cs/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/cs/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/cs/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/cs/title.txt diff --git a/mobile/fastlane/metadata/playstore/da/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/da/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/da/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/da/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/da/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/da/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/da/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/da/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/da/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/da/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/da/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/da/title.txt diff --git a/mobile/fastlane/metadata/playstore/de/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/de/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/de/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/de/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/de/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/de/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/de/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/de/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/de/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/de/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/de/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/de/title.txt diff --git a/mobile/fastlane/metadata/playstore/el/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/el/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/el/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/el/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/el/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/el/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/el/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/el/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/el/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/el/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/el/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/el/title.txt diff --git a/mobile/fastlane/metadata/playstore/en-US/changelogs/443.txt b/mobile/apps/photos/fastlane/metadata/playstore/en-US/changelogs/443.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/changelogs/443.txt rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/changelogs/443.txt diff --git a/mobile/fastlane/metadata/playstore/en-US/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/en-US/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/en-US/images/featureGraphic.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/featureGraphic.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/featureGraphic.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/featureGraphic.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/icon.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/icon.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/icon.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/icon.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/1_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/1_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/1_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/1_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/2_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/2_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/2_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/2_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/3_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/3_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/3_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/3_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/4_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/4_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/4_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/4_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/5_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/5_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/5_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/5_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/6_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/6_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/6_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/6_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/7_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/7_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/phoneScreenshots/7_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/phoneScreenshots/7_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/sevenInchScreenshots/1_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/sevenInchScreenshots/1_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/sevenInchScreenshots/1_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/sevenInchScreenshots/1_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/images/tenInchScreenshots/1_en-US.png b/mobile/apps/photos/fastlane/metadata/playstore/en-US/images/tenInchScreenshots/1_en-US.png similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/images/tenInchScreenshots/1_en-US.png rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/images/tenInchScreenshots/1_en-US.png diff --git a/mobile/fastlane/metadata/playstore/en-US/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/en-US/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/en-US/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/en-US/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/en-US/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/en-US/title.txt diff --git a/mobile/fastlane/metadata/playstore/es/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/es/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/es/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/es/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/es/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/es/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/es/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/es/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/es/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/es/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/es/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/es/title.txt diff --git a/mobile/fastlane/metadata/playstore/et/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/et/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/et/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/et/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/et/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/et/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/et/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/et/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/et/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/et/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/et/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/et/title.txt diff --git a/mobile/fastlane/metadata/playstore/eu/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/eu/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/eu/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/eu/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/eu/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/eu/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/eu/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/eu/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/eu/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/eu/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/eu/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/eu/title.txt diff --git a/mobile/fastlane/metadata/playstore/fa/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/fa/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/fa/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/fa/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/fa/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/fa/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/fa/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/fa/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/fa/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/fa/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/fa/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/fa/title.txt diff --git a/mobile/fastlane/metadata/playstore/fr/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/fr/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/fr/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/fr/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/fr/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/fr/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/fr/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/fr/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/fr/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/fr/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/fr/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/fr/title.txt diff --git a/mobile/fastlane/metadata/playstore/gu/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/gu/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/gu/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/gu/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/gu/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/gu/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/gu/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/gu/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/gu/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/gu/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/gu/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/gu/title.txt diff --git a/mobile/fastlane/metadata/playstore/he/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/he/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/he/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/he/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/he/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/he/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/he/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/he/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/he/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/he/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/he/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/he/title.txt diff --git a/mobile/fastlane/metadata/playstore/hi/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/hi/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/hi/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/hi/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/hi/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/hi/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/hi/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/hi/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/hi/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/hi/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/hi/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/hi/title.txt diff --git a/mobile/fastlane/metadata/playstore/hu/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/hu/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/hu/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/hu/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/hu/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/hu/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/hu/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/hu/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/hu/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/hu/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/hu/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/hu/title.txt diff --git a/mobile/fastlane/metadata/playstore/id/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/id/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/id/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/id/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/id/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/id/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/id/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/id/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/id/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/id/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/id/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/id/title.txt diff --git a/mobile/fastlane/metadata/playstore/it/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/it/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/it/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/it/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/it/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/it/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/it/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/it/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/it/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/it/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/it/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/it/title.txt diff --git a/mobile/fastlane/metadata/playstore/ja/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ja/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ja/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ja/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ja/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ja/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ja/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ja/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ja/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ja/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ja/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ja/title.txt diff --git a/mobile/fastlane/metadata/playstore/km/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/km/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/km/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/km/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/km/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/km/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/km/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/km/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/km/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/km/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/km/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/km/title.txt diff --git a/mobile/fastlane/metadata/playstore/ko/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ko/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ko/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ko/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ko/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ko/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ko/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ko/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ko/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ko/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ko/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ko/title.txt diff --git a/mobile/fastlane/metadata/playstore/ku/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ku/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ku/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ku/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ku/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ku/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ku/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ku/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ku/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ku/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ku/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ku/title.txt diff --git a/mobile/fastlane/metadata/playstore/lt/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/lt/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/lt/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/lt/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/lt/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/lt/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/lt/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/lt/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/lt/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/lt/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/lt/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/lt/title.txt diff --git a/mobile/fastlane/metadata/playstore/lv/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/lv/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/lv/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/lv/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/lv/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/lv/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/lv/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/lv/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/lv/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/lv/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/lv/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/lv/title.txt diff --git a/mobile/fastlane/metadata/playstore/ml/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ml/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ml/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ml/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ml/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ml/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ml/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ml/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ml/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ml/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ml/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ml/title.txt diff --git a/mobile/fastlane/metadata/playstore/nl/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/nl/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/nl/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/nl/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/nl/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/nl/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/nl/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/nl/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/nl/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/nl/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/nl/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/nl/title.txt diff --git a/mobile/fastlane/metadata/playstore/no/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/no/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/no/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/no/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/no/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/no/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/no/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/no/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/no/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/no/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/no/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/no/title.txt diff --git a/mobile/fastlane/metadata/playstore/or/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/or/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/or/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/or/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/or/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/or/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/or/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/or/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/or/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/or/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/or/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/or/title.txt diff --git a/mobile/fastlane/metadata/playstore/pl/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pl/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pl/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pl/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/pl/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pl/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pl/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pl/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/pl/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/pl/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pl/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pl/title.txt diff --git a/mobile/fastlane/metadata/playstore/pt/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/pt/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/pt/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt/title.txt diff --git a/mobile/fastlane/metadata/playstore/pt_BR/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt_BR/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt_BR/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt_BR/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/pt_BR/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt_BR/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt_BR/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt_BR/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/pt_BR/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt_BR/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt_BR/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt_BR/title.txt diff --git a/mobile/fastlane/metadata/playstore/pt_PT/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt_PT/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt_PT/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt_PT/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/pt_PT/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt_PT/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt_PT/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt_PT/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/pt_PT/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/pt_PT/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/pt_PT/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/pt_PT/title.txt diff --git a/mobile/fastlane/metadata/playstore/ro/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ro/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ro/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ro/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ro/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ro/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ro/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ro/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ro/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ro/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ro/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ro/title.txt diff --git a/mobile/fastlane/metadata/playstore/ru/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ru/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ru/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ru/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ru/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ru/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ru/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ru/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ru/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ru/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ru/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ru/title.txt diff --git a/mobile/fastlane/metadata/playstore/sl/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/sl/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sl/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sl/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/sl/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/sl/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sl/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sl/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/sl/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/sl/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sl/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sl/title.txt diff --git a/mobile/fastlane/metadata/playstore/sr/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/sr/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sr/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sr/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/sr/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/sr/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sr/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sr/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/sr/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/sr/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sr/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sr/title.txt diff --git a/mobile/fastlane/metadata/playstore/sv/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/sv/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sv/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sv/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/sv/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/sv/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sv/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sv/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/sv/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/sv/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/sv/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/sv/title.txt diff --git a/mobile/fastlane/metadata/playstore/ta/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ta/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ta/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ta/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ta/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ta/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ta/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ta/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ta/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ta/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ta/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ta/title.txt diff --git a/mobile/fastlane/metadata/playstore/te/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/te/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/te/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/te/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/te/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/te/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/te/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/te/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/te/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/te/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/te/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/te/title.txt diff --git a/mobile/fastlane/metadata/playstore/th/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/th/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/th/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/th/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/th/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/th/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/th/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/th/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/th/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/th/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/th/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/th/title.txt diff --git a/mobile/fastlane/metadata/playstore/ti/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ti/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ti/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ti/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/ti/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/ti/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ti/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ti/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/ti/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/ti/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/ti/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/ti/title.txt diff --git a/mobile/fastlane/metadata/playstore/tr/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/tr/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/tr/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/tr/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/tr/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/tr/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/tr/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/tr/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/tr/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/tr/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/tr/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/tr/title.txt diff --git a/mobile/fastlane/metadata/playstore/uk/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/uk/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/uk/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/uk/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/uk/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/uk/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/uk/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/uk/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/uk/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/uk/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/uk/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/uk/title.txt diff --git a/mobile/fastlane/metadata/playstore/vi/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/vi/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/vi/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/vi/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/vi/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/vi/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/vi/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/vi/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/vi/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/vi/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/vi/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/vi/title.txt diff --git a/mobile/fastlane/metadata/playstore/zh/full_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/zh/full_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/zh/full_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/zh/full_description.txt diff --git a/mobile/fastlane/metadata/playstore/zh/short_description.txt b/mobile/apps/photos/fastlane/metadata/playstore/zh/short_description.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/zh/short_description.txt rename to mobile/apps/photos/fastlane/metadata/playstore/zh/short_description.txt diff --git a/mobile/fastlane/metadata/playstore/zh/title.txt b/mobile/apps/photos/fastlane/metadata/playstore/zh/title.txt similarity index 100% rename from mobile/fastlane/metadata/playstore/zh/title.txt rename to mobile/apps/photos/fastlane/metadata/playstore/zh/title.txt diff --git a/mobile/fonts/Inter-Bold.ttf b/mobile/apps/photos/fonts/Inter-Bold.ttf similarity index 100% rename from mobile/fonts/Inter-Bold.ttf rename to mobile/apps/photos/fonts/Inter-Bold.ttf diff --git a/mobile/fonts/Inter-Light.ttf b/mobile/apps/photos/fonts/Inter-Light.ttf similarity index 100% rename from mobile/fonts/Inter-Light.ttf rename to mobile/apps/photos/fonts/Inter-Light.ttf diff --git a/mobile/fonts/Inter-Medium.ttf b/mobile/apps/photos/fonts/Inter-Medium.ttf similarity index 100% rename from mobile/fonts/Inter-Medium.ttf rename to mobile/apps/photos/fonts/Inter-Medium.ttf diff --git a/mobile/fonts/Inter-Regular.ttf b/mobile/apps/photos/fonts/Inter-Regular.ttf similarity index 100% rename from mobile/fonts/Inter-Regular.ttf rename to mobile/apps/photos/fonts/Inter-Regular.ttf diff --git a/mobile/fonts/Inter-SemiBold.ttf b/mobile/apps/photos/fonts/Inter-SemiBold.ttf similarity index 100% rename from mobile/fonts/Inter-SemiBold.ttf rename to mobile/apps/photos/fonts/Inter-SemiBold.ttf diff --git a/mobile/fonts/Montserrat-Bold.ttf b/mobile/apps/photos/fonts/Montserrat-Bold.ttf similarity index 100% rename from mobile/fonts/Montserrat-Bold.ttf rename to mobile/apps/photos/fonts/Montserrat-Bold.ttf diff --git a/mobile/hooks/pre-commit b/mobile/apps/photos/hooks/pre-commit similarity index 100% rename from mobile/hooks/pre-commit rename to mobile/apps/photos/hooks/pre-commit diff --git a/mobile/hooks/pre-commit-fdroid b/mobile/apps/photos/hooks/pre-commit-fdroid similarity index 100% rename from mobile/hooks/pre-commit-fdroid rename to mobile/apps/photos/hooks/pre-commit-fdroid diff --git a/mobile/integration_test/app_init_test.dart b/mobile/apps/photos/integration_test/app_init_test.dart similarity index 100% rename from mobile/integration_test/app_init_test.dart rename to mobile/apps/photos/integration_test/app_init_test.dart diff --git a/mobile/integration_test/home_gallery_scroll_test.dart b/mobile/apps/photos/integration_test/home_gallery_scroll_test.dart similarity index 100% rename from mobile/integration_test/home_gallery_scroll_test.dart rename to mobile/apps/photos/integration_test/home_gallery_scroll_test.dart diff --git a/mobile/ios/.gitignore b/mobile/apps/photos/ios/.gitignore similarity index 100% rename from mobile/ios/.gitignore rename to mobile/apps/photos/ios/.gitignore diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/AccentColor.colorset/Contents.json rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/AlbumsWidgetDefault.png b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/AlbumsWidgetDefault.png similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/AlbumsWidgetDefault.png rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/AlbumsWidgetDefault.png diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/Contents.json b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/Contents.json similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/Contents.json rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetDefault.imageset/Contents.json diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/AlbumsWidgetPreview.png b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/AlbumsWidgetPreview.png similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/AlbumsWidgetPreview.png rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/AlbumsWidgetPreview.png diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/Contents.json b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/Contents.json similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/Contents.json rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/AlbumsWidgetPreview.imageset/Contents.json diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/Contents.json b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/Contents.json similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/Contents.json rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/Contents.json diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/IconGreen.appiconset/Contents.json b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/IconGreen.appiconset/Contents.json similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/IconGreen.appiconset/Contents.json rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/IconGreen.appiconset/Contents.json diff --git a/mobile/ios/EnteAlbumWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json similarity index 100% rename from mobile/ios/EnteAlbumWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json rename to mobile/apps/photos/ios/EnteAlbumWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json diff --git a/mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift b/mobile/apps/photos/ios/EnteAlbumWidget/EnteAlbumWidget.swift similarity index 100% rename from mobile/ios/EnteAlbumWidget/EnteAlbumWidget.swift rename to mobile/apps/photos/ios/EnteAlbumWidget/EnteAlbumWidget.swift diff --git a/mobile/ios/EnteAlbumWidget/EnteAlbumWidgetBundle.swift b/mobile/apps/photos/ios/EnteAlbumWidget/EnteAlbumWidgetBundle.swift similarity index 100% rename from mobile/ios/EnteAlbumWidget/EnteAlbumWidgetBundle.swift rename to mobile/apps/photos/ios/EnteAlbumWidget/EnteAlbumWidgetBundle.swift diff --git a/mobile/ios/EnteAlbumWidget/Info.plist b/mobile/apps/photos/ios/EnteAlbumWidget/Info.plist similarity index 100% rename from mobile/ios/EnteAlbumWidget/Info.plist rename to mobile/apps/photos/ios/EnteAlbumWidget/Info.plist diff --git a/mobile/ios/EnteAlbumWidgetExtension.entitlements b/mobile/apps/photos/ios/EnteAlbumWidgetExtension.entitlements similarity index 100% rename from mobile/ios/EnteAlbumWidgetExtension.entitlements rename to mobile/apps/photos/ios/EnteAlbumWidgetExtension.entitlements diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/AccentColor.colorset/Contents.json rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/Contents.json b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/Contents.json similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/Contents.json rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/Contents.json diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/IconGreen.appiconset/Contents.json b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/IconGreen.appiconset/Contents.json similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/IconGreen.appiconset/Contents.json rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/IconGreen.appiconset/Contents.json diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/Contents.json b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/Contents.json similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/Contents.json rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/Contents.json diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/MemoriesWidgetDefault.png b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/MemoriesWidgetDefault.png similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/MemoriesWidgetDefault.png rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetDefault.imageset/MemoriesWidgetDefault.png diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/Contents.json b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/Contents.json similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/Contents.json rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/Contents.json diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/MemoriesWidgetPreview.png b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/MemoriesWidgetPreview.png similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/MemoriesWidgetPreview.png rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/MemoriesWidgetPreview.imageset/MemoriesWidgetPreview.png diff --git a/mobile/ios/EnteMemoryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json similarity index 100% rename from mobile/ios/EnteMemoryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json rename to mobile/apps/photos/ios/EnteMemoryWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json diff --git a/mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift b/mobile/apps/photos/ios/EnteMemoryWidget/EnteMemoryWidget.swift similarity index 100% rename from mobile/ios/EnteMemoryWidget/EnteMemoryWidget.swift rename to mobile/apps/photos/ios/EnteMemoryWidget/EnteMemoryWidget.swift diff --git a/mobile/ios/EnteMemoryWidget/EnteMemoryWidgetBundle.swift b/mobile/apps/photos/ios/EnteMemoryWidget/EnteMemoryWidgetBundle.swift similarity index 100% rename from mobile/ios/EnteMemoryWidget/EnteMemoryWidgetBundle.swift rename to mobile/apps/photos/ios/EnteMemoryWidget/EnteMemoryWidgetBundle.swift diff --git a/mobile/ios/EnteMemoryWidget/Info.plist b/mobile/apps/photos/ios/EnteMemoryWidget/Info.plist similarity index 100% rename from mobile/ios/EnteMemoryWidget/Info.plist rename to mobile/apps/photos/ios/EnteMemoryWidget/Info.plist diff --git a/mobile/ios/EnteMemoryWidgetExtension.entitlements b/mobile/apps/photos/ios/EnteMemoryWidgetExtension.entitlements similarity index 100% rename from mobile/ios/EnteMemoryWidgetExtension.entitlements rename to mobile/apps/photos/ios/EnteMemoryWidgetExtension.entitlements diff --git a/mobile/ios/EnteMemoryWidgetExtensionDebug.entitlements b/mobile/apps/photos/ios/EnteMemoryWidgetExtensionDebug.entitlements similarity index 100% rename from mobile/ios/EnteMemoryWidgetExtensionDebug.entitlements rename to mobile/apps/photos/ios/EnteMemoryWidgetExtensionDebug.entitlements diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/AccentColor.colorset/Contents.json b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/AccentColor.colorset/Contents.json rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/Contents.json b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/Contents.json similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/Contents.json rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/Contents.json diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/IconGreen.appiconset/Contents.json b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/IconGreen.appiconset/Contents.json similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/IconGreen.appiconset/Contents.json rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/IconGreen.appiconset/Contents.json diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/Contents.json b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/Contents.json similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/Contents.json rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/Contents.json diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/PeopleWidgetDefault.png b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/PeopleWidgetDefault.png similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/PeopleWidgetDefault.png rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetDefault.imageset/PeopleWidgetDefault.png diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/Contents.json b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/Contents.json similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/Contents.json rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/Contents.json diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/PeopleWidgetPreview.png b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/PeopleWidgetPreview.png similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/PeopleWidgetPreview.png rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/PeopleWidgetPreview.imageset/PeopleWidgetPreview.png diff --git a/mobile/ios/EntePeopleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json b/mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json similarity index 100% rename from mobile/ios/EntePeopleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json rename to mobile/apps/photos/ios/EntePeopleWidget/Assets.xcassets/WidgetBackground.colorset/Contents.json diff --git a/mobile/ios/EntePeopleWidget/EntePeopleWidget.swift b/mobile/apps/photos/ios/EntePeopleWidget/EntePeopleWidget.swift similarity index 100% rename from mobile/ios/EntePeopleWidget/EntePeopleWidget.swift rename to mobile/apps/photos/ios/EntePeopleWidget/EntePeopleWidget.swift diff --git a/mobile/ios/EntePeopleWidget/EntePeopleWidgetBundle.swift b/mobile/apps/photos/ios/EntePeopleWidget/EntePeopleWidgetBundle.swift similarity index 100% rename from mobile/ios/EntePeopleWidget/EntePeopleWidgetBundle.swift rename to mobile/apps/photos/ios/EntePeopleWidget/EntePeopleWidgetBundle.swift diff --git a/mobile/ios/EntePeopleWidget/Info.plist b/mobile/apps/photos/ios/EntePeopleWidget/Info.plist similarity index 100% rename from mobile/ios/EntePeopleWidget/Info.plist rename to mobile/apps/photos/ios/EntePeopleWidget/Info.plist diff --git a/mobile/ios/EntePeopleWidgetExtension.entitlements b/mobile/apps/photos/ios/EntePeopleWidgetExtension.entitlements similarity index 100% rename from mobile/ios/EntePeopleWidgetExtension.entitlements rename to mobile/apps/photos/ios/EntePeopleWidgetExtension.entitlements diff --git a/mobile/ios/Flutter/AppFrameworkInfo.plist b/mobile/apps/photos/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from mobile/ios/Flutter/AppFrameworkInfo.plist rename to mobile/apps/photos/ios/Flutter/AppFrameworkInfo.plist diff --git a/mobile/ios/Flutter/Debug.xcconfig b/mobile/apps/photos/ios/Flutter/Debug.xcconfig similarity index 100% rename from mobile/ios/Flutter/Debug.xcconfig rename to mobile/apps/photos/ios/Flutter/Debug.xcconfig diff --git a/mobile/ios/Flutter/Release.xcconfig b/mobile/apps/photos/ios/Flutter/Release.xcconfig similarity index 100% rename from mobile/ios/Flutter/Release.xcconfig rename to mobile/apps/photos/ios/Flutter/Release.xcconfig diff --git a/mobile/ios/GoogleService-Info.plist b/mobile/apps/photos/ios/GoogleService-Info.plist similarity index 100% rename from mobile/ios/GoogleService-Info.plist rename to mobile/apps/photos/ios/GoogleService-Info.plist diff --git a/mobile/ios/Podfile b/mobile/apps/photos/ios/Podfile similarity index 100% rename from mobile/ios/Podfile rename to mobile/apps/photos/ios/Podfile diff --git a/mobile/ios/Podfile.lock b/mobile/apps/photos/ios/Podfile.lock similarity index 100% rename from mobile/ios/Podfile.lock rename to mobile/apps/photos/ios/Podfile.lock diff --git a/mobile/ios/Runner.xcodeproj/project.pbxproj b/mobile/apps/photos/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from mobile/ios/Runner.xcodeproj/project.pbxproj rename to mobile/apps/photos/ios/Runner.xcodeproj/project.pbxproj diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/apps/photos/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from mobile/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to mobile/apps/photos/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/photos/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from mobile/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mobile/apps/photos/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/apps/photos/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from mobile/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to mobile/apps/photos/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/mobile/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/apps/photos/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from mobile/ios/Runner.xcworkspace/contents.xcworkspacedata rename to mobile/apps/photos/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/photos/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from mobile/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mobile/apps/photos/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/mobile/ios/Runner/AppDelegate.swift b/mobile/apps/photos/ios/Runner/AppDelegate.swift similarity index 100% rename from mobile/ios/Runner/AppDelegate.swift rename to mobile/apps/photos/ios/Runner/AppDelegate.swift diff --git a/mobile/ios/Runner/Assets.xcassets/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkAny.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkDark.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconDark.appiconset/IconDarkTinted.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenAny.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenDark.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconGreen.appiconset/IconGreenTinted.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightAny.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightDark.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconLight.appiconset/IconLightTinted.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGAny.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGDark.png diff --git a/mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGTinted.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGTinted.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGTinted.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/IconOG.appiconset/IconOGTinted.png diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from mobile/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to mobile/apps/photos/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/apps/photos/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from mobile/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to mobile/apps/photos/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/mobile/ios/Runner/Base.lproj/Main.storyboard b/mobile/apps/photos/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from mobile/ios/Runner/Base.lproj/Main.storyboard rename to mobile/apps/photos/ios/Runner/Base.lproj/Main.storyboard diff --git a/mobile/ios/Runner/Info.plist b/mobile/apps/photos/ios/Runner/Info.plist similarity index 100% rename from mobile/ios/Runner/Info.plist rename to mobile/apps/photos/ios/Runner/Info.plist diff --git a/mobile/ios/Runner/Runner-Bridging-Header.h b/mobile/apps/photos/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from mobile/ios/Runner/Runner-Bridging-Header.h rename to mobile/apps/photos/ios/Runner/Runner-Bridging-Header.h diff --git a/mobile/ios/Runner/Runner.entitlements b/mobile/apps/photos/ios/Runner/Runner.entitlements similarity index 100% rename from mobile/ios/Runner/Runner.entitlements rename to mobile/apps/photos/ios/Runner/Runner.entitlements diff --git a/mobile/l10n.yaml b/mobile/apps/photos/l10n.yaml similarity index 100% rename from mobile/l10n.yaml rename to mobile/apps/photos/l10n.yaml diff --git a/mobile/lib/app.dart b/mobile/apps/photos/lib/app.dart similarity index 100% rename from mobile/lib/app.dart rename to mobile/apps/photos/lib/app.dart diff --git a/mobile/lib/core/cache/image_cache.dart b/mobile/apps/photos/lib/core/cache/image_cache.dart similarity index 100% rename from mobile/lib/core/cache/image_cache.dart rename to mobile/apps/photos/lib/core/cache/image_cache.dart diff --git a/mobile/lib/core/cache/lru_map.dart b/mobile/apps/photos/lib/core/cache/lru_map.dart similarity index 100% rename from mobile/lib/core/cache/lru_map.dart rename to mobile/apps/photos/lib/core/cache/lru_map.dart diff --git a/mobile/lib/core/cache/thumbnail_in_memory_cache.dart b/mobile/apps/photos/lib/core/cache/thumbnail_in_memory_cache.dart similarity index 100% rename from mobile/lib/core/cache/thumbnail_in_memory_cache.dart rename to mobile/apps/photos/lib/core/cache/thumbnail_in_memory_cache.dart diff --git a/mobile/lib/core/cache/video_cache_manager.dart b/mobile/apps/photos/lib/core/cache/video_cache_manager.dart similarity index 100% rename from mobile/lib/core/cache/video_cache_manager.dart rename to mobile/apps/photos/lib/core/cache/video_cache_manager.dart diff --git a/mobile/lib/core/configuration.dart b/mobile/apps/photos/lib/core/configuration.dart similarity index 100% rename from mobile/lib/core/configuration.dart rename to mobile/apps/photos/lib/core/configuration.dart diff --git a/mobile/lib/core/constants.dart b/mobile/apps/photos/lib/core/constants.dart similarity index 100% rename from mobile/lib/core/constants.dart rename to mobile/apps/photos/lib/core/constants.dart diff --git a/mobile/lib/core/error-reporting/isolate_logging.dart b/mobile/apps/photos/lib/core/error-reporting/isolate_logging.dart similarity index 100% rename from mobile/lib/core/error-reporting/isolate_logging.dart rename to mobile/apps/photos/lib/core/error-reporting/isolate_logging.dart diff --git a/mobile/lib/core/error-reporting/super_logging.dart b/mobile/apps/photos/lib/core/error-reporting/super_logging.dart similarity index 100% rename from mobile/lib/core/error-reporting/super_logging.dart rename to mobile/apps/photos/lib/core/error-reporting/super_logging.dart diff --git a/mobile/lib/core/error-reporting/tunneled_transport.dart b/mobile/apps/photos/lib/core/error-reporting/tunneled_transport.dart similarity index 100% rename from mobile/lib/core/error-reporting/tunneled_transport.dart rename to mobile/apps/photos/lib/core/error-reporting/tunneled_transport.dart diff --git a/mobile/lib/core/errors.dart b/mobile/apps/photos/lib/core/errors.dart similarity index 100% rename from mobile/lib/core/errors.dart rename to mobile/apps/photos/lib/core/errors.dart diff --git a/mobile/lib/core/event_bus.dart b/mobile/apps/photos/lib/core/event_bus.dart similarity index 100% rename from mobile/lib/core/event_bus.dart rename to mobile/apps/photos/lib/core/event_bus.dart diff --git a/mobile/lib/core/network/ente_interceptor.dart b/mobile/apps/photos/lib/core/network/ente_interceptor.dart similarity index 100% rename from mobile/lib/core/network/ente_interceptor.dart rename to mobile/apps/photos/lib/core/network/ente_interceptor.dart diff --git a/mobile/lib/core/network/network.dart b/mobile/apps/photos/lib/core/network/network.dart similarity index 100% rename from mobile/lib/core/network/network.dart rename to mobile/apps/photos/lib/core/network/network.dart diff --git a/mobile/lib/data/holidays.dart b/mobile/apps/photos/lib/data/holidays.dart similarity index 100% rename from mobile/lib/data/holidays.dart rename to mobile/apps/photos/lib/data/holidays.dart diff --git a/mobile/lib/data/months.dart b/mobile/apps/photos/lib/data/months.dart similarity index 100% rename from mobile/lib/data/months.dart rename to mobile/apps/photos/lib/data/months.dart diff --git a/mobile/lib/data/years.dart b/mobile/apps/photos/lib/data/years.dart similarity index 100% rename from mobile/lib/data/years.dart rename to mobile/apps/photos/lib/data/years.dart diff --git a/mobile/lib/db/collections_db.dart b/mobile/apps/photos/lib/db/collections_db.dart similarity index 100% rename from mobile/lib/db/collections_db.dart rename to mobile/apps/photos/lib/db/collections_db.dart diff --git a/mobile/lib/db/common/base.dart b/mobile/apps/photos/lib/db/common/base.dart similarity index 100% rename from mobile/lib/db/common/base.dart rename to mobile/apps/photos/lib/db/common/base.dart diff --git a/mobile/lib/db/common/conflict_algo.dart b/mobile/apps/photos/lib/db/common/conflict_algo.dart similarity index 100% rename from mobile/lib/db/common/conflict_algo.dart rename to mobile/apps/photos/lib/db/common/conflict_algo.dart diff --git a/mobile/lib/db/device_files_db.dart b/mobile/apps/photos/lib/db/device_files_db.dart similarity index 100% rename from mobile/lib/db/device_files_db.dart rename to mobile/apps/photos/lib/db/device_files_db.dart diff --git a/mobile/lib/db/entities_db.dart b/mobile/apps/photos/lib/db/entities_db.dart similarity index 100% rename from mobile/lib/db/entities_db.dart rename to mobile/apps/photos/lib/db/entities_db.dart diff --git a/mobile/lib/db/file_updation_db.dart b/mobile/apps/photos/lib/db/file_updation_db.dart similarity index 100% rename from mobile/lib/db/file_updation_db.dart rename to mobile/apps/photos/lib/db/file_updation_db.dart diff --git a/mobile/lib/db/files_db.dart b/mobile/apps/photos/lib/db/files_db.dart similarity index 100% rename from mobile/lib/db/files_db.dart rename to mobile/apps/photos/lib/db/files_db.dart diff --git a/mobile/lib/db/ignored_files_db.dart b/mobile/apps/photos/lib/db/ignored_files_db.dart similarity index 100% rename from mobile/lib/db/ignored_files_db.dart rename to mobile/apps/photos/lib/db/ignored_files_db.dart diff --git a/mobile/lib/db/memories_db.dart b/mobile/apps/photos/lib/db/memories_db.dart similarity index 100% rename from mobile/lib/db/memories_db.dart rename to mobile/apps/photos/lib/db/memories_db.dart diff --git a/mobile/lib/db/ml/base.dart b/mobile/apps/photos/lib/db/ml/base.dart similarity index 100% rename from mobile/lib/db/ml/base.dart rename to mobile/apps/photos/lib/db/ml/base.dart diff --git a/mobile/lib/db/ml/db.dart b/mobile/apps/photos/lib/db/ml/db.dart similarity index 100% rename from mobile/lib/db/ml/db.dart rename to mobile/apps/photos/lib/db/ml/db.dart diff --git a/mobile/lib/db/ml/db_model_mappers.dart b/mobile/apps/photos/lib/db/ml/db_model_mappers.dart similarity index 100% rename from mobile/lib/db/ml/db_model_mappers.dart rename to mobile/apps/photos/lib/db/ml/db_model_mappers.dart diff --git a/mobile/lib/db/ml/filedata.dart b/mobile/apps/photos/lib/db/ml/filedata.dart similarity index 100% rename from mobile/lib/db/ml/filedata.dart rename to mobile/apps/photos/lib/db/ml/filedata.dart diff --git a/mobile/lib/db/ml/schema.dart b/mobile/apps/photos/lib/db/ml/schema.dart similarity index 100% rename from mobile/lib/db/ml/schema.dart rename to mobile/apps/photos/lib/db/ml/schema.dart diff --git a/mobile/lib/db/trash_db.dart b/mobile/apps/photos/lib/db/trash_db.dart similarity index 100% rename from mobile/lib/db/trash_db.dart rename to mobile/apps/photos/lib/db/trash_db.dart diff --git a/mobile/lib/db/upload_locks_db.dart b/mobile/apps/photos/lib/db/upload_locks_db.dart similarity index 100% rename from mobile/lib/db/upload_locks_db.dart rename to mobile/apps/photos/lib/db/upload_locks_db.dart diff --git a/mobile/lib/emergency/emergency_page.dart b/mobile/apps/photos/lib/emergency/emergency_page.dart similarity index 100% rename from mobile/lib/emergency/emergency_page.dart rename to mobile/apps/photos/lib/emergency/emergency_page.dart diff --git a/mobile/lib/emergency/emergency_service.dart b/mobile/apps/photos/lib/emergency/emergency_service.dart similarity index 100% rename from mobile/lib/emergency/emergency_service.dart rename to mobile/apps/photos/lib/emergency/emergency_service.dart diff --git a/mobile/lib/emergency/model.dart b/mobile/apps/photos/lib/emergency/model.dart similarity index 100% rename from mobile/lib/emergency/model.dart rename to mobile/apps/photos/lib/emergency/model.dart diff --git a/mobile/lib/emergency/other_contact_page.dart b/mobile/apps/photos/lib/emergency/other_contact_page.dart similarity index 100% rename from mobile/lib/emergency/other_contact_page.dart rename to mobile/apps/photos/lib/emergency/other_contact_page.dart diff --git a/mobile/lib/emergency/recover_others_account.dart b/mobile/apps/photos/lib/emergency/recover_others_account.dart similarity index 100% rename from mobile/lib/emergency/recover_others_account.dart rename to mobile/apps/photos/lib/emergency/recover_others_account.dart diff --git a/mobile/lib/emergency/select_contact_page.dart b/mobile/apps/photos/lib/emergency/select_contact_page.dart similarity index 100% rename from mobile/lib/emergency/select_contact_page.dart rename to mobile/apps/photos/lib/emergency/select_contact_page.dart diff --git a/mobile/lib/ente_theme_data.dart b/mobile/apps/photos/lib/ente_theme_data.dart similarity index 100% rename from mobile/lib/ente_theme_data.dart rename to mobile/apps/photos/lib/ente_theme_data.dart diff --git a/mobile/lib/events/account_configured_event.dart b/mobile/apps/photos/lib/events/account_configured_event.dart similarity index 100% rename from mobile/lib/events/account_configured_event.dart rename to mobile/apps/photos/lib/events/account_configured_event.dart diff --git a/mobile/lib/events/album_sort_order_change_event.dart b/mobile/apps/photos/lib/events/album_sort_order_change_event.dart similarity index 100% rename from mobile/lib/events/album_sort_order_change_event.dart rename to mobile/apps/photos/lib/events/album_sort_order_change_event.dart diff --git a/mobile/lib/events/backup_folders_updated_event.dart b/mobile/apps/photos/lib/events/backup_folders_updated_event.dart similarity index 100% rename from mobile/lib/events/backup_folders_updated_event.dart rename to mobile/apps/photos/lib/events/backup_folders_updated_event.dart diff --git a/mobile/lib/events/backup_updated_event.dart b/mobile/apps/photos/lib/events/backup_updated_event.dart similarity index 100% rename from mobile/lib/events/backup_updated_event.dart rename to mobile/apps/photos/lib/events/backup_updated_event.dart diff --git a/mobile/lib/events/clear_album_selections_event.dart b/mobile/apps/photos/lib/events/clear_album_selections_event.dart similarity index 100% rename from mobile/lib/events/clear_album_selections_event.dart rename to mobile/apps/photos/lib/events/clear_album_selections_event.dart diff --git a/mobile/lib/events/clear_and_unfocus_search_bar_event.dart b/mobile/apps/photos/lib/events/clear_and_unfocus_search_bar_event.dart similarity index 100% rename from mobile/lib/events/clear_and_unfocus_search_bar_event.dart rename to mobile/apps/photos/lib/events/clear_and_unfocus_search_bar_event.dart diff --git a/mobile/lib/events/clear_selections_event.dart b/mobile/apps/photos/lib/events/clear_selections_event.dart similarity index 100% rename from mobile/lib/events/clear_selections_event.dart rename to mobile/apps/photos/lib/events/clear_selections_event.dart diff --git a/mobile/lib/events/collection_meta_event.dart b/mobile/apps/photos/lib/events/collection_meta_event.dart similarity index 100% rename from mobile/lib/events/collection_meta_event.dart rename to mobile/apps/photos/lib/events/collection_meta_event.dart diff --git a/mobile/lib/events/collection_updated_event.dart b/mobile/apps/photos/lib/events/collection_updated_event.dart similarity index 100% rename from mobile/lib/events/collection_updated_event.dart rename to mobile/apps/photos/lib/events/collection_updated_event.dart diff --git a/mobile/lib/events/compute_control_event.dart b/mobile/apps/photos/lib/events/compute_control_event.dart similarity index 100% rename from mobile/lib/events/compute_control_event.dart rename to mobile/apps/photos/lib/events/compute_control_event.dart diff --git a/mobile/lib/events/create_new_album_event.dart b/mobile/apps/photos/lib/events/create_new_album_event.dart similarity index 100% rename from mobile/lib/events/create_new_album_event.dart rename to mobile/apps/photos/lib/events/create_new_album_event.dart diff --git a/mobile/lib/events/details_sheet_event.dart b/mobile/apps/photos/lib/events/details_sheet_event.dart similarity index 100% rename from mobile/lib/events/details_sheet_event.dart rename to mobile/apps/photos/lib/events/details_sheet_event.dart diff --git a/mobile/lib/events/diff_sync_complete_event.dart b/mobile/apps/photos/lib/events/diff_sync_complete_event.dart similarity index 100% rename from mobile/lib/events/diff_sync_complete_event.dart rename to mobile/apps/photos/lib/events/diff_sync_complete_event.dart diff --git a/mobile/lib/events/embedding_updated_event.dart b/mobile/apps/photos/lib/events/embedding_updated_event.dart similarity index 100% rename from mobile/lib/events/embedding_updated_event.dart rename to mobile/apps/photos/lib/events/embedding_updated_event.dart diff --git a/mobile/lib/events/endpoint_updated_event.dart b/mobile/apps/photos/lib/events/endpoint_updated_event.dart similarity index 100% rename from mobile/lib/events/endpoint_updated_event.dart rename to mobile/apps/photos/lib/events/endpoint_updated_event.dart diff --git a/mobile/lib/events/event.dart b/mobile/apps/photos/lib/events/event.dart similarity index 100% rename from mobile/lib/events/event.dart rename to mobile/apps/photos/lib/events/event.dart diff --git a/mobile/lib/events/favorites_service_init_complete_event.dart b/mobile/apps/photos/lib/events/favorites_service_init_complete_event.dart similarity index 100% rename from mobile/lib/events/favorites_service_init_complete_event.dart rename to mobile/apps/photos/lib/events/favorites_service_init_complete_event.dart diff --git a/mobile/lib/events/file_caption_updated_event.dart b/mobile/apps/photos/lib/events/file_caption_updated_event.dart similarity index 100% rename from mobile/lib/events/file_caption_updated_event.dart rename to mobile/apps/photos/lib/events/file_caption_updated_event.dart diff --git a/mobile/lib/events/file_uploaded_event.dart b/mobile/apps/photos/lib/events/file_uploaded_event.dart similarity index 100% rename from mobile/lib/events/file_uploaded_event.dart rename to mobile/apps/photos/lib/events/file_uploaded_event.dart diff --git a/mobile/lib/events/files_updated_event.dart b/mobile/apps/photos/lib/events/files_updated_event.dart similarity index 100% rename from mobile/lib/events/files_updated_event.dart rename to mobile/apps/photos/lib/events/files_updated_event.dart diff --git a/mobile/lib/events/force_reload_home_gallery_event.dart b/mobile/apps/photos/lib/events/force_reload_home_gallery_event.dart similarity index 100% rename from mobile/lib/events/force_reload_home_gallery_event.dart rename to mobile/apps/photos/lib/events/force_reload_home_gallery_event.dart diff --git a/mobile/lib/events/force_reload_trash_page_event.dart b/mobile/apps/photos/lib/events/force_reload_trash_page_event.dart similarity index 100% rename from mobile/lib/events/force_reload_trash_page_event.dart rename to mobile/apps/photos/lib/events/force_reload_trash_page_event.dart diff --git a/mobile/lib/events/guest_view_event.dart b/mobile/apps/photos/lib/events/guest_view_event.dart similarity index 100% rename from mobile/lib/events/guest_view_event.dart rename to mobile/apps/photos/lib/events/guest_view_event.dart diff --git a/mobile/lib/events/hide_shared_items_from_home_gallery_event.dart b/mobile/apps/photos/lib/events/hide_shared_items_from_home_gallery_event.dart similarity index 100% rename from mobile/lib/events/hide_shared_items_from_home_gallery_event.dart rename to mobile/apps/photos/lib/events/hide_shared_items_from_home_gallery_event.dart diff --git a/mobile/lib/events/local_import_progress.dart b/mobile/apps/photos/lib/events/local_import_progress.dart similarity index 100% rename from mobile/lib/events/local_import_progress.dart rename to mobile/apps/photos/lib/events/local_import_progress.dart diff --git a/mobile/lib/events/local_photos_updated_event.dart b/mobile/apps/photos/lib/events/local_photos_updated_event.dart similarity index 100% rename from mobile/lib/events/local_photos_updated_event.dart rename to mobile/apps/photos/lib/events/local_photos_updated_event.dart diff --git a/mobile/lib/events/location_tag_updated_event.dart b/mobile/apps/photos/lib/events/location_tag_updated_event.dart similarity index 100% rename from mobile/lib/events/location_tag_updated_event.dart rename to mobile/apps/photos/lib/events/location_tag_updated_event.dart diff --git a/mobile/lib/events/magic_cache_updated_event.dart b/mobile/apps/photos/lib/events/magic_cache_updated_event.dart similarity index 100% rename from mobile/lib/events/magic_cache_updated_event.dart rename to mobile/apps/photos/lib/events/magic_cache_updated_event.dart diff --git a/mobile/lib/events/magic_sort_change_event.dart b/mobile/apps/photos/lib/events/magic_sort_change_event.dart similarity index 100% rename from mobile/lib/events/magic_sort_change_event.dart rename to mobile/apps/photos/lib/events/magic_sort_change_event.dart diff --git a/mobile/lib/events/memories_changed_event.dart b/mobile/apps/photos/lib/events/memories_changed_event.dart similarity index 100% rename from mobile/lib/events/memories_changed_event.dart rename to mobile/apps/photos/lib/events/memories_changed_event.dart diff --git a/mobile/lib/events/memories_setting_changed.dart b/mobile/apps/photos/lib/events/memories_setting_changed.dart similarity index 100% rename from mobile/lib/events/memories_setting_changed.dart rename to mobile/apps/photos/lib/events/memories_setting_changed.dart diff --git a/mobile/lib/events/memory_seen_event.dart b/mobile/apps/photos/lib/events/memory_seen_event.dart similarity index 100% rename from mobile/lib/events/memory_seen_event.dart rename to mobile/apps/photos/lib/events/memory_seen_event.dart diff --git a/mobile/lib/events/notification_event.dart b/mobile/apps/photos/lib/events/notification_event.dart similarity index 100% rename from mobile/lib/events/notification_event.dart rename to mobile/apps/photos/lib/events/notification_event.dart diff --git a/mobile/lib/events/opened_settings_event.dart b/mobile/apps/photos/lib/events/opened_settings_event.dart similarity index 100% rename from mobile/lib/events/opened_settings_event.dart rename to mobile/apps/photos/lib/events/opened_settings_event.dart diff --git a/mobile/lib/events/pause_video_event.dart b/mobile/apps/photos/lib/events/pause_video_event.dart similarity index 100% rename from mobile/lib/events/pause_video_event.dart rename to mobile/apps/photos/lib/events/pause_video_event.dart diff --git a/mobile/lib/events/people_changed_event.dart b/mobile/apps/photos/lib/events/people_changed_event.dart similarity index 100% rename from mobile/lib/events/people_changed_event.dart rename to mobile/apps/photos/lib/events/people_changed_event.dart diff --git a/mobile/lib/events/permission_granted_event.dart b/mobile/apps/photos/lib/events/permission_granted_event.dart similarity index 100% rename from mobile/lib/events/permission_granted_event.dart rename to mobile/apps/photos/lib/events/permission_granted_event.dart diff --git a/mobile/lib/events/reset_zoom_of_photo_view_event.dart b/mobile/apps/photos/lib/events/reset_zoom_of_photo_view_event.dart similarity index 100% rename from mobile/lib/events/reset_zoom_of_photo_view_event.dart rename to mobile/apps/photos/lib/events/reset_zoom_of_photo_view_event.dart diff --git a/mobile/lib/events/seekbar_triggered_event.dart b/mobile/apps/photos/lib/events/seekbar_triggered_event.dart similarity index 100% rename from mobile/lib/events/seekbar_triggered_event.dart rename to mobile/apps/photos/lib/events/seekbar_triggered_event.dart diff --git a/mobile/lib/events/signed_in_event.dart b/mobile/apps/photos/lib/events/signed_in_event.dart similarity index 100% rename from mobile/lib/events/signed_in_event.dart rename to mobile/apps/photos/lib/events/signed_in_event.dart diff --git a/mobile/lib/events/stream_switched_event.dart b/mobile/apps/photos/lib/events/stream_switched_event.dart similarity index 100% rename from mobile/lib/events/stream_switched_event.dart rename to mobile/apps/photos/lib/events/stream_switched_event.dart diff --git a/mobile/lib/events/subscription_purchased_event.dart b/mobile/apps/photos/lib/events/subscription_purchased_event.dart similarity index 100% rename from mobile/lib/events/subscription_purchased_event.dart rename to mobile/apps/photos/lib/events/subscription_purchased_event.dart diff --git a/mobile/lib/events/sync_status_update_event.dart b/mobile/apps/photos/lib/events/sync_status_update_event.dart similarity index 100% rename from mobile/lib/events/sync_status_update_event.dart rename to mobile/apps/photos/lib/events/sync_status_update_event.dart diff --git a/mobile/lib/events/tab_changed_event.dart b/mobile/apps/photos/lib/events/tab_changed_event.dart similarity index 100% rename from mobile/lib/events/tab_changed_event.dart rename to mobile/apps/photos/lib/events/tab_changed_event.dart diff --git a/mobile/lib/events/trash_updated_event.dart b/mobile/apps/photos/lib/events/trash_updated_event.dart similarity index 100% rename from mobile/lib/events/trash_updated_event.dart rename to mobile/apps/photos/lib/events/trash_updated_event.dart diff --git a/mobile/lib/events/trigger_logout_event.dart b/mobile/apps/photos/lib/events/trigger_logout_event.dart similarity index 100% rename from mobile/lib/events/trigger_logout_event.dart rename to mobile/apps/photos/lib/events/trigger_logout_event.dart diff --git a/mobile/lib/events/two_factor_status_change_event.dart b/mobile/apps/photos/lib/events/two_factor_status_change_event.dart similarity index 100% rename from mobile/lib/events/two_factor_status_change_event.dart rename to mobile/apps/photos/lib/events/two_factor_status_change_event.dart diff --git a/mobile/lib/events/use_media_kit_for_video.dart b/mobile/apps/photos/lib/events/use_media_kit_for_video.dart similarity index 100% rename from mobile/lib/events/use_media_kit_for_video.dart rename to mobile/apps/photos/lib/events/use_media_kit_for_video.dart diff --git a/mobile/lib/events/user_details_changed_event.dart b/mobile/apps/photos/lib/events/user_details_changed_event.dart similarity index 100% rename from mobile/lib/events/user_details_changed_event.dart rename to mobile/apps/photos/lib/events/user_details_changed_event.dart diff --git a/mobile/lib/events/user_logged_out_event.dart b/mobile/apps/photos/lib/events/user_logged_out_event.dart similarity index 100% rename from mobile/lib/events/user_logged_out_event.dart rename to mobile/apps/photos/lib/events/user_logged_out_event.dart diff --git a/mobile/lib/events/video_streaming_changed.dart b/mobile/apps/photos/lib/events/video_streaming_changed.dart similarity index 100% rename from mobile/lib/events/video_streaming_changed.dart rename to mobile/apps/photos/lib/events/video_streaming_changed.dart diff --git a/mobile/lib/extensions/input_formatter.dart b/mobile/apps/photos/lib/extensions/input_formatter.dart similarity index 100% rename from mobile/lib/extensions/input_formatter.dart rename to mobile/apps/photos/lib/extensions/input_formatter.dart diff --git a/mobile/lib/extensions/list.dart b/mobile/apps/photos/lib/extensions/list.dart similarity index 100% rename from mobile/lib/extensions/list.dart rename to mobile/apps/photos/lib/extensions/list.dart diff --git a/mobile/lib/extensions/ml_linalg_extensions.dart b/mobile/apps/photos/lib/extensions/ml_linalg_extensions.dart similarity index 100% rename from mobile/lib/extensions/ml_linalg_extensions.dart rename to mobile/apps/photos/lib/extensions/ml_linalg_extensions.dart diff --git a/mobile/lib/extensions/stop_watch.dart b/mobile/apps/photos/lib/extensions/stop_watch.dart similarity index 100% rename from mobile/lib/extensions/stop_watch.dart rename to mobile/apps/photos/lib/extensions/stop_watch.dart diff --git a/mobile/lib/extensions/user_extension.dart b/mobile/apps/photos/lib/extensions/user_extension.dart similarity index 100% rename from mobile/lib/extensions/user_extension.dart rename to mobile/apps/photos/lib/extensions/user_extension.dart diff --git a/mobile/lib/gateways/cast_gw.dart b/mobile/apps/photos/lib/gateways/cast_gw.dart similarity index 100% rename from mobile/lib/gateways/cast_gw.dart rename to mobile/apps/photos/lib/gateways/cast_gw.dart diff --git a/mobile/lib/gateways/entity_gw.dart b/mobile/apps/photos/lib/gateways/entity_gw.dart similarity index 100% rename from mobile/lib/gateways/entity_gw.dart rename to mobile/apps/photos/lib/gateways/entity_gw.dart diff --git a/mobile/lib/gateways/storage_bonus_gw.dart b/mobile/apps/photos/lib/gateways/storage_bonus_gw.dart similarity index 100% rename from mobile/lib/gateways/storage_bonus_gw.dart rename to mobile/apps/photos/lib/gateways/storage_bonus_gw.dart diff --git a/mobile/lib/generated/intl/messages_all.dart b/mobile/apps/photos/lib/generated/intl/messages_all.dart similarity index 100% rename from mobile/lib/generated/intl/messages_all.dart rename to mobile/apps/photos/lib/generated/intl/messages_all.dart diff --git a/mobile/lib/generated/intl/messages_ar.dart b/mobile/apps/photos/lib/generated/intl/messages_ar.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ar.dart rename to mobile/apps/photos/lib/generated/intl/messages_ar.dart diff --git a/mobile/lib/generated/intl/messages_be.dart b/mobile/apps/photos/lib/generated/intl/messages_be.dart similarity index 100% rename from mobile/lib/generated/intl/messages_be.dart rename to mobile/apps/photos/lib/generated/intl/messages_be.dart diff --git a/mobile/lib/generated/intl/messages_bg.dart b/mobile/apps/photos/lib/generated/intl/messages_bg.dart similarity index 100% rename from mobile/lib/generated/intl/messages_bg.dart rename to mobile/apps/photos/lib/generated/intl/messages_bg.dart diff --git a/mobile/lib/generated/intl/messages_ca.dart b/mobile/apps/photos/lib/generated/intl/messages_ca.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ca.dart rename to mobile/apps/photos/lib/generated/intl/messages_ca.dart diff --git a/mobile/lib/generated/intl/messages_cs.dart b/mobile/apps/photos/lib/generated/intl/messages_cs.dart similarity index 100% rename from mobile/lib/generated/intl/messages_cs.dart rename to mobile/apps/photos/lib/generated/intl/messages_cs.dart diff --git a/mobile/lib/generated/intl/messages_da.dart b/mobile/apps/photos/lib/generated/intl/messages_da.dart similarity index 100% rename from mobile/lib/generated/intl/messages_da.dart rename to mobile/apps/photos/lib/generated/intl/messages_da.dart diff --git a/mobile/lib/generated/intl/messages_de.dart b/mobile/apps/photos/lib/generated/intl/messages_de.dart similarity index 100% rename from mobile/lib/generated/intl/messages_de.dart rename to mobile/apps/photos/lib/generated/intl/messages_de.dart diff --git a/mobile/lib/generated/intl/messages_el.dart b/mobile/apps/photos/lib/generated/intl/messages_el.dart similarity index 100% rename from mobile/lib/generated/intl/messages_el.dart rename to mobile/apps/photos/lib/generated/intl/messages_el.dart diff --git a/mobile/lib/generated/intl/messages_en.dart b/mobile/apps/photos/lib/generated/intl/messages_en.dart similarity index 100% rename from mobile/lib/generated/intl/messages_en.dart rename to mobile/apps/photos/lib/generated/intl/messages_en.dart diff --git a/mobile/lib/generated/intl/messages_es.dart b/mobile/apps/photos/lib/generated/intl/messages_es.dart similarity index 100% rename from mobile/lib/generated/intl/messages_es.dart rename to mobile/apps/photos/lib/generated/intl/messages_es.dart diff --git a/mobile/lib/generated/intl/messages_et.dart b/mobile/apps/photos/lib/generated/intl/messages_et.dart similarity index 100% rename from mobile/lib/generated/intl/messages_et.dart rename to mobile/apps/photos/lib/generated/intl/messages_et.dart diff --git a/mobile/lib/generated/intl/messages_eu.dart b/mobile/apps/photos/lib/generated/intl/messages_eu.dart similarity index 100% rename from mobile/lib/generated/intl/messages_eu.dart rename to mobile/apps/photos/lib/generated/intl/messages_eu.dart diff --git a/mobile/lib/generated/intl/messages_fa.dart b/mobile/apps/photos/lib/generated/intl/messages_fa.dart similarity index 100% rename from mobile/lib/generated/intl/messages_fa.dart rename to mobile/apps/photos/lib/generated/intl/messages_fa.dart diff --git a/mobile/lib/generated/intl/messages_fr.dart b/mobile/apps/photos/lib/generated/intl/messages_fr.dart similarity index 100% rename from mobile/lib/generated/intl/messages_fr.dart rename to mobile/apps/photos/lib/generated/intl/messages_fr.dart diff --git a/mobile/lib/generated/intl/messages_gu.dart b/mobile/apps/photos/lib/generated/intl/messages_gu.dart similarity index 100% rename from mobile/lib/generated/intl/messages_gu.dart rename to mobile/apps/photos/lib/generated/intl/messages_gu.dart diff --git a/mobile/lib/generated/intl/messages_he.dart b/mobile/apps/photos/lib/generated/intl/messages_he.dart similarity index 100% rename from mobile/lib/generated/intl/messages_he.dart rename to mobile/apps/photos/lib/generated/intl/messages_he.dart diff --git a/mobile/lib/generated/intl/messages_hi.dart b/mobile/apps/photos/lib/generated/intl/messages_hi.dart similarity index 100% rename from mobile/lib/generated/intl/messages_hi.dart rename to mobile/apps/photos/lib/generated/intl/messages_hi.dart diff --git a/mobile/lib/generated/intl/messages_hu.dart b/mobile/apps/photos/lib/generated/intl/messages_hu.dart similarity index 100% rename from mobile/lib/generated/intl/messages_hu.dart rename to mobile/apps/photos/lib/generated/intl/messages_hu.dart diff --git a/mobile/lib/generated/intl/messages_id.dart b/mobile/apps/photos/lib/generated/intl/messages_id.dart similarity index 100% rename from mobile/lib/generated/intl/messages_id.dart rename to mobile/apps/photos/lib/generated/intl/messages_id.dart diff --git a/mobile/lib/generated/intl/messages_it.dart b/mobile/apps/photos/lib/generated/intl/messages_it.dart similarity index 100% rename from mobile/lib/generated/intl/messages_it.dart rename to mobile/apps/photos/lib/generated/intl/messages_it.dart diff --git a/mobile/lib/generated/intl/messages_ja.dart b/mobile/apps/photos/lib/generated/intl/messages_ja.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ja.dart rename to mobile/apps/photos/lib/generated/intl/messages_ja.dart diff --git a/mobile/lib/generated/intl/messages_km.dart b/mobile/apps/photos/lib/generated/intl/messages_km.dart similarity index 100% rename from mobile/lib/generated/intl/messages_km.dart rename to mobile/apps/photos/lib/generated/intl/messages_km.dart diff --git a/mobile/lib/generated/intl/messages_ko.dart b/mobile/apps/photos/lib/generated/intl/messages_ko.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ko.dart rename to mobile/apps/photos/lib/generated/intl/messages_ko.dart diff --git a/mobile/lib/generated/intl/messages_ku.dart b/mobile/apps/photos/lib/generated/intl/messages_ku.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ku.dart rename to mobile/apps/photos/lib/generated/intl/messages_ku.dart diff --git a/mobile/lib/generated/intl/messages_lt.dart b/mobile/apps/photos/lib/generated/intl/messages_lt.dart similarity index 100% rename from mobile/lib/generated/intl/messages_lt.dart rename to mobile/apps/photos/lib/generated/intl/messages_lt.dart diff --git a/mobile/lib/generated/intl/messages_lv.dart b/mobile/apps/photos/lib/generated/intl/messages_lv.dart similarity index 100% rename from mobile/lib/generated/intl/messages_lv.dart rename to mobile/apps/photos/lib/generated/intl/messages_lv.dart diff --git a/mobile/lib/generated/intl/messages_ml.dart b/mobile/apps/photos/lib/generated/intl/messages_ml.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ml.dart rename to mobile/apps/photos/lib/generated/intl/messages_ml.dart diff --git a/mobile/lib/generated/intl/messages_nl.dart b/mobile/apps/photos/lib/generated/intl/messages_nl.dart similarity index 100% rename from mobile/lib/generated/intl/messages_nl.dart rename to mobile/apps/photos/lib/generated/intl/messages_nl.dart diff --git a/mobile/lib/generated/intl/messages_no.dart b/mobile/apps/photos/lib/generated/intl/messages_no.dart similarity index 100% rename from mobile/lib/generated/intl/messages_no.dart rename to mobile/apps/photos/lib/generated/intl/messages_no.dart diff --git a/mobile/lib/generated/intl/messages_or.dart b/mobile/apps/photos/lib/generated/intl/messages_or.dart similarity index 100% rename from mobile/lib/generated/intl/messages_or.dart rename to mobile/apps/photos/lib/generated/intl/messages_or.dart diff --git a/mobile/lib/generated/intl/messages_pl.dart b/mobile/apps/photos/lib/generated/intl/messages_pl.dart similarity index 100% rename from mobile/lib/generated/intl/messages_pl.dart rename to mobile/apps/photos/lib/generated/intl/messages_pl.dart diff --git a/mobile/lib/generated/intl/messages_pt.dart b/mobile/apps/photos/lib/generated/intl/messages_pt.dart similarity index 100% rename from mobile/lib/generated/intl/messages_pt.dart rename to mobile/apps/photos/lib/generated/intl/messages_pt.dart diff --git a/mobile/lib/generated/intl/messages_pt_BR.dart b/mobile/apps/photos/lib/generated/intl/messages_pt_BR.dart similarity index 100% rename from mobile/lib/generated/intl/messages_pt_BR.dart rename to mobile/apps/photos/lib/generated/intl/messages_pt_BR.dart diff --git a/mobile/lib/generated/intl/messages_pt_PT.dart b/mobile/apps/photos/lib/generated/intl/messages_pt_PT.dart similarity index 100% rename from mobile/lib/generated/intl/messages_pt_PT.dart rename to mobile/apps/photos/lib/generated/intl/messages_pt_PT.dart diff --git a/mobile/lib/generated/intl/messages_ro.dart b/mobile/apps/photos/lib/generated/intl/messages_ro.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ro.dart rename to mobile/apps/photos/lib/generated/intl/messages_ro.dart diff --git a/mobile/lib/generated/intl/messages_ru.dart b/mobile/apps/photos/lib/generated/intl/messages_ru.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ru.dart rename to mobile/apps/photos/lib/generated/intl/messages_ru.dart diff --git a/mobile/lib/generated/intl/messages_sl.dart b/mobile/apps/photos/lib/generated/intl/messages_sl.dart similarity index 100% rename from mobile/lib/generated/intl/messages_sl.dart rename to mobile/apps/photos/lib/generated/intl/messages_sl.dart diff --git a/mobile/lib/generated/intl/messages_sr.dart b/mobile/apps/photos/lib/generated/intl/messages_sr.dart similarity index 100% rename from mobile/lib/generated/intl/messages_sr.dart rename to mobile/apps/photos/lib/generated/intl/messages_sr.dart diff --git a/mobile/lib/generated/intl/messages_sv.dart b/mobile/apps/photos/lib/generated/intl/messages_sv.dart similarity index 100% rename from mobile/lib/generated/intl/messages_sv.dart rename to mobile/apps/photos/lib/generated/intl/messages_sv.dart diff --git a/mobile/lib/generated/intl/messages_ta.dart b/mobile/apps/photos/lib/generated/intl/messages_ta.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ta.dart rename to mobile/apps/photos/lib/generated/intl/messages_ta.dart diff --git a/mobile/lib/generated/intl/messages_te.dart b/mobile/apps/photos/lib/generated/intl/messages_te.dart similarity index 100% rename from mobile/lib/generated/intl/messages_te.dart rename to mobile/apps/photos/lib/generated/intl/messages_te.dart diff --git a/mobile/lib/generated/intl/messages_th.dart b/mobile/apps/photos/lib/generated/intl/messages_th.dart similarity index 100% rename from mobile/lib/generated/intl/messages_th.dart rename to mobile/apps/photos/lib/generated/intl/messages_th.dart diff --git a/mobile/lib/generated/intl/messages_ti.dart b/mobile/apps/photos/lib/generated/intl/messages_ti.dart similarity index 100% rename from mobile/lib/generated/intl/messages_ti.dart rename to mobile/apps/photos/lib/generated/intl/messages_ti.dart diff --git a/mobile/lib/generated/intl/messages_tr.dart b/mobile/apps/photos/lib/generated/intl/messages_tr.dart similarity index 100% rename from mobile/lib/generated/intl/messages_tr.dart rename to mobile/apps/photos/lib/generated/intl/messages_tr.dart diff --git a/mobile/lib/generated/intl/messages_uk.dart b/mobile/apps/photos/lib/generated/intl/messages_uk.dart similarity index 100% rename from mobile/lib/generated/intl/messages_uk.dart rename to mobile/apps/photos/lib/generated/intl/messages_uk.dart diff --git a/mobile/lib/generated/intl/messages_vi.dart b/mobile/apps/photos/lib/generated/intl/messages_vi.dart similarity index 100% rename from mobile/lib/generated/intl/messages_vi.dart rename to mobile/apps/photos/lib/generated/intl/messages_vi.dart diff --git a/mobile/lib/generated/intl/messages_zh.dart b/mobile/apps/photos/lib/generated/intl/messages_zh.dart similarity index 100% rename from mobile/lib/generated/intl/messages_zh.dart rename to mobile/apps/photos/lib/generated/intl/messages_zh.dart diff --git a/mobile/lib/generated/l10n.dart b/mobile/apps/photos/lib/generated/l10n.dart similarity index 100% rename from mobile/lib/generated/l10n.dart rename to mobile/apps/photos/lib/generated/l10n.dart diff --git a/mobile/lib/generated/protos/ente/common/box.pb.dart b/mobile/apps/photos/lib/generated/protos/ente/common/box.pb.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/box.pb.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/box.pb.dart diff --git a/mobile/lib/generated/protos/ente/common/box.pbenum.dart b/mobile/apps/photos/lib/generated/protos/ente/common/box.pbenum.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/box.pbenum.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/box.pbenum.dart diff --git a/mobile/lib/generated/protos/ente/common/box.pbjson.dart b/mobile/apps/photos/lib/generated/protos/ente/common/box.pbjson.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/box.pbjson.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/box.pbjson.dart diff --git a/mobile/lib/generated/protos/ente/common/box.pbserver.dart b/mobile/apps/photos/lib/generated/protos/ente/common/box.pbserver.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/box.pbserver.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/box.pbserver.dart diff --git a/mobile/lib/generated/protos/ente/common/point.pb.dart b/mobile/apps/photos/lib/generated/protos/ente/common/point.pb.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/point.pb.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/point.pb.dart diff --git a/mobile/lib/generated/protos/ente/common/point.pbenum.dart b/mobile/apps/photos/lib/generated/protos/ente/common/point.pbenum.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/point.pbenum.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/point.pbenum.dart diff --git a/mobile/lib/generated/protos/ente/common/point.pbjson.dart b/mobile/apps/photos/lib/generated/protos/ente/common/point.pbjson.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/point.pbjson.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/point.pbjson.dart diff --git a/mobile/lib/generated/protos/ente/common/point.pbserver.dart b/mobile/apps/photos/lib/generated/protos/ente/common/point.pbserver.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/point.pbserver.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/point.pbserver.dart diff --git a/mobile/lib/generated/protos/ente/common/vector.pb.dart b/mobile/apps/photos/lib/generated/protos/ente/common/vector.pb.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/vector.pb.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/vector.pb.dart diff --git a/mobile/lib/generated/protos/ente/common/vector.pbenum.dart b/mobile/apps/photos/lib/generated/protos/ente/common/vector.pbenum.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/vector.pbenum.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/vector.pbenum.dart diff --git a/mobile/lib/generated/protos/ente/common/vector.pbjson.dart b/mobile/apps/photos/lib/generated/protos/ente/common/vector.pbjson.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/vector.pbjson.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/vector.pbjson.dart diff --git a/mobile/lib/generated/protos/ente/common/vector.pbserver.dart b/mobile/apps/photos/lib/generated/protos/ente/common/vector.pbserver.dart similarity index 100% rename from mobile/lib/generated/protos/ente/common/vector.pbserver.dart rename to mobile/apps/photos/lib/generated/protos/ente/common/vector.pbserver.dart diff --git a/mobile/lib/generated/protos/ente/ml/face.pb.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/face.pb.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/face.pb.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/face.pb.dart diff --git a/mobile/lib/generated/protos/ente/ml/face.pbenum.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/face.pbenum.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/face.pbenum.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/face.pbenum.dart diff --git a/mobile/lib/generated/protos/ente/ml/face.pbjson.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/face.pbjson.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/face.pbjson.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/face.pbjson.dart diff --git a/mobile/lib/generated/protos/ente/ml/face.pbserver.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/face.pbserver.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/face.pbserver.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/face.pbserver.dart diff --git a/mobile/lib/generated/protos/ente/ml/fileml.pb.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pb.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/fileml.pb.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pb.dart diff --git a/mobile/lib/generated/protos/ente/ml/fileml.pbenum.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pbenum.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/fileml.pbenum.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pbenum.dart diff --git a/mobile/lib/generated/protos/ente/ml/fileml.pbjson.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pbjson.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/fileml.pbjson.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pbjson.dart diff --git a/mobile/lib/generated/protos/ente/ml/fileml.pbserver.dart b/mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pbserver.dart similarity index 100% rename from mobile/lib/generated/protos/ente/ml/fileml.pbserver.dart rename to mobile/apps/photos/lib/generated/protos/ente/ml/fileml.pbserver.dart diff --git a/mobile/lib/l10n/intl_ar.arb b/mobile/apps/photos/lib/l10n/intl_ar.arb similarity index 100% rename from mobile/lib/l10n/intl_ar.arb rename to mobile/apps/photos/lib/l10n/intl_ar.arb diff --git a/mobile/lib/l10n/intl_be.arb b/mobile/apps/photos/lib/l10n/intl_be.arb similarity index 100% rename from mobile/lib/l10n/intl_be.arb rename to mobile/apps/photos/lib/l10n/intl_be.arb diff --git a/mobile/lib/l10n/intl_bg.arb b/mobile/apps/photos/lib/l10n/intl_bg.arb similarity index 100% rename from mobile/lib/l10n/intl_bg.arb rename to mobile/apps/photos/lib/l10n/intl_bg.arb diff --git a/mobile/lib/l10n/intl_ca.arb b/mobile/apps/photos/lib/l10n/intl_ca.arb similarity index 100% rename from mobile/lib/l10n/intl_ca.arb rename to mobile/apps/photos/lib/l10n/intl_ca.arb diff --git a/mobile/lib/l10n/intl_cs.arb b/mobile/apps/photos/lib/l10n/intl_cs.arb similarity index 100% rename from mobile/lib/l10n/intl_cs.arb rename to mobile/apps/photos/lib/l10n/intl_cs.arb diff --git a/mobile/lib/l10n/intl_da.arb b/mobile/apps/photos/lib/l10n/intl_da.arb similarity index 100% rename from mobile/lib/l10n/intl_da.arb rename to mobile/apps/photos/lib/l10n/intl_da.arb diff --git a/mobile/lib/l10n/intl_de.arb b/mobile/apps/photos/lib/l10n/intl_de.arb similarity index 100% rename from mobile/lib/l10n/intl_de.arb rename to mobile/apps/photos/lib/l10n/intl_de.arb diff --git a/mobile/lib/l10n/intl_el.arb b/mobile/apps/photos/lib/l10n/intl_el.arb similarity index 100% rename from mobile/lib/l10n/intl_el.arb rename to mobile/apps/photos/lib/l10n/intl_el.arb diff --git a/mobile/lib/l10n/intl_en.arb b/mobile/apps/photos/lib/l10n/intl_en.arb similarity index 100% rename from mobile/lib/l10n/intl_en.arb rename to mobile/apps/photos/lib/l10n/intl_en.arb diff --git a/mobile/lib/l10n/intl_es.arb b/mobile/apps/photos/lib/l10n/intl_es.arb similarity index 100% rename from mobile/lib/l10n/intl_es.arb rename to mobile/apps/photos/lib/l10n/intl_es.arb diff --git a/mobile/lib/l10n/intl_et.arb b/mobile/apps/photos/lib/l10n/intl_et.arb similarity index 100% rename from mobile/lib/l10n/intl_et.arb rename to mobile/apps/photos/lib/l10n/intl_et.arb diff --git a/mobile/lib/l10n/intl_eu.arb b/mobile/apps/photos/lib/l10n/intl_eu.arb similarity index 100% rename from mobile/lib/l10n/intl_eu.arb rename to mobile/apps/photos/lib/l10n/intl_eu.arb diff --git a/mobile/lib/l10n/intl_fa.arb b/mobile/apps/photos/lib/l10n/intl_fa.arb similarity index 100% rename from mobile/lib/l10n/intl_fa.arb rename to mobile/apps/photos/lib/l10n/intl_fa.arb diff --git a/mobile/lib/l10n/intl_fr.arb b/mobile/apps/photos/lib/l10n/intl_fr.arb similarity index 100% rename from mobile/lib/l10n/intl_fr.arb rename to mobile/apps/photos/lib/l10n/intl_fr.arb diff --git a/mobile/lib/l10n/intl_gu.arb b/mobile/apps/photos/lib/l10n/intl_gu.arb similarity index 100% rename from mobile/lib/l10n/intl_gu.arb rename to mobile/apps/photos/lib/l10n/intl_gu.arb diff --git a/mobile/lib/l10n/intl_he.arb b/mobile/apps/photos/lib/l10n/intl_he.arb similarity index 100% rename from mobile/lib/l10n/intl_he.arb rename to mobile/apps/photos/lib/l10n/intl_he.arb diff --git a/mobile/lib/l10n/intl_hi.arb b/mobile/apps/photos/lib/l10n/intl_hi.arb similarity index 100% rename from mobile/lib/l10n/intl_hi.arb rename to mobile/apps/photos/lib/l10n/intl_hi.arb diff --git a/mobile/lib/l10n/intl_hu.arb b/mobile/apps/photos/lib/l10n/intl_hu.arb similarity index 100% rename from mobile/lib/l10n/intl_hu.arb rename to mobile/apps/photos/lib/l10n/intl_hu.arb diff --git a/mobile/lib/l10n/intl_id.arb b/mobile/apps/photos/lib/l10n/intl_id.arb similarity index 100% rename from mobile/lib/l10n/intl_id.arb rename to mobile/apps/photos/lib/l10n/intl_id.arb diff --git a/mobile/lib/l10n/intl_it.arb b/mobile/apps/photos/lib/l10n/intl_it.arb similarity index 100% rename from mobile/lib/l10n/intl_it.arb rename to mobile/apps/photos/lib/l10n/intl_it.arb diff --git a/mobile/lib/l10n/intl_ja.arb b/mobile/apps/photos/lib/l10n/intl_ja.arb similarity index 100% rename from mobile/lib/l10n/intl_ja.arb rename to mobile/apps/photos/lib/l10n/intl_ja.arb diff --git a/mobile/lib/l10n/intl_km.arb b/mobile/apps/photos/lib/l10n/intl_km.arb similarity index 100% rename from mobile/lib/l10n/intl_km.arb rename to mobile/apps/photos/lib/l10n/intl_km.arb diff --git a/mobile/lib/l10n/intl_ko.arb b/mobile/apps/photos/lib/l10n/intl_ko.arb similarity index 100% rename from mobile/lib/l10n/intl_ko.arb rename to mobile/apps/photos/lib/l10n/intl_ko.arb diff --git a/mobile/lib/l10n/intl_ku.arb b/mobile/apps/photos/lib/l10n/intl_ku.arb similarity index 100% rename from mobile/lib/l10n/intl_ku.arb rename to mobile/apps/photos/lib/l10n/intl_ku.arb diff --git a/mobile/lib/l10n/intl_lt.arb b/mobile/apps/photos/lib/l10n/intl_lt.arb similarity index 100% rename from mobile/lib/l10n/intl_lt.arb rename to mobile/apps/photos/lib/l10n/intl_lt.arb diff --git a/mobile/lib/l10n/intl_lv.arb b/mobile/apps/photos/lib/l10n/intl_lv.arb similarity index 100% rename from mobile/lib/l10n/intl_lv.arb rename to mobile/apps/photos/lib/l10n/intl_lv.arb diff --git a/mobile/lib/l10n/intl_ml.arb b/mobile/apps/photos/lib/l10n/intl_ml.arb similarity index 100% rename from mobile/lib/l10n/intl_ml.arb rename to mobile/apps/photos/lib/l10n/intl_ml.arb diff --git a/mobile/lib/l10n/intl_nl.arb b/mobile/apps/photos/lib/l10n/intl_nl.arb similarity index 100% rename from mobile/lib/l10n/intl_nl.arb rename to mobile/apps/photos/lib/l10n/intl_nl.arb diff --git a/mobile/lib/l10n/intl_no.arb b/mobile/apps/photos/lib/l10n/intl_no.arb similarity index 100% rename from mobile/lib/l10n/intl_no.arb rename to mobile/apps/photos/lib/l10n/intl_no.arb diff --git a/mobile/lib/l10n/intl_or.arb b/mobile/apps/photos/lib/l10n/intl_or.arb similarity index 100% rename from mobile/lib/l10n/intl_or.arb rename to mobile/apps/photos/lib/l10n/intl_or.arb diff --git a/mobile/lib/l10n/intl_pl.arb b/mobile/apps/photos/lib/l10n/intl_pl.arb similarity index 100% rename from mobile/lib/l10n/intl_pl.arb rename to mobile/apps/photos/lib/l10n/intl_pl.arb diff --git a/mobile/lib/l10n/intl_pt.arb b/mobile/apps/photos/lib/l10n/intl_pt.arb similarity index 100% rename from mobile/lib/l10n/intl_pt.arb rename to mobile/apps/photos/lib/l10n/intl_pt.arb diff --git a/mobile/lib/l10n/intl_pt_BR.arb b/mobile/apps/photos/lib/l10n/intl_pt_BR.arb similarity index 100% rename from mobile/lib/l10n/intl_pt_BR.arb rename to mobile/apps/photos/lib/l10n/intl_pt_BR.arb diff --git a/mobile/lib/l10n/intl_pt_PT.arb b/mobile/apps/photos/lib/l10n/intl_pt_PT.arb similarity index 100% rename from mobile/lib/l10n/intl_pt_PT.arb rename to mobile/apps/photos/lib/l10n/intl_pt_PT.arb diff --git a/mobile/lib/l10n/intl_ro.arb b/mobile/apps/photos/lib/l10n/intl_ro.arb similarity index 100% rename from mobile/lib/l10n/intl_ro.arb rename to mobile/apps/photos/lib/l10n/intl_ro.arb diff --git a/mobile/lib/l10n/intl_ru.arb b/mobile/apps/photos/lib/l10n/intl_ru.arb similarity index 100% rename from mobile/lib/l10n/intl_ru.arb rename to mobile/apps/photos/lib/l10n/intl_ru.arb diff --git a/mobile/lib/l10n/intl_sl.arb b/mobile/apps/photos/lib/l10n/intl_sl.arb similarity index 100% rename from mobile/lib/l10n/intl_sl.arb rename to mobile/apps/photos/lib/l10n/intl_sl.arb diff --git a/mobile/lib/l10n/intl_sr.arb b/mobile/apps/photos/lib/l10n/intl_sr.arb similarity index 100% rename from mobile/lib/l10n/intl_sr.arb rename to mobile/apps/photos/lib/l10n/intl_sr.arb diff --git a/mobile/lib/l10n/intl_sv.arb b/mobile/apps/photos/lib/l10n/intl_sv.arb similarity index 100% rename from mobile/lib/l10n/intl_sv.arb rename to mobile/apps/photos/lib/l10n/intl_sv.arb diff --git a/mobile/lib/l10n/intl_ta.arb b/mobile/apps/photos/lib/l10n/intl_ta.arb similarity index 100% rename from mobile/lib/l10n/intl_ta.arb rename to mobile/apps/photos/lib/l10n/intl_ta.arb diff --git a/mobile/lib/l10n/intl_te.arb b/mobile/apps/photos/lib/l10n/intl_te.arb similarity index 100% rename from mobile/lib/l10n/intl_te.arb rename to mobile/apps/photos/lib/l10n/intl_te.arb diff --git a/mobile/lib/l10n/intl_th.arb b/mobile/apps/photos/lib/l10n/intl_th.arb similarity index 100% rename from mobile/lib/l10n/intl_th.arb rename to mobile/apps/photos/lib/l10n/intl_th.arb diff --git a/mobile/lib/l10n/intl_ti.arb b/mobile/apps/photos/lib/l10n/intl_ti.arb similarity index 100% rename from mobile/lib/l10n/intl_ti.arb rename to mobile/apps/photos/lib/l10n/intl_ti.arb diff --git a/mobile/lib/l10n/intl_tr.arb b/mobile/apps/photos/lib/l10n/intl_tr.arb similarity index 100% rename from mobile/lib/l10n/intl_tr.arb rename to mobile/apps/photos/lib/l10n/intl_tr.arb diff --git a/mobile/lib/l10n/intl_uk.arb b/mobile/apps/photos/lib/l10n/intl_uk.arb similarity index 100% rename from mobile/lib/l10n/intl_uk.arb rename to mobile/apps/photos/lib/l10n/intl_uk.arb diff --git a/mobile/lib/l10n/intl_vi.arb b/mobile/apps/photos/lib/l10n/intl_vi.arb similarity index 100% rename from mobile/lib/l10n/intl_vi.arb rename to mobile/apps/photos/lib/l10n/intl_vi.arb diff --git a/mobile/lib/l10n/intl_zh.arb b/mobile/apps/photos/lib/l10n/intl_zh.arb similarity index 100% rename from mobile/lib/l10n/intl_zh.arb rename to mobile/apps/photos/lib/l10n/intl_zh.arb diff --git a/mobile/lib/l10n/l10n.dart b/mobile/apps/photos/lib/l10n/l10n.dart similarity index 100% rename from mobile/lib/l10n/l10n.dart rename to mobile/apps/photos/lib/l10n/l10n.dart diff --git a/mobile/lib/main.dart b/mobile/apps/photos/lib/main.dart similarity index 100% rename from mobile/lib/main.dart rename to mobile/apps/photos/lib/main.dart diff --git a/mobile/lib/models/account/two_factor.dart b/mobile/apps/photos/lib/models/account/two_factor.dart similarity index 100% rename from mobile/lib/models/account/two_factor.dart rename to mobile/apps/photos/lib/models/account/two_factor.dart diff --git a/mobile/lib/models/api/billing/billing_plan.dart b/mobile/apps/photos/lib/models/api/billing/billing_plan.dart similarity index 100% rename from mobile/lib/models/api/billing/billing_plan.dart rename to mobile/apps/photos/lib/models/api/billing/billing_plan.dart diff --git a/mobile/lib/models/api/billing/subscription.dart b/mobile/apps/photos/lib/models/api/billing/subscription.dart similarity index 100% rename from mobile/lib/models/api/billing/subscription.dart rename to mobile/apps/photos/lib/models/api/billing/subscription.dart diff --git a/mobile/lib/models/api/collection/collection_file_item.dart b/mobile/apps/photos/lib/models/api/collection/collection_file_item.dart similarity index 100% rename from mobile/lib/models/api/collection/collection_file_item.dart rename to mobile/apps/photos/lib/models/api/collection/collection_file_item.dart diff --git a/mobile/lib/models/api/collection/create_request.dart b/mobile/apps/photos/lib/models/api/collection/create_request.dart similarity index 100% rename from mobile/lib/models/api/collection/create_request.dart rename to mobile/apps/photos/lib/models/api/collection/create_request.dart diff --git a/mobile/lib/models/api/collection/public_url.dart b/mobile/apps/photos/lib/models/api/collection/public_url.dart similarity index 100% rename from mobile/lib/models/api/collection/public_url.dart rename to mobile/apps/photos/lib/models/api/collection/public_url.dart diff --git a/mobile/lib/models/api/collection/trash_item_request.dart b/mobile/apps/photos/lib/models/api/collection/trash_item_request.dart similarity index 100% rename from mobile/lib/models/api/collection/trash_item_request.dart rename to mobile/apps/photos/lib/models/api/collection/trash_item_request.dart diff --git a/mobile/lib/models/api/collection/user.dart b/mobile/apps/photos/lib/models/api/collection/user.dart similarity index 100% rename from mobile/lib/models/api/collection/user.dart rename to mobile/apps/photos/lib/models/api/collection/user.dart diff --git a/mobile/lib/models/api/entity/data.dart b/mobile/apps/photos/lib/models/api/entity/data.dart similarity index 100% rename from mobile/lib/models/api/entity/data.dart rename to mobile/apps/photos/lib/models/api/entity/data.dart diff --git a/mobile/lib/models/api/entity/key.dart b/mobile/apps/photos/lib/models/api/entity/key.dart similarity index 100% rename from mobile/lib/models/api/entity/key.dart rename to mobile/apps/photos/lib/models/api/entity/key.dart diff --git a/mobile/lib/models/api/entity/type.dart b/mobile/apps/photos/lib/models/api/entity/type.dart similarity index 100% rename from mobile/lib/models/api/entity/type.dart rename to mobile/apps/photos/lib/models/api/entity/type.dart diff --git a/mobile/lib/models/api/metadata.dart b/mobile/apps/photos/lib/models/api/metadata.dart similarity index 100% rename from mobile/lib/models/api/metadata.dart rename to mobile/apps/photos/lib/models/api/metadata.dart diff --git a/mobile/lib/models/api/storage_bonus/bonus.dart b/mobile/apps/photos/lib/models/api/storage_bonus/bonus.dart similarity index 100% rename from mobile/lib/models/api/storage_bonus/bonus.dart rename to mobile/apps/photos/lib/models/api/storage_bonus/bonus.dart diff --git a/mobile/lib/models/api/storage_bonus/storage_bonus.dart b/mobile/apps/photos/lib/models/api/storage_bonus/storage_bonus.dart similarity index 100% rename from mobile/lib/models/api/storage_bonus/storage_bonus.dart rename to mobile/apps/photos/lib/models/api/storage_bonus/storage_bonus.dart diff --git a/mobile/lib/models/api/user/delete_account.dart b/mobile/apps/photos/lib/models/api/user/delete_account.dart similarity index 100% rename from mobile/lib/models/api/user/delete_account.dart rename to mobile/apps/photos/lib/models/api/user/delete_account.dart diff --git a/mobile/lib/models/api/user/key_attributes.dart b/mobile/apps/photos/lib/models/api/user/key_attributes.dart similarity index 100% rename from mobile/lib/models/api/user/key_attributes.dart rename to mobile/apps/photos/lib/models/api/user/key_attributes.dart diff --git a/mobile/lib/models/api/user/key_gen_result.dart b/mobile/apps/photos/lib/models/api/user/key_gen_result.dart similarity index 100% rename from mobile/lib/models/api/user/key_gen_result.dart rename to mobile/apps/photos/lib/models/api/user/key_gen_result.dart diff --git a/mobile/lib/models/api/user/private_key_attributes.dart b/mobile/apps/photos/lib/models/api/user/private_key_attributes.dart similarity index 100% rename from mobile/lib/models/api/user/private_key_attributes.dart rename to mobile/apps/photos/lib/models/api/user/private_key_attributes.dart diff --git a/mobile/lib/models/api/user/sessions.dart b/mobile/apps/photos/lib/models/api/user/sessions.dart similarity index 100% rename from mobile/lib/models/api/user/sessions.dart rename to mobile/apps/photos/lib/models/api/user/sessions.dart diff --git a/mobile/lib/models/api/user/set_keys_request.dart b/mobile/apps/photos/lib/models/api/user/set_keys_request.dart similarity index 100% rename from mobile/lib/models/api/user/set_keys_request.dart rename to mobile/apps/photos/lib/models/api/user/set_keys_request.dart diff --git a/mobile/lib/models/api/user/set_recovery_key_request.dart b/mobile/apps/photos/lib/models/api/user/set_recovery_key_request.dart similarity index 100% rename from mobile/lib/models/api/user/set_recovery_key_request.dart rename to mobile/apps/photos/lib/models/api/user/set_recovery_key_request.dart diff --git a/mobile/lib/models/api/user/srp.dart b/mobile/apps/photos/lib/models/api/user/srp.dart similarity index 100% rename from mobile/lib/models/api/user/srp.dart rename to mobile/apps/photos/lib/models/api/user/srp.dart diff --git a/mobile/lib/models/backup/backup_item.dart b/mobile/apps/photos/lib/models/backup/backup_item.dart similarity index 100% rename from mobile/lib/models/backup/backup_item.dart rename to mobile/apps/photos/lib/models/backup/backup_item.dart diff --git a/mobile/lib/models/backup/backup_item_status.dart b/mobile/apps/photos/lib/models/backup/backup_item_status.dart similarity index 100% rename from mobile/lib/models/backup/backup_item_status.dart rename to mobile/apps/photos/lib/models/backup/backup_item_status.dart diff --git a/mobile/lib/models/backup_status.dart b/mobile/apps/photos/lib/models/backup_status.dart similarity index 100% rename from mobile/lib/models/backup_status.dart rename to mobile/apps/photos/lib/models/backup_status.dart diff --git a/mobile/lib/models/base/id.dart b/mobile/apps/photos/lib/models/base/id.dart similarity index 100% rename from mobile/lib/models/base/id.dart rename to mobile/apps/photos/lib/models/base/id.dart diff --git a/mobile/lib/models/base_location.dart b/mobile/apps/photos/lib/models/base_location.dart similarity index 100% rename from mobile/lib/models/base_location.dart rename to mobile/apps/photos/lib/models/base_location.dart diff --git a/mobile/lib/models/button_result.dart b/mobile/apps/photos/lib/models/button_result.dart similarity index 100% rename from mobile/lib/models/button_result.dart rename to mobile/apps/photos/lib/models/button_result.dart diff --git a/mobile/lib/models/collection/collection.dart b/mobile/apps/photos/lib/models/collection/collection.dart similarity index 100% rename from mobile/lib/models/collection/collection.dart rename to mobile/apps/photos/lib/models/collection/collection.dart diff --git a/mobile/lib/models/collection/collection_items.dart b/mobile/apps/photos/lib/models/collection/collection_items.dart similarity index 100% rename from mobile/lib/models/collection/collection_items.dart rename to mobile/apps/photos/lib/models/collection/collection_items.dart diff --git a/mobile/lib/models/device_collection.dart b/mobile/apps/photos/lib/models/device_collection.dart similarity index 100% rename from mobile/lib/models/device_collection.dart rename to mobile/apps/photos/lib/models/device_collection.dart diff --git a/mobile/lib/models/duplicate_files.dart b/mobile/apps/photos/lib/models/duplicate_files.dart similarity index 100% rename from mobile/lib/models/duplicate_files.dart rename to mobile/apps/photos/lib/models/duplicate_files.dart diff --git a/mobile/lib/models/execution_states.dart b/mobile/apps/photos/lib/models/execution_states.dart similarity index 100% rename from mobile/lib/models/execution_states.dart rename to mobile/apps/photos/lib/models/execution_states.dart diff --git a/mobile/lib/models/ffmpeg/channel_layouts.dart b/mobile/apps/photos/lib/models/ffmpeg/channel_layouts.dart similarity index 100% rename from mobile/lib/models/ffmpeg/channel_layouts.dart rename to mobile/apps/photos/lib/models/ffmpeg/channel_layouts.dart diff --git a/mobile/lib/models/ffmpeg/codecs.dart b/mobile/apps/photos/lib/models/ffmpeg/codecs.dart similarity index 100% rename from mobile/lib/models/ffmpeg/codecs.dart rename to mobile/apps/photos/lib/models/ffmpeg/codecs.dart diff --git a/mobile/lib/models/ffmpeg/ffprobe_keys.dart b/mobile/apps/photos/lib/models/ffmpeg/ffprobe_keys.dart similarity index 100% rename from mobile/lib/models/ffmpeg/ffprobe_keys.dart rename to mobile/apps/photos/lib/models/ffmpeg/ffprobe_keys.dart diff --git a/mobile/lib/models/ffmpeg/ffprobe_props.dart b/mobile/apps/photos/lib/models/ffmpeg/ffprobe_props.dart similarity index 100% rename from mobile/lib/models/ffmpeg/ffprobe_props.dart rename to mobile/apps/photos/lib/models/ffmpeg/ffprobe_props.dart diff --git a/mobile/lib/models/ffmpeg/language.dart b/mobile/apps/photos/lib/models/ffmpeg/language.dart similarity index 100% rename from mobile/lib/models/ffmpeg/language.dart rename to mobile/apps/photos/lib/models/ffmpeg/language.dart diff --git a/mobile/lib/models/ffmpeg/mp4.dart b/mobile/apps/photos/lib/models/ffmpeg/mp4.dart similarity index 100% rename from mobile/lib/models/ffmpeg/mp4.dart rename to mobile/apps/photos/lib/models/ffmpeg/mp4.dart diff --git a/mobile/lib/models/file/extensions/file_props.dart b/mobile/apps/photos/lib/models/file/extensions/file_props.dart similarity index 100% rename from mobile/lib/models/file/extensions/file_props.dart rename to mobile/apps/photos/lib/models/file/extensions/file_props.dart diff --git a/mobile/lib/models/file/file.dart b/mobile/apps/photos/lib/models/file/file.dart similarity index 100% rename from mobile/lib/models/file/file.dart rename to mobile/apps/photos/lib/models/file/file.dart diff --git a/mobile/lib/models/file/file_type.dart b/mobile/apps/photos/lib/models/file/file_type.dart similarity index 100% rename from mobile/lib/models/file/file_type.dart rename to mobile/apps/photos/lib/models/file/file_type.dart diff --git a/mobile/lib/models/file/trash_file.dart b/mobile/apps/photos/lib/models/file/trash_file.dart similarity index 100% rename from mobile/lib/models/file/trash_file.dart rename to mobile/apps/photos/lib/models/file/trash_file.dart diff --git a/mobile/lib/models/file_load_result.dart b/mobile/apps/photos/lib/models/file_load_result.dart similarity index 100% rename from mobile/lib/models/file_load_result.dart rename to mobile/apps/photos/lib/models/file_load_result.dart diff --git a/mobile/lib/models/files_split.dart b/mobile/apps/photos/lib/models/files_split.dart similarity index 100% rename from mobile/lib/models/files_split.dart rename to mobile/apps/photos/lib/models/files_split.dart diff --git a/mobile/lib/models/gallery_type.dart b/mobile/apps/photos/lib/models/gallery_type.dart similarity index 100% rename from mobile/lib/models/gallery_type.dart rename to mobile/apps/photos/lib/models/gallery_type.dart diff --git a/mobile/lib/models/ignored_file.dart b/mobile/apps/photos/lib/models/ignored_file.dart similarity index 100% rename from mobile/lib/models/ignored_file.dart rename to mobile/apps/photos/lib/models/ignored_file.dart diff --git a/mobile/lib/models/local_entity_data.dart b/mobile/apps/photos/lib/models/local_entity_data.dart similarity index 100% rename from mobile/lib/models/local_entity_data.dart rename to mobile/apps/photos/lib/models/local_entity_data.dart diff --git a/mobile/lib/models/location/location.dart b/mobile/apps/photos/lib/models/location/location.dart similarity index 100% rename from mobile/lib/models/location/location.dart rename to mobile/apps/photos/lib/models/location/location.dart diff --git a/mobile/lib/models/location/location.freezed.dart b/mobile/apps/photos/lib/models/location/location.freezed.dart similarity index 100% rename from mobile/lib/models/location/location.freezed.dart rename to mobile/apps/photos/lib/models/location/location.freezed.dart diff --git a/mobile/lib/models/location/location.g.dart b/mobile/apps/photos/lib/models/location/location.g.dart similarity index 100% rename from mobile/lib/models/location/location.g.dart rename to mobile/apps/photos/lib/models/location/location.g.dart diff --git a/mobile/lib/models/location_tag/location_tag.dart b/mobile/apps/photos/lib/models/location_tag/location_tag.dart similarity index 100% rename from mobile/lib/models/location_tag/location_tag.dart rename to mobile/apps/photos/lib/models/location_tag/location_tag.dart diff --git a/mobile/lib/models/location_tag/location_tag.freezed.dart b/mobile/apps/photos/lib/models/location_tag/location_tag.freezed.dart similarity index 100% rename from mobile/lib/models/location_tag/location_tag.freezed.dart rename to mobile/apps/photos/lib/models/location_tag/location_tag.freezed.dart diff --git a/mobile/lib/models/location_tag/location_tag.g.dart b/mobile/apps/photos/lib/models/location_tag/location_tag.g.dart similarity index 100% rename from mobile/lib/models/location_tag/location_tag.g.dart rename to mobile/apps/photos/lib/models/location_tag/location_tag.g.dart diff --git a/mobile/lib/models/memories/clip_memory.dart b/mobile/apps/photos/lib/models/memories/clip_memory.dart similarity index 100% rename from mobile/lib/models/memories/clip_memory.dart rename to mobile/apps/photos/lib/models/memories/clip_memory.dart diff --git a/mobile/lib/models/memories/filler_memory.dart b/mobile/apps/photos/lib/models/memories/filler_memory.dart similarity index 100% rename from mobile/lib/models/memories/filler_memory.dart rename to mobile/apps/photos/lib/models/memories/filler_memory.dart diff --git a/mobile/lib/models/memories/memories_cache.dart b/mobile/apps/photos/lib/models/memories/memories_cache.dart similarity index 100% rename from mobile/lib/models/memories/memories_cache.dart rename to mobile/apps/photos/lib/models/memories/memories_cache.dart diff --git a/mobile/lib/models/memories/memory.dart b/mobile/apps/photos/lib/models/memories/memory.dart similarity index 100% rename from mobile/lib/models/memories/memory.dart rename to mobile/apps/photos/lib/models/memories/memory.dart diff --git a/mobile/lib/models/memories/on_this_day_memory.dart b/mobile/apps/photos/lib/models/memories/on_this_day_memory.dart similarity index 100% rename from mobile/lib/models/memories/on_this_day_memory.dart rename to mobile/apps/photos/lib/models/memories/on_this_day_memory.dart diff --git a/mobile/lib/models/memories/people_memory.dart b/mobile/apps/photos/lib/models/memories/people_memory.dart similarity index 100% rename from mobile/lib/models/memories/people_memory.dart rename to mobile/apps/photos/lib/models/memories/people_memory.dart diff --git a/mobile/lib/models/memories/smart_memory.dart b/mobile/apps/photos/lib/models/memories/smart_memory.dart similarity index 100% rename from mobile/lib/models/memories/smart_memory.dart rename to mobile/apps/photos/lib/models/memories/smart_memory.dart diff --git a/mobile/lib/models/memories/smart_memory_constants.dart b/mobile/apps/photos/lib/models/memories/smart_memory_constants.dart similarity index 100% rename from mobile/lib/models/memories/smart_memory_constants.dart rename to mobile/apps/photos/lib/models/memories/smart_memory_constants.dart diff --git a/mobile/lib/models/memories/time_memory.dart b/mobile/apps/photos/lib/models/memories/time_memory.dart similarity index 100% rename from mobile/lib/models/memories/time_memory.dart rename to mobile/apps/photos/lib/models/memories/time_memory.dart diff --git a/mobile/lib/models/memories/trip_memory.dart b/mobile/apps/photos/lib/models/memories/trip_memory.dart similarity index 100% rename from mobile/lib/models/memories/trip_memory.dart rename to mobile/apps/photos/lib/models/memories/trip_memory.dart diff --git a/mobile/lib/models/metadata/collection_magic.dart b/mobile/apps/photos/lib/models/metadata/collection_magic.dart similarity index 100% rename from mobile/lib/models/metadata/collection_magic.dart rename to mobile/apps/photos/lib/models/metadata/collection_magic.dart diff --git a/mobile/lib/models/metadata/common_keys.dart b/mobile/apps/photos/lib/models/metadata/common_keys.dart similarity index 100% rename from mobile/lib/models/metadata/common_keys.dart rename to mobile/apps/photos/lib/models/metadata/common_keys.dart diff --git a/mobile/lib/models/metadata/file_magic.dart b/mobile/apps/photos/lib/models/metadata/file_magic.dart similarity index 100% rename from mobile/lib/models/metadata/file_magic.dart rename to mobile/apps/photos/lib/models/metadata/file_magic.dart diff --git a/mobile/lib/models/ml/clip.dart b/mobile/apps/photos/lib/models/ml/clip.dart similarity index 100% rename from mobile/lib/models/ml/clip.dart rename to mobile/apps/photos/lib/models/ml/clip.dart diff --git a/mobile/lib/models/ml/discover/prompt.dart b/mobile/apps/photos/lib/models/ml/discover/prompt.dart similarity index 100% rename from mobile/lib/models/ml/discover/prompt.dart rename to mobile/apps/photos/lib/models/ml/discover/prompt.dart diff --git a/mobile/lib/models/ml/face/box.dart b/mobile/apps/photos/lib/models/ml/face/box.dart similarity index 100% rename from mobile/lib/models/ml/face/box.dart rename to mobile/apps/photos/lib/models/ml/face/box.dart diff --git a/mobile/lib/models/ml/face/detection.dart b/mobile/apps/photos/lib/models/ml/face/detection.dart similarity index 100% rename from mobile/lib/models/ml/face/detection.dart rename to mobile/apps/photos/lib/models/ml/face/detection.dart diff --git a/mobile/lib/models/ml/face/dimension.dart b/mobile/apps/photos/lib/models/ml/face/dimension.dart similarity index 100% rename from mobile/lib/models/ml/face/dimension.dart rename to mobile/apps/photos/lib/models/ml/face/dimension.dart diff --git a/mobile/lib/models/ml/face/face.dart b/mobile/apps/photos/lib/models/ml/face/face.dart similarity index 100% rename from mobile/lib/models/ml/face/face.dart rename to mobile/apps/photos/lib/models/ml/face/face.dart diff --git a/mobile/lib/models/ml/face/face_with_embedding.dart b/mobile/apps/photos/lib/models/ml/face/face_with_embedding.dart similarity index 100% rename from mobile/lib/models/ml/face/face_with_embedding.dart rename to mobile/apps/photos/lib/models/ml/face/face_with_embedding.dart diff --git a/mobile/lib/models/ml/face/landmark.dart b/mobile/apps/photos/lib/models/ml/face/landmark.dart similarity index 100% rename from mobile/lib/models/ml/face/landmark.dart rename to mobile/apps/photos/lib/models/ml/face/landmark.dart diff --git a/mobile/lib/models/ml/face/person.dart b/mobile/apps/photos/lib/models/ml/face/person.dart similarity index 100% rename from mobile/lib/models/ml/face/person.dart rename to mobile/apps/photos/lib/models/ml/face/person.dart diff --git a/mobile/lib/models/ml/ml_typedefs.dart b/mobile/apps/photos/lib/models/ml/ml_typedefs.dart similarity index 100% rename from mobile/lib/models/ml/ml_typedefs.dart rename to mobile/apps/photos/lib/models/ml/ml_typedefs.dart diff --git a/mobile/lib/models/ml/ml_versions.dart b/mobile/apps/photos/lib/models/ml/ml_versions.dart similarity index 100% rename from mobile/lib/models/ml/ml_versions.dart rename to mobile/apps/photos/lib/models/ml/ml_versions.dart diff --git a/mobile/lib/models/ml/vector.dart b/mobile/apps/photos/lib/models/ml/vector.dart similarity index 100% rename from mobile/lib/models/ml/vector.dart rename to mobile/apps/photos/lib/models/ml/vector.dart diff --git a/mobile/lib/models/preview/playlist_data.dart b/mobile/apps/photos/lib/models/preview/playlist_data.dart similarity index 100% rename from mobile/lib/models/preview/playlist_data.dart rename to mobile/apps/photos/lib/models/preview/playlist_data.dart diff --git a/mobile/lib/models/preview/preview_item.dart b/mobile/apps/photos/lib/models/preview/preview_item.dart similarity index 100% rename from mobile/lib/models/preview/preview_item.dart rename to mobile/apps/photos/lib/models/preview/preview_item.dart diff --git a/mobile/lib/models/preview/preview_item_status.dart b/mobile/apps/photos/lib/models/preview/preview_item_status.dart similarity index 100% rename from mobile/lib/models/preview/preview_item_status.dart rename to mobile/apps/photos/lib/models/preview/preview_item_status.dart diff --git a/mobile/lib/models/search/album_search_result.dart b/mobile/apps/photos/lib/models/search/album_search_result.dart similarity index 100% rename from mobile/lib/models/search/album_search_result.dart rename to mobile/apps/photos/lib/models/search/album_search_result.dart diff --git a/mobile/lib/models/search/generic_search_result.dart b/mobile/apps/photos/lib/models/search/generic_search_result.dart similarity index 100% rename from mobile/lib/models/search/generic_search_result.dart rename to mobile/apps/photos/lib/models/search/generic_search_result.dart diff --git a/mobile/lib/models/search/hierarchical/album_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/album_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/album_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/album_filter.dart diff --git a/mobile/lib/models/search/hierarchical/contacts_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/contacts_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/contacts_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/contacts_filter.dart diff --git a/mobile/lib/models/search/hierarchical/face_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/face_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/face_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/face_filter.dart diff --git a/mobile/lib/models/search/hierarchical/file_type_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/file_type_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/file_type_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/file_type_filter.dart diff --git a/mobile/lib/models/search/hierarchical/hierarchical_search_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/hierarchical_search_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/hierarchical_search_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/hierarchical_search_filter.dart diff --git a/mobile/lib/models/search/hierarchical/location_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/location_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/location_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/location_filter.dart diff --git a/mobile/lib/models/search/hierarchical/magic_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/magic_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/magic_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/magic_filter.dart diff --git a/mobile/lib/models/search/hierarchical/only_them_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/only_them_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/only_them_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/only_them_filter.dart diff --git a/mobile/lib/models/search/hierarchical/top_level_generic_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/top_level_generic_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/top_level_generic_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/top_level_generic_filter.dart diff --git a/mobile/lib/models/search/hierarchical/uploader_filter.dart b/mobile/apps/photos/lib/models/search/hierarchical/uploader_filter.dart similarity index 100% rename from mobile/lib/models/search/hierarchical/uploader_filter.dart rename to mobile/apps/photos/lib/models/search/hierarchical/uploader_filter.dart diff --git a/mobile/lib/models/search/index_of_indexed_stack.dart b/mobile/apps/photos/lib/models/search/index_of_indexed_stack.dart similarity index 100% rename from mobile/lib/models/search/index_of_indexed_stack.dart rename to mobile/apps/photos/lib/models/search/index_of_indexed_stack.dart diff --git a/mobile/lib/models/search/recent_searches.dart b/mobile/apps/photos/lib/models/search/recent_searches.dart similarity index 100% rename from mobile/lib/models/search/recent_searches.dart rename to mobile/apps/photos/lib/models/search/recent_searches.dart diff --git a/mobile/lib/models/search/search_constants.dart b/mobile/apps/photos/lib/models/search/search_constants.dart similarity index 100% rename from mobile/lib/models/search/search_constants.dart rename to mobile/apps/photos/lib/models/search/search_constants.dart diff --git a/mobile/lib/models/search/search_result.dart b/mobile/apps/photos/lib/models/search/search_result.dart similarity index 100% rename from mobile/lib/models/search/search_result.dart rename to mobile/apps/photos/lib/models/search/search_result.dart diff --git a/mobile/lib/models/search/search_types.dart b/mobile/apps/photos/lib/models/search/search_types.dart similarity index 100% rename from mobile/lib/models/search/search_types.dart rename to mobile/apps/photos/lib/models/search/search_types.dart diff --git a/mobile/lib/models/selected_albums.dart b/mobile/apps/photos/lib/models/selected_albums.dart similarity index 100% rename from mobile/lib/models/selected_albums.dart rename to mobile/apps/photos/lib/models/selected_albums.dart diff --git a/mobile/lib/models/selected_files.dart b/mobile/apps/photos/lib/models/selected_files.dart similarity index 100% rename from mobile/lib/models/selected_files.dart rename to mobile/apps/photos/lib/models/selected_files.dart diff --git a/mobile/lib/models/selected_people.dart b/mobile/apps/photos/lib/models/selected_people.dart similarity index 100% rename from mobile/lib/models/selected_people.dart rename to mobile/apps/photos/lib/models/selected_people.dart diff --git a/mobile/lib/models/typedefs.dart b/mobile/apps/photos/lib/models/typedefs.dart similarity index 100% rename from mobile/lib/models/typedefs.dart rename to mobile/apps/photos/lib/models/typedefs.dart diff --git a/mobile/lib/models/upload_strategy.dart b/mobile/apps/photos/lib/models/upload_strategy.dart similarity index 100% rename from mobile/lib/models/upload_strategy.dart rename to mobile/apps/photos/lib/models/upload_strategy.dart diff --git a/mobile/lib/models/user_details.dart b/mobile/apps/photos/lib/models/user_details.dart similarity index 100% rename from mobile/lib/models/user_details.dart rename to mobile/apps/photos/lib/models/user_details.dart diff --git a/mobile/lib/module/download/file_url.dart b/mobile/apps/photos/lib/module/download/file_url.dart similarity index 100% rename from mobile/lib/module/download/file_url.dart rename to mobile/apps/photos/lib/module/download/file_url.dart diff --git a/mobile/lib/module/download/manager.dart b/mobile/apps/photos/lib/module/download/manager.dart similarity index 100% rename from mobile/lib/module/download/manager.dart rename to mobile/apps/photos/lib/module/download/manager.dart diff --git a/mobile/lib/module/download/task.dart b/mobile/apps/photos/lib/module/download/task.dart similarity index 100% rename from mobile/lib/module/download/task.dart rename to mobile/apps/photos/lib/module/download/task.dart diff --git a/mobile/lib/module/upload/model/multipart.dart b/mobile/apps/photos/lib/module/upload/model/multipart.dart similarity index 100% rename from mobile/lib/module/upload/model/multipart.dart rename to mobile/apps/photos/lib/module/upload/model/multipart.dart diff --git a/mobile/lib/module/upload/model/upload_url.dart b/mobile/apps/photos/lib/module/upload/model/upload_url.dart similarity index 100% rename from mobile/lib/module/upload/model/upload_url.dart rename to mobile/apps/photos/lib/module/upload/model/upload_url.dart diff --git a/mobile/lib/module/upload/model/xml.dart b/mobile/apps/photos/lib/module/upload/model/xml.dart similarity index 100% rename from mobile/lib/module/upload/model/xml.dart rename to mobile/apps/photos/lib/module/upload/model/xml.dart diff --git a/mobile/lib/module/upload/service/multipart.dart b/mobile/apps/photos/lib/module/upload/service/multipart.dart similarity index 100% rename from mobile/lib/module/upload/service/multipart.dart rename to mobile/apps/photos/lib/module/upload/service/multipart.dart diff --git a/mobile/lib/service_locator.dart b/mobile/apps/photos/lib/service_locator.dart similarity index 100% rename from mobile/lib/service_locator.dart rename to mobile/apps/photos/lib/service_locator.dart diff --git a/mobile/lib/services/account/billing_service.dart b/mobile/apps/photos/lib/services/account/billing_service.dart similarity index 100% rename from mobile/lib/services/account/billing_service.dart rename to mobile/apps/photos/lib/services/account/billing_service.dart diff --git a/mobile/lib/services/account/passkey_service.dart b/mobile/apps/photos/lib/services/account/passkey_service.dart similarity index 100% rename from mobile/lib/services/account/passkey_service.dart rename to mobile/apps/photos/lib/services/account/passkey_service.dart diff --git a/mobile/lib/services/account/user_service.dart b/mobile/apps/photos/lib/services/account/user_service.dart similarity index 100% rename from mobile/lib/services/account/user_service.dart rename to mobile/apps/photos/lib/services/account/user_service.dart diff --git a/mobile/lib/services/album_home_widget_service.dart b/mobile/apps/photos/lib/services/album_home_widget_service.dart similarity index 100% rename from mobile/lib/services/album_home_widget_service.dart rename to mobile/apps/photos/lib/services/album_home_widget_service.dart diff --git a/mobile/lib/services/app_lifecycle_service.dart b/mobile/apps/photos/lib/services/app_lifecycle_service.dart similarity index 100% rename from mobile/lib/services/app_lifecycle_service.dart rename to mobile/apps/photos/lib/services/app_lifecycle_service.dart diff --git a/mobile/lib/services/collections_service.dart b/mobile/apps/photos/lib/services/collections_service.dart similarity index 100% rename from mobile/lib/services/collections_service.dart rename to mobile/apps/photos/lib/services/collections_service.dart diff --git a/mobile/lib/services/deduplication_service.dart b/mobile/apps/photos/lib/services/deduplication_service.dart similarity index 100% rename from mobile/lib/services/deduplication_service.dart rename to mobile/apps/photos/lib/services/deduplication_service.dart diff --git a/mobile/lib/services/entity_service.dart b/mobile/apps/photos/lib/services/entity_service.dart similarity index 100% rename from mobile/lib/services/entity_service.dart rename to mobile/apps/photos/lib/services/entity_service.dart diff --git a/mobile/lib/services/favorites_service.dart b/mobile/apps/photos/lib/services/favorites_service.dart similarity index 100% rename from mobile/lib/services/favorites_service.dart rename to mobile/apps/photos/lib/services/favorites_service.dart diff --git a/mobile/lib/services/file_magic_service.dart b/mobile/apps/photos/lib/services/file_magic_service.dart similarity index 100% rename from mobile/lib/services/file_magic_service.dart rename to mobile/apps/photos/lib/services/file_magic_service.dart diff --git a/mobile/lib/services/filedata/filedata_service.dart b/mobile/apps/photos/lib/services/filedata/filedata_service.dart similarity index 100% rename from mobile/lib/services/filedata/filedata_service.dart rename to mobile/apps/photos/lib/services/filedata/filedata_service.dart diff --git a/mobile/lib/services/filedata/model/enc_file_data.dart b/mobile/apps/photos/lib/services/filedata/model/enc_file_data.dart similarity index 100% rename from mobile/lib/services/filedata/model/enc_file_data.dart rename to mobile/apps/photos/lib/services/filedata/model/enc_file_data.dart diff --git a/mobile/lib/services/filedata/model/file_data.dart b/mobile/apps/photos/lib/services/filedata/model/file_data.dart similarity index 100% rename from mobile/lib/services/filedata/model/file_data.dart rename to mobile/apps/photos/lib/services/filedata/model/file_data.dart diff --git a/mobile/lib/services/filedata/model/response.dart b/mobile/apps/photos/lib/services/filedata/model/response.dart similarity index 100% rename from mobile/lib/services/filedata/model/response.dart rename to mobile/apps/photos/lib/services/filedata/model/response.dart diff --git a/mobile/lib/services/files_service.dart b/mobile/apps/photos/lib/services/files_service.dart similarity index 100% rename from mobile/lib/services/files_service.dart rename to mobile/apps/photos/lib/services/files_service.dart diff --git a/mobile/lib/services/filter/collection_ignore.dart b/mobile/apps/photos/lib/services/filter/collection_ignore.dart similarity index 100% rename from mobile/lib/services/filter/collection_ignore.dart rename to mobile/apps/photos/lib/services/filter/collection_ignore.dart diff --git a/mobile/lib/services/filter/db_filters.dart b/mobile/apps/photos/lib/services/filter/db_filters.dart similarity index 100% rename from mobile/lib/services/filter/db_filters.dart rename to mobile/apps/photos/lib/services/filter/db_filters.dart diff --git a/mobile/lib/services/filter/dedupe_by_upload_id.dart b/mobile/apps/photos/lib/services/filter/dedupe_by_upload_id.dart similarity index 100% rename from mobile/lib/services/filter/dedupe_by_upload_id.dart rename to mobile/apps/photos/lib/services/filter/dedupe_by_upload_id.dart diff --git a/mobile/lib/services/filter/filter.dart b/mobile/apps/photos/lib/services/filter/filter.dart similarity index 100% rename from mobile/lib/services/filter/filter.dart rename to mobile/apps/photos/lib/services/filter/filter.dart diff --git a/mobile/lib/services/filter/only_uploaded_files_filter.dart b/mobile/apps/photos/lib/services/filter/only_uploaded_files_filter.dart similarity index 100% rename from mobile/lib/services/filter/only_uploaded_files_filter.dart rename to mobile/apps/photos/lib/services/filter/only_uploaded_files_filter.dart diff --git a/mobile/lib/services/filter/shared.dart b/mobile/apps/photos/lib/services/filter/shared.dart similarity index 100% rename from mobile/lib/services/filter/shared.dart rename to mobile/apps/photos/lib/services/filter/shared.dart diff --git a/mobile/lib/services/filter/type_filter.dart b/mobile/apps/photos/lib/services/filter/type_filter.dart similarity index 100% rename from mobile/lib/services/filter/type_filter.dart rename to mobile/apps/photos/lib/services/filter/type_filter.dart diff --git a/mobile/lib/services/filter/upload_ignore.dart b/mobile/apps/photos/lib/services/filter/upload_ignore.dart similarity index 100% rename from mobile/lib/services/filter/upload_ignore.dart rename to mobile/apps/photos/lib/services/filter/upload_ignore.dart diff --git a/mobile/lib/services/hidden_service.dart b/mobile/apps/photos/lib/services/hidden_service.dart similarity index 100% rename from mobile/lib/services/hidden_service.dart rename to mobile/apps/photos/lib/services/hidden_service.dart diff --git a/mobile/lib/services/home_widget_service.dart b/mobile/apps/photos/lib/services/home_widget_service.dart similarity index 100% rename from mobile/lib/services/home_widget_service.dart rename to mobile/apps/photos/lib/services/home_widget_service.dart diff --git a/mobile/lib/services/ignored_files_service.dart b/mobile/apps/photos/lib/services/ignored_files_service.dart similarity index 100% rename from mobile/lib/services/ignored_files_service.dart rename to mobile/apps/photos/lib/services/ignored_files_service.dart diff --git a/mobile/lib/services/isolate_functions.dart b/mobile/apps/photos/lib/services/isolate_functions.dart similarity index 100% rename from mobile/lib/services/isolate_functions.dart rename to mobile/apps/photos/lib/services/isolate_functions.dart diff --git a/mobile/lib/services/isolate_service.dart b/mobile/apps/photos/lib/services/isolate_service.dart similarity index 100% rename from mobile/lib/services/isolate_service.dart rename to mobile/apps/photos/lib/services/isolate_service.dart diff --git a/mobile/lib/services/language_service.dart b/mobile/apps/photos/lib/services/language_service.dart similarity index 100% rename from mobile/lib/services/language_service.dart rename to mobile/apps/photos/lib/services/language_service.dart diff --git a/mobile/lib/services/local_authentication_service.dart b/mobile/apps/photos/lib/services/local_authentication_service.dart similarity index 100% rename from mobile/lib/services/local_authentication_service.dart rename to mobile/apps/photos/lib/services/local_authentication_service.dart diff --git a/mobile/lib/services/local_file_update_service.dart b/mobile/apps/photos/lib/services/local_file_update_service.dart similarity index 100% rename from mobile/lib/services/local_file_update_service.dart rename to mobile/apps/photos/lib/services/local_file_update_service.dart diff --git a/mobile/lib/services/location_service.dart b/mobile/apps/photos/lib/services/location_service.dart similarity index 100% rename from mobile/lib/services/location_service.dart rename to mobile/apps/photos/lib/services/location_service.dart diff --git a/mobile/lib/services/machine_learning/compute_controller.dart b/mobile/apps/photos/lib/services/machine_learning/compute_controller.dart similarity index 100% rename from mobile/lib/services/machine_learning/compute_controller.dart rename to mobile/apps/photos/lib/services/machine_learning/compute_controller.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_alignment/alignment_result.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_alignment/alignment_result.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_alignment/alignment_result.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_alignment/alignment_result.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_alignment/similarity_transform.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_alignment/similarity_transform.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_alignment/similarity_transform.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_alignment/similarity_transform.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_clustering/face_clustering_service.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_clustering/face_db_info_for_clustering.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_detection/detection.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_detection/detection.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_detection/detection.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_detection/detection.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_detection/face_detection_postprocessing.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_detection/face_detection_postprocessing.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_detection/face_detection_postprocessing.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_detection/face_detection_postprocessing.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_detection/face_detection_service.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_detection/face_detection_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_detection/face_detection_service.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_detection/face_detection_service.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_embedding/face_embedding_service.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_filtering/blur_detection_service.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_filtering/blur_detection_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_filtering/blur_detection_service.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_filtering/blur_detection_service.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart diff --git a/mobile/lib/services/machine_learning/face_ml/face_recognition_service.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/face_recognition_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/face_recognition_service.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/face_recognition_service.dart diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart diff --git a/mobile/lib/services/machine_learning/face_ml/person/person_service.dart b/mobile/apps/photos/lib/services/machine_learning/face_ml/person/person_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_ml/person/person_service.dart rename to mobile/apps/photos/lib/services/machine_learning/face_ml/person/person_service.dart diff --git a/mobile/lib/services/machine_learning/face_thumbnail_generator.dart b/mobile/apps/photos/lib/services/machine_learning/face_thumbnail_generator.dart similarity index 100% rename from mobile/lib/services/machine_learning/face_thumbnail_generator.dart rename to mobile/apps/photos/lib/services/machine_learning/face_thumbnail_generator.dart diff --git a/mobile/lib/services/machine_learning/ml_computer.dart b/mobile/apps/photos/lib/services/machine_learning/ml_computer.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_computer.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_computer.dart diff --git a/mobile/lib/services/machine_learning/ml_constants.dart b/mobile/apps/photos/lib/services/machine_learning/ml_constants.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_constants.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_constants.dart diff --git a/mobile/lib/services/machine_learning/ml_exceptions.dart b/mobile/apps/photos/lib/services/machine_learning/ml_exceptions.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_exceptions.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_exceptions.dart diff --git a/mobile/lib/services/machine_learning/ml_indexing_isolate.dart b/mobile/apps/photos/lib/services/machine_learning/ml_indexing_isolate.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_indexing_isolate.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_indexing_isolate.dart diff --git a/mobile/lib/services/machine_learning/ml_model.dart b/mobile/apps/photos/lib/services/machine_learning/ml_model.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_model.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_model.dart diff --git a/mobile/lib/services/machine_learning/ml_models_overview.dart b/mobile/apps/photos/lib/services/machine_learning/ml_models_overview.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_models_overview.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_models_overview.dart diff --git a/mobile/lib/services/machine_learning/ml_result.dart b/mobile/apps/photos/lib/services/machine_learning/ml_result.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_result.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_result.dart diff --git a/mobile/lib/services/machine_learning/ml_service.dart b/mobile/apps/photos/lib/services/machine_learning/ml_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/ml_service.dart rename to mobile/apps/photos/lib/services/machine_learning/ml_service.dart diff --git a/mobile/lib/services/machine_learning/onnx_env.dart b/mobile/apps/photos/lib/services/machine_learning/onnx_env.dart similarity index 100% rename from mobile/lib/services/machine_learning/onnx_env.dart rename to mobile/apps/photos/lib/services/machine_learning/onnx_env.dart diff --git a/mobile/lib/services/machine_learning/semantic_search/clip/clip_image_encoder.dart b/mobile/apps/photos/lib/services/machine_learning/semantic_search/clip/clip_image_encoder.dart similarity index 100% rename from mobile/lib/services/machine_learning/semantic_search/clip/clip_image_encoder.dart rename to mobile/apps/photos/lib/services/machine_learning/semantic_search/clip/clip_image_encoder.dart diff --git a/mobile/lib/services/machine_learning/semantic_search/clip/clip_text_encoder.dart b/mobile/apps/photos/lib/services/machine_learning/semantic_search/clip/clip_text_encoder.dart similarity index 100% rename from mobile/lib/services/machine_learning/semantic_search/clip/clip_text_encoder.dart rename to mobile/apps/photos/lib/services/machine_learning/semantic_search/clip/clip_text_encoder.dart diff --git a/mobile/lib/services/machine_learning/semantic_search/clip/clip_text_tokenizer.dart b/mobile/apps/photos/lib/services/machine_learning/semantic_search/clip/clip_text_tokenizer.dart similarity index 100% rename from mobile/lib/services/machine_learning/semantic_search/clip/clip_text_tokenizer.dart rename to mobile/apps/photos/lib/services/machine_learning/semantic_search/clip/clip_text_tokenizer.dart diff --git a/mobile/lib/services/machine_learning/semantic_search/query_result.dart b/mobile/apps/photos/lib/services/machine_learning/semantic_search/query_result.dart similarity index 100% rename from mobile/lib/services/machine_learning/semantic_search/query_result.dart rename to mobile/apps/photos/lib/services/machine_learning/semantic_search/query_result.dart diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/apps/photos/lib/services/machine_learning/semantic_search/semantic_search_service.dart similarity index 100% rename from mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart rename to mobile/apps/photos/lib/services/machine_learning/semantic_search/semantic_search_service.dart diff --git a/mobile/lib/services/magic_cache_service.dart b/mobile/apps/photos/lib/services/magic_cache_service.dart similarity index 100% rename from mobile/lib/services/magic_cache_service.dart rename to mobile/apps/photos/lib/services/magic_cache_service.dart diff --git a/mobile/lib/services/memories_cache_service.dart b/mobile/apps/photos/lib/services/memories_cache_service.dart similarity index 100% rename from mobile/lib/services/memories_cache_service.dart rename to mobile/apps/photos/lib/services/memories_cache_service.dart diff --git a/mobile/lib/services/memory_home_widget_service.dart b/mobile/apps/photos/lib/services/memory_home_widget_service.dart similarity index 100% rename from mobile/lib/services/memory_home_widget_service.dart rename to mobile/apps/photos/lib/services/memory_home_widget_service.dart diff --git a/mobile/lib/services/notification_service.dart b/mobile/apps/photos/lib/services/notification_service.dart similarity index 100% rename from mobile/lib/services/notification_service.dart rename to mobile/apps/photos/lib/services/notification_service.dart diff --git a/mobile/lib/services/people_home_widget_service.dart b/mobile/apps/photos/lib/services/people_home_widget_service.dart similarity index 100% rename from mobile/lib/services/people_home_widget_service.dart rename to mobile/apps/photos/lib/services/people_home_widget_service.dart diff --git a/mobile/lib/services/permission/service.dart b/mobile/apps/photos/lib/services/permission/service.dart similarity index 100% rename from mobile/lib/services/permission/service.dart rename to mobile/apps/photos/lib/services/permission/service.dart diff --git a/mobile/lib/services/push_service.dart b/mobile/apps/photos/lib/services/push_service.dart similarity index 100% rename from mobile/lib/services/push_service.dart rename to mobile/apps/photos/lib/services/push_service.dart diff --git a/mobile/lib/services/remote_assets_service.dart b/mobile/apps/photos/lib/services/remote_assets_service.dart similarity index 100% rename from mobile/lib/services/remote_assets_service.dart rename to mobile/apps/photos/lib/services/remote_assets_service.dart diff --git a/mobile/lib/services/search_service.dart b/mobile/apps/photos/lib/services/search_service.dart similarity index 100% rename from mobile/lib/services/search_service.dart rename to mobile/apps/photos/lib/services/search_service.dart diff --git a/mobile/lib/services/smart_memories_service.dart b/mobile/apps/photos/lib/services/smart_memories_service.dart similarity index 100% rename from mobile/lib/services/smart_memories_service.dart rename to mobile/apps/photos/lib/services/smart_memories_service.dart diff --git a/mobile/lib/services/storage_bonus_service.dart b/mobile/apps/photos/lib/services/storage_bonus_service.dart similarity index 100% rename from mobile/lib/services/storage_bonus_service.dart rename to mobile/apps/photos/lib/services/storage_bonus_service.dart diff --git a/mobile/lib/services/sync/diff_fetcher.dart b/mobile/apps/photos/lib/services/sync/diff_fetcher.dart similarity index 100% rename from mobile/lib/services/sync/diff_fetcher.dart rename to mobile/apps/photos/lib/services/sync/diff_fetcher.dart diff --git a/mobile/lib/services/sync/import/diff.dart b/mobile/apps/photos/lib/services/sync/import/diff.dart similarity index 100% rename from mobile/lib/services/sync/import/diff.dart rename to mobile/apps/photos/lib/services/sync/import/diff.dart diff --git a/mobile/lib/services/sync/import/local_assets.dart b/mobile/apps/photos/lib/services/sync/import/local_assets.dart similarity index 100% rename from mobile/lib/services/sync/import/local_assets.dart rename to mobile/apps/photos/lib/services/sync/import/local_assets.dart diff --git a/mobile/lib/services/sync/import/model.dart b/mobile/apps/photos/lib/services/sync/import/model.dart similarity index 100% rename from mobile/lib/services/sync/import/model.dart rename to mobile/apps/photos/lib/services/sync/import/model.dart diff --git a/mobile/lib/services/sync/local_sync_service.dart b/mobile/apps/photos/lib/services/sync/local_sync_service.dart similarity index 100% rename from mobile/lib/services/sync/local_sync_service.dart rename to mobile/apps/photos/lib/services/sync/local_sync_service.dart diff --git a/mobile/lib/services/sync/remote_sync_service.dart b/mobile/apps/photos/lib/services/sync/remote_sync_service.dart similarity index 100% rename from mobile/lib/services/sync/remote_sync_service.dart rename to mobile/apps/photos/lib/services/sync/remote_sync_service.dart diff --git a/mobile/lib/services/sync/sync_service.dart b/mobile/apps/photos/lib/services/sync/sync_service.dart similarity index 100% rename from mobile/lib/services/sync/sync_service.dart rename to mobile/apps/photos/lib/services/sync/sync_service.dart diff --git a/mobile/lib/services/sync/trash_sync_service.dart b/mobile/apps/photos/lib/services/sync/trash_sync_service.dart similarity index 100% rename from mobile/lib/services/sync/trash_sync_service.dart rename to mobile/apps/photos/lib/services/sync/trash_sync_service.dart diff --git a/mobile/lib/services/update_service.dart b/mobile/apps/photos/lib/services/update_service.dart similarity index 100% rename from mobile/lib/services/update_service.dart rename to mobile/apps/photos/lib/services/update_service.dart diff --git a/mobile/lib/services/video_memory_service.dart b/mobile/apps/photos/lib/services/video_memory_service.dart similarity index 100% rename from mobile/lib/services/video_memory_service.dart rename to mobile/apps/photos/lib/services/video_memory_service.dart diff --git a/mobile/lib/services/video_preview_service.dart b/mobile/apps/photos/lib/services/video_preview_service.dart similarity index 100% rename from mobile/lib/services/video_preview_service.dart rename to mobile/apps/photos/lib/services/video_preview_service.dart diff --git a/mobile/lib/services/wake_lock_service.dart b/mobile/apps/photos/lib/services/wake_lock_service.dart similarity index 100% rename from mobile/lib/services/wake_lock_service.dart rename to mobile/apps/photos/lib/services/wake_lock_service.dart diff --git a/mobile/lib/states/all_sections_examples_state.dart b/mobile/apps/photos/lib/states/all_sections_examples_state.dart similarity index 100% rename from mobile/lib/states/all_sections_examples_state.dart rename to mobile/apps/photos/lib/states/all_sections_examples_state.dart diff --git a/mobile/lib/states/detail_page_state.dart b/mobile/apps/photos/lib/states/detail_page_state.dart similarity index 100% rename from mobile/lib/states/detail_page_state.dart rename to mobile/apps/photos/lib/states/detail_page_state.dart diff --git a/mobile/lib/states/location_screen_state.dart b/mobile/apps/photos/lib/states/location_screen_state.dart similarity index 100% rename from mobile/lib/states/location_screen_state.dart rename to mobile/apps/photos/lib/states/location_screen_state.dart diff --git a/mobile/lib/states/location_state.dart b/mobile/apps/photos/lib/states/location_state.dart similarity index 100% rename from mobile/lib/states/location_state.dart rename to mobile/apps/photos/lib/states/location_state.dart diff --git a/mobile/lib/states/user_details_state.dart b/mobile/apps/photos/lib/states/user_details_state.dart similarity index 100% rename from mobile/lib/states/user_details_state.dart rename to mobile/apps/photos/lib/states/user_details_state.dart diff --git a/mobile/lib/theme/colors.dart b/mobile/apps/photos/lib/theme/colors.dart similarity index 100% rename from mobile/lib/theme/colors.dart rename to mobile/apps/photos/lib/theme/colors.dart diff --git a/mobile/lib/theme/effects.dart b/mobile/apps/photos/lib/theme/effects.dart similarity index 100% rename from mobile/lib/theme/effects.dart rename to mobile/apps/photos/lib/theme/effects.dart diff --git a/mobile/lib/theme/ente_theme.dart b/mobile/apps/photos/lib/theme/ente_theme.dart similarity index 100% rename from mobile/lib/theme/ente_theme.dart rename to mobile/apps/photos/lib/theme/ente_theme.dart diff --git a/mobile/lib/theme/text_style.dart b/mobile/apps/photos/lib/theme/text_style.dart similarity index 100% rename from mobile/lib/theme/text_style.dart rename to mobile/apps/photos/lib/theme/text_style.dart diff --git a/mobile/lib/ui/account/change_email_dialog.dart b/mobile/apps/photos/lib/ui/account/change_email_dialog.dart similarity index 100% rename from mobile/lib/ui/account/change_email_dialog.dart rename to mobile/apps/photos/lib/ui/account/change_email_dialog.dart diff --git a/mobile/lib/ui/account/delete_account_page.dart b/mobile/apps/photos/lib/ui/account/delete_account_page.dart similarity index 100% rename from mobile/lib/ui/account/delete_account_page.dart rename to mobile/apps/photos/lib/ui/account/delete_account_page.dart diff --git a/mobile/lib/ui/account/email_entry_page.dart b/mobile/apps/photos/lib/ui/account/email_entry_page.dart similarity index 100% rename from mobile/lib/ui/account/email_entry_page.dart rename to mobile/apps/photos/lib/ui/account/email_entry_page.dart diff --git a/mobile/lib/ui/account/login_page.dart b/mobile/apps/photos/lib/ui/account/login_page.dart similarity index 100% rename from mobile/lib/ui/account/login_page.dart rename to mobile/apps/photos/lib/ui/account/login_page.dart diff --git a/mobile/lib/ui/account/login_pwd_verification_page.dart b/mobile/apps/photos/lib/ui/account/login_pwd_verification_page.dart similarity index 100% rename from mobile/lib/ui/account/login_pwd_verification_page.dart rename to mobile/apps/photos/lib/ui/account/login_pwd_verification_page.dart diff --git a/mobile/lib/ui/account/ott_verification_page.dart b/mobile/apps/photos/lib/ui/account/ott_verification_page.dart similarity index 100% rename from mobile/lib/ui/account/ott_verification_page.dart rename to mobile/apps/photos/lib/ui/account/ott_verification_page.dart diff --git a/mobile/lib/ui/account/passkey_page.dart b/mobile/apps/photos/lib/ui/account/passkey_page.dart similarity index 100% rename from mobile/lib/ui/account/passkey_page.dart rename to mobile/apps/photos/lib/ui/account/passkey_page.dart diff --git a/mobile/lib/ui/account/password_entry_page.dart b/mobile/apps/photos/lib/ui/account/password_entry_page.dart similarity index 100% rename from mobile/lib/ui/account/password_entry_page.dart rename to mobile/apps/photos/lib/ui/account/password_entry_page.dart diff --git a/mobile/lib/ui/account/password_reentry_page.dart b/mobile/apps/photos/lib/ui/account/password_reentry_page.dart similarity index 100% rename from mobile/lib/ui/account/password_reentry_page.dart rename to mobile/apps/photos/lib/ui/account/password_reentry_page.dart diff --git a/mobile/lib/ui/account/recovery_key_page.dart b/mobile/apps/photos/lib/ui/account/recovery_key_page.dart similarity index 100% rename from mobile/lib/ui/account/recovery_key_page.dart rename to mobile/apps/photos/lib/ui/account/recovery_key_page.dart diff --git a/mobile/lib/ui/account/recovery_page.dart b/mobile/apps/photos/lib/ui/account/recovery_page.dart similarity index 100% rename from mobile/lib/ui/account/recovery_page.dart rename to mobile/apps/photos/lib/ui/account/recovery_page.dart diff --git a/mobile/lib/ui/account/request_pwd_verification_page.dart b/mobile/apps/photos/lib/ui/account/request_pwd_verification_page.dart similarity index 100% rename from mobile/lib/ui/account/request_pwd_verification_page.dart rename to mobile/apps/photos/lib/ui/account/request_pwd_verification_page.dart diff --git a/mobile/lib/ui/account/sessions_page.dart b/mobile/apps/photos/lib/ui/account/sessions_page.dart similarity index 100% rename from mobile/lib/ui/account/sessions_page.dart rename to mobile/apps/photos/lib/ui/account/sessions_page.dart diff --git a/mobile/lib/ui/account/two_factor_authentication_page.dart b/mobile/apps/photos/lib/ui/account/two_factor_authentication_page.dart similarity index 100% rename from mobile/lib/ui/account/two_factor_authentication_page.dart rename to mobile/apps/photos/lib/ui/account/two_factor_authentication_page.dart diff --git a/mobile/lib/ui/account/two_factor_recovery_page.dart b/mobile/apps/photos/lib/ui/account/two_factor_recovery_page.dart similarity index 100% rename from mobile/lib/ui/account/two_factor_recovery_page.dart rename to mobile/apps/photos/lib/ui/account/two_factor_recovery_page.dart diff --git a/mobile/lib/ui/account/two_factor_setup_page.dart b/mobile/apps/photos/lib/ui/account/two_factor_setup_page.dart similarity index 100% rename from mobile/lib/ui/account/two_factor_setup_page.dart rename to mobile/apps/photos/lib/ui/account/two_factor_setup_page.dart diff --git a/mobile/lib/ui/account/verify_recovery_page.dart b/mobile/apps/photos/lib/ui/account/verify_recovery_page.dart similarity index 100% rename from mobile/lib/ui/account/verify_recovery_page.dart rename to mobile/apps/photos/lib/ui/account/verify_recovery_page.dart diff --git a/mobile/lib/ui/actions/collection/collection_file_actions.dart b/mobile/apps/photos/lib/ui/actions/collection/collection_file_actions.dart similarity index 100% rename from mobile/lib/ui/actions/collection/collection_file_actions.dart rename to mobile/apps/photos/lib/ui/actions/collection/collection_file_actions.dart diff --git a/mobile/lib/ui/actions/collection/collection_sharing_actions.dart b/mobile/apps/photos/lib/ui/actions/collection/collection_sharing_actions.dart similarity index 100% rename from mobile/lib/ui/actions/collection/collection_sharing_actions.dart rename to mobile/apps/photos/lib/ui/actions/collection/collection_sharing_actions.dart diff --git a/mobile/lib/ui/actions/file/file_actions.dart b/mobile/apps/photos/lib/ui/actions/file/file_actions.dart similarity index 100% rename from mobile/lib/ui/actions/file/file_actions.dart rename to mobile/apps/photos/lib/ui/actions/file/file_actions.dart diff --git a/mobile/lib/ui/cast/auto.dart b/mobile/apps/photos/lib/ui/cast/auto.dart similarity index 100% rename from mobile/lib/ui/cast/auto.dart rename to mobile/apps/photos/lib/ui/cast/auto.dart diff --git a/mobile/lib/ui/cast/choose.dart b/mobile/apps/photos/lib/ui/cast/choose.dart similarity index 100% rename from mobile/lib/ui/cast/choose.dart rename to mobile/apps/photos/lib/ui/cast/choose.dart diff --git a/mobile/lib/ui/collections/album/column_item.dart b/mobile/apps/photos/lib/ui/collections/album/column_item.dart similarity index 100% rename from mobile/lib/ui/collections/album/column_item.dart rename to mobile/apps/photos/lib/ui/collections/album/column_item.dart diff --git a/mobile/lib/ui/collections/album/horizontal_list.dart b/mobile/apps/photos/lib/ui/collections/album/horizontal_list.dart similarity index 100% rename from mobile/lib/ui/collections/album/horizontal_list.dart rename to mobile/apps/photos/lib/ui/collections/album/horizontal_list.dart diff --git a/mobile/lib/ui/collections/album/list_item.dart b/mobile/apps/photos/lib/ui/collections/album/list_item.dart similarity index 100% rename from mobile/lib/ui/collections/album/list_item.dart rename to mobile/apps/photos/lib/ui/collections/album/list_item.dart diff --git a/mobile/lib/ui/collections/album/new_list_item.dart b/mobile/apps/photos/lib/ui/collections/album/new_list_item.dart similarity index 100% rename from mobile/lib/ui/collections/album/new_list_item.dart rename to mobile/apps/photos/lib/ui/collections/album/new_list_item.dart diff --git a/mobile/lib/ui/collections/album/new_row_item.dart b/mobile/apps/photos/lib/ui/collections/album/new_row_item.dart similarity index 100% rename from mobile/lib/ui/collections/album/new_row_item.dart rename to mobile/apps/photos/lib/ui/collections/album/new_row_item.dart diff --git a/mobile/lib/ui/collections/album/row_item.dart b/mobile/apps/photos/lib/ui/collections/album/row_item.dart similarity index 100% rename from mobile/lib/ui/collections/album/row_item.dart rename to mobile/apps/photos/lib/ui/collections/album/row_item.dart diff --git a/mobile/lib/ui/collections/album/vertical_list.dart b/mobile/apps/photos/lib/ui/collections/album/vertical_list.dart similarity index 100% rename from mobile/lib/ui/collections/album/vertical_list.dart rename to mobile/apps/photos/lib/ui/collections/album/vertical_list.dart diff --git a/mobile/lib/ui/collections/button/archived_button.dart b/mobile/apps/photos/lib/ui/collections/button/archived_button.dart similarity index 100% rename from mobile/lib/ui/collections/button/archived_button.dart rename to mobile/apps/photos/lib/ui/collections/button/archived_button.dart diff --git a/mobile/lib/ui/collections/button/hidden_button.dart b/mobile/apps/photos/lib/ui/collections/button/hidden_button.dart similarity index 100% rename from mobile/lib/ui/collections/button/hidden_button.dart rename to mobile/apps/photos/lib/ui/collections/button/hidden_button.dart diff --git a/mobile/lib/ui/collections/button/trash_button.dart b/mobile/apps/photos/lib/ui/collections/button/trash_button.dart similarity index 100% rename from mobile/lib/ui/collections/button/trash_button.dart rename to mobile/apps/photos/lib/ui/collections/button/trash_button.dart diff --git a/mobile/lib/ui/collections/button/uncategorized_button.dart b/mobile/apps/photos/lib/ui/collections/button/uncategorized_button.dart similarity index 100% rename from mobile/lib/ui/collections/button/uncategorized_button.dart rename to mobile/apps/photos/lib/ui/collections/button/uncategorized_button.dart diff --git a/mobile/lib/ui/collections/collection_action_sheet.dart b/mobile/apps/photos/lib/ui/collections/collection_action_sheet.dart similarity index 100% rename from mobile/lib/ui/collections/collection_action_sheet.dart rename to mobile/apps/photos/lib/ui/collections/collection_action_sheet.dart diff --git a/mobile/lib/ui/collections/collection_list_page.dart b/mobile/apps/photos/lib/ui/collections/collection_list_page.dart similarity index 100% rename from mobile/lib/ui/collections/collection_list_page.dart rename to mobile/apps/photos/lib/ui/collections/collection_list_page.dart diff --git a/mobile/lib/ui/collections/device/device_folder_item.dart b/mobile/apps/photos/lib/ui/collections/device/device_folder_item.dart similarity index 100% rename from mobile/lib/ui/collections/device/device_folder_item.dart rename to mobile/apps/photos/lib/ui/collections/device/device_folder_item.dart diff --git a/mobile/lib/ui/collections/device/device_folders_grid_view.dart b/mobile/apps/photos/lib/ui/collections/device/device_folders_grid_view.dart similarity index 100% rename from mobile/lib/ui/collections/device/device_folders_grid_view.dart rename to mobile/apps/photos/lib/ui/collections/device/device_folders_grid_view.dart diff --git a/mobile/lib/ui/collections/device/device_folders_vertical_grid_view.dart b/mobile/apps/photos/lib/ui/collections/device/device_folders_vertical_grid_view.dart similarity index 100% rename from mobile/lib/ui/collections/device/device_folders_vertical_grid_view.dart rename to mobile/apps/photos/lib/ui/collections/device/device_folders_vertical_grid_view.dart diff --git a/mobile/lib/ui/collections/flex_grid_view.dart b/mobile/apps/photos/lib/ui/collections/flex_grid_view.dart similarity index 100% rename from mobile/lib/ui/collections/flex_grid_view.dart rename to mobile/apps/photos/lib/ui/collections/flex_grid_view.dart diff --git a/mobile/lib/ui/common/bottom_shadow.dart b/mobile/apps/photos/lib/ui/common/bottom_shadow.dart similarity index 100% rename from mobile/lib/ui/common/bottom_shadow.dart rename to mobile/apps/photos/lib/ui/common/bottom_shadow.dart diff --git a/mobile/lib/ui/common/date_input.dart b/mobile/apps/photos/lib/ui/common/date_input.dart similarity index 100% rename from mobile/lib/ui/common/date_input.dart rename to mobile/apps/photos/lib/ui/common/date_input.dart diff --git a/mobile/lib/ui/common/dynamic_fab.dart b/mobile/apps/photos/lib/ui/common/dynamic_fab.dart similarity index 100% rename from mobile/lib/ui/common/dynamic_fab.dart rename to mobile/apps/photos/lib/ui/common/dynamic_fab.dart diff --git a/mobile/lib/ui/common/fast_scroll_physics.dart b/mobile/apps/photos/lib/ui/common/fast_scroll_physics.dart similarity index 100% rename from mobile/lib/ui/common/fast_scroll_physics.dart rename to mobile/apps/photos/lib/ui/common/fast_scroll_physics.dart diff --git a/mobile/lib/ui/common/gradient_button.dart b/mobile/apps/photos/lib/ui/common/gradient_button.dart similarity index 100% rename from mobile/lib/ui/common/gradient_button.dart rename to mobile/apps/photos/lib/ui/common/gradient_button.dart diff --git a/mobile/lib/ui/common/linear_progress_dialog.dart b/mobile/apps/photos/lib/ui/common/linear_progress_dialog.dart similarity index 100% rename from mobile/lib/ui/common/linear_progress_dialog.dart rename to mobile/apps/photos/lib/ui/common/linear_progress_dialog.dart diff --git a/mobile/lib/ui/common/loading_widget.dart b/mobile/apps/photos/lib/ui/common/loading_widget.dart similarity index 100% rename from mobile/lib/ui/common/loading_widget.dart rename to mobile/apps/photos/lib/ui/common/loading_widget.dart diff --git a/mobile/lib/ui/common/popup_item.dart b/mobile/apps/photos/lib/ui/common/popup_item.dart similarity index 100% rename from mobile/lib/ui/common/popup_item.dart rename to mobile/apps/photos/lib/ui/common/popup_item.dart diff --git a/mobile/lib/ui/common/progress_dialog.dart b/mobile/apps/photos/lib/ui/common/progress_dialog.dart similarity index 100% rename from mobile/lib/ui/common/progress_dialog.dart rename to mobile/apps/photos/lib/ui/common/progress_dialog.dart diff --git a/mobile/lib/ui/common/user_dialogs.dart b/mobile/apps/photos/lib/ui/common/user_dialogs.dart similarity index 100% rename from mobile/lib/ui/common/user_dialogs.dart rename to mobile/apps/photos/lib/ui/common/user_dialogs.dart diff --git a/mobile/lib/ui/common/web_page.dart b/mobile/apps/photos/lib/ui/common/web_page.dart similarity index 100% rename from mobile/lib/ui/common/web_page.dart rename to mobile/apps/photos/lib/ui/common/web_page.dart diff --git a/mobile/lib/ui/components/action_sheet_widget.dart b/mobile/apps/photos/lib/ui/components/action_sheet_widget.dart similarity index 100% rename from mobile/lib/ui/components/action_sheet_widget.dart rename to mobile/apps/photos/lib/ui/components/action_sheet_widget.dart diff --git a/mobile/lib/ui/components/blur_menu_item_widget.dart b/mobile/apps/photos/lib/ui/components/blur_menu_item_widget.dart similarity index 100% rename from mobile/lib/ui/components/blur_menu_item_widget.dart rename to mobile/apps/photos/lib/ui/components/blur_menu_item_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/action_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/action_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/action_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/action_bar_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/album_action_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/album_action_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/album_action_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/album_action_bar_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/album_bottom_action_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/album_bottom_action_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/album_bottom_action_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/album_bottom_action_bar_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/bottom_action_bar_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/expanded_menu_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/expanded_menu_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/expanded_menu_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/expanded_menu_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/people_action_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/people_action_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/people_action_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/people_action_bar_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/people_bottom_action_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/people_bottom_action_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/people_bottom_action_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/people_bottom_action_bar_widget.dart diff --git a/mobile/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_action_bar/selection_action_button_widget.dart diff --git a/mobile/lib/ui/components/bottom_of_title_bar_widget.dart b/mobile/apps/photos/lib/ui/components/bottom_of_title_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/bottom_of_title_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/bottom_of_title_bar_widget.dart diff --git a/mobile/lib/ui/components/buttons/button_widget.dart b/mobile/apps/photos/lib/ui/components/buttons/button_widget.dart similarity index 100% rename from mobile/lib/ui/components/buttons/button_widget.dart rename to mobile/apps/photos/lib/ui/components/buttons/button_widget.dart diff --git a/mobile/lib/ui/components/buttons/chip_button_widget.dart b/mobile/apps/photos/lib/ui/components/buttons/chip_button_widget.dart similarity index 100% rename from mobile/lib/ui/components/buttons/chip_button_widget.dart rename to mobile/apps/photos/lib/ui/components/buttons/chip_button_widget.dart diff --git a/mobile/lib/ui/components/buttons/icon_button_widget.dart b/mobile/apps/photos/lib/ui/components/buttons/icon_button_widget.dart similarity index 100% rename from mobile/lib/ui/components/buttons/icon_button_widget.dart rename to mobile/apps/photos/lib/ui/components/buttons/icon_button_widget.dart diff --git a/mobile/lib/ui/components/buttons/inline_button_widget.dart b/mobile/apps/photos/lib/ui/components/buttons/inline_button_widget.dart similarity index 100% rename from mobile/lib/ui/components/buttons/inline_button_widget.dart rename to mobile/apps/photos/lib/ui/components/buttons/inline_button_widget.dart diff --git a/mobile/lib/ui/components/captioned_text_widget.dart b/mobile/apps/photos/lib/ui/components/captioned_text_widget.dart similarity index 100% rename from mobile/lib/ui/components/captioned_text_widget.dart rename to mobile/apps/photos/lib/ui/components/captioned_text_widget.dart diff --git a/mobile/lib/ui/components/dialog_widget.dart b/mobile/apps/photos/lib/ui/components/dialog_widget.dart similarity index 100% rename from mobile/lib/ui/components/dialog_widget.dart rename to mobile/apps/photos/lib/ui/components/dialog_widget.dart diff --git a/mobile/lib/ui/components/divider_widget.dart b/mobile/apps/photos/lib/ui/components/divider_widget.dart similarity index 100% rename from mobile/lib/ui/components/divider_widget.dart rename to mobile/apps/photos/lib/ui/components/divider_widget.dart diff --git a/mobile/lib/ui/components/empty_state_item_widget.dart b/mobile/apps/photos/lib/ui/components/empty_state_item_widget.dart similarity index 100% rename from mobile/lib/ui/components/empty_state_item_widget.dart rename to mobile/apps/photos/lib/ui/components/empty_state_item_widget.dart diff --git a/mobile/lib/ui/components/end_to_end_banner.dart b/mobile/apps/photos/lib/ui/components/end_to_end_banner.dart similarity index 100% rename from mobile/lib/ui/components/end_to_end_banner.dart rename to mobile/apps/photos/lib/ui/components/end_to_end_banner.dart diff --git a/mobile/lib/ui/components/expandable_menu_item_widget.dart b/mobile/apps/photos/lib/ui/components/expandable_menu_item_widget.dart similarity index 100% rename from mobile/lib/ui/components/expandable_menu_item_widget.dart rename to mobile/apps/photos/lib/ui/components/expandable_menu_item_widget.dart diff --git a/mobile/lib/ui/components/home_header_widget.dart b/mobile/apps/photos/lib/ui/components/home_header_widget.dart similarity index 100% rename from mobile/lib/ui/components/home_header_widget.dart rename to mobile/apps/photos/lib/ui/components/home_header_widget.dart diff --git a/mobile/lib/ui/components/info_item_widget.dart b/mobile/apps/photos/lib/ui/components/info_item_widget.dart similarity index 100% rename from mobile/lib/ui/components/info_item_widget.dart rename to mobile/apps/photos/lib/ui/components/info_item_widget.dart diff --git a/mobile/lib/ui/components/keyboard/keyboard_oveylay.dart b/mobile/apps/photos/lib/ui/components/keyboard/keyboard_oveylay.dart similarity index 100% rename from mobile/lib/ui/components/keyboard/keyboard_oveylay.dart rename to mobile/apps/photos/lib/ui/components/keyboard/keyboard_oveylay.dart diff --git a/mobile/lib/ui/components/keyboard/keyboard_top_button.dart b/mobile/apps/photos/lib/ui/components/keyboard/keyboard_top_button.dart similarity index 100% rename from mobile/lib/ui/components/keyboard/keyboard_top_button.dart rename to mobile/apps/photos/lib/ui/components/keyboard/keyboard_top_button.dart diff --git a/mobile/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart b/mobile/apps/photos/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart similarity index 100% rename from mobile/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart rename to mobile/apps/photos/lib/ui/components/menu_item_widget/menu_item_child_widgets.dart diff --git a/mobile/lib/ui/components/menu_item_widget/menu_item_widget.dart b/mobile/apps/photos/lib/ui/components/menu_item_widget/menu_item_widget.dart similarity index 100% rename from mobile/lib/ui/components/menu_item_widget/menu_item_widget.dart rename to mobile/apps/photos/lib/ui/components/menu_item_widget/menu_item_widget.dart diff --git a/mobile/lib/ui/components/menu_section_description_widget.dart b/mobile/apps/photos/lib/ui/components/menu_section_description_widget.dart similarity index 100% rename from mobile/lib/ui/components/menu_section_description_widget.dart rename to mobile/apps/photos/lib/ui/components/menu_section_description_widget.dart diff --git a/mobile/lib/ui/components/menu_section_title.dart b/mobile/apps/photos/lib/ui/components/menu_section_title.dart similarity index 100% rename from mobile/lib/ui/components/menu_section_title.dart rename to mobile/apps/photos/lib/ui/components/menu_section_title.dart diff --git a/mobile/lib/ui/components/models/button_type.dart b/mobile/apps/photos/lib/ui/components/models/button_type.dart similarity index 100% rename from mobile/lib/ui/components/models/button_type.dart rename to mobile/apps/photos/lib/ui/components/models/button_type.dart diff --git a/mobile/lib/ui/components/models/custom_button_style.dart b/mobile/apps/photos/lib/ui/components/models/custom_button_style.dart similarity index 100% rename from mobile/lib/ui/components/models/custom_button_style.dart rename to mobile/apps/photos/lib/ui/components/models/custom_button_style.dart diff --git a/mobile/lib/ui/components/notification_widget.dart b/mobile/apps/photos/lib/ui/components/notification_widget.dart similarity index 100% rename from mobile/lib/ui/components/notification_widget.dart rename to mobile/apps/photos/lib/ui/components/notification_widget.dart diff --git a/mobile/lib/ui/components/searchable_appbar.dart b/mobile/apps/photos/lib/ui/components/searchable_appbar.dart similarity index 100% rename from mobile/lib/ui/components/searchable_appbar.dart rename to mobile/apps/photos/lib/ui/components/searchable_appbar.dart diff --git a/mobile/lib/ui/components/text_input_widget.dart b/mobile/apps/photos/lib/ui/components/text_input_widget.dart similarity index 100% rename from mobile/lib/ui/components/text_input_widget.dart rename to mobile/apps/photos/lib/ui/components/text_input_widget.dart diff --git a/mobile/lib/ui/components/title_bar_title_widget.dart b/mobile/apps/photos/lib/ui/components/title_bar_title_widget.dart similarity index 100% rename from mobile/lib/ui/components/title_bar_title_widget.dart rename to mobile/apps/photos/lib/ui/components/title_bar_title_widget.dart diff --git a/mobile/lib/ui/components/title_bar_widget.dart b/mobile/apps/photos/lib/ui/components/title_bar_widget.dart similarity index 100% rename from mobile/lib/ui/components/title_bar_widget.dart rename to mobile/apps/photos/lib/ui/components/title_bar_widget.dart diff --git a/mobile/lib/ui/components/toggle_switch_widget.dart b/mobile/apps/photos/lib/ui/components/toggle_switch_widget.dart similarity index 100% rename from mobile/lib/ui/components/toggle_switch_widget.dart rename to mobile/apps/photos/lib/ui/components/toggle_switch_widget.dart diff --git a/mobile/lib/ui/extents_page_view.dart b/mobile/apps/photos/lib/ui/extents_page_view.dart similarity index 100% rename from mobile/lib/ui/extents_page_view.dart rename to mobile/apps/photos/lib/ui/extents_page_view.dart diff --git a/mobile/lib/ui/growth/apply_code_screen.dart b/mobile/apps/photos/lib/ui/growth/apply_code_screen.dart similarity index 100% rename from mobile/lib/ui/growth/apply_code_screen.dart rename to mobile/apps/photos/lib/ui/growth/apply_code_screen.dart diff --git a/mobile/lib/ui/growth/code_success_screen.dart b/mobile/apps/photos/lib/ui/growth/code_success_screen.dart similarity index 100% rename from mobile/lib/ui/growth/code_success_screen.dart rename to mobile/apps/photos/lib/ui/growth/code_success_screen.dart diff --git a/mobile/lib/ui/growth/referral_code_widget.dart b/mobile/apps/photos/lib/ui/growth/referral_code_widget.dart similarity index 100% rename from mobile/lib/ui/growth/referral_code_widget.dart rename to mobile/apps/photos/lib/ui/growth/referral_code_widget.dart diff --git a/mobile/lib/ui/growth/referral_screen.dart b/mobile/apps/photos/lib/ui/growth/referral_screen.dart similarity index 100% rename from mobile/lib/ui/growth/referral_screen.dart rename to mobile/apps/photos/lib/ui/growth/referral_screen.dart diff --git a/mobile/lib/ui/growth/storage_details_screen.dart b/mobile/apps/photos/lib/ui/growth/storage_details_screen.dart similarity index 100% rename from mobile/lib/ui/growth/storage_details_screen.dart rename to mobile/apps/photos/lib/ui/growth/storage_details_screen.dart diff --git a/mobile/lib/ui/home/grant_permissions_widget.dart b/mobile/apps/photos/lib/ui/home/grant_permissions_widget.dart similarity index 100% rename from mobile/lib/ui/home/grant_permissions_widget.dart rename to mobile/apps/photos/lib/ui/home/grant_permissions_widget.dart diff --git a/mobile/lib/ui/home/header_error_widget.dart b/mobile/apps/photos/lib/ui/home/header_error_widget.dart similarity index 100% rename from mobile/lib/ui/home/header_error_widget.dart rename to mobile/apps/photos/lib/ui/home/header_error_widget.dart diff --git a/mobile/lib/ui/home/header_widget.dart b/mobile/apps/photos/lib/ui/home/header_widget.dart similarity index 100% rename from mobile/lib/ui/home/header_widget.dart rename to mobile/apps/photos/lib/ui/home/header_widget.dart diff --git a/mobile/lib/ui/home/home_bottom_nav_bar.dart b/mobile/apps/photos/lib/ui/home/home_bottom_nav_bar.dart similarity index 100% rename from mobile/lib/ui/home/home_bottom_nav_bar.dart rename to mobile/apps/photos/lib/ui/home/home_bottom_nav_bar.dart diff --git a/mobile/lib/ui/home/home_gallery_widget.dart b/mobile/apps/photos/lib/ui/home/home_gallery_widget.dart similarity index 100% rename from mobile/lib/ui/home/home_gallery_widget.dart rename to mobile/apps/photos/lib/ui/home/home_gallery_widget.dart diff --git a/mobile/lib/ui/home/landing_page_widget.dart b/mobile/apps/photos/lib/ui/home/landing_page_widget.dart similarity index 100% rename from mobile/lib/ui/home/landing_page_widget.dart rename to mobile/apps/photos/lib/ui/home/landing_page_widget.dart diff --git a/mobile/lib/ui/home/loading_photos_widget.dart b/mobile/apps/photos/lib/ui/home/loading_photos_widget.dart similarity index 100% rename from mobile/lib/ui/home/loading_photos_widget.dart rename to mobile/apps/photos/lib/ui/home/loading_photos_widget.dart diff --git a/mobile/lib/ui/home/memories/all_memories_page.dart b/mobile/apps/photos/lib/ui/home/memories/all_memories_page.dart similarity index 100% rename from mobile/lib/ui/home/memories/all_memories_page.dart rename to mobile/apps/photos/lib/ui/home/memories/all_memories_page.dart diff --git a/mobile/lib/ui/home/memories/custom_listener.dart b/mobile/apps/photos/lib/ui/home/memories/custom_listener.dart similarity index 100% rename from mobile/lib/ui/home/memories/custom_listener.dart rename to mobile/apps/photos/lib/ui/home/memories/custom_listener.dart diff --git a/mobile/lib/ui/home/memories/full_screen_memory.dart b/mobile/apps/photos/lib/ui/home/memories/full_screen_memory.dart similarity index 100% rename from mobile/lib/ui/home/memories/full_screen_memory.dart rename to mobile/apps/photos/lib/ui/home/memories/full_screen_memory.dart diff --git a/mobile/lib/ui/home/memories/memories_widget.dart b/mobile/apps/photos/lib/ui/home/memories/memories_widget.dart similarity index 100% rename from mobile/lib/ui/home/memories/memories_widget.dart rename to mobile/apps/photos/lib/ui/home/memories/memories_widget.dart diff --git a/mobile/lib/ui/home/memories/memory_cover_widget.dart b/mobile/apps/photos/lib/ui/home/memories/memory_cover_widget.dart similarity index 100% rename from mobile/lib/ui/home/memories/memory_cover_widget.dart rename to mobile/apps/photos/lib/ui/home/memories/memory_cover_widget.dart diff --git a/mobile/lib/ui/home/memories/memory_progress_indicator.dart b/mobile/apps/photos/lib/ui/home/memories/memory_progress_indicator.dart similarity index 100% rename from mobile/lib/ui/home/memories/memory_progress_indicator.dart rename to mobile/apps/photos/lib/ui/home/memories/memory_progress_indicator.dart diff --git a/mobile/lib/ui/home/start_backup_hook_widget.dart b/mobile/apps/photos/lib/ui/home/start_backup_hook_widget.dart similarity index 100% rename from mobile/lib/ui/home/start_backup_hook_widget.dart rename to mobile/apps/photos/lib/ui/home/start_backup_hook_widget.dart diff --git a/mobile/lib/ui/home/status_bar_widget.dart b/mobile/apps/photos/lib/ui/home/status_bar_widget.dart similarity index 100% rename from mobile/lib/ui/home/status_bar_widget.dart rename to mobile/apps/photos/lib/ui/home/status_bar_widget.dart diff --git a/mobile/lib/ui/huge_listview/draggable_scrollbar.dart b/mobile/apps/photos/lib/ui/huge_listview/draggable_scrollbar.dart similarity index 100% rename from mobile/lib/ui/huge_listview/draggable_scrollbar.dart rename to mobile/apps/photos/lib/ui/huge_listview/draggable_scrollbar.dart diff --git a/mobile/lib/ui/huge_listview/huge_listview.dart b/mobile/apps/photos/lib/ui/huge_listview/huge_listview.dart similarity index 100% rename from mobile/lib/ui/huge_listview/huge_listview.dart rename to mobile/apps/photos/lib/ui/huge_listview/huge_listview.dart diff --git a/mobile/lib/ui/huge_listview/scroll_bar_thumb.dart b/mobile/apps/photos/lib/ui/huge_listview/scroll_bar_thumb.dart similarity index 100% rename from mobile/lib/ui/huge_listview/scroll_bar_thumb.dart rename to mobile/apps/photos/lib/ui/huge_listview/scroll_bar_thumb.dart diff --git a/mobile/lib/ui/lifecycle_event_handler.dart b/mobile/apps/photos/lib/ui/lifecycle_event_handler.dart similarity index 100% rename from mobile/lib/ui/lifecycle_event_handler.dart rename to mobile/apps/photos/lib/ui/lifecycle_event_handler.dart diff --git a/mobile/lib/ui/map/enable_map.dart b/mobile/apps/photos/lib/ui/map/enable_map.dart similarity index 100% rename from mobile/lib/ui/map/enable_map.dart rename to mobile/apps/photos/lib/ui/map/enable_map.dart diff --git a/mobile/lib/ui/map/image_marker.dart b/mobile/apps/photos/lib/ui/map/image_marker.dart similarity index 100% rename from mobile/lib/ui/map/image_marker.dart rename to mobile/apps/photos/lib/ui/map/image_marker.dart diff --git a/mobile/lib/ui/map/map_button.dart b/mobile/apps/photos/lib/ui/map/map_button.dart similarity index 100% rename from mobile/lib/ui/map/map_button.dart rename to mobile/apps/photos/lib/ui/map/map_button.dart diff --git a/mobile/lib/ui/map/map_gallery_tile.dart b/mobile/apps/photos/lib/ui/map/map_gallery_tile.dart similarity index 100% rename from mobile/lib/ui/map/map_gallery_tile.dart rename to mobile/apps/photos/lib/ui/map/map_gallery_tile.dart diff --git a/mobile/lib/ui/map/map_gallery_tile_badge.dart b/mobile/apps/photos/lib/ui/map/map_gallery_tile_badge.dart similarity index 100% rename from mobile/lib/ui/map/map_gallery_tile_badge.dart rename to mobile/apps/photos/lib/ui/map/map_gallery_tile_badge.dart diff --git a/mobile/lib/ui/map/map_isolate.dart b/mobile/apps/photos/lib/ui/map/map_isolate.dart similarity index 100% rename from mobile/lib/ui/map/map_isolate.dart rename to mobile/apps/photos/lib/ui/map/map_isolate.dart diff --git a/mobile/lib/ui/map/map_marker.dart b/mobile/apps/photos/lib/ui/map/map_marker.dart similarity index 100% rename from mobile/lib/ui/map/map_marker.dart rename to mobile/apps/photos/lib/ui/map/map_marker.dart diff --git a/mobile/lib/ui/map/map_pull_up_gallery.dart b/mobile/apps/photos/lib/ui/map/map_pull_up_gallery.dart similarity index 100% rename from mobile/lib/ui/map/map_pull_up_gallery.dart rename to mobile/apps/photos/lib/ui/map/map_pull_up_gallery.dart diff --git a/mobile/lib/ui/map/map_screen.dart b/mobile/apps/photos/lib/ui/map/map_screen.dart similarity index 100% rename from mobile/lib/ui/map/map_screen.dart rename to mobile/apps/photos/lib/ui/map/map_screen.dart diff --git a/mobile/lib/ui/map/map_view.dart b/mobile/apps/photos/lib/ui/map/map_view.dart similarity index 100% rename from mobile/lib/ui/map/map_view.dart rename to mobile/apps/photos/lib/ui/map/map_view.dart diff --git a/mobile/lib/ui/map/marker_image.dart b/mobile/apps/photos/lib/ui/map/marker_image.dart similarity index 100% rename from mobile/lib/ui/map/marker_image.dart rename to mobile/apps/photos/lib/ui/map/marker_image.dart diff --git a/mobile/lib/ui/map/tile/attribution/map_attribution.dart b/mobile/apps/photos/lib/ui/map/tile/attribution/map_attribution.dart similarity index 100% rename from mobile/lib/ui/map/tile/attribution/map_attribution.dart rename to mobile/apps/photos/lib/ui/map/tile/attribution/map_attribution.dart diff --git a/mobile/lib/ui/map/tile/cache.dart b/mobile/apps/photos/lib/ui/map/tile/cache.dart similarity index 100% rename from mobile/lib/ui/map/tile/cache.dart rename to mobile/apps/photos/lib/ui/map/tile/cache.dart diff --git a/mobile/lib/ui/map/tile/layers.dart b/mobile/apps/photos/lib/ui/map/tile/layers.dart similarity index 100% rename from mobile/lib/ui/map/tile/layers.dart rename to mobile/apps/photos/lib/ui/map/tile/layers.dart diff --git a/mobile/lib/ui/notification/toast.dart b/mobile/apps/photos/lib/ui/notification/toast.dart similarity index 100% rename from mobile/lib/ui/notification/toast.dart rename to mobile/apps/photos/lib/ui/notification/toast.dart diff --git a/mobile/lib/ui/notification/update/change_log_entry.dart b/mobile/apps/photos/lib/ui/notification/update/change_log_entry.dart similarity index 100% rename from mobile/lib/ui/notification/update/change_log_entry.dart rename to mobile/apps/photos/lib/ui/notification/update/change_log_entry.dart diff --git a/mobile/lib/ui/notification/update/change_log_page.dart b/mobile/apps/photos/lib/ui/notification/update/change_log_page.dart similarity index 100% rename from mobile/lib/ui/notification/update/change_log_page.dart rename to mobile/apps/photos/lib/ui/notification/update/change_log_page.dart diff --git a/mobile/lib/ui/payment/add_on_page.dart b/mobile/apps/photos/lib/ui/payment/add_on_page.dart similarity index 100% rename from mobile/lib/ui/payment/add_on_page.dart rename to mobile/apps/photos/lib/ui/payment/add_on_page.dart diff --git a/mobile/lib/ui/payment/billing_questions_widget.dart b/mobile/apps/photos/lib/ui/payment/billing_questions_widget.dart similarity index 100% rename from mobile/lib/ui/payment/billing_questions_widget.dart rename to mobile/apps/photos/lib/ui/payment/billing_questions_widget.dart diff --git a/mobile/lib/ui/payment/child_subscription_widget.dart b/mobile/apps/photos/lib/ui/payment/child_subscription_widget.dart similarity index 100% rename from mobile/lib/ui/payment/child_subscription_widget.dart rename to mobile/apps/photos/lib/ui/payment/child_subscription_widget.dart diff --git a/mobile/lib/ui/payment/payment_web_page.dart b/mobile/apps/photos/lib/ui/payment/payment_web_page.dart similarity index 100% rename from mobile/lib/ui/payment/payment_web_page.dart rename to mobile/apps/photos/lib/ui/payment/payment_web_page.dart diff --git a/mobile/lib/ui/payment/store_subscription_page.dart b/mobile/apps/photos/lib/ui/payment/store_subscription_page.dart similarity index 100% rename from mobile/lib/ui/payment/store_subscription_page.dart rename to mobile/apps/photos/lib/ui/payment/store_subscription_page.dart diff --git a/mobile/lib/ui/payment/stripe_subscription_page.dart b/mobile/apps/photos/lib/ui/payment/stripe_subscription_page.dart similarity index 100% rename from mobile/lib/ui/payment/stripe_subscription_page.dart rename to mobile/apps/photos/lib/ui/payment/stripe_subscription_page.dart diff --git a/mobile/lib/ui/payment/subscription.dart b/mobile/apps/photos/lib/ui/payment/subscription.dart similarity index 100% rename from mobile/lib/ui/payment/subscription.dart rename to mobile/apps/photos/lib/ui/payment/subscription.dart diff --git a/mobile/lib/ui/payment/subscription_common_widgets.dart b/mobile/apps/photos/lib/ui/payment/subscription_common_widgets.dart similarity index 100% rename from mobile/lib/ui/payment/subscription_common_widgets.dart rename to mobile/apps/photos/lib/ui/payment/subscription_common_widgets.dart diff --git a/mobile/lib/ui/payment/subscription_plan_widget.dart b/mobile/apps/photos/lib/ui/payment/subscription_plan_widget.dart similarity index 100% rename from mobile/lib/ui/payment/subscription_plan_widget.dart rename to mobile/apps/photos/lib/ui/payment/subscription_plan_widget.dart diff --git a/mobile/lib/ui/payment/view_add_on_widget.dart b/mobile/apps/photos/lib/ui/payment/view_add_on_widget.dart similarity index 100% rename from mobile/lib/ui/payment/view_add_on_widget.dart rename to mobile/apps/photos/lib/ui/payment/view_add_on_widget.dart diff --git a/mobile/lib/ui/settings/about_section_widget.dart b/mobile/apps/photos/lib/ui/settings/about_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/about_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/about_section_widget.dart diff --git a/mobile/lib/ui/settings/account_section_widget.dart b/mobile/apps/photos/lib/ui/settings/account_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/account_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/account_section_widget.dart diff --git a/mobile/lib/ui/settings/advanced_settings_screen.dart b/mobile/apps/photos/lib/ui/settings/advanced_settings_screen.dart similarity index 100% rename from mobile/lib/ui/settings/advanced_settings_screen.dart rename to mobile/apps/photos/lib/ui/settings/advanced_settings_screen.dart diff --git a/mobile/lib/ui/settings/app_icon_selection_screen.dart b/mobile/apps/photos/lib/ui/settings/app_icon_selection_screen.dart similarity index 100% rename from mobile/lib/ui/settings/app_icon_selection_screen.dart rename to mobile/apps/photos/lib/ui/settings/app_icon_selection_screen.dart diff --git a/mobile/lib/ui/settings/app_update_dialog.dart b/mobile/apps/photos/lib/ui/settings/app_update_dialog.dart similarity index 100% rename from mobile/lib/ui/settings/app_update_dialog.dart rename to mobile/apps/photos/lib/ui/settings/app_update_dialog.dart diff --git a/mobile/lib/ui/settings/app_version_widget.dart b/mobile/apps/photos/lib/ui/settings/app_version_widget.dart similarity index 100% rename from mobile/lib/ui/settings/app_version_widget.dart rename to mobile/apps/photos/lib/ui/settings/app_version_widget.dart diff --git a/mobile/lib/ui/settings/backup/backup_folder_selection_page.dart b/mobile/apps/photos/lib/ui/settings/backup/backup_folder_selection_page.dart similarity index 100% rename from mobile/lib/ui/settings/backup/backup_folder_selection_page.dart rename to mobile/apps/photos/lib/ui/settings/backup/backup_folder_selection_page.dart diff --git a/mobile/lib/ui/settings/backup/backup_item_card.dart b/mobile/apps/photos/lib/ui/settings/backup/backup_item_card.dart similarity index 100% rename from mobile/lib/ui/settings/backup/backup_item_card.dart rename to mobile/apps/photos/lib/ui/settings/backup/backup_item_card.dart diff --git a/mobile/lib/ui/settings/backup/backup_section_widget.dart b/mobile/apps/photos/lib/ui/settings/backup/backup_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/backup/backup_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/backup/backup_section_widget.dart diff --git a/mobile/lib/ui/settings/backup/backup_settings_screen.dart b/mobile/apps/photos/lib/ui/settings/backup/backup_settings_screen.dart similarity index 100% rename from mobile/lib/ui/settings/backup/backup_settings_screen.dart rename to mobile/apps/photos/lib/ui/settings/backup/backup_settings_screen.dart diff --git a/mobile/lib/ui/settings/backup/backup_status_screen.dart b/mobile/apps/photos/lib/ui/settings/backup/backup_status_screen.dart similarity index 100% rename from mobile/lib/ui/settings/backup/backup_status_screen.dart rename to mobile/apps/photos/lib/ui/settings/backup/backup_status_screen.dart diff --git a/mobile/lib/ui/settings/backup/free_space_options.dart b/mobile/apps/photos/lib/ui/settings/backup/free_space_options.dart similarity index 100% rename from mobile/lib/ui/settings/backup/free_space_options.dart rename to mobile/apps/photos/lib/ui/settings/backup/free_space_options.dart diff --git a/mobile/lib/ui/settings/common_settings.dart b/mobile/apps/photos/lib/ui/settings/common_settings.dart similarity index 100% rename from mobile/lib/ui/settings/common_settings.dart rename to mobile/apps/photos/lib/ui/settings/common_settings.dart diff --git a/mobile/lib/ui/settings/debug/debug_section_widget.dart b/mobile/apps/photos/lib/ui/settings/debug/debug_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/debug/debug_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/debug/debug_section_widget.dart diff --git a/mobile/lib/ui/settings/debug/ml_debug_section_widget.dart b/mobile/apps/photos/lib/ui/settings/debug/ml_debug_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/debug/ml_debug_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/debug/ml_debug_section_widget.dart diff --git a/mobile/lib/ui/settings/developer_settings_page.dart b/mobile/apps/photos/lib/ui/settings/developer_settings_page.dart similarity index 100% rename from mobile/lib/ui/settings/developer_settings_page.dart rename to mobile/apps/photos/lib/ui/settings/developer_settings_page.dart diff --git a/mobile/lib/ui/settings/developer_settings_widget.dart b/mobile/apps/photos/lib/ui/settings/developer_settings_widget.dart similarity index 100% rename from mobile/lib/ui/settings/developer_settings_widget.dart rename to mobile/apps/photos/lib/ui/settings/developer_settings_widget.dart diff --git a/mobile/lib/ui/settings/gallery_settings_screen.dart b/mobile/apps/photos/lib/ui/settings/gallery_settings_screen.dart similarity index 100% rename from mobile/lib/ui/settings/gallery_settings_screen.dart rename to mobile/apps/photos/lib/ui/settings/gallery_settings_screen.dart diff --git a/mobile/lib/ui/settings/general_section_widget.dart b/mobile/apps/photos/lib/ui/settings/general_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/general_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/general_section_widget.dart diff --git a/mobile/lib/ui/settings/inherited_settings_state.dart b/mobile/apps/photos/lib/ui/settings/inherited_settings_state.dart similarity index 100% rename from mobile/lib/ui/settings/inherited_settings_state.dart rename to mobile/apps/photos/lib/ui/settings/inherited_settings_state.dart diff --git a/mobile/lib/ui/settings/language_picker.dart b/mobile/apps/photos/lib/ui/settings/language_picker.dart similarity index 100% rename from mobile/lib/ui/settings/language_picker.dart rename to mobile/apps/photos/lib/ui/settings/language_picker.dart diff --git a/mobile/lib/ui/settings/lock_screen/custom_pin_keypad.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/custom_pin_keypad.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/custom_pin_keypad.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/custom_pin_keypad.dart diff --git a/mobile/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart diff --git a/mobile/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart diff --git a/mobile/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart diff --git a/mobile/lib/ui/settings/lock_screen/lock_screen_options.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_options.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/lock_screen_options.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_options.dart diff --git a/mobile/lib/ui/settings/lock_screen/lock_screen_password.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_password.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/lock_screen_password.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_password.dart diff --git a/mobile/lib/ui/settings/lock_screen/lock_screen_pin.dart b/mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_pin.dart similarity index 100% rename from mobile/lib/ui/settings/lock_screen/lock_screen_pin.dart rename to mobile/apps/photos/lib/ui/settings/lock_screen/lock_screen_pin.dart diff --git a/mobile/lib/ui/settings/ml/enable_ml_consent.dart b/mobile/apps/photos/lib/ui/settings/ml/enable_ml_consent.dart similarity index 100% rename from mobile/lib/ui/settings/ml/enable_ml_consent.dart rename to mobile/apps/photos/lib/ui/settings/ml/enable_ml_consent.dart diff --git a/mobile/lib/ui/settings/ml/machine_learning_settings_page.dart b/mobile/apps/photos/lib/ui/settings/ml/machine_learning_settings_page.dart similarity index 100% rename from mobile/lib/ui/settings/ml/machine_learning_settings_page.dart rename to mobile/apps/photos/lib/ui/settings/ml/machine_learning_settings_page.dart diff --git a/mobile/lib/ui/settings/ml/ml_user_dev_screen.dart b/mobile/apps/photos/lib/ui/settings/ml/ml_user_dev_screen.dart similarity index 100% rename from mobile/lib/ui/settings/ml/ml_user_dev_screen.dart rename to mobile/apps/photos/lib/ui/settings/ml/ml_user_dev_screen.dart diff --git a/mobile/lib/ui/settings/notification_settings_screen.dart b/mobile/apps/photos/lib/ui/settings/notification_settings_screen.dart similarity index 100% rename from mobile/lib/ui/settings/notification_settings_screen.dart rename to mobile/apps/photos/lib/ui/settings/notification_settings_screen.dart diff --git a/mobile/lib/ui/settings/pending_sync/path_info_storage_viewer.dart b/mobile/apps/photos/lib/ui/settings/pending_sync/path_info_storage_viewer.dart similarity index 100% rename from mobile/lib/ui/settings/pending_sync/path_info_storage_viewer.dart rename to mobile/apps/photos/lib/ui/settings/pending_sync/path_info_storage_viewer.dart diff --git a/mobile/lib/ui/settings/pending_sync/pending_sync_info_screen.dart b/mobile/apps/photos/lib/ui/settings/pending_sync/pending_sync_info_screen.dart similarity index 100% rename from mobile/lib/ui/settings/pending_sync/pending_sync_info_screen.dart rename to mobile/apps/photos/lib/ui/settings/pending_sync/pending_sync_info_screen.dart diff --git a/mobile/lib/ui/settings/security_section_widget.dart b/mobile/apps/photos/lib/ui/settings/security_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/security_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/security_section_widget.dart diff --git a/mobile/lib/ui/settings/settings_title_bar_widget.dart b/mobile/apps/photos/lib/ui/settings/settings_title_bar_widget.dart similarity index 100% rename from mobile/lib/ui/settings/settings_title_bar_widget.dart rename to mobile/apps/photos/lib/ui/settings/settings_title_bar_widget.dart diff --git a/mobile/lib/ui/settings/social_section_widget.dart b/mobile/apps/photos/lib/ui/settings/social_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/social_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/social_section_widget.dart diff --git a/mobile/lib/ui/settings/storage_card_widget.dart b/mobile/apps/photos/lib/ui/settings/storage_card_widget.dart similarity index 100% rename from mobile/lib/ui/settings/storage_card_widget.dart rename to mobile/apps/photos/lib/ui/settings/storage_card_widget.dart diff --git a/mobile/lib/ui/settings/storage_progress_widget.dart b/mobile/apps/photos/lib/ui/settings/storage_progress_widget.dart similarity index 100% rename from mobile/lib/ui/settings/storage_progress_widget.dart rename to mobile/apps/photos/lib/ui/settings/storage_progress_widget.dart diff --git a/mobile/lib/ui/settings/support_section_widget.dart b/mobile/apps/photos/lib/ui/settings/support_section_widget.dart similarity index 100% rename from mobile/lib/ui/settings/support_section_widget.dart rename to mobile/apps/photos/lib/ui/settings/support_section_widget.dart diff --git a/mobile/lib/ui/settings/theme_switch_widget.dart b/mobile/apps/photos/lib/ui/settings/theme_switch_widget.dart similarity index 100% rename from mobile/lib/ui/settings/theme_switch_widget.dart rename to mobile/apps/photos/lib/ui/settings/theme_switch_widget.dart diff --git a/mobile/lib/ui/settings/widget_settings_screen.dart b/mobile/apps/photos/lib/ui/settings/widget_settings_screen.dart similarity index 100% rename from mobile/lib/ui/settings/widget_settings_screen.dart rename to mobile/apps/photos/lib/ui/settings/widget_settings_screen.dart diff --git a/mobile/lib/ui/settings/widgets/albums_widget_settings.dart b/mobile/apps/photos/lib/ui/settings/widgets/albums_widget_settings.dart similarity index 100% rename from mobile/lib/ui/settings/widgets/albums_widget_settings.dart rename to mobile/apps/photos/lib/ui/settings/widgets/albums_widget_settings.dart diff --git a/mobile/lib/ui/settings/widgets/memories_widget_settings.dart b/mobile/apps/photos/lib/ui/settings/widgets/memories_widget_settings.dart similarity index 100% rename from mobile/lib/ui/settings/widgets/memories_widget_settings.dart rename to mobile/apps/photos/lib/ui/settings/widgets/memories_widget_settings.dart diff --git a/mobile/lib/ui/settings/widgets/people_widget_settings.dart b/mobile/apps/photos/lib/ui/settings/widgets/people_widget_settings.dart similarity index 100% rename from mobile/lib/ui/settings/widgets/people_widget_settings.dart rename to mobile/apps/photos/lib/ui/settings/widgets/people_widget_settings.dart diff --git a/mobile/lib/ui/settings_page.dart b/mobile/apps/photos/lib/ui/settings_page.dart similarity index 100% rename from mobile/lib/ui/settings_page.dart rename to mobile/apps/photos/lib/ui/settings_page.dart diff --git a/mobile/lib/ui/sharing/add_participant_page.dart b/mobile/apps/photos/lib/ui/sharing/add_participant_page.dart similarity index 100% rename from mobile/lib/ui/sharing/add_participant_page.dart rename to mobile/apps/photos/lib/ui/sharing/add_participant_page.dart diff --git a/mobile/lib/ui/sharing/album_participants_page.dart b/mobile/apps/photos/lib/ui/sharing/album_participants_page.dart similarity index 100% rename from mobile/lib/ui/sharing/album_participants_page.dart rename to mobile/apps/photos/lib/ui/sharing/album_participants_page.dart diff --git a/mobile/lib/ui/sharing/album_share_info_widget.dart b/mobile/apps/photos/lib/ui/sharing/album_share_info_widget.dart similarity index 100% rename from mobile/lib/ui/sharing/album_share_info_widget.dart rename to mobile/apps/photos/lib/ui/sharing/album_share_info_widget.dart diff --git a/mobile/lib/ui/sharing/manage_album_participant.dart b/mobile/apps/photos/lib/ui/sharing/manage_album_participant.dart similarity index 100% rename from mobile/lib/ui/sharing/manage_album_participant.dart rename to mobile/apps/photos/lib/ui/sharing/manage_album_participant.dart diff --git a/mobile/lib/ui/sharing/manage_links_widget.dart b/mobile/apps/photos/lib/ui/sharing/manage_links_widget.dart similarity index 100% rename from mobile/lib/ui/sharing/manage_links_widget.dart rename to mobile/apps/photos/lib/ui/sharing/manage_links_widget.dart diff --git a/mobile/lib/ui/sharing/more_count_badge.dart b/mobile/apps/photos/lib/ui/sharing/more_count_badge.dart similarity index 100% rename from mobile/lib/ui/sharing/more_count_badge.dart rename to mobile/apps/photos/lib/ui/sharing/more_count_badge.dart diff --git a/mobile/lib/ui/sharing/pickers/device_limit_picker_page.dart b/mobile/apps/photos/lib/ui/sharing/pickers/device_limit_picker_page.dart similarity index 100% rename from mobile/lib/ui/sharing/pickers/device_limit_picker_page.dart rename to mobile/apps/photos/lib/ui/sharing/pickers/device_limit_picker_page.dart diff --git a/mobile/lib/ui/sharing/pickers/link_expiry_picker_page.dart b/mobile/apps/photos/lib/ui/sharing/pickers/link_expiry_picker_page.dart similarity index 100% rename from mobile/lib/ui/sharing/pickers/link_expiry_picker_page.dart rename to mobile/apps/photos/lib/ui/sharing/pickers/link_expiry_picker_page.dart diff --git a/mobile/lib/ui/sharing/share_collection_page.dart b/mobile/apps/photos/lib/ui/sharing/share_collection_page.dart similarity index 100% rename from mobile/lib/ui/sharing/share_collection_page.dart rename to mobile/apps/photos/lib/ui/sharing/share_collection_page.dart diff --git a/mobile/lib/ui/sharing/show_images_prevew.dart b/mobile/apps/photos/lib/ui/sharing/show_images_prevew.dart similarity index 100% rename from mobile/lib/ui/sharing/show_images_prevew.dart rename to mobile/apps/photos/lib/ui/sharing/show_images_prevew.dart diff --git a/mobile/lib/ui/sharing/user_avator_widget.dart b/mobile/apps/photos/lib/ui/sharing/user_avator_widget.dart similarity index 100% rename from mobile/lib/ui/sharing/user_avator_widget.dart rename to mobile/apps/photos/lib/ui/sharing/user_avator_widget.dart diff --git a/mobile/lib/ui/sharing/verify_identity_dialog.dart b/mobile/apps/photos/lib/ui/sharing/verify_identity_dialog.dart similarity index 100% rename from mobile/lib/ui/sharing/verify_identity_dialog.dart rename to mobile/apps/photos/lib/ui/sharing/verify_identity_dialog.dart diff --git a/mobile/lib/ui/tabs/home_widget.dart b/mobile/apps/photos/lib/ui/tabs/home_widget.dart similarity index 100% rename from mobile/lib/ui/tabs/home_widget.dart rename to mobile/apps/photos/lib/ui/tabs/home_widget.dart diff --git a/mobile/lib/ui/tabs/nav_bar.dart b/mobile/apps/photos/lib/ui/tabs/nav_bar.dart similarity index 100% rename from mobile/lib/ui/tabs/nav_bar.dart rename to mobile/apps/photos/lib/ui/tabs/nav_bar.dart diff --git a/mobile/lib/ui/tabs/section_title.dart b/mobile/apps/photos/lib/ui/tabs/section_title.dart similarity index 100% rename from mobile/lib/ui/tabs/section_title.dart rename to mobile/apps/photos/lib/ui/tabs/section_title.dart diff --git a/mobile/lib/ui/tabs/shared/all_quick_links_page.dart b/mobile/apps/photos/lib/ui/tabs/shared/all_quick_links_page.dart similarity index 100% rename from mobile/lib/ui/tabs/shared/all_quick_links_page.dart rename to mobile/apps/photos/lib/ui/tabs/shared/all_quick_links_page.dart diff --git a/mobile/lib/ui/tabs/shared/empty_state.dart b/mobile/apps/photos/lib/ui/tabs/shared/empty_state.dart similarity index 100% rename from mobile/lib/ui/tabs/shared/empty_state.dart rename to mobile/apps/photos/lib/ui/tabs/shared/empty_state.dart diff --git a/mobile/lib/ui/tabs/shared/quick_link_album_item.dart b/mobile/apps/photos/lib/ui/tabs/shared/quick_link_album_item.dart similarity index 100% rename from mobile/lib/ui/tabs/shared/quick_link_album_item.dart rename to mobile/apps/photos/lib/ui/tabs/shared/quick_link_album_item.dart diff --git a/mobile/lib/ui/tabs/shared_collections_tab.dart b/mobile/apps/photos/lib/ui/tabs/shared_collections_tab.dart similarity index 100% rename from mobile/lib/ui/tabs/shared_collections_tab.dart rename to mobile/apps/photos/lib/ui/tabs/shared_collections_tab.dart diff --git a/mobile/lib/ui/tabs/user_collections_tab.dart b/mobile/apps/photos/lib/ui/tabs/user_collections_tab.dart similarity index 100% rename from mobile/lib/ui/tabs/user_collections_tab.dart rename to mobile/apps/photos/lib/ui/tabs/user_collections_tab.dart diff --git a/mobile/lib/ui/tools/app_lock.dart b/mobile/apps/photos/lib/ui/tools/app_lock.dart similarity index 100% rename from mobile/lib/ui/tools/app_lock.dart rename to mobile/apps/photos/lib/ui/tools/app_lock.dart diff --git a/mobile/lib/ui/tools/collage/collage_common_widgets.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_common_widgets.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_common_widgets.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_common_widgets.dart diff --git a/mobile/lib/ui/tools/collage/collage_creator_page.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_creator_page.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_creator_page.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_creator_page.dart diff --git a/mobile/lib/ui/tools/collage/collage_item_icon.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_item_icon.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_item_icon.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_item_icon.dart diff --git a/mobile/lib/ui/tools/collage/collage_item_widget.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_item_widget.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_item_widget.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_item_widget.dart diff --git a/mobile/lib/ui/tools/collage/collage_save_button.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_save_button.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_save_button.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_save_button.dart diff --git a/mobile/lib/ui/tools/collage/collage_test_grid.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_test_grid.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_test_grid.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_test_grid.dart diff --git a/mobile/lib/ui/tools/collage/collage_with_five_items.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_with_five_items.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_with_five_items.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_with_five_items.dart diff --git a/mobile/lib/ui/tools/collage/collage_with_four_items.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_with_four_items.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_with_four_items.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_with_four_items.dart diff --git a/mobile/lib/ui/tools/collage/collage_with_six_items.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_with_six_items.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_with_six_items.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_with_six_items.dart diff --git a/mobile/lib/ui/tools/collage/collage_with_three_items.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_with_three_items.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_with_three_items.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_with_three_items.dart diff --git a/mobile/lib/ui/tools/collage/collage_with_two_items.dart b/mobile/apps/photos/lib/ui/tools/collage/collage_with_two_items.dart similarity index 100% rename from mobile/lib/ui/tools/collage/collage_with_two_items.dart rename to mobile/apps/photos/lib/ui/tools/collage/collage_with_two_items.dart diff --git a/mobile/lib/ui/tools/debug/app_storage_viewer.dart b/mobile/apps/photos/lib/ui/tools/debug/app_storage_viewer.dart similarity index 100% rename from mobile/lib/ui/tools/debug/app_storage_viewer.dart rename to mobile/apps/photos/lib/ui/tools/debug/app_storage_viewer.dart diff --git a/mobile/lib/ui/tools/debug/log_file_viewer.dart b/mobile/apps/photos/lib/ui/tools/debug/log_file_viewer.dart similarity index 100% rename from mobile/lib/ui/tools/debug/log_file_viewer.dart rename to mobile/apps/photos/lib/ui/tools/debug/log_file_viewer.dart diff --git a/mobile/lib/ui/tools/debug/path_storage_viewer.dart b/mobile/apps/photos/lib/ui/tools/debug/path_storage_viewer.dart similarity index 100% rename from mobile/lib/ui/tools/debug/path_storage_viewer.dart rename to mobile/apps/photos/lib/ui/tools/debug/path_storage_viewer.dart diff --git a/mobile/lib/ui/tools/deduplicate_page.dart b/mobile/apps/photos/lib/ui/tools/deduplicate_page.dart similarity index 100% rename from mobile/lib/ui/tools/deduplicate_page.dart rename to mobile/apps/photos/lib/ui/tools/deduplicate_page.dart diff --git a/mobile/lib/ui/tools/editor/export_video_result.dart b/mobile/apps/photos/lib/ui/tools/editor/export_video_result.dart similarity index 100% rename from mobile/lib/ui/tools/editor/export_video_result.dart rename to mobile/apps/photos/lib/ui/tools/editor/export_video_result.dart diff --git a/mobile/lib/ui/tools/editor/export_video_service.dart b/mobile/apps/photos/lib/ui/tools/editor/export_video_service.dart similarity index 100% rename from mobile/lib/ui/tools/editor/export_video_service.dart rename to mobile/apps/photos/lib/ui/tools/editor/export_video_service.dart diff --git a/mobile/lib/ui/tools/editor/filtered_image.dart b/mobile/apps/photos/lib/ui/tools/editor/filtered_image.dart similarity index 100% rename from mobile/lib/ui/tools/editor/filtered_image.dart rename to mobile/apps/photos/lib/ui/tools/editor/filtered_image.dart diff --git a/mobile/lib/ui/tools/editor/image_editor_page.dart b/mobile/apps/photos/lib/ui/tools/editor/image_editor_page.dart similarity index 100% rename from mobile/lib/ui/tools/editor/image_editor_page.dart rename to mobile/apps/photos/lib/ui/tools/editor/image_editor_page.dart diff --git a/mobile/lib/ui/tools/editor/video_crop_page.dart b/mobile/apps/photos/lib/ui/tools/editor/video_crop_page.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_crop_page.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_crop_page.dart diff --git a/mobile/lib/ui/tools/editor/video_editor/crop_value.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/crop_value.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_editor/crop_value.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_editor/crop_value.dart diff --git a/mobile/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_bottom_action.dart diff --git a/mobile/lib/ui/tools/editor/video_editor/video_editor_main_actions.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_main_actions.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_editor/video_editor_main_actions.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_main_actions.dart diff --git a/mobile/lib/ui/tools/editor/video_editor/video_editor_navigation_options.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_navigation_options.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_editor/video_editor_navigation_options.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_navigation_options.dart diff --git a/mobile/lib/ui/tools/editor/video_editor/video_editor_player_control.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_player_control.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_editor/video_editor_player_control.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_editor/video_editor_player_control.dart diff --git a/mobile/lib/ui/tools/editor/video_editor_page.dart b/mobile/apps/photos/lib/ui/tools/editor/video_editor_page.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_editor_page.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_editor_page.dart diff --git a/mobile/lib/ui/tools/editor/video_rotate_page.dart b/mobile/apps/photos/lib/ui/tools/editor/video_rotate_page.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_rotate_page.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_rotate_page.dart diff --git a/mobile/lib/ui/tools/editor/video_trim_page.dart b/mobile/apps/photos/lib/ui/tools/editor/video_trim_page.dart similarity index 100% rename from mobile/lib/ui/tools/editor/video_trim_page.dart rename to mobile/apps/photos/lib/ui/tools/editor/video_trim_page.dart diff --git a/mobile/lib/ui/tools/free_space_page.dart b/mobile/apps/photos/lib/ui/tools/free_space_page.dart similarity index 100% rename from mobile/lib/ui/tools/free_space_page.dart rename to mobile/apps/photos/lib/ui/tools/free_space_page.dart diff --git a/mobile/lib/ui/tools/lock_screen.dart b/mobile/apps/photos/lib/ui/tools/lock_screen.dart similarity index 100% rename from mobile/lib/ui/tools/lock_screen.dart rename to mobile/apps/photos/lib/ui/tools/lock_screen.dart diff --git a/mobile/lib/ui/viewer/actions/album_selection_action_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/album_selection_action_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/album_selection_action_widget.dart rename to mobile/apps/photos/lib/ui/viewer/actions/album_selection_action_widget.dart diff --git a/mobile/lib/ui/viewer/actions/album_selection_overlay_bar.dart b/mobile/apps/photos/lib/ui/viewer/actions/album_selection_overlay_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/album_selection_overlay_bar.dart rename to mobile/apps/photos/lib/ui/viewer/actions/album_selection_overlay_bar.dart diff --git a/mobile/lib/ui/viewer/actions/delete_empty_albums.dart b/mobile/apps/photos/lib/ui/viewer/actions/delete_empty_albums.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/delete_empty_albums.dart rename to mobile/apps/photos/lib/ui/viewer/actions/delete_empty_albums.dart diff --git a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart rename to mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_overlay_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart rename to mobile/apps/photos/lib/ui/viewer/actions/file_selection_overlay_bar.dart diff --git a/mobile/lib/ui/viewer/actions/file_viewer.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_viewer.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/file_viewer.dart rename to mobile/apps/photos/lib/ui/viewer/actions/file_viewer.dart diff --git a/mobile/lib/ui/viewer/actions/people_selection_action_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/people_selection_action_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/actions/people_selection_action_widget.dart rename to mobile/apps/photos/lib/ui/viewer/actions/people_selection_action_widget.dart diff --git a/mobile/lib/ui/viewer/date/date_time_picker.dart b/mobile/apps/photos/lib/ui/viewer/date/date_time_picker.dart similarity index 100% rename from mobile/lib/ui/viewer/date/date_time_picker.dart rename to mobile/apps/photos/lib/ui/viewer/date/date_time_picker.dart diff --git a/mobile/lib/ui/viewer/date/edit_date_sheet.dart b/mobile/apps/photos/lib/ui/viewer/date/edit_date_sheet.dart similarity index 100% rename from mobile/lib/ui/viewer/date/edit_date_sheet.dart rename to mobile/apps/photos/lib/ui/viewer/date/edit_date_sheet.dart diff --git a/mobile/lib/ui/viewer/file/detail_page.dart b/mobile/apps/photos/lib/ui/viewer/file/detail_page.dart similarity index 100% rename from mobile/lib/ui/viewer/file/detail_page.dart rename to mobile/apps/photos/lib/ui/viewer/file/detail_page.dart diff --git a/mobile/lib/ui/viewer/file/exif_info_dialog.dart b/mobile/apps/photos/lib/ui/viewer/file/exif_info_dialog.dart similarity index 100% rename from mobile/lib/ui/viewer/file/exif_info_dialog.dart rename to mobile/apps/photos/lib/ui/viewer/file/exif_info_dialog.dart diff --git a/mobile/lib/ui/viewer/file/file_app_bar.dart b/mobile/apps/photos/lib/ui/viewer/file/file_app_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/file/file_app_bar.dart rename to mobile/apps/photos/lib/ui/viewer/file/file_app_bar.dart diff --git a/mobile/lib/ui/viewer/file/file_bottom_bar.dart b/mobile/apps/photos/lib/ui/viewer/file/file_bottom_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/file/file_bottom_bar.dart rename to mobile/apps/photos/lib/ui/viewer/file/file_bottom_bar.dart diff --git a/mobile/lib/ui/viewer/file/file_caption_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/file_caption_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/file_caption_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/file_caption_widget.dart diff --git a/mobile/lib/ui/viewer/file/file_details_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/file_details_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/file_details_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/file_details_widget.dart diff --git a/mobile/lib/ui/viewer/file/file_icons_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/file_icons_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/file_icons_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/file_icons_widget.dart diff --git a/mobile/lib/ui/viewer/file/file_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/file_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/file_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/file_widget.dart diff --git a/mobile/lib/ui/viewer/file/native_video_player_controls/play_pause_button.dart b/mobile/apps/photos/lib/ui/viewer/file/native_video_player_controls/play_pause_button.dart similarity index 100% rename from mobile/lib/ui/viewer/file/native_video_player_controls/play_pause_button.dart rename to mobile/apps/photos/lib/ui/viewer/file/native_video_player_controls/play_pause_button.dart diff --git a/mobile/lib/ui/viewer/file/native_video_player_controls/seek_bar.dart b/mobile/apps/photos/lib/ui/viewer/file/native_video_player_controls/seek_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/file/native_video_player_controls/seek_bar.dart rename to mobile/apps/photos/lib/ui/viewer/file/native_video_player_controls/seek_bar.dart diff --git a/mobile/lib/ui/viewer/file/no_thumbnail_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/no_thumbnail_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/no_thumbnail_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/no_thumbnail_widget.dart diff --git a/mobile/lib/ui/viewer/file/panorama_viewer_screen.dart b/mobile/apps/photos/lib/ui/viewer/file/panorama_viewer_screen.dart similarity index 100% rename from mobile/lib/ui/viewer/file/panorama_viewer_screen.dart rename to mobile/apps/photos/lib/ui/viewer/file/panorama_viewer_screen.dart diff --git a/mobile/lib/ui/viewer/file/thumbnail_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/thumbnail_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/thumbnail_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/thumbnail_widget.dart diff --git a/mobile/lib/ui/viewer/file/video_control/custom_progress_bar.dart b/mobile/apps/photos/lib/ui/viewer/file/video_control/custom_progress_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_control/custom_progress_bar.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_control/custom_progress_bar.dart diff --git a/mobile/lib/ui/viewer/file/video_exif_dialog.dart b/mobile/apps/photos/lib/ui/viewer/file/video_exif_dialog.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_exif_dialog.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_exif_dialog.dart diff --git a/mobile/lib/ui/viewer/file/video_stream_change.dart b/mobile/apps/photos/lib/ui/viewer/file/video_stream_change.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_stream_change.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_stream_change.dart diff --git a/mobile/lib/ui/viewer/file/video_widget.dart b/mobile/apps/photos/lib/ui/viewer/file/video_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_widget.dart diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit.dart b/mobile/apps/photos/lib/ui/viewer/file/video_widget_media_kit.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_widget_media_kit.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_widget_media_kit.dart diff --git a/mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart b/mobile/apps/photos/lib/ui/viewer/file/video_widget_media_kit_common.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_widget_media_kit_common.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_widget_media_kit_common.dart diff --git a/mobile/lib/ui/viewer/file/video_widget_native.dart b/mobile/apps/photos/lib/ui/viewer/file/video_widget_native.dart similarity index 100% rename from mobile/lib/ui/viewer/file/video_widget_native.dart rename to mobile/apps/photos/lib/ui/viewer/file/video_widget_native.dart diff --git a/mobile/lib/ui/viewer/file/zoomable_image.dart b/mobile/apps/photos/lib/ui/viewer/file/zoomable_image.dart similarity index 100% rename from mobile/lib/ui/viewer/file/zoomable_image.dart rename to mobile/apps/photos/lib/ui/viewer/file/zoomable_image.dart diff --git a/mobile/lib/ui/viewer/file/zoomable_live_image_new.dart b/mobile/apps/photos/lib/ui/viewer/file/zoomable_live_image_new.dart similarity index 100% rename from mobile/lib/ui/viewer/file/zoomable_live_image_new.dart rename to mobile/apps/photos/lib/ui/viewer/file/zoomable_live_image_new.dart diff --git a/mobile/lib/ui/viewer/file_details/added_by_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/added_by_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/added_by_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/added_by_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/albums_item_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/albums_item_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/albums_item_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/albums_item_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/backed_up_time_item_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/backed_up_time_item_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/backed_up_time_item_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/backed_up_time_item_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/creation_time_item_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/creation_time_item_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/creation_time_item_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/creation_time_item_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/exif_item_widgets.dart b/mobile/apps/photos/lib/ui/viewer/file_details/exif_item_widgets.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/exif_item_widgets.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/exif_item_widgets.dart diff --git a/mobile/lib/ui/viewer/file_details/favorite_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/favorite_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/favorite_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/favorite_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/file_info_face_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/file_info_face_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/file_info_face_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/file_info_face_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/file_info_faces_item_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/file_info_faces_item_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/file_info_faces_item_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/file_info_faces_item_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/file_properties_item_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/file_properties_item_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/file_properties_item_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/file_properties_item_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/location_tags_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/location_tags_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/location_tags_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/location_tags_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/preview_properties_item_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/preview_properties_item_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/preview_properties_item_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/preview_properties_item_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/upload_icon_widget.dart b/mobile/apps/photos/lib/ui/viewer/file_details/upload_icon_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/upload_icon_widget.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/upload_icon_widget.dart diff --git a/mobile/lib/ui/viewer/file_details/video_exif_item.dart b/mobile/apps/photos/lib/ui/viewer/file_details/video_exif_item.dart similarity index 100% rename from mobile/lib/ui/viewer/file_details/video_exif_item.dart rename to mobile/apps/photos/lib/ui/viewer/file_details/video_exif_item.dart diff --git a/mobile/lib/ui/viewer/gallery/archive_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/archive_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/archive_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/archive_page.dart diff --git a/mobile/lib/ui/viewer/gallery/collect_photos_bottom_buttons.dart b/mobile/apps/photos/lib/ui/viewer/gallery/collect_photos_bottom_buttons.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/collect_photos_bottom_buttons.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/collect_photos_bottom_buttons.dart diff --git a/mobile/lib/ui/viewer/gallery/collect_photos_card_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/collect_photos_card_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/collect_photos_card_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/collect_photos_card_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/collection_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/collection_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/collection_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/collection_page.dart diff --git a/mobile/lib/ui/viewer/gallery/component/gallery_file_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/gallery_file_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/gallery_file_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/gallery_file_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/grid/gallery_grid_view_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/component/grid/lazy_grid_view.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/grid/lazy_grid_view.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/grid/lazy_grid_view.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/grid/lazy_grid_view.dart diff --git a/mobile/lib/ui/viewer/gallery/component/grid/non_recyclable_grid_view_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/grid/non_recyclable_grid_view_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/grid/non_recyclable_grid_view_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/grid/non_recyclable_grid_view_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/grid/place_holder_grid_view_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/component/grid/recyclable_grid_view_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/grid/recyclable_grid_view_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/grid/recyclable_grid_view_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/grid/recyclable_grid_view_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/component/group/group_gallery.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/group/group_gallery.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/group/group_gallery.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/group/group_gallery.dart diff --git a/mobile/lib/ui/viewer/gallery/component/group/group_header_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/group/group_header_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/group/group_header_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/group/group_header_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/component/group/lazy_group_gallery.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/group/lazy_group_gallery.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/group/lazy_group_gallery.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/group/lazy_group_gallery.dart diff --git a/mobile/lib/ui/viewer/gallery/component/group/type.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/group/type.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/group/type.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/group/type.dart diff --git a/mobile/lib/ui/viewer/gallery/component/multiple_groups_gallery_view.dart b/mobile/apps/photos/lib/ui/viewer/gallery/component/multiple_groups_gallery_view.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/component/multiple_groups_gallery_view.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/component/multiple_groups_gallery_view.dart diff --git a/mobile/lib/ui/viewer/gallery/device_folder_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/device_folder_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/device_folder_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/device_folder_page.dart diff --git a/mobile/lib/ui/viewer/gallery/empty_album_state.dart b/mobile/apps/photos/lib/ui/viewer/gallery/empty_album_state.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/empty_album_state.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/empty_album_state.dart diff --git a/mobile/lib/ui/viewer/gallery/empty_hidden_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/empty_hidden_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/empty_hidden_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/empty_hidden_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/empty_state.dart b/mobile/apps/photos/lib/ui/viewer/gallery/empty_state.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/empty_state.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/empty_state.dart diff --git a/mobile/lib/ui/viewer/gallery/gallery.dart b/mobile/apps/photos/lib/ui/viewer/gallery/gallery.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/gallery.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/gallery.dart diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/gallery_app_bar_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/gallery_app_bar_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/hidden_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/hidden_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/hidden_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/hidden_page.dart diff --git a/mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart b/mobile/apps/photos/lib/ui/viewer/gallery/hierarchical_search_gallery.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/hierarchical_search_gallery.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/hierarchical_search_gallery.dart diff --git a/mobile/lib/ui/viewer/gallery/hooks/add_photos_sheet.dart b/mobile/apps/photos/lib/ui/viewer/gallery/hooks/add_photos_sheet.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/hooks/add_photos_sheet.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/hooks/add_photos_sheet.dart diff --git a/mobile/lib/ui/viewer/gallery/hooks/pick_cover_photo.dart b/mobile/apps/photos/lib/ui/viewer/gallery/hooks/pick_cover_photo.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/hooks/pick_cover_photo.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/hooks/pick_cover_photo.dart diff --git a/mobile/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart b/mobile/apps/photos/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/hooks/pick_person_avatar.dart diff --git a/mobile/lib/ui/viewer/gallery/large_files_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/large_files_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/large_files_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/large_files_page.dart diff --git a/mobile/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/photo_grid_size_picker_page.dart diff --git a/mobile/lib/ui/viewer/gallery/shared_public_collection_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/shared_public_collection_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/shared_public_collection_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/shared_public_collection_page.dart diff --git a/mobile/lib/ui/viewer/gallery/state/gallery_context_state.dart b/mobile/apps/photos/lib/ui/viewer/gallery/state/gallery_context_state.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/state/gallery_context_state.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/state/gallery_context_state.dart diff --git a/mobile/lib/ui/viewer/gallery/state/gallery_files_inherited_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/state/gallery_files_inherited_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/state/gallery_files_inherited_widget.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/state/gallery_files_inherited_widget.dart diff --git a/mobile/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart b/mobile/apps/photos/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/state/inherited_search_filter_data.dart diff --git a/mobile/lib/ui/viewer/gallery/state/search_filter_data_provider.dart b/mobile/apps/photos/lib/ui/viewer/gallery/state/search_filter_data_provider.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/state/search_filter_data_provider.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/state/search_filter_data_provider.dart diff --git a/mobile/lib/ui/viewer/gallery/state/selection_state.dart b/mobile/apps/photos/lib/ui/viewer/gallery/state/selection_state.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/state/selection_state.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/state/selection_state.dart diff --git a/mobile/lib/ui/viewer/gallery/trash_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/trash_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/trash_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/trash_page.dart diff --git a/mobile/lib/ui/viewer/gallery/uncategorized_page.dart b/mobile/apps/photos/lib/ui/viewer/gallery/uncategorized_page.dart similarity index 100% rename from mobile/lib/ui/viewer/gallery/uncategorized_page.dart rename to mobile/apps/photos/lib/ui/viewer/gallery/uncategorized_page.dart diff --git a/mobile/lib/ui/viewer/hierarchicial_search/applied_filters_for_appbar.dart b/mobile/apps/photos/lib/ui/viewer/hierarchicial_search/applied_filters_for_appbar.dart similarity index 100% rename from mobile/lib/ui/viewer/hierarchicial_search/applied_filters_for_appbar.dart rename to mobile/apps/photos/lib/ui/viewer/hierarchicial_search/applied_filters_for_appbar.dart diff --git a/mobile/lib/ui/viewer/hierarchicial_search/chip_widgets/face_filter_chip.dart b/mobile/apps/photos/lib/ui/viewer/hierarchicial_search/chip_widgets/face_filter_chip.dart similarity index 100% rename from mobile/lib/ui/viewer/hierarchicial_search/chip_widgets/face_filter_chip.dart rename to mobile/apps/photos/lib/ui/viewer/hierarchicial_search/chip_widgets/face_filter_chip.dart diff --git a/mobile/lib/ui/viewer/hierarchicial_search/chip_widgets/generic_filter_chip.dart b/mobile/apps/photos/lib/ui/viewer/hierarchicial_search/chip_widgets/generic_filter_chip.dart similarity index 100% rename from mobile/lib/ui/viewer/hierarchicial_search/chip_widgets/generic_filter_chip.dart rename to mobile/apps/photos/lib/ui/viewer/hierarchicial_search/chip_widgets/generic_filter_chip.dart diff --git a/mobile/lib/ui/viewer/hierarchicial_search/chip_widgets/only_them_filter_chip.dart b/mobile/apps/photos/lib/ui/viewer/hierarchicial_search/chip_widgets/only_them_filter_chip.dart similarity index 100% rename from mobile/lib/ui/viewer/hierarchicial_search/chip_widgets/only_them_filter_chip.dart rename to mobile/apps/photos/lib/ui/viewer/hierarchicial_search/chip_widgets/only_them_filter_chip.dart diff --git a/mobile/lib/ui/viewer/hierarchicial_search/filter_options_bottom_sheet.dart b/mobile/apps/photos/lib/ui/viewer/hierarchicial_search/filter_options_bottom_sheet.dart similarity index 100% rename from mobile/lib/ui/viewer/hierarchicial_search/filter_options_bottom_sheet.dart rename to mobile/apps/photos/lib/ui/viewer/hierarchicial_search/filter_options_bottom_sheet.dart diff --git a/mobile/lib/ui/viewer/hierarchicial_search/recommended_filters_for_appbar.dart b/mobile/apps/photos/lib/ui/viewer/hierarchicial_search/recommended_filters_for_appbar.dart similarity index 100% rename from mobile/lib/ui/viewer/hierarchicial_search/recommended_filters_for_appbar.dart rename to mobile/apps/photos/lib/ui/viewer/hierarchicial_search/recommended_filters_for_appbar.dart diff --git a/mobile/lib/ui/viewer/location/add_location_sheet.dart b/mobile/apps/photos/lib/ui/viewer/location/add_location_sheet.dart similarity index 100% rename from mobile/lib/ui/viewer/location/add_location_sheet.dart rename to mobile/apps/photos/lib/ui/viewer/location/add_location_sheet.dart diff --git a/mobile/lib/ui/viewer/location/dynamic_location_gallery_widget.dart b/mobile/apps/photos/lib/ui/viewer/location/dynamic_location_gallery_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/location/dynamic_location_gallery_widget.dart rename to mobile/apps/photos/lib/ui/viewer/location/dynamic_location_gallery_widget.dart diff --git a/mobile/lib/ui/viewer/location/edit_center_point_tile_widget.dart b/mobile/apps/photos/lib/ui/viewer/location/edit_center_point_tile_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/location/edit_center_point_tile_widget.dart rename to mobile/apps/photos/lib/ui/viewer/location/edit_center_point_tile_widget.dart diff --git a/mobile/lib/ui/viewer/location/edit_location_sheet.dart b/mobile/apps/photos/lib/ui/viewer/location/edit_location_sheet.dart similarity index 100% rename from mobile/lib/ui/viewer/location/edit_location_sheet.dart rename to mobile/apps/photos/lib/ui/viewer/location/edit_location_sheet.dart diff --git a/mobile/lib/ui/viewer/location/location_screen.dart b/mobile/apps/photos/lib/ui/viewer/location/location_screen.dart similarity index 100% rename from mobile/lib/ui/viewer/location/location_screen.dart rename to mobile/apps/photos/lib/ui/viewer/location/location_screen.dart diff --git a/mobile/lib/ui/viewer/location/pick_center_point_widget.dart b/mobile/apps/photos/lib/ui/viewer/location/pick_center_point_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/location/pick_center_point_widget.dart rename to mobile/apps/photos/lib/ui/viewer/location/pick_center_point_widget.dart diff --git a/mobile/lib/ui/viewer/location/radius_picker_widget.dart b/mobile/apps/photos/lib/ui/viewer/location/radius_picker_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/location/radius_picker_widget.dart rename to mobile/apps/photos/lib/ui/viewer/location/radius_picker_widget.dart diff --git a/mobile/lib/ui/viewer/location/update_location_data_widget.dart b/mobile/apps/photos/lib/ui/viewer/location/update_location_data_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/location/update_location_data_widget.dart rename to mobile/apps/photos/lib/ui/viewer/location/update_location_data_widget.dart diff --git a/mobile/lib/ui/viewer/people/add_person_action_sheet.dart b/mobile/apps/photos/lib/ui/viewer/people/add_person_action_sheet.dart similarity index 100% rename from mobile/lib/ui/viewer/people/add_person_action_sheet.dart rename to mobile/apps/photos/lib/ui/viewer/people/add_person_action_sheet.dart diff --git a/mobile/lib/ui/viewer/people/cluster_app_bar.dart b/mobile/apps/photos/lib/ui/viewer/people/cluster_app_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/people/cluster_app_bar.dart rename to mobile/apps/photos/lib/ui/viewer/people/cluster_app_bar.dart diff --git a/mobile/lib/ui/viewer/people/cluster_breakup_page.dart b/mobile/apps/photos/lib/ui/viewer/people/cluster_breakup_page.dart similarity index 100% rename from mobile/lib/ui/viewer/people/cluster_breakup_page.dart rename to mobile/apps/photos/lib/ui/viewer/people/cluster_breakup_page.dart diff --git a/mobile/lib/ui/viewer/people/cluster_page.dart b/mobile/apps/photos/lib/ui/viewer/people/cluster_page.dart similarity index 100% rename from mobile/lib/ui/viewer/people/cluster_page.dart rename to mobile/apps/photos/lib/ui/viewer/people/cluster_page.dart diff --git a/mobile/lib/ui/viewer/people/file_face_widget.dart b/mobile/apps/photos/lib/ui/viewer/people/file_face_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/people/file_face_widget.dart rename to mobile/apps/photos/lib/ui/viewer/people/file_face_widget.dart diff --git a/mobile/lib/ui/viewer/people/link_email_screen.dart b/mobile/apps/photos/lib/ui/viewer/people/link_email_screen.dart similarity index 100% rename from mobile/lib/ui/viewer/people/link_email_screen.dart rename to mobile/apps/photos/lib/ui/viewer/people/link_email_screen.dart diff --git a/mobile/lib/ui/viewer/people/people_app_bar.dart b/mobile/apps/photos/lib/ui/viewer/people/people_app_bar.dart similarity index 100% rename from mobile/lib/ui/viewer/people/people_app_bar.dart rename to mobile/apps/photos/lib/ui/viewer/people/people_app_bar.dart diff --git a/mobile/lib/ui/viewer/people/people_banner.dart b/mobile/apps/photos/lib/ui/viewer/people/people_banner.dart similarity index 100% rename from mobile/lib/ui/viewer/people/people_banner.dart rename to mobile/apps/photos/lib/ui/viewer/people/people_banner.dart diff --git a/mobile/lib/ui/viewer/people/people_page.dart b/mobile/apps/photos/lib/ui/viewer/people/people_page.dart similarity index 100% rename from mobile/lib/ui/viewer/people/people_page.dart rename to mobile/apps/photos/lib/ui/viewer/people/people_page.dart diff --git a/mobile/lib/ui/viewer/people/people_util.dart b/mobile/apps/photos/lib/ui/viewer/people/people_util.dart similarity index 100% rename from mobile/lib/ui/viewer/people/people_util.dart rename to mobile/apps/photos/lib/ui/viewer/people/people_util.dart diff --git a/mobile/lib/ui/viewer/people/person_cluster_suggestion.dart b/mobile/apps/photos/lib/ui/viewer/people/person_cluster_suggestion.dart similarity index 100% rename from mobile/lib/ui/viewer/people/person_cluster_suggestion.dart rename to mobile/apps/photos/lib/ui/viewer/people/person_cluster_suggestion.dart diff --git a/mobile/lib/ui/viewer/people/person_clusters_page.dart b/mobile/apps/photos/lib/ui/viewer/people/person_clusters_page.dart similarity index 100% rename from mobile/lib/ui/viewer/people/person_clusters_page.dart rename to mobile/apps/photos/lib/ui/viewer/people/person_clusters_page.dart diff --git a/mobile/lib/ui/viewer/people/person_face_widget.dart b/mobile/apps/photos/lib/ui/viewer/people/person_face_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/people/person_face_widget.dart rename to mobile/apps/photos/lib/ui/viewer/people/person_face_widget.dart diff --git a/mobile/lib/ui/viewer/people/person_gallery_suggestion.dart b/mobile/apps/photos/lib/ui/viewer/people/person_gallery_suggestion.dart similarity index 100% rename from mobile/lib/ui/viewer/people/person_gallery_suggestion.dart rename to mobile/apps/photos/lib/ui/viewer/people/person_gallery_suggestion.dart diff --git a/mobile/lib/ui/viewer/people/person_row_item.dart b/mobile/apps/photos/lib/ui/viewer/people/person_row_item.dart similarity index 100% rename from mobile/lib/ui/viewer/people/person_row_item.dart rename to mobile/apps/photos/lib/ui/viewer/people/person_row_item.dart diff --git a/mobile/lib/ui/viewer/people/person_selection_action_widgets.dart b/mobile/apps/photos/lib/ui/viewer/people/person_selection_action_widgets.dart similarity index 100% rename from mobile/lib/ui/viewer/people/person_selection_action_widgets.dart rename to mobile/apps/photos/lib/ui/viewer/people/person_selection_action_widgets.dart diff --git a/mobile/lib/ui/viewer/people/save_or_edit_person.dart b/mobile/apps/photos/lib/ui/viewer/people/save_or_edit_person.dart similarity index 100% rename from mobile/lib/ui/viewer/people/save_or_edit_person.dart rename to mobile/apps/photos/lib/ui/viewer/people/save_or_edit_person.dart diff --git a/mobile/lib/ui/viewer/search/result/contact_result_page.dart b/mobile/apps/photos/lib/ui/viewer/search/result/contact_result_page.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/contact_result_page.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/contact_result_page.dart diff --git a/mobile/lib/ui/viewer/search/result/go_to_map_widget.dart b/mobile/apps/photos/lib/ui/viewer/search/result/go_to_map_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/go_to_map_widget.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/go_to_map_widget.dart diff --git a/mobile/lib/ui/viewer/search/result/magic_result_screen.dart b/mobile/apps/photos/lib/ui/viewer/search/result/magic_result_screen.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/magic_result_screen.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/magic_result_screen.dart diff --git a/mobile/lib/ui/viewer/search/result/no_result_widget.dart b/mobile/apps/photos/lib/ui/viewer/search/result/no_result_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/no_result_widget.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/no_result_widget.dart diff --git a/mobile/lib/ui/viewer/search/result/people_section_all_page.dart b/mobile/apps/photos/lib/ui/viewer/search/result/people_section_all_page.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/people_section_all_page.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/people_section_all_page.dart diff --git a/mobile/lib/ui/viewer/search/result/search_result_page.dart b/mobile/apps/photos/lib/ui/viewer/search/result/search_result_page.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/search_result_page.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/search_result_page.dart diff --git a/mobile/lib/ui/viewer/search/result/search_result_widget.dart b/mobile/apps/photos/lib/ui/viewer/search/result/search_result_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/search_result_widget.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/search_result_widget.dart diff --git a/mobile/lib/ui/viewer/search/result/search_section_all_page.dart b/mobile/apps/photos/lib/ui/viewer/search/result/search_section_all_page.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/search_section_all_page.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/search_section_all_page.dart diff --git a/mobile/lib/ui/viewer/search/result/search_thumbnail_widget.dart b/mobile/apps/photos/lib/ui/viewer/search/result/search_thumbnail_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/search_thumbnail_widget.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/search_thumbnail_widget.dart diff --git a/mobile/lib/ui/viewer/search/result/searchable_item.dart b/mobile/apps/photos/lib/ui/viewer/search/result/searchable_item.dart similarity index 100% rename from mobile/lib/ui/viewer/search/result/searchable_item.dart rename to mobile/apps/photos/lib/ui/viewer/search/result/searchable_item.dart diff --git a/mobile/lib/ui/viewer/search/search_section_cta.dart b/mobile/apps/photos/lib/ui/viewer/search/search_section_cta.dart similarity index 100% rename from mobile/lib/ui/viewer/search/search_section_cta.dart rename to mobile/apps/photos/lib/ui/viewer/search/search_section_cta.dart diff --git a/mobile/lib/ui/viewer/search/search_suffix_icon_widget.dart b/mobile/apps/photos/lib/ui/viewer/search/search_suffix_icon_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/search/search_suffix_icon_widget.dart rename to mobile/apps/photos/lib/ui/viewer/search/search_suffix_icon_widget.dart diff --git a/mobile/lib/ui/viewer/search/search_suggestions.dart b/mobile/apps/photos/lib/ui/viewer/search/search_suggestions.dart similarity index 100% rename from mobile/lib/ui/viewer/search/search_suggestions.dart rename to mobile/apps/photos/lib/ui/viewer/search/search_suggestions.dart diff --git a/mobile/lib/ui/viewer/search/search_widget.dart b/mobile/apps/photos/lib/ui/viewer/search/search_widget.dart similarity index 100% rename from mobile/lib/ui/viewer/search/search_widget.dart rename to mobile/apps/photos/lib/ui/viewer/search/search_widget.dart diff --git a/mobile/lib/ui/viewer/search/tab_empty_state.dart b/mobile/apps/photos/lib/ui/viewer/search/tab_empty_state.dart similarity index 100% rename from mobile/lib/ui/viewer/search/tab_empty_state.dart rename to mobile/apps/photos/lib/ui/viewer/search/tab_empty_state.dart diff --git a/mobile/lib/ui/viewer/search_tab/albums_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/albums_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/albums_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/albums_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/contacts_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/contacts_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/contacts_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/contacts_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/file_type_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/file_type_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/file_type_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/file_type_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/locations_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/locations_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/locations_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/locations_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/magic_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/magic_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/magic_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/magic_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/moments_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/moments_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/moments_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/moments_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/people_section.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/people_section.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/people_section.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/people_section.dart diff --git a/mobile/lib/ui/viewer/search_tab/search_tab.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/search_tab.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/search_tab.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/search_tab.dart diff --git a/mobile/lib/ui/viewer/search_tab/section_header.dart b/mobile/apps/photos/lib/ui/viewer/search_tab/section_header.dart similarity index 100% rename from mobile/lib/ui/viewer/search_tab/section_header.dart rename to mobile/apps/photos/lib/ui/viewer/search_tab/section_header.dart diff --git a/mobile/lib/utils/auth_util.dart b/mobile/apps/photos/lib/utils/auth_util.dart similarity index 100% rename from mobile/lib/utils/auth_util.dart rename to mobile/apps/photos/lib/utils/auth_util.dart diff --git a/mobile/lib/utils/bg_task_utils.dart b/mobile/apps/photos/lib/utils/bg_task_utils.dart similarity index 100% rename from mobile/lib/utils/bg_task_utils.dart rename to mobile/apps/photos/lib/utils/bg_task_utils.dart diff --git a/mobile/lib/utils/cache_util.dart b/mobile/apps/photos/lib/utils/cache_util.dart similarity index 100% rename from mobile/lib/utils/cache_util.dart rename to mobile/apps/photos/lib/utils/cache_util.dart diff --git a/mobile/lib/utils/collection_util.dart b/mobile/apps/photos/lib/utils/collection_util.dart similarity index 100% rename from mobile/lib/utils/collection_util.dart rename to mobile/apps/photos/lib/utils/collection_util.dart diff --git a/mobile/lib/utils/debug_ml_export_data.dart b/mobile/apps/photos/lib/utils/debug_ml_export_data.dart similarity index 100% rename from mobile/lib/utils/debug_ml_export_data.dart rename to mobile/apps/photos/lib/utils/debug_ml_export_data.dart diff --git a/mobile/lib/utils/delete_file_util.dart b/mobile/apps/photos/lib/utils/delete_file_util.dart similarity index 100% rename from mobile/lib/utils/delete_file_util.dart rename to mobile/apps/photos/lib/utils/delete_file_util.dart diff --git a/mobile/lib/utils/device_info.dart b/mobile/apps/photos/lib/utils/device_info.dart similarity index 100% rename from mobile/lib/utils/device_info.dart rename to mobile/apps/photos/lib/utils/device_info.dart diff --git a/mobile/lib/utils/dialog_util.dart b/mobile/apps/photos/lib/utils/dialog_util.dart similarity index 100% rename from mobile/lib/utils/dialog_util.dart rename to mobile/apps/photos/lib/utils/dialog_util.dart diff --git a/mobile/lib/utils/email_util.dart b/mobile/apps/photos/lib/utils/email_util.dart similarity index 100% rename from mobile/lib/utils/email_util.dart rename to mobile/apps/photos/lib/utils/email_util.dart diff --git a/mobile/lib/utils/exif_util.dart b/mobile/apps/photos/lib/utils/exif_util.dart similarity index 100% rename from mobile/lib/utils/exif_util.dart rename to mobile/apps/photos/lib/utils/exif_util.dart diff --git a/mobile/lib/utils/face/face_thumbnail_cache.dart b/mobile/apps/photos/lib/utils/face/face_thumbnail_cache.dart similarity index 100% rename from mobile/lib/utils/face/face_thumbnail_cache.dart rename to mobile/apps/photos/lib/utils/face/face_thumbnail_cache.dart diff --git a/mobile/lib/utils/ffprobe_util.dart b/mobile/apps/photos/lib/utils/ffprobe_util.dart similarity index 100% rename from mobile/lib/utils/ffprobe_util.dart rename to mobile/apps/photos/lib/utils/ffprobe_util.dart diff --git a/mobile/lib/utils/file_download_util.dart b/mobile/apps/photos/lib/utils/file_download_util.dart similarity index 100% rename from mobile/lib/utils/file_download_util.dart rename to mobile/apps/photos/lib/utils/file_download_util.dart diff --git a/mobile/lib/utils/file_key.dart b/mobile/apps/photos/lib/utils/file_key.dart similarity index 100% rename from mobile/lib/utils/file_key.dart rename to mobile/apps/photos/lib/utils/file_key.dart diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/apps/photos/lib/utils/file_uploader.dart similarity index 100% rename from mobile/lib/utils/file_uploader.dart rename to mobile/apps/photos/lib/utils/file_uploader.dart diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/apps/photos/lib/utils/file_uploader_util.dart similarity index 100% rename from mobile/lib/utils/file_uploader_util.dart rename to mobile/apps/photos/lib/utils/file_uploader_util.dart diff --git a/mobile/lib/utils/file_util.dart b/mobile/apps/photos/lib/utils/file_util.dart similarity index 100% rename from mobile/lib/utils/file_util.dart rename to mobile/apps/photos/lib/utils/file_util.dart diff --git a/mobile/lib/utils/gzip.dart b/mobile/apps/photos/lib/utils/gzip.dart similarity index 100% rename from mobile/lib/utils/gzip.dart rename to mobile/apps/photos/lib/utils/gzip.dart diff --git a/mobile/lib/utils/hierarchical_search_util.dart b/mobile/apps/photos/lib/utils/hierarchical_search_util.dart similarity index 100% rename from mobile/lib/utils/hierarchical_search_util.dart rename to mobile/apps/photos/lib/utils/hierarchical_search_util.dart diff --git a/mobile/lib/utils/image_ml_util.dart b/mobile/apps/photos/lib/utils/image_ml_util.dart similarity index 100% rename from mobile/lib/utils/image_ml_util.dart rename to mobile/apps/photos/lib/utils/image_ml_util.dart diff --git a/mobile/lib/utils/image_util.dart b/mobile/apps/photos/lib/utils/image_util.dart similarity index 100% rename from mobile/lib/utils/image_util.dart rename to mobile/apps/photos/lib/utils/image_util.dart diff --git a/mobile/lib/utils/intent_util.dart b/mobile/apps/photos/lib/utils/intent_util.dart similarity index 100% rename from mobile/lib/utils/intent_util.dart rename to mobile/apps/photos/lib/utils/intent_util.dart diff --git a/mobile/lib/utils/local_settings.dart b/mobile/apps/photos/lib/utils/local_settings.dart similarity index 100% rename from mobile/lib/utils/local_settings.dart rename to mobile/apps/photos/lib/utils/local_settings.dart diff --git a/mobile/lib/utils/lock_screen_settings.dart b/mobile/apps/photos/lib/utils/lock_screen_settings.dart similarity index 100% rename from mobile/lib/utils/lock_screen_settings.dart rename to mobile/apps/photos/lib/utils/lock_screen_settings.dart diff --git a/mobile/lib/utils/magic_util.dart b/mobile/apps/photos/lib/utils/magic_util.dart similarity index 100% rename from mobile/lib/utils/magic_util.dart rename to mobile/apps/photos/lib/utils/magic_util.dart diff --git a/mobile/lib/utils/ml_util.dart b/mobile/apps/photos/lib/utils/ml_util.dart similarity index 100% rename from mobile/lib/utils/ml_util.dart rename to mobile/apps/photos/lib/utils/ml_util.dart diff --git a/mobile/lib/utils/navigation_util.dart b/mobile/apps/photos/lib/utils/navigation_util.dart similarity index 100% rename from mobile/lib/utils/navigation_util.dart rename to mobile/apps/photos/lib/utils/navigation_util.dart diff --git a/mobile/lib/utils/network_util.dart b/mobile/apps/photos/lib/utils/network_util.dart similarity index 100% rename from mobile/lib/utils/network_util.dart rename to mobile/apps/photos/lib/utils/network_util.dart diff --git a/mobile/lib/utils/panorama_util.dart b/mobile/apps/photos/lib/utils/panorama_util.dart similarity index 100% rename from mobile/lib/utils/panorama_util.dart rename to mobile/apps/photos/lib/utils/panorama_util.dart diff --git a/mobile/lib/utils/person_contact_linking_util.dart b/mobile/apps/photos/lib/utils/person_contact_linking_util.dart similarity index 100% rename from mobile/lib/utils/person_contact_linking_util.dart rename to mobile/apps/photos/lib/utils/person_contact_linking_util.dart diff --git a/mobile/lib/utils/ram_check_util.dart b/mobile/apps/photos/lib/utils/ram_check_util.dart similarity index 100% rename from mobile/lib/utils/ram_check_util.dart rename to mobile/apps/photos/lib/utils/ram_check_util.dart diff --git a/mobile/lib/utils/separators_util.dart b/mobile/apps/photos/lib/utils/separators_util.dart similarity index 100% rename from mobile/lib/utils/separators_util.dart rename to mobile/apps/photos/lib/utils/separators_util.dart diff --git a/mobile/lib/utils/share_util.dart b/mobile/apps/photos/lib/utils/share_util.dart similarity index 100% rename from mobile/lib/utils/share_util.dart rename to mobile/apps/photos/lib/utils/share_util.dart diff --git a/mobile/lib/utils/standalone/README.md b/mobile/apps/photos/lib/utils/standalone/README.md similarity index 100% rename from mobile/lib/utils/standalone/README.md rename to mobile/apps/photos/lib/utils/standalone/README.md diff --git a/mobile/lib/utils/standalone/data.dart b/mobile/apps/photos/lib/utils/standalone/data.dart similarity index 100% rename from mobile/lib/utils/standalone/data.dart rename to mobile/apps/photos/lib/utils/standalone/data.dart diff --git a/mobile/lib/utils/standalone/date_time.dart b/mobile/apps/photos/lib/utils/standalone/date_time.dart similarity index 100% rename from mobile/lib/utils/standalone/date_time.dart rename to mobile/apps/photos/lib/utils/standalone/date_time.dart diff --git a/mobile/lib/utils/standalone/debouncer.dart b/mobile/apps/photos/lib/utils/standalone/debouncer.dart similarity index 100% rename from mobile/lib/utils/standalone/debouncer.dart rename to mobile/apps/photos/lib/utils/standalone/debouncer.dart diff --git a/mobile/lib/utils/standalone/directory_content.dart b/mobile/apps/photos/lib/utils/standalone/directory_content.dart similarity index 100% rename from mobile/lib/utils/standalone/directory_content.dart rename to mobile/apps/photos/lib/utils/standalone/directory_content.dart diff --git a/mobile/lib/utils/standalone/fake_progress.dart b/mobile/apps/photos/lib/utils/standalone/fake_progress.dart similarity index 100% rename from mobile/lib/utils/standalone/fake_progress.dart rename to mobile/apps/photos/lib/utils/standalone/fake_progress.dart diff --git a/mobile/lib/utils/standalone/parse.dart b/mobile/apps/photos/lib/utils/standalone/parse.dart similarity index 100% rename from mobile/lib/utils/standalone/parse.dart rename to mobile/apps/photos/lib/utils/standalone/parse.dart diff --git a/mobile/lib/utils/standalone/simple_task_queue.dart b/mobile/apps/photos/lib/utils/standalone/simple_task_queue.dart similarity index 100% rename from mobile/lib/utils/standalone/simple_task_queue.dart rename to mobile/apps/photos/lib/utils/standalone/simple_task_queue.dart diff --git a/mobile/lib/utils/standalone/task_queue.dart b/mobile/apps/photos/lib/utils/standalone/task_queue.dart similarity index 100% rename from mobile/lib/utils/standalone/task_queue.dart rename to mobile/apps/photos/lib/utils/standalone/task_queue.dart diff --git a/mobile/lib/utils/thumbnail_util.dart b/mobile/apps/photos/lib/utils/thumbnail_util.dart similarity index 100% rename from mobile/lib/utils/thumbnail_util.dart rename to mobile/apps/photos/lib/utils/thumbnail_util.dart diff --git a/mobile/lib/utils/validator_util.dart b/mobile/apps/photos/lib/utils/validator_util.dart similarity index 100% rename from mobile/lib/utils/validator_util.dart rename to mobile/apps/photos/lib/utils/validator_util.dart diff --git a/mobile/plugins/ente_cast/.metadata b/mobile/apps/photos/plugins/ente_cast/.metadata similarity index 100% rename from mobile/plugins/ente_cast/.metadata rename to mobile/apps/photos/plugins/ente_cast/.metadata diff --git a/mobile/plugins/ente_cast/analysis_options.yaml b/mobile/apps/photos/plugins/ente_cast/analysis_options.yaml similarity index 100% rename from mobile/plugins/ente_cast/analysis_options.yaml rename to mobile/apps/photos/plugins/ente_cast/analysis_options.yaml diff --git a/mobile/plugins/ente_cast/lib/ente_cast.dart b/mobile/apps/photos/plugins/ente_cast/lib/ente_cast.dart similarity index 100% rename from mobile/plugins/ente_cast/lib/ente_cast.dart rename to mobile/apps/photos/plugins/ente_cast/lib/ente_cast.dart diff --git a/mobile/plugins/ente_cast/lib/src/model.dart b/mobile/apps/photos/plugins/ente_cast/lib/src/model.dart similarity index 100% rename from mobile/plugins/ente_cast/lib/src/model.dart rename to mobile/apps/photos/plugins/ente_cast/lib/src/model.dart diff --git a/mobile/plugins/ente_cast/lib/src/service.dart b/mobile/apps/photos/plugins/ente_cast/lib/src/service.dart similarity index 100% rename from mobile/plugins/ente_cast/lib/src/service.dart rename to mobile/apps/photos/plugins/ente_cast/lib/src/service.dart diff --git a/mobile/plugins/ente_cast/pubspec.lock b/mobile/apps/photos/plugins/ente_cast/pubspec.lock similarity index 100% rename from mobile/plugins/ente_cast/pubspec.lock rename to mobile/apps/photos/plugins/ente_cast/pubspec.lock diff --git a/mobile/plugins/ente_cast/pubspec.yaml b/mobile/apps/photos/plugins/ente_cast/pubspec.yaml similarity index 100% rename from mobile/plugins/ente_cast/pubspec.yaml rename to mobile/apps/photos/plugins/ente_cast/pubspec.yaml diff --git a/mobile/plugins/ente_cast_none/.metadata b/mobile/apps/photos/plugins/ente_cast_none/.metadata similarity index 100% rename from mobile/plugins/ente_cast_none/.metadata rename to mobile/apps/photos/plugins/ente_cast_none/.metadata diff --git a/mobile/plugins/ente_cast_none/analysis_options.yaml b/mobile/apps/photos/plugins/ente_cast_none/analysis_options.yaml similarity index 100% rename from mobile/plugins/ente_cast_none/analysis_options.yaml rename to mobile/apps/photos/plugins/ente_cast_none/analysis_options.yaml diff --git a/mobile/plugins/ente_cast_none/lib/ente_cast_none.dart b/mobile/apps/photos/plugins/ente_cast_none/lib/ente_cast_none.dart similarity index 100% rename from mobile/plugins/ente_cast_none/lib/ente_cast_none.dart rename to mobile/apps/photos/plugins/ente_cast_none/lib/ente_cast_none.dart diff --git a/mobile/plugins/ente_cast_none/lib/src/service.dart b/mobile/apps/photos/plugins/ente_cast_none/lib/src/service.dart similarity index 100% rename from mobile/plugins/ente_cast_none/lib/src/service.dart rename to mobile/apps/photos/plugins/ente_cast_none/lib/src/service.dart diff --git a/mobile/plugins/ente_cast_none/pubspec.lock b/mobile/apps/photos/plugins/ente_cast_none/pubspec.lock similarity index 100% rename from mobile/plugins/ente_cast_none/pubspec.lock rename to mobile/apps/photos/plugins/ente_cast_none/pubspec.lock diff --git a/mobile/plugins/ente_cast_none/pubspec.yaml b/mobile/apps/photos/plugins/ente_cast_none/pubspec.yaml similarity index 100% rename from mobile/plugins/ente_cast_none/pubspec.yaml rename to mobile/apps/photos/plugins/ente_cast_none/pubspec.yaml diff --git a/mobile/plugins/ente_cast_normal/.metadata b/mobile/apps/photos/plugins/ente_cast_normal/.metadata similarity index 100% rename from mobile/plugins/ente_cast_normal/.metadata rename to mobile/apps/photos/plugins/ente_cast_normal/.metadata diff --git a/mobile/plugins/ente_cast_normal/analysis_options.yaml b/mobile/apps/photos/plugins/ente_cast_normal/analysis_options.yaml similarity index 100% rename from mobile/plugins/ente_cast_normal/analysis_options.yaml rename to mobile/apps/photos/plugins/ente_cast_normal/analysis_options.yaml diff --git a/mobile/plugins/ente_cast_normal/lib/ente_cast_normal.dart b/mobile/apps/photos/plugins/ente_cast_normal/lib/ente_cast_normal.dart similarity index 100% rename from mobile/plugins/ente_cast_normal/lib/ente_cast_normal.dart rename to mobile/apps/photos/plugins/ente_cast_normal/lib/ente_cast_normal.dart diff --git a/mobile/plugins/ente_cast_normal/lib/src/service.dart b/mobile/apps/photos/plugins/ente_cast_normal/lib/src/service.dart similarity index 100% rename from mobile/plugins/ente_cast_normal/lib/src/service.dart rename to mobile/apps/photos/plugins/ente_cast_normal/lib/src/service.dart diff --git a/mobile/plugins/ente_cast_normal/pubspec.lock b/mobile/apps/photos/plugins/ente_cast_normal/pubspec.lock similarity index 100% rename from mobile/plugins/ente_cast_normal/pubspec.lock rename to mobile/apps/photos/plugins/ente_cast_normal/pubspec.lock diff --git a/mobile/plugins/ente_cast_normal/pubspec.yaml b/mobile/apps/photos/plugins/ente_cast_normal/pubspec.yaml similarity index 100% rename from mobile/plugins/ente_cast_normal/pubspec.yaml rename to mobile/apps/photos/plugins/ente_cast_normal/pubspec.yaml diff --git a/mobile/plugins/ente_crypto/.metadata b/mobile/apps/photos/plugins/ente_crypto/.metadata similarity index 100% rename from mobile/plugins/ente_crypto/.metadata rename to mobile/apps/photos/plugins/ente_crypto/.metadata diff --git a/mobile/plugins/ente_crypto/analysis_options.yaml b/mobile/apps/photos/plugins/ente_crypto/analysis_options.yaml similarity index 100% rename from mobile/plugins/ente_crypto/analysis_options.yaml rename to mobile/apps/photos/plugins/ente_crypto/analysis_options.yaml diff --git a/mobile/plugins/ente_crypto/lib/ente_crypto.dart b/mobile/apps/photos/plugins/ente_crypto/lib/ente_crypto.dart similarity index 100% rename from mobile/plugins/ente_crypto/lib/ente_crypto.dart rename to mobile/apps/photos/plugins/ente_crypto/lib/ente_crypto.dart diff --git a/mobile/plugins/ente_crypto/lib/src/crypto.dart b/mobile/apps/photos/plugins/ente_crypto/lib/src/crypto.dart similarity index 100% rename from mobile/plugins/ente_crypto/lib/src/crypto.dart rename to mobile/apps/photos/plugins/ente_crypto/lib/src/crypto.dart diff --git a/mobile/plugins/ente_crypto/lib/src/models/derived_key_result.dart b/mobile/apps/photos/plugins/ente_crypto/lib/src/models/derived_key_result.dart similarity index 100% rename from mobile/plugins/ente_crypto/lib/src/models/derived_key_result.dart rename to mobile/apps/photos/plugins/ente_crypto/lib/src/models/derived_key_result.dart diff --git a/mobile/plugins/ente_crypto/lib/src/models/encryption_result.dart b/mobile/apps/photos/plugins/ente_crypto/lib/src/models/encryption_result.dart similarity index 100% rename from mobile/plugins/ente_crypto/lib/src/models/encryption_result.dart rename to mobile/apps/photos/plugins/ente_crypto/lib/src/models/encryption_result.dart diff --git a/mobile/plugins/ente_crypto/lib/src/models/errors.dart b/mobile/apps/photos/plugins/ente_crypto/lib/src/models/errors.dart similarity index 100% rename from mobile/plugins/ente_crypto/lib/src/models/errors.dart rename to mobile/apps/photos/plugins/ente_crypto/lib/src/models/errors.dart diff --git a/mobile/plugins/ente_crypto/pubspec.lock b/mobile/apps/photos/plugins/ente_crypto/pubspec.lock similarity index 100% rename from mobile/plugins/ente_crypto/pubspec.lock rename to mobile/apps/photos/plugins/ente_crypto/pubspec.lock diff --git a/mobile/plugins/ente_crypto/pubspec.yaml b/mobile/apps/photos/plugins/ente_crypto/pubspec.yaml similarity index 100% rename from mobile/plugins/ente_crypto/pubspec.yaml rename to mobile/apps/photos/plugins/ente_crypto/pubspec.yaml diff --git a/mobile/plugins/ente_feature_flag/.metadata b/mobile/apps/photos/plugins/ente_feature_flag/.metadata similarity index 100% rename from mobile/plugins/ente_feature_flag/.metadata rename to mobile/apps/photos/plugins/ente_feature_flag/.metadata diff --git a/mobile/plugins/ente_feature_flag/analysis_options.yaml b/mobile/apps/photos/plugins/ente_feature_flag/analysis_options.yaml similarity index 100% rename from mobile/plugins/ente_feature_flag/analysis_options.yaml rename to mobile/apps/photos/plugins/ente_feature_flag/analysis_options.yaml diff --git a/mobile/plugins/ente_feature_flag/lib/ente_feature_flag.dart b/mobile/apps/photos/plugins/ente_feature_flag/lib/ente_feature_flag.dart similarity index 100% rename from mobile/plugins/ente_feature_flag/lib/ente_feature_flag.dart rename to mobile/apps/photos/plugins/ente_feature_flag/lib/ente_feature_flag.dart diff --git a/mobile/plugins/ente_feature_flag/lib/src/model.dart b/mobile/apps/photos/plugins/ente_feature_flag/lib/src/model.dart similarity index 100% rename from mobile/plugins/ente_feature_flag/lib/src/model.dart rename to mobile/apps/photos/plugins/ente_feature_flag/lib/src/model.dart diff --git a/mobile/plugins/ente_feature_flag/lib/src/service.dart b/mobile/apps/photos/plugins/ente_feature_flag/lib/src/service.dart similarity index 100% rename from mobile/plugins/ente_feature_flag/lib/src/service.dart rename to mobile/apps/photos/plugins/ente_feature_flag/lib/src/service.dart diff --git a/mobile/plugins/ente_feature_flag/pubspec.lock b/mobile/apps/photos/plugins/ente_feature_flag/pubspec.lock similarity index 100% rename from mobile/plugins/ente_feature_flag/pubspec.lock rename to mobile/apps/photos/plugins/ente_feature_flag/pubspec.lock diff --git a/mobile/plugins/ente_feature_flag/pubspec.yaml b/mobile/apps/photos/plugins/ente_feature_flag/pubspec.yaml similarity index 100% rename from mobile/plugins/ente_feature_flag/pubspec.yaml rename to mobile/apps/photos/plugins/ente_feature_flag/pubspec.yaml diff --git a/mobile/plugins/onnx_dart/.metadata b/mobile/apps/photos/plugins/onnx_dart/.metadata similarity index 100% rename from mobile/plugins/onnx_dart/.metadata rename to mobile/apps/photos/plugins/onnx_dart/.metadata diff --git a/mobile/plugins/onnx_dart/analysis_options.yaml b/mobile/apps/photos/plugins/onnx_dart/analysis_options.yaml similarity index 100% rename from mobile/plugins/onnx_dart/analysis_options.yaml rename to mobile/apps/photos/plugins/onnx_dart/analysis_options.yaml diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/checksums/checksums.lock b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/checksums/checksums.lock similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/checksums/checksums.lock rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/checksums/checksums.lock diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/dependencies-accessors.lock diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/gc.properties b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/gc.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/gc.properties rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/dependencies-accessors/gc.properties diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.bin b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.bin similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.bin rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.bin diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.lock b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.lock similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.lock rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/executionHistory/executionHistory.lock diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/fileChanges/last-build.bin b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/fileChanges/last-build.bin similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/fileChanges/last-build.bin rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/fileChanges/last-build.bin diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.bin b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.bin similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.bin rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.bin diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.lock b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.lock similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.lock rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/fileHashes/fileHashes.lock diff --git a/mobile/plugins/onnx_dart/android/.gradle/8.5/gc.properties b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/gc.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/8.5/gc.properties rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/8.5/gc.properties diff --git a/mobile/plugins/onnx_dart/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock diff --git a/mobile/plugins/onnx_dart/android/.gradle/buildOutputCleanup/cache.properties b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/buildOutputCleanup/cache.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/buildOutputCleanup/cache.properties rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/buildOutputCleanup/cache.properties diff --git a/mobile/plugins/onnx_dart/android/.gradle/buildOutputCleanup/outputFiles.bin b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/buildOutputCleanup/outputFiles.bin similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/buildOutputCleanup/outputFiles.bin rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/buildOutputCleanup/outputFiles.bin diff --git a/mobile/plugins/onnx_dart/android/.gradle/config.properties b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/config.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/config.properties rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/config.properties diff --git a/mobile/plugins/onnx_dart/android/.gradle/file-system.probe b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/file-system.probe similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/file-system.probe rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/file-system.probe diff --git a/mobile/plugins/onnx_dart/android/.gradle/vcs-1/gc.properties b/mobile/apps/photos/plugins/onnx_dart/android/.gradle/vcs-1/gc.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/.gradle/vcs-1/gc.properties rename to mobile/apps/photos/plugins/onnx_dart/android/.gradle/vcs-1/gc.properties diff --git a/mobile/plugins/onnx_dart/android/build.gradle b/mobile/apps/photos/plugins/onnx_dart/android/build.gradle similarity index 100% rename from mobile/plugins/onnx_dart/android/build.gradle rename to mobile/apps/photos/plugins/onnx_dart/android/build.gradle diff --git a/mobile/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.jar b/mobile/apps/photos/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from mobile/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.jar rename to mobile/apps/photos/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.jar diff --git a/mobile/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.properties b/mobile/apps/photos/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.properties rename to mobile/apps/photos/plugins/onnx_dart/android/gradle/wrapper/gradle-wrapper.properties diff --git a/mobile/plugins/onnx_dart/android/gradlew b/mobile/apps/photos/plugins/onnx_dart/android/gradlew similarity index 100% rename from mobile/plugins/onnx_dart/android/gradlew rename to mobile/apps/photos/plugins/onnx_dart/android/gradlew diff --git a/mobile/plugins/onnx_dart/android/gradlew.bat b/mobile/apps/photos/plugins/onnx_dart/android/gradlew.bat similarity index 100% rename from mobile/plugins/onnx_dart/android/gradlew.bat rename to mobile/apps/photos/plugins/onnx_dart/android/gradlew.bat diff --git a/mobile/plugins/onnx_dart/android/local.properties b/mobile/apps/photos/plugins/onnx_dart/android/local.properties similarity index 100% rename from mobile/plugins/onnx_dart/android/local.properties rename to mobile/apps/photos/plugins/onnx_dart/android/local.properties diff --git a/mobile/plugins/onnx_dart/android/settings.gradle b/mobile/apps/photos/plugins/onnx_dart/android/settings.gradle similarity index 100% rename from mobile/plugins/onnx_dart/android/settings.gradle rename to mobile/apps/photos/plugins/onnx_dart/android/settings.gradle diff --git a/mobile/plugins/onnx_dart/android/src/main/AndroidManifest.xml b/mobile/apps/photos/plugins/onnx_dart/android/src/main/AndroidManifest.xml similarity index 100% rename from mobile/plugins/onnx_dart/android/src/main/AndroidManifest.xml rename to mobile/apps/photos/plugins/onnx_dart/android/src/main/AndroidManifest.xml diff --git a/mobile/plugins/onnx_dart/android/src/main/kotlin/io/ente/photos/onnx_dart/OnnxDartPlugin.kt b/mobile/apps/photos/plugins/onnx_dart/android/src/main/kotlin/io/ente/photos/onnx_dart/OnnxDartPlugin.kt similarity index 100% rename from mobile/plugins/onnx_dart/android/src/main/kotlin/io/ente/photos/onnx_dart/OnnxDartPlugin.kt rename to mobile/apps/photos/plugins/onnx_dart/android/src/main/kotlin/io/ente/photos/onnx_dart/OnnxDartPlugin.kt diff --git a/mobile/plugins/onnx_dart/lib/onnx_dart.dart b/mobile/apps/photos/plugins/onnx_dart/lib/onnx_dart.dart similarity index 100% rename from mobile/plugins/onnx_dart/lib/onnx_dart.dart rename to mobile/apps/photos/plugins/onnx_dart/lib/onnx_dart.dart diff --git a/mobile/plugins/onnx_dart/lib/onnx_dart_method_channel.dart b/mobile/apps/photos/plugins/onnx_dart/lib/onnx_dart_method_channel.dart similarity index 100% rename from mobile/plugins/onnx_dart/lib/onnx_dart_method_channel.dart rename to mobile/apps/photos/plugins/onnx_dart/lib/onnx_dart_method_channel.dart diff --git a/mobile/plugins/onnx_dart/lib/onnx_dart_platform_interface.dart b/mobile/apps/photos/plugins/onnx_dart/lib/onnx_dart_platform_interface.dart similarity index 100% rename from mobile/plugins/onnx_dart/lib/onnx_dart_platform_interface.dart rename to mobile/apps/photos/plugins/onnx_dart/lib/onnx_dart_platform_interface.dart diff --git a/mobile/plugins/onnx_dart/pubspec.lock b/mobile/apps/photos/plugins/onnx_dart/pubspec.lock similarity index 100% rename from mobile/plugins/onnx_dart/pubspec.lock rename to mobile/apps/photos/plugins/onnx_dart/pubspec.lock diff --git a/mobile/plugins/onnx_dart/pubspec.yaml b/mobile/apps/photos/plugins/onnx_dart/pubspec.yaml similarity index 100% rename from mobile/plugins/onnx_dart/pubspec.yaml rename to mobile/apps/photos/plugins/onnx_dart/pubspec.yaml diff --git a/mobile/pubspec.lock b/mobile/apps/photos/pubspec.lock similarity index 100% rename from mobile/pubspec.lock rename to mobile/apps/photos/pubspec.lock diff --git a/mobile/pubspec.yaml b/mobile/apps/photos/pubspec.yaml similarity index 100% rename from mobile/pubspec.yaml rename to mobile/apps/photos/pubspec.yaml diff --git a/mobile/run.sh b/mobile/apps/photos/run.sh similarity index 100% rename from mobile/run.sh rename to mobile/apps/photos/run.sh diff --git a/mobile/scripts/app_init_perf_test.sh b/mobile/apps/photos/scripts/app_init_perf_test.sh similarity index 100% rename from mobile/scripts/app_init_perf_test.sh rename to mobile/apps/photos/scripts/app_init_perf_test.sh diff --git a/mobile/scripts/bump_version.sh b/mobile/apps/photos/scripts/bump_version.sh similarity index 100% rename from mobile/scripts/bump_version.sh rename to mobile/apps/photos/scripts/bump_version.sh diff --git a/mobile/scripts/create_tag.sh b/mobile/apps/photos/scripts/create_tag.sh similarity index 100% rename from mobile/scripts/create_tag.sh rename to mobile/apps/photos/scripts/create_tag.sh diff --git a/mobile/scripts/gallery_scroll_perf_test.sh b/mobile/apps/photos/scripts/gallery_scroll_perf_test.sh similarity index 100% rename from mobile/scripts/gallery_scroll_perf_test.sh rename to mobile/apps/photos/scripts/gallery_scroll_perf_test.sh diff --git a/mobile/test/utils/date_time_util_test.dart b/mobile/apps/photos/test/utils/date_time_util_test.dart similarity index 100% rename from mobile/test/utils/date_time_util_test.dart rename to mobile/apps/photos/test/utils/date_time_util_test.dart diff --git a/mobile/test/utils/parsing_loc_from_exif_test.dart b/mobile/apps/photos/test/utils/parsing_loc_from_exif_test.dart similarity index 100% rename from mobile/test/utils/parsing_loc_from_exif_test.dart rename to mobile/apps/photos/test/utils/parsing_loc_from_exif_test.dart diff --git a/mobile/test_driver/perf_driver.dart b/mobile/apps/photos/test_driver/perf_driver.dart similarity index 100% rename from mobile/test_driver/perf_driver.dart rename to mobile/apps/photos/test_driver/perf_driver.dart From 99d7685be2742e016dd000a1c1416d0d9534ec43 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:16:27 +0530 Subject: [PATCH 045/109] Move auth/ to mobile/apps/auth --- .gitmodules | 4 ++-- {auth => mobile/apps/auth}/.fvmrc | 0 {auth => mobile/apps/auth}/.gitignore | 0 {auth => mobile/apps/auth}/.metadata | 0 {auth => mobile/apps/auth}/README.md | 0 {auth => mobile/apps/auth}/analysis_options.yaml | 0 {auth => mobile/apps/auth}/android/.gitignore | 0 {auth => mobile/apps/auth}/android/app/build.gradle | 0 .../auth}/android/app/src/debug/AndroidManifest.xml | 0 .../app/src/development/ic_launcher-playstore.png | Bin .../res/drawable/ic_launcher_foreground.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../src/development/res/mipmap-hdpi/ic_launcher.png | Bin .../res/mipmap-hdpi/ic_launcher_round.png | Bin .../src/development/res/mipmap-mdpi/ic_launcher.png | Bin .../res/mipmap-mdpi/ic_launcher_round.png | Bin .../development/res/mipmap-xhdpi/ic_launcher.png | Bin .../res/mipmap-xhdpi/ic_launcher_round.png | Bin .../development/res/mipmap-xxhdpi/ic_launcher.png | Bin .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../development/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../res/values/ic_launcher_background.xml | 0 .../auth}/android/app/src/main/AndroidManifest.xml | 0 .../kotlin/io/ente/authenticator/MainActivity.kt | 0 .../main/play/listings/en-US/full-description.txt | 0 .../main/play/listings/en-US/graphics/icon/icon.png | Bin .../listings/en-US/graphics/phone-screenshots/1.png | Bin .../listings/en-US/graphics/phone-screenshots/2.png | Bin .../listings/en-US/graphics/phone-screenshots/3.png | Bin .../listings/en-US/graphics/phone-screenshots/4.png | Bin .../listings/en-US/graphics/phone-screenshots/5.png | Bin .../main/play/listings/en-US/short-description.txt | 0 .../app/src/main/play/listings/en-US/title.txt | 0 .../src/main/res/drawable-hdpi/android12splash.png | Bin .../res/drawable-hdpi/ic_launcher_background.png | Bin .../res/drawable-hdpi/ic_launcher_foreground.png | Bin .../res/drawable-hdpi/ic_launcher_monochrome.png | Bin .../app/src/main/res/drawable-hdpi/splash.png | Bin .../src/main/res/drawable-mdpi/android12splash.png | Bin .../res/drawable-mdpi/ic_launcher_background.png | Bin .../res/drawable-mdpi/ic_launcher_foreground.png | Bin .../res/drawable-mdpi/ic_launcher_monochrome.png | Bin .../app/src/main/res/drawable-mdpi/splash.png | Bin .../res/drawable-night-hdpi/android12splash.png | Bin .../res/drawable-night-mdpi/android12splash.png | Bin .../src/main/res/drawable-night-v21/background.png | Bin .../res/drawable-night-v21/launch_background.xml | 0 .../res/drawable-night-xhdpi/android12splash.png | Bin .../res/drawable-night-xxhdpi/android12splash.png | Bin .../res/drawable-night-xxxhdpi/android12splash.png | Bin .../app/src/main/res/drawable-night/background.png | Bin .../main/res/drawable-night/launch_background.xml | 0 .../app/src/main/res/drawable-v21/background.png | Bin .../src/main/res/drawable-v21/launch_background.xml | 0 .../src/main/res/drawable-xhdpi/android12splash.png | Bin .../res/drawable-xhdpi/ic_launcher_background.png | Bin .../res/drawable-xhdpi/ic_launcher_foreground.png | Bin .../res/drawable-xhdpi/ic_launcher_monochrome.png | Bin .../app/src/main/res/drawable-xhdpi/splash.png | Bin .../main/res/drawable-xxhdpi/android12splash.png | Bin .../res/drawable-xxhdpi/ic_launcher_background.png | Bin .../res/drawable-xxhdpi/ic_launcher_foreground.png | Bin .../res/drawable-xxhdpi/ic_launcher_monochrome.png | Bin .../app/src/main/res/drawable-xxhdpi/splash.png | Bin .../main/res/drawable-xxxhdpi/android12splash.png | Bin .../res/drawable-xxxhdpi/ic_launcher_background.png | Bin .../res/drawable-xxxhdpi/ic_launcher_foreground.png | Bin .../res/drawable-xxxhdpi/ic_launcher_monochrome.png | Bin .../app/src/main/res/drawable-xxxhdpi/splash.png | Bin .../app/src/main/res/drawable/background.png | Bin .../app/src/main/res/drawable/launch_background.xml | 0 .../app/src/main/res/drawable/notification_icon.png | Bin .../main/res/mipmap-anydpi-v26/launcher_icon.xml | 0 .../app/src/main/res/mipmap-hdpi/launcher_icon.png | Bin .../app/src/main/res/mipmap-mdpi/launcher_icon.png | Bin .../app/src/main/res/mipmap-xhdpi/launcher_icon.png | Bin .../src/main/res/mipmap-xxhdpi/launcher_icon.png | Bin .../src/main/res/mipmap-xxxhdpi/launcher_icon.png | Bin .../app/src/main/res/values-night-v31/styles.xml | 0 .../app/src/main/res/values-night/styles.xml | 0 .../android/app/src/main/res/values-v31/styles.xml | 0 .../android/app/src/main/res/values/colors.xml | 0 .../android/app/src/main/res/values/styles.xml | 0 .../android/app/src/profile/AndroidManifest.xml | 0 .../app/src/staging/ic_launcher-playstore.png | Bin .../staging/res/drawable/ic_launcher_foreground.xml | 0 .../staging/res/mipmap-anydpi-v26/ic_launcher.xml | 0 .../res/mipmap-anydpi-v26/ic_launcher_round.xml | 0 .../app/src/staging/res/mipmap-hdpi/ic_launcher.png | Bin .../staging/res/mipmap-hdpi/ic_launcher_round.png | Bin .../app/src/staging/res/mipmap-mdpi/ic_launcher.png | Bin .../staging/res/mipmap-mdpi/ic_launcher_round.png | Bin .../src/staging/res/mipmap-xhdpi/ic_launcher.png | Bin .../staging/res/mipmap-xhdpi/ic_launcher_round.png | Bin .../src/staging/res/mipmap-xxhdpi/ic_launcher.png | Bin .../staging/res/mipmap-xxhdpi/ic_launcher_round.png | Bin .../src/staging/res/mipmap-xxxhdpi/ic_launcher.png | Bin .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin .../staging/res/values/ic_launcher_background.xml | 0 {auth => mobile/apps/auth}/android/build.gradle | 0 .../apps/auth}/android/gradle.properties | 0 .../gradle/wrapper/gradle-wrapper.properties | 0 {auth => mobile/apps/auth}/android/settings.gradle | 0 {auth => mobile/apps/auth}/architecture/README.md | 0 .../auth}/architecture/assets/authentication.svg | 0 .../apps/auth}/architecture/assets/e2ee.svg | 0 .../auth}/architecture/assets/key-derivation.svg | 0 .../apps/auth}/architecture/assets/recovery.svg | 0 .../auth}/architecture/assets/token-encryption.svg | 0 .../apps/auth}/assets/2.0x/broken_heart.png | Bin .../apps/auth}/assets/2.0x/calender_banner_dark.png | Bin .../auth}/assets/2.0x/calender_banner_light.png | Bin {auth => mobile/apps/auth}/assets/2.0x/discount.png | Bin {auth => mobile/apps/auth}/assets/2.0x/ente_5gb.png | Bin .../auth}/assets/2.0x/loading_photos_background.png | Bin .../assets/2.0x/loading_photos_background_dark.png | Bin {auth => mobile/apps/auth}/assets/2.0x/rate_us.png | Bin .../auth}/assets/2.0x/sheild-front-gradient.png | Bin {auth => mobile/apps/auth}/assets/2.0x/star_us.png | Bin .../auth}/assets/2.0x/wallet-front-gradient.png | Bin .../apps/auth}/assets/3.0x/broken_heart.png | Bin .../apps/auth}/assets/3.0x/calender_banner_dark.png | Bin .../auth}/assets/3.0x/calender_banner_light.png | Bin {auth => mobile/apps/auth}/assets/3.0x/discount.png | Bin {auth => mobile/apps/auth}/assets/3.0x/ente_5gb.png | Bin .../auth}/assets/3.0x/loading_photos_background.png | Bin .../assets/3.0x/loading_photos_background_dark.png | Bin {auth => mobile/apps/auth}/assets/3.0x/rate_us.png | Bin .../auth}/assets/3.0x/sheild-front-gradient.png | Bin {auth => mobile/apps/auth}/assets/3.0x/star_us.png | Bin .../auth}/assets/3.0x/wallet-front-gradient.png | Bin {auth => mobile/apps/auth}/assets/broken_heart.png | Bin .../apps/auth}/assets/build/.last_build_id | 0 .../apps/auth}/assets/calender_banner_dark.png | Bin .../apps/auth}/assets/calender_banner_light.png | Bin .../assets/custom-icons/_data/custom-icons.json | 0 .../apps/auth}/assets/custom-icons/icons/1x_bet.svg | 0 .../auth}/assets/custom-icons/icons/23andme.svg | 0 .../auth}/assets/custom-icons/icons/3commas.svg | 0 .../auth}/assets/custom-icons/icons/addy_io.svg | 0 .../auth}/assets/custom-icons/icons/airtable.svg | 0 .../apps/auth}/assets/custom-icons/icons/airtm.svg | 0 .../auth}/assets/custom-icons/icons/aj_bell.svg | 0 .../apps/auth}/assets/custom-icons/icons/aliyun.svg | 0 .../apps/auth}/assets/custom-icons/icons/amazon.svg | 0 .../apps/auth}/assets/custom-icons/icons/ankama.svg | 0 .../assets/custom-icons/icons/ankara_university.svg | 0 .../assets/custom-icons/icons/anycoindirect.svg | 0 .../apps/auth}/assets/custom-icons/icons/ar24.svg | 0 .../apps/auth}/assets/custom-icons/icons/aruba.svg | 0 .../auth}/assets/custom-icons/icons/ascendex.svg | 0 .../auth}/assets/custom-icons/icons/aternos.svg | 0 .../auth}/assets/custom-icons/icons/authentik.svg | 0 .../auth}/assets/custom-icons/icons/azurhosts.svg | 0 .../auth}/assets/custom-icons/icons/azurware.svg | 0 .../auth}/assets/custom-icons/icons/badlion.svg | 0 .../auth}/assets/custom-icons/icons/baidu_cloud.svg | 0 .../apps/auth}/assets/custom-icons/icons/band.svg | 0 .../auth}/assets/custom-icons/icons/battlenet.svg | 0 .../auth}/assets/custom-icons/icons/bbs_nga.svg | 0 .../apps/auth}/assets/custom-icons/icons/belo.svg | 0 .../auth}/assets/custom-icons/icons/bethesda.svg | 0 .../assets/custom-icons/icons/binance_exchange.svg | 0 .../auth}/assets/custom-icons/icons/binance_tr.svg | 0 .../auth}/assets/custom-icons/icons/binance_us.svg | 0 .../apps/auth}/assets/custom-icons/icons/bingx.svg | 0 .../auth}/assets/custom-icons/icons/bitazza.svg | 0 .../auth}/assets/custom-icons/icons/bitfinex.svg | 0 .../apps/auth}/assets/custom-icons/icons/bitget.svg | 0 .../assets/custom-icons/icons/bitget_wallet.svg | 0 .../apps/auth}/assets/custom-icons/icons/bitkub.svg | 0 .../auth}/assets/custom-icons/icons/bitmart.svg | 0 .../apps/auth}/assets/custom-icons/icons/bitmex.svg | 0 .../auth}/assets/custom-icons/icons/bitoasis.svg | 0 .../auth}/assets/custom-icons/icons/bitskins.svg | 0 .../auth}/assets/custom-icons/icons/bitstamp.svg | 0 .../auth}/assets/custom-icons/icons/bitvavo.svg | 0 .../auth}/assets/custom-icons/icons/bitwarden.svg | 0 .../auth}/assets/custom-icons/icons/blockchain.svg | 0 .../auth}/assets/custom-icons/icons/bloom_host.svg | 0 .../auth}/assets/custom-icons/icons/blue_sky.svg | 0 .../apps/auth}/assets/custom-icons/icons/bonify.svg | 0 .../auth}/assets/custom-icons/icons/booking.svg | 0 .../auth}/assets/custom-icons/icons/borg_base.svg | 0 .../assets/custom-icons/icons/brave_creators.svg | 0 .../auth}/assets/custom-icons/icons/bugzilla.svg | 0 .../custom-icons/icons/bundesagentur_fur_arbeit.svg | 0 .../auth}/assets/custom-icons/icons/butterflymx.svg | 0 .../apps/auth}/assets/custom-icons/icons/bybit.svg | 0 .../apps/auth}/assets/custom-icons/icons/caixa.svg | 0 .../auth}/assets/custom-icons/icons/canada_flag.svg | 0 .../apps/auth}/assets/custom-icons/icons/canva.svg | 0 .../auth}/assets/custom-icons/icons/capacities.svg | 0 .../apps/auth}/assets/custom-icons/icons/carta.svg | 0 .../apps/auth}/assets/custom-icons/icons/cern.svg | 0 .../auth}/assets/custom-icons/icons/changenow.svg | 0 .../apps/auth}/assets/custom-icons/icons/cih.svg | 0 .../auth}/assets/custom-icons/icons/cloudamqp.svg | 0 .../auth}/assets/custom-icons/icons/cloudflare.svg | 0 .../auth}/assets/custom-icons/icons/cloudns.svg | 0 .../auth}/assets/custom-icons/icons/coinbase.svg | 0 .../auth}/assets/custom-icons/icons/coindcx.svg | 0 .../auth}/assets/custom-icons/icons/coinspot.svg | 0 .../auth}/assets/custom-icons/icons/configcat.svg | 0 .../auth}/assets/custom-icons/icons/controld.svg | 0 .../auth}/assets/custom-icons/icons/cronometer.svg | 0 .../auth}/assets/custom-icons/icons/crowdpear.svg | 0 .../auth}/assets/custom-icons/icons/cryptee.svg | 0 .../apps/auth}/assets/custom-icons/icons/crypto.svg | 0 .../apps/auth}/assets/custom-icons/icons/csam.svg | 0 .../auth}/assets/custom-icons/icons/csfloat.svg | 0 .../auth}/assets/custom-icons/icons/csgoroll.svg | 0 .../apps/auth}/assets/custom-icons/icons/cssbuy.svg | 0 .../auth}/assets/custom-icons/icons/cwallet.svg | 0 .../apps/auth}/assets/custom-icons/icons/dcs.svg | 0 .../apps/auth}/assets/custom-icons/icons/degiro.svg | 0 .../auth}/assets/custom-icons/icons/deloitte.svg | 0 .../apps/auth}/assets/custom-icons/icons/deriv.svg | 0 .../auth}/assets/custom-icons/icons/digifinex.svg | 0 .../auth}/assets/custom-icons/icons/directadmin.svg | 0 .../auth}/assets/custom-icons/icons/discourse.svg | 0 .../auth}/assets/custom-icons/icons/dmarket.svg | 0 .../auth}/assets/custom-icons/icons/docuseal.svg | 0 .../auth}/assets/custom-icons/icons/doppler.svg | 0 .../assets/custom-icons/icons/dreamhost_panel.svg | 0 .../auth}/assets/custom-icons/icons/dropbox.svg | 0 .../apps/auth}/assets/custom-icons/icons/dusnet.svg | 0 .../apps/auth}/assets/custom-icons/icons/ebay.svg | 0 .../assets/custom-icons/icons/ecitizen_kenya.svg | 0 .../apps/auth}/assets/custom-icons/icons/ecloud.svg | 0 .../apps/auth}/assets/custom-icons/icons/eneba.svg | 0 .../apps/auth}/assets/custom-icons/icons/enom.svg | 0 .../apps/auth}/assets/custom-icons/icons/ente.svg | 0 .../auth}/assets/custom-icons/icons/epic_games.svg | 0 .../auth}/assets/custom-icons/icons/esketit.svg | 0 .../auth}/assets/custom-icons/icons/estateguru.svg | 0 .../auth}/assets/custom-icons/icons/eve_online.svg | 0 .../auth}/assets/custom-icons/icons/fanatical.svg | 0 .../auth}/assets/custom-icons/icons/fastmail.svg | 0 .../custom-icons/icons/federal_student_aid.svg | 0 .../auth}/assets/custom-icons/icons/fidelity.svg | 0 .../apps/auth}/assets/custom-icons/icons/filen.svg | 0 .../auth}/assets/custom-icons/icons/finanzfluss.svg | 0 .../apps/auth}/assets/custom-icons/icons/finary.svg | 0 .../auth}/assets/custom-icons/icons/fortrabbit.svg | 0 .../auth}/assets/custom-icons/icons/forusall.svg | 0 .../auth}/assets/custom-icons/icons/freetaxusa.svg | 0 .../apps/auth}/assets/custom-icons/icons/fzj.svg | 0 .../apps/auth}/assets/custom-icons/icons/g2a.svg | 0 .../apps/auth}/assets/custom-icons/icons/gateio.svg | 0 .../apps/auth}/assets/custom-icons/icons/gerid.svg | 0 .../apps/auth}/assets/custom-icons/icons/github.svg | 0 .../apps/auth}/assets/custom-icons/icons/gitlab.svg | 0 .../apps/auth}/assets/custom-icons/icons/gmx.svg | 0 .../auth}/assets/custom-icons/icons/gommehd.svg | 0 .../apps/auth}/assets/custom-icons/icons/google.svg | 0 .../auth}/assets/custom-icons/icons/gosuslugi.svg | 0 .../apps/auth}/assets/custom-icons/icons/gov_uk.svg | 0 .../auth}/assets/custom-icons/icons/guideline.svg | 0 .../apps/auth}/assets/custom-icons/icons/gusto.svg | 0 .../apps/auth}/assets/custom-icons/icons/habbo.svg | 0 .../assets/custom-icons/icons/healthchecks.svg | 0 .../auth}/assets/custom-icons/icons/hivelocity.svg | 0 .../apps/auth}/assets/custom-icons/icons/htx.svg | 0 .../auth}/assets/custom-icons/icons/huggingface.svg | 0 .../apps/auth}/assets/custom-icons/icons/ibkr.svg | 0 .../auth}/assets/custom-icons/icons/ice_drive.svg | 0 .../auth}/assets/custom-icons/icons/iconomi.svg | 0 .../apps/auth}/assets/custom-icons/icons/id_me.svg | 0 .../assets/custom-icons/icons/immo_scout_24.svg | 0 .../auth}/assets/custom-icons/icons/infomaniak.svg | 0 .../apps/auth}/assets/custom-icons/icons/ing.svg | 0 .../auth}/assets/custom-icons/icons/instagram.svg | 0 .../assets/custom-icons/icons/instant_gaming.svg | 0 .../apps/auth}/assets/custom-icons/icons/inwx.svg | 0 .../auth}/assets/custom-icons/icons/itch_io.svg | 0 .../apps/auth}/assets/custom-icons/icons/ivpn.svg | 0 .../apps/auth}/assets/custom-icons/icons/jagex.svg | 0 .../auth}/assets/custom-icons/icons/jianguoyun.svg | 0 .../apps/auth}/assets/custom-icons/icons/kagi.svg | 0 .../apps/auth}/assets/custom-icons/icons/keygen.svg | 0 .../apps/auth}/assets/custom-icons/icons/kick.svg | 0 .../apps/auth}/assets/custom-icons/icons/kite.svg | 0 .../auth}/assets/custom-icons/icons/knownhost.svg | 0 .../apps/auth}/assets/custom-icons/icons/ko_fi.svg | 0 .../apps/auth}/assets/custom-icons/icons/koofr.svg | 0 .../apps/auth}/assets/custom-icons/icons/kotas.svg | 0 .../apps/auth}/assets/custom-icons/icons/kpn.svg | 0 .../apps/auth}/assets/custom-icons/icons/kraken.svg | 0 .../apps/auth}/assets/custom-icons/icons/kronos.svg | 0 .../apps/auth}/assets/custom-icons/icons/kucoin.svg | 0 .../auth}/assets/custom-icons/icons/labymod.svg | 0 .../auth}/assets/custom-icons/icons/laposte.svg | 0 .../apps/auth}/assets/custom-icons/icons/lark.svg | 0 .../assets/custom-icons/icons/launchdarkly.svg | 0 .../auth}/assets/custom-icons/icons/letterboxd.svg | 0 .../auth}/assets/custom-icons/icons/linkedin.svg | 0 .../auth}/assets/custom-icons/icons/linux_do.svg | 0 .../auth}/assets/custom-icons/icons/local_wp.svg | 0 .../auth}/assets/custom-icons/icons/login_gov.svg | 0 .../apps/auth}/assets/custom-icons/icons/luma.svg | 0 .../assets/custom-icons/icons/marketplacedottf.svg | 0 .../auth}/assets/custom-icons/icons/mastodon.svg | 0 .../apps/auth}/assets/custom-icons/icons/matlab.svg | 0 .../apps/auth}/assets/custom-icons/icons/mbin.svg | 0 .../apps/auth}/assets/custom-icons/icons/memed.svg | 0 .../assets/custom-icons/icons/mercado_libre.svg | 0 .../apps/auth}/assets/custom-icons/icons/mexc.svg | 0 .../auth}/assets/custom-icons/icons/microsoft.svg | 0 .../assets/custom-icons/icons/microsoft365.svg | 0 .../apps/auth}/assets/custom-icons/icons/migros.svg | 0 .../apps/auth}/assets/custom-icons/icons/mintos.svg | 0 .../auth}/assets/custom-icons/icons/mistral.svg | 0 .../auth}/assets/custom-icons/icons/mozilla.svg | 0 .../auth}/assets/custom-icons/icons/myfritz.svg | 0 .../auth}/assets/custom-icons/icons/name_com.svg | 0 .../auth}/assets/custom-icons/icons/nekohosting.svg | 0 .../assets/custom-icons/icons/nekohosting_gp.svg | 0 .../apps/auth}/assets/custom-icons/icons/nelnet.svg | 0 .../assets/custom-icons/icons/netease_mail.svg | 0 .../auth}/assets/custom-icons/icons/newgrounds.svg | 0 .../apps/auth}/assets/custom-icons/icons/newton.svg | 0 .../auth}/assets/custom-icons/icons/nextcloud.svg | 0 .../auth}/assets/custom-icons/icons/nextdns.svg | 0 .../apps/auth}/assets/custom-icons/icons/ngrok.svg | 0 .../auth}/assets/custom-icons/icons/nintendo.svg | 0 .../apps/auth}/assets/custom-icons/icons/njalla.svg | 0 .../apps/auth}/assets/custom-icons/icons/noip.svg | 0 .../auth}/assets/custom-icons/icons/nordaccount.svg | 0 .../auth}/assets/custom-icons/icons/notesnook.svg | 0 .../apps/auth}/assets/custom-icons/icons/notion.svg | 0 .../auth}/assets/custom-icons/icons/nucommunity.svg | 0 .../apps/auth}/assets/custom-icons/icons/nvidia.svg | 0 .../apps/auth}/assets/custom-icons/icons/odido.svg | 0 .../apps/auth}/assets/custom-icons/icons/okx.svg | 0 .../assets/custom-icons/icons/open_observe.svg | 0 .../assets/custom-icons/icons/oracle_cloud.svg | 0 .../apps/auth}/assets/custom-icons/icons/parqet.svg | 0 .../apps/auth}/assets/custom-icons/icons/parsec.svg | 0 .../assets/custom-icons/icons/patient_access.svg | 0 .../apps/auth}/assets/custom-icons/icons/paypal.svg | 0 .../apps/auth}/assets/custom-icons/icons/pbtech.svg | 0 .../apps/auth}/assets/custom-icons/icons/pcloud.svg | 0 .../auth}/assets/custom-icons/icons/pebble_host.svg | 0 .../auth}/assets/custom-icons/icons/peerberry.svg | 0 .../assets/custom-icons/icons/pingvinshare.svg | 0 .../apps/auth}/assets/custom-icons/icons/pionex.svg | 0 .../apps/auth}/assets/custom-icons/icons/plutus.svg | 0 .../auth}/assets/custom-icons/icons/poloniex.svg | 0 .../auth}/assets/custom-icons/icons/porkbun.svg | 0 .../auth}/assets/custom-icons/icons/postmarkapp.svg | 0 .../apps/auth}/assets/custom-icons/icons/postnl.svg | 0 .../assets/custom-icons/icons/postscanmail.svg | 0 .../assets/custom-icons/icons/prey_project.svg | 0 .../auth}/assets/custom-icons/icons/privacy.svg | 0 .../assets/custom-icons/icons/privacyguides.svg | 0 .../apps/auth}/assets/custom-icons/icons/proton.svg | 0 .../auth}/assets/custom-icons/icons/proxmox.svg | 0 .../auth}/assets/custom-icons/icons/pushover.svg | 0 .../auth}/assets/custom-icons/icons/qiniuyun.svg | 0 .../apps/auth}/assets/custom-icons/icons/r10.svg | 0 .../auth}/assets/custom-icons/icons/raindrop_io.svg | 0 .../auth}/assets/custom-icons/icons/randstad.svg | 0 .../auth}/assets/custom-icons/icons/real_debrid.svg | 0 .../apps/auth}/assets/custom-icons/icons/realme.svg | 0 .../auth}/assets/custom-icons/icons/realvnc.svg | 0 .../auth}/assets/custom-icons/icons/redotpay.svg | 0 .../auth}/assets/custom-icons/icons/registro_br.svg | 0 .../auth}/assets/custom-icons/icons/remarkable.svg | 0 .../apps/auth}/assets/custom-icons/icons/render.svg | 0 .../auth}/assets/custom-icons/icons/restream.svg | 0 .../apps/auth}/assets/custom-icons/icons/revolt.svg | 0 .../auth}/assets/custom-icons/icons/ripplematch.svg | 0 .../assets/custom-icons/icons/rockstar_games.svg | 0 .../auth}/assets/custom-icons/icons/runemate.svg | 0 .../assets/custom-icons/icons/runescape_wiki.svg | 0 .../custom-icons/icons/rust_language_forum.svg | 0 .../auth}/assets/custom-icons/icons/samsung.svg | 0 .../auth}/assets/custom-icons/icons/seafile.svg | 0 .../apps/auth}/assets/custom-icons/icons/sei.svg | 0 .../auth}/assets/custom-icons/icons/sendgrid.svg | 0 .../auth}/assets/custom-icons/icons/service-bw.svg | 0 .../auth}/assets/custom-icons/icons/shakepay.svg | 0 .../auth}/assets/custom-icons/icons/simplelogin.svg | 0 .../auth}/assets/custom-icons/icons/simplicity.svg | 0 .../auth}/assets/custom-icons/icons/sipgate.svg | 0 .../apps/auth}/assets/custom-icons/icons/skiff.svg | 0 .../auth}/assets/custom-icons/icons/skinport.svg | 0 .../assets/custom-icons/icons/sms_pool_net.svg | 0 .../auth}/assets/custom-icons/icons/smtp2go.svg | 0 .../auth}/assets/custom-icons/icons/snapchat.svg | 0 .../auth}/assets/custom-icons/icons/spacehey.svg | 0 .../assets/custom-icons/icons/standardnotes.svg | 0 .../auth}/assets/custom-icons/icons/starbreeze.svg | 0 .../apps/auth}/assets/custom-icons/icons/strato.svg | 0 .../auth}/assets/custom-icons/icons/surfshark.svg | 0 .../assets/custom-icons/icons/synology_dsm.svg | 0 .../auth}/assets/custom-icons/icons/t-mobile.svg | 0 .../auth}/assets/custom-icons/icons/tcpshield.svg | 0 .../apps/auth}/assets/custom-icons/icons/tebex.svg | 0 .../auth}/assets/custom-icons/icons/techlore.svg | 0 .../auth}/assets/custom-icons/icons/teleport.svg | 0 .../assets/custom-icons/icons/tencent_cloud.svg | 0 .../auth}/assets/custom-icons/icons/terabit.svg | 0 .../auth}/assets/custom-icons/icons/termius.svg | 0 .../auth}/assets/custom-icons/icons/tianyiyun.svg | 0 .../apps/auth}/assets/custom-icons/icons/tiktok.svg | 0 .../apps/auth}/assets/custom-icons/icons/titan.svg | 0 .../auth}/assets/custom-icons/icons/torguard.svg | 0 .../assets/custom-icons/icons/toshl_finance.svg | 0 .../auth}/assets/custom-icons/icons/trading212.svg | 0 .../auth}/assets/custom-icons/icons/tradingview.svg | 0 .../auth}/assets/custom-icons/icons/transip.svg | 0 .../auth}/assets/custom-icons/icons/tresorit.svg | 0 .../auth}/assets/custom-icons/icons/troweprice.svg | 0 .../auth}/assets/custom-icons/icons/tweakers.svg | 0 .../auth}/assets/custom-icons/icons/twingate.svg | 0 .../apps/auth}/assets/custom-icons/icons/twitch.svg | 0 .../auth}/assets/custom-icons/icons/ubiquiti.svg | 0 .../auth}/assets/custom-icons/icons/ubisoft.svg | 0 .../auth}/assets/custom-icons/icons/ubuntu_one.svg | 0 .../apps/auth}/assets/custom-icons/icons/unity.svg | 0 .../apps/auth}/assets/custom-icons/icons/uollet.svg | 0 .../apps/auth}/assets/custom-icons/icons/uphold.svg | 0 .../apps/auth}/assets/custom-icons/icons/upstox.svg | 0 .../auth}/assets/custom-icons/icons/us_mobile.svg | 0 .../auth}/assets/custom-icons/icons/vikunja.svg | 0 .../auth}/assets/custom-icons/icons/volcengine.svg | 0 .../assets/custom-icons/icons/wargamingnet.svg | 0 .../auth}/assets/custom-icons/icons/warner_bros.svg | 0 .../apps/auth}/assets/custom-icons/icons/wca.svg | 0 .../auth}/assets/custom-icons/icons/wealthfront.svg | 0 .../assets/custom-icons/icons/wealthsimple.svg | 0 .../apps/auth}/assets/custom-icons/icons/web_de.svg | 0 .../apps/auth}/assets/custom-icons/icons/whmcs.svg | 0 .../auth}/assets/custom-icons/icons/windscribe.svg | 0 .../apps/auth}/assets/custom-icons/icons/wise.svg | 0 .../auth}/assets/custom-icons/icons/wolvesville.svg | 0 .../auth}/assets/custom-icons/icons/workflowy.svg | 0 .../apps/auth}/assets/custom-icons/icons/workos.svg | 0 .../apps/auth}/assets/custom-icons/icons/wyze.svg | 0 .../apps/auth}/assets/custom-icons/icons/xai.svg | 0 .../apps/auth}/assets/custom-icons/icons/xbox.svg | 0 .../apps/auth}/assets/custom-icons/icons/yahoo.svg | 0 .../apps/auth}/assets/custom-icons/icons/yandex.svg | 0 .../apps/auth}/assets/custom-icons/icons/ynab.svg | 0 .../auth}/assets/custom-icons/icons/zitadel.svg | 0 .../apps/auth}/assets/custom-icons/icons/zoom.svg | 0 {auth => mobile/apps/auth}/assets/discount.png | Bin {auth => mobile/apps/auth}/assets/ente_5gb.png | Bin .../apps/auth}/assets/fonts/Inter-Bold.ttf | Bin .../apps/auth}/assets/fonts/Inter-Light.ttf | Bin .../apps/auth}/assets/fonts/Inter-Medium.ttf | Bin .../apps/auth}/assets/fonts/Inter-Regular.ttf | Bin .../apps/auth}/assets/fonts/Inter-SemiBold.ttf | Bin .../apps/auth}/assets/fonts/Montserrat-Bold.ttf | Bin .../generation-icons/icon-light-adaptive-bg.png | Bin .../generation-icons/icon-light-adaptive-fg.png | Bin .../auth}/assets/generation-icons/icon-light.png | Bin .../assets/generation-icons/icon-monochrome.png | Bin .../apps/auth}/assets/icons/auth-icon.ico | Bin .../apps/auth}/assets/icons/auth-icon.png | Bin .../apps/auth}/assets/loading_photos_background.png | Bin .../auth}/assets/loading_photos_background_dark.png | Bin {auth => mobile/apps/auth}/assets/rate_us.png | Bin .../apps/auth}/assets/sheild-front-gradient.png | Bin {auth => mobile/apps/auth}/assets/simple-icons | 0 .../apps/auth}/assets/splash/splash-icon-fg-12.png | Bin .../apps/auth}/assets/splash/splash-icon-fg.png | Bin {auth => mobile/apps/auth}/assets/star_us.png | Bin .../apps/auth}/assets/svg/button-tint.svg | 0 .../apps/auth}/assets/svg/pin-active.svg | 0 {auth => mobile/apps/auth}/assets/svg/pin-card.svg | 0 .../apps/auth}/assets/svg/pin-inactive.svg | 0 .../apps/auth}/assets/wallet-front-gradient.png | Bin {auth => mobile/apps/auth}/coverage/lcov.info | 0 {auth => mobile/apps/auth}/crowdin.yml | 0 {auth => mobile/apps/auth}/devtools_options.yaml | 0 {auth => mobile/apps/auth}/distribute_options.yaml | 0 {auth => mobile/apps/auth}/docs/README.md | 0 {auth => mobile/apps/auth}/docs/adding-icons.md | 0 {auth => mobile/apps/auth}/docs/localization.md | 0 {auth => mobile/apps/auth}/docs/release.md | 0 .../apps/auth}/docs/vscode/extensions.json | 0 {auth => mobile/apps/auth}/docs/vscode/launch.json | 0 .../metadata/android/en-US/changelogs/23.txt | 0 .../metadata/android/en-US/changelogs/39.txt | 0 .../metadata/android/en-US/full_description.txt | 0 .../fastlane/metadata/android/en-US/images/icon.png | Bin .../android/en-US/images/phoneScreenshots/1.png | Bin .../android/en-US/images/phoneScreenshots/2.png | Bin .../android/en-US/images/phoneScreenshots/3.png | Bin .../android/en-US/images/phoneScreenshots/4.png | Bin .../android/en-US/images/phoneScreenshots/5.png | Bin .../metadata/android/en-US/short_description.txt | 0 .../auth}/fastlane/metadata/android/en-US/title.txt | 0 .../apps/auth}/fdroid_flutter_icons.yaml | 0 {auth => mobile/apps/auth}/flutter | 0 {auth => mobile/apps/auth}/fonts/Inter-Bold.ttf | Bin {auth => mobile/apps/auth}/fonts/Inter-Light.ttf | Bin {auth => mobile/apps/auth}/fonts/Inter-Medium.ttf | Bin {auth => mobile/apps/auth}/fonts/Inter-Regular.ttf | Bin {auth => mobile/apps/auth}/fonts/Inter-SemiBold.ttf | Bin .../apps/auth}/fonts/Montserrat-Bold.ttf | Bin {auth => mobile/apps/auth}/ios/.gitignore | 0 .../apps/auth}/ios/Flutter/AppFrameworkInfo.plist | 0 .../apps/auth}/ios/Flutter/Debug.xcconfig | 0 .../apps/auth}/ios/Flutter/Release.xcconfig | 0 {auth => mobile/apps/auth}/ios/Podfile | 0 {auth => mobile/apps/auth}/ios/Podfile.lock | 0 .../apps/auth}/ios/Runner.xcodeproj/project.pbxproj | 0 .../project.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../ios/Runner.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 0 .../apps/auth}/ios/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/Dark mode-1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-1024x1024@1x.png | Bin .../AppIcon.appiconset/Icon-App-50x50@1x.png | Bin .../AppIcon.appiconset/Icon-App-50x50@2x.png | Bin .../AppIcon.appiconset/Icon-App-57x57@1x.png | Bin .../AppIcon.appiconset/Icon-App-57x57@2x.png | Bin .../AppIcon.appiconset/Icon-App-72x72@1x.png | Bin .../AppIcon.appiconset/Icon-App-72x72@2x.png | Bin .../AppIcon.appiconset/ItunesArtwork@2x.png | Bin .../AppIcon.appiconset/Tinted icon-1024@1x.png | Bin .../auth}/ios/Runner/Assets.xcassets/Contents.json | 0 .../LaunchBackground.imageset/Contents.json | 0 .../LaunchBackground.imageset/background.png | Bin .../LaunchBackground.imageset/darkbackground.png | Bin .../LaunchImage.imageset/Contents.json | 0 .../LaunchImage.imageset/LaunchImage.png | Bin .../LaunchImage.imageset/LaunchImage@2x.png | Bin .../LaunchImage.imageset/LaunchImage@3x.png | Bin .../Assets.xcassets/LaunchImage.imageset/README.md | 0 .../ios/Runner/Base.lproj/LaunchScreen.storyboard | 0 .../auth}/ios/Runner/Base.lproj/Main.storyboard | 0 {auth => mobile/apps/auth}/ios/Runner/Info.plist | 0 .../apps/auth}/ios/Runner/Runner-Bridging-Header.h | 0 .../apps/auth}/ios/ci_scripts/ci_post_clone.sh | 0 {auth => mobile/apps/auth}/l10n.yaml | 0 {auth => mobile/apps/auth}/lib/app/app.dart | 0 {auth => mobile/apps/auth}/lib/app/view/app.dart | 0 {auth => mobile/apps/auth}/lib/bootstrap.dart | 0 .../apps/auth}/lib/core/configuration.dart | 0 {auth => mobile/apps/auth}/lib/core/constants.dart | 0 {auth => mobile/apps/auth}/lib/core/errors.dart | 0 {auth => mobile/apps/auth}/lib/core/event_bus.dart | 0 .../apps/auth}/lib/core/logging/super_logging.dart | 0 .../auth}/lib/core/logging/tunneled_transport.dart | 0 {auth => mobile/apps/auth}/lib/core/network.dart | 0 {auth => mobile/apps/auth}/lib/ente_theme_data.dart | 0 .../apps/auth}/lib/events/codes_updated_event.dart | 0 .../auth}/lib/events/endpoint_updated_event.dart | 0 {auth => mobile/apps/auth}/lib/events/event.dart | 0 .../apps/auth}/lib/events/icons_changed_event.dart | 0 .../apps/auth}/lib/events/notification_event.dart | 0 .../apps/auth}/lib/events/signed_in_event.dart | 0 .../apps/auth}/lib/events/signed_out_event.dart | 0 .../apps/auth}/lib/events/trigger_logout_event.dart | 0 .../lib/events/user_details_changed_event.dart | 0 .../apps/auth}/lib/gateway/authenticator.dart | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ar.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_be.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_bg.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ca.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_cs.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_da.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_de.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_el.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_en.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_es.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_et.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_fa.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_fi.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_fr.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_gu.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_he.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_hi.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_hu.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_id.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_it.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ja.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ka.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_km.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ko.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ku.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_lt.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_lv.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ml.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_nl.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_or.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_pl.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_pt.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ro.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ru.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_sk.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_sl.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_sr.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_sv.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ta.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_te.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_th.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_ti.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_tr.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_uk.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_vi.arb | 0 {auth => mobile/apps/auth}/lib/l10n/arb/app_zh.arb | 0 .../apps/auth}/lib/l10n/arb/app_zh_CN.arb | 0 .../apps/auth}/lib/l10n/arb/app_zh_TW.arb | 0 {auth => mobile/apps/auth}/lib/l10n/l10n.dart | 0 {auth => mobile/apps/auth}/lib/locale.dart | 0 {auth => mobile/apps/auth}/lib/main.dart | 0 .../apps/auth}/lib/main_development.dart | 0 {auth => mobile/apps/auth}/lib/main_production.dart | 0 {auth => mobile/apps/auth}/lib/main_staging.dart | 0 .../apps/auth}/lib/models/account/two_factor.dart | 0 .../apps/auth}/lib/models/all_icon_data.dart | 0 .../apps/auth}/lib/models/api/user/srp.dart | 0 .../auth}/lib/models/authenticator/auth_entity.dart | 0 .../auth}/lib/models/authenticator/auth_key.dart | 0 .../lib/models/authenticator/entity_result.dart | 0 .../lib/models/authenticator/local_auth_entity.dart | 0 .../apps/auth}/lib/models/billing_plan.dart | 0 {auth => mobile/apps/auth}/lib/models/code.dart | 0 .../apps/auth}/lib/models/code_display.dart | 0 .../apps/auth}/lib/models/delete_account.dart | 0 .../apps/auth}/lib/models/execution_states.dart | 0 .../apps/auth}/lib/models/export/ente.dart | 0 .../apps/auth}/lib/models/key_attributes.dart | 0 .../apps/auth}/lib/models/key_gen_result.dart | 0 .../auth}/lib/models/private_key_attributes.dart | 0 .../apps/auth}/lib/models/protos/googleauth.pb.dart | 0 .../auth}/lib/models/protos/googleauth.pbenum.dart | 0 .../auth}/lib/models/protos/googleauth.pbjson.dart | 0 .../lib/models/protos/googleauth.pbserver.dart | 0 {auth => mobile/apps/auth}/lib/models/sessions.dart | 0 .../apps/auth}/lib/models/set_keys_request.dart | 0 .../auth}/lib/models/set_recovery_key_request.dart | 0 .../apps/auth}/lib/models/subscription.dart | 0 {auth => mobile/apps/auth}/lib/models/typedefs.dart | 0 .../apps/auth}/lib/models/upload_url.dart | 0 .../apps/auth}/lib/models/user_details.dart | 0 .../apps/auth}/lib/onboarding/model/tag_enums.dart | 0 .../auth}/lib/onboarding/view/common/add_chip.dart | 0 .../auth}/lib/onboarding/view/common/add_tag.dart | 0 .../auth}/lib/onboarding/view/common/edit_tag.dart | 0 .../lib/onboarding/view/common/field_label.dart | 0 .../auth}/lib/onboarding/view/common/tag_chip.dart | 0 .../auth}/lib/onboarding/view/onboarding_page.dart | 0 .../view/setup_enter_secret_key_page.dart | 0 .../auth}/lib/onboarding/view/view_qr_page.dart | 0 .../auth}/lib/services/authenticator_service.dart | 0 .../apps/auth}/lib/services/billing_service.dart | 0 .../auth}/lib/services/deduplication_service.dart | 0 .../lib/services/local_authentication_service.dart | 0 .../auth}/lib/services/notification_service.dart | 0 .../apps/auth}/lib/services/passkey_service.dart | 0 .../apps/auth}/lib/services/preference_service.dart | 0 .../apps/auth}/lib/services/update_service.dart | 0 .../apps/auth}/lib/services/user_service.dart | 0 .../auth}/lib/services/window_listener_service.dart | 0 .../apps/auth}/lib/store/authenticator_db.dart | 0 .../apps/auth}/lib/store/code_display_store.dart | 0 .../apps/auth}/lib/store/code_store.dart | 0 .../auth}/lib/store/offline_authenticator_db.dart | 0 {auth => mobile/apps/auth}/lib/theme/colors.dart | 0 {auth => mobile/apps/auth}/lib/theme/effects.dart | 0 .../apps/auth}/lib/theme/ente_theme.dart | 0 .../apps/auth}/lib/theme/text_style.dart | 0 .../auth}/lib/ui/account/change_email_dialog.dart | 0 .../auth}/lib/ui/account/delete_account_page.dart | 0 .../apps/auth}/lib/ui/account/email_entry_page.dart | 0 .../apps/auth}/lib/ui/account/login_page.dart | 0 .../lib/ui/account/login_pwd_verification_page.dart | 0 .../apps/auth}/lib/ui/account/logout_dialog.dart | 0 .../auth}/lib/ui/account/ott_verification_page.dart | 0 .../auth}/lib/ui/account/password_entry_page.dart | 0 .../auth}/lib/ui/account/password_reentry_page.dart | 0 .../auth}/lib/ui/account/recovery_key_page.dart | 0 .../apps/auth}/lib/ui/account/recovery_page.dart | 0 .../ui/account/request_pwd_verification_page.dart | 0 .../apps/auth}/lib/ui/account/sessions_page.dart | 0 .../auth}/lib/ui/algorithm_selector_widget.dart | 0 .../apps/auth}/lib/ui/code_error_widget.dart | 0 .../apps/auth}/lib/ui/code_timer_progress.dart | 0 {auth => mobile/apps/auth}/lib/ui/code_widget.dart | 0 .../apps/auth}/lib/ui/common/dialogs.dart | 0 .../auth}/lib/ui/common/divider_with_padding.dart | 0 .../apps/auth}/lib/ui/common/dynamic_fab.dart | 0 .../apps/auth}/lib/ui/common/gradient_button.dart | 0 .../apps/auth}/lib/ui/common/loading_widget.dart | 0 .../apps/auth}/lib/ui/common/progress_dialog.dart | 0 .../apps/auth}/lib/ui/common/report_bug.dart | 0 .../apps/auth}/lib/ui/common/web_page.dart | 0 .../lib/ui/components/action_sheet_widget.dart | 0 .../auth}/lib/ui/components/actions_bar_widget.dart | 0 .../apps/auth}/lib/ui/components/banner_widget.dart | 0 .../lib/ui/components/bottom_action_bar_widget.dart | 0 .../lib/ui/components/buttons/button_widget.dart | 0 .../ui/components/buttons/icon_button_widget.dart | 0 .../lib/ui/components/captioned_text_widget.dart | 0 .../components/code_selection_actions_widget.dart | 0 .../lib/ui/components/components_constants.dart | 0 .../auth}/lib/ui/components/custom_icon_widget.dart | 0 .../apps/auth}/lib/ui/components/dialog_widget.dart | 0 .../auth}/lib/ui/components/divider_widget.dart | 0 .../ui/components/expandable_menu_item_widget.dart | 0 .../lib/ui/components/menu_item_child_widgets.dart | 0 .../auth}/lib/ui/components/menu_item_widget.dart | 0 .../components/menu_section_description_widget.dart | 0 .../lib/ui/components/models/button_result.dart | 0 .../auth}/lib/ui/components/models/button_type.dart | 0 .../ui/components/models/custom_button_style.dart | 0 .../ui/components/notification_warning_widget.dart | 0 .../lib/ui/components/selection_action_button.dart | 0 .../apps/auth}/lib/ui/components/separators.dart | 0 .../auth}/lib/ui/components/text_input_widget.dart | 0 .../lib/ui/components/title_bar_title_widget.dart | 0 .../auth}/lib/ui/components/title_bar_widget.dart | 0 .../lib/ui/components/toggle_switch_widget.dart | 0 .../apps/auth}/lib/ui/custom_icon_page.dart | 0 .../apps/auth}/lib/ui/home/coach_mark_widget.dart | 0 .../apps/auth}/lib/ui/home/home_empty_state.dart | 0 .../auth}/lib/ui/home/speed_dial_label_widget.dart | 0 {auth => mobile/apps/auth}/lib/ui/home_page.dart | 0 .../apps/auth}/lib/ui/lifecycle_event_handler.dart | 0 .../apps/auth}/lib/ui/linear_progress_widget.dart | 0 {auth => mobile/apps/auth}/lib/ui/passkey_page.dart | 0 .../apps/auth}/lib/ui/reorder_codes_page.dart | 0 .../apps/auth}/lib/ui/scanner_gauth_page.dart | 0 {auth => mobile/apps/auth}/lib/ui/scanner_page.dart | 0 .../auth}/lib/ui/settings/about_section_widget.dart | 0 .../lib/ui/settings/account_section_widget.dart | 0 .../auth}/lib/ui/settings/app_update_dialog.dart | 0 .../auth}/lib/ui/settings/app_version_widget.dart | 0 .../apps/auth}/lib/ui/settings/common_settings.dart | 0 .../lib/ui/settings/data/data_section_widget.dart | 0 .../lib/ui/settings/data/duplicate_code_page.dart | 0 .../auth}/lib/ui/settings/data/export_widget.dart | 0 .../auth}/lib/ui/settings/data/html_export.dart | 0 .../lib/ui/settings/data/import/aegis_import.dart | 0 .../ui/settings/data/import/bitwarden_import.dart | 0 .../settings/data/import/encrypted_ente_import.dart | 0 .../ui/settings/data/import/google_auth_import.dart | 0 .../lib/ui/settings/data/import/import_service.dart | 0 .../lib/ui/settings/data/import/import_success.dart | 0 .../ui/settings/data/import/lastpass_import.dart | 0 .../ui/settings/data/import/plain_text_import.dart | 0 .../data/import/raivo_plain_text_import.dart | 0 .../lib/ui/settings/data/import/two_fas_import.dart | 0 .../auth}/lib/ui/settings/data/import_page.dart | 0 .../lib/ui/settings/developer_settings_page.dart | 0 .../lib/ui/settings/developer_settings_widget.dart | 0 .../lib/ui/settings/general_section_widget.dart | 0 .../apps/auth}/lib/ui/settings/language_picker.dart | 0 .../ui/settings/lock_screen/custom_pin_keypad.dart | 0 .../settings/lock_screen/lock_screen_auto_lock.dart | 0 .../lock_screen/lock_screen_confirm_password.dart | 0 .../lock_screen/lock_screen_confirm_pin.dart | 0 .../settings/lock_screen/lock_screen_options.dart | 0 .../settings/lock_screen/lock_screen_password.dart | 0 .../ui/settings/lock_screen/lock_screen_pin.dart | 0 .../lib/ui/settings/notification_banner_widget.dart | 0 .../lib/ui/settings/security_section_widget.dart | 0 .../lib/ui/settings/settings_section_title.dart | 0 .../lib/ui/settings/social_section_widget.dart | 0 .../lib/ui/settings/support_section_widget.dart | 0 .../auth}/lib/ui/settings/theme_switch_widget.dart | 0 .../auth}/lib/ui/settings/title_bar_widget.dart | 0 .../apps/auth}/lib/ui/settings_page.dart | 0 .../apps/auth}/lib/ui/share/code_share.dart | 0 .../apps/auth}/lib/ui/sort_option_menu.dart | 0 .../apps/auth}/lib/ui/tools/app_lock.dart | 0 .../auth}/lib/ui/tools/debug/log_file_viewer.dart | 0 .../auth}/lib/ui/tools/debug/raw_codes_viewer.dart | 0 .../apps/auth}/lib/ui/tools/lock_screen.dart | 0 .../apps/auth}/lib/ui/topt_selector_widget.dart | 0 .../lib/ui/two_factor_authentication_page.dart | 0 .../apps/auth}/lib/ui/two_factor_recovery_page.dart | 0 .../apps/auth}/lib/ui/utils/icon_utils.dart | 0 {auth => mobile/apps/auth}/lib/utils/auth_util.dart | 0 .../apps/auth}/lib/utils/date_time_util.dart | 0 {auth => mobile/apps/auth}/lib/utils/debouncer.dart | 0 .../apps/auth}/lib/utils/dialog_util.dart | 0 .../apps/auth}/lib/utils/directory_utils.dart | 0 .../apps/auth}/lib/utils/email_util.dart | 0 .../apps/auth}/lib/utils/lock_screen_settings.dart | 0 .../apps/auth}/lib/utils/navigation_util.dart | 0 .../apps/auth}/lib/utils/package_info_util.dart | 0 .../apps/auth}/lib/utils/platform_util.dart | 0 .../apps/auth}/lib/utils/share_utils.dart | 0 .../apps/auth}/lib/utils/toast_util.dart | 0 {auth => mobile/apps/auth}/lib/utils/totp_util.dart | 0 .../auth}/lib/utils/window_protocol_handler.dart | 0 {auth => mobile/apps/auth}/linux/.gitignore | 0 {auth => mobile/apps/auth}/linux/CMakeLists.txt | 0 .../apps/auth}/linux/flutter/CMakeLists.txt | 0 .../linux/flutter/generated_plugin_registrant.cc | 0 .../linux/flutter/generated_plugin_registrant.h | 0 .../auth}/linux/flutter/generated_plugins.cmake | 0 {auth => mobile/apps/auth}/linux/main.cc | 0 {auth => mobile/apps/auth}/linux/my_application.cc | 0 {auth => mobile/apps/auth}/linux/my_application.h | 0 .../auth}/linux/packaging/appimage/make_config.yaml | 0 .../apps/auth}/linux/packaging/deb/make_config.yaml | 0 .../apps/auth}/linux/packaging/enteauth.appdata.xml | 0 .../auth}/linux/packaging/pacman/make_config.yaml | 0 .../apps/auth}/linux/packaging/rpm/make_config.yaml | 0 {auth => mobile/apps/auth}/macos/.gitignore | 0 .../apps/auth}/macos/Flutter/Flutter-Debug.xcconfig | 0 .../auth}/macos/Flutter/Flutter-Release.xcconfig | 0 .../macos/Flutter/GeneratedPluginRegistrant.swift | 0 {auth => mobile/apps/auth}/macos/Podfile | 0 {auth => mobile/apps/auth}/macos/Podfile.lock | 0 .../auth}/macos/Runner.xcodeproj/project.pbxproj | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/xcschemes/Runner.xcscheme | 0 .../Runner.xcworkspace/contents.xcworkspacedata | 0 .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../apps/auth}/macos/Runner/AppDelegate.swift | 0 .../Assets.xcassets/AppIcon.appiconset/1024-mac.png | Bin .../Assets.xcassets/AppIcon.appiconset/128-mac.png | Bin .../Assets.xcassets/AppIcon.appiconset/16-mac.png | Bin .../Assets.xcassets/AppIcon.appiconset/256-mac.png | Bin .../Assets.xcassets/AppIcon.appiconset/32-mac.png | Bin .../Assets.xcassets/AppIcon.appiconset/512-mac.png | Bin .../Assets.xcassets/AppIcon.appiconset/64-mac.png | Bin .../AppIcon.appiconset/Contents.json | 0 .../apps/auth}/macos/Runner/Base.lproj/MainMenu.xib | 0 .../auth}/macos/Runner/Configs/AppInfo.xcconfig | 0 .../apps/auth}/macos/Runner/Configs/Debug.xcconfig | 0 .../auth}/macos/Runner/Configs/Release.xcconfig | 0 .../auth}/macos/Runner/Configs/Warnings.xcconfig | 0 .../auth}/macos/Runner/DebugProfile.entitlements | 0 {auth => mobile/apps/auth}/macos/Runner/Info.plist | 0 .../apps/auth}/macos/Runner/MainFlutterWindow.swift | 0 .../apps/auth}/macos/Runner/Release.entitlements | 0 .../apps/auth}/macos/build/.last_build_id | 0 .../apps/auth}/macos/packaging/dmg/make_config.yaml | 0 .../apps/auth}/migration-guides/README.md | 0 .../apps/auth}/migration-guides/authy.md | 0 .../apps/auth}/migration-guides/encrypted_export.md | 0 {auth => mobile/apps/auth}/protos/googleauth.proto | 0 {auth => mobile/apps/auth}/pubspec.lock | 0 {auth => mobile/apps/auth}/pubspec.yaml | 0 .../apps/auth}/screenshots/screenshots.png | Bin {auth => mobile/apps/auth}/scripts/release_tag.sh | 0 .../apps/auth}/test/helpers/helpers.dart | 0 .../apps/auth}/test/helpers/pump_app.dart | 0 .../apps/auth}/test/models/code_test.dart | 0 {auth => mobile/apps/auth}/web/favicon.png | Bin {auth => mobile/apps/auth}/web/icons/Icon-192.png | Bin {auth => mobile/apps/auth}/web/icons/Icon-512.png | Bin {auth => mobile/apps/auth}/web/icons/favicon.png | Bin {auth => mobile/apps/auth}/web/index.html | 0 {auth => mobile/apps/auth}/web/manifest.json | 0 .../apps/auth}/web/splash/img/dark-1x.png | Bin .../apps/auth}/web/splash/img/dark-2x.png | Bin .../apps/auth}/web/splash/img/dark-3x.png | Bin .../apps/auth}/web/splash/img/dark-4x.png | Bin .../apps/auth}/web/splash/img/light-1x.png | Bin .../apps/auth}/web/splash/img/light-2x.png | Bin .../apps/auth}/web/splash/img/light-3x.png | Bin .../apps/auth}/web/splash/img/light-4x.png | Bin {auth => mobile/apps/auth}/web/splash/splash.js | 0 {auth => mobile/apps/auth}/web/splash/style.css | 0 {auth => mobile/apps/auth}/windows/.gitignore | 0 {auth => mobile/apps/auth}/windows/CMakeLists.txt | 0 .../apps/auth}/windows/flutter/CMakeLists.txt | 0 .../windows/flutter/generated_plugin_registrant.cc | 0 .../windows/flutter/generated_plugin_registrant.h | 0 .../auth}/windows/flutter/generated_plugins.cmake | 0 .../apps/auth}/windows/packaging/exe/inno_setup.iss | 0 .../auth}/windows/packaging/exe/make_config.yaml | 0 .../apps/auth}/windows/runner/CMakeLists.txt | 0 {auth => mobile/apps/auth}/windows/runner/Runner.rc | 0 .../apps/auth}/windows/runner/flutter_window.cpp | 0 .../apps/auth}/windows/runner/flutter_window.h | 0 {auth => mobile/apps/auth}/windows/runner/main.cpp | 0 .../apps/auth}/windows/runner/resource.h | 0 .../auth}/windows/runner/resources/app_icon.ico | Bin .../apps/auth}/windows/runner/runner.exe.manifest | 0 {auth => mobile/apps/auth}/windows/runner/utils.cpp | 0 {auth => mobile/apps/auth}/windows/runner/utils.h | 0 .../apps/auth}/windows/runner/win32_window.cpp | 0 .../apps/auth}/windows/runner/win32_window.h | 0 893 files changed, 2 insertions(+), 2 deletions(-) rename {auth => mobile/apps/auth}/.fvmrc (100%) rename {auth => mobile/apps/auth}/.gitignore (100%) rename {auth => mobile/apps/auth}/.metadata (100%) rename {auth => mobile/apps/auth}/README.md (100%) rename {auth => mobile/apps/auth}/analysis_options.yaml (100%) rename {auth => mobile/apps/auth}/android/.gitignore (100%) rename {auth => mobile/apps/auth}/android/app/build.gradle (100%) rename {auth => mobile/apps/auth}/android/app/src/debug/AndroidManifest.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/development/ic_launcher-playstore.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/drawable/ic_launcher_foreground.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-hdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-mdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/development/res/values/ic_launcher_background.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/AndroidManifest.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/kotlin/io/ente/authenticator/MainActivity.kt (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/full-description.txt (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/graphics/icon/icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/short-description.txt (100%) rename {auth => mobile/apps/auth}/android/app/src/main/play/listings/en-US/title.txt (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-hdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-hdpi/splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-mdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-mdpi/splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-hdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-mdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-v21/background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-v21/launch_background.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-xhdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night/background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-night/launch_background.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-v21/background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-v21/launch_background.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xhdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xhdpi/splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxhdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxhdpi/splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxxhdpi/android12splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable-xxxhdpi/splash.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable/background.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable/launch_background.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/drawable/notification_icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/mipmap-hdpi/launcher_icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/mipmap-mdpi/launcher_icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/values-night-v31/styles.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/values-night/styles.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/values-v31/styles.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/values/colors.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/main/res/values/styles.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/profile/AndroidManifest.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/ic_launcher-playstore.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/drawable/ic_launcher_foreground.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher_round.xml (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-hdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-mdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-xhdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher_round.png (100%) rename {auth => mobile/apps/auth}/android/app/src/staging/res/values/ic_launcher_background.xml (100%) rename {auth => mobile/apps/auth}/android/build.gradle (100%) rename {auth => mobile/apps/auth}/android/gradle.properties (100%) rename {auth => mobile/apps/auth}/android/gradle/wrapper/gradle-wrapper.properties (100%) rename {auth => mobile/apps/auth}/android/settings.gradle (100%) rename {auth => mobile/apps/auth}/architecture/README.md (100%) rename {auth => mobile/apps/auth}/architecture/assets/authentication.svg (100%) rename {auth => mobile/apps/auth}/architecture/assets/e2ee.svg (100%) rename {auth => mobile/apps/auth}/architecture/assets/key-derivation.svg (100%) rename {auth => mobile/apps/auth}/architecture/assets/recovery.svg (100%) rename {auth => mobile/apps/auth}/architecture/assets/token-encryption.svg (100%) rename {auth => mobile/apps/auth}/assets/2.0x/broken_heart.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/calender_banner_dark.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/calender_banner_light.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/discount.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/ente_5gb.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/loading_photos_background.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/loading_photos_background_dark.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/rate_us.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/sheild-front-gradient.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/star_us.png (100%) rename {auth => mobile/apps/auth}/assets/2.0x/wallet-front-gradient.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/broken_heart.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/calender_banner_dark.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/calender_banner_light.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/discount.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/ente_5gb.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/loading_photos_background.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/loading_photos_background_dark.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/rate_us.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/sheild-front-gradient.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/star_us.png (100%) rename {auth => mobile/apps/auth}/assets/3.0x/wallet-front-gradient.png (100%) rename {auth => mobile/apps/auth}/assets/broken_heart.png (100%) rename {auth => mobile/apps/auth}/assets/build/.last_build_id (100%) rename {auth => mobile/apps/auth}/assets/calender_banner_dark.png (100%) rename {auth => mobile/apps/auth}/assets/calender_banner_light.png (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/_data/custom-icons.json (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/1x_bet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/23andme.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/3commas.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/addy_io.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/airtable.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/airtm.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/aj_bell.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/aliyun.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/amazon.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ankama.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ankara_university.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/anycoindirect.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ar24.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/aruba.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ascendex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/aternos.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/authentik.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/azurhosts.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/azurware.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/badlion.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/baidu_cloud.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/band.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/battlenet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bbs_nga.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/belo.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bethesda.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/binance_exchange.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/binance_tr.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/binance_us.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bingx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitazza.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitfinex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitget.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitget_wallet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitkub.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitmart.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitmex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitoasis.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitskins.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitstamp.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitvavo.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bitwarden.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/blockchain.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bloom_host.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/blue_sky.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bonify.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/booking.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/borg_base.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/brave_creators.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bugzilla.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bundesagentur_fur_arbeit.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/butterflymx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/bybit.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/caixa.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/canada_flag.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/canva.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/capacities.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/carta.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cern.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/changenow.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cih.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cloudamqp.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cloudflare.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cloudns.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/coinbase.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/coindcx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/coinspot.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/configcat.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/controld.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cronometer.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/crowdpear.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cryptee.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/crypto.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/csam.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/csfloat.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/csgoroll.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cssbuy.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/cwallet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/dcs.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/degiro.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/deloitte.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/deriv.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/digifinex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/directadmin.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/discourse.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/dmarket.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/docuseal.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/doppler.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/dreamhost_panel.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/dropbox.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/dusnet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ebay.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ecitizen_kenya.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ecloud.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/eneba.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/enom.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ente.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/epic_games.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/esketit.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/estateguru.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/eve_online.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/fanatical.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/fastmail.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/federal_student_aid.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/fidelity.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/filen.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/finanzfluss.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/finary.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/fortrabbit.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/forusall.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/freetaxusa.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/fzj.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/g2a.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gateio.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gerid.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/github.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gitlab.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gmx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gommehd.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/google.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gosuslugi.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gov_uk.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/guideline.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/gusto.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/habbo.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/healthchecks.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/hivelocity.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/htx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/huggingface.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ibkr.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ice_drive.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/iconomi.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/id_me.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/immo_scout_24.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/infomaniak.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ing.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/instagram.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/instant_gaming.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/inwx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/itch_io.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ivpn.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/jagex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/jianguoyun.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kagi.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/keygen.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kick.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kite.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/knownhost.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ko_fi.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/koofr.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kotas.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kpn.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kraken.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kronos.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/kucoin.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/labymod.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/laposte.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/lark.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/launchdarkly.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/letterboxd.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/linkedin.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/linux_do.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/local_wp.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/login_gov.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/luma.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/marketplacedottf.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mastodon.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/matlab.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mbin.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/memed.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mercado_libre.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mexc.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/microsoft.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/microsoft365.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/migros.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mintos.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mistral.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/mozilla.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/myfritz.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/name_com.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nekohosting.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nekohosting_gp.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nelnet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/netease_mail.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/newgrounds.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/newton.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nextcloud.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nextdns.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ngrok.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nintendo.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/njalla.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/noip.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nordaccount.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/notesnook.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/notion.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nucommunity.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/nvidia.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/odido.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/okx.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/open_observe.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/oracle_cloud.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/parqet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/parsec.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/patient_access.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/paypal.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/pbtech.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/pcloud.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/pebble_host.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/peerberry.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/pingvinshare.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/pionex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/plutus.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/poloniex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/porkbun.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/postmarkapp.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/postnl.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/postscanmail.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/prey_project.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/privacy.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/privacyguides.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/proton.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/proxmox.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/pushover.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/qiniuyun.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/r10.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/raindrop_io.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/randstad.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/real_debrid.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/realme.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/realvnc.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/redotpay.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/registro_br.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/remarkable.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/render.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/restream.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/revolt.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ripplematch.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/rockstar_games.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/runemate.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/runescape_wiki.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/rust_language_forum.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/samsung.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/seafile.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/sei.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/sendgrid.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/service-bw.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/shakepay.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/simplelogin.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/simplicity.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/sipgate.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/skiff.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/skinport.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/sms_pool_net.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/smtp2go.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/snapchat.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/spacehey.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/standardnotes.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/starbreeze.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/strato.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/surfshark.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/synology_dsm.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/t-mobile.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tcpshield.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tebex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/techlore.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/teleport.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tencent_cloud.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/terabit.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/termius.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tianyiyun.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tiktok.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/titan.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/torguard.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/toshl_finance.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/trading212.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tradingview.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/transip.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tresorit.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/troweprice.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/tweakers.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/twingate.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/twitch.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ubiquiti.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ubisoft.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ubuntu_one.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/unity.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/uollet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/uphold.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/upstox.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/us_mobile.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/vikunja.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/volcengine.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wargamingnet.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/warner_bros.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wca.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wealthfront.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wealthsimple.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/web_de.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/whmcs.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/windscribe.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wise.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wolvesville.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/workflowy.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/workos.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/wyze.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/xai.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/xbox.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/yahoo.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/yandex.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/ynab.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/zitadel.svg (100%) rename {auth => mobile/apps/auth}/assets/custom-icons/icons/zoom.svg (100%) rename {auth => mobile/apps/auth}/assets/discount.png (100%) rename {auth => mobile/apps/auth}/assets/ente_5gb.png (100%) rename {auth => mobile/apps/auth}/assets/fonts/Inter-Bold.ttf (100%) rename {auth => mobile/apps/auth}/assets/fonts/Inter-Light.ttf (100%) rename {auth => mobile/apps/auth}/assets/fonts/Inter-Medium.ttf (100%) rename {auth => mobile/apps/auth}/assets/fonts/Inter-Regular.ttf (100%) rename {auth => mobile/apps/auth}/assets/fonts/Inter-SemiBold.ttf (100%) rename {auth => mobile/apps/auth}/assets/fonts/Montserrat-Bold.ttf (100%) rename {auth => mobile/apps/auth}/assets/generation-icons/icon-light-adaptive-bg.png (100%) rename {auth => mobile/apps/auth}/assets/generation-icons/icon-light-adaptive-fg.png (100%) rename {auth => mobile/apps/auth}/assets/generation-icons/icon-light.png (100%) rename {auth => mobile/apps/auth}/assets/generation-icons/icon-monochrome.png (100%) rename {auth => mobile/apps/auth}/assets/icons/auth-icon.ico (100%) rename {auth => mobile/apps/auth}/assets/icons/auth-icon.png (100%) rename {auth => mobile/apps/auth}/assets/loading_photos_background.png (100%) rename {auth => mobile/apps/auth}/assets/loading_photos_background_dark.png (100%) rename {auth => mobile/apps/auth}/assets/rate_us.png (100%) rename {auth => mobile/apps/auth}/assets/sheild-front-gradient.png (100%) rename {auth => mobile/apps/auth}/assets/simple-icons (100%) rename {auth => mobile/apps/auth}/assets/splash/splash-icon-fg-12.png (100%) rename {auth => mobile/apps/auth}/assets/splash/splash-icon-fg.png (100%) rename {auth => mobile/apps/auth}/assets/star_us.png (100%) rename {auth => mobile/apps/auth}/assets/svg/button-tint.svg (100%) rename {auth => mobile/apps/auth}/assets/svg/pin-active.svg (100%) rename {auth => mobile/apps/auth}/assets/svg/pin-card.svg (100%) rename {auth => mobile/apps/auth}/assets/svg/pin-inactive.svg (100%) rename {auth => mobile/apps/auth}/assets/wallet-front-gradient.png (100%) rename {auth => mobile/apps/auth}/coverage/lcov.info (100%) rename {auth => mobile/apps/auth}/crowdin.yml (100%) rename {auth => mobile/apps/auth}/devtools_options.yaml (100%) rename {auth => mobile/apps/auth}/distribute_options.yaml (100%) rename {auth => mobile/apps/auth}/docs/README.md (100%) rename {auth => mobile/apps/auth}/docs/adding-icons.md (100%) rename {auth => mobile/apps/auth}/docs/localization.md (100%) rename {auth => mobile/apps/auth}/docs/release.md (100%) rename {auth => mobile/apps/auth}/docs/vscode/extensions.json (100%) rename {auth => mobile/apps/auth}/docs/vscode/launch.json (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/changelogs/23.txt (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/changelogs/39.txt (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/full_description.txt (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/images/icon.png (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/short_description.txt (100%) rename {auth => mobile/apps/auth}/fastlane/metadata/android/en-US/title.txt (100%) rename {auth => mobile/apps/auth}/fdroid_flutter_icons.yaml (100%) rename {auth => mobile/apps/auth}/flutter (100%) rename {auth => mobile/apps/auth}/fonts/Inter-Bold.ttf (100%) rename {auth => mobile/apps/auth}/fonts/Inter-Light.ttf (100%) rename {auth => mobile/apps/auth}/fonts/Inter-Medium.ttf (100%) rename {auth => mobile/apps/auth}/fonts/Inter-Regular.ttf (100%) rename {auth => mobile/apps/auth}/fonts/Inter-SemiBold.ttf (100%) rename {auth => mobile/apps/auth}/fonts/Montserrat-Bold.ttf (100%) rename {auth => mobile/apps/auth}/ios/.gitignore (100%) rename {auth => mobile/apps/auth}/ios/Flutter/AppFrameworkInfo.plist (100%) rename {auth => mobile/apps/auth}/ios/Flutter/Debug.xcconfig (100%) rename {auth => mobile/apps/auth}/ios/Flutter/Release.xcconfig (100%) rename {auth => mobile/apps/auth}/ios/Podfile (100%) rename {auth => mobile/apps/auth}/ios/Podfile.lock (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcodeproj/project.pbxproj (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {auth => mobile/apps/auth}/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings (100%) rename {auth => mobile/apps/auth}/ios/Runner/AppDelegate.swift (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Dark mode-1024@1x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/AppIcon.appiconset/Tinted icon-1024@1x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/Contents.json (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png (100%) rename {auth => mobile/apps/auth}/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md (100%) rename {auth => mobile/apps/auth}/ios/Runner/Base.lproj/LaunchScreen.storyboard (100%) rename {auth => mobile/apps/auth}/ios/Runner/Base.lproj/Main.storyboard (100%) rename {auth => mobile/apps/auth}/ios/Runner/Info.plist (100%) rename {auth => mobile/apps/auth}/ios/Runner/Runner-Bridging-Header.h (100%) rename {auth => mobile/apps/auth}/ios/ci_scripts/ci_post_clone.sh (100%) rename {auth => mobile/apps/auth}/l10n.yaml (100%) rename {auth => mobile/apps/auth}/lib/app/app.dart (100%) rename {auth => mobile/apps/auth}/lib/app/view/app.dart (100%) rename {auth => mobile/apps/auth}/lib/bootstrap.dart (100%) rename {auth => mobile/apps/auth}/lib/core/configuration.dart (100%) rename {auth => mobile/apps/auth}/lib/core/constants.dart (100%) rename {auth => mobile/apps/auth}/lib/core/errors.dart (100%) rename {auth => mobile/apps/auth}/lib/core/event_bus.dart (100%) rename {auth => mobile/apps/auth}/lib/core/logging/super_logging.dart (100%) rename {auth => mobile/apps/auth}/lib/core/logging/tunneled_transport.dart (100%) rename {auth => mobile/apps/auth}/lib/core/network.dart (100%) rename {auth => mobile/apps/auth}/lib/ente_theme_data.dart (100%) rename {auth => mobile/apps/auth}/lib/events/codes_updated_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/endpoint_updated_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/icons_changed_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/notification_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/signed_in_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/signed_out_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/trigger_logout_event.dart (100%) rename {auth => mobile/apps/auth}/lib/events/user_details_changed_event.dart (100%) rename {auth => mobile/apps/auth}/lib/gateway/authenticator.dart (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ar.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_be.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_bg.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ca.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_cs.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_da.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_de.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_el.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_en.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_es.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_et.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_fa.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_fi.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_fr.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_gu.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_he.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_hi.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_hu.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_id.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_it.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ja.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ka.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_km.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ko.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ku.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_lt.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_lv.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ml.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_nl.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_or.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_pl.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_pt.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ro.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ru.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_sk.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_sl.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_sr.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_sv.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ta.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_te.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_th.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_ti.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_tr.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_uk.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_vi.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_zh.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_zh_CN.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/arb/app_zh_TW.arb (100%) rename {auth => mobile/apps/auth}/lib/l10n/l10n.dart (100%) rename {auth => mobile/apps/auth}/lib/locale.dart (100%) rename {auth => mobile/apps/auth}/lib/main.dart (100%) rename {auth => mobile/apps/auth}/lib/main_development.dart (100%) rename {auth => mobile/apps/auth}/lib/main_production.dart (100%) rename {auth => mobile/apps/auth}/lib/main_staging.dart (100%) rename {auth => mobile/apps/auth}/lib/models/account/two_factor.dart (100%) rename {auth => mobile/apps/auth}/lib/models/all_icon_data.dart (100%) rename {auth => mobile/apps/auth}/lib/models/api/user/srp.dart (100%) rename {auth => mobile/apps/auth}/lib/models/authenticator/auth_entity.dart (100%) rename {auth => mobile/apps/auth}/lib/models/authenticator/auth_key.dart (100%) rename {auth => mobile/apps/auth}/lib/models/authenticator/entity_result.dart (100%) rename {auth => mobile/apps/auth}/lib/models/authenticator/local_auth_entity.dart (100%) rename {auth => mobile/apps/auth}/lib/models/billing_plan.dart (100%) rename {auth => mobile/apps/auth}/lib/models/code.dart (100%) rename {auth => mobile/apps/auth}/lib/models/code_display.dart (100%) rename {auth => mobile/apps/auth}/lib/models/delete_account.dart (100%) rename {auth => mobile/apps/auth}/lib/models/execution_states.dart (100%) rename {auth => mobile/apps/auth}/lib/models/export/ente.dart (100%) rename {auth => mobile/apps/auth}/lib/models/key_attributes.dart (100%) rename {auth => mobile/apps/auth}/lib/models/key_gen_result.dart (100%) rename {auth => mobile/apps/auth}/lib/models/private_key_attributes.dart (100%) rename {auth => mobile/apps/auth}/lib/models/protos/googleauth.pb.dart (100%) rename {auth => mobile/apps/auth}/lib/models/protos/googleauth.pbenum.dart (100%) rename {auth => mobile/apps/auth}/lib/models/protos/googleauth.pbjson.dart (100%) rename {auth => mobile/apps/auth}/lib/models/protos/googleauth.pbserver.dart (100%) rename {auth => mobile/apps/auth}/lib/models/sessions.dart (100%) rename {auth => mobile/apps/auth}/lib/models/set_keys_request.dart (100%) rename {auth => mobile/apps/auth}/lib/models/set_recovery_key_request.dart (100%) rename {auth => mobile/apps/auth}/lib/models/subscription.dart (100%) rename {auth => mobile/apps/auth}/lib/models/typedefs.dart (100%) rename {auth => mobile/apps/auth}/lib/models/upload_url.dart (100%) rename {auth => mobile/apps/auth}/lib/models/user_details.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/model/tag_enums.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/common/add_chip.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/common/add_tag.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/common/edit_tag.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/common/field_label.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/common/tag_chip.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/onboarding_page.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/setup_enter_secret_key_page.dart (100%) rename {auth => mobile/apps/auth}/lib/onboarding/view/view_qr_page.dart (100%) rename {auth => mobile/apps/auth}/lib/services/authenticator_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/billing_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/deduplication_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/local_authentication_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/notification_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/passkey_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/preference_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/update_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/user_service.dart (100%) rename {auth => mobile/apps/auth}/lib/services/window_listener_service.dart (100%) rename {auth => mobile/apps/auth}/lib/store/authenticator_db.dart (100%) rename {auth => mobile/apps/auth}/lib/store/code_display_store.dart (100%) rename {auth => mobile/apps/auth}/lib/store/code_store.dart (100%) rename {auth => mobile/apps/auth}/lib/store/offline_authenticator_db.dart (100%) rename {auth => mobile/apps/auth}/lib/theme/colors.dart (100%) rename {auth => mobile/apps/auth}/lib/theme/effects.dart (100%) rename {auth => mobile/apps/auth}/lib/theme/ente_theme.dart (100%) rename {auth => mobile/apps/auth}/lib/theme/text_style.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/change_email_dialog.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/delete_account_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/email_entry_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/login_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/login_pwd_verification_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/logout_dialog.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/ott_verification_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/password_entry_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/password_reentry_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/recovery_key_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/recovery_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/request_pwd_verification_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/account/sessions_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/algorithm_selector_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/code_error_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/code_timer_progress.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/code_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/dialogs.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/divider_with_padding.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/dynamic_fab.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/gradient_button.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/loading_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/progress_dialog.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/report_bug.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/common/web_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/action_sheet_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/actions_bar_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/banner_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/bottom_action_bar_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/buttons/button_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/buttons/icon_button_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/captioned_text_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/code_selection_actions_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/components_constants.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/custom_icon_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/dialog_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/divider_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/expandable_menu_item_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/menu_item_child_widgets.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/menu_item_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/menu_section_description_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/models/button_result.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/models/button_type.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/models/custom_button_style.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/notification_warning_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/selection_action_button.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/separators.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/text_input_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/title_bar_title_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/title_bar_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/components/toggle_switch_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/custom_icon_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/home/coach_mark_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/home/home_empty_state.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/home/speed_dial_label_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/home_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/lifecycle_event_handler.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/linear_progress_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/passkey_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/reorder_codes_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/scanner_gauth_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/scanner_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/about_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/account_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/app_update_dialog.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/app_version_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/common_settings.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/data_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/duplicate_code_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/export_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/html_export.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/aegis_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/bitwarden_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/encrypted_ente_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/google_auth_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/import_service.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/import_success.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/lastpass_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/plain_text_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/raivo_plain_text_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import/two_fas_import.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/data/import_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/developer_settings_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/developer_settings_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/general_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/language_picker.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/custom_pin_keypad.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/lock_screen_options.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/lock_screen_password.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/lock_screen/lock_screen_pin.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/notification_banner_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/security_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/settings_section_title.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/social_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/support_section_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/theme_switch_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings/title_bar_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/settings_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/share/code_share.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/sort_option_menu.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/tools/app_lock.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/tools/debug/log_file_viewer.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/tools/debug/raw_codes_viewer.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/tools/lock_screen.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/topt_selector_widget.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/two_factor_authentication_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/two_factor_recovery_page.dart (100%) rename {auth => mobile/apps/auth}/lib/ui/utils/icon_utils.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/auth_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/date_time_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/debouncer.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/dialog_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/directory_utils.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/email_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/lock_screen_settings.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/navigation_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/package_info_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/platform_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/share_utils.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/toast_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/totp_util.dart (100%) rename {auth => mobile/apps/auth}/lib/utils/window_protocol_handler.dart (100%) rename {auth => mobile/apps/auth}/linux/.gitignore (100%) rename {auth => mobile/apps/auth}/linux/CMakeLists.txt (100%) rename {auth => mobile/apps/auth}/linux/flutter/CMakeLists.txt (100%) rename {auth => mobile/apps/auth}/linux/flutter/generated_plugin_registrant.cc (100%) rename {auth => mobile/apps/auth}/linux/flutter/generated_plugin_registrant.h (100%) rename {auth => mobile/apps/auth}/linux/flutter/generated_plugins.cmake (100%) rename {auth => mobile/apps/auth}/linux/main.cc (100%) rename {auth => mobile/apps/auth}/linux/my_application.cc (100%) rename {auth => mobile/apps/auth}/linux/my_application.h (100%) rename {auth => mobile/apps/auth}/linux/packaging/appimage/make_config.yaml (100%) rename {auth => mobile/apps/auth}/linux/packaging/deb/make_config.yaml (100%) rename {auth => mobile/apps/auth}/linux/packaging/enteauth.appdata.xml (100%) rename {auth => mobile/apps/auth}/linux/packaging/pacman/make_config.yaml (100%) rename {auth => mobile/apps/auth}/linux/packaging/rpm/make_config.yaml (100%) rename {auth => mobile/apps/auth}/macos/.gitignore (100%) rename {auth => mobile/apps/auth}/macos/Flutter/Flutter-Debug.xcconfig (100%) rename {auth => mobile/apps/auth}/macos/Flutter/Flutter-Release.xcconfig (100%) rename {auth => mobile/apps/auth}/macos/Flutter/GeneratedPluginRegistrant.swift (100%) rename {auth => mobile/apps/auth}/macos/Podfile (100%) rename {auth => mobile/apps/auth}/macos/Podfile.lock (100%) rename {auth => mobile/apps/auth}/macos/Runner.xcodeproj/project.pbxproj (100%) rename {auth => mobile/apps/auth}/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {auth => mobile/apps/auth}/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (100%) rename {auth => mobile/apps/auth}/macos/Runner.xcworkspace/contents.xcworkspacedata (100%) rename {auth => mobile/apps/auth}/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) rename {auth => mobile/apps/auth}/macos/Runner/AppDelegate.swift (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/128-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/16-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/256-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/32-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/512-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/64-mac.png (100%) rename {auth => mobile/apps/auth}/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename {auth => mobile/apps/auth}/macos/Runner/Base.lproj/MainMenu.xib (100%) rename {auth => mobile/apps/auth}/macos/Runner/Configs/AppInfo.xcconfig (100%) rename {auth => mobile/apps/auth}/macos/Runner/Configs/Debug.xcconfig (100%) rename {auth => mobile/apps/auth}/macos/Runner/Configs/Release.xcconfig (100%) rename {auth => mobile/apps/auth}/macos/Runner/Configs/Warnings.xcconfig (100%) rename {auth => mobile/apps/auth}/macos/Runner/DebugProfile.entitlements (100%) rename {auth => mobile/apps/auth}/macos/Runner/Info.plist (100%) rename {auth => mobile/apps/auth}/macos/Runner/MainFlutterWindow.swift (100%) rename {auth => mobile/apps/auth}/macos/Runner/Release.entitlements (100%) rename {auth => mobile/apps/auth}/macos/build/.last_build_id (100%) rename {auth => mobile/apps/auth}/macos/packaging/dmg/make_config.yaml (100%) rename {auth => mobile/apps/auth}/migration-guides/README.md (100%) rename {auth => mobile/apps/auth}/migration-guides/authy.md (100%) rename {auth => mobile/apps/auth}/migration-guides/encrypted_export.md (100%) rename {auth => mobile/apps/auth}/protos/googleauth.proto (100%) rename {auth => mobile/apps/auth}/pubspec.lock (100%) rename {auth => mobile/apps/auth}/pubspec.yaml (100%) rename {auth => mobile/apps/auth}/screenshots/screenshots.png (100%) rename {auth => mobile/apps/auth}/scripts/release_tag.sh (100%) rename {auth => mobile/apps/auth}/test/helpers/helpers.dart (100%) rename {auth => mobile/apps/auth}/test/helpers/pump_app.dart (100%) rename {auth => mobile/apps/auth}/test/models/code_test.dart (100%) rename {auth => mobile/apps/auth}/web/favicon.png (100%) rename {auth => mobile/apps/auth}/web/icons/Icon-192.png (100%) rename {auth => mobile/apps/auth}/web/icons/Icon-512.png (100%) rename {auth => mobile/apps/auth}/web/icons/favicon.png (100%) rename {auth => mobile/apps/auth}/web/index.html (100%) rename {auth => mobile/apps/auth}/web/manifest.json (100%) rename {auth => mobile/apps/auth}/web/splash/img/dark-1x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/dark-2x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/dark-3x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/dark-4x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/light-1x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/light-2x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/light-3x.png (100%) rename {auth => mobile/apps/auth}/web/splash/img/light-4x.png (100%) rename {auth => mobile/apps/auth}/web/splash/splash.js (100%) rename {auth => mobile/apps/auth}/web/splash/style.css (100%) rename {auth => mobile/apps/auth}/windows/.gitignore (100%) rename {auth => mobile/apps/auth}/windows/CMakeLists.txt (100%) rename {auth => mobile/apps/auth}/windows/flutter/CMakeLists.txt (100%) rename {auth => mobile/apps/auth}/windows/flutter/generated_plugin_registrant.cc (100%) rename {auth => mobile/apps/auth}/windows/flutter/generated_plugin_registrant.h (100%) rename {auth => mobile/apps/auth}/windows/flutter/generated_plugins.cmake (100%) rename {auth => mobile/apps/auth}/windows/packaging/exe/inno_setup.iss (100%) rename {auth => mobile/apps/auth}/windows/packaging/exe/make_config.yaml (100%) rename {auth => mobile/apps/auth}/windows/runner/CMakeLists.txt (100%) rename {auth => mobile/apps/auth}/windows/runner/Runner.rc (100%) rename {auth => mobile/apps/auth}/windows/runner/flutter_window.cpp (100%) rename {auth => mobile/apps/auth}/windows/runner/flutter_window.h (100%) rename {auth => mobile/apps/auth}/windows/runner/main.cpp (100%) rename {auth => mobile/apps/auth}/windows/runner/resource.h (100%) rename {auth => mobile/apps/auth}/windows/runner/resources/app_icon.ico (100%) rename {auth => mobile/apps/auth}/windows/runner/runner.exe.manifest (100%) rename {auth => mobile/apps/auth}/windows/runner/utils.cpp (100%) rename {auth => mobile/apps/auth}/windows/runner/utils.h (100%) rename {auth => mobile/apps/auth}/windows/runner/win32_window.cpp (100%) rename {auth => mobile/apps/auth}/windows/runner/win32_window.h (100%) diff --git a/.gitmodules b/.gitmodules index 115f28e530..44b02538e6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,9 +3,9 @@ url = https://github.com/ente-io/sentry-dart.git branch = sentry_flutter_ente [submodule "auth/flutter"] - path = auth/flutter + path = mobile/apps/auth/flutter url = https://github.com/flutter/flutter.git branch = stable [submodule "auth/assets/simple-icons"] - path = auth/assets/simple-icons + path = mobile/apps/auth/assets/simple-icons url = https://github.com/simple-icons/simple-icons.git diff --git a/auth/.fvmrc b/mobile/apps/auth/.fvmrc similarity index 100% rename from auth/.fvmrc rename to mobile/apps/auth/.fvmrc diff --git a/auth/.gitignore b/mobile/apps/auth/.gitignore similarity index 100% rename from auth/.gitignore rename to mobile/apps/auth/.gitignore diff --git a/auth/.metadata b/mobile/apps/auth/.metadata similarity index 100% rename from auth/.metadata rename to mobile/apps/auth/.metadata diff --git a/auth/README.md b/mobile/apps/auth/README.md similarity index 100% rename from auth/README.md rename to mobile/apps/auth/README.md diff --git a/auth/analysis_options.yaml b/mobile/apps/auth/analysis_options.yaml similarity index 100% rename from auth/analysis_options.yaml rename to mobile/apps/auth/analysis_options.yaml diff --git a/auth/android/.gitignore b/mobile/apps/auth/android/.gitignore similarity index 100% rename from auth/android/.gitignore rename to mobile/apps/auth/android/.gitignore diff --git a/auth/android/app/build.gradle b/mobile/apps/auth/android/app/build.gradle similarity index 100% rename from auth/android/app/build.gradle rename to mobile/apps/auth/android/app/build.gradle diff --git a/auth/android/app/src/debug/AndroidManifest.xml b/mobile/apps/auth/android/app/src/debug/AndroidManifest.xml similarity index 100% rename from auth/android/app/src/debug/AndroidManifest.xml rename to mobile/apps/auth/android/app/src/debug/AndroidManifest.xml diff --git a/auth/android/app/src/development/ic_launcher-playstore.png b/mobile/apps/auth/android/app/src/development/ic_launcher-playstore.png similarity index 100% rename from auth/android/app/src/development/ic_launcher-playstore.png rename to mobile/apps/auth/android/app/src/development/ic_launcher-playstore.png diff --git a/auth/android/app/src/development/res/drawable/ic_launcher_foreground.xml b/mobile/apps/auth/android/app/src/development/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from auth/android/app/src/development/res/drawable/ic_launcher_foreground.xml rename to mobile/apps/auth/android/app/src/development/res/drawable/ic_launcher_foreground.xml diff --git a/auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/apps/auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml rename to mobile/apps/auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml b/mobile/apps/auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to mobile/apps/auth/android/app/src/development/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/auth/android/app/src/development/res/mipmap-hdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/development/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-hdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-hdpi/ic_launcher.png diff --git a/auth/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-hdpi/ic_launcher_round.png diff --git a/auth/android/app/src/development/res/mipmap-mdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/development/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-mdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-mdpi/ic_launcher.png diff --git a/auth/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-mdpi/ic_launcher_round.png diff --git a/auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher.png diff --git a/auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher.png diff --git a/auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/development/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/auth/android/app/src/development/res/values/ic_launcher_background.xml b/mobile/apps/auth/android/app/src/development/res/values/ic_launcher_background.xml similarity index 100% rename from auth/android/app/src/development/res/values/ic_launcher_background.xml rename to mobile/apps/auth/android/app/src/development/res/values/ic_launcher_background.xml diff --git a/auth/android/app/src/main/AndroidManifest.xml b/mobile/apps/auth/android/app/src/main/AndroidManifest.xml similarity index 100% rename from auth/android/app/src/main/AndroidManifest.xml rename to mobile/apps/auth/android/app/src/main/AndroidManifest.xml diff --git a/auth/android/app/src/main/kotlin/io/ente/authenticator/MainActivity.kt b/mobile/apps/auth/android/app/src/main/kotlin/io/ente/authenticator/MainActivity.kt similarity index 100% rename from auth/android/app/src/main/kotlin/io/ente/authenticator/MainActivity.kt rename to mobile/apps/auth/android/app/src/main/kotlin/io/ente/authenticator/MainActivity.kt diff --git a/auth/android/app/src/main/play/listings/en-US/full-description.txt b/mobile/apps/auth/android/app/src/main/play/listings/en-US/full-description.txt similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/full-description.txt rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/full-description.txt diff --git a/auth/android/app/src/main/play/listings/en-US/graphics/icon/icon.png b/mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/icon/icon.png similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/graphics/icon/icon.png rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/icon/icon.png diff --git a/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png b/mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/1.png diff --git a/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png b/mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/2.png diff --git a/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png b/mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/3.png diff --git a/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png b/mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/4.png diff --git a/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png b/mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/graphics/phone-screenshots/5.png diff --git a/auth/android/app/src/main/play/listings/en-US/short-description.txt b/mobile/apps/auth/android/app/src/main/play/listings/en-US/short-description.txt similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/short-description.txt rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/short-description.txt diff --git a/auth/android/app/src/main/play/listings/en-US/title.txt b/mobile/apps/auth/android/app/src/main/play/listings/en-US/title.txt similarity index 100% rename from auth/android/app/src/main/play/listings/en-US/title.txt rename to mobile/apps/auth/android/app/src/main/play/listings/en-US/title.txt diff --git a/auth/android/app/src/main/res/drawable-hdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-hdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-hdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-hdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png b/mobile/apps/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_background.png diff --git a/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png b/mobile/apps/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png similarity index 100% rename from auth/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png rename to mobile/apps/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_foreground.png diff --git a/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png b/mobile/apps/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png similarity index 100% rename from auth/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png rename to mobile/apps/auth/android/app/src/main/res/drawable-hdpi/ic_launcher_monochrome.png diff --git a/auth/android/app/src/main/res/drawable-hdpi/splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-hdpi/splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-hdpi/splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-hdpi/splash.png diff --git a/auth/android/app/src/main/res/drawable-mdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-mdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-mdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-mdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png b/mobile/apps/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_background.png diff --git a/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png b/mobile/apps/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png similarity index 100% rename from auth/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png rename to mobile/apps/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_foreground.png diff --git a/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png b/mobile/apps/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png similarity index 100% rename from auth/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png rename to mobile/apps/auth/android/app/src/main/res/drawable-mdpi/ic_launcher_monochrome.png diff --git a/auth/android/app/src/main/res/drawable-mdpi/splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-mdpi/splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-mdpi/splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-mdpi/splash.png diff --git a/auth/android/app/src/main/res/drawable-night-hdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-night-hdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night-hdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night-hdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-night-mdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-night-mdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night-mdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night-mdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-night-v21/background.png b/mobile/apps/auth/android/app/src/main/res/drawable-night-v21/background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night-v21/background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night-v21/background.png diff --git a/auth/android/app/src/main/res/drawable-night-v21/launch_background.xml b/mobile/apps/auth/android/app/src/main/res/drawable-night-v21/launch_background.xml similarity index 100% rename from auth/android/app/src/main/res/drawable-night-v21/launch_background.xml rename to mobile/apps/auth/android/app/src/main/res/drawable-night-v21/launch_background.xml diff --git a/auth/android/app/src/main/res/drawable-night-xhdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-night-xhdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night-xhdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night-xhdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night-xxhdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night-xxxhdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-night/background.png b/mobile/apps/auth/android/app/src/main/res/drawable-night/background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-night/background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-night/background.png diff --git a/auth/android/app/src/main/res/drawable-night/launch_background.xml b/mobile/apps/auth/android/app/src/main/res/drawable-night/launch_background.xml similarity index 100% rename from auth/android/app/src/main/res/drawable-night/launch_background.xml rename to mobile/apps/auth/android/app/src/main/res/drawable-night/launch_background.xml diff --git a/auth/android/app/src/main/res/drawable-v21/background.png b/mobile/apps/auth/android/app/src/main/res/drawable-v21/background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-v21/background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-v21/background.png diff --git a/auth/android/app/src/main/res/drawable-v21/launch_background.xml b/mobile/apps/auth/android/app/src/main/res/drawable-v21/launch_background.xml similarity index 100% rename from auth/android/app/src/main/res/drawable-v21/launch_background.xml rename to mobile/apps/auth/android/app/src/main/res/drawable-v21/launch_background.xml diff --git a/auth/android/app/src/main/res/drawable-xhdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xhdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png b/mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_background.png diff --git a/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png b/mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_foreground.png diff --git a/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png b/mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/ic_launcher_monochrome.png diff --git a/auth/android/app/src/main/res/drawable-xhdpi/splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xhdpi/splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xhdpi/splash.png diff --git a/auth/android/app/src/main/res/drawable-xxhdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxhdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_background.png diff --git a/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_foreground.png diff --git a/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/ic_launcher_monochrome.png diff --git a/auth/android/app/src/main/res/drawable-xxhdpi/splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxhdpi/splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxhdpi/splash.png diff --git a/auth/android/app/src/main/res/drawable-xxxhdpi/android12splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/android12splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxxhdpi/android12splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/android12splash.png diff --git a/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_background.png diff --git a/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_foreground.png diff --git a/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/ic_launcher_monochrome.png diff --git a/auth/android/app/src/main/res/drawable-xxxhdpi/splash.png b/mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/splash.png similarity index 100% rename from auth/android/app/src/main/res/drawable-xxxhdpi/splash.png rename to mobile/apps/auth/android/app/src/main/res/drawable-xxxhdpi/splash.png diff --git a/auth/android/app/src/main/res/drawable/background.png b/mobile/apps/auth/android/app/src/main/res/drawable/background.png similarity index 100% rename from auth/android/app/src/main/res/drawable/background.png rename to mobile/apps/auth/android/app/src/main/res/drawable/background.png diff --git a/auth/android/app/src/main/res/drawable/launch_background.xml b/mobile/apps/auth/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from auth/android/app/src/main/res/drawable/launch_background.xml rename to mobile/apps/auth/android/app/src/main/res/drawable/launch_background.xml diff --git a/auth/android/app/src/main/res/drawable/notification_icon.png b/mobile/apps/auth/android/app/src/main/res/drawable/notification_icon.png similarity index 100% rename from auth/android/app/src/main/res/drawable/notification_icon.png rename to mobile/apps/auth/android/app/src/main/res/drawable/notification_icon.png diff --git a/auth/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml b/mobile/apps/auth/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml similarity index 100% rename from auth/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml rename to mobile/apps/auth/android/app/src/main/res/mipmap-anydpi-v26/launcher_icon.xml diff --git a/auth/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/mobile/apps/auth/android/app/src/main/res/mipmap-hdpi/launcher_icon.png similarity index 100% rename from auth/android/app/src/main/res/mipmap-hdpi/launcher_icon.png rename to mobile/apps/auth/android/app/src/main/res/mipmap-hdpi/launcher_icon.png diff --git a/auth/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/mobile/apps/auth/android/app/src/main/res/mipmap-mdpi/launcher_icon.png similarity index 100% rename from auth/android/app/src/main/res/mipmap-mdpi/launcher_icon.png rename to mobile/apps/auth/android/app/src/main/res/mipmap-mdpi/launcher_icon.png diff --git a/auth/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/mobile/apps/auth/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png similarity index 100% rename from auth/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png rename to mobile/apps/auth/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png diff --git a/auth/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/mobile/apps/auth/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png similarity index 100% rename from auth/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png rename to mobile/apps/auth/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png diff --git a/auth/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/mobile/apps/auth/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png similarity index 100% rename from auth/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png rename to mobile/apps/auth/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png diff --git a/auth/android/app/src/main/res/values-night-v31/styles.xml b/mobile/apps/auth/android/app/src/main/res/values-night-v31/styles.xml similarity index 100% rename from auth/android/app/src/main/res/values-night-v31/styles.xml rename to mobile/apps/auth/android/app/src/main/res/values-night-v31/styles.xml diff --git a/auth/android/app/src/main/res/values-night/styles.xml b/mobile/apps/auth/android/app/src/main/res/values-night/styles.xml similarity index 100% rename from auth/android/app/src/main/res/values-night/styles.xml rename to mobile/apps/auth/android/app/src/main/res/values-night/styles.xml diff --git a/auth/android/app/src/main/res/values-v31/styles.xml b/mobile/apps/auth/android/app/src/main/res/values-v31/styles.xml similarity index 100% rename from auth/android/app/src/main/res/values-v31/styles.xml rename to mobile/apps/auth/android/app/src/main/res/values-v31/styles.xml diff --git a/auth/android/app/src/main/res/values/colors.xml b/mobile/apps/auth/android/app/src/main/res/values/colors.xml similarity index 100% rename from auth/android/app/src/main/res/values/colors.xml rename to mobile/apps/auth/android/app/src/main/res/values/colors.xml diff --git a/auth/android/app/src/main/res/values/styles.xml b/mobile/apps/auth/android/app/src/main/res/values/styles.xml similarity index 100% rename from auth/android/app/src/main/res/values/styles.xml rename to mobile/apps/auth/android/app/src/main/res/values/styles.xml diff --git a/auth/android/app/src/profile/AndroidManifest.xml b/mobile/apps/auth/android/app/src/profile/AndroidManifest.xml similarity index 100% rename from auth/android/app/src/profile/AndroidManifest.xml rename to mobile/apps/auth/android/app/src/profile/AndroidManifest.xml diff --git a/auth/android/app/src/staging/ic_launcher-playstore.png b/mobile/apps/auth/android/app/src/staging/ic_launcher-playstore.png similarity index 100% rename from auth/android/app/src/staging/ic_launcher-playstore.png rename to mobile/apps/auth/android/app/src/staging/ic_launcher-playstore.png diff --git a/auth/android/app/src/staging/res/drawable/ic_launcher_foreground.xml b/mobile/apps/auth/android/app/src/staging/res/drawable/ic_launcher_foreground.xml similarity index 100% rename from auth/android/app/src/staging/res/drawable/ic_launcher_foreground.xml rename to mobile/apps/auth/android/app/src/staging/res/drawable/ic_launcher_foreground.xml diff --git a/auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher.xml b/mobile/apps/auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher.xml rename to mobile/apps/auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher_round.xml b/mobile/apps/auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to mobile/apps/auth/android/app/src/staging/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher.png diff --git a/auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-hdpi/ic_launcher_round.png diff --git a/auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher.png diff --git a/auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-mdpi/ic_launcher_round.png diff --git a/auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher.png diff --git a/auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher.png diff --git a/auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher_round.png b/mobile/apps/auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher_round.png rename to mobile/apps/auth/android/app/src/staging/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/auth/android/app/src/staging/res/values/ic_launcher_background.xml b/mobile/apps/auth/android/app/src/staging/res/values/ic_launcher_background.xml similarity index 100% rename from auth/android/app/src/staging/res/values/ic_launcher_background.xml rename to mobile/apps/auth/android/app/src/staging/res/values/ic_launcher_background.xml diff --git a/auth/android/build.gradle b/mobile/apps/auth/android/build.gradle similarity index 100% rename from auth/android/build.gradle rename to mobile/apps/auth/android/build.gradle diff --git a/auth/android/gradle.properties b/mobile/apps/auth/android/gradle.properties similarity index 100% rename from auth/android/gradle.properties rename to mobile/apps/auth/android/gradle.properties diff --git a/auth/android/gradle/wrapper/gradle-wrapper.properties b/mobile/apps/auth/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from auth/android/gradle/wrapper/gradle-wrapper.properties rename to mobile/apps/auth/android/gradle/wrapper/gradle-wrapper.properties diff --git a/auth/android/settings.gradle b/mobile/apps/auth/android/settings.gradle similarity index 100% rename from auth/android/settings.gradle rename to mobile/apps/auth/android/settings.gradle diff --git a/auth/architecture/README.md b/mobile/apps/auth/architecture/README.md similarity index 100% rename from auth/architecture/README.md rename to mobile/apps/auth/architecture/README.md diff --git a/auth/architecture/assets/authentication.svg b/mobile/apps/auth/architecture/assets/authentication.svg similarity index 100% rename from auth/architecture/assets/authentication.svg rename to mobile/apps/auth/architecture/assets/authentication.svg diff --git a/auth/architecture/assets/e2ee.svg b/mobile/apps/auth/architecture/assets/e2ee.svg similarity index 100% rename from auth/architecture/assets/e2ee.svg rename to mobile/apps/auth/architecture/assets/e2ee.svg diff --git a/auth/architecture/assets/key-derivation.svg b/mobile/apps/auth/architecture/assets/key-derivation.svg similarity index 100% rename from auth/architecture/assets/key-derivation.svg rename to mobile/apps/auth/architecture/assets/key-derivation.svg diff --git a/auth/architecture/assets/recovery.svg b/mobile/apps/auth/architecture/assets/recovery.svg similarity index 100% rename from auth/architecture/assets/recovery.svg rename to mobile/apps/auth/architecture/assets/recovery.svg diff --git a/auth/architecture/assets/token-encryption.svg b/mobile/apps/auth/architecture/assets/token-encryption.svg similarity index 100% rename from auth/architecture/assets/token-encryption.svg rename to mobile/apps/auth/architecture/assets/token-encryption.svg diff --git a/auth/assets/2.0x/broken_heart.png b/mobile/apps/auth/assets/2.0x/broken_heart.png similarity index 100% rename from auth/assets/2.0x/broken_heart.png rename to mobile/apps/auth/assets/2.0x/broken_heart.png diff --git a/auth/assets/2.0x/calender_banner_dark.png b/mobile/apps/auth/assets/2.0x/calender_banner_dark.png similarity index 100% rename from auth/assets/2.0x/calender_banner_dark.png rename to mobile/apps/auth/assets/2.0x/calender_banner_dark.png diff --git a/auth/assets/2.0x/calender_banner_light.png b/mobile/apps/auth/assets/2.0x/calender_banner_light.png similarity index 100% rename from auth/assets/2.0x/calender_banner_light.png rename to mobile/apps/auth/assets/2.0x/calender_banner_light.png diff --git a/auth/assets/2.0x/discount.png b/mobile/apps/auth/assets/2.0x/discount.png similarity index 100% rename from auth/assets/2.0x/discount.png rename to mobile/apps/auth/assets/2.0x/discount.png diff --git a/auth/assets/2.0x/ente_5gb.png b/mobile/apps/auth/assets/2.0x/ente_5gb.png similarity index 100% rename from auth/assets/2.0x/ente_5gb.png rename to mobile/apps/auth/assets/2.0x/ente_5gb.png diff --git a/auth/assets/2.0x/loading_photos_background.png b/mobile/apps/auth/assets/2.0x/loading_photos_background.png similarity index 100% rename from auth/assets/2.0x/loading_photos_background.png rename to mobile/apps/auth/assets/2.0x/loading_photos_background.png diff --git a/auth/assets/2.0x/loading_photos_background_dark.png b/mobile/apps/auth/assets/2.0x/loading_photos_background_dark.png similarity index 100% rename from auth/assets/2.0x/loading_photos_background_dark.png rename to mobile/apps/auth/assets/2.0x/loading_photos_background_dark.png diff --git a/auth/assets/2.0x/rate_us.png b/mobile/apps/auth/assets/2.0x/rate_us.png similarity index 100% rename from auth/assets/2.0x/rate_us.png rename to mobile/apps/auth/assets/2.0x/rate_us.png diff --git a/auth/assets/2.0x/sheild-front-gradient.png b/mobile/apps/auth/assets/2.0x/sheild-front-gradient.png similarity index 100% rename from auth/assets/2.0x/sheild-front-gradient.png rename to mobile/apps/auth/assets/2.0x/sheild-front-gradient.png diff --git a/auth/assets/2.0x/star_us.png b/mobile/apps/auth/assets/2.0x/star_us.png similarity index 100% rename from auth/assets/2.0x/star_us.png rename to mobile/apps/auth/assets/2.0x/star_us.png diff --git a/auth/assets/2.0x/wallet-front-gradient.png b/mobile/apps/auth/assets/2.0x/wallet-front-gradient.png similarity index 100% rename from auth/assets/2.0x/wallet-front-gradient.png rename to mobile/apps/auth/assets/2.0x/wallet-front-gradient.png diff --git a/auth/assets/3.0x/broken_heart.png b/mobile/apps/auth/assets/3.0x/broken_heart.png similarity index 100% rename from auth/assets/3.0x/broken_heart.png rename to mobile/apps/auth/assets/3.0x/broken_heart.png diff --git a/auth/assets/3.0x/calender_banner_dark.png b/mobile/apps/auth/assets/3.0x/calender_banner_dark.png similarity index 100% rename from auth/assets/3.0x/calender_banner_dark.png rename to mobile/apps/auth/assets/3.0x/calender_banner_dark.png diff --git a/auth/assets/3.0x/calender_banner_light.png b/mobile/apps/auth/assets/3.0x/calender_banner_light.png similarity index 100% rename from auth/assets/3.0x/calender_banner_light.png rename to mobile/apps/auth/assets/3.0x/calender_banner_light.png diff --git a/auth/assets/3.0x/discount.png b/mobile/apps/auth/assets/3.0x/discount.png similarity index 100% rename from auth/assets/3.0x/discount.png rename to mobile/apps/auth/assets/3.0x/discount.png diff --git a/auth/assets/3.0x/ente_5gb.png b/mobile/apps/auth/assets/3.0x/ente_5gb.png similarity index 100% rename from auth/assets/3.0x/ente_5gb.png rename to mobile/apps/auth/assets/3.0x/ente_5gb.png diff --git a/auth/assets/3.0x/loading_photos_background.png b/mobile/apps/auth/assets/3.0x/loading_photos_background.png similarity index 100% rename from auth/assets/3.0x/loading_photos_background.png rename to mobile/apps/auth/assets/3.0x/loading_photos_background.png diff --git a/auth/assets/3.0x/loading_photos_background_dark.png b/mobile/apps/auth/assets/3.0x/loading_photos_background_dark.png similarity index 100% rename from auth/assets/3.0x/loading_photos_background_dark.png rename to mobile/apps/auth/assets/3.0x/loading_photos_background_dark.png diff --git a/auth/assets/3.0x/rate_us.png b/mobile/apps/auth/assets/3.0x/rate_us.png similarity index 100% rename from auth/assets/3.0x/rate_us.png rename to mobile/apps/auth/assets/3.0x/rate_us.png diff --git a/auth/assets/3.0x/sheild-front-gradient.png b/mobile/apps/auth/assets/3.0x/sheild-front-gradient.png similarity index 100% rename from auth/assets/3.0x/sheild-front-gradient.png rename to mobile/apps/auth/assets/3.0x/sheild-front-gradient.png diff --git a/auth/assets/3.0x/star_us.png b/mobile/apps/auth/assets/3.0x/star_us.png similarity index 100% rename from auth/assets/3.0x/star_us.png rename to mobile/apps/auth/assets/3.0x/star_us.png diff --git a/auth/assets/3.0x/wallet-front-gradient.png b/mobile/apps/auth/assets/3.0x/wallet-front-gradient.png similarity index 100% rename from auth/assets/3.0x/wallet-front-gradient.png rename to mobile/apps/auth/assets/3.0x/wallet-front-gradient.png diff --git a/auth/assets/broken_heart.png b/mobile/apps/auth/assets/broken_heart.png similarity index 100% rename from auth/assets/broken_heart.png rename to mobile/apps/auth/assets/broken_heart.png diff --git a/auth/assets/build/.last_build_id b/mobile/apps/auth/assets/build/.last_build_id similarity index 100% rename from auth/assets/build/.last_build_id rename to mobile/apps/auth/assets/build/.last_build_id diff --git a/auth/assets/calender_banner_dark.png b/mobile/apps/auth/assets/calender_banner_dark.png similarity index 100% rename from auth/assets/calender_banner_dark.png rename to mobile/apps/auth/assets/calender_banner_dark.png diff --git a/auth/assets/calender_banner_light.png b/mobile/apps/auth/assets/calender_banner_light.png similarity index 100% rename from auth/assets/calender_banner_light.png rename to mobile/apps/auth/assets/calender_banner_light.png diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json similarity index 100% rename from auth/assets/custom-icons/_data/custom-icons.json rename to mobile/apps/auth/assets/custom-icons/_data/custom-icons.json diff --git a/auth/assets/custom-icons/icons/1x_bet.svg b/mobile/apps/auth/assets/custom-icons/icons/1x_bet.svg similarity index 100% rename from auth/assets/custom-icons/icons/1x_bet.svg rename to mobile/apps/auth/assets/custom-icons/icons/1x_bet.svg diff --git a/auth/assets/custom-icons/icons/23andme.svg b/mobile/apps/auth/assets/custom-icons/icons/23andme.svg similarity index 100% rename from auth/assets/custom-icons/icons/23andme.svg rename to mobile/apps/auth/assets/custom-icons/icons/23andme.svg diff --git a/auth/assets/custom-icons/icons/3commas.svg b/mobile/apps/auth/assets/custom-icons/icons/3commas.svg similarity index 100% rename from auth/assets/custom-icons/icons/3commas.svg rename to mobile/apps/auth/assets/custom-icons/icons/3commas.svg diff --git a/auth/assets/custom-icons/icons/addy_io.svg b/mobile/apps/auth/assets/custom-icons/icons/addy_io.svg similarity index 100% rename from auth/assets/custom-icons/icons/addy_io.svg rename to mobile/apps/auth/assets/custom-icons/icons/addy_io.svg diff --git a/auth/assets/custom-icons/icons/airtable.svg b/mobile/apps/auth/assets/custom-icons/icons/airtable.svg similarity index 100% rename from auth/assets/custom-icons/icons/airtable.svg rename to mobile/apps/auth/assets/custom-icons/icons/airtable.svg diff --git a/auth/assets/custom-icons/icons/airtm.svg b/mobile/apps/auth/assets/custom-icons/icons/airtm.svg similarity index 100% rename from auth/assets/custom-icons/icons/airtm.svg rename to mobile/apps/auth/assets/custom-icons/icons/airtm.svg diff --git a/auth/assets/custom-icons/icons/aj_bell.svg b/mobile/apps/auth/assets/custom-icons/icons/aj_bell.svg similarity index 100% rename from auth/assets/custom-icons/icons/aj_bell.svg rename to mobile/apps/auth/assets/custom-icons/icons/aj_bell.svg diff --git a/auth/assets/custom-icons/icons/aliyun.svg b/mobile/apps/auth/assets/custom-icons/icons/aliyun.svg similarity index 100% rename from auth/assets/custom-icons/icons/aliyun.svg rename to mobile/apps/auth/assets/custom-icons/icons/aliyun.svg diff --git a/auth/assets/custom-icons/icons/amazon.svg b/mobile/apps/auth/assets/custom-icons/icons/amazon.svg similarity index 100% rename from auth/assets/custom-icons/icons/amazon.svg rename to mobile/apps/auth/assets/custom-icons/icons/amazon.svg diff --git a/auth/assets/custom-icons/icons/ankama.svg b/mobile/apps/auth/assets/custom-icons/icons/ankama.svg similarity index 100% rename from auth/assets/custom-icons/icons/ankama.svg rename to mobile/apps/auth/assets/custom-icons/icons/ankama.svg diff --git a/auth/assets/custom-icons/icons/ankara_university.svg b/mobile/apps/auth/assets/custom-icons/icons/ankara_university.svg similarity index 100% rename from auth/assets/custom-icons/icons/ankara_university.svg rename to mobile/apps/auth/assets/custom-icons/icons/ankara_university.svg diff --git a/auth/assets/custom-icons/icons/anycoindirect.svg b/mobile/apps/auth/assets/custom-icons/icons/anycoindirect.svg similarity index 100% rename from auth/assets/custom-icons/icons/anycoindirect.svg rename to mobile/apps/auth/assets/custom-icons/icons/anycoindirect.svg diff --git a/auth/assets/custom-icons/icons/ar24.svg b/mobile/apps/auth/assets/custom-icons/icons/ar24.svg similarity index 100% rename from auth/assets/custom-icons/icons/ar24.svg rename to mobile/apps/auth/assets/custom-icons/icons/ar24.svg diff --git a/auth/assets/custom-icons/icons/aruba.svg b/mobile/apps/auth/assets/custom-icons/icons/aruba.svg similarity index 100% rename from auth/assets/custom-icons/icons/aruba.svg rename to mobile/apps/auth/assets/custom-icons/icons/aruba.svg diff --git a/auth/assets/custom-icons/icons/ascendex.svg b/mobile/apps/auth/assets/custom-icons/icons/ascendex.svg similarity index 100% rename from auth/assets/custom-icons/icons/ascendex.svg rename to mobile/apps/auth/assets/custom-icons/icons/ascendex.svg diff --git a/auth/assets/custom-icons/icons/aternos.svg b/mobile/apps/auth/assets/custom-icons/icons/aternos.svg similarity index 100% rename from auth/assets/custom-icons/icons/aternos.svg rename to mobile/apps/auth/assets/custom-icons/icons/aternos.svg diff --git a/auth/assets/custom-icons/icons/authentik.svg b/mobile/apps/auth/assets/custom-icons/icons/authentik.svg similarity index 100% rename from auth/assets/custom-icons/icons/authentik.svg rename to mobile/apps/auth/assets/custom-icons/icons/authentik.svg diff --git a/auth/assets/custom-icons/icons/azurhosts.svg b/mobile/apps/auth/assets/custom-icons/icons/azurhosts.svg similarity index 100% rename from auth/assets/custom-icons/icons/azurhosts.svg rename to mobile/apps/auth/assets/custom-icons/icons/azurhosts.svg diff --git a/auth/assets/custom-icons/icons/azurware.svg b/mobile/apps/auth/assets/custom-icons/icons/azurware.svg similarity index 100% rename from auth/assets/custom-icons/icons/azurware.svg rename to mobile/apps/auth/assets/custom-icons/icons/azurware.svg diff --git a/auth/assets/custom-icons/icons/badlion.svg b/mobile/apps/auth/assets/custom-icons/icons/badlion.svg similarity index 100% rename from auth/assets/custom-icons/icons/badlion.svg rename to mobile/apps/auth/assets/custom-icons/icons/badlion.svg diff --git a/auth/assets/custom-icons/icons/baidu_cloud.svg b/mobile/apps/auth/assets/custom-icons/icons/baidu_cloud.svg similarity index 100% rename from auth/assets/custom-icons/icons/baidu_cloud.svg rename to mobile/apps/auth/assets/custom-icons/icons/baidu_cloud.svg diff --git a/auth/assets/custom-icons/icons/band.svg b/mobile/apps/auth/assets/custom-icons/icons/band.svg similarity index 100% rename from auth/assets/custom-icons/icons/band.svg rename to mobile/apps/auth/assets/custom-icons/icons/band.svg diff --git a/auth/assets/custom-icons/icons/battlenet.svg b/mobile/apps/auth/assets/custom-icons/icons/battlenet.svg similarity index 100% rename from auth/assets/custom-icons/icons/battlenet.svg rename to mobile/apps/auth/assets/custom-icons/icons/battlenet.svg diff --git a/auth/assets/custom-icons/icons/bbs_nga.svg b/mobile/apps/auth/assets/custom-icons/icons/bbs_nga.svg similarity index 100% rename from auth/assets/custom-icons/icons/bbs_nga.svg rename to mobile/apps/auth/assets/custom-icons/icons/bbs_nga.svg diff --git a/auth/assets/custom-icons/icons/belo.svg b/mobile/apps/auth/assets/custom-icons/icons/belo.svg similarity index 100% rename from auth/assets/custom-icons/icons/belo.svg rename to mobile/apps/auth/assets/custom-icons/icons/belo.svg diff --git a/auth/assets/custom-icons/icons/bethesda.svg b/mobile/apps/auth/assets/custom-icons/icons/bethesda.svg similarity index 100% rename from auth/assets/custom-icons/icons/bethesda.svg rename to mobile/apps/auth/assets/custom-icons/icons/bethesda.svg diff --git a/auth/assets/custom-icons/icons/binance_exchange.svg b/mobile/apps/auth/assets/custom-icons/icons/binance_exchange.svg similarity index 100% rename from auth/assets/custom-icons/icons/binance_exchange.svg rename to mobile/apps/auth/assets/custom-icons/icons/binance_exchange.svg diff --git a/auth/assets/custom-icons/icons/binance_tr.svg b/mobile/apps/auth/assets/custom-icons/icons/binance_tr.svg similarity index 100% rename from auth/assets/custom-icons/icons/binance_tr.svg rename to mobile/apps/auth/assets/custom-icons/icons/binance_tr.svg diff --git a/auth/assets/custom-icons/icons/binance_us.svg b/mobile/apps/auth/assets/custom-icons/icons/binance_us.svg similarity index 100% rename from auth/assets/custom-icons/icons/binance_us.svg rename to mobile/apps/auth/assets/custom-icons/icons/binance_us.svg diff --git a/auth/assets/custom-icons/icons/bingx.svg b/mobile/apps/auth/assets/custom-icons/icons/bingx.svg similarity index 100% rename from auth/assets/custom-icons/icons/bingx.svg rename to mobile/apps/auth/assets/custom-icons/icons/bingx.svg diff --git a/auth/assets/custom-icons/icons/bitazza.svg b/mobile/apps/auth/assets/custom-icons/icons/bitazza.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitazza.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitazza.svg diff --git a/auth/assets/custom-icons/icons/bitfinex.svg b/mobile/apps/auth/assets/custom-icons/icons/bitfinex.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitfinex.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitfinex.svg diff --git a/auth/assets/custom-icons/icons/bitget.svg b/mobile/apps/auth/assets/custom-icons/icons/bitget.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitget.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitget.svg diff --git a/auth/assets/custom-icons/icons/bitget_wallet.svg b/mobile/apps/auth/assets/custom-icons/icons/bitget_wallet.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitget_wallet.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitget_wallet.svg diff --git a/auth/assets/custom-icons/icons/bitkub.svg b/mobile/apps/auth/assets/custom-icons/icons/bitkub.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitkub.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitkub.svg diff --git a/auth/assets/custom-icons/icons/bitmart.svg b/mobile/apps/auth/assets/custom-icons/icons/bitmart.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitmart.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitmart.svg diff --git a/auth/assets/custom-icons/icons/bitmex.svg b/mobile/apps/auth/assets/custom-icons/icons/bitmex.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitmex.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitmex.svg diff --git a/auth/assets/custom-icons/icons/bitoasis.svg b/mobile/apps/auth/assets/custom-icons/icons/bitoasis.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitoasis.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitoasis.svg diff --git a/auth/assets/custom-icons/icons/bitskins.svg b/mobile/apps/auth/assets/custom-icons/icons/bitskins.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitskins.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitskins.svg diff --git a/auth/assets/custom-icons/icons/bitstamp.svg b/mobile/apps/auth/assets/custom-icons/icons/bitstamp.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitstamp.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitstamp.svg diff --git a/auth/assets/custom-icons/icons/bitvavo.svg b/mobile/apps/auth/assets/custom-icons/icons/bitvavo.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitvavo.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitvavo.svg diff --git a/auth/assets/custom-icons/icons/bitwarden.svg b/mobile/apps/auth/assets/custom-icons/icons/bitwarden.svg similarity index 100% rename from auth/assets/custom-icons/icons/bitwarden.svg rename to mobile/apps/auth/assets/custom-icons/icons/bitwarden.svg diff --git a/auth/assets/custom-icons/icons/blockchain.svg b/mobile/apps/auth/assets/custom-icons/icons/blockchain.svg similarity index 100% rename from auth/assets/custom-icons/icons/blockchain.svg rename to mobile/apps/auth/assets/custom-icons/icons/blockchain.svg diff --git a/auth/assets/custom-icons/icons/bloom_host.svg b/mobile/apps/auth/assets/custom-icons/icons/bloom_host.svg similarity index 100% rename from auth/assets/custom-icons/icons/bloom_host.svg rename to mobile/apps/auth/assets/custom-icons/icons/bloom_host.svg diff --git a/auth/assets/custom-icons/icons/blue_sky.svg b/mobile/apps/auth/assets/custom-icons/icons/blue_sky.svg similarity index 100% rename from auth/assets/custom-icons/icons/blue_sky.svg rename to mobile/apps/auth/assets/custom-icons/icons/blue_sky.svg diff --git a/auth/assets/custom-icons/icons/bonify.svg b/mobile/apps/auth/assets/custom-icons/icons/bonify.svg similarity index 100% rename from auth/assets/custom-icons/icons/bonify.svg rename to mobile/apps/auth/assets/custom-icons/icons/bonify.svg diff --git a/auth/assets/custom-icons/icons/booking.svg b/mobile/apps/auth/assets/custom-icons/icons/booking.svg similarity index 100% rename from auth/assets/custom-icons/icons/booking.svg rename to mobile/apps/auth/assets/custom-icons/icons/booking.svg diff --git a/auth/assets/custom-icons/icons/borg_base.svg b/mobile/apps/auth/assets/custom-icons/icons/borg_base.svg similarity index 100% rename from auth/assets/custom-icons/icons/borg_base.svg rename to mobile/apps/auth/assets/custom-icons/icons/borg_base.svg diff --git a/auth/assets/custom-icons/icons/brave_creators.svg b/mobile/apps/auth/assets/custom-icons/icons/brave_creators.svg similarity index 100% rename from auth/assets/custom-icons/icons/brave_creators.svg rename to mobile/apps/auth/assets/custom-icons/icons/brave_creators.svg diff --git a/auth/assets/custom-icons/icons/bugzilla.svg b/mobile/apps/auth/assets/custom-icons/icons/bugzilla.svg similarity index 100% rename from auth/assets/custom-icons/icons/bugzilla.svg rename to mobile/apps/auth/assets/custom-icons/icons/bugzilla.svg diff --git a/auth/assets/custom-icons/icons/bundesagentur_fur_arbeit.svg b/mobile/apps/auth/assets/custom-icons/icons/bundesagentur_fur_arbeit.svg similarity index 100% rename from auth/assets/custom-icons/icons/bundesagentur_fur_arbeit.svg rename to mobile/apps/auth/assets/custom-icons/icons/bundesagentur_fur_arbeit.svg diff --git a/auth/assets/custom-icons/icons/butterflymx.svg b/mobile/apps/auth/assets/custom-icons/icons/butterflymx.svg similarity index 100% rename from auth/assets/custom-icons/icons/butterflymx.svg rename to mobile/apps/auth/assets/custom-icons/icons/butterflymx.svg diff --git a/auth/assets/custom-icons/icons/bybit.svg b/mobile/apps/auth/assets/custom-icons/icons/bybit.svg similarity index 100% rename from auth/assets/custom-icons/icons/bybit.svg rename to mobile/apps/auth/assets/custom-icons/icons/bybit.svg diff --git a/auth/assets/custom-icons/icons/caixa.svg b/mobile/apps/auth/assets/custom-icons/icons/caixa.svg similarity index 100% rename from auth/assets/custom-icons/icons/caixa.svg rename to mobile/apps/auth/assets/custom-icons/icons/caixa.svg diff --git a/auth/assets/custom-icons/icons/canada_flag.svg b/mobile/apps/auth/assets/custom-icons/icons/canada_flag.svg similarity index 100% rename from auth/assets/custom-icons/icons/canada_flag.svg rename to mobile/apps/auth/assets/custom-icons/icons/canada_flag.svg diff --git a/auth/assets/custom-icons/icons/canva.svg b/mobile/apps/auth/assets/custom-icons/icons/canva.svg similarity index 100% rename from auth/assets/custom-icons/icons/canva.svg rename to mobile/apps/auth/assets/custom-icons/icons/canva.svg diff --git a/auth/assets/custom-icons/icons/capacities.svg b/mobile/apps/auth/assets/custom-icons/icons/capacities.svg similarity index 100% rename from auth/assets/custom-icons/icons/capacities.svg rename to mobile/apps/auth/assets/custom-icons/icons/capacities.svg diff --git a/auth/assets/custom-icons/icons/carta.svg b/mobile/apps/auth/assets/custom-icons/icons/carta.svg similarity index 100% rename from auth/assets/custom-icons/icons/carta.svg rename to mobile/apps/auth/assets/custom-icons/icons/carta.svg diff --git a/auth/assets/custom-icons/icons/cern.svg b/mobile/apps/auth/assets/custom-icons/icons/cern.svg similarity index 100% rename from auth/assets/custom-icons/icons/cern.svg rename to mobile/apps/auth/assets/custom-icons/icons/cern.svg diff --git a/auth/assets/custom-icons/icons/changenow.svg b/mobile/apps/auth/assets/custom-icons/icons/changenow.svg similarity index 100% rename from auth/assets/custom-icons/icons/changenow.svg rename to mobile/apps/auth/assets/custom-icons/icons/changenow.svg diff --git a/auth/assets/custom-icons/icons/cih.svg b/mobile/apps/auth/assets/custom-icons/icons/cih.svg similarity index 100% rename from auth/assets/custom-icons/icons/cih.svg rename to mobile/apps/auth/assets/custom-icons/icons/cih.svg diff --git a/auth/assets/custom-icons/icons/cloudamqp.svg b/mobile/apps/auth/assets/custom-icons/icons/cloudamqp.svg similarity index 100% rename from auth/assets/custom-icons/icons/cloudamqp.svg rename to mobile/apps/auth/assets/custom-icons/icons/cloudamqp.svg diff --git a/auth/assets/custom-icons/icons/cloudflare.svg b/mobile/apps/auth/assets/custom-icons/icons/cloudflare.svg similarity index 100% rename from auth/assets/custom-icons/icons/cloudflare.svg rename to mobile/apps/auth/assets/custom-icons/icons/cloudflare.svg diff --git a/auth/assets/custom-icons/icons/cloudns.svg b/mobile/apps/auth/assets/custom-icons/icons/cloudns.svg similarity index 100% rename from auth/assets/custom-icons/icons/cloudns.svg rename to mobile/apps/auth/assets/custom-icons/icons/cloudns.svg diff --git a/auth/assets/custom-icons/icons/coinbase.svg b/mobile/apps/auth/assets/custom-icons/icons/coinbase.svg similarity index 100% rename from auth/assets/custom-icons/icons/coinbase.svg rename to mobile/apps/auth/assets/custom-icons/icons/coinbase.svg diff --git a/auth/assets/custom-icons/icons/coindcx.svg b/mobile/apps/auth/assets/custom-icons/icons/coindcx.svg similarity index 100% rename from auth/assets/custom-icons/icons/coindcx.svg rename to mobile/apps/auth/assets/custom-icons/icons/coindcx.svg diff --git a/auth/assets/custom-icons/icons/coinspot.svg b/mobile/apps/auth/assets/custom-icons/icons/coinspot.svg similarity index 100% rename from auth/assets/custom-icons/icons/coinspot.svg rename to mobile/apps/auth/assets/custom-icons/icons/coinspot.svg diff --git a/auth/assets/custom-icons/icons/configcat.svg b/mobile/apps/auth/assets/custom-icons/icons/configcat.svg similarity index 100% rename from auth/assets/custom-icons/icons/configcat.svg rename to mobile/apps/auth/assets/custom-icons/icons/configcat.svg diff --git a/auth/assets/custom-icons/icons/controld.svg b/mobile/apps/auth/assets/custom-icons/icons/controld.svg similarity index 100% rename from auth/assets/custom-icons/icons/controld.svg rename to mobile/apps/auth/assets/custom-icons/icons/controld.svg diff --git a/auth/assets/custom-icons/icons/cronometer.svg b/mobile/apps/auth/assets/custom-icons/icons/cronometer.svg similarity index 100% rename from auth/assets/custom-icons/icons/cronometer.svg rename to mobile/apps/auth/assets/custom-icons/icons/cronometer.svg diff --git a/auth/assets/custom-icons/icons/crowdpear.svg b/mobile/apps/auth/assets/custom-icons/icons/crowdpear.svg similarity index 100% rename from auth/assets/custom-icons/icons/crowdpear.svg rename to mobile/apps/auth/assets/custom-icons/icons/crowdpear.svg diff --git a/auth/assets/custom-icons/icons/cryptee.svg b/mobile/apps/auth/assets/custom-icons/icons/cryptee.svg similarity index 100% rename from auth/assets/custom-icons/icons/cryptee.svg rename to mobile/apps/auth/assets/custom-icons/icons/cryptee.svg diff --git a/auth/assets/custom-icons/icons/crypto.svg b/mobile/apps/auth/assets/custom-icons/icons/crypto.svg similarity index 100% rename from auth/assets/custom-icons/icons/crypto.svg rename to mobile/apps/auth/assets/custom-icons/icons/crypto.svg diff --git a/auth/assets/custom-icons/icons/csam.svg b/mobile/apps/auth/assets/custom-icons/icons/csam.svg similarity index 100% rename from auth/assets/custom-icons/icons/csam.svg rename to mobile/apps/auth/assets/custom-icons/icons/csam.svg diff --git a/auth/assets/custom-icons/icons/csfloat.svg b/mobile/apps/auth/assets/custom-icons/icons/csfloat.svg similarity index 100% rename from auth/assets/custom-icons/icons/csfloat.svg rename to mobile/apps/auth/assets/custom-icons/icons/csfloat.svg diff --git a/auth/assets/custom-icons/icons/csgoroll.svg b/mobile/apps/auth/assets/custom-icons/icons/csgoroll.svg similarity index 100% rename from auth/assets/custom-icons/icons/csgoroll.svg rename to mobile/apps/auth/assets/custom-icons/icons/csgoroll.svg diff --git a/auth/assets/custom-icons/icons/cssbuy.svg b/mobile/apps/auth/assets/custom-icons/icons/cssbuy.svg similarity index 100% rename from auth/assets/custom-icons/icons/cssbuy.svg rename to mobile/apps/auth/assets/custom-icons/icons/cssbuy.svg diff --git a/auth/assets/custom-icons/icons/cwallet.svg b/mobile/apps/auth/assets/custom-icons/icons/cwallet.svg similarity index 100% rename from auth/assets/custom-icons/icons/cwallet.svg rename to mobile/apps/auth/assets/custom-icons/icons/cwallet.svg diff --git a/auth/assets/custom-icons/icons/dcs.svg b/mobile/apps/auth/assets/custom-icons/icons/dcs.svg similarity index 100% rename from auth/assets/custom-icons/icons/dcs.svg rename to mobile/apps/auth/assets/custom-icons/icons/dcs.svg diff --git a/auth/assets/custom-icons/icons/degiro.svg b/mobile/apps/auth/assets/custom-icons/icons/degiro.svg similarity index 100% rename from auth/assets/custom-icons/icons/degiro.svg rename to mobile/apps/auth/assets/custom-icons/icons/degiro.svg diff --git a/auth/assets/custom-icons/icons/deloitte.svg b/mobile/apps/auth/assets/custom-icons/icons/deloitte.svg similarity index 100% rename from auth/assets/custom-icons/icons/deloitte.svg rename to mobile/apps/auth/assets/custom-icons/icons/deloitte.svg diff --git a/auth/assets/custom-icons/icons/deriv.svg b/mobile/apps/auth/assets/custom-icons/icons/deriv.svg similarity index 100% rename from auth/assets/custom-icons/icons/deriv.svg rename to mobile/apps/auth/assets/custom-icons/icons/deriv.svg diff --git a/auth/assets/custom-icons/icons/digifinex.svg b/mobile/apps/auth/assets/custom-icons/icons/digifinex.svg similarity index 100% rename from auth/assets/custom-icons/icons/digifinex.svg rename to mobile/apps/auth/assets/custom-icons/icons/digifinex.svg diff --git a/auth/assets/custom-icons/icons/directadmin.svg b/mobile/apps/auth/assets/custom-icons/icons/directadmin.svg similarity index 100% rename from auth/assets/custom-icons/icons/directadmin.svg rename to mobile/apps/auth/assets/custom-icons/icons/directadmin.svg diff --git a/auth/assets/custom-icons/icons/discourse.svg b/mobile/apps/auth/assets/custom-icons/icons/discourse.svg similarity index 100% rename from auth/assets/custom-icons/icons/discourse.svg rename to mobile/apps/auth/assets/custom-icons/icons/discourse.svg diff --git a/auth/assets/custom-icons/icons/dmarket.svg b/mobile/apps/auth/assets/custom-icons/icons/dmarket.svg similarity index 100% rename from auth/assets/custom-icons/icons/dmarket.svg rename to mobile/apps/auth/assets/custom-icons/icons/dmarket.svg diff --git a/auth/assets/custom-icons/icons/docuseal.svg b/mobile/apps/auth/assets/custom-icons/icons/docuseal.svg similarity index 100% rename from auth/assets/custom-icons/icons/docuseal.svg rename to mobile/apps/auth/assets/custom-icons/icons/docuseal.svg diff --git a/auth/assets/custom-icons/icons/doppler.svg b/mobile/apps/auth/assets/custom-icons/icons/doppler.svg similarity index 100% rename from auth/assets/custom-icons/icons/doppler.svg rename to mobile/apps/auth/assets/custom-icons/icons/doppler.svg diff --git a/auth/assets/custom-icons/icons/dreamhost_panel.svg b/mobile/apps/auth/assets/custom-icons/icons/dreamhost_panel.svg similarity index 100% rename from auth/assets/custom-icons/icons/dreamhost_panel.svg rename to mobile/apps/auth/assets/custom-icons/icons/dreamhost_panel.svg diff --git a/auth/assets/custom-icons/icons/dropbox.svg b/mobile/apps/auth/assets/custom-icons/icons/dropbox.svg similarity index 100% rename from auth/assets/custom-icons/icons/dropbox.svg rename to mobile/apps/auth/assets/custom-icons/icons/dropbox.svg diff --git a/auth/assets/custom-icons/icons/dusnet.svg b/mobile/apps/auth/assets/custom-icons/icons/dusnet.svg similarity index 100% rename from auth/assets/custom-icons/icons/dusnet.svg rename to mobile/apps/auth/assets/custom-icons/icons/dusnet.svg diff --git a/auth/assets/custom-icons/icons/ebay.svg b/mobile/apps/auth/assets/custom-icons/icons/ebay.svg similarity index 100% rename from auth/assets/custom-icons/icons/ebay.svg rename to mobile/apps/auth/assets/custom-icons/icons/ebay.svg diff --git a/auth/assets/custom-icons/icons/ecitizen_kenya.svg b/mobile/apps/auth/assets/custom-icons/icons/ecitizen_kenya.svg similarity index 100% rename from auth/assets/custom-icons/icons/ecitizen_kenya.svg rename to mobile/apps/auth/assets/custom-icons/icons/ecitizen_kenya.svg diff --git a/auth/assets/custom-icons/icons/ecloud.svg b/mobile/apps/auth/assets/custom-icons/icons/ecloud.svg similarity index 100% rename from auth/assets/custom-icons/icons/ecloud.svg rename to mobile/apps/auth/assets/custom-icons/icons/ecloud.svg diff --git a/auth/assets/custom-icons/icons/eneba.svg b/mobile/apps/auth/assets/custom-icons/icons/eneba.svg similarity index 100% rename from auth/assets/custom-icons/icons/eneba.svg rename to mobile/apps/auth/assets/custom-icons/icons/eneba.svg diff --git a/auth/assets/custom-icons/icons/enom.svg b/mobile/apps/auth/assets/custom-icons/icons/enom.svg similarity index 100% rename from auth/assets/custom-icons/icons/enom.svg rename to mobile/apps/auth/assets/custom-icons/icons/enom.svg diff --git a/auth/assets/custom-icons/icons/ente.svg b/mobile/apps/auth/assets/custom-icons/icons/ente.svg similarity index 100% rename from auth/assets/custom-icons/icons/ente.svg rename to mobile/apps/auth/assets/custom-icons/icons/ente.svg diff --git a/auth/assets/custom-icons/icons/epic_games.svg b/mobile/apps/auth/assets/custom-icons/icons/epic_games.svg similarity index 100% rename from auth/assets/custom-icons/icons/epic_games.svg rename to mobile/apps/auth/assets/custom-icons/icons/epic_games.svg diff --git a/auth/assets/custom-icons/icons/esketit.svg b/mobile/apps/auth/assets/custom-icons/icons/esketit.svg similarity index 100% rename from auth/assets/custom-icons/icons/esketit.svg rename to mobile/apps/auth/assets/custom-icons/icons/esketit.svg diff --git a/auth/assets/custom-icons/icons/estateguru.svg b/mobile/apps/auth/assets/custom-icons/icons/estateguru.svg similarity index 100% rename from auth/assets/custom-icons/icons/estateguru.svg rename to mobile/apps/auth/assets/custom-icons/icons/estateguru.svg diff --git a/auth/assets/custom-icons/icons/eve_online.svg b/mobile/apps/auth/assets/custom-icons/icons/eve_online.svg similarity index 100% rename from auth/assets/custom-icons/icons/eve_online.svg rename to mobile/apps/auth/assets/custom-icons/icons/eve_online.svg diff --git a/auth/assets/custom-icons/icons/fanatical.svg b/mobile/apps/auth/assets/custom-icons/icons/fanatical.svg similarity index 100% rename from auth/assets/custom-icons/icons/fanatical.svg rename to mobile/apps/auth/assets/custom-icons/icons/fanatical.svg diff --git a/auth/assets/custom-icons/icons/fastmail.svg b/mobile/apps/auth/assets/custom-icons/icons/fastmail.svg similarity index 100% rename from auth/assets/custom-icons/icons/fastmail.svg rename to mobile/apps/auth/assets/custom-icons/icons/fastmail.svg diff --git a/auth/assets/custom-icons/icons/federal_student_aid.svg b/mobile/apps/auth/assets/custom-icons/icons/federal_student_aid.svg similarity index 100% rename from auth/assets/custom-icons/icons/federal_student_aid.svg rename to mobile/apps/auth/assets/custom-icons/icons/federal_student_aid.svg diff --git a/auth/assets/custom-icons/icons/fidelity.svg b/mobile/apps/auth/assets/custom-icons/icons/fidelity.svg similarity index 100% rename from auth/assets/custom-icons/icons/fidelity.svg rename to mobile/apps/auth/assets/custom-icons/icons/fidelity.svg diff --git a/auth/assets/custom-icons/icons/filen.svg b/mobile/apps/auth/assets/custom-icons/icons/filen.svg similarity index 100% rename from auth/assets/custom-icons/icons/filen.svg rename to mobile/apps/auth/assets/custom-icons/icons/filen.svg diff --git a/auth/assets/custom-icons/icons/finanzfluss.svg b/mobile/apps/auth/assets/custom-icons/icons/finanzfluss.svg similarity index 100% rename from auth/assets/custom-icons/icons/finanzfluss.svg rename to mobile/apps/auth/assets/custom-icons/icons/finanzfluss.svg diff --git a/auth/assets/custom-icons/icons/finary.svg b/mobile/apps/auth/assets/custom-icons/icons/finary.svg similarity index 100% rename from auth/assets/custom-icons/icons/finary.svg rename to mobile/apps/auth/assets/custom-icons/icons/finary.svg diff --git a/auth/assets/custom-icons/icons/fortrabbit.svg b/mobile/apps/auth/assets/custom-icons/icons/fortrabbit.svg similarity index 100% rename from auth/assets/custom-icons/icons/fortrabbit.svg rename to mobile/apps/auth/assets/custom-icons/icons/fortrabbit.svg diff --git a/auth/assets/custom-icons/icons/forusall.svg b/mobile/apps/auth/assets/custom-icons/icons/forusall.svg similarity index 100% rename from auth/assets/custom-icons/icons/forusall.svg rename to mobile/apps/auth/assets/custom-icons/icons/forusall.svg diff --git a/auth/assets/custom-icons/icons/freetaxusa.svg b/mobile/apps/auth/assets/custom-icons/icons/freetaxusa.svg similarity index 100% rename from auth/assets/custom-icons/icons/freetaxusa.svg rename to mobile/apps/auth/assets/custom-icons/icons/freetaxusa.svg diff --git a/auth/assets/custom-icons/icons/fzj.svg b/mobile/apps/auth/assets/custom-icons/icons/fzj.svg similarity index 100% rename from auth/assets/custom-icons/icons/fzj.svg rename to mobile/apps/auth/assets/custom-icons/icons/fzj.svg diff --git a/auth/assets/custom-icons/icons/g2a.svg b/mobile/apps/auth/assets/custom-icons/icons/g2a.svg similarity index 100% rename from auth/assets/custom-icons/icons/g2a.svg rename to mobile/apps/auth/assets/custom-icons/icons/g2a.svg diff --git a/auth/assets/custom-icons/icons/gateio.svg b/mobile/apps/auth/assets/custom-icons/icons/gateio.svg similarity index 100% rename from auth/assets/custom-icons/icons/gateio.svg rename to mobile/apps/auth/assets/custom-icons/icons/gateio.svg diff --git a/auth/assets/custom-icons/icons/gerid.svg b/mobile/apps/auth/assets/custom-icons/icons/gerid.svg similarity index 100% rename from auth/assets/custom-icons/icons/gerid.svg rename to mobile/apps/auth/assets/custom-icons/icons/gerid.svg diff --git a/auth/assets/custom-icons/icons/github.svg b/mobile/apps/auth/assets/custom-icons/icons/github.svg similarity index 100% rename from auth/assets/custom-icons/icons/github.svg rename to mobile/apps/auth/assets/custom-icons/icons/github.svg diff --git a/auth/assets/custom-icons/icons/gitlab.svg b/mobile/apps/auth/assets/custom-icons/icons/gitlab.svg similarity index 100% rename from auth/assets/custom-icons/icons/gitlab.svg rename to mobile/apps/auth/assets/custom-icons/icons/gitlab.svg diff --git a/auth/assets/custom-icons/icons/gmx.svg b/mobile/apps/auth/assets/custom-icons/icons/gmx.svg similarity index 100% rename from auth/assets/custom-icons/icons/gmx.svg rename to mobile/apps/auth/assets/custom-icons/icons/gmx.svg diff --git a/auth/assets/custom-icons/icons/gommehd.svg b/mobile/apps/auth/assets/custom-icons/icons/gommehd.svg similarity index 100% rename from auth/assets/custom-icons/icons/gommehd.svg rename to mobile/apps/auth/assets/custom-icons/icons/gommehd.svg diff --git a/auth/assets/custom-icons/icons/google.svg b/mobile/apps/auth/assets/custom-icons/icons/google.svg similarity index 100% rename from auth/assets/custom-icons/icons/google.svg rename to mobile/apps/auth/assets/custom-icons/icons/google.svg diff --git a/auth/assets/custom-icons/icons/gosuslugi.svg b/mobile/apps/auth/assets/custom-icons/icons/gosuslugi.svg similarity index 100% rename from auth/assets/custom-icons/icons/gosuslugi.svg rename to mobile/apps/auth/assets/custom-icons/icons/gosuslugi.svg diff --git a/auth/assets/custom-icons/icons/gov_uk.svg b/mobile/apps/auth/assets/custom-icons/icons/gov_uk.svg similarity index 100% rename from auth/assets/custom-icons/icons/gov_uk.svg rename to mobile/apps/auth/assets/custom-icons/icons/gov_uk.svg diff --git a/auth/assets/custom-icons/icons/guideline.svg b/mobile/apps/auth/assets/custom-icons/icons/guideline.svg similarity index 100% rename from auth/assets/custom-icons/icons/guideline.svg rename to mobile/apps/auth/assets/custom-icons/icons/guideline.svg diff --git a/auth/assets/custom-icons/icons/gusto.svg b/mobile/apps/auth/assets/custom-icons/icons/gusto.svg similarity index 100% rename from auth/assets/custom-icons/icons/gusto.svg rename to mobile/apps/auth/assets/custom-icons/icons/gusto.svg diff --git a/auth/assets/custom-icons/icons/habbo.svg b/mobile/apps/auth/assets/custom-icons/icons/habbo.svg similarity index 100% rename from auth/assets/custom-icons/icons/habbo.svg rename to mobile/apps/auth/assets/custom-icons/icons/habbo.svg diff --git a/auth/assets/custom-icons/icons/healthchecks.svg b/mobile/apps/auth/assets/custom-icons/icons/healthchecks.svg similarity index 100% rename from auth/assets/custom-icons/icons/healthchecks.svg rename to mobile/apps/auth/assets/custom-icons/icons/healthchecks.svg diff --git a/auth/assets/custom-icons/icons/hivelocity.svg b/mobile/apps/auth/assets/custom-icons/icons/hivelocity.svg similarity index 100% rename from auth/assets/custom-icons/icons/hivelocity.svg rename to mobile/apps/auth/assets/custom-icons/icons/hivelocity.svg diff --git a/auth/assets/custom-icons/icons/htx.svg b/mobile/apps/auth/assets/custom-icons/icons/htx.svg similarity index 100% rename from auth/assets/custom-icons/icons/htx.svg rename to mobile/apps/auth/assets/custom-icons/icons/htx.svg diff --git a/auth/assets/custom-icons/icons/huggingface.svg b/mobile/apps/auth/assets/custom-icons/icons/huggingface.svg similarity index 100% rename from auth/assets/custom-icons/icons/huggingface.svg rename to mobile/apps/auth/assets/custom-icons/icons/huggingface.svg diff --git a/auth/assets/custom-icons/icons/ibkr.svg b/mobile/apps/auth/assets/custom-icons/icons/ibkr.svg similarity index 100% rename from auth/assets/custom-icons/icons/ibkr.svg rename to mobile/apps/auth/assets/custom-icons/icons/ibkr.svg diff --git a/auth/assets/custom-icons/icons/ice_drive.svg b/mobile/apps/auth/assets/custom-icons/icons/ice_drive.svg similarity index 100% rename from auth/assets/custom-icons/icons/ice_drive.svg rename to mobile/apps/auth/assets/custom-icons/icons/ice_drive.svg diff --git a/auth/assets/custom-icons/icons/iconomi.svg b/mobile/apps/auth/assets/custom-icons/icons/iconomi.svg similarity index 100% rename from auth/assets/custom-icons/icons/iconomi.svg rename to mobile/apps/auth/assets/custom-icons/icons/iconomi.svg diff --git a/auth/assets/custom-icons/icons/id_me.svg b/mobile/apps/auth/assets/custom-icons/icons/id_me.svg similarity index 100% rename from auth/assets/custom-icons/icons/id_me.svg rename to mobile/apps/auth/assets/custom-icons/icons/id_me.svg diff --git a/auth/assets/custom-icons/icons/immo_scout_24.svg b/mobile/apps/auth/assets/custom-icons/icons/immo_scout_24.svg similarity index 100% rename from auth/assets/custom-icons/icons/immo_scout_24.svg rename to mobile/apps/auth/assets/custom-icons/icons/immo_scout_24.svg diff --git a/auth/assets/custom-icons/icons/infomaniak.svg b/mobile/apps/auth/assets/custom-icons/icons/infomaniak.svg similarity index 100% rename from auth/assets/custom-icons/icons/infomaniak.svg rename to mobile/apps/auth/assets/custom-icons/icons/infomaniak.svg diff --git a/auth/assets/custom-icons/icons/ing.svg b/mobile/apps/auth/assets/custom-icons/icons/ing.svg similarity index 100% rename from auth/assets/custom-icons/icons/ing.svg rename to mobile/apps/auth/assets/custom-icons/icons/ing.svg diff --git a/auth/assets/custom-icons/icons/instagram.svg b/mobile/apps/auth/assets/custom-icons/icons/instagram.svg similarity index 100% rename from auth/assets/custom-icons/icons/instagram.svg rename to mobile/apps/auth/assets/custom-icons/icons/instagram.svg diff --git a/auth/assets/custom-icons/icons/instant_gaming.svg b/mobile/apps/auth/assets/custom-icons/icons/instant_gaming.svg similarity index 100% rename from auth/assets/custom-icons/icons/instant_gaming.svg rename to mobile/apps/auth/assets/custom-icons/icons/instant_gaming.svg diff --git a/auth/assets/custom-icons/icons/inwx.svg b/mobile/apps/auth/assets/custom-icons/icons/inwx.svg similarity index 100% rename from auth/assets/custom-icons/icons/inwx.svg rename to mobile/apps/auth/assets/custom-icons/icons/inwx.svg diff --git a/auth/assets/custom-icons/icons/itch_io.svg b/mobile/apps/auth/assets/custom-icons/icons/itch_io.svg similarity index 100% rename from auth/assets/custom-icons/icons/itch_io.svg rename to mobile/apps/auth/assets/custom-icons/icons/itch_io.svg diff --git a/auth/assets/custom-icons/icons/ivpn.svg b/mobile/apps/auth/assets/custom-icons/icons/ivpn.svg similarity index 100% rename from auth/assets/custom-icons/icons/ivpn.svg rename to mobile/apps/auth/assets/custom-icons/icons/ivpn.svg diff --git a/auth/assets/custom-icons/icons/jagex.svg b/mobile/apps/auth/assets/custom-icons/icons/jagex.svg similarity index 100% rename from auth/assets/custom-icons/icons/jagex.svg rename to mobile/apps/auth/assets/custom-icons/icons/jagex.svg diff --git a/auth/assets/custom-icons/icons/jianguoyun.svg b/mobile/apps/auth/assets/custom-icons/icons/jianguoyun.svg similarity index 100% rename from auth/assets/custom-icons/icons/jianguoyun.svg rename to mobile/apps/auth/assets/custom-icons/icons/jianguoyun.svg diff --git a/auth/assets/custom-icons/icons/kagi.svg b/mobile/apps/auth/assets/custom-icons/icons/kagi.svg similarity index 100% rename from auth/assets/custom-icons/icons/kagi.svg rename to mobile/apps/auth/assets/custom-icons/icons/kagi.svg diff --git a/auth/assets/custom-icons/icons/keygen.svg b/mobile/apps/auth/assets/custom-icons/icons/keygen.svg similarity index 100% rename from auth/assets/custom-icons/icons/keygen.svg rename to mobile/apps/auth/assets/custom-icons/icons/keygen.svg diff --git a/auth/assets/custom-icons/icons/kick.svg b/mobile/apps/auth/assets/custom-icons/icons/kick.svg similarity index 100% rename from auth/assets/custom-icons/icons/kick.svg rename to mobile/apps/auth/assets/custom-icons/icons/kick.svg diff --git a/auth/assets/custom-icons/icons/kite.svg b/mobile/apps/auth/assets/custom-icons/icons/kite.svg similarity index 100% rename from auth/assets/custom-icons/icons/kite.svg rename to mobile/apps/auth/assets/custom-icons/icons/kite.svg diff --git a/auth/assets/custom-icons/icons/knownhost.svg b/mobile/apps/auth/assets/custom-icons/icons/knownhost.svg similarity index 100% rename from auth/assets/custom-icons/icons/knownhost.svg rename to mobile/apps/auth/assets/custom-icons/icons/knownhost.svg diff --git a/auth/assets/custom-icons/icons/ko_fi.svg b/mobile/apps/auth/assets/custom-icons/icons/ko_fi.svg similarity index 100% rename from auth/assets/custom-icons/icons/ko_fi.svg rename to mobile/apps/auth/assets/custom-icons/icons/ko_fi.svg diff --git a/auth/assets/custom-icons/icons/koofr.svg b/mobile/apps/auth/assets/custom-icons/icons/koofr.svg similarity index 100% rename from auth/assets/custom-icons/icons/koofr.svg rename to mobile/apps/auth/assets/custom-icons/icons/koofr.svg diff --git a/auth/assets/custom-icons/icons/kotas.svg b/mobile/apps/auth/assets/custom-icons/icons/kotas.svg similarity index 100% rename from auth/assets/custom-icons/icons/kotas.svg rename to mobile/apps/auth/assets/custom-icons/icons/kotas.svg diff --git a/auth/assets/custom-icons/icons/kpn.svg b/mobile/apps/auth/assets/custom-icons/icons/kpn.svg similarity index 100% rename from auth/assets/custom-icons/icons/kpn.svg rename to mobile/apps/auth/assets/custom-icons/icons/kpn.svg diff --git a/auth/assets/custom-icons/icons/kraken.svg b/mobile/apps/auth/assets/custom-icons/icons/kraken.svg similarity index 100% rename from auth/assets/custom-icons/icons/kraken.svg rename to mobile/apps/auth/assets/custom-icons/icons/kraken.svg diff --git a/auth/assets/custom-icons/icons/kronos.svg b/mobile/apps/auth/assets/custom-icons/icons/kronos.svg similarity index 100% rename from auth/assets/custom-icons/icons/kronos.svg rename to mobile/apps/auth/assets/custom-icons/icons/kronos.svg diff --git a/auth/assets/custom-icons/icons/kucoin.svg b/mobile/apps/auth/assets/custom-icons/icons/kucoin.svg similarity index 100% rename from auth/assets/custom-icons/icons/kucoin.svg rename to mobile/apps/auth/assets/custom-icons/icons/kucoin.svg diff --git a/auth/assets/custom-icons/icons/labymod.svg b/mobile/apps/auth/assets/custom-icons/icons/labymod.svg similarity index 100% rename from auth/assets/custom-icons/icons/labymod.svg rename to mobile/apps/auth/assets/custom-icons/icons/labymod.svg diff --git a/auth/assets/custom-icons/icons/laposte.svg b/mobile/apps/auth/assets/custom-icons/icons/laposte.svg similarity index 100% rename from auth/assets/custom-icons/icons/laposte.svg rename to mobile/apps/auth/assets/custom-icons/icons/laposte.svg diff --git a/auth/assets/custom-icons/icons/lark.svg b/mobile/apps/auth/assets/custom-icons/icons/lark.svg similarity index 100% rename from auth/assets/custom-icons/icons/lark.svg rename to mobile/apps/auth/assets/custom-icons/icons/lark.svg diff --git a/auth/assets/custom-icons/icons/launchdarkly.svg b/mobile/apps/auth/assets/custom-icons/icons/launchdarkly.svg similarity index 100% rename from auth/assets/custom-icons/icons/launchdarkly.svg rename to mobile/apps/auth/assets/custom-icons/icons/launchdarkly.svg diff --git a/auth/assets/custom-icons/icons/letterboxd.svg b/mobile/apps/auth/assets/custom-icons/icons/letterboxd.svg similarity index 100% rename from auth/assets/custom-icons/icons/letterboxd.svg rename to mobile/apps/auth/assets/custom-icons/icons/letterboxd.svg diff --git a/auth/assets/custom-icons/icons/linkedin.svg b/mobile/apps/auth/assets/custom-icons/icons/linkedin.svg similarity index 100% rename from auth/assets/custom-icons/icons/linkedin.svg rename to mobile/apps/auth/assets/custom-icons/icons/linkedin.svg diff --git a/auth/assets/custom-icons/icons/linux_do.svg b/mobile/apps/auth/assets/custom-icons/icons/linux_do.svg similarity index 100% rename from auth/assets/custom-icons/icons/linux_do.svg rename to mobile/apps/auth/assets/custom-icons/icons/linux_do.svg diff --git a/auth/assets/custom-icons/icons/local_wp.svg b/mobile/apps/auth/assets/custom-icons/icons/local_wp.svg similarity index 100% rename from auth/assets/custom-icons/icons/local_wp.svg rename to mobile/apps/auth/assets/custom-icons/icons/local_wp.svg diff --git a/auth/assets/custom-icons/icons/login_gov.svg b/mobile/apps/auth/assets/custom-icons/icons/login_gov.svg similarity index 100% rename from auth/assets/custom-icons/icons/login_gov.svg rename to mobile/apps/auth/assets/custom-icons/icons/login_gov.svg diff --git a/auth/assets/custom-icons/icons/luma.svg b/mobile/apps/auth/assets/custom-icons/icons/luma.svg similarity index 100% rename from auth/assets/custom-icons/icons/luma.svg rename to mobile/apps/auth/assets/custom-icons/icons/luma.svg diff --git a/auth/assets/custom-icons/icons/marketplacedottf.svg b/mobile/apps/auth/assets/custom-icons/icons/marketplacedottf.svg similarity index 100% rename from auth/assets/custom-icons/icons/marketplacedottf.svg rename to mobile/apps/auth/assets/custom-icons/icons/marketplacedottf.svg diff --git a/auth/assets/custom-icons/icons/mastodon.svg b/mobile/apps/auth/assets/custom-icons/icons/mastodon.svg similarity index 100% rename from auth/assets/custom-icons/icons/mastodon.svg rename to mobile/apps/auth/assets/custom-icons/icons/mastodon.svg diff --git a/auth/assets/custom-icons/icons/matlab.svg b/mobile/apps/auth/assets/custom-icons/icons/matlab.svg similarity index 100% rename from auth/assets/custom-icons/icons/matlab.svg rename to mobile/apps/auth/assets/custom-icons/icons/matlab.svg diff --git a/auth/assets/custom-icons/icons/mbin.svg b/mobile/apps/auth/assets/custom-icons/icons/mbin.svg similarity index 100% rename from auth/assets/custom-icons/icons/mbin.svg rename to mobile/apps/auth/assets/custom-icons/icons/mbin.svg diff --git a/auth/assets/custom-icons/icons/memed.svg b/mobile/apps/auth/assets/custom-icons/icons/memed.svg similarity index 100% rename from auth/assets/custom-icons/icons/memed.svg rename to mobile/apps/auth/assets/custom-icons/icons/memed.svg diff --git a/auth/assets/custom-icons/icons/mercado_libre.svg b/mobile/apps/auth/assets/custom-icons/icons/mercado_libre.svg similarity index 100% rename from auth/assets/custom-icons/icons/mercado_libre.svg rename to mobile/apps/auth/assets/custom-icons/icons/mercado_libre.svg diff --git a/auth/assets/custom-icons/icons/mexc.svg b/mobile/apps/auth/assets/custom-icons/icons/mexc.svg similarity index 100% rename from auth/assets/custom-icons/icons/mexc.svg rename to mobile/apps/auth/assets/custom-icons/icons/mexc.svg diff --git a/auth/assets/custom-icons/icons/microsoft.svg b/mobile/apps/auth/assets/custom-icons/icons/microsoft.svg similarity index 100% rename from auth/assets/custom-icons/icons/microsoft.svg rename to mobile/apps/auth/assets/custom-icons/icons/microsoft.svg diff --git a/auth/assets/custom-icons/icons/microsoft365.svg b/mobile/apps/auth/assets/custom-icons/icons/microsoft365.svg similarity index 100% rename from auth/assets/custom-icons/icons/microsoft365.svg rename to mobile/apps/auth/assets/custom-icons/icons/microsoft365.svg diff --git a/auth/assets/custom-icons/icons/migros.svg b/mobile/apps/auth/assets/custom-icons/icons/migros.svg similarity index 100% rename from auth/assets/custom-icons/icons/migros.svg rename to mobile/apps/auth/assets/custom-icons/icons/migros.svg diff --git a/auth/assets/custom-icons/icons/mintos.svg b/mobile/apps/auth/assets/custom-icons/icons/mintos.svg similarity index 100% rename from auth/assets/custom-icons/icons/mintos.svg rename to mobile/apps/auth/assets/custom-icons/icons/mintos.svg diff --git a/auth/assets/custom-icons/icons/mistral.svg b/mobile/apps/auth/assets/custom-icons/icons/mistral.svg similarity index 100% rename from auth/assets/custom-icons/icons/mistral.svg rename to mobile/apps/auth/assets/custom-icons/icons/mistral.svg diff --git a/auth/assets/custom-icons/icons/mozilla.svg b/mobile/apps/auth/assets/custom-icons/icons/mozilla.svg similarity index 100% rename from auth/assets/custom-icons/icons/mozilla.svg rename to mobile/apps/auth/assets/custom-icons/icons/mozilla.svg diff --git a/auth/assets/custom-icons/icons/myfritz.svg b/mobile/apps/auth/assets/custom-icons/icons/myfritz.svg similarity index 100% rename from auth/assets/custom-icons/icons/myfritz.svg rename to mobile/apps/auth/assets/custom-icons/icons/myfritz.svg diff --git a/auth/assets/custom-icons/icons/name_com.svg b/mobile/apps/auth/assets/custom-icons/icons/name_com.svg similarity index 100% rename from auth/assets/custom-icons/icons/name_com.svg rename to mobile/apps/auth/assets/custom-icons/icons/name_com.svg diff --git a/auth/assets/custom-icons/icons/nekohosting.svg b/mobile/apps/auth/assets/custom-icons/icons/nekohosting.svg similarity index 100% rename from auth/assets/custom-icons/icons/nekohosting.svg rename to mobile/apps/auth/assets/custom-icons/icons/nekohosting.svg diff --git a/auth/assets/custom-icons/icons/nekohosting_gp.svg b/mobile/apps/auth/assets/custom-icons/icons/nekohosting_gp.svg similarity index 100% rename from auth/assets/custom-icons/icons/nekohosting_gp.svg rename to mobile/apps/auth/assets/custom-icons/icons/nekohosting_gp.svg diff --git a/auth/assets/custom-icons/icons/nelnet.svg b/mobile/apps/auth/assets/custom-icons/icons/nelnet.svg similarity index 100% rename from auth/assets/custom-icons/icons/nelnet.svg rename to mobile/apps/auth/assets/custom-icons/icons/nelnet.svg diff --git a/auth/assets/custom-icons/icons/netease_mail.svg b/mobile/apps/auth/assets/custom-icons/icons/netease_mail.svg similarity index 100% rename from auth/assets/custom-icons/icons/netease_mail.svg rename to mobile/apps/auth/assets/custom-icons/icons/netease_mail.svg diff --git a/auth/assets/custom-icons/icons/newgrounds.svg b/mobile/apps/auth/assets/custom-icons/icons/newgrounds.svg similarity index 100% rename from auth/assets/custom-icons/icons/newgrounds.svg rename to mobile/apps/auth/assets/custom-icons/icons/newgrounds.svg diff --git a/auth/assets/custom-icons/icons/newton.svg b/mobile/apps/auth/assets/custom-icons/icons/newton.svg similarity index 100% rename from auth/assets/custom-icons/icons/newton.svg rename to mobile/apps/auth/assets/custom-icons/icons/newton.svg diff --git a/auth/assets/custom-icons/icons/nextcloud.svg b/mobile/apps/auth/assets/custom-icons/icons/nextcloud.svg similarity index 100% rename from auth/assets/custom-icons/icons/nextcloud.svg rename to mobile/apps/auth/assets/custom-icons/icons/nextcloud.svg diff --git a/auth/assets/custom-icons/icons/nextdns.svg b/mobile/apps/auth/assets/custom-icons/icons/nextdns.svg similarity index 100% rename from auth/assets/custom-icons/icons/nextdns.svg rename to mobile/apps/auth/assets/custom-icons/icons/nextdns.svg diff --git a/auth/assets/custom-icons/icons/ngrok.svg b/mobile/apps/auth/assets/custom-icons/icons/ngrok.svg similarity index 100% rename from auth/assets/custom-icons/icons/ngrok.svg rename to mobile/apps/auth/assets/custom-icons/icons/ngrok.svg diff --git a/auth/assets/custom-icons/icons/nintendo.svg b/mobile/apps/auth/assets/custom-icons/icons/nintendo.svg similarity index 100% rename from auth/assets/custom-icons/icons/nintendo.svg rename to mobile/apps/auth/assets/custom-icons/icons/nintendo.svg diff --git a/auth/assets/custom-icons/icons/njalla.svg b/mobile/apps/auth/assets/custom-icons/icons/njalla.svg similarity index 100% rename from auth/assets/custom-icons/icons/njalla.svg rename to mobile/apps/auth/assets/custom-icons/icons/njalla.svg diff --git a/auth/assets/custom-icons/icons/noip.svg b/mobile/apps/auth/assets/custom-icons/icons/noip.svg similarity index 100% rename from auth/assets/custom-icons/icons/noip.svg rename to mobile/apps/auth/assets/custom-icons/icons/noip.svg diff --git a/auth/assets/custom-icons/icons/nordaccount.svg b/mobile/apps/auth/assets/custom-icons/icons/nordaccount.svg similarity index 100% rename from auth/assets/custom-icons/icons/nordaccount.svg rename to mobile/apps/auth/assets/custom-icons/icons/nordaccount.svg diff --git a/auth/assets/custom-icons/icons/notesnook.svg b/mobile/apps/auth/assets/custom-icons/icons/notesnook.svg similarity index 100% rename from auth/assets/custom-icons/icons/notesnook.svg rename to mobile/apps/auth/assets/custom-icons/icons/notesnook.svg diff --git a/auth/assets/custom-icons/icons/notion.svg b/mobile/apps/auth/assets/custom-icons/icons/notion.svg similarity index 100% rename from auth/assets/custom-icons/icons/notion.svg rename to mobile/apps/auth/assets/custom-icons/icons/notion.svg diff --git a/auth/assets/custom-icons/icons/nucommunity.svg b/mobile/apps/auth/assets/custom-icons/icons/nucommunity.svg similarity index 100% rename from auth/assets/custom-icons/icons/nucommunity.svg rename to mobile/apps/auth/assets/custom-icons/icons/nucommunity.svg diff --git a/auth/assets/custom-icons/icons/nvidia.svg b/mobile/apps/auth/assets/custom-icons/icons/nvidia.svg similarity index 100% rename from auth/assets/custom-icons/icons/nvidia.svg rename to mobile/apps/auth/assets/custom-icons/icons/nvidia.svg diff --git a/auth/assets/custom-icons/icons/odido.svg b/mobile/apps/auth/assets/custom-icons/icons/odido.svg similarity index 100% rename from auth/assets/custom-icons/icons/odido.svg rename to mobile/apps/auth/assets/custom-icons/icons/odido.svg diff --git a/auth/assets/custom-icons/icons/okx.svg b/mobile/apps/auth/assets/custom-icons/icons/okx.svg similarity index 100% rename from auth/assets/custom-icons/icons/okx.svg rename to mobile/apps/auth/assets/custom-icons/icons/okx.svg diff --git a/auth/assets/custom-icons/icons/open_observe.svg b/mobile/apps/auth/assets/custom-icons/icons/open_observe.svg similarity index 100% rename from auth/assets/custom-icons/icons/open_observe.svg rename to mobile/apps/auth/assets/custom-icons/icons/open_observe.svg diff --git a/auth/assets/custom-icons/icons/oracle_cloud.svg b/mobile/apps/auth/assets/custom-icons/icons/oracle_cloud.svg similarity index 100% rename from auth/assets/custom-icons/icons/oracle_cloud.svg rename to mobile/apps/auth/assets/custom-icons/icons/oracle_cloud.svg diff --git a/auth/assets/custom-icons/icons/parqet.svg b/mobile/apps/auth/assets/custom-icons/icons/parqet.svg similarity index 100% rename from auth/assets/custom-icons/icons/parqet.svg rename to mobile/apps/auth/assets/custom-icons/icons/parqet.svg diff --git a/auth/assets/custom-icons/icons/parsec.svg b/mobile/apps/auth/assets/custom-icons/icons/parsec.svg similarity index 100% rename from auth/assets/custom-icons/icons/parsec.svg rename to mobile/apps/auth/assets/custom-icons/icons/parsec.svg diff --git a/auth/assets/custom-icons/icons/patient_access.svg b/mobile/apps/auth/assets/custom-icons/icons/patient_access.svg similarity index 100% rename from auth/assets/custom-icons/icons/patient_access.svg rename to mobile/apps/auth/assets/custom-icons/icons/patient_access.svg diff --git a/auth/assets/custom-icons/icons/paypal.svg b/mobile/apps/auth/assets/custom-icons/icons/paypal.svg similarity index 100% rename from auth/assets/custom-icons/icons/paypal.svg rename to mobile/apps/auth/assets/custom-icons/icons/paypal.svg diff --git a/auth/assets/custom-icons/icons/pbtech.svg b/mobile/apps/auth/assets/custom-icons/icons/pbtech.svg similarity index 100% rename from auth/assets/custom-icons/icons/pbtech.svg rename to mobile/apps/auth/assets/custom-icons/icons/pbtech.svg diff --git a/auth/assets/custom-icons/icons/pcloud.svg b/mobile/apps/auth/assets/custom-icons/icons/pcloud.svg similarity index 100% rename from auth/assets/custom-icons/icons/pcloud.svg rename to mobile/apps/auth/assets/custom-icons/icons/pcloud.svg diff --git a/auth/assets/custom-icons/icons/pebble_host.svg b/mobile/apps/auth/assets/custom-icons/icons/pebble_host.svg similarity index 100% rename from auth/assets/custom-icons/icons/pebble_host.svg rename to mobile/apps/auth/assets/custom-icons/icons/pebble_host.svg diff --git a/auth/assets/custom-icons/icons/peerberry.svg b/mobile/apps/auth/assets/custom-icons/icons/peerberry.svg similarity index 100% rename from auth/assets/custom-icons/icons/peerberry.svg rename to mobile/apps/auth/assets/custom-icons/icons/peerberry.svg diff --git a/auth/assets/custom-icons/icons/pingvinshare.svg b/mobile/apps/auth/assets/custom-icons/icons/pingvinshare.svg similarity index 100% rename from auth/assets/custom-icons/icons/pingvinshare.svg rename to mobile/apps/auth/assets/custom-icons/icons/pingvinshare.svg diff --git a/auth/assets/custom-icons/icons/pionex.svg b/mobile/apps/auth/assets/custom-icons/icons/pionex.svg similarity index 100% rename from auth/assets/custom-icons/icons/pionex.svg rename to mobile/apps/auth/assets/custom-icons/icons/pionex.svg diff --git a/auth/assets/custom-icons/icons/plutus.svg b/mobile/apps/auth/assets/custom-icons/icons/plutus.svg similarity index 100% rename from auth/assets/custom-icons/icons/plutus.svg rename to mobile/apps/auth/assets/custom-icons/icons/plutus.svg diff --git a/auth/assets/custom-icons/icons/poloniex.svg b/mobile/apps/auth/assets/custom-icons/icons/poloniex.svg similarity index 100% rename from auth/assets/custom-icons/icons/poloniex.svg rename to mobile/apps/auth/assets/custom-icons/icons/poloniex.svg diff --git a/auth/assets/custom-icons/icons/porkbun.svg b/mobile/apps/auth/assets/custom-icons/icons/porkbun.svg similarity index 100% rename from auth/assets/custom-icons/icons/porkbun.svg rename to mobile/apps/auth/assets/custom-icons/icons/porkbun.svg diff --git a/auth/assets/custom-icons/icons/postmarkapp.svg b/mobile/apps/auth/assets/custom-icons/icons/postmarkapp.svg similarity index 100% rename from auth/assets/custom-icons/icons/postmarkapp.svg rename to mobile/apps/auth/assets/custom-icons/icons/postmarkapp.svg diff --git a/auth/assets/custom-icons/icons/postnl.svg b/mobile/apps/auth/assets/custom-icons/icons/postnl.svg similarity index 100% rename from auth/assets/custom-icons/icons/postnl.svg rename to mobile/apps/auth/assets/custom-icons/icons/postnl.svg diff --git a/auth/assets/custom-icons/icons/postscanmail.svg b/mobile/apps/auth/assets/custom-icons/icons/postscanmail.svg similarity index 100% rename from auth/assets/custom-icons/icons/postscanmail.svg rename to mobile/apps/auth/assets/custom-icons/icons/postscanmail.svg diff --git a/auth/assets/custom-icons/icons/prey_project.svg b/mobile/apps/auth/assets/custom-icons/icons/prey_project.svg similarity index 100% rename from auth/assets/custom-icons/icons/prey_project.svg rename to mobile/apps/auth/assets/custom-icons/icons/prey_project.svg diff --git a/auth/assets/custom-icons/icons/privacy.svg b/mobile/apps/auth/assets/custom-icons/icons/privacy.svg similarity index 100% rename from auth/assets/custom-icons/icons/privacy.svg rename to mobile/apps/auth/assets/custom-icons/icons/privacy.svg diff --git a/auth/assets/custom-icons/icons/privacyguides.svg b/mobile/apps/auth/assets/custom-icons/icons/privacyguides.svg similarity index 100% rename from auth/assets/custom-icons/icons/privacyguides.svg rename to mobile/apps/auth/assets/custom-icons/icons/privacyguides.svg diff --git a/auth/assets/custom-icons/icons/proton.svg b/mobile/apps/auth/assets/custom-icons/icons/proton.svg similarity index 100% rename from auth/assets/custom-icons/icons/proton.svg rename to mobile/apps/auth/assets/custom-icons/icons/proton.svg diff --git a/auth/assets/custom-icons/icons/proxmox.svg b/mobile/apps/auth/assets/custom-icons/icons/proxmox.svg similarity index 100% rename from auth/assets/custom-icons/icons/proxmox.svg rename to mobile/apps/auth/assets/custom-icons/icons/proxmox.svg diff --git a/auth/assets/custom-icons/icons/pushover.svg b/mobile/apps/auth/assets/custom-icons/icons/pushover.svg similarity index 100% rename from auth/assets/custom-icons/icons/pushover.svg rename to mobile/apps/auth/assets/custom-icons/icons/pushover.svg diff --git a/auth/assets/custom-icons/icons/qiniuyun.svg b/mobile/apps/auth/assets/custom-icons/icons/qiniuyun.svg similarity index 100% rename from auth/assets/custom-icons/icons/qiniuyun.svg rename to mobile/apps/auth/assets/custom-icons/icons/qiniuyun.svg diff --git a/auth/assets/custom-icons/icons/r10.svg b/mobile/apps/auth/assets/custom-icons/icons/r10.svg similarity index 100% rename from auth/assets/custom-icons/icons/r10.svg rename to mobile/apps/auth/assets/custom-icons/icons/r10.svg diff --git a/auth/assets/custom-icons/icons/raindrop_io.svg b/mobile/apps/auth/assets/custom-icons/icons/raindrop_io.svg similarity index 100% rename from auth/assets/custom-icons/icons/raindrop_io.svg rename to mobile/apps/auth/assets/custom-icons/icons/raindrop_io.svg diff --git a/auth/assets/custom-icons/icons/randstad.svg b/mobile/apps/auth/assets/custom-icons/icons/randstad.svg similarity index 100% rename from auth/assets/custom-icons/icons/randstad.svg rename to mobile/apps/auth/assets/custom-icons/icons/randstad.svg diff --git a/auth/assets/custom-icons/icons/real_debrid.svg b/mobile/apps/auth/assets/custom-icons/icons/real_debrid.svg similarity index 100% rename from auth/assets/custom-icons/icons/real_debrid.svg rename to mobile/apps/auth/assets/custom-icons/icons/real_debrid.svg diff --git a/auth/assets/custom-icons/icons/realme.svg b/mobile/apps/auth/assets/custom-icons/icons/realme.svg similarity index 100% rename from auth/assets/custom-icons/icons/realme.svg rename to mobile/apps/auth/assets/custom-icons/icons/realme.svg diff --git a/auth/assets/custom-icons/icons/realvnc.svg b/mobile/apps/auth/assets/custom-icons/icons/realvnc.svg similarity index 100% rename from auth/assets/custom-icons/icons/realvnc.svg rename to mobile/apps/auth/assets/custom-icons/icons/realvnc.svg diff --git a/auth/assets/custom-icons/icons/redotpay.svg b/mobile/apps/auth/assets/custom-icons/icons/redotpay.svg similarity index 100% rename from auth/assets/custom-icons/icons/redotpay.svg rename to mobile/apps/auth/assets/custom-icons/icons/redotpay.svg diff --git a/auth/assets/custom-icons/icons/registro_br.svg b/mobile/apps/auth/assets/custom-icons/icons/registro_br.svg similarity index 100% rename from auth/assets/custom-icons/icons/registro_br.svg rename to mobile/apps/auth/assets/custom-icons/icons/registro_br.svg diff --git a/auth/assets/custom-icons/icons/remarkable.svg b/mobile/apps/auth/assets/custom-icons/icons/remarkable.svg similarity index 100% rename from auth/assets/custom-icons/icons/remarkable.svg rename to mobile/apps/auth/assets/custom-icons/icons/remarkable.svg diff --git a/auth/assets/custom-icons/icons/render.svg b/mobile/apps/auth/assets/custom-icons/icons/render.svg similarity index 100% rename from auth/assets/custom-icons/icons/render.svg rename to mobile/apps/auth/assets/custom-icons/icons/render.svg diff --git a/auth/assets/custom-icons/icons/restream.svg b/mobile/apps/auth/assets/custom-icons/icons/restream.svg similarity index 100% rename from auth/assets/custom-icons/icons/restream.svg rename to mobile/apps/auth/assets/custom-icons/icons/restream.svg diff --git a/auth/assets/custom-icons/icons/revolt.svg b/mobile/apps/auth/assets/custom-icons/icons/revolt.svg similarity index 100% rename from auth/assets/custom-icons/icons/revolt.svg rename to mobile/apps/auth/assets/custom-icons/icons/revolt.svg diff --git a/auth/assets/custom-icons/icons/ripplematch.svg b/mobile/apps/auth/assets/custom-icons/icons/ripplematch.svg similarity index 100% rename from auth/assets/custom-icons/icons/ripplematch.svg rename to mobile/apps/auth/assets/custom-icons/icons/ripplematch.svg diff --git a/auth/assets/custom-icons/icons/rockstar_games.svg b/mobile/apps/auth/assets/custom-icons/icons/rockstar_games.svg similarity index 100% rename from auth/assets/custom-icons/icons/rockstar_games.svg rename to mobile/apps/auth/assets/custom-icons/icons/rockstar_games.svg diff --git a/auth/assets/custom-icons/icons/runemate.svg b/mobile/apps/auth/assets/custom-icons/icons/runemate.svg similarity index 100% rename from auth/assets/custom-icons/icons/runemate.svg rename to mobile/apps/auth/assets/custom-icons/icons/runemate.svg diff --git a/auth/assets/custom-icons/icons/runescape_wiki.svg b/mobile/apps/auth/assets/custom-icons/icons/runescape_wiki.svg similarity index 100% rename from auth/assets/custom-icons/icons/runescape_wiki.svg rename to mobile/apps/auth/assets/custom-icons/icons/runescape_wiki.svg diff --git a/auth/assets/custom-icons/icons/rust_language_forum.svg b/mobile/apps/auth/assets/custom-icons/icons/rust_language_forum.svg similarity index 100% rename from auth/assets/custom-icons/icons/rust_language_forum.svg rename to mobile/apps/auth/assets/custom-icons/icons/rust_language_forum.svg diff --git a/auth/assets/custom-icons/icons/samsung.svg b/mobile/apps/auth/assets/custom-icons/icons/samsung.svg similarity index 100% rename from auth/assets/custom-icons/icons/samsung.svg rename to mobile/apps/auth/assets/custom-icons/icons/samsung.svg diff --git a/auth/assets/custom-icons/icons/seafile.svg b/mobile/apps/auth/assets/custom-icons/icons/seafile.svg similarity index 100% rename from auth/assets/custom-icons/icons/seafile.svg rename to mobile/apps/auth/assets/custom-icons/icons/seafile.svg diff --git a/auth/assets/custom-icons/icons/sei.svg b/mobile/apps/auth/assets/custom-icons/icons/sei.svg similarity index 100% rename from auth/assets/custom-icons/icons/sei.svg rename to mobile/apps/auth/assets/custom-icons/icons/sei.svg diff --git a/auth/assets/custom-icons/icons/sendgrid.svg b/mobile/apps/auth/assets/custom-icons/icons/sendgrid.svg similarity index 100% rename from auth/assets/custom-icons/icons/sendgrid.svg rename to mobile/apps/auth/assets/custom-icons/icons/sendgrid.svg diff --git a/auth/assets/custom-icons/icons/service-bw.svg b/mobile/apps/auth/assets/custom-icons/icons/service-bw.svg similarity index 100% rename from auth/assets/custom-icons/icons/service-bw.svg rename to mobile/apps/auth/assets/custom-icons/icons/service-bw.svg diff --git a/auth/assets/custom-icons/icons/shakepay.svg b/mobile/apps/auth/assets/custom-icons/icons/shakepay.svg similarity index 100% rename from auth/assets/custom-icons/icons/shakepay.svg rename to mobile/apps/auth/assets/custom-icons/icons/shakepay.svg diff --git a/auth/assets/custom-icons/icons/simplelogin.svg b/mobile/apps/auth/assets/custom-icons/icons/simplelogin.svg similarity index 100% rename from auth/assets/custom-icons/icons/simplelogin.svg rename to mobile/apps/auth/assets/custom-icons/icons/simplelogin.svg diff --git a/auth/assets/custom-icons/icons/simplicity.svg b/mobile/apps/auth/assets/custom-icons/icons/simplicity.svg similarity index 100% rename from auth/assets/custom-icons/icons/simplicity.svg rename to mobile/apps/auth/assets/custom-icons/icons/simplicity.svg diff --git a/auth/assets/custom-icons/icons/sipgate.svg b/mobile/apps/auth/assets/custom-icons/icons/sipgate.svg similarity index 100% rename from auth/assets/custom-icons/icons/sipgate.svg rename to mobile/apps/auth/assets/custom-icons/icons/sipgate.svg diff --git a/auth/assets/custom-icons/icons/skiff.svg b/mobile/apps/auth/assets/custom-icons/icons/skiff.svg similarity index 100% rename from auth/assets/custom-icons/icons/skiff.svg rename to mobile/apps/auth/assets/custom-icons/icons/skiff.svg diff --git a/auth/assets/custom-icons/icons/skinport.svg b/mobile/apps/auth/assets/custom-icons/icons/skinport.svg similarity index 100% rename from auth/assets/custom-icons/icons/skinport.svg rename to mobile/apps/auth/assets/custom-icons/icons/skinport.svg diff --git a/auth/assets/custom-icons/icons/sms_pool_net.svg b/mobile/apps/auth/assets/custom-icons/icons/sms_pool_net.svg similarity index 100% rename from auth/assets/custom-icons/icons/sms_pool_net.svg rename to mobile/apps/auth/assets/custom-icons/icons/sms_pool_net.svg diff --git a/auth/assets/custom-icons/icons/smtp2go.svg b/mobile/apps/auth/assets/custom-icons/icons/smtp2go.svg similarity index 100% rename from auth/assets/custom-icons/icons/smtp2go.svg rename to mobile/apps/auth/assets/custom-icons/icons/smtp2go.svg diff --git a/auth/assets/custom-icons/icons/snapchat.svg b/mobile/apps/auth/assets/custom-icons/icons/snapchat.svg similarity index 100% rename from auth/assets/custom-icons/icons/snapchat.svg rename to mobile/apps/auth/assets/custom-icons/icons/snapchat.svg diff --git a/auth/assets/custom-icons/icons/spacehey.svg b/mobile/apps/auth/assets/custom-icons/icons/spacehey.svg similarity index 100% rename from auth/assets/custom-icons/icons/spacehey.svg rename to mobile/apps/auth/assets/custom-icons/icons/spacehey.svg diff --git a/auth/assets/custom-icons/icons/standardnotes.svg b/mobile/apps/auth/assets/custom-icons/icons/standardnotes.svg similarity index 100% rename from auth/assets/custom-icons/icons/standardnotes.svg rename to mobile/apps/auth/assets/custom-icons/icons/standardnotes.svg diff --git a/auth/assets/custom-icons/icons/starbreeze.svg b/mobile/apps/auth/assets/custom-icons/icons/starbreeze.svg similarity index 100% rename from auth/assets/custom-icons/icons/starbreeze.svg rename to mobile/apps/auth/assets/custom-icons/icons/starbreeze.svg diff --git a/auth/assets/custom-icons/icons/strato.svg b/mobile/apps/auth/assets/custom-icons/icons/strato.svg similarity index 100% rename from auth/assets/custom-icons/icons/strato.svg rename to mobile/apps/auth/assets/custom-icons/icons/strato.svg diff --git a/auth/assets/custom-icons/icons/surfshark.svg b/mobile/apps/auth/assets/custom-icons/icons/surfshark.svg similarity index 100% rename from auth/assets/custom-icons/icons/surfshark.svg rename to mobile/apps/auth/assets/custom-icons/icons/surfshark.svg diff --git a/auth/assets/custom-icons/icons/synology_dsm.svg b/mobile/apps/auth/assets/custom-icons/icons/synology_dsm.svg similarity index 100% rename from auth/assets/custom-icons/icons/synology_dsm.svg rename to mobile/apps/auth/assets/custom-icons/icons/synology_dsm.svg diff --git a/auth/assets/custom-icons/icons/t-mobile.svg b/mobile/apps/auth/assets/custom-icons/icons/t-mobile.svg similarity index 100% rename from auth/assets/custom-icons/icons/t-mobile.svg rename to mobile/apps/auth/assets/custom-icons/icons/t-mobile.svg diff --git a/auth/assets/custom-icons/icons/tcpshield.svg b/mobile/apps/auth/assets/custom-icons/icons/tcpshield.svg similarity index 100% rename from auth/assets/custom-icons/icons/tcpshield.svg rename to mobile/apps/auth/assets/custom-icons/icons/tcpshield.svg diff --git a/auth/assets/custom-icons/icons/tebex.svg b/mobile/apps/auth/assets/custom-icons/icons/tebex.svg similarity index 100% rename from auth/assets/custom-icons/icons/tebex.svg rename to mobile/apps/auth/assets/custom-icons/icons/tebex.svg diff --git a/auth/assets/custom-icons/icons/techlore.svg b/mobile/apps/auth/assets/custom-icons/icons/techlore.svg similarity index 100% rename from auth/assets/custom-icons/icons/techlore.svg rename to mobile/apps/auth/assets/custom-icons/icons/techlore.svg diff --git a/auth/assets/custom-icons/icons/teleport.svg b/mobile/apps/auth/assets/custom-icons/icons/teleport.svg similarity index 100% rename from auth/assets/custom-icons/icons/teleport.svg rename to mobile/apps/auth/assets/custom-icons/icons/teleport.svg diff --git a/auth/assets/custom-icons/icons/tencent_cloud.svg b/mobile/apps/auth/assets/custom-icons/icons/tencent_cloud.svg similarity index 100% rename from auth/assets/custom-icons/icons/tencent_cloud.svg rename to mobile/apps/auth/assets/custom-icons/icons/tencent_cloud.svg diff --git a/auth/assets/custom-icons/icons/terabit.svg b/mobile/apps/auth/assets/custom-icons/icons/terabit.svg similarity index 100% rename from auth/assets/custom-icons/icons/terabit.svg rename to mobile/apps/auth/assets/custom-icons/icons/terabit.svg diff --git a/auth/assets/custom-icons/icons/termius.svg b/mobile/apps/auth/assets/custom-icons/icons/termius.svg similarity index 100% rename from auth/assets/custom-icons/icons/termius.svg rename to mobile/apps/auth/assets/custom-icons/icons/termius.svg diff --git a/auth/assets/custom-icons/icons/tianyiyun.svg b/mobile/apps/auth/assets/custom-icons/icons/tianyiyun.svg similarity index 100% rename from auth/assets/custom-icons/icons/tianyiyun.svg rename to mobile/apps/auth/assets/custom-icons/icons/tianyiyun.svg diff --git a/auth/assets/custom-icons/icons/tiktok.svg b/mobile/apps/auth/assets/custom-icons/icons/tiktok.svg similarity index 100% rename from auth/assets/custom-icons/icons/tiktok.svg rename to mobile/apps/auth/assets/custom-icons/icons/tiktok.svg diff --git a/auth/assets/custom-icons/icons/titan.svg b/mobile/apps/auth/assets/custom-icons/icons/titan.svg similarity index 100% rename from auth/assets/custom-icons/icons/titan.svg rename to mobile/apps/auth/assets/custom-icons/icons/titan.svg diff --git a/auth/assets/custom-icons/icons/torguard.svg b/mobile/apps/auth/assets/custom-icons/icons/torguard.svg similarity index 100% rename from auth/assets/custom-icons/icons/torguard.svg rename to mobile/apps/auth/assets/custom-icons/icons/torguard.svg diff --git a/auth/assets/custom-icons/icons/toshl_finance.svg b/mobile/apps/auth/assets/custom-icons/icons/toshl_finance.svg similarity index 100% rename from auth/assets/custom-icons/icons/toshl_finance.svg rename to mobile/apps/auth/assets/custom-icons/icons/toshl_finance.svg diff --git a/auth/assets/custom-icons/icons/trading212.svg b/mobile/apps/auth/assets/custom-icons/icons/trading212.svg similarity index 100% rename from auth/assets/custom-icons/icons/trading212.svg rename to mobile/apps/auth/assets/custom-icons/icons/trading212.svg diff --git a/auth/assets/custom-icons/icons/tradingview.svg b/mobile/apps/auth/assets/custom-icons/icons/tradingview.svg similarity index 100% rename from auth/assets/custom-icons/icons/tradingview.svg rename to mobile/apps/auth/assets/custom-icons/icons/tradingview.svg diff --git a/auth/assets/custom-icons/icons/transip.svg b/mobile/apps/auth/assets/custom-icons/icons/transip.svg similarity index 100% rename from auth/assets/custom-icons/icons/transip.svg rename to mobile/apps/auth/assets/custom-icons/icons/transip.svg diff --git a/auth/assets/custom-icons/icons/tresorit.svg b/mobile/apps/auth/assets/custom-icons/icons/tresorit.svg similarity index 100% rename from auth/assets/custom-icons/icons/tresorit.svg rename to mobile/apps/auth/assets/custom-icons/icons/tresorit.svg diff --git a/auth/assets/custom-icons/icons/troweprice.svg b/mobile/apps/auth/assets/custom-icons/icons/troweprice.svg similarity index 100% rename from auth/assets/custom-icons/icons/troweprice.svg rename to mobile/apps/auth/assets/custom-icons/icons/troweprice.svg diff --git a/auth/assets/custom-icons/icons/tweakers.svg b/mobile/apps/auth/assets/custom-icons/icons/tweakers.svg similarity index 100% rename from auth/assets/custom-icons/icons/tweakers.svg rename to mobile/apps/auth/assets/custom-icons/icons/tweakers.svg diff --git a/auth/assets/custom-icons/icons/twingate.svg b/mobile/apps/auth/assets/custom-icons/icons/twingate.svg similarity index 100% rename from auth/assets/custom-icons/icons/twingate.svg rename to mobile/apps/auth/assets/custom-icons/icons/twingate.svg diff --git a/auth/assets/custom-icons/icons/twitch.svg b/mobile/apps/auth/assets/custom-icons/icons/twitch.svg similarity index 100% rename from auth/assets/custom-icons/icons/twitch.svg rename to mobile/apps/auth/assets/custom-icons/icons/twitch.svg diff --git a/auth/assets/custom-icons/icons/ubiquiti.svg b/mobile/apps/auth/assets/custom-icons/icons/ubiquiti.svg similarity index 100% rename from auth/assets/custom-icons/icons/ubiquiti.svg rename to mobile/apps/auth/assets/custom-icons/icons/ubiquiti.svg diff --git a/auth/assets/custom-icons/icons/ubisoft.svg b/mobile/apps/auth/assets/custom-icons/icons/ubisoft.svg similarity index 100% rename from auth/assets/custom-icons/icons/ubisoft.svg rename to mobile/apps/auth/assets/custom-icons/icons/ubisoft.svg diff --git a/auth/assets/custom-icons/icons/ubuntu_one.svg b/mobile/apps/auth/assets/custom-icons/icons/ubuntu_one.svg similarity index 100% rename from auth/assets/custom-icons/icons/ubuntu_one.svg rename to mobile/apps/auth/assets/custom-icons/icons/ubuntu_one.svg diff --git a/auth/assets/custom-icons/icons/unity.svg b/mobile/apps/auth/assets/custom-icons/icons/unity.svg similarity index 100% rename from auth/assets/custom-icons/icons/unity.svg rename to mobile/apps/auth/assets/custom-icons/icons/unity.svg diff --git a/auth/assets/custom-icons/icons/uollet.svg b/mobile/apps/auth/assets/custom-icons/icons/uollet.svg similarity index 100% rename from auth/assets/custom-icons/icons/uollet.svg rename to mobile/apps/auth/assets/custom-icons/icons/uollet.svg diff --git a/auth/assets/custom-icons/icons/uphold.svg b/mobile/apps/auth/assets/custom-icons/icons/uphold.svg similarity index 100% rename from auth/assets/custom-icons/icons/uphold.svg rename to mobile/apps/auth/assets/custom-icons/icons/uphold.svg diff --git a/auth/assets/custom-icons/icons/upstox.svg b/mobile/apps/auth/assets/custom-icons/icons/upstox.svg similarity index 100% rename from auth/assets/custom-icons/icons/upstox.svg rename to mobile/apps/auth/assets/custom-icons/icons/upstox.svg diff --git a/auth/assets/custom-icons/icons/us_mobile.svg b/mobile/apps/auth/assets/custom-icons/icons/us_mobile.svg similarity index 100% rename from auth/assets/custom-icons/icons/us_mobile.svg rename to mobile/apps/auth/assets/custom-icons/icons/us_mobile.svg diff --git a/auth/assets/custom-icons/icons/vikunja.svg b/mobile/apps/auth/assets/custom-icons/icons/vikunja.svg similarity index 100% rename from auth/assets/custom-icons/icons/vikunja.svg rename to mobile/apps/auth/assets/custom-icons/icons/vikunja.svg diff --git a/auth/assets/custom-icons/icons/volcengine.svg b/mobile/apps/auth/assets/custom-icons/icons/volcengine.svg similarity index 100% rename from auth/assets/custom-icons/icons/volcengine.svg rename to mobile/apps/auth/assets/custom-icons/icons/volcengine.svg diff --git a/auth/assets/custom-icons/icons/wargamingnet.svg b/mobile/apps/auth/assets/custom-icons/icons/wargamingnet.svg similarity index 100% rename from auth/assets/custom-icons/icons/wargamingnet.svg rename to mobile/apps/auth/assets/custom-icons/icons/wargamingnet.svg diff --git a/auth/assets/custom-icons/icons/warner_bros.svg b/mobile/apps/auth/assets/custom-icons/icons/warner_bros.svg similarity index 100% rename from auth/assets/custom-icons/icons/warner_bros.svg rename to mobile/apps/auth/assets/custom-icons/icons/warner_bros.svg diff --git a/auth/assets/custom-icons/icons/wca.svg b/mobile/apps/auth/assets/custom-icons/icons/wca.svg similarity index 100% rename from auth/assets/custom-icons/icons/wca.svg rename to mobile/apps/auth/assets/custom-icons/icons/wca.svg diff --git a/auth/assets/custom-icons/icons/wealthfront.svg b/mobile/apps/auth/assets/custom-icons/icons/wealthfront.svg similarity index 100% rename from auth/assets/custom-icons/icons/wealthfront.svg rename to mobile/apps/auth/assets/custom-icons/icons/wealthfront.svg diff --git a/auth/assets/custom-icons/icons/wealthsimple.svg b/mobile/apps/auth/assets/custom-icons/icons/wealthsimple.svg similarity index 100% rename from auth/assets/custom-icons/icons/wealthsimple.svg rename to mobile/apps/auth/assets/custom-icons/icons/wealthsimple.svg diff --git a/auth/assets/custom-icons/icons/web_de.svg b/mobile/apps/auth/assets/custom-icons/icons/web_de.svg similarity index 100% rename from auth/assets/custom-icons/icons/web_de.svg rename to mobile/apps/auth/assets/custom-icons/icons/web_de.svg diff --git a/auth/assets/custom-icons/icons/whmcs.svg b/mobile/apps/auth/assets/custom-icons/icons/whmcs.svg similarity index 100% rename from auth/assets/custom-icons/icons/whmcs.svg rename to mobile/apps/auth/assets/custom-icons/icons/whmcs.svg diff --git a/auth/assets/custom-icons/icons/windscribe.svg b/mobile/apps/auth/assets/custom-icons/icons/windscribe.svg similarity index 100% rename from auth/assets/custom-icons/icons/windscribe.svg rename to mobile/apps/auth/assets/custom-icons/icons/windscribe.svg diff --git a/auth/assets/custom-icons/icons/wise.svg b/mobile/apps/auth/assets/custom-icons/icons/wise.svg similarity index 100% rename from auth/assets/custom-icons/icons/wise.svg rename to mobile/apps/auth/assets/custom-icons/icons/wise.svg diff --git a/auth/assets/custom-icons/icons/wolvesville.svg b/mobile/apps/auth/assets/custom-icons/icons/wolvesville.svg similarity index 100% rename from auth/assets/custom-icons/icons/wolvesville.svg rename to mobile/apps/auth/assets/custom-icons/icons/wolvesville.svg diff --git a/auth/assets/custom-icons/icons/workflowy.svg b/mobile/apps/auth/assets/custom-icons/icons/workflowy.svg similarity index 100% rename from auth/assets/custom-icons/icons/workflowy.svg rename to mobile/apps/auth/assets/custom-icons/icons/workflowy.svg diff --git a/auth/assets/custom-icons/icons/workos.svg b/mobile/apps/auth/assets/custom-icons/icons/workos.svg similarity index 100% rename from auth/assets/custom-icons/icons/workos.svg rename to mobile/apps/auth/assets/custom-icons/icons/workos.svg diff --git a/auth/assets/custom-icons/icons/wyze.svg b/mobile/apps/auth/assets/custom-icons/icons/wyze.svg similarity index 100% rename from auth/assets/custom-icons/icons/wyze.svg rename to mobile/apps/auth/assets/custom-icons/icons/wyze.svg diff --git a/auth/assets/custom-icons/icons/xai.svg b/mobile/apps/auth/assets/custom-icons/icons/xai.svg similarity index 100% rename from auth/assets/custom-icons/icons/xai.svg rename to mobile/apps/auth/assets/custom-icons/icons/xai.svg diff --git a/auth/assets/custom-icons/icons/xbox.svg b/mobile/apps/auth/assets/custom-icons/icons/xbox.svg similarity index 100% rename from auth/assets/custom-icons/icons/xbox.svg rename to mobile/apps/auth/assets/custom-icons/icons/xbox.svg diff --git a/auth/assets/custom-icons/icons/yahoo.svg b/mobile/apps/auth/assets/custom-icons/icons/yahoo.svg similarity index 100% rename from auth/assets/custom-icons/icons/yahoo.svg rename to mobile/apps/auth/assets/custom-icons/icons/yahoo.svg diff --git a/auth/assets/custom-icons/icons/yandex.svg b/mobile/apps/auth/assets/custom-icons/icons/yandex.svg similarity index 100% rename from auth/assets/custom-icons/icons/yandex.svg rename to mobile/apps/auth/assets/custom-icons/icons/yandex.svg diff --git a/auth/assets/custom-icons/icons/ynab.svg b/mobile/apps/auth/assets/custom-icons/icons/ynab.svg similarity index 100% rename from auth/assets/custom-icons/icons/ynab.svg rename to mobile/apps/auth/assets/custom-icons/icons/ynab.svg diff --git a/auth/assets/custom-icons/icons/zitadel.svg b/mobile/apps/auth/assets/custom-icons/icons/zitadel.svg similarity index 100% rename from auth/assets/custom-icons/icons/zitadel.svg rename to mobile/apps/auth/assets/custom-icons/icons/zitadel.svg diff --git a/auth/assets/custom-icons/icons/zoom.svg b/mobile/apps/auth/assets/custom-icons/icons/zoom.svg similarity index 100% rename from auth/assets/custom-icons/icons/zoom.svg rename to mobile/apps/auth/assets/custom-icons/icons/zoom.svg diff --git a/auth/assets/discount.png b/mobile/apps/auth/assets/discount.png similarity index 100% rename from auth/assets/discount.png rename to mobile/apps/auth/assets/discount.png diff --git a/auth/assets/ente_5gb.png b/mobile/apps/auth/assets/ente_5gb.png similarity index 100% rename from auth/assets/ente_5gb.png rename to mobile/apps/auth/assets/ente_5gb.png diff --git a/auth/assets/fonts/Inter-Bold.ttf b/mobile/apps/auth/assets/fonts/Inter-Bold.ttf similarity index 100% rename from auth/assets/fonts/Inter-Bold.ttf rename to mobile/apps/auth/assets/fonts/Inter-Bold.ttf diff --git a/auth/assets/fonts/Inter-Light.ttf b/mobile/apps/auth/assets/fonts/Inter-Light.ttf similarity index 100% rename from auth/assets/fonts/Inter-Light.ttf rename to mobile/apps/auth/assets/fonts/Inter-Light.ttf diff --git a/auth/assets/fonts/Inter-Medium.ttf b/mobile/apps/auth/assets/fonts/Inter-Medium.ttf similarity index 100% rename from auth/assets/fonts/Inter-Medium.ttf rename to mobile/apps/auth/assets/fonts/Inter-Medium.ttf diff --git a/auth/assets/fonts/Inter-Regular.ttf b/mobile/apps/auth/assets/fonts/Inter-Regular.ttf similarity index 100% rename from auth/assets/fonts/Inter-Regular.ttf rename to mobile/apps/auth/assets/fonts/Inter-Regular.ttf diff --git a/auth/assets/fonts/Inter-SemiBold.ttf b/mobile/apps/auth/assets/fonts/Inter-SemiBold.ttf similarity index 100% rename from auth/assets/fonts/Inter-SemiBold.ttf rename to mobile/apps/auth/assets/fonts/Inter-SemiBold.ttf diff --git a/auth/assets/fonts/Montserrat-Bold.ttf b/mobile/apps/auth/assets/fonts/Montserrat-Bold.ttf similarity index 100% rename from auth/assets/fonts/Montserrat-Bold.ttf rename to mobile/apps/auth/assets/fonts/Montserrat-Bold.ttf diff --git a/auth/assets/generation-icons/icon-light-adaptive-bg.png b/mobile/apps/auth/assets/generation-icons/icon-light-adaptive-bg.png similarity index 100% rename from auth/assets/generation-icons/icon-light-adaptive-bg.png rename to mobile/apps/auth/assets/generation-icons/icon-light-adaptive-bg.png diff --git a/auth/assets/generation-icons/icon-light-adaptive-fg.png b/mobile/apps/auth/assets/generation-icons/icon-light-adaptive-fg.png similarity index 100% rename from auth/assets/generation-icons/icon-light-adaptive-fg.png rename to mobile/apps/auth/assets/generation-icons/icon-light-adaptive-fg.png diff --git a/auth/assets/generation-icons/icon-light.png b/mobile/apps/auth/assets/generation-icons/icon-light.png similarity index 100% rename from auth/assets/generation-icons/icon-light.png rename to mobile/apps/auth/assets/generation-icons/icon-light.png diff --git a/auth/assets/generation-icons/icon-monochrome.png b/mobile/apps/auth/assets/generation-icons/icon-monochrome.png similarity index 100% rename from auth/assets/generation-icons/icon-monochrome.png rename to mobile/apps/auth/assets/generation-icons/icon-monochrome.png diff --git a/auth/assets/icons/auth-icon.ico b/mobile/apps/auth/assets/icons/auth-icon.ico similarity index 100% rename from auth/assets/icons/auth-icon.ico rename to mobile/apps/auth/assets/icons/auth-icon.ico diff --git a/auth/assets/icons/auth-icon.png b/mobile/apps/auth/assets/icons/auth-icon.png similarity index 100% rename from auth/assets/icons/auth-icon.png rename to mobile/apps/auth/assets/icons/auth-icon.png diff --git a/auth/assets/loading_photos_background.png b/mobile/apps/auth/assets/loading_photos_background.png similarity index 100% rename from auth/assets/loading_photos_background.png rename to mobile/apps/auth/assets/loading_photos_background.png diff --git a/auth/assets/loading_photos_background_dark.png b/mobile/apps/auth/assets/loading_photos_background_dark.png similarity index 100% rename from auth/assets/loading_photos_background_dark.png rename to mobile/apps/auth/assets/loading_photos_background_dark.png diff --git a/auth/assets/rate_us.png b/mobile/apps/auth/assets/rate_us.png similarity index 100% rename from auth/assets/rate_us.png rename to mobile/apps/auth/assets/rate_us.png diff --git a/auth/assets/sheild-front-gradient.png b/mobile/apps/auth/assets/sheild-front-gradient.png similarity index 100% rename from auth/assets/sheild-front-gradient.png rename to mobile/apps/auth/assets/sheild-front-gradient.png diff --git a/auth/assets/simple-icons b/mobile/apps/auth/assets/simple-icons similarity index 100% rename from auth/assets/simple-icons rename to mobile/apps/auth/assets/simple-icons diff --git a/auth/assets/splash/splash-icon-fg-12.png b/mobile/apps/auth/assets/splash/splash-icon-fg-12.png similarity index 100% rename from auth/assets/splash/splash-icon-fg-12.png rename to mobile/apps/auth/assets/splash/splash-icon-fg-12.png diff --git a/auth/assets/splash/splash-icon-fg.png b/mobile/apps/auth/assets/splash/splash-icon-fg.png similarity index 100% rename from auth/assets/splash/splash-icon-fg.png rename to mobile/apps/auth/assets/splash/splash-icon-fg.png diff --git a/auth/assets/star_us.png b/mobile/apps/auth/assets/star_us.png similarity index 100% rename from auth/assets/star_us.png rename to mobile/apps/auth/assets/star_us.png diff --git a/auth/assets/svg/button-tint.svg b/mobile/apps/auth/assets/svg/button-tint.svg similarity index 100% rename from auth/assets/svg/button-tint.svg rename to mobile/apps/auth/assets/svg/button-tint.svg diff --git a/auth/assets/svg/pin-active.svg b/mobile/apps/auth/assets/svg/pin-active.svg similarity index 100% rename from auth/assets/svg/pin-active.svg rename to mobile/apps/auth/assets/svg/pin-active.svg diff --git a/auth/assets/svg/pin-card.svg b/mobile/apps/auth/assets/svg/pin-card.svg similarity index 100% rename from auth/assets/svg/pin-card.svg rename to mobile/apps/auth/assets/svg/pin-card.svg diff --git a/auth/assets/svg/pin-inactive.svg b/mobile/apps/auth/assets/svg/pin-inactive.svg similarity index 100% rename from auth/assets/svg/pin-inactive.svg rename to mobile/apps/auth/assets/svg/pin-inactive.svg diff --git a/auth/assets/wallet-front-gradient.png b/mobile/apps/auth/assets/wallet-front-gradient.png similarity index 100% rename from auth/assets/wallet-front-gradient.png rename to mobile/apps/auth/assets/wallet-front-gradient.png diff --git a/auth/coverage/lcov.info b/mobile/apps/auth/coverage/lcov.info similarity index 100% rename from auth/coverage/lcov.info rename to mobile/apps/auth/coverage/lcov.info diff --git a/auth/crowdin.yml b/mobile/apps/auth/crowdin.yml similarity index 100% rename from auth/crowdin.yml rename to mobile/apps/auth/crowdin.yml diff --git a/auth/devtools_options.yaml b/mobile/apps/auth/devtools_options.yaml similarity index 100% rename from auth/devtools_options.yaml rename to mobile/apps/auth/devtools_options.yaml diff --git a/auth/distribute_options.yaml b/mobile/apps/auth/distribute_options.yaml similarity index 100% rename from auth/distribute_options.yaml rename to mobile/apps/auth/distribute_options.yaml diff --git a/auth/docs/README.md b/mobile/apps/auth/docs/README.md similarity index 100% rename from auth/docs/README.md rename to mobile/apps/auth/docs/README.md diff --git a/auth/docs/adding-icons.md b/mobile/apps/auth/docs/adding-icons.md similarity index 100% rename from auth/docs/adding-icons.md rename to mobile/apps/auth/docs/adding-icons.md diff --git a/auth/docs/localization.md b/mobile/apps/auth/docs/localization.md similarity index 100% rename from auth/docs/localization.md rename to mobile/apps/auth/docs/localization.md diff --git a/auth/docs/release.md b/mobile/apps/auth/docs/release.md similarity index 100% rename from auth/docs/release.md rename to mobile/apps/auth/docs/release.md diff --git a/auth/docs/vscode/extensions.json b/mobile/apps/auth/docs/vscode/extensions.json similarity index 100% rename from auth/docs/vscode/extensions.json rename to mobile/apps/auth/docs/vscode/extensions.json diff --git a/auth/docs/vscode/launch.json b/mobile/apps/auth/docs/vscode/launch.json similarity index 100% rename from auth/docs/vscode/launch.json rename to mobile/apps/auth/docs/vscode/launch.json diff --git a/auth/fastlane/metadata/android/en-US/changelogs/23.txt b/mobile/apps/auth/fastlane/metadata/android/en-US/changelogs/23.txt similarity index 100% rename from auth/fastlane/metadata/android/en-US/changelogs/23.txt rename to mobile/apps/auth/fastlane/metadata/android/en-US/changelogs/23.txt diff --git a/auth/fastlane/metadata/android/en-US/changelogs/39.txt b/mobile/apps/auth/fastlane/metadata/android/en-US/changelogs/39.txt similarity index 100% rename from auth/fastlane/metadata/android/en-US/changelogs/39.txt rename to mobile/apps/auth/fastlane/metadata/android/en-US/changelogs/39.txt diff --git a/auth/fastlane/metadata/android/en-US/full_description.txt b/mobile/apps/auth/fastlane/metadata/android/en-US/full_description.txt similarity index 100% rename from auth/fastlane/metadata/android/en-US/full_description.txt rename to mobile/apps/auth/fastlane/metadata/android/en-US/full_description.txt diff --git a/auth/fastlane/metadata/android/en-US/images/icon.png b/mobile/apps/auth/fastlane/metadata/android/en-US/images/icon.png similarity index 100% rename from auth/fastlane/metadata/android/en-US/images/icon.png rename to mobile/apps/auth/fastlane/metadata/android/en-US/images/icon.png diff --git a/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png similarity index 100% rename from auth/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png rename to mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png diff --git a/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png similarity index 100% rename from auth/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png rename to mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png diff --git a/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png similarity index 100% rename from auth/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png rename to mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png diff --git a/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png similarity index 100% rename from auth/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png rename to mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png diff --git a/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png similarity index 100% rename from auth/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png rename to mobile/apps/auth/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png diff --git a/auth/fastlane/metadata/android/en-US/short_description.txt b/mobile/apps/auth/fastlane/metadata/android/en-US/short_description.txt similarity index 100% rename from auth/fastlane/metadata/android/en-US/short_description.txt rename to mobile/apps/auth/fastlane/metadata/android/en-US/short_description.txt diff --git a/auth/fastlane/metadata/android/en-US/title.txt b/mobile/apps/auth/fastlane/metadata/android/en-US/title.txt similarity index 100% rename from auth/fastlane/metadata/android/en-US/title.txt rename to mobile/apps/auth/fastlane/metadata/android/en-US/title.txt diff --git a/auth/fdroid_flutter_icons.yaml b/mobile/apps/auth/fdroid_flutter_icons.yaml similarity index 100% rename from auth/fdroid_flutter_icons.yaml rename to mobile/apps/auth/fdroid_flutter_icons.yaml diff --git a/auth/flutter b/mobile/apps/auth/flutter similarity index 100% rename from auth/flutter rename to mobile/apps/auth/flutter diff --git a/auth/fonts/Inter-Bold.ttf b/mobile/apps/auth/fonts/Inter-Bold.ttf similarity index 100% rename from auth/fonts/Inter-Bold.ttf rename to mobile/apps/auth/fonts/Inter-Bold.ttf diff --git a/auth/fonts/Inter-Light.ttf b/mobile/apps/auth/fonts/Inter-Light.ttf similarity index 100% rename from auth/fonts/Inter-Light.ttf rename to mobile/apps/auth/fonts/Inter-Light.ttf diff --git a/auth/fonts/Inter-Medium.ttf b/mobile/apps/auth/fonts/Inter-Medium.ttf similarity index 100% rename from auth/fonts/Inter-Medium.ttf rename to mobile/apps/auth/fonts/Inter-Medium.ttf diff --git a/auth/fonts/Inter-Regular.ttf b/mobile/apps/auth/fonts/Inter-Regular.ttf similarity index 100% rename from auth/fonts/Inter-Regular.ttf rename to mobile/apps/auth/fonts/Inter-Regular.ttf diff --git a/auth/fonts/Inter-SemiBold.ttf b/mobile/apps/auth/fonts/Inter-SemiBold.ttf similarity index 100% rename from auth/fonts/Inter-SemiBold.ttf rename to mobile/apps/auth/fonts/Inter-SemiBold.ttf diff --git a/auth/fonts/Montserrat-Bold.ttf b/mobile/apps/auth/fonts/Montserrat-Bold.ttf similarity index 100% rename from auth/fonts/Montserrat-Bold.ttf rename to mobile/apps/auth/fonts/Montserrat-Bold.ttf diff --git a/auth/ios/.gitignore b/mobile/apps/auth/ios/.gitignore similarity index 100% rename from auth/ios/.gitignore rename to mobile/apps/auth/ios/.gitignore diff --git a/auth/ios/Flutter/AppFrameworkInfo.plist b/mobile/apps/auth/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from auth/ios/Flutter/AppFrameworkInfo.plist rename to mobile/apps/auth/ios/Flutter/AppFrameworkInfo.plist diff --git a/auth/ios/Flutter/Debug.xcconfig b/mobile/apps/auth/ios/Flutter/Debug.xcconfig similarity index 100% rename from auth/ios/Flutter/Debug.xcconfig rename to mobile/apps/auth/ios/Flutter/Debug.xcconfig diff --git a/auth/ios/Flutter/Release.xcconfig b/mobile/apps/auth/ios/Flutter/Release.xcconfig similarity index 100% rename from auth/ios/Flutter/Release.xcconfig rename to mobile/apps/auth/ios/Flutter/Release.xcconfig diff --git a/auth/ios/Podfile b/mobile/apps/auth/ios/Podfile similarity index 100% rename from auth/ios/Podfile rename to mobile/apps/auth/ios/Podfile diff --git a/auth/ios/Podfile.lock b/mobile/apps/auth/ios/Podfile.lock similarity index 100% rename from auth/ios/Podfile.lock rename to mobile/apps/auth/ios/Podfile.lock diff --git a/auth/ios/Runner.xcodeproj/project.pbxproj b/mobile/apps/auth/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from auth/ios/Runner.xcodeproj/project.pbxproj rename to mobile/apps/auth/ios/Runner.xcodeproj/project.pbxproj diff --git a/auth/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mobile/apps/auth/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from auth/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to mobile/apps/auth/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mobile/apps/auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/apps/auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to mobile/apps/auth/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/apps/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to mobile/apps/auth/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/auth/ios/Runner.xcworkspace/contents.xcworkspacedata b/mobile/apps/auth/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from auth/ios/Runner.xcworkspace/contents.xcworkspacedata rename to mobile/apps/auth/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/auth/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/auth/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from auth/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mobile/apps/auth/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/auth/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/mobile/apps/auth/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings similarity index 100% rename from auth/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings rename to mobile/apps/auth/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings diff --git a/auth/ios/Runner/AppDelegate.swift b/mobile/apps/auth/ios/Runner/AppDelegate.swift similarity index 100% rename from auth/ios/Runner/AppDelegate.swift rename to mobile/apps/auth/ios/Runner/AppDelegate.swift diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Dark mode-1024@1x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Dark mode-1024@1x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Dark mode-1024@1x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Dark mode-1024@1x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@1x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-50x50@2x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@1x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-57x57@2x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@1x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-72x72@2x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/ItunesArtwork@2x.png diff --git a/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Tinted icon-1024@1x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Tinted icon-1024@1x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Tinted icon-1024@1x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/AppIcon.appiconset/Tinted icon-1024@1x.png diff --git a/auth/ios/Runner/Assets.xcassets/Contents.json b/mobile/apps/auth/ios/Runner/Assets.xcassets/Contents.json similarity index 100% rename from auth/ios/Runner/Assets.xcassets/Contents.json rename to mobile/apps/auth/ios/Runner/Assets.xcassets/Contents.json diff --git a/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/Contents.json diff --git a/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/background.png diff --git a/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchBackground.imageset/darkbackground.png diff --git a/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to mobile/apps/auth/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/auth/ios/Runner/Base.lproj/LaunchScreen.storyboard b/mobile/apps/auth/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from auth/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to mobile/apps/auth/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/auth/ios/Runner/Base.lproj/Main.storyboard b/mobile/apps/auth/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from auth/ios/Runner/Base.lproj/Main.storyboard rename to mobile/apps/auth/ios/Runner/Base.lproj/Main.storyboard diff --git a/auth/ios/Runner/Info.plist b/mobile/apps/auth/ios/Runner/Info.plist similarity index 100% rename from auth/ios/Runner/Info.plist rename to mobile/apps/auth/ios/Runner/Info.plist diff --git a/auth/ios/Runner/Runner-Bridging-Header.h b/mobile/apps/auth/ios/Runner/Runner-Bridging-Header.h similarity index 100% rename from auth/ios/Runner/Runner-Bridging-Header.h rename to mobile/apps/auth/ios/Runner/Runner-Bridging-Header.h diff --git a/auth/ios/ci_scripts/ci_post_clone.sh b/mobile/apps/auth/ios/ci_scripts/ci_post_clone.sh similarity index 100% rename from auth/ios/ci_scripts/ci_post_clone.sh rename to mobile/apps/auth/ios/ci_scripts/ci_post_clone.sh diff --git a/auth/l10n.yaml b/mobile/apps/auth/l10n.yaml similarity index 100% rename from auth/l10n.yaml rename to mobile/apps/auth/l10n.yaml diff --git a/auth/lib/app/app.dart b/mobile/apps/auth/lib/app/app.dart similarity index 100% rename from auth/lib/app/app.dart rename to mobile/apps/auth/lib/app/app.dart diff --git a/auth/lib/app/view/app.dart b/mobile/apps/auth/lib/app/view/app.dart similarity index 100% rename from auth/lib/app/view/app.dart rename to mobile/apps/auth/lib/app/view/app.dart diff --git a/auth/lib/bootstrap.dart b/mobile/apps/auth/lib/bootstrap.dart similarity index 100% rename from auth/lib/bootstrap.dart rename to mobile/apps/auth/lib/bootstrap.dart diff --git a/auth/lib/core/configuration.dart b/mobile/apps/auth/lib/core/configuration.dart similarity index 100% rename from auth/lib/core/configuration.dart rename to mobile/apps/auth/lib/core/configuration.dart diff --git a/auth/lib/core/constants.dart b/mobile/apps/auth/lib/core/constants.dart similarity index 100% rename from auth/lib/core/constants.dart rename to mobile/apps/auth/lib/core/constants.dart diff --git a/auth/lib/core/errors.dart b/mobile/apps/auth/lib/core/errors.dart similarity index 100% rename from auth/lib/core/errors.dart rename to mobile/apps/auth/lib/core/errors.dart diff --git a/auth/lib/core/event_bus.dart b/mobile/apps/auth/lib/core/event_bus.dart similarity index 100% rename from auth/lib/core/event_bus.dart rename to mobile/apps/auth/lib/core/event_bus.dart diff --git a/auth/lib/core/logging/super_logging.dart b/mobile/apps/auth/lib/core/logging/super_logging.dart similarity index 100% rename from auth/lib/core/logging/super_logging.dart rename to mobile/apps/auth/lib/core/logging/super_logging.dart diff --git a/auth/lib/core/logging/tunneled_transport.dart b/mobile/apps/auth/lib/core/logging/tunneled_transport.dart similarity index 100% rename from auth/lib/core/logging/tunneled_transport.dart rename to mobile/apps/auth/lib/core/logging/tunneled_transport.dart diff --git a/auth/lib/core/network.dart b/mobile/apps/auth/lib/core/network.dart similarity index 100% rename from auth/lib/core/network.dart rename to mobile/apps/auth/lib/core/network.dart diff --git a/auth/lib/ente_theme_data.dart b/mobile/apps/auth/lib/ente_theme_data.dart similarity index 100% rename from auth/lib/ente_theme_data.dart rename to mobile/apps/auth/lib/ente_theme_data.dart diff --git a/auth/lib/events/codes_updated_event.dart b/mobile/apps/auth/lib/events/codes_updated_event.dart similarity index 100% rename from auth/lib/events/codes_updated_event.dart rename to mobile/apps/auth/lib/events/codes_updated_event.dart diff --git a/auth/lib/events/endpoint_updated_event.dart b/mobile/apps/auth/lib/events/endpoint_updated_event.dart similarity index 100% rename from auth/lib/events/endpoint_updated_event.dart rename to mobile/apps/auth/lib/events/endpoint_updated_event.dart diff --git a/auth/lib/events/event.dart b/mobile/apps/auth/lib/events/event.dart similarity index 100% rename from auth/lib/events/event.dart rename to mobile/apps/auth/lib/events/event.dart diff --git a/auth/lib/events/icons_changed_event.dart b/mobile/apps/auth/lib/events/icons_changed_event.dart similarity index 100% rename from auth/lib/events/icons_changed_event.dart rename to mobile/apps/auth/lib/events/icons_changed_event.dart diff --git a/auth/lib/events/notification_event.dart b/mobile/apps/auth/lib/events/notification_event.dart similarity index 100% rename from auth/lib/events/notification_event.dart rename to mobile/apps/auth/lib/events/notification_event.dart diff --git a/auth/lib/events/signed_in_event.dart b/mobile/apps/auth/lib/events/signed_in_event.dart similarity index 100% rename from auth/lib/events/signed_in_event.dart rename to mobile/apps/auth/lib/events/signed_in_event.dart diff --git a/auth/lib/events/signed_out_event.dart b/mobile/apps/auth/lib/events/signed_out_event.dart similarity index 100% rename from auth/lib/events/signed_out_event.dart rename to mobile/apps/auth/lib/events/signed_out_event.dart diff --git a/auth/lib/events/trigger_logout_event.dart b/mobile/apps/auth/lib/events/trigger_logout_event.dart similarity index 100% rename from auth/lib/events/trigger_logout_event.dart rename to mobile/apps/auth/lib/events/trigger_logout_event.dart diff --git a/auth/lib/events/user_details_changed_event.dart b/mobile/apps/auth/lib/events/user_details_changed_event.dart similarity index 100% rename from auth/lib/events/user_details_changed_event.dart rename to mobile/apps/auth/lib/events/user_details_changed_event.dart diff --git a/auth/lib/gateway/authenticator.dart b/mobile/apps/auth/lib/gateway/authenticator.dart similarity index 100% rename from auth/lib/gateway/authenticator.dart rename to mobile/apps/auth/lib/gateway/authenticator.dart diff --git a/auth/lib/l10n/arb/app_ar.arb b/mobile/apps/auth/lib/l10n/arb/app_ar.arb similarity index 100% rename from auth/lib/l10n/arb/app_ar.arb rename to mobile/apps/auth/lib/l10n/arb/app_ar.arb diff --git a/auth/lib/l10n/arb/app_be.arb b/mobile/apps/auth/lib/l10n/arb/app_be.arb similarity index 100% rename from auth/lib/l10n/arb/app_be.arb rename to mobile/apps/auth/lib/l10n/arb/app_be.arb diff --git a/auth/lib/l10n/arb/app_bg.arb b/mobile/apps/auth/lib/l10n/arb/app_bg.arb similarity index 100% rename from auth/lib/l10n/arb/app_bg.arb rename to mobile/apps/auth/lib/l10n/arb/app_bg.arb diff --git a/auth/lib/l10n/arb/app_ca.arb b/mobile/apps/auth/lib/l10n/arb/app_ca.arb similarity index 100% rename from auth/lib/l10n/arb/app_ca.arb rename to mobile/apps/auth/lib/l10n/arb/app_ca.arb diff --git a/auth/lib/l10n/arb/app_cs.arb b/mobile/apps/auth/lib/l10n/arb/app_cs.arb similarity index 100% rename from auth/lib/l10n/arb/app_cs.arb rename to mobile/apps/auth/lib/l10n/arb/app_cs.arb diff --git a/auth/lib/l10n/arb/app_da.arb b/mobile/apps/auth/lib/l10n/arb/app_da.arb similarity index 100% rename from auth/lib/l10n/arb/app_da.arb rename to mobile/apps/auth/lib/l10n/arb/app_da.arb diff --git a/auth/lib/l10n/arb/app_de.arb b/mobile/apps/auth/lib/l10n/arb/app_de.arb similarity index 100% rename from auth/lib/l10n/arb/app_de.arb rename to mobile/apps/auth/lib/l10n/arb/app_de.arb diff --git a/auth/lib/l10n/arb/app_el.arb b/mobile/apps/auth/lib/l10n/arb/app_el.arb similarity index 100% rename from auth/lib/l10n/arb/app_el.arb rename to mobile/apps/auth/lib/l10n/arb/app_el.arb diff --git a/auth/lib/l10n/arb/app_en.arb b/mobile/apps/auth/lib/l10n/arb/app_en.arb similarity index 100% rename from auth/lib/l10n/arb/app_en.arb rename to mobile/apps/auth/lib/l10n/arb/app_en.arb diff --git a/auth/lib/l10n/arb/app_es.arb b/mobile/apps/auth/lib/l10n/arb/app_es.arb similarity index 100% rename from auth/lib/l10n/arb/app_es.arb rename to mobile/apps/auth/lib/l10n/arb/app_es.arb diff --git a/auth/lib/l10n/arb/app_et.arb b/mobile/apps/auth/lib/l10n/arb/app_et.arb similarity index 100% rename from auth/lib/l10n/arb/app_et.arb rename to mobile/apps/auth/lib/l10n/arb/app_et.arb diff --git a/auth/lib/l10n/arb/app_fa.arb b/mobile/apps/auth/lib/l10n/arb/app_fa.arb similarity index 100% rename from auth/lib/l10n/arb/app_fa.arb rename to mobile/apps/auth/lib/l10n/arb/app_fa.arb diff --git a/auth/lib/l10n/arb/app_fi.arb b/mobile/apps/auth/lib/l10n/arb/app_fi.arb similarity index 100% rename from auth/lib/l10n/arb/app_fi.arb rename to mobile/apps/auth/lib/l10n/arb/app_fi.arb diff --git a/auth/lib/l10n/arb/app_fr.arb b/mobile/apps/auth/lib/l10n/arb/app_fr.arb similarity index 100% rename from auth/lib/l10n/arb/app_fr.arb rename to mobile/apps/auth/lib/l10n/arb/app_fr.arb diff --git a/auth/lib/l10n/arb/app_gu.arb b/mobile/apps/auth/lib/l10n/arb/app_gu.arb similarity index 100% rename from auth/lib/l10n/arb/app_gu.arb rename to mobile/apps/auth/lib/l10n/arb/app_gu.arb diff --git a/auth/lib/l10n/arb/app_he.arb b/mobile/apps/auth/lib/l10n/arb/app_he.arb similarity index 100% rename from auth/lib/l10n/arb/app_he.arb rename to mobile/apps/auth/lib/l10n/arb/app_he.arb diff --git a/auth/lib/l10n/arb/app_hi.arb b/mobile/apps/auth/lib/l10n/arb/app_hi.arb similarity index 100% rename from auth/lib/l10n/arb/app_hi.arb rename to mobile/apps/auth/lib/l10n/arb/app_hi.arb diff --git a/auth/lib/l10n/arb/app_hu.arb b/mobile/apps/auth/lib/l10n/arb/app_hu.arb similarity index 100% rename from auth/lib/l10n/arb/app_hu.arb rename to mobile/apps/auth/lib/l10n/arb/app_hu.arb diff --git a/auth/lib/l10n/arb/app_id.arb b/mobile/apps/auth/lib/l10n/arb/app_id.arb similarity index 100% rename from auth/lib/l10n/arb/app_id.arb rename to mobile/apps/auth/lib/l10n/arb/app_id.arb diff --git a/auth/lib/l10n/arb/app_it.arb b/mobile/apps/auth/lib/l10n/arb/app_it.arb similarity index 100% rename from auth/lib/l10n/arb/app_it.arb rename to mobile/apps/auth/lib/l10n/arb/app_it.arb diff --git a/auth/lib/l10n/arb/app_ja.arb b/mobile/apps/auth/lib/l10n/arb/app_ja.arb similarity index 100% rename from auth/lib/l10n/arb/app_ja.arb rename to mobile/apps/auth/lib/l10n/arb/app_ja.arb diff --git a/auth/lib/l10n/arb/app_ka.arb b/mobile/apps/auth/lib/l10n/arb/app_ka.arb similarity index 100% rename from auth/lib/l10n/arb/app_ka.arb rename to mobile/apps/auth/lib/l10n/arb/app_ka.arb diff --git a/auth/lib/l10n/arb/app_km.arb b/mobile/apps/auth/lib/l10n/arb/app_km.arb similarity index 100% rename from auth/lib/l10n/arb/app_km.arb rename to mobile/apps/auth/lib/l10n/arb/app_km.arb diff --git a/auth/lib/l10n/arb/app_ko.arb b/mobile/apps/auth/lib/l10n/arb/app_ko.arb similarity index 100% rename from auth/lib/l10n/arb/app_ko.arb rename to mobile/apps/auth/lib/l10n/arb/app_ko.arb diff --git a/auth/lib/l10n/arb/app_ku.arb b/mobile/apps/auth/lib/l10n/arb/app_ku.arb similarity index 100% rename from auth/lib/l10n/arb/app_ku.arb rename to mobile/apps/auth/lib/l10n/arb/app_ku.arb diff --git a/auth/lib/l10n/arb/app_lt.arb b/mobile/apps/auth/lib/l10n/arb/app_lt.arb similarity index 100% rename from auth/lib/l10n/arb/app_lt.arb rename to mobile/apps/auth/lib/l10n/arb/app_lt.arb diff --git a/auth/lib/l10n/arb/app_lv.arb b/mobile/apps/auth/lib/l10n/arb/app_lv.arb similarity index 100% rename from auth/lib/l10n/arb/app_lv.arb rename to mobile/apps/auth/lib/l10n/arb/app_lv.arb diff --git a/auth/lib/l10n/arb/app_ml.arb b/mobile/apps/auth/lib/l10n/arb/app_ml.arb similarity index 100% rename from auth/lib/l10n/arb/app_ml.arb rename to mobile/apps/auth/lib/l10n/arb/app_ml.arb diff --git a/auth/lib/l10n/arb/app_nl.arb b/mobile/apps/auth/lib/l10n/arb/app_nl.arb similarity index 100% rename from auth/lib/l10n/arb/app_nl.arb rename to mobile/apps/auth/lib/l10n/arb/app_nl.arb diff --git a/auth/lib/l10n/arb/app_or.arb b/mobile/apps/auth/lib/l10n/arb/app_or.arb similarity index 100% rename from auth/lib/l10n/arb/app_or.arb rename to mobile/apps/auth/lib/l10n/arb/app_or.arb diff --git a/auth/lib/l10n/arb/app_pl.arb b/mobile/apps/auth/lib/l10n/arb/app_pl.arb similarity index 100% rename from auth/lib/l10n/arb/app_pl.arb rename to mobile/apps/auth/lib/l10n/arb/app_pl.arb diff --git a/auth/lib/l10n/arb/app_pt.arb b/mobile/apps/auth/lib/l10n/arb/app_pt.arb similarity index 100% rename from auth/lib/l10n/arb/app_pt.arb rename to mobile/apps/auth/lib/l10n/arb/app_pt.arb diff --git a/auth/lib/l10n/arb/app_ro.arb b/mobile/apps/auth/lib/l10n/arb/app_ro.arb similarity index 100% rename from auth/lib/l10n/arb/app_ro.arb rename to mobile/apps/auth/lib/l10n/arb/app_ro.arb diff --git a/auth/lib/l10n/arb/app_ru.arb b/mobile/apps/auth/lib/l10n/arb/app_ru.arb similarity index 100% rename from auth/lib/l10n/arb/app_ru.arb rename to mobile/apps/auth/lib/l10n/arb/app_ru.arb diff --git a/auth/lib/l10n/arb/app_sk.arb b/mobile/apps/auth/lib/l10n/arb/app_sk.arb similarity index 100% rename from auth/lib/l10n/arb/app_sk.arb rename to mobile/apps/auth/lib/l10n/arb/app_sk.arb diff --git a/auth/lib/l10n/arb/app_sl.arb b/mobile/apps/auth/lib/l10n/arb/app_sl.arb similarity index 100% rename from auth/lib/l10n/arb/app_sl.arb rename to mobile/apps/auth/lib/l10n/arb/app_sl.arb diff --git a/auth/lib/l10n/arb/app_sr.arb b/mobile/apps/auth/lib/l10n/arb/app_sr.arb similarity index 100% rename from auth/lib/l10n/arb/app_sr.arb rename to mobile/apps/auth/lib/l10n/arb/app_sr.arb diff --git a/auth/lib/l10n/arb/app_sv.arb b/mobile/apps/auth/lib/l10n/arb/app_sv.arb similarity index 100% rename from auth/lib/l10n/arb/app_sv.arb rename to mobile/apps/auth/lib/l10n/arb/app_sv.arb diff --git a/auth/lib/l10n/arb/app_ta.arb b/mobile/apps/auth/lib/l10n/arb/app_ta.arb similarity index 100% rename from auth/lib/l10n/arb/app_ta.arb rename to mobile/apps/auth/lib/l10n/arb/app_ta.arb diff --git a/auth/lib/l10n/arb/app_te.arb b/mobile/apps/auth/lib/l10n/arb/app_te.arb similarity index 100% rename from auth/lib/l10n/arb/app_te.arb rename to mobile/apps/auth/lib/l10n/arb/app_te.arb diff --git a/auth/lib/l10n/arb/app_th.arb b/mobile/apps/auth/lib/l10n/arb/app_th.arb similarity index 100% rename from auth/lib/l10n/arb/app_th.arb rename to mobile/apps/auth/lib/l10n/arb/app_th.arb diff --git a/auth/lib/l10n/arb/app_ti.arb b/mobile/apps/auth/lib/l10n/arb/app_ti.arb similarity index 100% rename from auth/lib/l10n/arb/app_ti.arb rename to mobile/apps/auth/lib/l10n/arb/app_ti.arb diff --git a/auth/lib/l10n/arb/app_tr.arb b/mobile/apps/auth/lib/l10n/arb/app_tr.arb similarity index 100% rename from auth/lib/l10n/arb/app_tr.arb rename to mobile/apps/auth/lib/l10n/arb/app_tr.arb diff --git a/auth/lib/l10n/arb/app_uk.arb b/mobile/apps/auth/lib/l10n/arb/app_uk.arb similarity index 100% rename from auth/lib/l10n/arb/app_uk.arb rename to mobile/apps/auth/lib/l10n/arb/app_uk.arb diff --git a/auth/lib/l10n/arb/app_vi.arb b/mobile/apps/auth/lib/l10n/arb/app_vi.arb similarity index 100% rename from auth/lib/l10n/arb/app_vi.arb rename to mobile/apps/auth/lib/l10n/arb/app_vi.arb diff --git a/auth/lib/l10n/arb/app_zh.arb b/mobile/apps/auth/lib/l10n/arb/app_zh.arb similarity index 100% rename from auth/lib/l10n/arb/app_zh.arb rename to mobile/apps/auth/lib/l10n/arb/app_zh.arb diff --git a/auth/lib/l10n/arb/app_zh_CN.arb b/mobile/apps/auth/lib/l10n/arb/app_zh_CN.arb similarity index 100% rename from auth/lib/l10n/arb/app_zh_CN.arb rename to mobile/apps/auth/lib/l10n/arb/app_zh_CN.arb diff --git a/auth/lib/l10n/arb/app_zh_TW.arb b/mobile/apps/auth/lib/l10n/arb/app_zh_TW.arb similarity index 100% rename from auth/lib/l10n/arb/app_zh_TW.arb rename to mobile/apps/auth/lib/l10n/arb/app_zh_TW.arb diff --git a/auth/lib/l10n/l10n.dart b/mobile/apps/auth/lib/l10n/l10n.dart similarity index 100% rename from auth/lib/l10n/l10n.dart rename to mobile/apps/auth/lib/l10n/l10n.dart diff --git a/auth/lib/locale.dart b/mobile/apps/auth/lib/locale.dart similarity index 100% rename from auth/lib/locale.dart rename to mobile/apps/auth/lib/locale.dart diff --git a/auth/lib/main.dart b/mobile/apps/auth/lib/main.dart similarity index 100% rename from auth/lib/main.dart rename to mobile/apps/auth/lib/main.dart diff --git a/auth/lib/main_development.dart b/mobile/apps/auth/lib/main_development.dart similarity index 100% rename from auth/lib/main_development.dart rename to mobile/apps/auth/lib/main_development.dart diff --git a/auth/lib/main_production.dart b/mobile/apps/auth/lib/main_production.dart similarity index 100% rename from auth/lib/main_production.dart rename to mobile/apps/auth/lib/main_production.dart diff --git a/auth/lib/main_staging.dart b/mobile/apps/auth/lib/main_staging.dart similarity index 100% rename from auth/lib/main_staging.dart rename to mobile/apps/auth/lib/main_staging.dart diff --git a/auth/lib/models/account/two_factor.dart b/mobile/apps/auth/lib/models/account/two_factor.dart similarity index 100% rename from auth/lib/models/account/two_factor.dart rename to mobile/apps/auth/lib/models/account/two_factor.dart diff --git a/auth/lib/models/all_icon_data.dart b/mobile/apps/auth/lib/models/all_icon_data.dart similarity index 100% rename from auth/lib/models/all_icon_data.dart rename to mobile/apps/auth/lib/models/all_icon_data.dart diff --git a/auth/lib/models/api/user/srp.dart b/mobile/apps/auth/lib/models/api/user/srp.dart similarity index 100% rename from auth/lib/models/api/user/srp.dart rename to mobile/apps/auth/lib/models/api/user/srp.dart diff --git a/auth/lib/models/authenticator/auth_entity.dart b/mobile/apps/auth/lib/models/authenticator/auth_entity.dart similarity index 100% rename from auth/lib/models/authenticator/auth_entity.dart rename to mobile/apps/auth/lib/models/authenticator/auth_entity.dart diff --git a/auth/lib/models/authenticator/auth_key.dart b/mobile/apps/auth/lib/models/authenticator/auth_key.dart similarity index 100% rename from auth/lib/models/authenticator/auth_key.dart rename to mobile/apps/auth/lib/models/authenticator/auth_key.dart diff --git a/auth/lib/models/authenticator/entity_result.dart b/mobile/apps/auth/lib/models/authenticator/entity_result.dart similarity index 100% rename from auth/lib/models/authenticator/entity_result.dart rename to mobile/apps/auth/lib/models/authenticator/entity_result.dart diff --git a/auth/lib/models/authenticator/local_auth_entity.dart b/mobile/apps/auth/lib/models/authenticator/local_auth_entity.dart similarity index 100% rename from auth/lib/models/authenticator/local_auth_entity.dart rename to mobile/apps/auth/lib/models/authenticator/local_auth_entity.dart diff --git a/auth/lib/models/billing_plan.dart b/mobile/apps/auth/lib/models/billing_plan.dart similarity index 100% rename from auth/lib/models/billing_plan.dart rename to mobile/apps/auth/lib/models/billing_plan.dart diff --git a/auth/lib/models/code.dart b/mobile/apps/auth/lib/models/code.dart similarity index 100% rename from auth/lib/models/code.dart rename to mobile/apps/auth/lib/models/code.dart diff --git a/auth/lib/models/code_display.dart b/mobile/apps/auth/lib/models/code_display.dart similarity index 100% rename from auth/lib/models/code_display.dart rename to mobile/apps/auth/lib/models/code_display.dart diff --git a/auth/lib/models/delete_account.dart b/mobile/apps/auth/lib/models/delete_account.dart similarity index 100% rename from auth/lib/models/delete_account.dart rename to mobile/apps/auth/lib/models/delete_account.dart diff --git a/auth/lib/models/execution_states.dart b/mobile/apps/auth/lib/models/execution_states.dart similarity index 100% rename from auth/lib/models/execution_states.dart rename to mobile/apps/auth/lib/models/execution_states.dart diff --git a/auth/lib/models/export/ente.dart b/mobile/apps/auth/lib/models/export/ente.dart similarity index 100% rename from auth/lib/models/export/ente.dart rename to mobile/apps/auth/lib/models/export/ente.dart diff --git a/auth/lib/models/key_attributes.dart b/mobile/apps/auth/lib/models/key_attributes.dart similarity index 100% rename from auth/lib/models/key_attributes.dart rename to mobile/apps/auth/lib/models/key_attributes.dart diff --git a/auth/lib/models/key_gen_result.dart b/mobile/apps/auth/lib/models/key_gen_result.dart similarity index 100% rename from auth/lib/models/key_gen_result.dart rename to mobile/apps/auth/lib/models/key_gen_result.dart diff --git a/auth/lib/models/private_key_attributes.dart b/mobile/apps/auth/lib/models/private_key_attributes.dart similarity index 100% rename from auth/lib/models/private_key_attributes.dart rename to mobile/apps/auth/lib/models/private_key_attributes.dart diff --git a/auth/lib/models/protos/googleauth.pb.dart b/mobile/apps/auth/lib/models/protos/googleauth.pb.dart similarity index 100% rename from auth/lib/models/protos/googleauth.pb.dart rename to mobile/apps/auth/lib/models/protos/googleauth.pb.dart diff --git a/auth/lib/models/protos/googleauth.pbenum.dart b/mobile/apps/auth/lib/models/protos/googleauth.pbenum.dart similarity index 100% rename from auth/lib/models/protos/googleauth.pbenum.dart rename to mobile/apps/auth/lib/models/protos/googleauth.pbenum.dart diff --git a/auth/lib/models/protos/googleauth.pbjson.dart b/mobile/apps/auth/lib/models/protos/googleauth.pbjson.dart similarity index 100% rename from auth/lib/models/protos/googleauth.pbjson.dart rename to mobile/apps/auth/lib/models/protos/googleauth.pbjson.dart diff --git a/auth/lib/models/protos/googleauth.pbserver.dart b/mobile/apps/auth/lib/models/protos/googleauth.pbserver.dart similarity index 100% rename from auth/lib/models/protos/googleauth.pbserver.dart rename to mobile/apps/auth/lib/models/protos/googleauth.pbserver.dart diff --git a/auth/lib/models/sessions.dart b/mobile/apps/auth/lib/models/sessions.dart similarity index 100% rename from auth/lib/models/sessions.dart rename to mobile/apps/auth/lib/models/sessions.dart diff --git a/auth/lib/models/set_keys_request.dart b/mobile/apps/auth/lib/models/set_keys_request.dart similarity index 100% rename from auth/lib/models/set_keys_request.dart rename to mobile/apps/auth/lib/models/set_keys_request.dart diff --git a/auth/lib/models/set_recovery_key_request.dart b/mobile/apps/auth/lib/models/set_recovery_key_request.dart similarity index 100% rename from auth/lib/models/set_recovery_key_request.dart rename to mobile/apps/auth/lib/models/set_recovery_key_request.dart diff --git a/auth/lib/models/subscription.dart b/mobile/apps/auth/lib/models/subscription.dart similarity index 100% rename from auth/lib/models/subscription.dart rename to mobile/apps/auth/lib/models/subscription.dart diff --git a/auth/lib/models/typedefs.dart b/mobile/apps/auth/lib/models/typedefs.dart similarity index 100% rename from auth/lib/models/typedefs.dart rename to mobile/apps/auth/lib/models/typedefs.dart diff --git a/auth/lib/models/upload_url.dart b/mobile/apps/auth/lib/models/upload_url.dart similarity index 100% rename from auth/lib/models/upload_url.dart rename to mobile/apps/auth/lib/models/upload_url.dart diff --git a/auth/lib/models/user_details.dart b/mobile/apps/auth/lib/models/user_details.dart similarity index 100% rename from auth/lib/models/user_details.dart rename to mobile/apps/auth/lib/models/user_details.dart diff --git a/auth/lib/onboarding/model/tag_enums.dart b/mobile/apps/auth/lib/onboarding/model/tag_enums.dart similarity index 100% rename from auth/lib/onboarding/model/tag_enums.dart rename to mobile/apps/auth/lib/onboarding/model/tag_enums.dart diff --git a/auth/lib/onboarding/view/common/add_chip.dart b/mobile/apps/auth/lib/onboarding/view/common/add_chip.dart similarity index 100% rename from auth/lib/onboarding/view/common/add_chip.dart rename to mobile/apps/auth/lib/onboarding/view/common/add_chip.dart diff --git a/auth/lib/onboarding/view/common/add_tag.dart b/mobile/apps/auth/lib/onboarding/view/common/add_tag.dart similarity index 100% rename from auth/lib/onboarding/view/common/add_tag.dart rename to mobile/apps/auth/lib/onboarding/view/common/add_tag.dart diff --git a/auth/lib/onboarding/view/common/edit_tag.dart b/mobile/apps/auth/lib/onboarding/view/common/edit_tag.dart similarity index 100% rename from auth/lib/onboarding/view/common/edit_tag.dart rename to mobile/apps/auth/lib/onboarding/view/common/edit_tag.dart diff --git a/auth/lib/onboarding/view/common/field_label.dart b/mobile/apps/auth/lib/onboarding/view/common/field_label.dart similarity index 100% rename from auth/lib/onboarding/view/common/field_label.dart rename to mobile/apps/auth/lib/onboarding/view/common/field_label.dart diff --git a/auth/lib/onboarding/view/common/tag_chip.dart b/mobile/apps/auth/lib/onboarding/view/common/tag_chip.dart similarity index 100% rename from auth/lib/onboarding/view/common/tag_chip.dart rename to mobile/apps/auth/lib/onboarding/view/common/tag_chip.dart diff --git a/auth/lib/onboarding/view/onboarding_page.dart b/mobile/apps/auth/lib/onboarding/view/onboarding_page.dart similarity index 100% rename from auth/lib/onboarding/view/onboarding_page.dart rename to mobile/apps/auth/lib/onboarding/view/onboarding_page.dart diff --git a/auth/lib/onboarding/view/setup_enter_secret_key_page.dart b/mobile/apps/auth/lib/onboarding/view/setup_enter_secret_key_page.dart similarity index 100% rename from auth/lib/onboarding/view/setup_enter_secret_key_page.dart rename to mobile/apps/auth/lib/onboarding/view/setup_enter_secret_key_page.dart diff --git a/auth/lib/onboarding/view/view_qr_page.dart b/mobile/apps/auth/lib/onboarding/view/view_qr_page.dart similarity index 100% rename from auth/lib/onboarding/view/view_qr_page.dart rename to mobile/apps/auth/lib/onboarding/view/view_qr_page.dart diff --git a/auth/lib/services/authenticator_service.dart b/mobile/apps/auth/lib/services/authenticator_service.dart similarity index 100% rename from auth/lib/services/authenticator_service.dart rename to mobile/apps/auth/lib/services/authenticator_service.dart diff --git a/auth/lib/services/billing_service.dart b/mobile/apps/auth/lib/services/billing_service.dart similarity index 100% rename from auth/lib/services/billing_service.dart rename to mobile/apps/auth/lib/services/billing_service.dart diff --git a/auth/lib/services/deduplication_service.dart b/mobile/apps/auth/lib/services/deduplication_service.dart similarity index 100% rename from auth/lib/services/deduplication_service.dart rename to mobile/apps/auth/lib/services/deduplication_service.dart diff --git a/auth/lib/services/local_authentication_service.dart b/mobile/apps/auth/lib/services/local_authentication_service.dart similarity index 100% rename from auth/lib/services/local_authentication_service.dart rename to mobile/apps/auth/lib/services/local_authentication_service.dart diff --git a/auth/lib/services/notification_service.dart b/mobile/apps/auth/lib/services/notification_service.dart similarity index 100% rename from auth/lib/services/notification_service.dart rename to mobile/apps/auth/lib/services/notification_service.dart diff --git a/auth/lib/services/passkey_service.dart b/mobile/apps/auth/lib/services/passkey_service.dart similarity index 100% rename from auth/lib/services/passkey_service.dart rename to mobile/apps/auth/lib/services/passkey_service.dart diff --git a/auth/lib/services/preference_service.dart b/mobile/apps/auth/lib/services/preference_service.dart similarity index 100% rename from auth/lib/services/preference_service.dart rename to mobile/apps/auth/lib/services/preference_service.dart diff --git a/auth/lib/services/update_service.dart b/mobile/apps/auth/lib/services/update_service.dart similarity index 100% rename from auth/lib/services/update_service.dart rename to mobile/apps/auth/lib/services/update_service.dart diff --git a/auth/lib/services/user_service.dart b/mobile/apps/auth/lib/services/user_service.dart similarity index 100% rename from auth/lib/services/user_service.dart rename to mobile/apps/auth/lib/services/user_service.dart diff --git a/auth/lib/services/window_listener_service.dart b/mobile/apps/auth/lib/services/window_listener_service.dart similarity index 100% rename from auth/lib/services/window_listener_service.dart rename to mobile/apps/auth/lib/services/window_listener_service.dart diff --git a/auth/lib/store/authenticator_db.dart b/mobile/apps/auth/lib/store/authenticator_db.dart similarity index 100% rename from auth/lib/store/authenticator_db.dart rename to mobile/apps/auth/lib/store/authenticator_db.dart diff --git a/auth/lib/store/code_display_store.dart b/mobile/apps/auth/lib/store/code_display_store.dart similarity index 100% rename from auth/lib/store/code_display_store.dart rename to mobile/apps/auth/lib/store/code_display_store.dart diff --git a/auth/lib/store/code_store.dart b/mobile/apps/auth/lib/store/code_store.dart similarity index 100% rename from auth/lib/store/code_store.dart rename to mobile/apps/auth/lib/store/code_store.dart diff --git a/auth/lib/store/offline_authenticator_db.dart b/mobile/apps/auth/lib/store/offline_authenticator_db.dart similarity index 100% rename from auth/lib/store/offline_authenticator_db.dart rename to mobile/apps/auth/lib/store/offline_authenticator_db.dart diff --git a/auth/lib/theme/colors.dart b/mobile/apps/auth/lib/theme/colors.dart similarity index 100% rename from auth/lib/theme/colors.dart rename to mobile/apps/auth/lib/theme/colors.dart diff --git a/auth/lib/theme/effects.dart b/mobile/apps/auth/lib/theme/effects.dart similarity index 100% rename from auth/lib/theme/effects.dart rename to mobile/apps/auth/lib/theme/effects.dart diff --git a/auth/lib/theme/ente_theme.dart b/mobile/apps/auth/lib/theme/ente_theme.dart similarity index 100% rename from auth/lib/theme/ente_theme.dart rename to mobile/apps/auth/lib/theme/ente_theme.dart diff --git a/auth/lib/theme/text_style.dart b/mobile/apps/auth/lib/theme/text_style.dart similarity index 100% rename from auth/lib/theme/text_style.dart rename to mobile/apps/auth/lib/theme/text_style.dart diff --git a/auth/lib/ui/account/change_email_dialog.dart b/mobile/apps/auth/lib/ui/account/change_email_dialog.dart similarity index 100% rename from auth/lib/ui/account/change_email_dialog.dart rename to mobile/apps/auth/lib/ui/account/change_email_dialog.dart diff --git a/auth/lib/ui/account/delete_account_page.dart b/mobile/apps/auth/lib/ui/account/delete_account_page.dart similarity index 100% rename from auth/lib/ui/account/delete_account_page.dart rename to mobile/apps/auth/lib/ui/account/delete_account_page.dart diff --git a/auth/lib/ui/account/email_entry_page.dart b/mobile/apps/auth/lib/ui/account/email_entry_page.dart similarity index 100% rename from auth/lib/ui/account/email_entry_page.dart rename to mobile/apps/auth/lib/ui/account/email_entry_page.dart diff --git a/auth/lib/ui/account/login_page.dart b/mobile/apps/auth/lib/ui/account/login_page.dart similarity index 100% rename from auth/lib/ui/account/login_page.dart rename to mobile/apps/auth/lib/ui/account/login_page.dart diff --git a/auth/lib/ui/account/login_pwd_verification_page.dart b/mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart similarity index 100% rename from auth/lib/ui/account/login_pwd_verification_page.dart rename to mobile/apps/auth/lib/ui/account/login_pwd_verification_page.dart diff --git a/auth/lib/ui/account/logout_dialog.dart b/mobile/apps/auth/lib/ui/account/logout_dialog.dart similarity index 100% rename from auth/lib/ui/account/logout_dialog.dart rename to mobile/apps/auth/lib/ui/account/logout_dialog.dart diff --git a/auth/lib/ui/account/ott_verification_page.dart b/mobile/apps/auth/lib/ui/account/ott_verification_page.dart similarity index 100% rename from auth/lib/ui/account/ott_verification_page.dart rename to mobile/apps/auth/lib/ui/account/ott_verification_page.dart diff --git a/auth/lib/ui/account/password_entry_page.dart b/mobile/apps/auth/lib/ui/account/password_entry_page.dart similarity index 100% rename from auth/lib/ui/account/password_entry_page.dart rename to mobile/apps/auth/lib/ui/account/password_entry_page.dart diff --git a/auth/lib/ui/account/password_reentry_page.dart b/mobile/apps/auth/lib/ui/account/password_reentry_page.dart similarity index 100% rename from auth/lib/ui/account/password_reentry_page.dart rename to mobile/apps/auth/lib/ui/account/password_reentry_page.dart diff --git a/auth/lib/ui/account/recovery_key_page.dart b/mobile/apps/auth/lib/ui/account/recovery_key_page.dart similarity index 100% rename from auth/lib/ui/account/recovery_key_page.dart rename to mobile/apps/auth/lib/ui/account/recovery_key_page.dart diff --git a/auth/lib/ui/account/recovery_page.dart b/mobile/apps/auth/lib/ui/account/recovery_page.dart similarity index 100% rename from auth/lib/ui/account/recovery_page.dart rename to mobile/apps/auth/lib/ui/account/recovery_page.dart diff --git a/auth/lib/ui/account/request_pwd_verification_page.dart b/mobile/apps/auth/lib/ui/account/request_pwd_verification_page.dart similarity index 100% rename from auth/lib/ui/account/request_pwd_verification_page.dart rename to mobile/apps/auth/lib/ui/account/request_pwd_verification_page.dart diff --git a/auth/lib/ui/account/sessions_page.dart b/mobile/apps/auth/lib/ui/account/sessions_page.dart similarity index 100% rename from auth/lib/ui/account/sessions_page.dart rename to mobile/apps/auth/lib/ui/account/sessions_page.dart diff --git a/auth/lib/ui/algorithm_selector_widget.dart b/mobile/apps/auth/lib/ui/algorithm_selector_widget.dart similarity index 100% rename from auth/lib/ui/algorithm_selector_widget.dart rename to mobile/apps/auth/lib/ui/algorithm_selector_widget.dart diff --git a/auth/lib/ui/code_error_widget.dart b/mobile/apps/auth/lib/ui/code_error_widget.dart similarity index 100% rename from auth/lib/ui/code_error_widget.dart rename to mobile/apps/auth/lib/ui/code_error_widget.dart diff --git a/auth/lib/ui/code_timer_progress.dart b/mobile/apps/auth/lib/ui/code_timer_progress.dart similarity index 100% rename from auth/lib/ui/code_timer_progress.dart rename to mobile/apps/auth/lib/ui/code_timer_progress.dart diff --git a/auth/lib/ui/code_widget.dart b/mobile/apps/auth/lib/ui/code_widget.dart similarity index 100% rename from auth/lib/ui/code_widget.dart rename to mobile/apps/auth/lib/ui/code_widget.dart diff --git a/auth/lib/ui/common/dialogs.dart b/mobile/apps/auth/lib/ui/common/dialogs.dart similarity index 100% rename from auth/lib/ui/common/dialogs.dart rename to mobile/apps/auth/lib/ui/common/dialogs.dart diff --git a/auth/lib/ui/common/divider_with_padding.dart b/mobile/apps/auth/lib/ui/common/divider_with_padding.dart similarity index 100% rename from auth/lib/ui/common/divider_with_padding.dart rename to mobile/apps/auth/lib/ui/common/divider_with_padding.dart diff --git a/auth/lib/ui/common/dynamic_fab.dart b/mobile/apps/auth/lib/ui/common/dynamic_fab.dart similarity index 100% rename from auth/lib/ui/common/dynamic_fab.dart rename to mobile/apps/auth/lib/ui/common/dynamic_fab.dart diff --git a/auth/lib/ui/common/gradient_button.dart b/mobile/apps/auth/lib/ui/common/gradient_button.dart similarity index 100% rename from auth/lib/ui/common/gradient_button.dart rename to mobile/apps/auth/lib/ui/common/gradient_button.dart diff --git a/auth/lib/ui/common/loading_widget.dart b/mobile/apps/auth/lib/ui/common/loading_widget.dart similarity index 100% rename from auth/lib/ui/common/loading_widget.dart rename to mobile/apps/auth/lib/ui/common/loading_widget.dart diff --git a/auth/lib/ui/common/progress_dialog.dart b/mobile/apps/auth/lib/ui/common/progress_dialog.dart similarity index 100% rename from auth/lib/ui/common/progress_dialog.dart rename to mobile/apps/auth/lib/ui/common/progress_dialog.dart diff --git a/auth/lib/ui/common/report_bug.dart b/mobile/apps/auth/lib/ui/common/report_bug.dart similarity index 100% rename from auth/lib/ui/common/report_bug.dart rename to mobile/apps/auth/lib/ui/common/report_bug.dart diff --git a/auth/lib/ui/common/web_page.dart b/mobile/apps/auth/lib/ui/common/web_page.dart similarity index 100% rename from auth/lib/ui/common/web_page.dart rename to mobile/apps/auth/lib/ui/common/web_page.dart diff --git a/auth/lib/ui/components/action_sheet_widget.dart b/mobile/apps/auth/lib/ui/components/action_sheet_widget.dart similarity index 100% rename from auth/lib/ui/components/action_sheet_widget.dart rename to mobile/apps/auth/lib/ui/components/action_sheet_widget.dart diff --git a/auth/lib/ui/components/actions_bar_widget.dart b/mobile/apps/auth/lib/ui/components/actions_bar_widget.dart similarity index 100% rename from auth/lib/ui/components/actions_bar_widget.dart rename to mobile/apps/auth/lib/ui/components/actions_bar_widget.dart diff --git a/auth/lib/ui/components/banner_widget.dart b/mobile/apps/auth/lib/ui/components/banner_widget.dart similarity index 100% rename from auth/lib/ui/components/banner_widget.dart rename to mobile/apps/auth/lib/ui/components/banner_widget.dart diff --git a/auth/lib/ui/components/bottom_action_bar_widget.dart b/mobile/apps/auth/lib/ui/components/bottom_action_bar_widget.dart similarity index 100% rename from auth/lib/ui/components/bottom_action_bar_widget.dart rename to mobile/apps/auth/lib/ui/components/bottom_action_bar_widget.dart diff --git a/auth/lib/ui/components/buttons/button_widget.dart b/mobile/apps/auth/lib/ui/components/buttons/button_widget.dart similarity index 100% rename from auth/lib/ui/components/buttons/button_widget.dart rename to mobile/apps/auth/lib/ui/components/buttons/button_widget.dart diff --git a/auth/lib/ui/components/buttons/icon_button_widget.dart b/mobile/apps/auth/lib/ui/components/buttons/icon_button_widget.dart similarity index 100% rename from auth/lib/ui/components/buttons/icon_button_widget.dart rename to mobile/apps/auth/lib/ui/components/buttons/icon_button_widget.dart diff --git a/auth/lib/ui/components/captioned_text_widget.dart b/mobile/apps/auth/lib/ui/components/captioned_text_widget.dart similarity index 100% rename from auth/lib/ui/components/captioned_text_widget.dart rename to mobile/apps/auth/lib/ui/components/captioned_text_widget.dart diff --git a/auth/lib/ui/components/code_selection_actions_widget.dart b/mobile/apps/auth/lib/ui/components/code_selection_actions_widget.dart similarity index 100% rename from auth/lib/ui/components/code_selection_actions_widget.dart rename to mobile/apps/auth/lib/ui/components/code_selection_actions_widget.dart diff --git a/auth/lib/ui/components/components_constants.dart b/mobile/apps/auth/lib/ui/components/components_constants.dart similarity index 100% rename from auth/lib/ui/components/components_constants.dart rename to mobile/apps/auth/lib/ui/components/components_constants.dart diff --git a/auth/lib/ui/components/custom_icon_widget.dart b/mobile/apps/auth/lib/ui/components/custom_icon_widget.dart similarity index 100% rename from auth/lib/ui/components/custom_icon_widget.dart rename to mobile/apps/auth/lib/ui/components/custom_icon_widget.dart diff --git a/auth/lib/ui/components/dialog_widget.dart b/mobile/apps/auth/lib/ui/components/dialog_widget.dart similarity index 100% rename from auth/lib/ui/components/dialog_widget.dart rename to mobile/apps/auth/lib/ui/components/dialog_widget.dart diff --git a/auth/lib/ui/components/divider_widget.dart b/mobile/apps/auth/lib/ui/components/divider_widget.dart similarity index 100% rename from auth/lib/ui/components/divider_widget.dart rename to mobile/apps/auth/lib/ui/components/divider_widget.dart diff --git a/auth/lib/ui/components/expandable_menu_item_widget.dart b/mobile/apps/auth/lib/ui/components/expandable_menu_item_widget.dart similarity index 100% rename from auth/lib/ui/components/expandable_menu_item_widget.dart rename to mobile/apps/auth/lib/ui/components/expandable_menu_item_widget.dart diff --git a/auth/lib/ui/components/menu_item_child_widgets.dart b/mobile/apps/auth/lib/ui/components/menu_item_child_widgets.dart similarity index 100% rename from auth/lib/ui/components/menu_item_child_widgets.dart rename to mobile/apps/auth/lib/ui/components/menu_item_child_widgets.dart diff --git a/auth/lib/ui/components/menu_item_widget.dart b/mobile/apps/auth/lib/ui/components/menu_item_widget.dart similarity index 100% rename from auth/lib/ui/components/menu_item_widget.dart rename to mobile/apps/auth/lib/ui/components/menu_item_widget.dart diff --git a/auth/lib/ui/components/menu_section_description_widget.dart b/mobile/apps/auth/lib/ui/components/menu_section_description_widget.dart similarity index 100% rename from auth/lib/ui/components/menu_section_description_widget.dart rename to mobile/apps/auth/lib/ui/components/menu_section_description_widget.dart diff --git a/auth/lib/ui/components/models/button_result.dart b/mobile/apps/auth/lib/ui/components/models/button_result.dart similarity index 100% rename from auth/lib/ui/components/models/button_result.dart rename to mobile/apps/auth/lib/ui/components/models/button_result.dart diff --git a/auth/lib/ui/components/models/button_type.dart b/mobile/apps/auth/lib/ui/components/models/button_type.dart similarity index 100% rename from auth/lib/ui/components/models/button_type.dart rename to mobile/apps/auth/lib/ui/components/models/button_type.dart diff --git a/auth/lib/ui/components/models/custom_button_style.dart b/mobile/apps/auth/lib/ui/components/models/custom_button_style.dart similarity index 100% rename from auth/lib/ui/components/models/custom_button_style.dart rename to mobile/apps/auth/lib/ui/components/models/custom_button_style.dart diff --git a/auth/lib/ui/components/notification_warning_widget.dart b/mobile/apps/auth/lib/ui/components/notification_warning_widget.dart similarity index 100% rename from auth/lib/ui/components/notification_warning_widget.dart rename to mobile/apps/auth/lib/ui/components/notification_warning_widget.dart diff --git a/auth/lib/ui/components/selection_action_button.dart b/mobile/apps/auth/lib/ui/components/selection_action_button.dart similarity index 100% rename from auth/lib/ui/components/selection_action_button.dart rename to mobile/apps/auth/lib/ui/components/selection_action_button.dart diff --git a/auth/lib/ui/components/separators.dart b/mobile/apps/auth/lib/ui/components/separators.dart similarity index 100% rename from auth/lib/ui/components/separators.dart rename to mobile/apps/auth/lib/ui/components/separators.dart diff --git a/auth/lib/ui/components/text_input_widget.dart b/mobile/apps/auth/lib/ui/components/text_input_widget.dart similarity index 100% rename from auth/lib/ui/components/text_input_widget.dart rename to mobile/apps/auth/lib/ui/components/text_input_widget.dart diff --git a/auth/lib/ui/components/title_bar_title_widget.dart b/mobile/apps/auth/lib/ui/components/title_bar_title_widget.dart similarity index 100% rename from auth/lib/ui/components/title_bar_title_widget.dart rename to mobile/apps/auth/lib/ui/components/title_bar_title_widget.dart diff --git a/auth/lib/ui/components/title_bar_widget.dart b/mobile/apps/auth/lib/ui/components/title_bar_widget.dart similarity index 100% rename from auth/lib/ui/components/title_bar_widget.dart rename to mobile/apps/auth/lib/ui/components/title_bar_widget.dart diff --git a/auth/lib/ui/components/toggle_switch_widget.dart b/mobile/apps/auth/lib/ui/components/toggle_switch_widget.dart similarity index 100% rename from auth/lib/ui/components/toggle_switch_widget.dart rename to mobile/apps/auth/lib/ui/components/toggle_switch_widget.dart diff --git a/auth/lib/ui/custom_icon_page.dart b/mobile/apps/auth/lib/ui/custom_icon_page.dart similarity index 100% rename from auth/lib/ui/custom_icon_page.dart rename to mobile/apps/auth/lib/ui/custom_icon_page.dart diff --git a/auth/lib/ui/home/coach_mark_widget.dart b/mobile/apps/auth/lib/ui/home/coach_mark_widget.dart similarity index 100% rename from auth/lib/ui/home/coach_mark_widget.dart rename to mobile/apps/auth/lib/ui/home/coach_mark_widget.dart diff --git a/auth/lib/ui/home/home_empty_state.dart b/mobile/apps/auth/lib/ui/home/home_empty_state.dart similarity index 100% rename from auth/lib/ui/home/home_empty_state.dart rename to mobile/apps/auth/lib/ui/home/home_empty_state.dart diff --git a/auth/lib/ui/home/speed_dial_label_widget.dart b/mobile/apps/auth/lib/ui/home/speed_dial_label_widget.dart similarity index 100% rename from auth/lib/ui/home/speed_dial_label_widget.dart rename to mobile/apps/auth/lib/ui/home/speed_dial_label_widget.dart diff --git a/auth/lib/ui/home_page.dart b/mobile/apps/auth/lib/ui/home_page.dart similarity index 100% rename from auth/lib/ui/home_page.dart rename to mobile/apps/auth/lib/ui/home_page.dart diff --git a/auth/lib/ui/lifecycle_event_handler.dart b/mobile/apps/auth/lib/ui/lifecycle_event_handler.dart similarity index 100% rename from auth/lib/ui/lifecycle_event_handler.dart rename to mobile/apps/auth/lib/ui/lifecycle_event_handler.dart diff --git a/auth/lib/ui/linear_progress_widget.dart b/mobile/apps/auth/lib/ui/linear_progress_widget.dart similarity index 100% rename from auth/lib/ui/linear_progress_widget.dart rename to mobile/apps/auth/lib/ui/linear_progress_widget.dart diff --git a/auth/lib/ui/passkey_page.dart b/mobile/apps/auth/lib/ui/passkey_page.dart similarity index 100% rename from auth/lib/ui/passkey_page.dart rename to mobile/apps/auth/lib/ui/passkey_page.dart diff --git a/auth/lib/ui/reorder_codes_page.dart b/mobile/apps/auth/lib/ui/reorder_codes_page.dart similarity index 100% rename from auth/lib/ui/reorder_codes_page.dart rename to mobile/apps/auth/lib/ui/reorder_codes_page.dart diff --git a/auth/lib/ui/scanner_gauth_page.dart b/mobile/apps/auth/lib/ui/scanner_gauth_page.dart similarity index 100% rename from auth/lib/ui/scanner_gauth_page.dart rename to mobile/apps/auth/lib/ui/scanner_gauth_page.dart diff --git a/auth/lib/ui/scanner_page.dart b/mobile/apps/auth/lib/ui/scanner_page.dart similarity index 100% rename from auth/lib/ui/scanner_page.dart rename to mobile/apps/auth/lib/ui/scanner_page.dart diff --git a/auth/lib/ui/settings/about_section_widget.dart b/mobile/apps/auth/lib/ui/settings/about_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/about_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/about_section_widget.dart diff --git a/auth/lib/ui/settings/account_section_widget.dart b/mobile/apps/auth/lib/ui/settings/account_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/account_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/account_section_widget.dart diff --git a/auth/lib/ui/settings/app_update_dialog.dart b/mobile/apps/auth/lib/ui/settings/app_update_dialog.dart similarity index 100% rename from auth/lib/ui/settings/app_update_dialog.dart rename to mobile/apps/auth/lib/ui/settings/app_update_dialog.dart diff --git a/auth/lib/ui/settings/app_version_widget.dart b/mobile/apps/auth/lib/ui/settings/app_version_widget.dart similarity index 100% rename from auth/lib/ui/settings/app_version_widget.dart rename to mobile/apps/auth/lib/ui/settings/app_version_widget.dart diff --git a/auth/lib/ui/settings/common_settings.dart b/mobile/apps/auth/lib/ui/settings/common_settings.dart similarity index 100% rename from auth/lib/ui/settings/common_settings.dart rename to mobile/apps/auth/lib/ui/settings/common_settings.dart diff --git a/auth/lib/ui/settings/data/data_section_widget.dart b/mobile/apps/auth/lib/ui/settings/data/data_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/data/data_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/data/data_section_widget.dart diff --git a/auth/lib/ui/settings/data/duplicate_code_page.dart b/mobile/apps/auth/lib/ui/settings/data/duplicate_code_page.dart similarity index 100% rename from auth/lib/ui/settings/data/duplicate_code_page.dart rename to mobile/apps/auth/lib/ui/settings/data/duplicate_code_page.dart diff --git a/auth/lib/ui/settings/data/export_widget.dart b/mobile/apps/auth/lib/ui/settings/data/export_widget.dart similarity index 100% rename from auth/lib/ui/settings/data/export_widget.dart rename to mobile/apps/auth/lib/ui/settings/data/export_widget.dart diff --git a/auth/lib/ui/settings/data/html_export.dart b/mobile/apps/auth/lib/ui/settings/data/html_export.dart similarity index 100% rename from auth/lib/ui/settings/data/html_export.dart rename to mobile/apps/auth/lib/ui/settings/data/html_export.dart diff --git a/auth/lib/ui/settings/data/import/aegis_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/aegis_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/aegis_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/aegis_import.dart diff --git a/auth/lib/ui/settings/data/import/bitwarden_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/bitwarden_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/bitwarden_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/bitwarden_import.dart diff --git a/auth/lib/ui/settings/data/import/encrypted_ente_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/encrypted_ente_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/encrypted_ente_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/encrypted_ente_import.dart diff --git a/auth/lib/ui/settings/data/import/google_auth_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/google_auth_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/google_auth_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/google_auth_import.dart diff --git a/auth/lib/ui/settings/data/import/import_service.dart b/mobile/apps/auth/lib/ui/settings/data/import/import_service.dart similarity index 100% rename from auth/lib/ui/settings/data/import/import_service.dart rename to mobile/apps/auth/lib/ui/settings/data/import/import_service.dart diff --git a/auth/lib/ui/settings/data/import/import_success.dart b/mobile/apps/auth/lib/ui/settings/data/import/import_success.dart similarity index 100% rename from auth/lib/ui/settings/data/import/import_success.dart rename to mobile/apps/auth/lib/ui/settings/data/import/import_success.dart diff --git a/auth/lib/ui/settings/data/import/lastpass_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/lastpass_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/lastpass_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/lastpass_import.dart diff --git a/auth/lib/ui/settings/data/import/plain_text_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/plain_text_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/plain_text_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/plain_text_import.dart diff --git a/auth/lib/ui/settings/data/import/raivo_plain_text_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/raivo_plain_text_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/raivo_plain_text_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/raivo_plain_text_import.dart diff --git a/auth/lib/ui/settings/data/import/two_fas_import.dart b/mobile/apps/auth/lib/ui/settings/data/import/two_fas_import.dart similarity index 100% rename from auth/lib/ui/settings/data/import/two_fas_import.dart rename to mobile/apps/auth/lib/ui/settings/data/import/two_fas_import.dart diff --git a/auth/lib/ui/settings/data/import_page.dart b/mobile/apps/auth/lib/ui/settings/data/import_page.dart similarity index 100% rename from auth/lib/ui/settings/data/import_page.dart rename to mobile/apps/auth/lib/ui/settings/data/import_page.dart diff --git a/auth/lib/ui/settings/developer_settings_page.dart b/mobile/apps/auth/lib/ui/settings/developer_settings_page.dart similarity index 100% rename from auth/lib/ui/settings/developer_settings_page.dart rename to mobile/apps/auth/lib/ui/settings/developer_settings_page.dart diff --git a/auth/lib/ui/settings/developer_settings_widget.dart b/mobile/apps/auth/lib/ui/settings/developer_settings_widget.dart similarity index 100% rename from auth/lib/ui/settings/developer_settings_widget.dart rename to mobile/apps/auth/lib/ui/settings/developer_settings_widget.dart diff --git a/auth/lib/ui/settings/general_section_widget.dart b/mobile/apps/auth/lib/ui/settings/general_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/general_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/general_section_widget.dart diff --git a/auth/lib/ui/settings/language_picker.dart b/mobile/apps/auth/lib/ui/settings/language_picker.dart similarity index 100% rename from auth/lib/ui/settings/language_picker.dart rename to mobile/apps/auth/lib/ui/settings/language_picker.dart diff --git a/auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/custom_pin_keypad.dart diff --git a/auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_auto_lock.dart diff --git a/auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_password.dart diff --git a/auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_confirm_pin.dart diff --git a/auth/lib/ui/settings/lock_screen/lock_screen_options.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_options.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/lock_screen_options.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_options.dart diff --git a/auth/lib/ui/settings/lock_screen/lock_screen_password.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_password.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/lock_screen_password.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_password.dart diff --git a/auth/lib/ui/settings/lock_screen/lock_screen_pin.dart b/mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_pin.dart similarity index 100% rename from auth/lib/ui/settings/lock_screen/lock_screen_pin.dart rename to mobile/apps/auth/lib/ui/settings/lock_screen/lock_screen_pin.dart diff --git a/auth/lib/ui/settings/notification_banner_widget.dart b/mobile/apps/auth/lib/ui/settings/notification_banner_widget.dart similarity index 100% rename from auth/lib/ui/settings/notification_banner_widget.dart rename to mobile/apps/auth/lib/ui/settings/notification_banner_widget.dart diff --git a/auth/lib/ui/settings/security_section_widget.dart b/mobile/apps/auth/lib/ui/settings/security_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/security_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/security_section_widget.dart diff --git a/auth/lib/ui/settings/settings_section_title.dart b/mobile/apps/auth/lib/ui/settings/settings_section_title.dart similarity index 100% rename from auth/lib/ui/settings/settings_section_title.dart rename to mobile/apps/auth/lib/ui/settings/settings_section_title.dart diff --git a/auth/lib/ui/settings/social_section_widget.dart b/mobile/apps/auth/lib/ui/settings/social_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/social_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/social_section_widget.dart diff --git a/auth/lib/ui/settings/support_section_widget.dart b/mobile/apps/auth/lib/ui/settings/support_section_widget.dart similarity index 100% rename from auth/lib/ui/settings/support_section_widget.dart rename to mobile/apps/auth/lib/ui/settings/support_section_widget.dart diff --git a/auth/lib/ui/settings/theme_switch_widget.dart b/mobile/apps/auth/lib/ui/settings/theme_switch_widget.dart similarity index 100% rename from auth/lib/ui/settings/theme_switch_widget.dart rename to mobile/apps/auth/lib/ui/settings/theme_switch_widget.dart diff --git a/auth/lib/ui/settings/title_bar_widget.dart b/mobile/apps/auth/lib/ui/settings/title_bar_widget.dart similarity index 100% rename from auth/lib/ui/settings/title_bar_widget.dart rename to mobile/apps/auth/lib/ui/settings/title_bar_widget.dart diff --git a/auth/lib/ui/settings_page.dart b/mobile/apps/auth/lib/ui/settings_page.dart similarity index 100% rename from auth/lib/ui/settings_page.dart rename to mobile/apps/auth/lib/ui/settings_page.dart diff --git a/auth/lib/ui/share/code_share.dart b/mobile/apps/auth/lib/ui/share/code_share.dart similarity index 100% rename from auth/lib/ui/share/code_share.dart rename to mobile/apps/auth/lib/ui/share/code_share.dart diff --git a/auth/lib/ui/sort_option_menu.dart b/mobile/apps/auth/lib/ui/sort_option_menu.dart similarity index 100% rename from auth/lib/ui/sort_option_menu.dart rename to mobile/apps/auth/lib/ui/sort_option_menu.dart diff --git a/auth/lib/ui/tools/app_lock.dart b/mobile/apps/auth/lib/ui/tools/app_lock.dart similarity index 100% rename from auth/lib/ui/tools/app_lock.dart rename to mobile/apps/auth/lib/ui/tools/app_lock.dart diff --git a/auth/lib/ui/tools/debug/log_file_viewer.dart b/mobile/apps/auth/lib/ui/tools/debug/log_file_viewer.dart similarity index 100% rename from auth/lib/ui/tools/debug/log_file_viewer.dart rename to mobile/apps/auth/lib/ui/tools/debug/log_file_viewer.dart diff --git a/auth/lib/ui/tools/debug/raw_codes_viewer.dart b/mobile/apps/auth/lib/ui/tools/debug/raw_codes_viewer.dart similarity index 100% rename from auth/lib/ui/tools/debug/raw_codes_viewer.dart rename to mobile/apps/auth/lib/ui/tools/debug/raw_codes_viewer.dart diff --git a/auth/lib/ui/tools/lock_screen.dart b/mobile/apps/auth/lib/ui/tools/lock_screen.dart similarity index 100% rename from auth/lib/ui/tools/lock_screen.dart rename to mobile/apps/auth/lib/ui/tools/lock_screen.dart diff --git a/auth/lib/ui/topt_selector_widget.dart b/mobile/apps/auth/lib/ui/topt_selector_widget.dart similarity index 100% rename from auth/lib/ui/topt_selector_widget.dart rename to mobile/apps/auth/lib/ui/topt_selector_widget.dart diff --git a/auth/lib/ui/two_factor_authentication_page.dart b/mobile/apps/auth/lib/ui/two_factor_authentication_page.dart similarity index 100% rename from auth/lib/ui/two_factor_authentication_page.dart rename to mobile/apps/auth/lib/ui/two_factor_authentication_page.dart diff --git a/auth/lib/ui/two_factor_recovery_page.dart b/mobile/apps/auth/lib/ui/two_factor_recovery_page.dart similarity index 100% rename from auth/lib/ui/two_factor_recovery_page.dart rename to mobile/apps/auth/lib/ui/two_factor_recovery_page.dart diff --git a/auth/lib/ui/utils/icon_utils.dart b/mobile/apps/auth/lib/ui/utils/icon_utils.dart similarity index 100% rename from auth/lib/ui/utils/icon_utils.dart rename to mobile/apps/auth/lib/ui/utils/icon_utils.dart diff --git a/auth/lib/utils/auth_util.dart b/mobile/apps/auth/lib/utils/auth_util.dart similarity index 100% rename from auth/lib/utils/auth_util.dart rename to mobile/apps/auth/lib/utils/auth_util.dart diff --git a/auth/lib/utils/date_time_util.dart b/mobile/apps/auth/lib/utils/date_time_util.dart similarity index 100% rename from auth/lib/utils/date_time_util.dart rename to mobile/apps/auth/lib/utils/date_time_util.dart diff --git a/auth/lib/utils/debouncer.dart b/mobile/apps/auth/lib/utils/debouncer.dart similarity index 100% rename from auth/lib/utils/debouncer.dart rename to mobile/apps/auth/lib/utils/debouncer.dart diff --git a/auth/lib/utils/dialog_util.dart b/mobile/apps/auth/lib/utils/dialog_util.dart similarity index 100% rename from auth/lib/utils/dialog_util.dart rename to mobile/apps/auth/lib/utils/dialog_util.dart diff --git a/auth/lib/utils/directory_utils.dart b/mobile/apps/auth/lib/utils/directory_utils.dart similarity index 100% rename from auth/lib/utils/directory_utils.dart rename to mobile/apps/auth/lib/utils/directory_utils.dart diff --git a/auth/lib/utils/email_util.dart b/mobile/apps/auth/lib/utils/email_util.dart similarity index 100% rename from auth/lib/utils/email_util.dart rename to mobile/apps/auth/lib/utils/email_util.dart diff --git a/auth/lib/utils/lock_screen_settings.dart b/mobile/apps/auth/lib/utils/lock_screen_settings.dart similarity index 100% rename from auth/lib/utils/lock_screen_settings.dart rename to mobile/apps/auth/lib/utils/lock_screen_settings.dart diff --git a/auth/lib/utils/navigation_util.dart b/mobile/apps/auth/lib/utils/navigation_util.dart similarity index 100% rename from auth/lib/utils/navigation_util.dart rename to mobile/apps/auth/lib/utils/navigation_util.dart diff --git a/auth/lib/utils/package_info_util.dart b/mobile/apps/auth/lib/utils/package_info_util.dart similarity index 100% rename from auth/lib/utils/package_info_util.dart rename to mobile/apps/auth/lib/utils/package_info_util.dart diff --git a/auth/lib/utils/platform_util.dart b/mobile/apps/auth/lib/utils/platform_util.dart similarity index 100% rename from auth/lib/utils/platform_util.dart rename to mobile/apps/auth/lib/utils/platform_util.dart diff --git a/auth/lib/utils/share_utils.dart b/mobile/apps/auth/lib/utils/share_utils.dart similarity index 100% rename from auth/lib/utils/share_utils.dart rename to mobile/apps/auth/lib/utils/share_utils.dart diff --git a/auth/lib/utils/toast_util.dart b/mobile/apps/auth/lib/utils/toast_util.dart similarity index 100% rename from auth/lib/utils/toast_util.dart rename to mobile/apps/auth/lib/utils/toast_util.dart diff --git a/auth/lib/utils/totp_util.dart b/mobile/apps/auth/lib/utils/totp_util.dart similarity index 100% rename from auth/lib/utils/totp_util.dart rename to mobile/apps/auth/lib/utils/totp_util.dart diff --git a/auth/lib/utils/window_protocol_handler.dart b/mobile/apps/auth/lib/utils/window_protocol_handler.dart similarity index 100% rename from auth/lib/utils/window_protocol_handler.dart rename to mobile/apps/auth/lib/utils/window_protocol_handler.dart diff --git a/auth/linux/.gitignore b/mobile/apps/auth/linux/.gitignore similarity index 100% rename from auth/linux/.gitignore rename to mobile/apps/auth/linux/.gitignore diff --git a/auth/linux/CMakeLists.txt b/mobile/apps/auth/linux/CMakeLists.txt similarity index 100% rename from auth/linux/CMakeLists.txt rename to mobile/apps/auth/linux/CMakeLists.txt diff --git a/auth/linux/flutter/CMakeLists.txt b/mobile/apps/auth/linux/flutter/CMakeLists.txt similarity index 100% rename from auth/linux/flutter/CMakeLists.txt rename to mobile/apps/auth/linux/flutter/CMakeLists.txt diff --git a/auth/linux/flutter/generated_plugin_registrant.cc b/mobile/apps/auth/linux/flutter/generated_plugin_registrant.cc similarity index 100% rename from auth/linux/flutter/generated_plugin_registrant.cc rename to mobile/apps/auth/linux/flutter/generated_plugin_registrant.cc diff --git a/auth/linux/flutter/generated_plugin_registrant.h b/mobile/apps/auth/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from auth/linux/flutter/generated_plugin_registrant.h rename to mobile/apps/auth/linux/flutter/generated_plugin_registrant.h diff --git a/auth/linux/flutter/generated_plugins.cmake b/mobile/apps/auth/linux/flutter/generated_plugins.cmake similarity index 100% rename from auth/linux/flutter/generated_plugins.cmake rename to mobile/apps/auth/linux/flutter/generated_plugins.cmake diff --git a/auth/linux/main.cc b/mobile/apps/auth/linux/main.cc similarity index 100% rename from auth/linux/main.cc rename to mobile/apps/auth/linux/main.cc diff --git a/auth/linux/my_application.cc b/mobile/apps/auth/linux/my_application.cc similarity index 100% rename from auth/linux/my_application.cc rename to mobile/apps/auth/linux/my_application.cc diff --git a/auth/linux/my_application.h b/mobile/apps/auth/linux/my_application.h similarity index 100% rename from auth/linux/my_application.h rename to mobile/apps/auth/linux/my_application.h diff --git a/auth/linux/packaging/appimage/make_config.yaml b/mobile/apps/auth/linux/packaging/appimage/make_config.yaml similarity index 100% rename from auth/linux/packaging/appimage/make_config.yaml rename to mobile/apps/auth/linux/packaging/appimage/make_config.yaml diff --git a/auth/linux/packaging/deb/make_config.yaml b/mobile/apps/auth/linux/packaging/deb/make_config.yaml similarity index 100% rename from auth/linux/packaging/deb/make_config.yaml rename to mobile/apps/auth/linux/packaging/deb/make_config.yaml diff --git a/auth/linux/packaging/enteauth.appdata.xml b/mobile/apps/auth/linux/packaging/enteauth.appdata.xml similarity index 100% rename from auth/linux/packaging/enteauth.appdata.xml rename to mobile/apps/auth/linux/packaging/enteauth.appdata.xml diff --git a/auth/linux/packaging/pacman/make_config.yaml b/mobile/apps/auth/linux/packaging/pacman/make_config.yaml similarity index 100% rename from auth/linux/packaging/pacman/make_config.yaml rename to mobile/apps/auth/linux/packaging/pacman/make_config.yaml diff --git a/auth/linux/packaging/rpm/make_config.yaml b/mobile/apps/auth/linux/packaging/rpm/make_config.yaml similarity index 100% rename from auth/linux/packaging/rpm/make_config.yaml rename to mobile/apps/auth/linux/packaging/rpm/make_config.yaml diff --git a/auth/macos/.gitignore b/mobile/apps/auth/macos/.gitignore similarity index 100% rename from auth/macos/.gitignore rename to mobile/apps/auth/macos/.gitignore diff --git a/auth/macos/Flutter/Flutter-Debug.xcconfig b/mobile/apps/auth/macos/Flutter/Flutter-Debug.xcconfig similarity index 100% rename from auth/macos/Flutter/Flutter-Debug.xcconfig rename to mobile/apps/auth/macos/Flutter/Flutter-Debug.xcconfig diff --git a/auth/macos/Flutter/Flutter-Release.xcconfig b/mobile/apps/auth/macos/Flutter/Flutter-Release.xcconfig similarity index 100% rename from auth/macos/Flutter/Flutter-Release.xcconfig rename to mobile/apps/auth/macos/Flutter/Flutter-Release.xcconfig diff --git a/auth/macos/Flutter/GeneratedPluginRegistrant.swift b/mobile/apps/auth/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 100% rename from auth/macos/Flutter/GeneratedPluginRegistrant.swift rename to mobile/apps/auth/macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/auth/macos/Podfile b/mobile/apps/auth/macos/Podfile similarity index 100% rename from auth/macos/Podfile rename to mobile/apps/auth/macos/Podfile diff --git a/auth/macos/Podfile.lock b/mobile/apps/auth/macos/Podfile.lock similarity index 100% rename from auth/macos/Podfile.lock rename to mobile/apps/auth/macos/Podfile.lock diff --git a/auth/macos/Runner.xcodeproj/project.pbxproj b/mobile/apps/auth/macos/Runner.xcodeproj/project.pbxproj similarity index 100% rename from auth/macos/Runner.xcodeproj/project.pbxproj rename to mobile/apps/auth/macos/Runner.xcodeproj/project.pbxproj diff --git a/auth/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/auth/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from auth/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mobile/apps/auth/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/mobile/apps/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to mobile/apps/auth/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/auth/macos/Runner.xcworkspace/contents.xcworkspacedata b/mobile/apps/auth/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from auth/macos/Runner.xcworkspace/contents.xcworkspacedata rename to mobile/apps/auth/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/auth/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/mobile/apps/auth/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from auth/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to mobile/apps/auth/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/auth/macos/Runner/AppDelegate.swift b/mobile/apps/auth/macos/Runner/AppDelegate.swift similarity index 100% rename from auth/macos/Runner/AppDelegate.swift rename to mobile/apps/auth/macos/Runner/AppDelegate.swift diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/1024-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/128-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/16-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/256-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/32-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/512-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64-mac.png b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64-mac.png similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64-mac.png rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/64-mac.png diff --git a/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to mobile/apps/auth/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/auth/macos/Runner/Base.lproj/MainMenu.xib b/mobile/apps/auth/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from auth/macos/Runner/Base.lproj/MainMenu.xib rename to mobile/apps/auth/macos/Runner/Base.lproj/MainMenu.xib diff --git a/auth/macos/Runner/Configs/AppInfo.xcconfig b/mobile/apps/auth/macos/Runner/Configs/AppInfo.xcconfig similarity index 100% rename from auth/macos/Runner/Configs/AppInfo.xcconfig rename to mobile/apps/auth/macos/Runner/Configs/AppInfo.xcconfig diff --git a/auth/macos/Runner/Configs/Debug.xcconfig b/mobile/apps/auth/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from auth/macos/Runner/Configs/Debug.xcconfig rename to mobile/apps/auth/macos/Runner/Configs/Debug.xcconfig diff --git a/auth/macos/Runner/Configs/Release.xcconfig b/mobile/apps/auth/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from auth/macos/Runner/Configs/Release.xcconfig rename to mobile/apps/auth/macos/Runner/Configs/Release.xcconfig diff --git a/auth/macos/Runner/Configs/Warnings.xcconfig b/mobile/apps/auth/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from auth/macos/Runner/Configs/Warnings.xcconfig rename to mobile/apps/auth/macos/Runner/Configs/Warnings.xcconfig diff --git a/auth/macos/Runner/DebugProfile.entitlements b/mobile/apps/auth/macos/Runner/DebugProfile.entitlements similarity index 100% rename from auth/macos/Runner/DebugProfile.entitlements rename to mobile/apps/auth/macos/Runner/DebugProfile.entitlements diff --git a/auth/macos/Runner/Info.plist b/mobile/apps/auth/macos/Runner/Info.plist similarity index 100% rename from auth/macos/Runner/Info.plist rename to mobile/apps/auth/macos/Runner/Info.plist diff --git a/auth/macos/Runner/MainFlutterWindow.swift b/mobile/apps/auth/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from auth/macos/Runner/MainFlutterWindow.swift rename to mobile/apps/auth/macos/Runner/MainFlutterWindow.swift diff --git a/auth/macos/Runner/Release.entitlements b/mobile/apps/auth/macos/Runner/Release.entitlements similarity index 100% rename from auth/macos/Runner/Release.entitlements rename to mobile/apps/auth/macos/Runner/Release.entitlements diff --git a/auth/macos/build/.last_build_id b/mobile/apps/auth/macos/build/.last_build_id similarity index 100% rename from auth/macos/build/.last_build_id rename to mobile/apps/auth/macos/build/.last_build_id diff --git a/auth/macos/packaging/dmg/make_config.yaml b/mobile/apps/auth/macos/packaging/dmg/make_config.yaml similarity index 100% rename from auth/macos/packaging/dmg/make_config.yaml rename to mobile/apps/auth/macos/packaging/dmg/make_config.yaml diff --git a/auth/migration-guides/README.md b/mobile/apps/auth/migration-guides/README.md similarity index 100% rename from auth/migration-guides/README.md rename to mobile/apps/auth/migration-guides/README.md diff --git a/auth/migration-guides/authy.md b/mobile/apps/auth/migration-guides/authy.md similarity index 100% rename from auth/migration-guides/authy.md rename to mobile/apps/auth/migration-guides/authy.md diff --git a/auth/migration-guides/encrypted_export.md b/mobile/apps/auth/migration-guides/encrypted_export.md similarity index 100% rename from auth/migration-guides/encrypted_export.md rename to mobile/apps/auth/migration-guides/encrypted_export.md diff --git a/auth/protos/googleauth.proto b/mobile/apps/auth/protos/googleauth.proto similarity index 100% rename from auth/protos/googleauth.proto rename to mobile/apps/auth/protos/googleauth.proto diff --git a/auth/pubspec.lock b/mobile/apps/auth/pubspec.lock similarity index 100% rename from auth/pubspec.lock rename to mobile/apps/auth/pubspec.lock diff --git a/auth/pubspec.yaml b/mobile/apps/auth/pubspec.yaml similarity index 100% rename from auth/pubspec.yaml rename to mobile/apps/auth/pubspec.yaml diff --git a/auth/screenshots/screenshots.png b/mobile/apps/auth/screenshots/screenshots.png similarity index 100% rename from auth/screenshots/screenshots.png rename to mobile/apps/auth/screenshots/screenshots.png diff --git a/auth/scripts/release_tag.sh b/mobile/apps/auth/scripts/release_tag.sh similarity index 100% rename from auth/scripts/release_tag.sh rename to mobile/apps/auth/scripts/release_tag.sh diff --git a/auth/test/helpers/helpers.dart b/mobile/apps/auth/test/helpers/helpers.dart similarity index 100% rename from auth/test/helpers/helpers.dart rename to mobile/apps/auth/test/helpers/helpers.dart diff --git a/auth/test/helpers/pump_app.dart b/mobile/apps/auth/test/helpers/pump_app.dart similarity index 100% rename from auth/test/helpers/pump_app.dart rename to mobile/apps/auth/test/helpers/pump_app.dart diff --git a/auth/test/models/code_test.dart b/mobile/apps/auth/test/models/code_test.dart similarity index 100% rename from auth/test/models/code_test.dart rename to mobile/apps/auth/test/models/code_test.dart diff --git a/auth/web/favicon.png b/mobile/apps/auth/web/favicon.png similarity index 100% rename from auth/web/favicon.png rename to mobile/apps/auth/web/favicon.png diff --git a/auth/web/icons/Icon-192.png b/mobile/apps/auth/web/icons/Icon-192.png similarity index 100% rename from auth/web/icons/Icon-192.png rename to mobile/apps/auth/web/icons/Icon-192.png diff --git a/auth/web/icons/Icon-512.png b/mobile/apps/auth/web/icons/Icon-512.png similarity index 100% rename from auth/web/icons/Icon-512.png rename to mobile/apps/auth/web/icons/Icon-512.png diff --git a/auth/web/icons/favicon.png b/mobile/apps/auth/web/icons/favicon.png similarity index 100% rename from auth/web/icons/favicon.png rename to mobile/apps/auth/web/icons/favicon.png diff --git a/auth/web/index.html b/mobile/apps/auth/web/index.html similarity index 100% rename from auth/web/index.html rename to mobile/apps/auth/web/index.html diff --git a/auth/web/manifest.json b/mobile/apps/auth/web/manifest.json similarity index 100% rename from auth/web/manifest.json rename to mobile/apps/auth/web/manifest.json diff --git a/auth/web/splash/img/dark-1x.png b/mobile/apps/auth/web/splash/img/dark-1x.png similarity index 100% rename from auth/web/splash/img/dark-1x.png rename to mobile/apps/auth/web/splash/img/dark-1x.png diff --git a/auth/web/splash/img/dark-2x.png b/mobile/apps/auth/web/splash/img/dark-2x.png similarity index 100% rename from auth/web/splash/img/dark-2x.png rename to mobile/apps/auth/web/splash/img/dark-2x.png diff --git a/auth/web/splash/img/dark-3x.png b/mobile/apps/auth/web/splash/img/dark-3x.png similarity index 100% rename from auth/web/splash/img/dark-3x.png rename to mobile/apps/auth/web/splash/img/dark-3x.png diff --git a/auth/web/splash/img/dark-4x.png b/mobile/apps/auth/web/splash/img/dark-4x.png similarity index 100% rename from auth/web/splash/img/dark-4x.png rename to mobile/apps/auth/web/splash/img/dark-4x.png diff --git a/auth/web/splash/img/light-1x.png b/mobile/apps/auth/web/splash/img/light-1x.png similarity index 100% rename from auth/web/splash/img/light-1x.png rename to mobile/apps/auth/web/splash/img/light-1x.png diff --git a/auth/web/splash/img/light-2x.png b/mobile/apps/auth/web/splash/img/light-2x.png similarity index 100% rename from auth/web/splash/img/light-2x.png rename to mobile/apps/auth/web/splash/img/light-2x.png diff --git a/auth/web/splash/img/light-3x.png b/mobile/apps/auth/web/splash/img/light-3x.png similarity index 100% rename from auth/web/splash/img/light-3x.png rename to mobile/apps/auth/web/splash/img/light-3x.png diff --git a/auth/web/splash/img/light-4x.png b/mobile/apps/auth/web/splash/img/light-4x.png similarity index 100% rename from auth/web/splash/img/light-4x.png rename to mobile/apps/auth/web/splash/img/light-4x.png diff --git a/auth/web/splash/splash.js b/mobile/apps/auth/web/splash/splash.js similarity index 100% rename from auth/web/splash/splash.js rename to mobile/apps/auth/web/splash/splash.js diff --git a/auth/web/splash/style.css b/mobile/apps/auth/web/splash/style.css similarity index 100% rename from auth/web/splash/style.css rename to mobile/apps/auth/web/splash/style.css diff --git a/auth/windows/.gitignore b/mobile/apps/auth/windows/.gitignore similarity index 100% rename from auth/windows/.gitignore rename to mobile/apps/auth/windows/.gitignore diff --git a/auth/windows/CMakeLists.txt b/mobile/apps/auth/windows/CMakeLists.txt similarity index 100% rename from auth/windows/CMakeLists.txt rename to mobile/apps/auth/windows/CMakeLists.txt diff --git a/auth/windows/flutter/CMakeLists.txt b/mobile/apps/auth/windows/flutter/CMakeLists.txt similarity index 100% rename from auth/windows/flutter/CMakeLists.txt rename to mobile/apps/auth/windows/flutter/CMakeLists.txt diff --git a/auth/windows/flutter/generated_plugin_registrant.cc b/mobile/apps/auth/windows/flutter/generated_plugin_registrant.cc similarity index 100% rename from auth/windows/flutter/generated_plugin_registrant.cc rename to mobile/apps/auth/windows/flutter/generated_plugin_registrant.cc diff --git a/auth/windows/flutter/generated_plugin_registrant.h b/mobile/apps/auth/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from auth/windows/flutter/generated_plugin_registrant.h rename to mobile/apps/auth/windows/flutter/generated_plugin_registrant.h diff --git a/auth/windows/flutter/generated_plugins.cmake b/mobile/apps/auth/windows/flutter/generated_plugins.cmake similarity index 100% rename from auth/windows/flutter/generated_plugins.cmake rename to mobile/apps/auth/windows/flutter/generated_plugins.cmake diff --git a/auth/windows/packaging/exe/inno_setup.iss b/mobile/apps/auth/windows/packaging/exe/inno_setup.iss similarity index 100% rename from auth/windows/packaging/exe/inno_setup.iss rename to mobile/apps/auth/windows/packaging/exe/inno_setup.iss diff --git a/auth/windows/packaging/exe/make_config.yaml b/mobile/apps/auth/windows/packaging/exe/make_config.yaml similarity index 100% rename from auth/windows/packaging/exe/make_config.yaml rename to mobile/apps/auth/windows/packaging/exe/make_config.yaml diff --git a/auth/windows/runner/CMakeLists.txt b/mobile/apps/auth/windows/runner/CMakeLists.txt similarity index 100% rename from auth/windows/runner/CMakeLists.txt rename to mobile/apps/auth/windows/runner/CMakeLists.txt diff --git a/auth/windows/runner/Runner.rc b/mobile/apps/auth/windows/runner/Runner.rc similarity index 100% rename from auth/windows/runner/Runner.rc rename to mobile/apps/auth/windows/runner/Runner.rc diff --git a/auth/windows/runner/flutter_window.cpp b/mobile/apps/auth/windows/runner/flutter_window.cpp similarity index 100% rename from auth/windows/runner/flutter_window.cpp rename to mobile/apps/auth/windows/runner/flutter_window.cpp diff --git a/auth/windows/runner/flutter_window.h b/mobile/apps/auth/windows/runner/flutter_window.h similarity index 100% rename from auth/windows/runner/flutter_window.h rename to mobile/apps/auth/windows/runner/flutter_window.h diff --git a/auth/windows/runner/main.cpp b/mobile/apps/auth/windows/runner/main.cpp similarity index 100% rename from auth/windows/runner/main.cpp rename to mobile/apps/auth/windows/runner/main.cpp diff --git a/auth/windows/runner/resource.h b/mobile/apps/auth/windows/runner/resource.h similarity index 100% rename from auth/windows/runner/resource.h rename to mobile/apps/auth/windows/runner/resource.h diff --git a/auth/windows/runner/resources/app_icon.ico b/mobile/apps/auth/windows/runner/resources/app_icon.ico similarity index 100% rename from auth/windows/runner/resources/app_icon.ico rename to mobile/apps/auth/windows/runner/resources/app_icon.ico diff --git a/auth/windows/runner/runner.exe.manifest b/mobile/apps/auth/windows/runner/runner.exe.manifest similarity index 100% rename from auth/windows/runner/runner.exe.manifest rename to mobile/apps/auth/windows/runner/runner.exe.manifest diff --git a/auth/windows/runner/utils.cpp b/mobile/apps/auth/windows/runner/utils.cpp similarity index 100% rename from auth/windows/runner/utils.cpp rename to mobile/apps/auth/windows/runner/utils.cpp diff --git a/auth/windows/runner/utils.h b/mobile/apps/auth/windows/runner/utils.h similarity index 100% rename from auth/windows/runner/utils.h rename to mobile/apps/auth/windows/runner/utils.h diff --git a/auth/windows/runner/win32_window.cpp b/mobile/apps/auth/windows/runner/win32_window.cpp similarity index 100% rename from auth/windows/runner/win32_window.cpp rename to mobile/apps/auth/windows/runner/win32_window.cpp diff --git a/auth/windows/runner/win32_window.h b/mobile/apps/auth/windows/runner/win32_window.h similarity index 100% rename from auth/windows/runner/win32_window.h rename to mobile/apps/auth/windows/runner/win32_window.h From b08545d40d7023d3207cb54aa2f088b8efb96d08 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 4 Jul 2025 14:29:09 +0530 Subject: [PATCH 046/109] Update workflows --- .github/workflows/auth-crowdin-push.yml | 4 ++-- .github/workflows/auth-crowdin-sync.yml | 4 ++-- .github/workflows/auth-internal-release.yml | 4 ++-- .github/workflows/auth-lint.yml | 2 +- .github/workflows/auth-release.yml | 18 +++++++++--------- .github/workflows/auth-win-sign.yml | 6 +++--- .github/workflows/mobile-crowdin-push.yml | 6 +++--- .github/workflows/mobile-crowdin-sync.yml | 8 ++++---- .../workflows/mobile-internal-release-rust.yml | 4 ++-- .github/workflows/mobile-internal-release.yml | 4 ++-- .github/workflows/mobile-lint.yml | 4 ++-- .github/workflows/mobile-release.yml | 4 ++-- 12 files changed, 34 insertions(+), 34 deletions(-) diff --git a/.github/workflows/auth-crowdin-push.yml b/.github/workflows/auth-crowdin-push.yml index 4cefb1d1c6..74c5ef808f 100644 --- a/.github/workflows/auth-crowdin-push.yml +++ b/.github/workflows/auth-crowdin-push.yml @@ -24,8 +24,8 @@ jobs: - name: Crowdin's action uses: crowdin/github-action@v2 with: - base_path: "auth/" - config: "auth/crowdin.yml" + base_path: "mobile/apps/auth/" + config: "mobile/apps/auth/crowdin.yml" upload_sources: true upload_translations: false download_translations: false diff --git a/.github/workflows/auth-crowdin-sync.yml b/.github/workflows/auth-crowdin-sync.yml index 695aea7245..477b62cf44 100644 --- a/.github/workflows/auth-crowdin-sync.yml +++ b/.github/workflows/auth-crowdin-sync.yml @@ -23,8 +23,8 @@ jobs: - name: Crowdin's action uses: crowdin/github-action@v2 with: - base_path: "auth/" - config: "auth/crowdin.yml" + base_path: "mobile/apps/auth/" + config: "mobile/apps/auth/crowdin.yml" upload_sources: true upload_translations: false download_translations: true diff --git a/.github/workflows/auth-internal-release.yml b/.github/workflows/auth-internal-release.yml index 9668e0e336..13f6310cef 100644 --- a/.github/workflows/auth-internal-release.yml +++ b/.github/workflows/auth-internal-release.yml @@ -15,7 +15,7 @@ jobs: defaults: run: - working-directory: auth + working-directory: mobile/apps/auth steps: - name: Checkout code and submodules @@ -55,7 +55,7 @@ jobs: with: serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} packageName: io.ente.auth - releaseFiles: auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab + releaseFiles: mobile/apps/auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab track: internal - name: Notify Discord diff --git a/.github/workflows/auth-lint.yml b/.github/workflows/auth-lint.yml index e48317d59f..34e5ff8a78 100644 --- a/.github/workflows/auth-lint.yml +++ b/.github/workflows/auth-lint.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: auth + working-directory: mobile/apps/auth steps: - name: Checkout code and submodules uses: actions/checkout@v4 diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml index 97d079a681..d8c9f4e1cc 100644 --- a/.github/workflows/auth-release.yml +++ b/.github/workflows/auth-release.yml @@ -40,7 +40,7 @@ jobs: defaults: run: - working-directory: auth + working-directory: mobile/apps/auth steps: - name: Checkout code and submodules @@ -124,7 +124,7 @@ jobs: - name: Create a draft GitHub release uses: ncipollo/release-action@v1 with: - artifacts: "auth/artifacts/*" + artifacts: "mobile/apps/auth/artifacts/*" draft: true allowUpdates: true updateOnlyUnreleased: true @@ -136,7 +136,7 @@ jobs: with: serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} packageName: io.ente.auth - releaseFiles: auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab + releaseFiles: mobile/apps/auth/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab track: internal build-windows: @@ -145,7 +145,7 @@ jobs: defaults: run: - working-directory: auth + working-directory: mobile/apps/auth steps: - name: Checkout code and submodules @@ -185,8 +185,8 @@ jobs: trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }} certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }} files: | - ${{ github.workspace }}/auth/artifacts/ente-${{ github.ref_name }}-installer.exe - ${{ github.workspace }}/auth/ente-${{ github.ref_name }}-windows/auth.exe + ${{ github.workspace }}/mobile/apps/auth/artifacts/ente-${{ github.ref_name }}-installer.exe + ${{ github.workspace }}/mobile/apps/auth/ente-${{ github.ref_name }}-windows/auth.exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 @@ -201,7 +201,7 @@ jobs: - name: Create a draft GitHub release uses: ncipollo/release-action@v1 with: - artifacts: "auth/artifacts/*" + artifacts: "mobile/apps/auth/artifacts/*" draft: true allowUpdates: true updateOnlyUnreleased: true @@ -211,7 +211,7 @@ jobs: defaults: run: - working-directory: auth + working-directory: mobile/apps/auth steps: - name: Checkout code and submodules @@ -298,7 +298,7 @@ jobs: - name: Create a draft GitHub release uses: ncipollo/release-action@v1 with: - artifacts: "auth/artifacts/*" + artifacts: "mobile/apps/auth/artifacts/*" draft: true allowUpdates: true updateOnlyUnreleased: true diff --git a/.github/workflows/auth-win-sign.yml b/.github/workflows/auth-win-sign.yml index 860cde2ca2..2793f5a98e 100644 --- a/.github/workflows/auth-win-sign.yml +++ b/.github/workflows/auth-win-sign.yml @@ -17,7 +17,7 @@ jobs: defaults: run: - working-directory: auth + working-directory: mobile/apps/auth steps: - name: Checkout code and submodules @@ -57,8 +57,8 @@ jobs: trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }} certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }} files: | - ${{ github.workspace }}/auth/artifacts/ente-${{ github.ref_name }}-installer.exe - ${{ github.workspace }}/auth/ente-${{ github.ref_name }}-windows/auth.exe + ${{ github.workspace }}/mobile/apps/auth/artifacts/ente-${{ github.ref_name }}-installer.exe + ${{ github.workspace }}/mobile/apps/auth/apps/auth/ente-${{ github.ref_name }}-windows/auth.exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 diff --git a/.github/workflows/mobile-crowdin-push.yml b/.github/workflows/mobile-crowdin-push.yml index e8b219d5c7..972a13bd6b 100644 --- a/.github/workflows/mobile-crowdin-push.yml +++ b/.github/workflows/mobile-crowdin-push.yml @@ -5,7 +5,7 @@ on: branches: [main] paths: # Run workflow when mobiles's intl_en.arb is changed - - "mobile/lib/l10n/intl_en.arb" + - "mobile/apps/photos/lib/l10n/intl_en.arb" # Or the workflow itself is changed - ".github/workflows/mobile-crowdin.yml" @@ -24,8 +24,8 @@ jobs: - name: Crowdin's action uses: crowdin/github-action@v2 with: - base_path: "mobile/" - config: "mobile/crowdin.yml" + base_path: "mobile/apps/photos/" + config: "mobile/apps/photos/crowdin.yml" upload_sources: true upload_translations: false download_translations: false diff --git a/.github/workflows/mobile-crowdin-sync.yml b/.github/workflows/mobile-crowdin-sync.yml index f064105331..60b87fbb4b 100644 --- a/.github/workflows/mobile-crowdin-sync.yml +++ b/.github/workflows/mobile-crowdin-sync.yml @@ -1,4 +1,4 @@ -name: "Sync Crowdin translations (mobile)" +name: "Sync Crowdin translations (mobile/photos)" on: schedule: @@ -23,14 +23,14 @@ jobs: - name: Crowdin's action uses: crowdin/github-action@v2 with: - base_path: "mobile/" - config: "mobile/crowdin.yml" + base_path: "mobile/apps/photos/" + config: "mobile/apps/photos/crowdin.yml" upload_sources: true upload_translations: false download_translations: true localization_branch_name: translations/mobile create_pull_request: true - pull_request_title: "[mobile] New translations" + pull_request_title: "[mobile/photos] New translations" pull_request_body: "New translations from [Crowdin](https://crowdin.com/project/ente-photos-app)" pull_request_base_branch_name: "main" project_id: 574741 diff --git a/.github/workflows/mobile-internal-release-rust.yml b/.github/workflows/mobile-internal-release-rust.yml index aa254f2268..28c93d8618 100644 --- a/.github/workflows/mobile-internal-release-rust.yml +++ b/.github/workflows/mobile-internal-release-rust.yml @@ -16,7 +16,7 @@ jobs: defaults: run: - working-directory: mobile + working-directory: mobile/apps/photos steps: - name: Checkout code and submodules @@ -64,7 +64,7 @@ jobs: with: serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} packageName: io.ente.photos - releaseFiles: mobile/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab + releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab track: internal - name: Notify Discord diff --git a/.github/workflows/mobile-internal-release.yml b/.github/workflows/mobile-internal-release.yml index f1d5724f97..1a689d3785 100644 --- a/.github/workflows/mobile-internal-release.yml +++ b/.github/workflows/mobile-internal-release.yml @@ -15,7 +15,7 @@ jobs: defaults: run: - working-directory: mobile + working-directory: mobile/apps/photos steps: - name: Checkout code and submodules @@ -55,7 +55,7 @@ jobs: with: serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} packageName: io.ente.photos - releaseFiles: mobile/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab + releaseFiles: mobile/apps/photos/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab track: internal - name: Notify Discord diff --git a/.github/workflows/mobile-lint.yml b/.github/workflows/mobile-lint.yml index f404c5a65a..f0944416b5 100644 --- a/.github/workflows/mobile-lint.yml +++ b/.github/workflows/mobile-lint.yml @@ -4,7 +4,7 @@ on: # Run on every pull request (open or push to it) that changes mobile/ pull_request: paths: - - "mobile/**" + - "mobile/apps/photos/**" - ".github/workflows/mobile-lint.yml" env: @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: mobile + working-directory: mobile/apps/photos steps: - name: Checkout code and submodules uses: actions/checkout@v4 diff --git a/.github/workflows/mobile-release.yml b/.github/workflows/mobile-release.yml index e15cd937e9..d0466f3b49 100644 --- a/.github/workflows/mobile-release.yml +++ b/.github/workflows/mobile-release.yml @@ -20,7 +20,7 @@ jobs: defaults: run: - working-directory: mobile + working-directory: mobile/apps/photos steps: - name: Checkout code and submodules @@ -62,5 +62,5 @@ jobs: - name: Create a draft GitHub release uses: ncipollo/release-action@v1 with: - artifacts: "mobile/build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk,mobile/build/app/outputs/flutter-apk/sha256sum" + artifacts: "mobile/apps/photos/build/app/outputs/flutter-apk/ente-${{ github.ref_name }}.apk,mobile/apps/photos/build/app/outputs/flutter-apk/sha256sum" draft: true From 369fcddc39c896a5a4af688e210d474993d97a96 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 4 Jul 2025 16:29:03 +0530 Subject: [PATCH 047/109] Fix typo in path --- .github/workflows/auth-win-sign.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auth-win-sign.yml b/.github/workflows/auth-win-sign.yml index 2793f5a98e..dada811c96 100644 --- a/.github/workflows/auth-win-sign.yml +++ b/.github/workflows/auth-win-sign.yml @@ -58,7 +58,7 @@ jobs: certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }} files: | ${{ github.workspace }}/mobile/apps/auth/artifacts/ente-${{ github.ref_name }}-installer.exe - ${{ github.workspace }}/mobile/apps/auth/apps/auth/ente-${{ github.ref_name }}-windows/auth.exe + ${{ github.workspace }}/mobile/apps/auth/ente-${{ github.ref_name }}-windows/auth.exe file-digest: SHA256 timestamp-rfc3161: http://timestamp.acs.microsoft.com timestamp-digest: SHA256 From 9020907b56ab3cd117a3268c50dac1b064af47df Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 16:18:30 +0530 Subject: [PATCH 048/109] Sketch --- .../Collections/GalleryBarAndListHeader.tsx | 14 +-- ...ss.tsx => DownloadStatusNotifications.tsx} | 114 ++++++++---------- web/apps/photos/src/pages/gallery.tsx | 8 +- web/apps/photos/src/pages/shared-albums.tsx | 10 +- web/packages/gallery/services/save.ts | 56 ++++++--- 5 files changed, 106 insertions(+), 96 deletions(-) rename web/apps/photos/src/components/{DownloadProgress.tsx => DownloadStatusNotifications.tsx} (55%) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index ea797f5045..594113060b 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -5,7 +5,11 @@ import { } from "components/Collections/CollectionShare"; import type { TimeStampListItem } from "components/FileList"; import { useModalVisibility } from "ente-base/components/utils/modal"; -import { type FilesDownloadProgressAttributes } from "ente-gallery/services/save"; +import { + isFilesDownloadCancelled, + isSaveComplete, + type SaveGroup, +} from "ente-gallery/services/save"; import type { Collection } from "ente-media/collection"; import { GalleryBarImpl, @@ -22,10 +26,6 @@ import { import { includes } from "ente-utils/type-guards"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { sortCollectionSummaries } from "services/collectionService"; -import { - isFilesDownloadCancelled, - isFilesDownloadCompleted, -} from "../DownloadProgress"; import { AlbumCastDialog } from "./AlbumCastDialog"; import { CollectionHeader, @@ -47,7 +47,7 @@ type GalleryBarAndListHeaderProps = Omit< activeCollection: Collection; setActiveCollectionID: (collectionID: number) => void; setPhotoListHeader: (value: TimeStampListItem) => void; - filesDownloadProgressAttributesList: FilesDownloadProgressAttributes[]; + filesDownloadProgressAttributesList: SaveGroup[]; } & Pick< CollectionHeaderProps, "setFilesDownloadProgressAttributesCreator" | "onRemotePull" @@ -131,7 +131,7 @@ export const GalleryBarAndListHeader: React.FC< return ( attributes && !isFilesDownloadCancelled(attributes) && - !isFilesDownloadCompleted(attributes) + !isSaveComplete(attributes) ); }, [activeCollectionID, filesDownloadProgressAttributesList]); diff --git a/web/apps/photos/src/components/DownloadProgress.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx similarity index 55% rename from web/apps/photos/src/components/DownloadProgress.tsx rename to web/apps/photos/src/components/DownloadStatusNotifications.tsx index 87e628287c..7c0c188438 100644 --- a/web/apps/photos/src/components/DownloadProgress.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -1,13 +1,23 @@ -// TODO: Audit this file -/* eslint-disable react-refresh/only-export-components */ import { useBaseContext } from "ente-base/context"; -import type { FilesDownloadProgressAttributes } from "ente-gallery/services/save"; +import { + isSaveComplete, + isSaveCompleteWithErrors, + isSaveStarted, + type SaveGroup, +} from "ente-gallery/services/save"; import { Notification } from "ente-new/photos/components/Notification"; import { t } from "i18next"; -interface FilesDownloadProgressProps { - attributesList: FilesDownloadProgressAttributes[]; - setAttributesList: (value: FilesDownloadProgressAttributes[]) => void; +interface DownloadStatusNotificationsProps { + /** + * A list of user-initiated downloads for which a status should be shown. + * + * An entry is added to this list when the user initiates the download, and + * remains here until the user explicitly closes the corresponding + * {@link Notification} component that was showing the save group's status. + */ + saveGroups: SaveGroup[]; + setAttributesList: (value: SaveGroup[]) => void; /** * Called when the hidden section should be shown. * @@ -29,56 +39,25 @@ interface FilesDownloadProgressProps { onShowCollection?: (collectionID: number) => void; } -export const isFilesDownloadStarted = ( - attributes: FilesDownloadProgressAttributes, -) => { - return attributes && attributes.total > 0; -}; - -export const isFilesDownloadCompleted = ( - attributes: FilesDownloadProgressAttributes, -) => { - return ( - attributes && - attributes.success + attributes.failed === attributes.total - ); -}; - -const isFilesDownloadCompletedWithErrors = ( - attributes: FilesDownloadProgressAttributes, -) => { - return ( - attributes && - attributes.failed > 0 && - isFilesDownloadCompleted(attributes) - ); -}; - -export const isFilesDownloadCancelled = ( - attributes: FilesDownloadProgressAttributes, -) => { - return attributes?.canceller?.signal?.aborted; -}; - -export const FilesDownloadProgress: React.FC = ({ - attributesList, +/** + * A component that shows a list of notifications, one each for an active + * user-initiated download. + */ +export const DownloadStatusNotifications: React.FC< + DownloadStatusNotificationsProps +> = ({ + saveGroups, setAttributesList, onShowHiddenSection, onShowCollection, }) => { const { showMiniDialog } = useBaseContext(); - if (!attributesList) { - return <>; - } - const onClose = (id: number) => { - setAttributesList(attributesList.filter((attr) => attr.id !== id)); + setAttributesList(saveGroups.filter((attr) => attr.id !== id)); }; - const confirmCancelDownload = ( - attributes: FilesDownloadProgressAttributes, - ) => { + const confirmCancelDownload = (attributes: SaveGroup) => { showMiniDialog({ title: t("stop_downloads_title"), message: t("stop_downloads_message"), @@ -94,8 +73,8 @@ export const FilesDownloadProgress: React.FC = ({ }); }; - const handleClose = (attributes: FilesDownloadProgressAttributes) => () => { - if (isFilesDownloadCompleted(attributes)) { + const handleClose = (attributes: SaveGroup) => () => { + if (isSaveComplete(attributes)) { onClose(attributes.id); } else { confirmCancelDownload(attributes); @@ -105,7 +84,7 @@ export const FilesDownloadProgress: React.FC = ({ const createHandleOnClick = (id: number, onShowCollection: (collectionID: number) => void) => () => { - const attributes = attributesList.find((attr) => attr.id === id); + const attributes = saveGroups.find((attr) => attr.id === id); const electron = globalThis.electron; if (electron) { electron.openDirectory(attributes.downloadDirPath); @@ -120,40 +99,43 @@ export const FilesDownloadProgress: React.FC = ({ } }; + if (!saveGroups) { + return <>; + } + const notifications: React.ReactNode[] = []; + let visibleIndex = 0; - for (const attributes of attributesList) { + for (const group of saveGroups) { // Skip attempted downloads of empty albums, which had no effect. - if (!isFilesDownloadStarted(attributes)) continue; + if (!isSaveStarted(group)) continue; const index = visibleIndex++; notifications.push( , diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 334910b2d8..e0e95c0dc7 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -5,9 +5,9 @@ import { IconButton, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; import { - FilesDownloadProgress, + DownloadStatusNotifications, type FilesDownloadProgressAttributes, -} from "components/DownloadProgress"; +} from "components/DownloadStatusNotifications"; import { type TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FixCreationTime } from "components/FixCreationTime"; @@ -995,8 +995,8 @@ const Page: React.FC = () => { )! } /> - ([]); + ] = useState([]); const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator = useCallback((folderName, collectionID, isHidden) => { @@ -572,8 +572,8 @@ export default function PublicCollectionGallery() { onShowSessionExpiredDialog={showPublicLinkExpiredMessage} {...{ dragAndDropFiles }} /> - diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 627f76e8a8..1d35e4a7a5 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -27,32 +27,60 @@ import { import { wait } from "ente-utils/promise"; /** - * An object that keeps track of progress of the download of a set of files. + * An object that keeps track of progress of a user-initiated download of a set + * of files to the user's device. * - * This "download" is distinct from the downloads the app does from remote. What - * we're doing here is perhaps more accurately but too verbosely described as "a - * user initiated download of files to the user's device", aka "saving them". In - * contrast, the app does the "download the file from remote" internal action, - * e.g. for showing to the user or performing some indexing etc. + * This "download" is distinct from the downloads the app does from remote (e.g. + * when the user is viewing them). + * + * What we're doing here is perhaps more accurately described "a user initiated + * download of files to the user's device", but that is too long, so we instead + * refer to this process as "saving them". + * + * Note however that the app's UI itself takes the user perspective, so the + * upper (UI) layers use the word "download", while this implementation layer + * uses the word "save", and there is an unavoidable incongruity in the middle. */ -export interface FilesDownloadProgressAttributes { +export interface SaveGroup { + /** + * A unique identifier of this set of saves. + */ id: number; - success: number; - failed: number; + /** + * The total number of files to save to the user's device. + */ total: number; + /** + * The number of files that have already been save. + */ + success: number; + /** + * The number of failures. + */ + failed: number; folderName: string; collectionID: number; isHidden: boolean; downloadDirPath: string; + /** + * An {@link AbortController} that can be used to cancel the save. + */ canceller: AbortController; } +export const isSaveStarted = (group: SaveGroup) => group.total > 0; + +export const isSaveComplete = ({ total, success, failed }: SaveGroup) => + total == success + failed; + +export const isSaveCompleteWithErrors = (group: SaveGroup) => + group.failed > 0 && isSaveComplete(group); + +export const isSaveCancelled = (group: SaveGroup) => + group.canceller.signal.aborted; + export type SetFilesDownloadProgressAttributes = ( - value: - | Partial - | (( - prev: FilesDownloadProgressAttributes, - ) => FilesDownloadProgressAttributes), + value: Partial | ((prev: SaveGroup) => SaveGroup), ) => void; export type SetFilesDownloadProgressAttributesCreator = ( From 4e474d4f2984e4a263e843145cdf8d01eb183234 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 16:52:26 +0530 Subject: [PATCH 049/109] Sketch --- .../DownloadStatusNotifications.tsx | 64 +++++++++---------- web/apps/photos/src/pages/gallery.tsx | 7 +- web/apps/photos/src/pages/shared-albums.tsx | 10 ++- web/packages/gallery/services/save.ts | 21 +++++- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index 7c0c188438..f83035bdcd 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -17,7 +17,11 @@ interface DownloadStatusNotificationsProps { * {@link Notification} component that was showing the save group's status. */ saveGroups: SaveGroup[]; - setAttributesList: (value: SaveGroup[]) => void; + /** + * Called when the user closes the download status associated with the given + * {@link saveGroup}. + */ + onCloseSaveGroup: (saveGroup: SaveGroup) => void; /** * Called when the hidden section should be shown. * @@ -47,17 +51,13 @@ export const DownloadStatusNotifications: React.FC< DownloadStatusNotificationsProps > = ({ saveGroups, - setAttributesList, + onCloseSaveGroup, onShowHiddenSection, onShowCollection, }) => { const { showMiniDialog } = useBaseContext(); - const onClose = (id: number) => { - setAttributesList(saveGroups.filter((attr) => attr.id !== id)); - }; - - const confirmCancelDownload = (attributes: SaveGroup) => { + const confirmCancelDownload = (group: SaveGroup) => showMiniDialog({ title: t("stop_downloads_title"), message: t("stop_downloads_message"), @@ -65,39 +65,37 @@ export const DownloadStatusNotifications: React.FC< text: t("yes_stop_downloads"), color: "critical", action: () => { - attributes?.canceller.abort(); - onClose(attributes.id); + group?.canceller.abort(); + onCloseSaveGroup(group); }, }, cancel: t("no"), }); - }; - const handleClose = (attributes: SaveGroup) => () => { - if (isSaveComplete(attributes)) { - onClose(attributes.id); + const createOnClose = (group: SaveGroup) => () => { + if (isSaveComplete(group)) { + onCloseSaveGroup(group); } else { - confirmCancelDownload(attributes); + confirmCancelDownload(group); } }; - const createHandleOnClick = - (id: number, onShowCollection: (collectionID: number) => void) => - () => { - const attributes = saveGroups.find((attr) => attr.id === id); - const electron = globalThis.electron; - if (electron) { - electron.openDirectory(attributes.downloadDirPath); - } else if (onShowCollection) { - if (attributes.isHidden) { - void onShowHiddenSection().then(() => { - onShowCollection(attributes.collectionID); - }); - } else { - onShowCollection(attributes.collectionID); - } + const createOnClick = (group: SaveGroup) => () => { + const electron = globalThis.electron; + if (electron) { + electron.openDirectory(group.downloadDirPath); + } else if (onShowCollection) { + if (group.isHidden) { + void onShowHiddenSection().then(() => { + onShowCollection(group.collectionID); + }); + } else { + onShowCollection(group.collectionID); } - }; + } else { + return undefined; + } + }; if (!saveGroups) { return <>; @@ -117,7 +115,7 @@ export const DownloadStatusNotifications: React.FC< horizontal="left" sx={{ "&&": { bottom: `${index * 80 + 20}px` } }} open={isSaveStarted(group)} - onClose={handleClose(group)} + onClose={createOnClose(group)} keepOpenOnClick attributes={{ color: isSaveCompleteWithErrors(group) @@ -134,9 +132,7 @@ export const DownloadStatusNotifications: React.FC< count: group.success + group.failed, total: group.total, }), - onClick: onShowCollection - ? createHandleOnClick(group.id, onShowCollection) - : undefined, + onClick: createOnClick(group), }} />, ); diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index e0e95c0dc7..738508e5bc 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -42,6 +42,7 @@ import { savedAuthToken } from "ente-base/token"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; import type { + SaveGroup, SetFilesDownloadProgressAttributes, SetFilesDownloadProgressAttributesCreator, } from "ente-gallery/services/save"; @@ -631,6 +632,10 @@ const Page: React.FC = () => { }; }; + const handleCloseSaveGroup = useCallback(({id}) => setFilesDownloadProgressAttributesList((groups) => groups.filter((g) => g.id != id)), []); + + + const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator = useCallback((folderName, collectionID, isHidden) => { const id = Math.random(); @@ -997,7 +1002,7 @@ const Page: React.FC = () => { /> diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 030562840f..535060e4bb 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -135,6 +135,14 @@ export default function PublicCollectionGallery() { setFilesDownloadProgressAttributesList, ] = useState([]); + const handleCloseSaveGroup = useCallback( + ({ id }) => + setFilesDownloadProgressAttributesList((groups) => + groups.filter((g) => g.id != id), + ), + [], + ); + const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator = useCallback((folderName, collectionID, isHidden) => { const id = Math.random(); @@ -574,7 +582,7 @@ export default function PublicCollectionGallery() { /> diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 1d35e4a7a5..75ea519560 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -61,7 +61,16 @@ export interface SaveGroup { folderName: string; collectionID: number; isHidden: boolean; - downloadDirPath: string; + /** + * The path to a directory on the user's file system that was selected by + * the user to save the files in when they initiated the download on the + * desktop app. + * + * This property is only set when running in the context of the desktop app. + * The web app downloads to the user's default downloads folder, and when + * running in the web app this property will not be set. + */ + downloadDirPath?: string; /** * An {@link AbortController} that can be used to cancel the save. */ @@ -70,12 +79,22 @@ export interface SaveGroup { export const isSaveStarted = (group: SaveGroup) => group.total > 0; +/** + * Return `true` if there are no files in this save group that are pending. + */ export const isSaveComplete = ({ total, success, failed }: SaveGroup) => total == success + failed; +/** + * Return `true` if there are no files in this save group that are pending, but + * one or more files had failed to download. + */ export const isSaveCompleteWithErrors = (group: SaveGroup) => group.failed > 0 && isSaveComplete(group); +/** + * Return `true` if this save was cancelled on a user request. + */ export const isSaveCancelled = (group: SaveGroup) => group.canceller.signal.aborted; From 12f890a5011a84ea15aa299acbeb0605c293fcc7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 17:31:15 +0530 Subject: [PATCH 050/109] Sketch 2 --- web/packages/gallery/services/save.ts | 71 --------------------------- 1 file changed, 71 deletions(-) diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 75ea519560..10949c8aca 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -26,77 +26,6 @@ import { } from "ente-new/photos/utils/native-fs"; import { wait } from "ente-utils/promise"; -/** - * An object that keeps track of progress of a user-initiated download of a set - * of files to the user's device. - * - * This "download" is distinct from the downloads the app does from remote (e.g. - * when the user is viewing them). - * - * What we're doing here is perhaps more accurately described "a user initiated - * download of files to the user's device", but that is too long, so we instead - * refer to this process as "saving them". - * - * Note however that the app's UI itself takes the user perspective, so the - * upper (UI) layers use the word "download", while this implementation layer - * uses the word "save", and there is an unavoidable incongruity in the middle. - */ -export interface SaveGroup { - /** - * A unique identifier of this set of saves. - */ - id: number; - /** - * The total number of files to save to the user's device. - */ - total: number; - /** - * The number of files that have already been save. - */ - success: number; - /** - * The number of failures. - */ - failed: number; - folderName: string; - collectionID: number; - isHidden: boolean; - /** - * The path to a directory on the user's file system that was selected by - * the user to save the files in when they initiated the download on the - * desktop app. - * - * This property is only set when running in the context of the desktop app. - * The web app downloads to the user's default downloads folder, and when - * running in the web app this property will not be set. - */ - downloadDirPath?: string; - /** - * An {@link AbortController} that can be used to cancel the save. - */ - canceller: AbortController; -} - -export const isSaveStarted = (group: SaveGroup) => group.total > 0; - -/** - * Return `true` if there are no files in this save group that are pending. - */ -export const isSaveComplete = ({ total, success, failed }: SaveGroup) => - total == success + failed; - -/** - * Return `true` if there are no files in this save group that are pending, but - * one or more files had failed to download. - */ -export const isSaveCompleteWithErrors = (group: SaveGroup) => - group.failed > 0 && isSaveComplete(group); - -/** - * Return `true` if this save was cancelled on a user request. - */ -export const isSaveCancelled = (group: SaveGroup) => - group.canceller.signal.aborted; export type SetFilesDownloadProgressAttributes = ( value: Partial | ((prev: SaveGroup) => SaveGroup), From dcb4dd4944394753b6bd5f0d2b7f406fe1e2714a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 17:45:51 +0530 Subject: [PATCH 051/109] Sketch 3 --- .../DownloadStatusNotifications.tsx | 14 ++--- web/apps/photos/src/pages/gallery.tsx | 56 ++--------------- web/apps/photos/src/pages/shared-albums.tsx | 63 ++----------------- web/apps/photos/src/utils/file/index.ts | 3 +- 4 files changed, 18 insertions(+), 118 deletions(-) diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index f83035bdcd..a48afc16cd 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -4,7 +4,7 @@ import { isSaveCompleteWithErrors, isSaveStarted, type SaveGroup, -} from "ente-gallery/services/save"; +} from "ente-gallery/components/utils/save-groups"; import { Notification } from "ente-new/photos/components/Notification"; import { t } from "i18next"; @@ -21,7 +21,7 @@ interface DownloadStatusNotificationsProps { * Called when the user closes the download status associated with the given * {@link saveGroup}. */ - onCloseSaveGroup: (saveGroup: SaveGroup) => void; + onRemoveSaveGroup: (saveGroup: SaveGroup) => void; /** * Called when the hidden section should be shown. * @@ -51,7 +51,7 @@ export const DownloadStatusNotifications: React.FC< DownloadStatusNotificationsProps > = ({ saveGroups, - onCloseSaveGroup, + onRemoveSaveGroup, onShowHiddenSection, onShowCollection, }) => { @@ -66,7 +66,7 @@ export const DownloadStatusNotifications: React.FC< color: "critical", action: () => { group?.canceller.abort(); - onCloseSaveGroup(group); + onRemoveSaveGroup(group); }, }, cancel: t("no"), @@ -74,7 +74,7 @@ export const DownloadStatusNotifications: React.FC< const createOnClose = (group: SaveGroup) => () => { if (isSaveComplete(group)) { - onCloseSaveGroup(group); + onRemoveSaveGroup(group); } else { confirmCancelDownload(group); } @@ -125,9 +125,9 @@ export const DownloadStatusNotifications: React.FC< ? t("download_failed") : isSaveComplete(group) ? t("download_complete") - : t("downloading_album", { name: group.folderName }), + : t("downloading_album", { name: group.title }), caption: isSaveComplete(group) - ? group.folderName + ? group.title : t("download_progress", { count: group.success + group.failed, total: group.total, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 738508e5bc..52ae6589e1 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -4,10 +4,7 @@ import MenuIcon from "@mui/icons-material/Menu"; import { IconButton, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; -import { - DownloadStatusNotifications, - type FilesDownloadProgressAttributes, -} from "components/DownloadStatusNotifications"; +import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; import { type TimeStampListItem } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FixCreationTime } from "components/FixCreationTime"; @@ -41,11 +38,7 @@ import { import { savedAuthToken } from "ente-base/token"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; -import type { - SaveGroup, - SetFilesDownloadProgressAttributes, - SetFilesDownloadProgressAttributesCreator, -} from "ente-gallery/services/save"; +import { useSaveGroups } from "ente-gallery/components/utils/save-groups"; import { type Collection } from "ente-media/collection"; import { type EnteFile } from "ente-media/file"; import { type ItemVisibility } from "ente-media/file-metadata"; @@ -192,10 +185,7 @@ const Page: React.FC = () => { const [photoListHeader, setPhotoListHeader] = useState(null); - const [ - filesDownloadProgressAttributesList, - setFilesDownloadProgressAttributesList, - ] = useState([]); + const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); const [, setPostCreateAlbumOp] = useState( undefined, ); @@ -632,43 +622,6 @@ const Page: React.FC = () => { }; }; - const handleCloseSaveGroup = useCallback(({id}) => setFilesDownloadProgressAttributesList((groups) => groups.filter((g) => g.id != id)), []); - - - - const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator = - useCallback((folderName, collectionID, isHidden) => { - const id = Math.random(); - const updater: SetFilesDownloadProgressAttributes = (value) => { - setFilesDownloadProgressAttributesList((prev) => { - const attributes = prev?.find((attr) => attr.id === id); - const updatedAttributes = - typeof value == "function" - ? value(attributes) - : { ...attributes, ...value }; - const updatedAttributesList = attributes - ? prev.map((attr) => - attr.id === id ? updatedAttributes : attr, - ) - : [...prev, updatedAttributes]; - - return updatedAttributesList; - }); - }; - updater({ - id, - folderName, - collectionID, - isHidden, - canceller: null, - total: 0, - success: 0, - failed: 0, - downloadDirPath: null, - }); - return updater; - }, []); - const handleRemoveFilesFromCollection = (collection: Collection) => { void (async () => { showLoadingBar(); @@ -1001,8 +954,7 @@ const Page: React.FC = () => { } /> diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 535060e4bb..23124776e2 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -47,12 +47,11 @@ import { } from "ente-base/http"; import log from "ente-base/log"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; +import { useSaveGroups } from "ente-gallery/components/utils/save-groups"; import { downloadManager } from "ente-gallery/services/download"; import { downloadCollectionFiles, downloadSelectedFiles, - type SaveGroup, - type SetFilesDownloadProgressAttributes, type SetFilesDownloadProgressAttributesCreator, } from "ente-gallery/services/save"; import { extractCollectionKeyFromShareURL } from "ente-gallery/services/share"; @@ -129,52 +128,7 @@ export default function PublicCollectionGallery() { collectionID: 0, context: undefined, }); - - const [ - filesDownloadProgressAttributesList, - setFilesDownloadProgressAttributesList, - ] = useState([]); - - const handleCloseSaveGroup = useCallback( - ({ id }) => - setFilesDownloadProgressAttributesList((groups) => - groups.filter((g) => g.id != id), - ), - [], - ); - - const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator = - useCallback((folderName, collectionID, isHidden) => { - const id = Math.random(); - const updater: SetFilesDownloadProgressAttributes = (value) => { - setFilesDownloadProgressAttributesList((prev) => { - const attributes = prev?.find((attr) => attr.id === id); - const updatedAttributes = - typeof value == "function" - ? value(attributes) - : { ...attributes, ...value }; - const updatedAttributesList = attributes - ? prev.map((attr) => - attr.id === id ? updatedAttributes : attr, - ) - : [...prev, updatedAttributes]; - - return updatedAttributesList; - }); - }; - updater({ - id, - folderName, - collectionID, - isHidden, - canceller: null, - total: 0, - success: 0, - failed: 0, - downloadDirPath: null, - }); - return updater; - }, []); + const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); const onAddPhotos = useMemo(() => { return publicCollection?.publicURLs[0]?.enableCollect @@ -281,11 +235,7 @@ export default function PublicCollectionGallery() { setPhotoListHeader({ item: ( ), tag: "header", @@ -561,11 +511,9 @@ export default function PublicCollectionGallery() { selected={selected} setSelected={setSelected} activeCollectionID={PseudoCollectionID.all} - setFilesDownloadProgressAttributesCreator={ - setFilesDownloadProgressAttributesCreator - } onRemotePull={publicAlbumsRemotePull} onVisualFeedback={handleVisualFeedback} + onAddSaveGroup={onAddSaveGroup} /> {blockingLoad && } diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index d21dadf160..c61f569bb1 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,4 +1,5 @@ import type { LocalUser } from "ente-accounts/services/user"; +import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; import { type SetFilesDownloadProgressAttributesCreator, downloadSelectedFiles, @@ -60,7 +61,7 @@ export const performFileOp = async ( markTempHidden: (files: EnteFile[]) => void, clearTempHidden: () => void, fixCreationTime: (files: EnteFile[]) => void, - setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator, + onAddSaveGroup: AddSaveGroup, ) => { switch (op) { case "download": { From ec23e869e8dc922615bd5f66daeec39c6db1fcd9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 18:10:33 +0530 Subject: [PATCH 052/109] Sketch 4 --- web/apps/photos/src/pages/gallery.tsx | 2 +- web/apps/photos/src/utils/file/index.ts | 17 +-- web/packages/gallery/services/save.ts | 174 ++++++------------------ 3 files changed, 46 insertions(+), 147 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 52ae6589e1..b7e01b81eb 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -729,6 +729,7 @@ const Page: React.FC = () => { await performFileOp( op, toProcessFiles, + onAddSaveGroup, handleMarkTempDeleted, () => dispatch({ type: "clearTempDeleted" }), (files) => dispatch({ type: "markTempHidden", files }), @@ -737,7 +738,6 @@ const Page: React.FC = () => { setFixCreationTimeFiles(files); showFixCreationTime(); }, - setFilesDownloadProgressAttributesCreator, ); } // Apart from download, the other operations currently only work diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index c61f569bb1..23c35e9635 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,9 +1,6 @@ import type { LocalUser } from "ente-accounts/services/user"; import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; -import { - type SetFilesDownloadProgressAttributesCreator, - downloadSelectedFiles, -} from "ente-gallery/services/save"; +import { saveFiles } from "ente-gallery/services/save"; import type { EnteFile } from "ente-media/file"; import { ItemVisibility } from "ente-media/file-metadata"; import { type FileOp } from "ente-new/photos/components/SelectedFileOptions"; @@ -14,7 +11,6 @@ import { moveToTrash, } from "ente-new/photos/services/collection"; import { updateFilesVisibility } from "ente-new/photos/services/file"; -import { t } from "i18next"; import type { SelectedState } from "types/gallery"; export function getSelectedFiles( @@ -56,23 +52,16 @@ export const shouldShowAvatar = ( export const performFileOp = async ( op: FileOp, files: EnteFile[], + onAddSaveGroup: AddSaveGroup, markTempDeleted: (files: EnteFile[]) => void, clearTempDeleted: () => void, markTempHidden: (files: EnteFile[]) => void, clearTempHidden: () => void, fixCreationTime: (files: EnteFile[]) => void, - onAddSaveGroup: AddSaveGroup, ) => { switch (op) { case "download": { - const setSelectedFileDownloadProgressAttributes = - setFilesDownloadProgressAttributesCreator( - t("files_count", { count: files.length }), - ); - await downloadSelectedFiles( - files, - setSelectedFileDownloadProgressAttributes, - ); + await saveFiles(files, onAddSaveGroup); break; } case "fixTime": diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 10949c8aca..409a6cde67 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -25,127 +25,61 @@ import { safeFileName, } from "ente-new/photos/utils/native-fs"; import { wait } from "ente-utils/promise"; +import { t } from "i18next"; +import type { AddSaveGroup } from "../components/utils/save-groups"; - -export type SetFilesDownloadProgressAttributes = ( - value: Partial | ((prev: SaveGroup) => SaveGroup), -) => void; - -export type SetFilesDownloadProgressAttributesCreator = ( - folderName: string, - collectionID?: number, - isHidden?: boolean, -) => SetFilesDownloadProgressAttributes; - -export async function downloadFilesWithProgress( +/** + * Save the given {@link files} to the user's device. + * + * If we're running in the context of the web app, the files will be saved to + * the user's download folder. If we're running in the context of our desktop + * app, the user will be prompted to select a directory on their file system and + * the files will be saved therein. + * + * @param files The files to save. + * + * @param onAddSaveGroup A function that can be used to create a save group + * associated with the save. The newly added save group will correspond to a + * notification shown in the UI, and the progress and status of the save can be + * communicated by updating the save group's state using the updater function + * obtained when adding the save group. + */ +export async function saveFiles( files: EnteFile[], - downloadDirPath: string, - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, + onAddSaveGroup: AddSaveGroup, ) { - if (!files.length) { - return; - } - const canceller = new AbortController(); - const increaseSuccess = () => { - if (canceller.signal.aborted) return; - setFilesDownloadProgressAttributes((prev) => ({ - ...prev, - success: prev.success + 1, - })); - }; - const increaseFailed = () => { - if (canceller.signal.aborted) return; - setFilesDownloadProgressAttributes((prev) => ({ - ...prev, - failed: prev.failed + 1, - })); - }; - const isCancelled = () => canceller.signal.aborted; + const electron = globalThis.electron; - setFilesDownloadProgressAttributes({ + let downloadDirPath: string | undefined; + if (electron) { + downloadDirPath = await electron.selectDirectory(); + if (!downloadDirPath) { + // The user cancelled on the directory selection dialog. + return; + } + } + + const canceller = new AbortController(); + + const updateSaveGroup = onAddSaveGroup({ + title: t("files_count", { count: files.length }), downloadDirPath, - success: 0, - failed: 0, total: files.length, canceller, }); - const electron = globalThis.electron; - if (electron) { - await downloadFilesDesktop( - electron, - files, - { increaseSuccess, increaseFailed, isCancelled }, - downloadDirPath, - ); - } else { - await downloadFiles(files, { - increaseSuccess, - increaseFailed, - isCancelled, - }); - } -} - -export async function downloadSelectedFiles( - files: EnteFile[], - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - if (!files.length) { - return; - } - let downloadDirPath: string; - const electron = globalThis.electron; - if (electron) { - downloadDirPath = await electron.selectDirectory(); - if (!downloadDirPath) { - return; - } - } - await downloadFilesWithProgress( - files, - downloadDirPath, - setFilesDownloadProgressAttributes, - ); -} - -export async function downloadSingleFile( - file: EnteFile, - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - let downloadDirPath: string; - const electron = globalThis.electron; - if (electron) { - downloadDirPath = await electron.selectDirectory(); - if (!downloadDirPath) { - return; - } - } - await downloadFilesWithProgress( - [file], - downloadDirPath, - setFilesDownloadProgressAttributes, - ); -} - -export async function downloadFiles( - files: EnteFile[], - progressBarUpdater: { - increaseSuccess: () => void; - increaseFailed: () => void; - isCancelled: () => boolean; - }, -) { for (const file of files) { + if (canceller.signal.aborted) break; try { - if (progressBarUpdater?.isCancelled()) { - return; + if (electron && downloadDirPath) { + await saveFileDesktop(electron, file, downloadDirPath); + } else { + await saveAsFile(file); } - await saveAsFile(file); - progressBarUpdater?.increaseSuccess(); + updateSaveGroup((g) => ({ ...g, success: g.success + 1 })); } catch (e) { - log.error("download fail for file", e); - progressBarUpdater?.increaseFailed(); + log.error("File download failed", e); + updateSaveGroup((g) => ({ ...g, failed: g.failed + 1 })); } } } @@ -185,30 +119,6 @@ const createTypedObjectURL = async (blobPart: BlobPart, fileName: string) => { return URL.createObjectURL(new Blob([blob], { type: mimeType })); }; -async function downloadFilesDesktop( - electron: Electron, - files: EnteFile[], - progressBarUpdater: { - increaseSuccess: () => void; - increaseFailed: () => void; - isCancelled: () => boolean; - }, - downloadPath: string, -) { - for (const file of files) { - try { - if (progressBarUpdater?.isCancelled()) { - return; - } - await saveFileDesktop(electron, file, downloadPath); - progressBarUpdater?.increaseSuccess(); - } catch (e) { - log.error("download fail for file", e); - progressBarUpdater?.increaseFailed(); - } - } -} - /** * Save a file to the given {@link directoryPath} using native filesystem APIs. * From 3b273a9e7b248f10d233ad801229480667e46c48 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 19:41:35 +0530 Subject: [PATCH 053/109] Sketch 6 --- .../Collections/CollectionHeader.tsx | 50 +++-- .../Collections/GalleryBarAndListHeader.tsx | 29 +-- .../src/components/FileListWithViewer.tsx | 23 +- web/apps/photos/src/pages/gallery.tsx | 8 +- web/apps/photos/src/pages/shared-albums.tsx | 38 ++-- web/apps/photos/src/utils/file/index.ts | 9 +- .../gallery/components/utils/save-groups.ts | 157 ++++++++++++++ web/packages/gallery/services/save.ts | 203 ++++++++---------- 8 files changed, 326 insertions(+), 191 deletions(-) create mode 100644 web/packages/gallery/components/utils/save-groups.ts diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index bbf99742bc..800df6edc2 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -25,6 +25,9 @@ import { import { SingleInputDialog } from "ente-base/components/SingleInputDialog"; import { useModalVisibility } from "ente-base/components/utils/modal"; import { useBaseContext } from "ente-base/context"; +import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; +import { downloadAndSaveCollectionFiles } from "ente-gallery/services/save"; +import { uniqueFilesByID } from "ente-gallery/utils/file"; import { CollectionOrder, type Collection } from "ente-media/collection"; import { ItemVisibility } from "ente-media/file-metadata"; import type { RemotePullOpts } from "ente-new/photos/components/gallery"; @@ -33,7 +36,9 @@ import { GalleryItemsSummary, } from "ente-new/photos/components/gallery/ListHeader"; import { + defaultHiddenCollectionUserFacingName, deleteCollection, + findDefaultHiddenCollectionIDs, isHiddenCollection, leaveSharedCollection, renameCollection, @@ -46,16 +51,15 @@ import { type CollectionSummary, type CollectionSummaryType, } from "ente-new/photos/services/collection-summary"; +import { + savedCollectionFiles, + savedCollections, +} from "ente-new/photos/services/photos-fdb"; import { emptyTrash } from "ente-new/photos/services/trash"; import { usePhotosAppContext } from "ente-new/photos/types/context"; import { t } from "i18next"; import React, { useCallback, useRef } from "react"; import { Trans } from "react-i18next"; -import type { SetFilesDownloadProgressAttributesCreator } from "types/gallery"; -import { - downloadCollectionHelper, - downloadDefaultHiddenCollectionHelper, -} from "ente-gallery/services/save"; export interface CollectionHeaderProps { collectionSummary: CollectionSummary; @@ -69,7 +73,11 @@ export interface CollectionHeaderProps { onRemotePull: (opts?: RemotePullOpts) => Promise; onCollectionShare: () => void; onCollectionCast: () => void; - setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator; + /** + * A function that can be used to create a UI notification to track the + * progress of user-initiated download, and to cancel it if needed. + */ + onAddSaveGroup: AddSaveGroup; } /** @@ -119,7 +127,7 @@ const CollectionHeaderOptions: React.FC = ({ onRemotePull, onCollectionShare, onCollectionCast, - setFilesDownloadProgressAttributesCreator, + onAddSaveGroup, isActiveCollectionDownloadInProgress, }) => { const { showMiniDialog, onGenericError } = useBaseContext(); @@ -225,17 +233,31 @@ const CollectionHeaderOptions: React.FC = ({ if (isActiveCollectionDownloadInProgress()) return; if (collectionSummaryType == "hiddenItems") { - await downloadDefaultHiddenCollectionHelper( - setFilesDownloadProgressAttributesCreator, + const defaultHiddenCollectionsIDs = findDefaultHiddenCollectionIDs( + await savedCollections(), + ); + const collectionFiles = await savedCollectionFiles(); + const defaultHiddenCollectionFiles = uniqueFilesByID( + collectionFiles.filter((file) => + defaultHiddenCollectionsIDs.has(file.collectionID), + ), + ); + await downloadAndSaveCollectionFiles( + defaultHiddenCollectionUserFacingName, + PseudoCollectionID.hiddenItems, + defaultHiddenCollectionFiles, + true, + onAddSaveGroup, ); } else { - await downloadCollectionHelper( + await downloadAndSaveCollectionFiles( + activeCollection.name, activeCollection.id, - setFilesDownloadProgressAttributesCreator( - activeCollection.name, - activeCollection.id, - isHiddenCollection(activeCollection), + (await savedCollectionFiles()).filter( + (file) => file.collectionID == activeCollection.id, ), + isHiddenCollection(activeCollection), + onAddSaveGroup, ); } }; diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 594113060b..0aaeb27c80 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -6,10 +6,10 @@ import { import type { TimeStampListItem } from "components/FileList"; import { useModalVisibility } from "ente-base/components/utils/modal"; import { - isFilesDownloadCancelled, + isSaveCancelled, isSaveComplete, type SaveGroup, -} from "ente-gallery/services/save"; +} from "ente-gallery/components/utils/save-groups"; import type { Collection } from "ente-media/collection"; import { GalleryBarImpl, @@ -47,11 +47,8 @@ type GalleryBarAndListHeaderProps = Omit< activeCollection: Collection; setActiveCollectionID: (collectionID: number) => void; setPhotoListHeader: (value: TimeStampListItem) => void; - filesDownloadProgressAttributesList: SaveGroup[]; -} & Pick< - CollectionHeaderProps, - "setFilesDownloadProgressAttributesCreator" | "onRemotePull" - > & + saveGroups: SaveGroup[]; +} & Pick & Pick< CollectionShareProps, "user" | "emailByUserID" | "shareSuggestionEmails" | "setBlockingLoad" @@ -88,14 +85,14 @@ export const GalleryBarAndListHeader: React.FC< setActiveCollectionID, setBlockingLoad, people, + saveGroups, activePerson, emailByUserID, shareSuggestionEmails, onRemotePull, + onAddSaveGroup, onSelectPerson, setPhotoListHeader, - filesDownloadProgressAttributesList, - setFilesDownloadProgressAttributesCreator, }) => { const { show: showAllAlbums, props: allAlbumsVisibilityProps } = useModalVisibility(); @@ -125,15 +122,11 @@ export const GalleryBarAndListHeader: React.FC< ); const isActiveCollectionDownloadInProgress = useCallback(() => { - const attributes = filesDownloadProgressAttributesList.find( - (attr) => attr.collectionID === activeCollectionID, + const group = saveGroups.find( + (g) => g.collectionID == activeCollectionID, ); - return ( - attributes && - !isFilesDownloadCancelled(attributes) && - !isSaveComplete(attributes) - ); - }, [activeCollectionID, filesDownloadProgressAttributesList]); + return group && !isSaveCancelled(group) && !isSaveComplete(group); + }, [saveGroups, activeCollectionID]); useEffect(() => { if (shouldHide) return; @@ -145,9 +138,9 @@ export const GalleryBarAndListHeader: React.FC< {...{ activeCollection, setActiveCollectionID, - setFilesDownloadProgressAttributesCreator, isActiveCollectionDownloadInProgress, onRemotePull, + onAddSaveGroup, }} collectionSummary={toShowCollectionSummaries.get( activeCollectionID, diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 7881e2d2f2..0d72cf0c69 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -1,14 +1,12 @@ import { styled } from "@mui/material"; import { isSameDay } from "ente-base/date"; import { formattedDate } from "ente-base/i18n-date"; +import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; import { FileViewer, type FileViewerProps, } from "ente-gallery/components/viewer/FileViewer"; -import { - downloadSingleFile, - type SetFilesDownloadProgressAttributesCreator, -} from "ente-gallery/services/save"; +import { downloadAndSaveFiles } from "ente-gallery/services/save"; import type { Collection } from "ente-media/collection"; import type { EnteFile } from "ente-media/file"; import { fileCreationTime, fileFileName } from "ente-media/file-metadata"; @@ -40,7 +38,6 @@ export type FileListWithViewerProps = { * Not set in the context of the shared albums app. */ onMarkTempDeleted?: (files: EnteFile[]) => void; - setFilesDownloadProgressAttributesCreator?: SetFilesDownloadProgressAttributesCreator; /** * Called when the visibility of the file viewer dialog changes. */ @@ -50,6 +47,11 @@ export type FileListWithViewerProps = { * pull from remote. */ onRemotePull: () => Promise; + /** + * A function that can be used to create a UI notification to track the + * progress of user-initiated download, and to cancel it if needed. + */ + onAddSaveGroup: AddSaveGroup; } & Pick< FileListProps, | "mode" @@ -109,11 +111,11 @@ export const FileListWithViewer: React.FC = ({ collectionNameByID, pendingFavoriteUpdates, pendingVisibilityUpdates, - setFilesDownloadProgressAttributesCreator, onSetOpenFileViewer, onRemotePull, onRemoteFilesPull, onVisualFeedback, + onAddSaveGroup, onToggleFavorite, onFileVisibilityUpdate, onMarkTempDeleted, @@ -149,12 +151,9 @@ export const FileListWithViewer: React.FC = ({ ); const handleDownload = useCallback( - (file: EnteFile) => { - const setSingleFileDownloadProgress = - setFilesDownloadProgressAttributesCreator!(fileFileName(file)); - void downloadSingleFile(file, setSingleFileDownloadProgress); - }, - [setFilesDownloadProgressAttributesCreator], + (file: EnteFile) => + downloadAndSaveFiles([file], fileFileName(file), onAddSaveGroup), + [onAddSaveGroup], ); const handleDelete = useMemo(() => { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index b7e01b81eb..51bd8a3324 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1024,8 +1024,8 @@ const Page: React.FC = () => { activeCollectionID, activePerson, setPhotoListHeader, - setFilesDownloadProgressAttributesCreator, - filesDownloadProgressAttributesList, + saveGroups, + onAddSaveGroup, }} mode={barMode} shouldHide={isInSearchMode} @@ -1122,11 +1122,9 @@ const Page: React.FC = () => { fileNormalCollectionIDs, pendingFavoriteUpdates, pendingVisibilityUpdates, + onAddSaveGroup, }} emailByUserID={state.emailByUserID} - setFilesDownloadProgressAttributesCreator={ - setFilesDownloadProgressAttributesCreator - } onToggleFavorite={handleFileViewerToggleFavorite} onFileVisibilityUpdate={ handleFileViewerFileVisibilityUpdate diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 23124776e2..7d1e52b8a3 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -47,12 +47,14 @@ import { } from "ente-base/http"; import log from "ente-base/log"; import { FullScreenDropZone } from "ente-gallery/components/FullScreenDropZone"; -import { useSaveGroups } from "ente-gallery/components/utils/save-groups"; +import { + useSaveGroups, + type AddSaveGroup, +} from "ente-gallery/components/utils/save-groups"; import { downloadManager } from "ente-gallery/services/download"; import { - downloadCollectionFiles, - downloadSelectedFiles, - type SetFilesDownloadProgressAttributesCreator, + downloadAndSaveCollectionFiles, + downloadAndSaveFiles, } from "ente-gallery/services/save"; import { extractCollectionKeyFromShareURL } from "ente-gallery/services/share"; import { updateShouldDisableCFUploadProxy } from "ente-gallery/services/upload"; @@ -410,13 +412,10 @@ export default function PublicCollectionGallery() { const downloadFilesHelper = async () => { try { const selectedFiles = getSelectedFiles(selected, publicFiles); - const setFilesDownloadProgressAttributes = - setFilesDownloadProgressAttributesCreator( - t("files_count", { count: selectedFiles.length }), - ); - await downloadSelectedFiles( + await downloadAndSaveFiles( selectedFiles, - setFilesDownloadProgressAttributes, + t("files_count", { count: selectedFiles.length }), + onAddSaveGroup, ); clearSelection(); } catch (e) { @@ -634,30 +633,25 @@ const SelectedFileOptions: React.FC = ({ interface ListHeaderProps { publicCollection: Collection; publicFiles: EnteFile[]; - setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator; + onAddSaveGroup: AddSaveGroup; } const ListHeader: React.FC = ({ publicCollection, publicFiles, - setFilesDownloadProgressAttributesCreator, + onAddSaveGroup, }) => { const downloadEnabled = publicCollection.publicURLs?.[0]?.enableDownload ?? true; - const downloadAllFiles = async () => { - const setFilesDownloadProgressAttributes = - setFilesDownloadProgressAttributesCreator( - publicCollection.name, - publicCollection.id, - isHiddenCollection(publicCollection), - ); - await downloadCollectionFiles( + const downloadAllFiles = () => + downloadAndSaveCollectionFiles( publicCollection.name, + publicCollection.id, publicFiles, - setFilesDownloadProgressAttributes, + isHiddenCollection(publicCollection), + onAddSaveGroup, ); - }; return ( diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 23c35e9635..b2d3199bb7 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,6 +1,6 @@ import type { LocalUser } from "ente-accounts/services/user"; import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; -import { saveFiles } from "ente-gallery/services/save"; +import { downloadAndSaveFiles } from "ente-gallery/services/save"; import type { EnteFile } from "ente-media/file"; import { ItemVisibility } from "ente-media/file-metadata"; import { type FileOp } from "ente-new/photos/components/SelectedFileOptions"; @@ -11,6 +11,7 @@ import { moveToTrash, } from "ente-new/photos/services/collection"; import { updateFilesVisibility } from "ente-new/photos/services/file"; +import { t } from "i18next"; import type { SelectedState } from "types/gallery"; export function getSelectedFiles( @@ -61,7 +62,11 @@ export const performFileOp = async ( ) => { switch (op) { case "download": { - await saveFiles(files, onAddSaveGroup); + await downloadAndSaveFiles( + files, + t("files_count", { count: files.length }), + onAddSaveGroup, + ); break; } case "fixTime": diff --git a/web/packages/gallery/components/utils/save-groups.ts b/web/packages/gallery/components/utils/save-groups.ts new file mode 100644 index 0000000000..3a6fc788dc --- /dev/null +++ b/web/packages/gallery/components/utils/save-groups.ts @@ -0,0 +1,157 @@ +import { useCallback, useState } from "react"; + +/** + * An object that keeps track of progress of a user-initiated download of a set + * of files to the user's device. + * + * This "download" is distinct from the downloads the app does from remote (e.g. + * when the user is viewing them). + * + * What we're doing here is perhaps more accurately described "a user initiated + * download of files to the user's device", but that is too long, so we instead + * refer to this process as "saving them". + * + * Note however that the app's UI itself takes the user perspective, so the + * upper (UI) layers use the word "download", while this implementation layer + * uses the word "save", and there is an unavoidable incongruity in the middle. + */ +export interface SaveGroup { + /** + * A randomly generated unique identifier of this set of saves. + */ + id: number; + /** + * The user visible title of the save group. + * + * Depending on the context can either be an auto generated string (e.g "5 + * files"), or the name of the collection which is being downloaded. + */ + title: string; + /** + * If this save group is associated with a collection, then the ID of the + * collection. + */ + collectionID?: number; + /** + * `true` if the collection associated with the save group is a hidden + * collection. + */ + isHidden?: boolean; + /** + * The path to a directory on the user's file system that was selected by + * the user to save the files in when they initiated the download on the + * desktop app. + * + * This property is only set when running in the context of the desktop app. + * The web app downloads to the user's default downloads folder, and when + * running in the web app this property will not be set. + */ + downloadDirPath?: string; + /** + * The total number of files to save to the user's device. + */ + total: number; + /** + * The number of files that have already been save. + */ + success: number; + /** + * The number of failures. + */ + failed: number; + /** + * An {@link AbortController} that can be used to cancel the save. + */ + canceller?: AbortController; +} + +export const isSaveStarted = (group: SaveGroup) => group.total > 0; + +/** + * Return `true` if there are no files in this save group that are pending. + */ +export const isSaveComplete = ({ total, success, failed }: SaveGroup) => + total == success + failed; + +/** + * Return `true` if there are no files in this save group that are pending, but + * one or more files had failed to download. + */ +export const isSaveCompleteWithErrors = (group: SaveGroup) => + group.failed > 0 && isSaveComplete(group); + +/** + * Return `true` if this save was cancelled on a user request. + */ +export const isSaveCancelled = (group: SaveGroup) => + group.canceller?.signal.aborted; + +/** + * A function that can be used to add a save group. + * + * It returns a function that can subsequently be used to update the save group + * by applying a transform to it (see {@link UpdateSaveGroup}). The UI will + * react and update itself on updates done this way. + */ +export type AddSaveGroup = (group: Partial) => UpdateSaveGroup; + +/** + * A function that can be used to update a instance of a save group by applying + * the provided transform. + * + * This is obtained by a call to an instance of {@link AddSaveGroup}. The UI + * will update itself to reflect the changes made by the transform. + */ +export type UpdateSaveGroup = ( + tranform: (prev: SaveGroup) => SaveGroup, +) => void; + +/** + * A function that can be used to remove a save group. + * + * Save groups can be removed both on user actions - if the user presses the + * close button to discard the notification showing the status of the save group + * (cancelling it if needed) - or programmatically, if it is found that there + * are no files that need saving for a particular request. + */ +export type RemoveSaveGroup = (saveGroup: SaveGroup) => void; + +/** + * A custom React hook that manages a list of active {@link SaveGroup}s, and + * provides functions to add and remove entries to the list. + */ +export const useSaveGroups = () => { + const [saveGroups, setSaveGroups] = useState([]); + + const handleAddSaveGroup: AddSaveGroup = useCallback((saveGroup) => { + const id = Math.random(); + setSaveGroups((groups) => [ + ...groups, + { + ...saveGroup, + id, + // TODO(RE): + title: saveGroup.title ?? "", + total: saveGroup.total ?? 0, + success: 0, + failed: 0, + }, + ]); + return (tx: (group: SaveGroup) => SaveGroup) => { + setSaveGroups((groups) => + groups.map((g) => (g.id == id ? tx(g) : g)), + ); + }; + }, []); + + const handleRemoveSaveGroup: RemoveSaveGroup = useCallback( + ({ id }) => setSaveGroups((groups) => groups.filter((g) => g.id != id)), + [], + ); + + return { + saveGroups, + onAddSaveGroup: handleAddSaveGroup, + onRemoveSaveGroup: handleRemoveSaveGroup, + }; +}; diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 409a6cde67..517388c88f 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -1,31 +1,19 @@ -import { ensureElectron } from "ente-base/electron"; import { joinPath } from "ente-base/file-name"; import log from "ente-base/log"; import { type Electron } from "ente-base/types/ipc"; import { saveAsFileAndRevokeObjectURL } from "ente-base/utils/web"; import { downloadManager } from "ente-gallery/services/download"; import { detectFileTypeInfo } from "ente-gallery/utils/detect-type"; -import { uniqueFilesByID } from "ente-gallery/utils/file"; import { writeStream } from "ente-gallery/utils/native-stream"; import type { EnteFile } from "ente-media/file"; import { fileFileName } from "ente-media/file-metadata"; import { FileType } from "ente-media/file-type"; import { decodeLivePhoto } from "ente-media/live-photo"; -import { - defaultHiddenCollectionUserFacingName, - findDefaultHiddenCollectionIDs, -} from "ente-new/photos/services/collection"; -import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; -import { - savedCollectionFiles, - savedCollections, -} from "ente-new/photos/services/photos-fdb"; import { safeDirectoryName, safeFileName, } from "ente-new/photos/utils/native-fs"; import { wait } from "ente-utils/promise"; -import { t } from "i18next"; import type { AddSaveGroup } from "../components/utils/save-groups"; /** @@ -38,16 +26,60 @@ import type { AddSaveGroup } from "../components/utils/save-groups"; * * @param files The files to save. * + * @param title A title to show in the UI notification that indicates the + * progress of the save. + * * @param onAddSaveGroup A function that can be used to create a save group * associated with the save. The newly added save group will correspond to a * notification shown in the UI, and the progress and status of the save can be * communicated by updating the save group's state using the updater function * obtained when adding the save group. */ -export async function saveFiles( +export const downloadAndSaveFiles = ( files: EnteFile[], + title: string, onAddSaveGroup: AddSaveGroup, -) { +) => downloadAndSave(files, title, onAddSaveGroup); + +/** + * Save all the files of a collection to the user's device. + * + * This is a variant of {@link downloadAndSaveFiles}, except instead of taking a + * list of files to save, this variant is tailored for saving saves all the + * files that belong to a collection. Otherwise, it broadly behaves similarly; + * see that method's documentation for more details. + * + * When running in the context of the desktop app, instead of saving the files + * in the directory selected by the user, files are saved in a directory with + * the same name as the collection. + */ +export const downloadAndSaveCollectionFiles = async ( + collectionName: string, + collectionID: number | undefined, + files: EnteFile[], + isHidden: boolean, + onAddSaveGroup: AddSaveGroup, +) => + downloadAndSave( + files, + collectionName, + onAddSaveGroup, + collectionName, + collectionID, + isHidden, + ); + +/** + * The lower level primitive that the public API of this module delegates to. + */ +const downloadAndSave = async ( + files: EnteFile[], + title: string, + onAddSaveGroup: AddSaveGroup, + collectionName?: string, + collectionID?: number, + isHidden?: boolean, +) => { const electron = globalThis.electron; let downloadDirPath: string | undefined; @@ -57,14 +89,24 @@ export async function saveFiles( // The user cancelled on the directory selection dialog. return; } + if (collectionName) { + downloadDirPath = await mkdirCollectionDownloadFolder( + electron, + downloadDirPath, + collectionName, + ); + } } const canceller = new AbortController(); + const total = files.length; const updateSaveGroup = onAddSaveGroup({ - title: t("files_count", { count: files.length }), + title, + collectionID, + isHidden, downloadDirPath, - total: files.length, + total, canceller, }); @@ -82,7 +124,7 @@ export async function saveFiles( updateSaveGroup((g) => ({ ...g, failed: g.failed + 1 })); } } -} +}; /** * Save the given {@link EnteFile} as a file in the user's download folder. @@ -119,6 +161,32 @@ const createTypedObjectURL = async (blobPart: BlobPart, fileName: string) => { return URL.createObjectURL(new Blob([blob], { type: mimeType })); }; +/** + * Create a new directory on the user's file system with the same name as the + * provided {@link collectionName} under the provided {@link downloadDirPath}, + * and return the full path to the created directory. + * + * This function can be used only when running in the context of our desktop + * app, and so such requires an {@link Electron} instance as the witness. + */ +const mkdirCollectionDownloadFolder = async ( + { fs }: Electron, + downloadDirPath: string, + collectionName: string, +) => { + const collectionDownloadName = await safeDirectoryName( + downloadDirPath, + collectionName, + fs.exists, + ); + const collectionDownloadPath = joinPath( + downloadDirPath, + collectionDownloadName, + ); + await fs.mkdirIfNeeded(collectionDownloadPath); + return collectionDownloadPath; +}; + /** * Save a file to the given {@link directoryPath} using native filesystem APIs. * @@ -171,104 +239,3 @@ const saveFileDesktop = async ( await writeStreamToFile(await createExportName(fileName), stream); } }; - -export async function downloadCollectionHelper( - collectionID: number, - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - try { - const allFiles = await savedCollectionFiles(); - const collectionFiles = allFiles.filter( - (file) => file.collectionID == collectionID, - ); - const allCollections = await savedCollections(); - const collection = allCollections.find( - (collection) => collection.id == collectionID, - ); - if (!collection) { - throw Error("collection not found"); - } - await downloadCollectionFiles( - collection.name, - collectionFiles, - setFilesDownloadProgressAttributes, - ); - } catch (e) { - log.error("download collection failed ", e); - } -} - -export async function downloadDefaultHiddenCollectionHelper( - setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator, -) { - try { - const defaultHiddenCollectionsIDs = findDefaultHiddenCollectionIDs( - await savedCollections(), - ); - const collectionFiles = await savedCollectionFiles(); - const defaultHiddenCollectionFiles = uniqueFilesByID( - collectionFiles.filter((file) => - defaultHiddenCollectionsIDs.has(file.collectionID), - ), - ); - const setFilesDownloadProgressAttributes = - setFilesDownloadProgressAttributesCreator( - defaultHiddenCollectionUserFacingName, - PseudoCollectionID.hiddenItems, - true, - ); - - await downloadCollectionFiles( - defaultHiddenCollectionUserFacingName, - defaultHiddenCollectionFiles, - setFilesDownloadProgressAttributes, - ); - } catch (e) { - log.error("download hidden files failed ", e); - } -} - -export async function downloadCollectionFiles( - collectionName: string, - collectionFiles: EnteFile[], - setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes, -) { - if (!collectionFiles.length) { - return; - } - let downloadDirPath: string; - const electron = globalThis.electron; - if (electron) { - const selectedDir = await electron.selectDirectory(); - if (!selectedDir) { - return; - } - downloadDirPath = await createCollectionDownloadFolder( - selectedDir, - collectionName, - ); - } - await downloadFilesWithProgress( - collectionFiles, - downloadDirPath, - setFilesDownloadProgressAttributes, - ); -} - -async function createCollectionDownloadFolder( - downloadDirPath: string, - collectionName: string, -) { - const fs = ensureElectron().fs; - const collectionDownloadName = await safeDirectoryName( - downloadDirPath, - collectionName, - fs.exists, - ); - const collectionDownloadPath = joinPath( - downloadDirPath, - collectionDownloadName, - ); - await fs.mkdirIfNeeded(collectionDownloadPath); - return collectionDownloadPath; -} From 4445ef8aef826a2895bc38587eb0dd1399f0fa6d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 4 Jul 2025 19:51:16 +0530 Subject: [PATCH 054/109] Tweaks --- .../Collections/GalleryBarAndListHeader.tsx | 4 +-- .../DownloadStatusNotifications.tsx | 6 ++-- .../gallery/components/utils/save-groups.ts | 12 ++++---- web/packages/gallery/services/save.ts | 28 +++++++++---------- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 0aaeb27c80..5ce5eddfd8 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -123,9 +123,9 @@ export const GalleryBarAndListHeader: React.FC< const isActiveCollectionDownloadInProgress = useCallback(() => { const group = saveGroups.find( - (g) => g.collectionID == activeCollectionID, + (g) => g.collectionSummaryID === activeCollectionID, ); - return group && !isSaveCancelled(group) && !isSaveComplete(group); + return group && !isSaveComplete(group) && !isSaveCancelled(group); }, [saveGroups, activeCollectionID]); useEffect(() => { diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index a48afc16cd..9872a858a3 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -85,12 +85,12 @@ export const DownloadStatusNotifications: React.FC< if (electron) { electron.openDirectory(group.downloadDirPath); } else if (onShowCollection) { - if (group.isHidden) { + if (group.isHiddenCollectionSummary) { void onShowHiddenSection().then(() => { - onShowCollection(group.collectionID); + onShowCollection(group.collectionSummaryID); }); } else { - onShowCollection(group.collectionID); + onShowCollection(group.collectionSummaryID); } } else { return undefined; diff --git a/web/packages/gallery/components/utils/save-groups.ts b/web/packages/gallery/components/utils/save-groups.ts index 3a6fc788dc..f37fd40b69 100644 --- a/web/packages/gallery/components/utils/save-groups.ts +++ b/web/packages/gallery/components/utils/save-groups.ts @@ -28,15 +28,15 @@ export interface SaveGroup { */ title: string; /** - * If this save group is associated with a collection, then the ID of the - * collection. + * If this save group is associated with a {@link CollectionSummary}, then + * the ID of that collection summary. */ - collectionID?: number; + collectionSummaryID?: number; /** - * `true` if the collection associated with the save group is a hidden - * collection. + * `true` if the collection summary associated with the save group is + * hidden. */ - isHidden?: boolean; + isHiddenCollectionSummary?: boolean; /** * The path to a directory on the user's file system that was selected by * the user to save the files in when they initiated the download on the diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 517388c88f..1eb1701087 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -54,19 +54,19 @@ export const downloadAndSaveFiles = ( * the same name as the collection. */ export const downloadAndSaveCollectionFiles = async ( - collectionName: string, - collectionID: number | undefined, + collectionSummaryName: string, + collectionSummaryID: number, files: EnteFile[], - isHidden: boolean, + isHiddenCollectionSummary: boolean, onAddSaveGroup: AddSaveGroup, ) => downloadAndSave( files, - collectionName, + collectionSummaryName, onAddSaveGroup, - collectionName, - collectionID, - isHidden, + collectionSummaryName, + collectionSummaryID, + isHiddenCollectionSummary, ); /** @@ -76,9 +76,9 @@ const downloadAndSave = async ( files: EnteFile[], title: string, onAddSaveGroup: AddSaveGroup, - collectionName?: string, - collectionID?: number, - isHidden?: boolean, + collectionSummaryName?: string, + collectionSummaryID?: number, + isHiddenCollectionSummary?: boolean, ) => { const electron = globalThis.electron; @@ -89,11 +89,11 @@ const downloadAndSave = async ( // The user cancelled on the directory selection dialog. return; } - if (collectionName) { + if (collectionSummaryName) { downloadDirPath = await mkdirCollectionDownloadFolder( electron, downloadDirPath, - collectionName, + collectionSummaryName, ); } } @@ -103,8 +103,8 @@ const downloadAndSave = async ( const updateSaveGroup = onAddSaveGroup({ title, - collectionID, - isHidden, + collectionSummaryID, + isHiddenCollectionSummary, downloadDirPath, total, canceller, From 7e93b52e5e6e9e324448e802653e6383cdeffaa0 Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Fri, 4 Jul 2025 23:35:12 +0530 Subject: [PATCH 055/109] Clear selected file after share action --- .../actions/file_selection_actions_widget.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart index 548395a9c4..0103dece2e 100644 --- a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -480,11 +480,7 @@ class _FileSelectionActionsWidgetState labelText: S.of(context).share, icon: Icons.adaptive.share_outlined, key: shareButtonKey, - onTap: () => shareSelected( - context, - shareButtonKey, - widget.selectedFiles.files.toList(), - ), + onTap: _shareSelectedFiles, ), ); } @@ -524,6 +520,15 @@ class _FileSelectionActionsWidgetState return const SizedBox(); } + Future _shareSelectedFiles() async { + shareSelected( + context, + shareButtonKey, + widget.selectedFiles.files.toList(), + ); + widget.selectedFiles.clearAll(); + } + Future _moveFiles() async { if (split.pendingUploads.isNotEmpty || split.ownedByOtherUsers.isNotEmpty) { widget.selectedFiles From 574803937113a7b3f66b19aac5cfd92f8e81d50b Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Fri, 4 Jul 2025 23:37:52 +0530 Subject: [PATCH 056/109] Revert "Clear selected file after share action" This reverts commit 7e93b52e5e6e9e324448e802653e6383cdeffaa0. --- .../actions/file_selection_actions_widget.dart | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart index 0103dece2e..548395a9c4 100644 --- a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -480,7 +480,11 @@ class _FileSelectionActionsWidgetState labelText: S.of(context).share, icon: Icons.adaptive.share_outlined, key: shareButtonKey, - onTap: _shareSelectedFiles, + onTap: () => shareSelected( + context, + shareButtonKey, + widget.selectedFiles.files.toList(), + ), ), ); } @@ -520,15 +524,6 @@ class _FileSelectionActionsWidgetState return const SizedBox(); } - Future _shareSelectedFiles() async { - shareSelected( - context, - shareButtonKey, - widget.selectedFiles.files.toList(), - ); - widget.selectedFiles.clearAll(); - } - Future _moveFiles() async { if (split.pendingUploads.isNotEmpty || split.ownedByOtherUsers.isNotEmpty) { widget.selectedFiles From bf644477dce6e38fe294422e13627f567e86233c Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Fri, 4 Jul 2025 23:39:12 +0530 Subject: [PATCH 057/109] Clear selectedFiles after share action --- .../actions/file_selection_actions_widget.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart index 548395a9c4..0103dece2e 100644 --- a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -480,11 +480,7 @@ class _FileSelectionActionsWidgetState labelText: S.of(context).share, icon: Icons.adaptive.share_outlined, key: shareButtonKey, - onTap: () => shareSelected( - context, - shareButtonKey, - widget.selectedFiles.files.toList(), - ), + onTap: _shareSelectedFiles, ), ); } @@ -524,6 +520,15 @@ class _FileSelectionActionsWidgetState return const SizedBox(); } + Future _shareSelectedFiles() async { + shareSelected( + context, + shareButtonKey, + widget.selectedFiles.files.toList(), + ); + widget.selectedFiles.clearAll(); + } + Future _moveFiles() async { if (split.pendingUploads.isNotEmpty || split.ownedByOtherUsers.isNotEmpty) { widget.selectedFiles From 87f04dbaa61f214c13fba30f4833c84a5f0f8d31 Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Fri, 4 Jul 2025 23:40:29 +0530 Subject: [PATCH 058/109] Minor refractor --- .../file_selection_actions_widget.dart | 78 ++++++++++--------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart index 0103dece2e..8afde97947 100644 --- a/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/apps/photos/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -423,44 +423,7 @@ class _FileSelectionActionsWidgetState ), labelText: S.of(context).editLocation, icon: Icons.edit_location_alt_outlined, - onTap: () async { - await showBarModalBottomSheet( - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(5), - ), - ), - backgroundColor: getEnteColorScheme(context).backgroundElevated, - barrierColor: backdropFaintDark, - topControl: Stack( - alignment: Alignment.bottomCenter, - children: [ - // This container is for increasing the tap area - Container( - width: double.infinity, - height: 36, - color: Colors.transparent, - ), - Container( - height: 5, - width: 40, - decoration: const BoxDecoration( - color: backgroundElevated2Light, - borderRadius: BorderRadius.all( - Radius.circular(5), - ), - ), - ), - ], - ), - context: context, - builder: (context) { - return UpdateLocationDataWidget( - widget.selectedFiles.files.toList(), - ); - }, - ); - }, + onTap: _editLocation, ), ); } @@ -520,6 +483,45 @@ class _FileSelectionActionsWidgetState return const SizedBox(); } + Future _editLocation() async { + await showBarModalBottomSheet( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(5), + ), + ), + backgroundColor: getEnteColorScheme(context).backgroundElevated, + barrierColor: backdropFaintDark, + topControl: Stack( + alignment: Alignment.bottomCenter, + children: [ + // This container is for increasing the tap area + Container( + width: double.infinity, + height: 36, + color: Colors.transparent, + ), + Container( + height: 5, + width: 40, + decoration: const BoxDecoration( + color: backgroundElevated2Light, + borderRadius: BorderRadius.all( + Radius.circular(5), + ), + ), + ), + ], + ), + context: context, + builder: (context) { + return UpdateLocationDataWidget( + widget.selectedFiles.files.toList(), + ); + }, + ); + } + Future _shareSelectedFiles() async { shareSelected( context, From cc19b24cc47ed13060b940809981f2bf768065aa Mon Sep 17 00:00:00 2001 From: Sven Date: Fri, 4 Jul 2025 20:51:49 +0200 Subject: [PATCH 059/109] Add Proton suite icons and update custom icons data --- .../custom-icons/_data/custom-icons.json | 24 +++++++ .../custom-icons/icons/proton_calendar.svg | 23 +++++++ .../custom-icons/icons/proton_drive.svg | 20 ++++++ .../assets/custom-icons/icons/proton_mail.svg | 24 +++++++ .../assets/custom-icons/icons/proton_pass.svg | 67 +++++++++++++++++++ .../assets/custom-icons/icons/proton_vpn.svg | 25 +++++++ .../custom-icons/icons/proton_wallet.svg | 24 +++++++ 7 files changed, 207 insertions(+) create mode 100644 mobile/apps/auth/assets/custom-icons/icons/proton_calendar.svg create mode 100644 mobile/apps/auth/assets/custom-icons/icons/proton_drive.svg create mode 100644 mobile/apps/auth/assets/custom-icons/icons/proton_mail.svg create mode 100644 mobile/apps/auth/assets/custom-icons/icons/proton_pass.svg create mode 100644 mobile/apps/auth/assets/custom-icons/icons/proton_vpn.svg create mode 100644 mobile/apps/auth/assets/custom-icons/icons/proton_wallet.svg diff --git a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json index 9bfacf845d..589db8113c 100644 --- a/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json +++ b/mobile/apps/auth/assets/custom-icons/_data/custom-icons.json @@ -1038,6 +1038,30 @@ { "title": "Proton" }, + { + "title": "Proton Calendar", + "slug": "proton_calendar" + }, + { + "title": "Proton Drive", + "slug": "proton_drive" + }, + { + "title": "Proton Mail", + "slug": "proton_mail" + }, + { + "title": "Proton Pass", + "slug": "proton_pass" + }, + { + "title": "Proton VPN", + "slug": "proton_vpn" + }, + { + "title": "Proton Wallet", + "slug": "proton_wallet" + }, { "title": "Proxmox" }, diff --git a/mobile/apps/auth/assets/custom-icons/icons/proton_calendar.svg b/mobile/apps/auth/assets/custom-icons/icons/proton_calendar.svg new file mode 100644 index 0000000000..08c9c8a4ca --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/proton_calendar.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/auth/assets/custom-icons/icons/proton_drive.svg b/mobile/apps/auth/assets/custom-icons/icons/proton_drive.svg new file mode 100644 index 0000000000..130c520feb --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/proton_drive.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/auth/assets/custom-icons/icons/proton_mail.svg b/mobile/apps/auth/assets/custom-icons/icons/proton_mail.svg new file mode 100644 index 0000000000..9c62d2ede9 --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/proton_mail.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/auth/assets/custom-icons/icons/proton_pass.svg b/mobile/apps/auth/assets/custom-icons/icons/proton_pass.svg new file mode 100644 index 0000000000..73db71e92b --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/proton_pass.svg @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/auth/assets/custom-icons/icons/proton_vpn.svg b/mobile/apps/auth/assets/custom-icons/icons/proton_vpn.svg new file mode 100644 index 0000000000..d26f0d64b6 --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/proton_vpn.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/mobile/apps/auth/assets/custom-icons/icons/proton_wallet.svg b/mobile/apps/auth/assets/custom-icons/icons/proton_wallet.svg new file mode 100644 index 0000000000..e279e248c5 --- /dev/null +++ b/mobile/apps/auth/assets/custom-icons/icons/proton_wallet.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + From efc19d12629a74a351ff6d11850f9979470708ba Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 7 Jul 2025 00:43:36 +0000 Subject: [PATCH 060/109] New Crowdin translations by GitHub Action --- .../base/locales/ar-SA/translation.json | 2 +- .../base/locales/fa-IR/translation.json | 62 +-- .../base/locales/pt-BR/translation.json | 4 +- .../base/locales/ru-RU/translation.json | 120 ++-- .../base/locales/vi-VN/translation.json | 526 +++++++++--------- .../base/locales/zh-CN/translation.json | 52 +- 6 files changed, 383 insertions(+), 383 deletions(-) diff --git a/web/packages/base/locales/ar-SA/translation.json b/web/packages/base/locales/ar-SA/translation.json index 99b03eba83..13e6a75b41 100644 --- a/web/packages/base/locales/ar-SA/translation.json +++ b/web/packages/base/locales/ar-SA/translation.json @@ -685,5 +685,5 @@ "person_favorites": "مفضلات {{name}}", "shared_favorites": "", "added_by_name": "أضيفت بواسطة {{name}}", - "unowned_files_not_processed": "" + "unowned_files_not_processed": "لم تتم معالجة الملفات المضافة من قبل مستخدمين آخرين" } diff --git a/web/packages/base/locales/fa-IR/translation.json b/web/packages/base/locales/fa-IR/translation.json index 108bdc5291..b1956bcf24 100644 --- a/web/packages/base/locales/fa-IR/translation.json +++ b/web/packages/base/locales/fa-IR/translation.json @@ -4,9 +4,9 @@ "intro_slide_2_title": "", "intro_slide_2": "", "intro_slide_3_title": "", - "intro_slide_3": "", - "login": "", - "sign_up": "", + "intro_slide_3": "اندروید، آی‌اواس، وب، رایانه رومیزی", + "login": "ورود", + "sign_up": "ثبت نام", "new_to_ente": "", "existing_user": "", "enter_email": "", @@ -16,27 +16,27 @@ "email_already_registered": "", "email_sent": "", "check_inbox_hint": "", - "verification_code": "", - "resend_code": "", - "verify": "", - "send_otp": "", - "generic_error": "", - "generic_error_retry": "", + "verification_code": "کد تایید", + "resend_code": "ارسال مجدد کد", + "verify": "تایید", + "send_otp": "ارسال رمز یک‌بار مصرف", + "generic_error": "یک مشکلی پیش آمده", + "generic_error_retry": "مشکلی پیش آمده، لطفا دوباره تلاش کنید", "invalid_code_error": "", - "expired_code_error": "", - "status_sending": "", - "status_sent": "", - "password": "", - "link_password_description": "", - "unlock": "", - "set_password": "", - "sign_in": "", - "incorrect_password": "", - "incorrect_password_or_no_account": "", + "expired_code_error": "کد تایید شما باطل شد", + "status_sending": "در حال ارسال...", + "status_sent": "ارسال شد!", + "password": "رمز عبور", + "link_password_description": "رمز عبور خودرا جهت باز شدن آلبوم بنویسید", + "unlock": "بازکردن", + "set_password": "تنظیم رمز عبور", + "sign_in": "ورود", + "incorrect_password": "رمز عبور نادرست", + "incorrect_password_or_no_account": "رمز عبور نادرست یا ایمیل ثبت نام نشده", "pick_password_hint": "", "pick_password_caution": "", "key_generation_in_progress": "", - "confirm_password": "", + "confirm_password": "تایید رمز عبور", "referral_source_hint": "", "referral_source_info": "", "password_mismatch_error": "", @@ -46,12 +46,12 @@ "new_album": "", "create_albums": "", "album_name": "", - "close": "", - "yes": "", - "no": "", - "nothing_here": "", - "upload": "", - "import": "", + "close": "بستن", + "yes": "بله", + "no": "خیر", + "nothing_here": "هیچی درحال حاضر اینجا نیست", + "upload": "بارگذاری", + "import": "وارد کردن", "add_photos": "", "add_more_photos": "", "add_photos_count_one": "", @@ -79,11 +79,11 @@ "mouse_scroll": "", "pan": "", "pinch": "", - "drag": "", - "tap_inside_image": "", - "tap_outside_image": "", - "shortcuts": "", - "show_shortcuts": "", + "drag": "کشیدن", + "tap_inside_image": "زدن در داخل تصویر", + "tap_outside_image": "زدن در بیرون تصویر", + "shortcuts": "میانبرها", + "show_shortcuts": "نمایش میانبرها", "zoom_preset": "", "toggle_controls": "", "toggle_live": "", diff --git a/web/packages/base/locales/pt-BR/translation.json b/web/packages/base/locales/pt-BR/translation.json index f07b501dd7..ffb09b5b4a 100644 --- a/web/packages/base/locales/pt-BR/translation.json +++ b/web/packages/base/locales/pt-BR/translation.json @@ -162,7 +162,7 @@ "ok": "OK", "success": "Sucesso", "error": "Erro", - "note": "", + "note": "Nota", "offline_message": "Você está sem internet, as memórias em cache estão sendo exibidas", "install": "Instalar", "install_mobile_app": "Instale nosso aplicativo para Android ou iOS para copiar todas suas fotos com segurança", @@ -685,5 +685,5 @@ "person_favorites": "Favoritos de {{name}}", "shared_favorites": "Favoritos compartilhados", "added_by_name": "Adicionado por {{name}}", - "unowned_files_not_processed": "" + "unowned_files_not_processed": "Não processou os arquivos adicionados por outros usuários" } diff --git a/web/packages/base/locales/ru-RU/translation.json b/web/packages/base/locales/ru-RU/translation.json index 9479460f08..39401daefc 100644 --- a/web/packages/base/locales/ru-RU/translation.json +++ b/web/packages/base/locales/ru-RU/translation.json @@ -9,8 +9,8 @@ "sign_up": "Регистрация", "new_to_ente": "Новенький в Ente", "existing_user": "Существующий пользователь", - "enter_email": "Введите ваш email адрес", - "invalid_email_error": "Введите действительный email адрес", + "enter_email": "Введите адрес электронной почты", + "invalid_email_error": "Введите действительный адрес электронной почты", "required": "Обязательное поле", "email_not_registered": "Такой email не зарегистрирован", "email_already_registered": "Такой email уже зарегистрирован", @@ -32,7 +32,7 @@ "set_password": "Установить пароль", "sign_in": "Зарегистрироваться", "incorrect_password": "Неверный пароль", - "incorrect_password_or_no_account": "", + "incorrect_password_or_no_account": "Неверный пароль или электронная почта не зарегистрирована", "pick_password_hint": "Пожалуйста, введите пароль, который мы можем использовать для шифрования ваших данных", "pick_password_caution": "Мы не храним ваш пароль, поэтому, если вы его забудете, мы ничем не сможем вам помочьдля восстановления ваших данных без пароля.", "key_generation_in_progress": "Генерируем ключи шифрования...", @@ -40,7 +40,7 @@ "referral_source_hint": "Как вы узнали о Ente? (необязательно)", "referral_source_info": "Будет полезно, если вы укажете, где вы узнали о нас, так как мы не отслеживаем установки приложения!", "password_mismatch_error": "Пароли не совпадают", - "show_or_hide_password": "", + "show_or_hide_password": "Показать или скрыть пароль", "welcome_to_ente_title": "Добро пожаловать в ", "welcome_to_ente_subtitle": "Сквозное зашифрованное хранение фотографий и общий доступ к ним", "new_album": "Новый альбом", @@ -59,11 +59,11 @@ "select_photos": "Выбрать фотографии", "file_upload": "Загрузка файла", "preparing": "Подготовка", - "processed_counts": "", - "upload_reading_metadata_files": "", + "processed_counts": "{{count, number}} / {{total, number}}", + "upload_reading_metadata_files": "Чтение файлов метаданных", "upload_cancelling": "Отмена оставшихся загрузок", - "upload_done": "", - "upload_skipped": "", + "upload_done": "{{count, number}} загружено", + "upload_skipped": "{{count, number}} пропущено", "initial_load_delay_warning": "Первая загрузка может занять некоторое время", "no_account": "У меня нет учетной записи", "existing_account": "Уже есть аккаунт", @@ -74,28 +74,28 @@ "download_favorites": "Скачать избранные", "download_uncategorized": "Скачать без категорий", "download_hidden_items": "Скачать скрытые элементы", - "audio": "", - "more": "", - "mouse_scroll": "", - "pan": "", - "pinch": "", - "drag": "", - "tap_inside_image": "", - "tap_outside_image": "", - "shortcuts": "", - "show_shortcuts": "", - "zoom_preset": "", - "toggle_controls": "", - "toggle_live": "", - "toggle_audio": "", - "toggle_favorite": "", - "toggle_archive": "", - "view_info": "", + "audio": "Аудио", + "more": "Ещё", + "mouse_scroll": "Прокрутка мышью", + "pan": "Pan", + "pinch": "Pinch", + "drag": "Drag", + "tap_inside_image": "Нажмите внутри изображения", + "tap_outside_image": "Нажмите снаружи изображения", + "shortcuts": "Ярлыки", + "show_shortcuts": "Показать ярлыки", + "zoom_preset": "Предустановленный масштаб", + "toggle_controls": "Переключить управление", + "toggle_live": "Переключить прямую трансляцию", + "toggle_audio": "Переключить аудио", + "toggle_favorite": "Переключить избранное", + "toggle_archive": "Переключить архив", + "view_info": "Посмотреть информацию", "copy_as_png": "Скопировать как PNG", "toggle_fullscreen": "Полноэкранный режим", - "exit_fullscreen": "", - "go_fullscreen": "", - "zoom": "", + "exit_fullscreen": "Выйти из полноэкранного режима", + "go_fullscreen": "Перейти в полноэкранный режим", + "zoom": "Увеличить", "play": "Воспроизведение", "pause": "Пауза", "previous": "Предыдущий", @@ -132,7 +132,7 @@ "password_changed_elsewhere": "Пароль изменен в другом месте", "password_changed_elsewhere_message": "Пожалуйста, войдите снова на этом устройстве, чтобы использовать новый пароль для аутентификации.", "go_back": "Вернуться назад", - "account": "", + "account": "Аккаунт", "recovery_key": "Ключ восстановления", "do_this_later": "Сделать позже", "save_key": "Сохранить ключ", @@ -148,9 +148,9 @@ "no_recovery_key_message": "Из-за природы нашего сквозного протокола шифрования ваши данные не могут быть расшифрованы без вашего пароля или ключа восстановления", "no_two_factor_recovery_key_message": "Пожалуйста, отправьте электронное письмо на адрес {{emailID}} с вашего зарегистрированного адреса электронной почты", "contact_support": "Связаться с поддержкой", - "help": "", - "ente_help": "", - "blog": "", + "help": "Помощь", + "ente_help": "Ente Помощь", + "blog": "Блог", "request_feature": "Запросить функцию", "support": "Поддержка", "cancel": "Отменить", @@ -158,11 +158,11 @@ "logout_message": "Вы уверены, что хотите выйти?", "delete_account": "Удалить аккаунт", "delete_account_manually_message": "

Пожалуйста, отправьте письмо по адресу {{emailID}} с вашего зарегистрированного адреса электронной почты.

Ваш запрос будет обработан в течение 72 часов

", - "change_email": "Изменить email адрес", + "change_email": "Изменить адрес электронной почты", "ok": "ОК", "success": "Успешно", "error": "Ошибка", - "note": "", + "note": "Заметка", "offline_message": "Вы не в сети, кэшированные воспоминания отображаются", "install": "Устанавливать", "install_mobile_app": "Установите наше приложение Android или iOS для автоматического резервного копирования всех ваших фотографий", @@ -226,11 +226,11 @@ "delete_photos": "Удалить фото", "keep_photos": "Оставить фото", "share_album": "Поделиться альбомом", - "sharing_with_self": "", - "sharing_already_shared": "", + "sharing_with_self": "Вы не можете поделиться с самим собой", + "sharing_already_shared": "Вы уже поделились этим с {{email}}", "sharing_album_not_allowed": "Делиться альбомом запрещено", "sharing_disabled_for_free_accounts": "Совместное использование отключено для бесплатных аккаунтов", - "sharing_user_does_not_exist": "", + "sharing_user_does_not_exist": "Пользователь с такой электронной почтой не найден", "search": "Поиск", "search_results": "Результаты поиска", "no_results": "Ничего не найдено", @@ -246,9 +246,9 @@ "terms_and_conditions": "Я согласен с тем, что условия и политика конфиденциальности", "people": "Люди", "indexing_scheduled": "Индексация запланирована...", - "indexing_photos": "", - "indexing_fetching": "", - "indexing_people": "", + "indexing_photos": "Обновление индексов...", + "indexing_fetching": "Синхронизация индексов...", + "indexing_people": "Синхронизация людей...", "syncing_wait": "Синхронизация...", "people_empty_too_few": "Люди будут показаны здесь, когда будет достаточно фотографий человека", "unnamed_person": "Безымянный человек", @@ -371,7 +371,7 @@ "leave_shared_album": "Да, уходи", "confirm_remove_message": "Выбранные элементы будут удалены из этого альбома. Элементы, которые есть только в этом альбоме, будут перемещены в раздел Без категории.", "confirm_remove_incl_others_message": "Некоторые из удаляемых вами элементов были добавлены другими пользователями, и вы потеряете к ним доступ.", - "oldest": "Старейший", + "oldest": "Самые старые", "last_updated": "Последнее обновление", "name": "Имя", "fix_creation_time": "Назначьте время", @@ -388,7 +388,7 @@ "sharing_details": "Обмен подробностями", "modify_sharing": "Изменить общий доступ", "add_collaborators": "Добавление соавторов", - "add_new_email": "Добавить новый email адрес", + "add_new_email": "Добавить новую электронную почту", "shared_with_people_count_zero": "Делитесь с конкретными людьми", "shared_with_people_count_one": "Совместно с 1 человеком", "shared_with_people_count": "Поделился с {{count, number}} люди", @@ -495,8 +495,8 @@ "stop_watching_folder_message": "Ваши существующие файлы не будут удалены, но Ente прекратит автоматическое обновление связанного альбома Ente при внесении изменений в эту папку.", "yes_stop": "Да, остановись", "change_folder": "Изменить папку", - "view_logs": "", - "view_logs_message": "", + "view_logs": "Просмотреть логи", + "view_logs_message": "

При этом будут показаны журналы отладки, которые вы можете отправить нам по электронной почте, чтобы помочь в устранении вашей проблемы.

Обратите внимание, что будут указаны имена файлов, которые помогут отслеживать проблемы с конкретными файлами.

", "weak_device_hint": "Используемый вами веб-браузер недостаточно мощный, чтобы зашифровать ваши фотографии. Пожалуйста, попробуйте войти в Ente на своем компьютере или загрузить мобильное/настольное приложение Ente.", "drag_and_drop_hint": "Или перетащите в основное окно", "authenticate": "Проверка подлинности", @@ -595,8 +595,8 @@ "image": "Изображение", "video": "Видео", "live_photo": "Живое фото", - "live": "", - "edit_image": "", + "live": "Прямая трансляция", + "edit_image": "Редактировать изображение", "photo_editor": "Редактор фото", "confirm_editor_close": "Вы уверены, что хотите закрыть редактор?", "confirm_editor_close_message": "Загрузите отредактированное изображение или сохраните копию в ente, чтобы сохранить внесенные изменения.", @@ -625,9 +625,9 @@ "reset": "Сбросить", "faster_upload": "Более быстрая загрузка данных", "faster_upload_description": "Загрузка маршрута через близлежащие серверы", - "open_ente_on_startup": "", + "open_ente_on_startup": "Открывать Enter при запуске", "cast_album_to_tv": "Воспроизвести альбом на ТВ", - "cast_to_tv": "", + "cast_to_tv": "Воспроизвести на ТВ", "enter_cast_pin_code": "Введите код, который вы видите на экране телевизора ниже, чтобы выполнить сопряжение с этим устройством.", "code": "Код", "pair_device_to_tv": "Сопряжение устройств", @@ -639,7 +639,7 @@ "pair_with_pin": "Соединение с помощью булавки", "pair_with_pin_description": "Пара с PIN-кодом работает с любым экраном, на котором вы хотите посмотреть ваш альбом.", "visit_cast_url": "Перейдите на страницу {{url}} на устройстве, которое вы хотите подключить.", - "passkeys": "Passkeys", + "passkeys": "Ключ доступа", "passkey_fetch_failed": "Не удалось получить ваши ключи.", "manage_passkey": "Управление ключами", "delete_passkey": "Удалить пароль", @@ -675,15 +675,15 @@ "server_endpoint": "Конечная точка сервера", "more_information": "Дополнительная информация", "save": "Сохранить", - "theme": "", - "system": "", - "light": "", - "dark": "", - "streamable_videos": "", - "processing_videos_status": "", - "share_favorites": "", - "person_favorites": "", - "shared_favorites": "", - "added_by_name": "", - "unowned_files_not_processed": "" + "theme": "Тема", + "system": "Системная", + "light": "Светлая", + "dark": "Тёмная", + "streamable_videos": "Потоковое видео", + "processing_videos_status": "Обработка видео...", + "share_favorites": "Поделиться избранными", + "person_favorites": "{{name}} избранных", + "shared_favorites": "Общие избранные", + "added_by_name": "Добавлено {{name}}", + "unowned_files_not_processed": "Файлы, добавленные другими пользователями, не были обработаны" } diff --git a/web/packages/base/locales/vi-VN/translation.json b/web/packages/base/locales/vi-VN/translation.json index 6b3904aca7..6212bc57a2 100644 --- a/web/packages/base/locales/vi-VN/translation.json +++ b/web/packages/base/locales/vi-VN/translation.json @@ -1,19 +1,19 @@ { "intro_slide_1_title": "Sao lưu riêng tư
cho những kỷ niệm của bạn", - "intro_slide_1": "Mã hóa đầu cuối mặc định", - "intro_slide_2_title": "Lưu trữ an toàn
tại nơi trú ẩn", - "intro_slide_2": "Được thiết kế để tồn tại lâu dài", + "intro_slide_1": "Mã hóa đầu cuối theo mặc định", + "intro_slide_2_title": "Lưu trữ an toàn
ở hầm trú ẩn hạt nhân", + "intro_slide_2": "Được thiết kế để trường tồn", "intro_slide_3_title": "Có sẵn
mọi nơi", "intro_slide_3": "Android, iOS, Web, Desktop", "login": "Đăng nhập", "sign_up": "Đăng ký", - "new_to_ente": "Mới đến Ente", - "existing_user": "Người dùng hiện tại", + "new_to_ente": "Mới dùng Ente", + "existing_user": "Đã có tài khoản", "enter_email": "Nhập địa chỉ email", "invalid_email_error": "Nhập một email hợp lệ", "required": "Bắt buộc", "email_not_registered": "Email chưa được đăng kí", - "email_already_registered": "Email đã được đăng kí", + "email_already_registered": "Email đã được đăng ký", "email_sent": "Mã xác minh đã được gửi đến {{email}}", "check_inbox_hint": "Vui lòng kiểm tra hộp thư đến (và thư rác) để hoàn tất xác minh", "verification_code": "Mã xác minh", @@ -32,15 +32,15 @@ "set_password": "Đặt mật khẩu", "sign_in": "Đăng nhập", "incorrect_password": "Mật khẩu không chính xác", - "incorrect_password_or_no_account": "", - "pick_password_hint": "Vui lòng nhập mật khẩu mà chúng tôi có thể sử dụng để mã hóa dữ liệu của bạn", - "pick_password_caution": "Chúng tôi không lưu trữ mật khẩu của bạn, vì vậy nếu bạn quên, chúng tôi sẽ không thể giúp bạn khôi phục dữ liệu mà không có khóa khôi phục.", - "key_generation_in_progress": "Đang tạo khóa mã hóa...", + "incorrect_password_or_no_account": "Sai mật khẩu hoặc email chưa được đăng ký", + "pick_password_hint": "Vui lòng nhập một mật khẩu dùng để mã hóa dữ liệu của bạn", + "pick_password_caution": "Chúng tôi không lưu trữ mật khẩu của bạn, nên nếu bạn quên, chúng tôi sẽ không thể giúp bạn khôi phục dữ liệu nếu không có mã khôi phục.", + "key_generation_in_progress": "Đang mã hóa...", "confirm_password": "Xác nhận mật khẩu", - "referral_source_hint": "Bạn đã nghe về Ente từ đâu? (tùy chọn)", - "referral_source_info": "Chúng tôi không theo dõi cài đặt ứng dụng, sẽ rất hữu ích nếu bạn cho chúng tôi biết bạn đã tìm thấy chúng tôi ở đâu!", + "referral_source_hint": "Bạn biết Ente từ đâu? (tùy chọn)", + "referral_source_info": "Chúng tôi không theo dõi cài đặt ứng dụng, nên nếu bạn bật mí bạn tìm thấy chúng tôi từ đâu sẽ rất hữu ích!", "password_mismatch_error": "Mật khẩu không khớp", - "show_or_hide_password": "", + "show_or_hide_password": "Ẩn hoặc hiện mật khẩu", "welcome_to_ente_title": "Chào mừng đến với ", "welcome_to_ente_subtitle": "Lưu trữ và chia sẻ ảnh được mã hóa đầu cuối", "new_album": "Album mới", @@ -53,222 +53,222 @@ "upload": "Tải lên", "import": "Nhập", "add_photos": "Thêm ảnh", - "add_more_photos": "Thêm nhiều ảnh hơn", + "add_more_photos": "Thêm ảnh", "add_photos_count_one": "Thêm 1 mục", "add_photos_count": "Thêm {{count, number}} mục", "select_photos": "Chọn ảnh", "file_upload": "Tải tệp lên", - "preparing": "", - "processed_counts": "", - "upload_reading_metadata_files": "", - "upload_cancelling": "Hủy bỏ các tải lên còn lại", - "upload_done": "", - "upload_skipped": "", - "initial_load_delay_warning": "Tải lần đầu có thể mất một chút thời gian", - "no_account": "Không có tài khoản", + "preparing": "Đang chuẩn bị", + "processed_counts": "{{count, number}} / {{total, number}}", + "upload_reading_metadata_files": "Đang đọc siêu dữ liệu", + "upload_cancelling": "Hủy bỏ các lượt tải lên còn lại", + "upload_done": "Đã tải lên {{count, number}}", + "upload_skipped": "{{count, number}} bị bỏ qua", + "initial_load_delay_warning": "Lần đầu tải có thể mất một ít thời gian", + "no_account": "Chưa có tài khoản", "existing_account": "Đã có tài khoản", "create": "Tạo", - "files_count": "", + "files_count": "{{count, number}} tệp", "download": "Tải xuống", "download_album": "Tải xuống album", "download_favorites": "Tải xuống mục yêu thích", - "download_uncategorized": "Tải xuống chưa phân loại", - "download_hidden_items": "Tải xuống các mục ẩn", - "audio": "", - "more": "", - "mouse_scroll": "", - "pan": "", - "pinch": "", - "drag": "", - "tap_inside_image": "", - "tap_outside_image": "", - "shortcuts": "", - "show_shortcuts": "", - "zoom_preset": "", - "toggle_controls": "", - "toggle_live": "", - "toggle_audio": "", - "toggle_favorite": "", - "toggle_archive": "", - "view_info": "", - "copy_as_png": "Sao chép dưới dạng PNG", - "toggle_fullscreen": "Chuyển đổi chế độ toàn màn hình", - "exit_fullscreen": "", - "go_fullscreen": "", - "zoom": "", - "play": "", - "pause": "", + "download_uncategorized": "Tải xuống mục chưa phân loại", + "download_hidden_items": "Tải xuống mục ẩn", + "audio": "Âm thanh", + "more": "Thêm", + "mouse_scroll": "Cuộn chuột", + "pan": "Lia qua", + "pinch": "Chụm 2 ngón", + "drag": "Kéo", + "tap_inside_image": "Nhấn lên ảnh", + "tap_outside_image": "Nhấn ngoài ảnh", + "shortcuts": "Phím tắt", + "show_shortcuts": "Hiện phím tắt", + "zoom_preset": "Phóng to chi tiết", + "toggle_controls": "Bật/tắt điều khiển", + "toggle_live": "Bật/tắt Live", + "toggle_audio": "Bật/tắt âm thanh", + "toggle_favorite": "Thích/bỏ thích", + "toggle_archive": "Lưu trữ/bỏ lưu trữ", + "view_info": "Xem thông tin", + "copy_as_png": "Sao chép dạng PNG", + "toggle_fullscreen": "Chế độ toàn màn hình", + "exit_fullscreen": "Thoát toàn màn hình", + "go_fullscreen": "Toàn màn hình", + "zoom": "Thu phóng", + "play": "Phát", + "pause": "Dừng", "previous": "Trước", - "next": "Tiếp theo", - "video_seek": "", - "quality": "", - "auto": "", - "original": "", - "speed": "", - "title_photos": "Ảnh Ente", - "title_auth": "Xác thực Ente", + "next": "Kế tiếp", + "video_seek": "Tua video", + "quality": "Chất lượng", + "auto": "Tự động", + "original": "Gốc", + "speed": "Tốc độ", + "title_photos": "Ente Photos", + "title_auth": "Ente Auth", "title_accounts": "Tài khoản Ente", "upload_first_photo": "Tải lên ảnh đầu tiên của bạn", - "import_your_folders": "Nhập các thư mục của bạn", - "upload_dropzone_hint": "Thả để sao lưu các tệp của bạn", - "watch_folder_dropzone_hint": "Thả để thêm thư mục theo dõi", - "trash_files_title": "Xóa tệp?", + "import_your_folders": "Nhập thư mục của bạn", + "upload_dropzone_hint": "Kéo thả để sao lưu tệp của bạn", + "watch_folder_dropzone_hint": "Kéo thả để thêm thư mục theo dõi", + "trash_files_title": "Xóa các tệp?", "trash_file_title": "Xóa tệp?", "delete_files_title": "Xóa ngay lập tức?", "delete_files_message": "Các tệp đã chọn sẽ bị xóa vĩnh viễn khỏi tài khoản Ente của bạn.", - "selected_count": "{{selected, number}} đã chọn", - "selected_and_yours_count": "{{selected, number}} đã chọn {{yours, number}} của bạn", + "selected_count": "{{selected, number}} mục đã chọn", + "selected_and_yours_count": "{{selected, number}} mục đã chọn, trong đó {{yours, number}} là của bạn", "delete": "Xóa", - "favorite": "Yêu thích", + "favorite": "Thích", "convert": "Chuyển đổi", "multi_folder_upload": "Phát hiện nhiều thư mục", - "upload_to_choice": "Bạn có muốn tải chúng vào", + "upload_to_choice": "Bạn có muốn tải chúng thành", "upload_to_single_album": "Một album duy nhất", - "upload_to_album_per_folder": "Album riêng biệt", + "upload_to_album_per_folder": "Các album riêng biệt", "session_expired": "Phiên đã hết hạn", "session_expired_message": "Phiên của bạn đã hết hạn, vui lòng đăng nhập lại để tiếp tục", - "password_generation_failed": "Trình duyệt của bạn không thể tạo một khóa mạnh đáp ứng tiêu chuẩn mã hóa của Ente, vui lòng thử sử dụng ứng dụng di động hoặc trình duyệt khác", + "password_generation_failed": "Trình duyệt của bạn không thể tạo một mã mạnh đáp ứng tiêu chuẩn mã hóa của Ente, vui lòng dùng ứng dụng di động hoặc trình duyệt khác", "change_password": "Đổi mật khẩu", "password_changed_elsewhere": "Mật khẩu đã được thay đổi ở nơi khác", - "password_changed_elsewhere_message": "Vui lòng đăng nhập lại trên thiết bị này để sử dụng mật khẩu mới của bạn để xác thực.", + "password_changed_elsewhere_message": "Vui lòng đăng nhập lại trên thiết bị này và dùng mật khẩu mới của bạn.", "go_back": "Quay lại", - "account": "", - "recovery_key": "Khóa khôi phục", - "do_this_later": "Làm điều này sau", - "save_key": "Lưu khóa", - "recovery_key_description": "Nếu bạn quên mật khẩu của mình, cách duy nhất để khôi phục dữ liệu của bạn là với khóa này.", - "key_not_stored_note": "Chúng tôi không lưu trữ khóa này, vì vậy hãy lưu nó ở một nơi an toàn", - "recovery_key_generation_failed": "Mã khôi phục không thể được tạo, vui lòng thử lại", + "account": "Tài khoản", + "recovery_key": "Mã khôi phục", + "do_this_later": "Để sau", + "save_key": "Lưu mã", + "recovery_key_description": "Nếu bạn quên mật khẩu, cách duy nhất để khôi phục dữ liệu của bạn là dùng mã này.", + "key_not_stored_note": "Chúng tôi không lưu trữ mã này, nên hãy lưu nó ở một nơi an toàn", + "recovery_key_generation_failed": "Không tạo được mã khôi phục, vui lòng thử lại", "forgot_password": "Quên mật khẩu", "recover_account": "Khôi phục tài khoản", "recover": "Khôi phục", - "no_recovery_key_title": "Không có khóa khôi phục?", - "incorrect_recovery_key": "Khóa khôi phục không chính xác", - "sorry": "Xin lỗi", - "no_recovery_key_message": "Do tính chất của giao thức mã hóa đầu cuối của chúng tôi, dữ liệu của bạn không thể được giải mã mà không có mật khẩu hoặc khóa khôi phục của bạn", + "no_recovery_key_title": "Không có mã khôi phục?", + "incorrect_recovery_key": "Mã khôi phục không chính xác", + "sorry": "Rất tiếc", + "no_recovery_key_message": "Do tính chất của giao thức mã hóa đầu cuối, không thể giải mã dữ liệu của bạn mà không có mật khẩu hoặc mã khôi phục", "no_two_factor_recovery_key_message": "Vui lòng gửi email đến {{emailID}} từ địa chỉ email đã đăng ký của bạn", "contact_support": "Liên hệ hỗ trợ", - "help": "", - "ente_help": "", - "blog": "", - "request_feature": "Yêu cầu tính năng", + "help": "Trợ giúp", + "ente_help": "Trợ giúp Ente", + "blog": "Blog", + "request_feature": "Đề xuất tính năng", "support": "Hỗ trợ", "cancel": "Hủy", "logout": "Đăng xuất", - "logout_message": "Bạn có chắc chắn muốn đăng xuất không?", + "logout_message": "Bạn có chắc muốn đăng xuất không?", "delete_account": "Xóa tài khoản", "delete_account_manually_message": "

Vui lòng gửi email đến {{emailID}} từ địa chỉ email đã đăng ký của bạn.

Yêu cầu của bạn sẽ được xử lý trong vòng 72 giờ.

", "change_email": "Đổi email", "ok": "OK", "success": "Thành công", "error": "Lỗi", - "note": "", - "offline_message": "Bạn đang ngoại tuyến, các kỷ niệm đã được lưu vào bộ nhớ cache đang được hiển thị", + "note": "Ghi chú", + "offline_message": "Bạn đang ngoại tuyến, các kỷ niệm hiển thị là từ bộ nhớ đệm", "install": "Cài đặt", "install_mobile_app": "Cài đặt ứng dụng Android hoặc iOS của chúng tôi để tự động sao lưu tất cả ảnh của bạn", - "download_app": "Tải xuống ứng dụng desktop", - "download_app_message": "Xin lỗi, thao tác này hiện chỉ được hỗ trợ trên ứng dụng desktop của chúng tôi", + "download_app": "Tải xuống ứng dụng máy tính", + "download_app_message": "Rất tiếc, thao tác này hiện chỉ hỗ trợ trên ứng dụng máy tính", "subscription": "Gói đăng ký", "manage_payment_method": "Quản lý phương thức thanh toán", "manage_family": "Quản lý gia đình", "family_plan": "Gói gia đình", "leave_family_plan": "Rời khỏi gói gia đình", "leave": "Rời", - "leave_family_plan_confirm": "Bạn có chắc chắn muốn rời khỏi gói gia đình không?", + "leave_family_plan_confirm": "Bạn có chắc muốn rời khỏi gói gia đình không?", "choose_plan": "Chọn gói của bạn", - "manage_plan": "Quản lý đăng ký của bạn", - "current_usage": "Sử dụng hiện tại là {{usage}}", - "two_months_free": "Nhận 2 tháng miễn phí với các gói hàng năm", - "free_plan_option": "Tiếp tục với gói miễn phí", - "free_plan_description": "{{storage}} miễn phí mãi mãi", + "manage_plan": "Quản lý gói đăng ký", + "current_usage": "Hiện dùng {{usage}}", + "two_months_free": "Nhận 2 tháng miễn phí với các gói theo năm", + "free_plan_option": "Dùng tiếp gói miễn phí", + "free_plan_description": "{{storage}} miễn phí vĩnh viễn", "active": "Hoạt động", - "subscription_info_free": "Bạn đang ở gói miễn phí", - "subscription_info_family": "Bạn đang ở gói gia đình do", - "subscription_info_expired": "Gói đăng ký của bạn đã hết hạn, vui lòng gia hạn", - "subscription_info_renewal_cancelled": "Gói đăng ký của bạn sẽ bị hủy vào {{date, date}}", - "subscription_info_storage_quota_exceeded": "Bạn đã vượt quá hạn mức lưu trữ của mình, vui lòng nâng cấp", + "subscription_info_free": "Bạn đang dùng gói miễn phí", + "subscription_info_family": "Bạn đang dùng gói gia đình của", + "subscription_info_expired": "Gói của bạn đã hết hạn, vui lòng gia hạn", + "subscription_info_renewal_cancelled": "Gói của bạn sẽ bị hủy vào {{date, date}}", + "subscription_info_storage_quota_exceeded": "Bạn đã vượt hạn mức lưu trữ của mình, vui lòng nâng cấp", "subscription_status_renewal_active": "Gia hạn vào {{date, date}}", "subscription_status_renewal_cancelled": "Kết thúc vào {{date, date}}", "add_on_valid_till": "Gói bổ sung {{storage}} của bạn có hiệu lực đến {{date, date}}", "subscription_expired": "Gói đăng ký đã hết hạn", - "storage_quota_exceeded": "Đã vượt quá giới hạn lưu trữ", - "subscription_purchase_success": "

Chúng tôi đã nhận được thanh toán của bạn

Gói đăng ký của bạn có hiệu lực đến {{date, date}}

", + "storage_quota_exceeded": "Đã vượt hạn mức lưu trữ", + "subscription_purchase_success": "

Chúng tôi đã nhận được thanh toán

Gói của bạn có hiệu lực đến {{date, date}}

", "subscription_purchase_cancelled": "Giao dịch của bạn đã bị hủy, vui lòng thử lại nếu bạn muốn đăng ký", - "subscription_purchase_failed": "Giao dịch đăng ký không thành công, vui lòng thử lại", - "subscription_verification_error": "Xác minh gói đăng ký không thành công", - "update_payment_method_message": "Chúng tôi xin lỗi, thanh toán không thành công khi chúng tôi cố gắng tính phí thẻ của bạn, vui lòng cập nhật phương thức thanh toán của bạn và thử lại", + "subscription_purchase_failed": "Giao dịch không thành công, vui lòng thử lại", + "subscription_verification_error": "Xác minh gói không thành công", + "update_payment_method_message": "Rất tiếc, thẻ của bạn thanh toán không thành công, vui lòng cập nhật phương thức thanh toán và thử lại", "payment_method_authentication_failed": "Chúng tôi không thể xác thực phương thức thanh toán của bạn. Vui lòng chọn phương thức thanh toán khác và thử lại", "update_payment_method": "Cập nhật phương thức thanh toán", - "monthly": "Hàng tháng", - "yearly": "Hàng năm", - "month_short": "th", + "monthly": "Theo tháng", + "yearly": "Theo năm", + "month_short": "tháng", "year": "năm", "update_subscription": "Thay đổi gói", "update_subscription_title": "Xác nhận thay đổi gói", - "update_subscription_message": "Bạn có chắc chắn muốn thay đổi gói của mình không?", - "cancel_subscription": "Hủy đăng ký", - "cancel_subscription_message": "

Tất cả dữ liệu của bạn sẽ bị xóa khỏi máy chủ của chúng tôi vào cuối kỳ thanh toán này.

Bạn có chắc chắn muốn hủy đăng ký của mình không?

", - "cancel_subscription_with_addon_message": "

Bạn có chắc chắn muốn hủy đăng ký của mình không?

", - "subscription_cancel_success": "Hủy đăng ký thành công", - "reactivate_subscription": "Kích hoạt lại đăng ký", - "reactivate_subscription_message": "Khi được kích hoạt lại, bạn sẽ bị tính phí vào {{date, date}}", - "subscription_activate_success": "Kích hoạt đăng ký thành công", + "update_subscription_message": "Bạn có chắc muốn thay đổi gói của mình không?", + "cancel_subscription": "Hủy gói", + "cancel_subscription_message": "

Toàn bộ dữ liệu của bạn sẽ bị xóa khỏi máy chủ của chúng tôi vào cuối kỳ thanh toán này.

Bạn có chắc muốn hủy gói của mình không?

", + "cancel_subscription_with_addon_message": "

Bạn có chắc muốn hủy gói của mình không?

", + "subscription_cancel_success": "Hủy gói thành công", + "reactivate_subscription": "Kích hoạt lại gói", + "reactivate_subscription_message": "Khi kích hoạt lại, bạn sẽ bị tính phí vào {{date, date}}", + "subscription_activate_success": "Kích hoạt gói thành công ", "thank_you": "Cảm ơn bạn", - "cancel_subscription_on_mobile": "Hủy đăng ký di động", - "cancel_subscription_on_mobile_message": "Vui lòng hủy đăng ký của bạn từ ứng dụng di động để kích hoạt một đăng ký ở đây", - "mail_to_manage_subscription": "Vui lòng liên hệ với chúng tôi tại {{emailID}} để quản lý đăng ký của bạn", + "cancel_subscription_on_mobile": "Hủy gói trên điện thoại", + "cancel_subscription_on_mobile_message": "Vui lòng hủy gói của bạn từ ứng dụng di động để kích hoạt một gói ở đây", + "mail_to_manage_subscription": "Vui lòng liên hệ với chúng tôi qua {{emailID}} để quản lý gói của bạn", "rename": "Đổi tên", "rename_file": "Đổi tên tệp", "rename_album": "Đổi tên album", "delete_album": "Xóa album", "delete_album_title": "Xóa album?", - "delete_album_message": "Có xóa các bức ảnh (và video) có trong album này từ tất cả các album khác mà chúng là một phần không?", + "delete_album_message": "Xóa luôn các tấm ảnh (và video) có trong album này khỏi toàn bộ album khác cũng đang chứa chúng?", "delete_photos": "Xóa ảnh", "keep_photos": "Giữ ảnh", "share_album": "Chia sẻ album", - "sharing_with_self": "", - "sharing_already_shared": "", + "sharing_with_self": "Bạn không thể chia sẻ với chính mình", + "sharing_already_shared": "Bạn đã chia sẻ với {{email}} rồi", "sharing_album_not_allowed": "Chia sẻ album không được phép", - "sharing_disabled_for_free_accounts": "Chia sẻ bị vô hiệu hóa cho các tài khoản miễn phí", - "sharing_user_does_not_exist": "", + "sharing_disabled_for_free_accounts": "Tài khoản miễn phí không thể chia sẻ", + "sharing_user_does_not_exist": "Không tìm thấy người dùng với email này", "search": "Tìm kiếm", "search_results": "Kết quả tìm kiếm", "no_results": "Không tìm thấy kết quả", - "search_hint": "Tìm kiếm album, ngày tháng, mô tả, ...", + "search_hint": "Tìm album, ngày chụp, mô tả,...", "album": "Album", "date": "Ngày", "description": "Mô tả", "file_type": "Loại tệp", "magic": "Ma thuật", - "photos_count_zero": "Không có kỷ niệm", - "photos_count_one": "1 kỷ niệm", - "photos_count": "{{count, number}} kỷ niệm", - "terms_and_conditions": "Tôi đồng ý với các điều khoảnchính sách bảo mật", + "photos_count_zero": "Chưa có ảnh nào", + "photos_count_one": "1 ảnh", + "photos_count": "{{count, number}} ảnh", + "terms_and_conditions": "Tôi đồng ý điều khoảnchính sách bảo mật", "people": "Người", - "indexing_scheduled": "Lập chỉ mục đã được lên lịch...", - "indexing_photos": "", - "indexing_fetching": "", - "indexing_people": "", + "indexing_scheduled": "Đã lên lịch lập chỉ mục...", + "indexing_photos": "Đang cập nhật chỉ mục...", + "indexing_fetching": "Đang đồng bộ chỉ mục...", + "indexing_people": "Đang đồng bộ người...", "syncing_wait": "Đang đồng bộ...", - "people_empty_too_few": "Người sẽ được hiển thị ở đây khi có đủ ảnh của một người", - "unnamed_person": "Người không tên", + "people_empty_too_few": "Sẽ hiện người ở đây khi có ảnh của một người", + "unnamed_person": "Chưa đặt tên", "add_a_name": "Thêm một tên", "new_person": "Người mới", "add_name": "Thêm tên", "rename_person": "Đổi tên người", "reset_person_confirm": "Đặt lại người?", - "reset_person_confirm_message": "Tên, nhóm khuôn mặt và gợi ý cho người này sẽ được đặt lại", + "reset_person_confirm_message": "Tên, nhóm khuôn mặt và những gợi ý cho người này sẽ bị đặt lại", "ignore": "Bỏ qua", "ignore_person_confirm": "Bỏ qua người?", "ignore_person_confirm_message": "Nhóm khuôn mặt này sẽ không được hiển thị trong danh sách người", "ignored": "Đã bỏ qua", "show_person": "Hiển thị người", - "review_suggestions": "Xem xét gợi ý", + "review_suggestions": "Xem qua gợi ý", "saved_choices": "Lựa chọn đã lưu", "discard_changes": "Bỏ qua thay đổi", "discard_changes_confirm_message": "Bạn có thay đổi chưa được lưu. Những thay đổi này sẽ bị mất nếu bạn đóng mà không lưu", - "people_suggestions_finding": "Tìm kiếm khuôn mặt tương tự...", - "people_suggestions_empty": "Không còn gợi ý nào cho bây giờ", + "people_suggestions_finding": "Tìm khuôn mặt tương tự...", + "people_suggestions_empty": "Không còn gợi ý nào", "info": "Thông tin", "file_name": "Tên tệp", "caption_placeholder": "Thêm mô tả", @@ -277,79 +277,79 @@ "map": "Bản đồ", "enable_map": "Bật bản đồ", "enable_maps_confirm": "Bật bản đồ?", - "enable_maps_confirm_message": "

Điều này sẽ hiển thị ảnh của bạn trên bản đồ thế giới.

Bản đồ được lưu trữ bởi OpenStreetMap, và vị trí chính xác của ảnh của bạn sẽ không bao giờ được chia sẻ.

Bạn có thể tắt tính năng này bất cứ lúc nào từ Cài đặt.

", + "enable_maps_confirm_message": "

Ảnh của bạn sẽ hiển thị trên bản đồ thế giới.

Bản đồ được lưu trữ bởi OpenStreetMap, và vị trí chính xác ảnh của bạn không bao giờ được chia sẻ.

Bạn có thể tắt tính năng này bất cứ lúc nào từ Cài đặt.

", "disable_map": "Tắt bản đồ", "disable_maps_confirm": "Tắt bản đồ?", - "disable_maps_confirm_message": "

Điều này sẽ tắt hiển thị ảnh của bạn trên bản đồ thế giới.

Bạn có thể bật tính năng này bất cứ lúc nào từ Cài đặt.

", + "disable_maps_confirm_message": "

Ảnh của bạn sẽ thôi hiển thị trên bản đồ thế giới.

Bạn có thể bật tính năng này bất cứ lúc nào từ Cài đặt.

", "details": "Chi tiết", - "view_exif": "Xem tất cả dữ liệu Exif", - "no_exif": "Không có dữ liệu Exif", + "view_exif": "Xem thông số Exif", + "no_exif": "No Exif data", "exif": "Exif", - "two_factor": "Xác thực hai yếu tố", - "two_factor_authentication": "Xác thực hai yếu tố", + "two_factor": "Xác thực 2 bước", + "two_factor_authentication": "Xác thực 2 bước", "two_factor_qr_help": "Quét mã QR bên dưới bằng ứng dụng xác thực yêu thích của bạn", "two_factor_manual_entry_title": "Nhập mã thủ công", "two_factor_manual_entry_message": "Vui lòng nhập mã này vào ứng dụng xác thực yêu thích của bạn", - "scan_qr_title": "Quét mã QR thay thế", - "enable_two_factor": "Bật xác thực hai yếu tố", + "scan_qr_title": "Quét mã QR", + "enable_two_factor": "Bật xác thực 2 bước", "enable": "Bật", "enabled": "Đã bật", - "lost_2fa_device": "Thiết bị xác thực hai yếu tố bị mất", + "lost_2fa_device": "Mất thiết bị xác thực 2 bước", "incorrect_code": "Mã không chính xác", - "two_factor_info": "Thêm một lớp bảo mật bổ sung bằng cách yêu cầu nhiều hơn email và mật khẩu của bạn để đăng nhập vào tài khoản của bạn", + "two_factor_info": "Thêm một lớp bảo mật bổ sung bằng cách yêu cầu nhiều hơn email và mật khẩu của bạn để đăng nhập", "disable": "Tắt", "reconfigure": "Cấu hình lại", "reconfigure_two_factor_hint": "Cập nhật thiết bị xác thực của bạn", - "update_two_factor": "Cập nhật xác thực hai yếu tố", - "update_two_factor_message": "Tiếp tục sẽ làm vô hiệu hóa bất kỳ thiết bị xác thực nào đã được cấu hình trước đó", + "update_two_factor": "Cập nhật xác thực 2 bước", + "update_two_factor_message": "Tiếp tục sẽ khiến mọi thiết bị xác thực được cấu hình trước đó bị vô hiệu hóa", "update": "Cập nhật", - "disable_two_factor": "Tắt xác thực hai yếu tố", - "disable_two_factor_message": "Bạn có chắc chắn muốn tắt xác thực hai yếu tố của mình không", + "disable_two_factor": "Tắt xác thực 2 bước", + "disable_two_factor_message": "Bạn có chắc muốn tắt xác thực 2 bước không", "export_data": "Xuất dữ liệu", "select_folder": "Chọn thư mục", "select_zips": "Chọn tệp zip", "faq": "Câu hỏi thường gặp", - "takeout_hint": "Giải nén tất cả các tệp zip vào cùng một thư mục và tải lên. Hoặc tải lên các tệp zip trực tiếp. Xem Câu hỏi thường gặp để biết chi tiết.", - "destination": "Điểm đến", + "takeout_hint": "Giải nén tất cả tệp zip vào cùng một thư mục và tải lên. Hoặc tải lên trực tiếp các tệp zip. Xem Câu hỏi thường gặp để biết thêm.", + "destination": "Đích đến", "start": "Bắt đầu", - "last_export_time": "Thời gian xuất cuối cùng", + "last_export_time": "Thời gian xuất gần nhất", "export_again": "Đồng bộ lại", - "local_storage_not_accessible": "Trình duyệt của bạn hoặc một tiện ích mở rộng đang chặn Ente không lưu dữ liệu vào bộ nhớ cục bộ", + "local_storage_not_accessible": "Trình duyệt của bạn hoặc một tiện ích mở rộng đang chặn Ente lưu dữ liệu vào bộ nhớ thiết bị", "email_already_taken": "Email đã được sử dụng", "live_photos_detected": "Các tệp ảnh và video từ Live Photos của bạn đã được gộp thành một tệp duy nhất", "ignored_uploads": "Tải lên đã bị bỏ qua", - "ignored_uploads_hint": "Bỏ qua những tệp này vì có tệp có tên và nội dung trùng khớp trong cùng một album", + "ignored_uploads_hint": "Những tệp này bị bỏ qua vì có tên và nội dung trùng khớp trong cùng một album", "file_not_uploaded_list": "Các tệp sau không được tải lên", "failed_uploads": "Tải lên không thành công", - "failed_uploads_hint": "Sẽ có một tùy chọn để thử lại khi việc tải lên hoàn tất", - "retry_failed_uploads": "Thử lại các tệp tải lên không thành công", + "failed_uploads_hint": "Sẽ có tùy chọn thử lại sau khi việc tải lên hoàn tất", + "retry_failed_uploads": "Thử tải lên lại các tệp không thành công", "thumbnail_generation_failed": "Tạo hình thu nhỏ không thành công", "thumbnail_generation_failed_hint": "Các tệp này đã được tải lên, nhưng rất tiếc chúng tôi không thể tạo hình thu nhỏ cho chúng.", "unsupported_files": "Tệp không được hỗ trợ", "unsupported_files_hint": "Ente chưa hỗ trợ các định dạng tệp này", "blocked_uploads": "Tải lên bị chặn", - "blocked_uploads_hint": "Trình duyệt của bạn hoặc một tiện ích mở rộng đang ngăn Ente sử dụng eTags để tải lên các tệp lớn.", + "blocked_uploads_hint": "Trình duyệt của bạn hoặc một tiện ích mở rộng đang chặn Ente sử dụng eTags để tải lên các tệp lớn.", "large_files": "Tệp lớn", - "large_files_hint": "Các tệp này đã không được tải lên vì chúng vượt quá giới hạn kích thước tệp tối đa của chúng tôi", + "large_files_hint": "Các tệp này không thể tải lên vì chúng vượt quá dung lượng tệp tối đa của chúng tôi", "insufficient_storage": "Không đủ dung lượng lưu trữ", - "insufficient_storage_hint": "Các tệp này đã không được tải lên vì chúng vượt quá giới hạn kích thước tối đa cho gói lưu trữ của bạn", - "uploads_in_progress": "Tải lên đang tiến hành", + "insufficient_storage_hint": "Các tệp này không thể tải lên vì chúng vượt quá dung lượng tối đa gói của bạn", + "uploads_in_progress": "Đang tải lên", "successful_uploads": "Tải lên thành công", "upload_to_album": "Tải lên album", "add_to_album": "Thêm vào album", "move_to_album": "Di chuyển đến album", - "unhide_to_album": "Hiện lại vào album", + "unhide_to_album": "Hiện lại trong album", "restore_to_album": "Khôi phục vào album", "section_all": "Tất cả", "section_uncategorized": "Chưa phân loại", "section_archive": "Lưu trữ", "section_hidden": "Ẩn", "section_trash": "Thùng rác", - "favorites": "Yêu thích", + "favorites": "Đã thích", "archive": "Lưu trữ", "archive_album": "Lưu trữ album", - "unarchive": "Khôi phục lưu trữ", - "unarchive_album": "Khôi phục lưu trữ album", + "unarchive": "Bỏ lưu trữ", + "unarchive_album": "Bỏ lưu trữ album", "hide_collection": "Ẩn album", "unhide_collection": "Hiện lại album", "move": "Di chuyển", @@ -357,28 +357,28 @@ "remove": "Xóa", "yes_remove": "Có, xóa", "remove_from_album": "Xóa khỏi album", - "move_to_trash": "Di chuyển vào thùng rác", - "trash_files_message": "Các tệp đã chọn sẽ bị xóa khỏi tất cả các album và di chuyển vào thùng rác.", - "trash_file_message": "Tệp sẽ bị xóa khỏi tất cả các album và di chuyển vào thùng rác.", + "move_to_trash": "Cho vào thùng rác", + "trash_files_message": "Các tệp đã chọn sẽ bị xóa khỏi tất cả album và cho vào thùng rác.", + "trash_file_message": "Tệp sẽ bị xóa khỏi tất cả album và cho vào thùng rác.", "delete_permanently": "Xóa vĩnh viễn", "restore": "Khôi phục", - "empty_trash": "Làm rỗng thùng rác", - "empty_trash_title": "Làm rỗng thùng rác?", + "empty_trash": "Xóa sạch thùng rác", + "empty_trash_title": "Xóa sạch thùng rác?", "empty_trash_message": "Các tệp này sẽ bị xóa vĩnh viễn khỏi tài khoản Ente của bạn.", "leave_album": "Rời album", - "leave_shared_album_title": "Rời album chia sẻ?", - "leave_shared_album_message": "Bạn sẽ rời album, và nó sẽ không còn hiển thị cho bạn.", + "leave_shared_album_title": "Rời album được chia sẻ?", + "leave_shared_album_message": "Bạn sẽ rời album, và nó sẽ không còn hiển thị với bạn.", "leave_shared_album": "Có, rời", "confirm_remove_message": "Các mục đã chọn sẽ bị xóa khỏi album này. Các mục chỉ có trong album này sẽ được chuyển đến Chưa phân loại.", - "confirm_remove_incl_others_message": "Một số mục bạn đang xóa đã được thêm bởi người khác, và bạn sẽ mất quyền truy cập vào chúng.", + "confirm_remove_incl_others_message": "Vài mục mà bạn đang xóa được thêm bởi người khác, và bạn sẽ mất quyền truy cập vào chúng.", "oldest": "Cũ nhất", - "last_updated": "Cập nhật lần cuối", + "last_updated": "Mới cập nhật", "name": "Tên", "fix_creation_time": "Sửa thời gian", "fix_creation_time_in_progress": "Đang sửa thời gian", "fix_creation_time_file_updated": "Thời gian tệp đã được cập nhật", "fix_creation_time_completed": "Đã cập nhật thành công tất cả các tệp", - "fix_creation_time_completed_with_errors": "Cập nhật thời gian tệp không thành công cho một số tệp, vui lòng thử lại", + "fix_creation_time_completed_with_errors": "Cập nhật thời gian một số tệp không thành công, vui lòng thử lại", "fix_creation_time_options": "Chọn tùy chọn bạn muốn sử dụng", "exif_date_time_original": "Exif:DateTimeOriginal", "exif_date_time_digitized": "Exif:DateTimeDigitized", @@ -396,7 +396,7 @@ "participants_count_one": "1 người tham gia", "participants_count": "{{count, number}} người tham gia", "add_viewers": "Thêm người xem", - "change_permission_to_viewer": "

{{selectedEmail}} sẽ không thể thêm nhiều ảnh hơn vào album

Họ vẫn có thể xóa ảnh đã thêm bởi họ

", + "change_permission_to_viewer": "

{{selectedEmail}} sẽ không thể thêm ảnh vào album

Họ vẫn có thể xóa ảnh đã thêm bởi họ

", "change_permission_to_collaborator": "{{selectedEmail}} sẽ có thể thêm ảnh vào album", "change_permission_title": "Thay đổi quyền?", "confirm_convert_to_viewer": "Có, chuyển thành người xem", @@ -417,10 +417,10 @@ "link_expired": "Liên kết đã hết hạn", "link_expired_message": "Liên kết này đã hết hạn hoặc đã bị vô hiệu hóa", "manage_link": "Quản lý liên kết", - "link_request_limit_exceeded": "Album này đã được xem trên quá nhiều thiết bị", + "link_request_limit_exceeded": "Album này đang được xem trên quá nhiều thiết bị", "allow_downloads": "Cho phép tải xuống", "allow_adding_photos": "Cho phép thêm ảnh", - "allow_adding_photos_hint": "Cho phép người có liên kết cũng thêm ảnh vào album chia sẻ.", + "allow_adding_photos_hint": "Cho phép người có liên kết thêm ảnh vào album chia sẻ.", "device_limit": "Giới hạn thiết bị", "none": "Không", "link_expiry": "Hết hạn liên kết", @@ -440,30 +440,30 @@ "public_link_created": "Liên kết công khai đã được tạo", "public_link_enabled": "Liên kết công khai đã được bật", "collect_photos": "Thu thập ảnh", - "disable_file_download": "Vô hiệu hóa tải xuống", - "disable_file_download_message": "

Bạn có chắc chắn muốn vô hiệu hóa nút tải xuống cho các tệp không?

Người xem vẫn có thể chụp ảnh màn hình hoặc lưu bản sao của ảnh của bạn bằng các công cụ bên ngoài.

", + "disable_file_download": "Tắt tải xuống", + "disable_file_download_message": "

Bạn có chắc muốn tắt nút tải xuống các tệp không?

Người xem vẫn có thể chụp ảnh màn hình hoặc sao chép ảnh của bạn bằng các công cụ bên ngoài.

", "shared_using": "Chia sẻ bằng {{url}}", - "sharing_referral_code": "Sử dụng mã {{referralCode}} để nhận 10 GB miễn phí", + "sharing_referral_code": "Dùng mã {{referralCode}} để nhận 10 GB miễn phí", "disable_password": "Vô hiệu hóa khóa mật khẩu", - "disable_password_message": "Bạn có chắc chắn muốn vô hiệu hóa khóa mật khẩu không?", + "disable_password_message": "Bạn có chắc muốn vô hiệu hóa khóa mật khẩu không?", "password_lock": "Khóa mật khẩu", "lock": "Khóa", "file": "Tệp", "folder": "Thư mục", - "google_takeout": "Google takeout", - "deduplicate_files": "Xóa trùng tệp", - "remove_duplicates": "", - "total_size": "", - "count": "", - "deselect_all": "", - "no_duplicates": "", - "duplicate_group_description": "", - "remove_duplicates_button_count": "", + "google_takeout": "Google Takeout", + "deduplicate_files": "Xóa tệp trùng", + "remove_duplicates": "Xóa trùng lặp", + "total_size": "Tổng dung lượng", + "count": "Số lượng", + "deselect_all": "Bỏ chọn tất cả", + "no_duplicates": "Không có trùng lặp", + "duplicate_group_description": "{{count}} mục, {{itemSize}} mỗi mục", + "remove_duplicates_button_count": "Xóa {{count, number}} mục", "stop_uploads_title": "Dừng tải lên?", - "stop_uploads_message": "Bạn có chắc chắn muốn dừng tất cả các tải lên đang diễn ra không?", + "stop_uploads_message": "Bạn có chắc muốn dừng tất cả mục đang tải lên không?", "yes_stop_uploads": "Có, dừng tải lên", "stop_downloads_title": "Dừng tải xuống?", - "stop_downloads_message": "Bạn có chắc chắn muốn dừng tất cả các tải xuống đang diễn ra không?", + "stop_downloads_message": "Bạn có chắc muốn dừng tất cả mục đang tải xuống không?", "yes_stop_downloads": "Có, dừng tải xuống", "albums": "Album", "albums_count_one": "1 Album", @@ -478,14 +478,14 @@ "upgrade_now": "Nâng cấp ngay", "renew_now": "Gia hạn ngay", "storage": "Lưu trữ", - "used": "đã sử dụng", + "used": "đã dùng", "you": "Bạn", "family": "Gia đình", "free": "miễn phí", "of": "của", "watch_folders": "Theo dõi thư mục", "watched_folders": "Thư mục đã theo dõi", - "no_folders_added": "Chưa có thư mục nào được thêm", + "no_folders_added": "Chưa thêm thư mục nào", "watch_folders_hint_1": "Các thư mục bạn thêm ở đây sẽ được theo dõi tự động", "watch_folders_hint_2": "Tải lên tệp mới vào Ente", "watch_folders_hint_3": "Xóa tệp đã xóa khỏi Ente", @@ -495,51 +495,51 @@ "stop_watching_folder_message": "Các tệp hiện có của bạn sẽ không bị xóa, nhưng Ente sẽ ngừng tự động cập nhật album Ente liên kết khi có thay đổi trong thư mục này.", "yes_stop": "Có, dừng lại", "change_folder": "Thay đổi Thư mục", - "view_logs": "", - "view_logs_message": "", - "weak_device_hint": "Trình duyệt web bạn đang sử dụng không đủ mạnh để mã hóa ảnh của bạn. Vui lòng thử đăng nhập vào Ente trên máy tính của bạn, hoặc tải xuống ứng dụng di động/desktop của Ente.", - "drag_and_drop_hint": "Hoặc kéo và thả vào cửa sổ Ente", + "view_logs": "Xem log", + "view_logs_message": "

Tải xuống nhật ký lỗi, để bạn có thể gửi qua email cho chúng tôi.

Lưu ý rằng, trong nhật ký lỗi sẽ bao gồm tên các tệp để giúp theo dõi vấn đề với từng tệp cụ thể.

", + "weak_device_hint": "Trình duyệt bạn đang sử dụng không đủ mạnh để mã hóa ảnh. Vui lòng dùng Ente trên máy tính, hoặc tải xuống ứng dụng di động/máy tính của Ente.", + "drag_and_drop_hint": "Hoặc kéo thả vào cửa sổ Ente", "authenticate": "Xác thực", "uploaded_to_single_collection": "Đã tải lên một bộ sưu tập", "uploaded_to_separate_collections": "Đã tải lên các bộ sưu tập riêng biệt", "nevermind": "Không sao", - "update_available": "Cập nhật có sẵn", - "update_installable_message": "Một phiên bản mới của Ente đã sẵn sàng để được cài đặt.", + "update_available": "Phiên bản mới", + "update_installable_message": "Ente có một phiên bản mới, sẵn sàng để cài đặt.", "install_now": "Cài đặt ngay", - "install_on_next_launch": "Cài đặt khi khởi động tiếp theo", - "update_available_message": "Một phiên bản mới của Ente đã được phát hành, nhưng không thể tự động tải xuống và cài đặt.", + "install_on_next_launch": "Cài đặt trong lần khởi động sau", + "update_available_message": "Ente có một phiên bản mới, nhưng không thể tự động tải xuống và cài đặt.", "download_and_install": "Tải xuống và cài đặt", "ignore_this_version": "Bỏ qua phiên bản này", "today": "Hôm nay", "yesterday": "Hôm qua", "enter_name": "Nhập tên", - "uploader_name_hint": "Thêm một tên để bạn bè biết ai là người đáng cảm ơn cho những bức ảnh tuyệt vời này!", + "uploader_name_hint": "Thêm một tên để bạn bè biết ai là người chụp những tấm ảnh tuyệt vời này!", "name_placeholder": "Tên...", "more_details": "Thêm chi tiết", "ml_search": "Học máy", - "ml_search_description": "Ente hỗ trợ học máy trên thiết bị cho nhận diện khuôn mặt, tìm kiếm kỳ diệu và các tính năng tìm kiếm nâng cao khác", - "ml_search_footnote": "Tìm kiếm kỳ diệu cho phép tìm kiếm ảnh theo nội dung của chúng, ví dụ: 'xe hơi', 'xe hơi đỏ', 'Ferrari'", + "ml_search_description": "Ente hỗ trợ học máy trên-thiết-bị nhằm nhận diện khuôn mặt, tìm kiếm vi diệu và các tính năng tìm kiếm nâng cao khác", + "ml_search_footnote": "Tìm kiếm vi diệu cho phép tìm ảnh theo nội dung của chúng, ví dụ: 'xe hơi', 'xe hơi đỏ', 'Ferrari'", "indexing": "Đang lập chỉ mục", "processed": "Đã xử lý", "indexing_status_running": "Đang chạy", "indexing_status_fetching": "Đang lấy", "indexing_status_scheduled": "Đã lên lịch", "indexing_status_done": "Đã hoàn thành", - "ml_search_disable": "Vô hiệu hóa học máy", - "ml_search_disable_confirm": "Bạn có muốn vô hiệu hóa học máy trên tất cả các thiết bị của bạn không?", + "ml_search_disable": "Tắt học máy", + "ml_search_disable_confirm": "Bạn có muốn tắt học máy trên tất cả các thiết bị của bạn không?", "ml_consent": "Bật học máy", "ml_consent_title": "Bật học máy?", - "ml_consent_description": "

Nếu bạn bật học máy, Ente sẽ trích xuất thông tin như hình dạng khuôn mặt từ các tệp, bao gồm cả những tệp được chia sẻ với bạn.

Điều này sẽ xảy ra trên thiết bị của bạn, và bất kỳ thông tin sinh trắc học nào được tạo ra sẽ được mã hóa đầu cuối.

Vui lòng nhấp vào đây để biết thêm chi tiết về tính năng này trong chính sách quyền riêng tư của chúng tôi

", + "ml_consent_description": "

Nếu bạn bật học máy, Ente sẽ trích xuất thông tin như hình dạng khuôn mặt từ các tệp, gồm cả những tệp mà bạn được chia sẻ.

Việc này sẽ diễn ra trên thiết bị của bạn, với mọi thông tin sinh trắc học tạo ra đều được mã hóa đầu cuối.

Vui lòng nhấn vào đây để biết thêm chi tiết về tính năng này trong chính sách quyền riêng tư của chúng tôi

", "ml_consent_confirmation": "Tôi hiểu và muốn bật học máy", - "labs": "Phòng thí nghiệm", + "labs": "Thử nghiệm", "password_strength_weak": "Độ mạnh mật khẩu: Yếu", "password_strength_moderate": "Độ mạnh mật khẩu: Trung bình", "password_strength_strong": "Độ mạnh mật khẩu: Mạnh", - "preferences": "Tùy chọn", + "preferences": "Thiết lập", "language": "Ngôn ngữ", "advanced": "Nâng cao", "export_directory_does_not_exist": "Thư mục xuất không hợp lệ", - "export_directory_does_not_exist_message": "

Thư mục xuất mà bạn đã chọn không tồn tại.

Vui lòng chọn một thư mục hợp lệ.

", + "export_directory_does_not_exist_message": "

Thư mục xuất mà bạn đã chọn không tồn tại.

Vui lòng chọn một thư mục khác.

", "storage_unit": { "b": "B", "kb": "KB", @@ -549,33 +549,33 @@ }, "stop": "Dừng", "sync_continuously": "Đồng bộ liên tục", - "export_starting": "Xuất bắt đầu...", + "export_starting": "Bắt đầu xuất...", "export_preparing": "Đang chuẩn bị...", "export_renaming_album_folders": "Đang đổi tên thư mục album...", - "export_trashing_deleted_files": "Đang xóa tệp đã xóa...", - "export_trashing_deleted_albums": "Đang xóa album đã xóa...", + "export_trashing_deleted_files": "Đang xóa vĩnh viễn các tệp...", + "export_trashing_deleted_albums": "Đang xóa vĩnh viễn các album...", "export_progress": "{{progress.success, number}} / {{progress.total, number}} mục đã đồng bộ", "pending_items": "Mục đang chờ", "delete_account_reason_label": "Lý do chính bạn xóa tài khoản là gì?", "delete_account_reason_placeholder": "Chọn một lý do", "delete_reason": { "missing_feature": "Thiếu một tính năng quan trọng mà tôi cần", - "behaviour": "Ứng dụng hoặc một tính năng nhất định không hoạt động như tôi nghĩ nó nên", - "found_another_service": "Tôi đã tìm thấy một dịch vụ khác mà tôi thích hơn", - "not_listed": "Lý do của tôi không có trong danh sách" + "behaviour": "Ứng dụng hoặc một tính năng nhất định không hoạt động như tôi muốn", + "found_another_service": "Tôi tìm thấy một dịch vụ khác mà tôi thích hơn", + "not_listed": "Lý do không có trong danh sách" }, "delete_account_feedback_label": "Chúng tôi rất tiếc khi thấy bạn ra đi. Vui lòng giải thích lý do bạn rời đi để giúp chúng tôi cải thiện.", "delete_account_feedback_placeholder": "Phản hồi", - "delete_account_confirm_checkbox_label": "Có, tôi muốn xóa tài khoản này và tất cả dữ liệu của nó vĩnh viễn", + "delete_account_confirm_checkbox_label": "Có, tôi muốn xóa vĩnh viễn tài khoản này và tất cả dữ liệu của nó", "delete_account_confirm": "Xác nhận xóa tài khoản", - "delete_account_confirm_message": "

Tài khoản này được liên kết với các ứng dụng Ente khác, nếu bạn sử dụng bất kỳ.

Dữ liệu bạn đã tải lên, trên tất cả các ứng dụng Ente, sẽ được lên lịch để xóa, và tài khoản của bạn sẽ bị xóa vĩnh viễn.

", - "feedback_required": "Xin vui lòng giúp chúng tôi với thông tin này", + "delete_account_confirm_message": "

Tài khoản này được liên kết với các ứng dụng Ente khác, nếu bạn có dùng.

Dữ liệu bạn đã tải lên, trên tất cả ứng dụng Ente, sẽ được lên lịch để xóa, và tài khoản của bạn sẽ bị xóa vĩnh viễn.

", + "feedback_required": "Mong bạn giúp chúng tôi thông tin này", "feedback_required_found_another_service": "Dịch vụ khác làm tốt hơn điều gì?", - "recover_two_factor": "Khôi phục xác thực hai yếu tố", + "recover_two_factor": "Khôi phục xác thực 2 bước", "at": "tại", "auth_next": "tiếp theo", "auth_download_mobile_app": "Tải xuống ứng dụng di động của chúng tôi để quản lý bí mật của bạn", - "no_codes_added_yet": "Chưa có mã nào được thêm", + "no_codes_added_yet": "Chưa thêm mã nào", "hide": "Ẩn", "unhide": "Hiện", "sort_by": "Sắp xếp theo", @@ -583,30 +583,30 @@ "oldest_first": "Cũ nhất trước", "pin_album": "Ghim album", "unpin_album": "Bỏ ghim album", - "unpreviewable_file_message": "Tệp này không thể được xem trước", - "download_complete": "Tải xuống hoàn tất", + "unpreviewable_file_message": "Không thể xem trước tệp này", + "download_complete": "Tải xuống xong", "downloading_album": "Đang tải xuống {{name}}", "download_failed": "Tải xuống thất bại", "download_progress": "{{count, number}} / {{total, number}} tệp", - "christmas": "Giáng sinh", - "christmas_eve": "Đêm Giáng sinh", - "new_year": "Năm mới", - "new_year_eve": "Đêm giao thừa", + "christmas": "Giáng Sinh", + "christmas_eve": "Đêm Thánh", + "new_year": "Năm Mới", + "new_year_eve": "Đêm Giao Thừa", "image": "Hình ảnh", "video": "Video", - "live_photo": "Ảnh trực tiếp", - "live": "", - "edit_image": "", + "live_photo": "Ảnh Live", + "live": "Live", + "edit_image": "Chỉnh sửa ảnh", "photo_editor": "Trình chỉnh sửa ảnh", - "confirm_editor_close": "Bạn có chắc chắn muốn đóng trình chỉnh sửa không?", - "confirm_editor_close_message": "Tải xuống hình ảnh đã chỉnh sửa của bạn hoặc lưu bản sao vào Ente để giữ lại các thay đổi của bạn.", + "confirm_editor_close": "Bạn có chắc muốn đóng trình chỉnh sửa không?", + "confirm_editor_close_message": "Tải xuống hình ảnh đã chỉnh sửa hoặc lưu bản sao vào Ente để giữ các thay đổi của bạn.", "brightness": "Độ sáng", "contrast": "Độ tương phản", "saturation": "Độ bão hòa", - "blur": "Mờ", + "blur": "Độ mờ", "transform": "Biến đổi", "crop": "Cắt", - "aspect_ratio": "Tỷ lệ khung hình", + "aspect_ratio": "Tỉ lệ khung hình", "square": "Hình vuông", "freehand": "Vẽ tự do", "apply_crop": "Áp dụng cắt", @@ -614,27 +614,27 @@ "rotate_left": "Xoay trái", "rotate_right": "Xoay phải", "flip": "Lật", - "flip_vertically": "Lật theo chiều dọc", - "flip_horizontally": "Lật theo chiều ngang", + "flip_vertically": "Lật dọc", + "flip_horizontally": "Lật ngang", "download_edited": "Tải xuống đã chỉnh sửa", "save_a_copy_to_ente": "Lưu một bản sao vào Ente", "restore_original": "Khôi phục gốc", - "photo_edit_required_to_save": "Ít nhất một biến đổi hoặc điều chỉnh màu sắc phải được thực hiện trước khi lưu.", + "photo_edit_required_to_save": "Phải thực hiện ít nhất một biến đổi hoặc điều chỉnh màu sắc trước khi lưu.", "colors": "Màu sắc", "invert_colors": "Đảo ngược màu", "reset": "Đặt lại", "faster_upload": "Tải lên nhanh hơn", - "faster_upload_description": "Định tuyến tải lên qua các máy chủ gần đó", - "open_ente_on_startup": "", + "faster_upload_description": "Tải lên các máy chủ gần bạn", + "open_ente_on_startup": "Mở Ente khi khởi động", "cast_album_to_tv": "Phát album trên TV", - "cast_to_tv": "", + "cast_to_tv": "Phát trên TV", "enter_cast_pin_code": "Nhập mã bạn thấy trên TV bên dưới để ghép nối thiết bị này.", "code": "Mã", "pair_device_to_tv": "Ghép nối thiết bị", "tv_not_found": "Không tìm thấy TV. Bạn đã nhập mã PIN đúng chưa?", "cast_auto_pair": "Ghép nối tự động", "cast_auto_pair_description": "Ghép nối tự động chỉ hoạt động với các thiết bị hỗ trợ Chromecast.", - "choose_device_from_browser": "Chọn một thiết bị tương thích với phát từ cửa sổ trình duyệt.", + "choose_device_from_browser": "Chọn một thiết bị phát tương thích từ cửa sổ trình duyệt.", "cast_auto_pair_failed": "Ghép nối tự động Chromecast thất bại. Vui lòng thử lại.", "pair_with_pin": "Ghép nối bằng PIN", "pair_with_pin_description": "Ghép nối bằng PIN hoạt động với bất kỳ màn hình nào bạn muốn xem album của mình.", @@ -643,29 +643,29 @@ "passkey_fetch_failed": "Không thể lấy khóa truy cập của bạn.", "manage_passkey": "Quản lý khóa truy cập", "delete_passkey": "Xóa khóa truy cập", - "delete_passkey_confirmation": "Bạn có chắc chắn muốn xóa khóa truy cập này không? Hành động này không thể hoàn tác.", + "delete_passkey_confirmation": "Bạn có chắc muốn xóa khóa truy cập này không? Hành động này không thể hoàn tác.", "rename_passkey": "Đổi tên khóa truy cập", "add_passkey": "Thêm khóa truy cập", "enter_passkey_name": "Nhập tên khóa truy cập", - "passkeys_description": "Khóa truy cập là một yếu tố thứ hai hiện đại và an toàn cho tài khoản Ente của bạn. Chúng sử dụng xác thực sinh trắc học trên thiết bị để tiện lợi và an toàn.", - "created_at": "Được tạo vào", + "passkeys_description": "Khóa truy cập là một yếu tố bảo mật hiện đại cho tài khoản Ente của bạn. Chúng sử dụng xác thực sinh trắc học trên thiết bị để tiện lợi và an toàn.", + "created_at": "Đã tạo vào", "passkey_add_failed": "Không thể thêm khóa truy cập", "passkey_login_failed": "Đăng nhập bằng khóa truy cập thất bại", "passkey_login_invalid_url": "URL đăng nhập không hợp lệ.", "passkey_login_already_claimed_session": "Phiên này đã được xác minh.", "passkey_login_generic_error": "Đã xảy ra lỗi khi đăng nhập bằng khóa truy cập.", "passkey_login_credential_hint": "Nếu khóa truy cập của bạn ở trên thiết bị khác, bạn có thể mở trang này trên thiết bị đó để xác minh.", - "passkeys_not_supported": "Khóa truy cập không được hỗ trợ trong trình duyệt này", + "passkeys_not_supported": "Khóa truy cập không được hỗ trợ trên trình duyệt này", "try_again": "Thử lại", "check_status": "Kiểm tra trạng thái", "passkey_login_instructions": "Thực hiện các bước từ trình duyệt của bạn để tiếp tục đăng nhập.", "passkey_login": "Đăng nhập bằng khóa truy cập", "totp_login": "Đăng nhập bằng TOTP", - "passkey": "Mã khóa", - "passkey_verify_description": "Xác minh mã khóa của bạn để đăng nhập vào tài khoản.", + "passkey": "Khóa truy cập", + "passkey_verify_description": "Xác minh khóa truy cập của bạn để đăng nhập vào tài khoản.", "waiting_for_verification": "Đang chờ xác minh...", "verification_still_pending": "Xác minh vẫn đang chờ", - "passkey_verified": "Mã khóa đã được xác minh", + "passkey_verified": "Khóa truy cập đã được xác minh", "redirecting_back_to_app": "Đang chuyển hướng bạn trở lại ứng dụng...", "redirect_close_instructions": "Bạn có thể đóng cửa sổ này sau khi ứng dụng mở.", "redirect_again": "Chuyển hướng lại", @@ -675,15 +675,15 @@ "server_endpoint": "Điểm cuối máy chủ", "more_information": "Thêm thông tin", "save": "Lưu", - "theme": "", - "system": "", - "light": "", - "dark": "", - "streamable_videos": "", - "processing_videos_status": "", - "share_favorites": "", - "person_favorites": "", - "shared_favorites": "", - "added_by_name": "", - "unowned_files_not_processed": "" + "theme": "Chủ đề", + "system": "Giống hệ thống", + "light": "Sáng", + "dark": "Tối", + "streamable_videos": "Video có thể phát", + "processing_videos_status": "Đang xử lý video...", + "share_favorites": "Chia sẻ những mục thích", + "person_favorites": "{{name}} đã thích", + "shared_favorites": "Những mục thích đã chia sẻ", + "added_by_name": "Được thêm bởi {{name}}", + "unowned_files_not_processed": "Các tệp được thêm bởi người dùng khác không được xử lý" } diff --git a/web/packages/base/locales/zh-CN/translation.json b/web/packages/base/locales/zh-CN/translation.json index 95b6ff8d84..0bfb3cce8e 100644 --- a/web/packages/base/locales/zh-CN/translation.json +++ b/web/packages/base/locales/zh-CN/translation.json @@ -32,7 +32,7 @@ "set_password": "设置密码", "sign_in": "登录", "incorrect_password": "密码错误", - "incorrect_password_or_no_account": "", + "incorrect_password_or_no_account": "密码错误或邮箱未注册", "pick_password_hint": "请输入我们可以用来加密您数据的密码", "pick_password_caution": "我们不会存储您的密码,因此如果您忘记密码, 我们将无法帮助您在没有恢复密钥的情况下恢复您的数据。", "key_generation_in_progress": "正在生成加密密钥...", @@ -40,7 +40,7 @@ "referral_source_hint": "您是如何知道Ente的? (可选的)", "referral_source_info": "我们不跟踪应用程序安装情况,如果您告诉我们您是在哪里找到我们的,将会有所帮助!", "password_mismatch_error": "两次输入的密码不一致", - "show_or_hide_password": "", + "show_or_hide_password": "显示或隐藏密码", "welcome_to_ente_title": "欢迎来到 ", "welcome_to_ente_subtitle": "端到端加密的照片存储和共享", "new_album": "新建相册", @@ -58,12 +58,12 @@ "add_photos_count": "添加 {{count, number}} 个项目", "select_photos": "选择照片", "file_upload": "上传文件", - "preparing": "", - "processed_counts": "", - "upload_reading_metadata_files": "", + "preparing": "准备中", + "processed_counts": "{{count, number}} / {{total, number}}", + "upload_reading_metadata_files": "正在读取元数据文件", "upload_cancelling": "正在取消剩余的上传内容", - "upload_done": "", - "upload_skipped": "", + "upload_done": "{{count, number}} 个已上传", + "upload_skipped": "{{count, number}} 个已跳过", "initial_load_delay_warning": "第一次加载可能需要一些时间", "no_account": "没有账号", "existing_account": "已有账号", @@ -96,15 +96,15 @@ "exit_fullscreen": "退出全屏", "go_fullscreen": "全屏显示", "zoom": "缩放", - "play": "", - "pause": "", + "play": "播放", + "pause": "暂停", "previous": "上一个", "next": "下一个", - "video_seek": "", - "quality": "", - "auto": "", - "original": "", - "speed": "", + "video_seek": "视频跳转", + "quality": "质量", + "auto": "自动", + "original": "原始", + "speed": "速度", "title_photos": "Ente 照片", "title_auth": "Ente 验证器", "title_accounts": "Ente 账户", @@ -162,7 +162,7 @@ "ok": "确定", "success": "成功", "error": "错误", - "note": "", + "note": "提示", "offline_message": "您处于离线状态,正在显示已缓存的回忆", "install": "安装", "install_mobile_app": "安装我们的 AndroidiOS 应用程序来自动备份您的所有照片", @@ -225,7 +225,7 @@ "delete_album_message": "也删除此相册中存在的照片(和视频),从 他们所加入的所有 个其他相册?", "delete_photos": "删除照片", "keep_photos": "保留照片", - "share_album": "分享相册", + "share_album": "共享相册", "sharing_with_self": "您不能与自己共享", "sharing_already_shared": "您已经与 {{email}} 共享了", "sharing_album_not_allowed": "不允许分享相册", @@ -389,7 +389,7 @@ "modify_sharing": "更改共享", "add_collaborators": "添加协作者", "add_new_email": "添加新的电子邮件", - "shared_with_people_count_zero": "与特定人员分享", + "shared_with_people_count_zero": "与特定人员共享", "shared_with_people_count_one": "已与1个人共享", "shared_with_people_count": "已与 {count, number} 个人共享", "participants_count_zero": "暂无参与者", @@ -442,7 +442,7 @@ "collect_photos": "收集照片", "disable_file_download": "禁止下载", "disable_file_download_message": "

您确定要禁用文件下载按钮吗?

观看者仍然可以使用外部工具进行屏幕截图或保存您的照片副本。

", - "shared_using": "分享方式 {{url}}", + "shared_using": "共享方式 {{url}}", "sharing_referral_code": "使用代码 {{referralCode}} 获得 10 GB 免费空间", "disable_password": "禁用密码锁", "disable_password_message": "您确定要禁用密码锁吗?", @@ -627,7 +627,7 @@ "faster_upload_description": "通过附近的服务器路由上传", "open_ente_on_startup": "启动时打开 Ente", "cast_album_to_tv": "在电视上播放相册", - "cast_to_tv": "", + "cast_to_tv": "在电视上播放", "enter_cast_pin_code": "输入您在下面的电视上看到的代码来配对此设备。", "code": "代码", "pair_device_to_tv": "配对设备", @@ -679,11 +679,11 @@ "system": "系统", "light": "浅色", "dark": "深色", - "streamable_videos": "", - "processing_videos_status": "", - "share_favorites": "", - "person_favorites": "", - "shared_favorites": "", - "added_by_name": "", - "unowned_files_not_processed": "" + "streamable_videos": "可流媒体播放的视频", + "processing_videos_status": "正在处理视频...", + "share_favorites": "共享收藏", + "person_favorites": "{{name}}的收藏", + "shared_favorites": "已共享的收藏", + "added_by_name": "由{{name}}添加", + "unowned_files_not_processed": "由其他用户添加的文件未被处理" } From 53210c2212204796eaadda5b2995ddf74636e08f Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 7 Jul 2025 01:17:58 +0000 Subject: [PATCH 061/109] New Crowdin translations by GitHub Action --- mobile/apps/auth/lib/l10n/arb/app_et.arb | 204 ++++++++++++++++++++++- mobile/apps/auth/lib/l10n/arb/app_hu.arb | 12 +- mobile/apps/auth/lib/l10n/arb/app_ru.arb | 14 +- 3 files changed, 221 insertions(+), 9 deletions(-) diff --git a/mobile/apps/auth/lib/l10n/arb/app_et.arb b/mobile/apps/auth/lib/l10n/arb/app_et.arb index 9a53d6796f..57294fe402 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_et.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_et.arb @@ -1,4 +1,206 @@ { + "account": "Kasutajakonto", + "unlock": "Ava", + "recoveryKey": "Taastevõti", + "counterAppBarTitle": "Loendur", + "@counterAppBarTitle": { + "description": "Text shown in the AppBar of the Counter Page" + }, + "onBoardingBody": "Sinu kaheastmelise autentimise koodide turvaline varundus", + "onBoardingGetStarted": "Alusta", + "setupFirstAccount": "Lisa oma esimene kasutajakonto", + "importScanQrCode": "Skanneeri QR-koodi", + "qrCode": "QR-kood", + "importAccountPageTitle": "Sisesta kasutajakonto üksikasjad", + "secretCanNotBeEmpty": "Saladus ei tohi jääda tühjaks", + "bothIssuerAndAccountCanNotBeEmpty": "Nii kasutajakonto kui väljaandja ei tohi tühjaks jääda", + "incorrectDetails": "Viga üksikasjades", + "pleaseVerifyDetails": "Palun kontrolli andmeid ja proovi uuesti", + "codeIssuerHint": "Väljaandja", + "codeSecretKeyHint": "Salajane võti", + "secret": "Saladus", + "all": "Kõik", + "notes": "Märkmed", + "notesLengthLimit": "Märkmete pikkus võib olla kuni {count} tähemärki", + "@notesLengthLimit": { + "description": "Text to indicate the maximum number of characters allowed for notes", + "placeholders": { + "count": { + "description": "The maximum number of characters allowed for notes", + "type": "int", + "example": "100" + } + } + }, + "codeTagHint": "Silt", + "accountKeyType": "Võtme tüüp", + "sessionExpired": "Sessioon on aegunud", + "@sessionExpired": { + "description": "Title of the dialog when the users current session is invalid/expired" + }, + "pleaseLoginAgain": "Palun logi uuesti sisse", "loggingOut": "Väljalogimine...", - "useRecoveryKey": "Kasuta taastevõtit" + "saveAction": "Salvesta", + "trash": "Prügikast", + "viewLogsAction": "Vaata logisid", + "preparingLogsTitle": "Valmistan logisid ette...", + "emailLogsTitle": "Logide saatmine e-postiga", + "emailLogsMessage": "Palun saada logid e-posti aadressile {email}", + "@emailLogsMessage": { + "placeholders": { + "email": { + "type": "String" + } + } + }, + "copyEmailAction": "Kopeeri e-posti aadress", + "exportLogsAction": "Ekspordi logid", + "reportABug": "Teata veast", + "contactSupport": "Võtke ühendust klienditoega", + "blog": "Blogi", + "verifyPassword": "Korda salasõna", + "pleaseWait": "Palun oota...", + "generatingEncryptionKeysTitle": "Loon krüptovõtmeid...", + "recreatePassword": "Loo salasõna uuesti", + "useRecoveryKey": "Kasuta taastevõtit", + "incorrectPasswordTitle": "Vale salasõna", + "welcomeBack": "Tere tulemast tagasi!", + "emailAlreadyRegistered": "E-posti aadress on juba registreeritud.", + "emailNotRegistered": "E-posti aadress pole registreeritud.", + "changeEmail": "Muuda e-posti aadressi", + "changePassword": "Muuda salasõna", + "data": "Andmed", + "passwordForDecryptingExport": "Salasõna eksporditud andmete dekrüptimiseks", + "passwordEmptyError": "Salasõna väli ei saa olla tühi", + "ok": "Sobib", + "cancel": "Katkesta", + "yes": "Jah", + "no": "Ei", + "email": "E-post", + "support": "Kasutajatugi", + "settings": "Seadistused", + "copied": "Kopeeritud", + "pleaseTryAgain": "Palun proovi uuesti", + "existingUser": "Olemasolev kasutaja", + "newUser": "Uus kasutaja Ente jaoks", + "delete": "Kustuta", + "enterYourPasswordHint": "Sisesta oma salasõna", + "forgotPassword": "Unustasin salasõna", + "oops": "Vaat kus lops!", + "faq": "KKK", + "somethingWentWrongMessage": "Midagi läks valesti, palun proovi uuesti", + "scan": "Skanneeri", + "scanACode": "Skanneeri QR-koodi", + "verify": "Kinnita", + "verifyEmail": "Kinnita e-posti aadress", + "enterCodeHint": "Sisesta oma autentimisrakendusest\n6-numbriline kood", + "lostDeviceTitle": "Kas kaotasid oma seadme?", + "recoverAccount": "Taasta oma kasutajakonto", + "enterRecoveryKeyHint": "Sisesta oma taastevõti", + "recover": "Taasta", + "invalidQRCode": "Vigane QR-kood", + "noRecoveryKeyTitle": "Sul pole taastevõtit?", + "enterEmailHint": "Sisesta oma e-posti aadress", + "enterNewEmailHint": "Sisesta oma uus e-posti aadress", + "invalidEmailTitle": "Vigane e-posti aadress", + "invalidEmailMessage": "Palun sisesta korrektne e-posti aadress.", + "deleteAccount": "Kustuta kasutajakonto", + "deleteAccountQuery": "Meil on kahju, et soovid lahkuda. Kas sul tekkis mõni viga või probleem?", + "yesSendFeedbackAction": "Jah, saadan tagasisidet", + "noDeleteAccountAction": "Ei, kustuta kasutajakonto", + "initiateAccountDeleteTitle": "Kasutajakonto kustutamiseks palun tuvasta end", + "sendEmail": "Saada e-kiri", + "createNewAccount": "Loo uus kasutajakonto", + "weakStrength": "Nõrk", + "strongStrength": "Tugev", + "moderateStrength": "Keskmine", + "confirmPassword": "Korda salasõna", + "close": "Sulge", + "oopsSomethingWentWrong": "Vaat kus lops! Midagi läks valesti.", + "selectLanguage": "Vali keel", + "language": "Keel", + "security": "Turvalisus", + "lockscreen": "Lukustusvaade", + "search": "Otsi", + "noResult": "Tulemusi pole", + "addCode": "Lisa kood", + "scanAQrCode": "Skanneeri QR-koodi", + "enterDetailsManually": "Sisesta üksikasjad käsitsi", + "edit": "Muuda", + "share": "Jaga", + "shareCodes": "Jaga koodi", + "restore": "Taasta", + "copiedToClipboard": "Kopeeritud lõikelauale", + "copiedNextToClipboard": "Järgmine kood on kopeeritud lõikelauale", + "error": "Viga", + "recoveryKeyCopiedToClipboard": "Taastevõti on kopeeritud lõikelauale", + "recoveryKeyOnForgotPassword": "Kui unustad oma salasõna, siis see krüptovõti on ainus võimalus sinu andmete taastamiseks.", + "saveKey": "Salvesta võti", + "save": "Salvesta", + "send": "Saada", + "back": "Tagasi", + "passwordStrength": "Salasõna tugevus: {passwordStrengthValue}", + "@passwordStrength": { + "description": "Text to indicate the password strength", + "placeholders": { + "passwordStrengthValue": { + "description": "The strength of the password as a string", + "type": "String", + "example": "Weak or Moderate or Strong" + } + }, + "message": "Password Strength: {passwordStrengthText}" + }, + "password": "Salasõna", + "privacyPolicyTitle": "Privaatsusreeglid", + "termsOfServicesTitle": "Kasutustingimused", + "encryption": "Krüptimine", + "setPasswordTitle": "Sisesta salasõna", + "changePasswordTitle": "Muuda salasõna", + "resetPasswordTitle": "Lähtesta salasõna", + "encryptionKeys": "Krüptovõtmed", + "passwordChangedSuccessfully": "Salasõna muutmine õnnestus", + "howItWorks": "Kuidas see töötab", + "ackPasswordLostWarning": "Ma saan aru, et salasõna kaotamisel kaotan ka ligipääsu oma andmetele - minu andmed on ju läbivalt krüptitud.", + "loginTerms": "Sisselogdes nõustun kasutustingimustega ja privaatsusreeglitega", + "logInLabel": "Logi sisse", + "logout": "Logi välja", + "areYouSureYouWantToLogout": "Kas oled kindel, et soovid välja logida?", + "yesLogout": "Jah, logi välja", + "theme": "Kujundus", + "lightTheme": "Hele kujundus", + "darkTheme": "Tume kujundus", + "systemTheme": "Süsteemi kujundus", + "verifyingRecoveryKey": "Kontrollin taastevõtit...", + "recoveryKeyVerified": "Taastevõti on kontrollitud", + "recreatePasswordTitle": "Loo salasõna uuesti", + "tryAgain": "Proovi uuesti", + "privacy": "Privaatsus", + "terms": "Kasutustingimused", + "checkForUpdates": "Kontrolli uuendusi", + "checkStatus": "Kontrolli olekut", + "downloadUpdate": "Laadi alla", + "criticalUpdateAvailable": "Saadaval on kriitiline uuendus", + "updateAvailable": "Saadaval on uuendus", + "update": "Uuenda", + "checking": "Kontrollin...", + "youAreOnTheLatestVersion": "Kasutad viimast versiooni", + "warning": "Hoiatus", + "exportWarningDesc": "Eksporditud failis on privaatsed andmed. Palun hoia seda turvaliselt.", + "iUnderStand": "Sain aru", + "@iUnderStand": { + "description": "Text for the button to confirm the user understands the warning" + }, + "enterPassword": "Sisesta salasõna", + "createNewTag": "Lisa uus silt", + "tag": "Silt", + "create": "Loo", + "editTag": "Muuda silti", + "deleteTagTitle": "Kas kustutame sildi?", + "deleteTagMessage": "Kas sa oled kindel, et soovid selle sildi kustutada? Seda tegevust ei saa tagasi pöörata.", + "updateNotAvailable": "Uuendust pole saadaval", + "reEnterPassword": "Sisesta salasõna uuesti", + "setNewPassword": "Sisesta uus salasõna", + "enterPin": "Sisesta PIN-kood", + "setNewPin": "Määra uus PIN-kood" } \ No newline at end of file diff --git a/mobile/apps/auth/lib/l10n/arb/app_hu.arb b/mobile/apps/auth/lib/l10n/arb/app_hu.arb index 68f8c804da..7494047857 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_hu.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_hu.arb @@ -18,7 +18,7 @@ "incorrectDetails": "Helytelen adatok", "pleaseVerifyDetails": "Kérjük, ellenőrizd az adataid, majd próbáld meg újra", "codeIssuerHint": "Kibocsátó", - "codeSecretKeyHint": "Titkos (Secret) kulcs", + "codeSecretKeyHint": "Titkos kulcs", "secret": "Titkos kód", "all": "Minden", "notes": "Megjegyzések", @@ -223,7 +223,7 @@ "saveOrSendDescription": "El szeretné menteni ezt a tárhelyére (alapértelmezés szerint a Letöltések mappába), vagy elküldi más alkalmazásoknak?", "saveOnlyDescription": "El szeretné menteni ezt a tárhelyére (alapértelmezés szerint a Letöltések mappába)?", "back": "Vissza", - "createAccount": "Jelszó erőssége:", + "createAccount": "Felhasználó létrehozás", "passwordStrength": "Jelszó erőssége: {passwordStrengthValue}", "@passwordStrength": { "description": "Text to indicate the password strength", @@ -381,7 +381,7 @@ "deleteCodeAuthMessage": "Hitelesítés a kód törléséhez", "showQRAuthMessage": "Hitelesítés a QR kód megjelenítéséhez", "confirmAccountDeleteTitle": "Fiók törlésének megerősítése", - "confirmAccountDeleteMessage": "", + "confirmAccountDeleteMessage": "Ez a fiók össze van kapcsolva más Ente-alkalmazásokkal, ha használ ilyet.\n\nA feltöltött adataid törlését ütemezzük az összes Ente alkalmazásban, és a fiókod véglegesen törlésre kerül.", "androidBiometricHint": "Személyazonosság ellenőrzése", "@androidBiometricHint": { "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." @@ -515,5 +515,9 @@ "loginWithAuthAccount": "Jelentkezzen be Auth fiókjával", "freeStorageOffer": "10% kedvezmény on ente photos", "freeStorageOfferDescription": "Használja az \"AUTH\" kódot, hogy 10% kedvezményt kapjon az első évben", - "type": "Típus" + "advanced": "Haladó", + "algorithm": "Algoritmus", + "type": "Típus", + "period": "Időszak", + "digits": "Számjegyek" } \ No newline at end of file diff --git a/mobile/apps/auth/lib/l10n/arb/app_ru.arb b/mobile/apps/auth/lib/l10n/arb/app_ru.arb index 0c4d01fe25..d28c0cc888 100644 --- a/mobile/apps/auth/lib/l10n/arb/app_ru.arb +++ b/mobile/apps/auth/lib/l10n/arb/app_ru.arb @@ -173,6 +173,7 @@ "invalidQRCode": "Неверный QR-код", "noRecoveryKeyTitle": "Нет ключа восстановления?", "enterEmailHint": "Введите адрес электронной почты", + "enterNewEmailHint": "Введите ваш новый адрес электронной почты", "invalidEmailTitle": "Неверный адрес электронной почты", "invalidEmailMessage": "Пожалуйста, введите действительный адрес электронной почты.", "deleteAccount": "Удалить аккаунт", @@ -334,10 +335,10 @@ } } }, - "manualSort": "Ручная", + "manualSort": "Пользовательская", "editOrder": "Изменить порядок", - "mostFrequentlyUsed": "Частота использования", - "mostRecentlyUsed": "Недавно использованные", + "mostFrequentlyUsed": "Часто используемые", + "mostRecentlyUsed": "Недавно используемые", "activeSessions": "Активные сеансы", "somethingWentWrongPleaseTryAgain": "Что-то пошло не так. Попробуйте еще раз", "thisWillLogYouOutOfThisDevice": "Вы выйдете из этого устройства!", @@ -513,5 +514,10 @@ "free5GB": "5Гб бесплатного пространства на ente Фото", "loginWithAuthAccount": "Войти с помощью учетной записи Auth", "freeStorageOffer": "Скидка 10% на ente фото", - "freeStorageOfferDescription": "Используйте код \"AUTH\", чтобы получить скидку 10% в первый год" + "freeStorageOfferDescription": "Используйте код \"AUTH\", чтобы получить скидку 10% в первый год", + "advanced": "Расширенные", + "algorithm": "Алгоритм", + "type": "Тип", + "period": "Период", + "digits": "Цифр" } \ No newline at end of file From 0748c9bf8d73fbfdf96c0650dbe3e5af685621b6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 08:07:18 +0530 Subject: [PATCH 062/109] Albums static link preview --- web/apps/photos/public/images/preview.jpg | Bin 0 -> 476599 bytes web/apps/photos/src/pages/_app.tsx | 4 +- web/packages/base/components/Head.tsx | 62 ++++++++++++++++++++++ web/packages/base/origins.ts | 7 +++ 4 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 web/apps/photos/public/images/preview.jpg diff --git a/web/apps/photos/public/images/preview.jpg b/web/apps/photos/public/images/preview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2e3d9f1da442f9251dd0e5e079ca7abdc1291c7f GIT binary patch literal 476599 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 z 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
  • +1u8ib93Gol!?82Ecf?};Ak)7^fBWmHe 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
      Z_AdBT8RB@~9 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 Jv+Mb9|Jg = ({ Component, pageProps }) => { return ( - + diff --git a/web/packages/base/components/Head.tsx b/web/packages/base/components/Head.tsx index 3ada4645a5..988ec68929 100644 --- a/web/packages/base/components/Head.tsx +++ b/web/packages/base/components/Head.tsx @@ -1,8 +1,11 @@ import Head from "next/head"; import React from "react"; +import { haveWindow } from "../env"; +import { albumsAppOrigin, isCustomAlbumsAppOrigin } from "../origins"; interface CustomHeadProps { title: string; + description?: string; } /** @@ -23,3 +26,62 @@ export const CustomHead: React.FC = ({ title }) => ( ); + +/** + * A static SSR-ed variant of {@link CustomHead} for use with the albums app + * deployed on production Ente instances for link previews. + * + * In particular, + * + * - Any client side modifications to the document's head will be too late for + * use by the link previews, so the contents of this need to part of the + * static HTML. + * + * - "og:image" needs to be an absolute URL. + * + * To avoid getting in the way of self hosters, we do a deployment URL check + * before inlining this into the build. + */ +export const CustomHeadAlbums: React.FC = () => ( + + Ente Photos + + + + + + +); + +/** + * A convenience fan out to conditionally show one of {@link CustomHead} or + * {@link CustomHeadAlbums}. + * + * 1. This component defaults to {@link CustomHeadAlbums} during SSR unless a + * custom endpoint is defined. + * + * 2. Currently the photos and albums app use the same code. During SSR this + * uses the albums variant, and then does a client side update to the photos + * head when it detects that the origin it is being served on is not the + * albums origin. + * + * The current content of the head is such that it sort of works for both photos + * and public albums, so the client side update is just an enhancement. We + * should not need this component when the photos and public albums app split. + */ +export const CustomHeadPhotosOrAlbums: React.FC = ({ + title, +}) => + isCustomAlbumsAppOrigin || + (haveWindow() && + new URL(window.location.href).origin != albumsAppOrigin()) ? ( + + ) : ( + + ); diff --git a/web/packages/base/origins.ts b/web/packages/base/origins.ts index 597eb5b908..b12606625c 100644 --- a/web/packages/base/origins.ts +++ b/web/packages/base/origins.ts @@ -81,6 +81,13 @@ export const customAPIHost = async () => { export const uploaderOrigin = async () => (await customAPIOrigin()) ?? "https://uploader.ente.io"; +/** + * A static build time constant that is `true` if the {@link albumsAppOrigin} is + * different from the default value (Ente's production instance). + */ +export const isCustomAlbumsAppOrigin = + process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT != "https://albums.ente.io"; + /** * Return the origin that serves public albums. * From 043f8913c20f367e6f31c9e64d905bf68bd62657 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 08:13:42 +0530 Subject: [PATCH 063/109] Unused --- web/packages/base/components/Head.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/packages/base/components/Head.tsx b/web/packages/base/components/Head.tsx index 988ec68929..605afcc08f 100644 --- a/web/packages/base/components/Head.tsx +++ b/web/packages/base/components/Head.tsx @@ -5,7 +5,6 @@ import { albumsAppOrigin, isCustomAlbumsAppOrigin } from "../origins"; interface CustomHeadProps { title: string; - description?: string; } /** From 6f181a7bbdb8aa85eee3ad8cac2f7287e2c0c33e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 09:18:29 +0530 Subject: [PATCH 064/109] Take 2 --- web/packages/base/origins.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/packages/base/origins.ts b/web/packages/base/origins.ts index b12606625c..8e1ef1ddac 100644 --- a/web/packages/base/origins.ts +++ b/web/packages/base/origins.ts @@ -82,11 +82,11 @@ export const uploaderOrigin = async () => (await customAPIOrigin()) ?? "https://uploader.ente.io"; /** - * A static build time constant that is `true` if the {@link albumsAppOrigin} is - * different from the default value (Ente's production instance). + * A static build time constant that is `true` if {@link albumsAppOrigin} has + * been customized. */ export const isCustomAlbumsAppOrigin = - process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT != "https://albums.ente.io"; + !!process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT; /** * Return the origin that serves public albums. From cbe7d2532bf44411e312d7d18eb3a8b3fffb8efb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 09:36:05 +0530 Subject: [PATCH 065/109] Fix typo --- web/packages/base/components/Head.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/base/components/Head.tsx b/web/packages/base/components/Head.tsx index 605afcc08f..4e7b7ae502 100644 --- a/web/packages/base/components/Head.tsx +++ b/web/packages/base/components/Head.tsx @@ -51,7 +51,7 @@ export const CustomHeadAlbums: React.FC = () => ( /> From ec5b5ca80d3d0d8b239c928129ef886bae8bc1fa Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 10:23:51 +0530 Subject: [PATCH 066/109] Move to gallery --- .../DownloadStatusNotifications.tsx | 44 ++++++--------- web/apps/photos/src/pages/gallery.tsx | 54 ++++++++++++++++--- web/apps/photos/src/pages/shared-albums.tsx | 3 +- .../gallery/components/utils/save-groups.ts | 6 +++ web/packages/gallery/services/save.ts | 6 ++- .../new/photos/services/collection-summary.ts | 2 +- 6 files changed, 74 insertions(+), 41 deletions(-) diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index 9872a858a3..95be991e99 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -23,24 +23,18 @@ interface DownloadStatusNotificationsProps { */ onRemoveSaveGroup: (saveGroup: SaveGroup) => void; /** - * Called when the hidden section should be shown. - * - * This triggers the display of the dialog to authenticate the user, and the - * returned promise when (and only if) the user successfully reauthenticates. - * - * Since the hidden section is only relevant in the context of the photos - * app where there is a logged in user, this callback can be omitted in the - * context of the public albums app. - */ - onShowHiddenSection?: () => Promise; - /** - * Called when the collection with the given {@link collectionID} should be - * shown. + * Called when the collection summary with the given {@link collectionID} + * and "hidden" {@link attribute} should be shown. * * This is only relevant in the context of the photos app, and can be - * omitted by the public albums app. + * omitted by the public albums app. See the documentation of + * {@link SaveGroup}'s {@link collectionSummaryID} property for why we don't + * store the collection summary itself. */ - onShowCollection?: (collectionID: number) => void; + onShowCollectionSummary?: ( + collectionSummaryID: number | undefined, + isHiddenCollectionSummary: boolean | undefined, + ) => void; } /** @@ -49,12 +43,7 @@ interface DownloadStatusNotificationsProps { */ export const DownloadStatusNotifications: React.FC< DownloadStatusNotificationsProps -> = ({ - saveGroups, - onRemoveSaveGroup, - onShowHiddenSection, - onShowCollection, -}) => { +> = ({ saveGroups, onRemoveSaveGroup, onShowCollectionSummary }) => { const { showMiniDialog } = useBaseContext(); const confirmCancelDownload = (group: SaveGroup) => @@ -84,14 +73,11 @@ export const DownloadStatusNotifications: React.FC< const electron = globalThis.electron; if (electron) { electron.openDirectory(group.downloadDirPath); - } else if (onShowCollection) { - if (group.isHiddenCollectionSummary) { - void onShowHiddenSection().then(() => { - onShowCollection(group.collectionSummaryID); - }); - } else { - onShowCollection(group.collectionSummaryID); - } + } else if (onShowCollectionSummary) { + onShowCollectionSummary( + group.collectionSummaryID, + group.isHiddenCollectionSummary, + ); } else { return undefined; } diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 51bd8a3324..08a75f4a67 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -89,6 +89,7 @@ import { import { haveOnlySystemCollections, PseudoCollectionID, + type CollectionSummary, } from "ente-new/photos/services/collection-summary"; import exportService from "ente-new/photos/services/export"; import { updateFilesVisibility } from "ente-new/photos/services/file"; @@ -789,7 +790,7 @@ const Page: React.FC = () => { setUploadTypeSelectorIntent(intent ?? "upload"); }; - const handleShowCollectionSummary = ( + const handleShowCollectionSummaryWithID = ( collectionSummaryID: number | undefined, ) => { // Trigger a pull of the latest data from remote when opening the trash. @@ -813,9 +814,45 @@ const Page: React.FC = () => { dispatch({ type: "showCollectionSummary", collectionSummaryID }); }; - // The same function can also be used to show collections since the - // namespace for the collection IDs and collection summary IDs are disjoint. - const handleShowCollection = handleShowCollectionSummary; + /** + * Switch to gallery view to show the {@link CollectionSummary}. + * + * @param cs The {@link CollectionSummary} to show. + * If a {@link CollectionSummary} is not provided, show the "All" section. + * + * If the given {@link CollectionSummary} is hidden, first perform any + * reauthentication as would be needed for showing the hidden section in the + * app, and then shows the {@link CollectionSummary}. + */ + // TODO(RE): + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const handleShowCollectionSummary = (cs: CollectionSummary | undefined) => { + if (cs?.attributes.has("hidden")) { + void handleShowHiddenSection().then(() => { + handleShowCollectionSummaryWithID(cs.id); + }); + } else { + handleShowCollectionSummaryWithID(cs.id); + } + }; + + /** + * A variant / reimplementation of {@link handleShowCollectionSummary} for + * use by the {@link DownloadStatusNotifications} component (which does not + * know about the {@link CollectionSummary} TypeScript type). + */ + const handleDownloadStatusNotificationsShowCollectionSummary = ( + collectionSummaryID: number | undefined, + isHiddenCollectionSummary: boolean | undefined, + ) => { + if (isHiddenCollectionSummary) { + void handleShowHiddenSection().then(() => { + handleShowCollectionSummaryWithID(collectionSummaryID); + }); + } else { + handleShowCollectionSummaryWithID(collectionSummaryID); + } + }; const handleChangeBarMode = (mode: GalleryBarMode) => mode == "people" @@ -955,8 +992,9 @@ const Page: React.FC = () => { /> { } onChangeMode={handleChangeBarMode} setBlockingLoad={setBlockingLoad} - setActiveCollectionID={handleShowCollectionSummary} + setActiveCollectionID={handleShowCollectionSummaryWithID} onRemotePull={remotePull} onSelectPerson={handleSelectPerson} /> @@ -1076,7 +1114,7 @@ const Page: React.FC = () => { state.uncategorizedCollectionSummaryID } onShowPlanSelector={showPlanSelector} - onShowCollectionSummary={handleShowCollectionSummary} + onShowCollectionSummary={handleShowCollectionSummaryWithID} onShowHiddenSection={handleShowHiddenSection} onShowExport={showExport} onAuthenticateUser={authenticateUser} diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 7d1e52b8a3..9b2fe11689 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -80,7 +80,6 @@ import { GalleryItemsHeaderAdapter, GalleryItemsSummary, } from "ente-new/photos/components/gallery/ListHeader"; -import { isHiddenCollection } from "ente-new/photos/services/collection"; import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; import { usePhotosAppContext } from "ente-new/photos/types/context"; import { t } from "i18next"; @@ -649,7 +648,7 @@ const ListHeader: React.FC = ({ publicCollection.name, publicCollection.id, publicFiles, - isHiddenCollection(publicCollection), + undefined, onAddSaveGroup, ); diff --git a/web/packages/gallery/components/utils/save-groups.ts b/web/packages/gallery/components/utils/save-groups.ts index f37fd40b69..cb9c15dde9 100644 --- a/web/packages/gallery/components/utils/save-groups.ts +++ b/web/packages/gallery/components/utils/save-groups.ts @@ -30,6 +30,12 @@ export interface SaveGroup { /** * If this save group is associated with a {@link CollectionSummary}, then * the ID of that collection summary. + * + * The {@link SaveGroup} type is also used in the context of the albums app, + * which does not use or need the concept of link {@link CollectionSummary}, + * we to avoid taking a dependency of the type we store these two relevant + * properties - {@link collectionSummaryID} and + * {@link isHiddenCollectionSummary} - inline. */ collectionSummaryID?: number; /** diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 1eb1701087..73faa7e3d2 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -52,12 +52,16 @@ export const downloadAndSaveFiles = ( * When running in the context of the desktop app, instead of saving the files * in the directory selected by the user, files are saved in a directory with * the same name as the collection. + * + * @param isHiddenCollectionSummary `true` if the collection is associated with + * a "hidden" collection or pseudo-collection in the app. Only relevant when + * running in the context of the photos app, can be `undefined` otherwise. */ export const downloadAndSaveCollectionFiles = async ( collectionSummaryName: string, collectionSummaryID: number, files: EnteFile[], - isHiddenCollectionSummary: boolean, + isHiddenCollectionSummary: boolean | undefined, onAddSaveGroup: AddSaveGroup, ) => downloadAndSave( diff --git a/web/packages/new/photos/services/collection-summary.ts b/web/packages/new/photos/services/collection-summary.ts index a735dc8114..2876e305e1 100644 --- a/web/packages/new/photos/services/collection-summary.ts +++ b/web/packages/new/photos/services/collection-summary.ts @@ -19,8 +19,8 @@ export type CollectionSummaryAttribute = | "sharedIncomingCollaborator" | "sharedOnlyViaLink" | "system" - | "hideFromCollectionBar" | "archived" + | "hideFromCollectionBar" | "pinned"; /** From a5f3085e0181cd96fd2142558ab35d0f8d676e25 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 10:57:59 +0530 Subject: [PATCH 067/109] + sidebar --- .../DownloadStatusNotifications.tsx | 6 +- web/apps/photos/src/components/Sidebar.tsx | 67 +++++----- web/apps/photos/src/pages/gallery.tsx | 120 ++++++++---------- 3 files changed, 96 insertions(+), 97 deletions(-) diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index 95be991e99..27a246f120 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -24,7 +24,11 @@ interface DownloadStatusNotificationsProps { onRemoveSaveGroup: (saveGroup: SaveGroup) => void; /** * Called when the collection summary with the given {@link collectionID} - * and "hidden" {@link attribute} should be shown. + * should be shown. If {@link isHiddenCollectionSummary} is set, then any + * reauthentication as appropriate before switching to the hidden section of + * the app is performed first. + * + * and hidden attribute should be shown. * * This is only relevant in the context of the photos app, and can be * omitted by the public albums app. See the documentation of diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 5830d79014..ce9ae1c2ba 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -140,18 +140,23 @@ type SidebarProps = ModalVisibilityProps & { */ onShowPlanSelector: () => void; /** - * Called when the collection summary with the given - * {@link collectionSummaryID} should be shown. - */ - onShowCollectionSummary: (collectionSummaryID: number) => void; - /** - * Called when the hidden section should be shown. + * Called when the collection summary with the given {@link collectionID} + * should be shown. * - * This triggers the display of the dialog to authenticate the user, exactly - * as if {@link onAuthenticateUser} were called. Then, on successful - * authentication, the gallery will switch to the hidden section. + * @param collectionSummaryID The ID of the {@link CollectionSummary} to + * switch to. + * + * @param isHiddenCollectionSummary If `true`, then any reauthentication as + * appropriate before switching to the hidden section of the app is + * performed first before showing the collection summary. + * + * @return A promise that fullfills after any needed reauthentication has + * been peformed (The view transition might still be in progress). */ - onShowHiddenSection: () => Promise; + onShowCollectionSummary: ( + collectionSummaryID: number, + isHiddenCollectionSummary?: boolean, + ) => Promise; /** * Called when the export dialog should be shown. */ @@ -175,7 +180,6 @@ export const Sidebar: React.FC = ({ uncategorizedCollectionSummaryID, onShowPlanSelector, onShowCollectionSummary, - onShowHiddenSection, onShowExport, onAuthenticateUser, }) => ( @@ -189,7 +193,6 @@ export const Sidebar: React.FC = ({ normalCollectionSummaries, uncategorizedCollectionSummaryID, onShowCollectionSummary, - onShowHiddenSection, }} /> ; const ShortcutSection: React.FC = ({ @@ -469,25 +471,26 @@ const ShortcutSection: React.FC = ({ normalCollectionSummaries, uncategorizedCollectionSummaryID, onShowCollectionSummary, - onShowHiddenSection, }) => { - const openUncategorizedSection = () => { - onShowCollectionSummary(uncategorizedCollectionSummaryID); - onCloseSidebar(); - }; + const handleOpenUncategorizedSection = () => + void onShowCollectionSummary(uncategorizedCollectionSummaryID).then( + onCloseSidebar, + ); - const openTrashSection = () => { - onShowCollectionSummary(PseudoCollectionID.trash); - onCloseSidebar(); - }; + const handleOpenTrashSection = () => + void onShowCollectionSummary(PseudoCollectionID.trash).then( + onCloseSidebar, + ); - const openArchiveSection = () => { - onShowCollectionSummary(PseudoCollectionID.archiveItems); - onCloseSidebar(); - }; + const handleOpenArchiveSection = () => + void onShowCollectionSummary(PseudoCollectionID.archiveItems).then( + onCloseSidebar, + ); - const openHiddenSection = () => - void onShowHiddenSection().then(onCloseSidebar); + const handleOpenHiddenSection = () => + void onShowCollectionSummary(PseudoCollectionID.hiddenItems, true).then( + onCloseSidebar, + ); const summaryCaption = (summaryID: number) => normalCollectionSummaries.get(summaryID)?.fileCount.toString(); @@ -498,13 +501,13 @@ const ShortcutSection: React.FC = ({ startIcon={} label={t("section_uncategorized")} caption={summaryCaption(uncategorizedCollectionSummaryID)} - onClick={openUncategorizedSection} + onClick={handleOpenUncategorizedSection} /> } label={t("section_archive")} caption={summaryCaption(PseudoCollectionID.archiveItems)} - onClick={openArchiveSection} + onClick={handleOpenArchiveSection} /> } @@ -517,13 +520,13 @@ const ShortcutSection: React.FC = ({ }} /> } - onClick={openHiddenSection} + onClick={handleOpenHiddenSection} /> } label={t("section_trash")} caption={summaryCaption(PseudoCollectionID.trash)} - onClick={openTrashSection} + onClick={handleOpenTrashSection} /> ); diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 08a75f4a67..fc32b99276 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -89,7 +89,6 @@ import { import { haveOnlySystemCollections, PseudoCollectionID, - type CollectionSummary, } from "ente-new/photos/services/collection-summary"; import exportService from "ente-new/photos/services/export"; import { updateFilesVisibility } from "ente-new/photos/services/file"; @@ -790,80 +789,74 @@ const Page: React.FC = () => { setUploadTypeSelectorIntent(intent ?? "upload"); }; - const handleShowCollectionSummaryWithID = ( - collectionSummaryID: number | undefined, - ) => { - // Trigger a pull of the latest data from remote when opening the trash. - // - // This is needed for a specific scenario: - // - // 1. User deletes a collection, selecting the option to delete files. - // 2. Museum acks, and then client does a trash pull. - // - // This trash pull will not contain the files that belonged to the - // collection that got deleted because the collection deletion is a - // asynchronous operation. - // - // So the user might not see the entry for the just deleted file if they - // were to go to the trash meanwhile (until the next pull happens). To - // avoid this, we trigger a trash pull whenever it is opened. - if (collectionSummaryID == PseudoCollectionID.trash) { - void remoteFilesPull(); - } + const handleShowCollectionSummaryWithID = useCallback( + (collectionSummaryID: number | undefined) => { + // Trigger a pull of the latest data from remote when opening the trash. + // + // This is needed for a specific scenario: + // + // 1. User deletes a collection, selecting the option to delete files. + // 2. Museum acks, and then client does a trash pull. + // + // This trash pull will not contain the files that belonged to the + // collection that got deleted because the collection deletion is a + // asynchronous operation. + // + // So the user might not see the entry for the just deleted file if they + // were to go to the trash meanwhile (until the next pull happens). To + // avoid this, we trigger a trash pull whenever it is opened. + if (collectionSummaryID == PseudoCollectionID.trash) { + void remoteFilesPull(); + } - dispatch({ type: "showCollectionSummary", collectionSummaryID }); - }; + dispatch({ type: "showCollectionSummary", collectionSummaryID }); + }, + [], + ); /** - * Switch to gallery view to show the {@link CollectionSummary}. + * Switch to gallery view to show a collection or pseudo-collection. * - * @param cs The {@link CollectionSummary} to show. - * If a {@link CollectionSummary} is not provided, show the "All" section. + * @param collectionSummaryID The ID of the {@link CollectionSummary} to + * show. If not provided, show the "All" section. * - * If the given {@link CollectionSummary} is hidden, first perform any - * reauthentication as would be needed for showing the hidden section in the - * app, and then shows the {@link CollectionSummary}. + * @param isHidden If `true`, then any reauthentication as appropriate + * before switching to the hidden section of the app is performed first + * before before switching to the relevant collection or pseudo-collection. */ - // TODO(RE): - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const handleShowCollectionSummary = (cs: CollectionSummary | undefined) => { - if (cs?.attributes.has("hidden")) { - void handleShowHiddenSection().then(() => { - handleShowCollectionSummaryWithID(cs.id); - }); - } else { - handleShowCollectionSummaryWithID(cs.id); - } - }; - - /** - * A variant / reimplementation of {@link handleShowCollectionSummary} for - * use by the {@link DownloadStatusNotifications} component (which does not - * know about the {@link CollectionSummary} TypeScript type). - */ - const handleDownloadStatusNotificationsShowCollectionSummary = ( - collectionSummaryID: number | undefined, - isHiddenCollectionSummary: boolean | undefined, - ) => { - if (isHiddenCollectionSummary) { - void handleShowHiddenSection().then(() => { - handleShowCollectionSummaryWithID(collectionSummaryID); - }); - } else { + const showCollectionSummary = useCallback( + async ( + collectionSummaryID: number | undefined, + isHiddenCollectionSummary: boolean | undefined, + ) => { + if (isHiddenCollectionSummary) { + await authenticateUser(); + } handleShowCollectionSummaryWithID(collectionSummaryID); - } - }; + }, + [authenticateUser, handleShowCollectionSummaryWithID], + ); + + const handleSidebarShowCollectionSummary = showCollectionSummary; + + const handleDownloadStatusNotificationsShowCollectionSummary = useCallback( + ( + collectionSummaryID: number | undefined, + isHiddenCollectionSummary: boolean | undefined, + ) => { + void showCollectionSummary( + collectionSummaryID, + isHiddenCollectionSummary, + ); + }, + [showCollectionSummary], + ); const handleChangeBarMode = (mode: GalleryBarMode) => mode == "people" ? dispatch({ type: "showPeople" }) : dispatch({ type: "showAlbums" }); - const handleShowHiddenSection = useCallback( - () => authenticateUser().then(() => dispatch({ type: "showHidden" })), - [], - ); - const handleFileViewerToggleFavorite = useCallback( async (file: EnteFile) => { const fileID = file.id; @@ -1114,8 +1107,7 @@ const Page: React.FC = () => { state.uncategorizedCollectionSummaryID } onShowPlanSelector={showPlanSelector} - onShowCollectionSummary={handleShowCollectionSummaryWithID} - onShowHiddenSection={handleShowHiddenSection} + onShowCollectionSummary={handleSidebarShowCollectionSummary} onShowExport={showExport} onAuthenticateUser={authenticateUser} /> From e79426e47ff2f29390b60750900f643c96fb0578 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 11:11:05 +0530 Subject: [PATCH 068/109] Types --- .../gallery/components/utils/save-groups.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/web/packages/gallery/components/utils/save-groups.ts b/web/packages/gallery/components/utils/save-groups.ts index cb9c15dde9..5dcfd8aa7d 100644 --- a/web/packages/gallery/components/utils/save-groups.ts +++ b/web/packages/gallery/components/utils/save-groups.ts @@ -58,7 +58,7 @@ export interface SaveGroup { */ total: number; /** - * The number of files that have already been save. + * The number of files that have been saved so far. */ success: number; /** @@ -68,7 +68,7 @@ export interface SaveGroup { /** * An {@link AbortController} that can be used to cancel the save. */ - canceller?: AbortController; + canceller: AbortController; } export const isSaveStarted = (group: SaveGroup) => group.total > 0; @@ -90,7 +90,7 @@ export const isSaveCompleteWithErrors = (group: SaveGroup) => * Return `true` if this save was cancelled on a user request. */ export const isSaveCancelled = (group: SaveGroup) => - group.canceller?.signal.aborted; + group.canceller.signal.aborted; /** * A function that can be used to add a save group. @@ -99,7 +99,17 @@ export const isSaveCancelled = (group: SaveGroup) => * by applying a transform to it (see {@link UpdateSaveGroup}). The UI will * react and update itself on updates done this way. */ -export type AddSaveGroup = (group: Partial) => UpdateSaveGroup; +export type AddSaveGroup = ( + group: Pick< + SaveGroup, + | "title" + | "collectionSummaryID" + | "isHiddenCollectionSummary" + | "downloadDirPath" + | "total" + | "canceller" + >, +) => UpdateSaveGroup; /** * A function that can be used to update a instance of a save group by applying @@ -133,15 +143,7 @@ export const useSaveGroups = () => { const id = Math.random(); setSaveGroups((groups) => [ ...groups, - { - ...saveGroup, - id, - // TODO(RE): - title: saveGroup.title ?? "", - total: saveGroup.total ?? 0, - success: 0, - failed: 0, - }, + { ...saveGroup, id, success: 0, failed: 0 }, ]); return (tx: (group: SaveGroup) => SaveGroup) => { setSaveGroups((groups) => From 148a3c13dbb0d4a3e052b0afd1c2380f7a6d08fb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 11:27:14 +0530 Subject: [PATCH 069/109] Better handling of empty albums --- .../Collections/CollectionHeader.tsx | 62 ++++++++++------- .../DownloadStatusNotifications.tsx | 66 ++++++++----------- web/packages/gallery/services/save.ts | 9 ++- 3 files changed, 73 insertions(+), 64 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index 800df6edc2..91790a6a97 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -139,7 +139,7 @@ const CollectionHeaderOptions: React.FC = ({ const { show: showAlbumNameInput, props: albumNameInputVisibilityProps } = useModalVisibility(); - const { type: collectionSummaryType } = collectionSummary; + const { type: collectionSummaryType, fileCount } = collectionSummary; /** * Return a new function by wrapping an async function in an error handler, @@ -334,13 +334,17 @@ const CollectionHeaderOptions: React.FC = ({ case "userFavorites": menuOptions = [ - - {t("download_favorites")} - , + fileCount && ( + + {t("download_favorites")} + + ), = ({ case "uncategorized": menuOptions = [ - - {t("download_uncategorized")} - , + fileCount && ( + + {t("download_uncategorized")} + + ), ]; break; case "hiddenItems": menuOptions = [ - - {t("download_hidden_items")} - , + fileCount && ( + + {t("download_hidden_items")} + + ), ]; break; @@ -509,6 +517,8 @@ const CollectionHeaderOptions: React.FC = ({ break; } + const validMenuOptions = menuOptions.filter((o) => !!o); + return ( = ({ onDownloadClick={downloadCollection} onShareClick={onCollectionShare} /> - - } - > - {...menuOptions} - + {validMenuOptions.length > 0 && ( + + } + > + {validMenuOptions} + + )} = ({ )} {showDownloadQuickOption(collectionSummary) && + collectionSummary.fileCount > 0 && (isDownloadInProgress() ? ( ) : ( diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index 27a246f120..cc69d2027e 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -2,7 +2,6 @@ import { useBaseContext } from "ente-base/context"; import { isSaveComplete, isSaveCompleteWithErrors, - isSaveStarted, type SaveGroup, } from "ente-gallery/components/utils/save-groups"; import { Notification } from "ente-new/photos/components/Notification"; @@ -91,42 +90,31 @@ export const DownloadStatusNotifications: React.FC< return <>; } - const notifications: React.ReactNode[] = []; - - let visibleIndex = 0; - for (const group of saveGroups) { - // Skip attempted downloads of empty albums, which had no effect. - if (!isSaveStarted(group)) continue; - - const index = visibleIndex++; - notifications.push( - , - ); - } - - return notifications; + return saveGroups.map((group, index) => ( + + )); }; diff --git a/web/packages/gallery/services/save.ts b/web/packages/gallery/services/save.ts index 73faa7e3d2..72cf8cd021 100644 --- a/web/packages/gallery/services/save.ts +++ b/web/packages/gallery/services/save.ts @@ -1,3 +1,4 @@ +import { assertionFailed } from "ente-base/assert"; import { joinPath } from "ente-base/file-name"; import log from "ente-base/log"; import { type Electron } from "ente-base/types/ipc"; @@ -86,6 +87,13 @@ const downloadAndSave = async ( ) => { const electron = globalThis.electron; + const total = files.length; + if (!files.length) { + // Nothing to download. + assertionFailed(); + return; + } + let downloadDirPath: string | undefined; if (electron) { downloadDirPath = await electron.selectDirectory(); @@ -103,7 +111,6 @@ const downloadAndSave = async ( } const canceller = new AbortController(); - const total = files.length; const updateSaveGroup = onAddSaveGroup({ title, From 63a9ef9455d0bedc8d7c0e582c02a54560f7b0d7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 11:36:01 +0530 Subject: [PATCH 070/109] Skip redundant --- web/apps/photos/src/pages/gallery.tsx | 4 ++-- web/packages/gallery/components/utils/save-groups.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index fc32b99276..132a7754a3 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -829,12 +829,12 @@ const Page: React.FC = () => { collectionSummaryID: number | undefined, isHiddenCollectionSummary: boolean | undefined, ) => { - if (isHiddenCollectionSummary) { + if (isHiddenCollectionSummary && barMode != "hidden-albums") { await authenticateUser(); } handleShowCollectionSummaryWithID(collectionSummaryID); }, - [authenticateUser, handleShowCollectionSummaryWithID], + [authenticateUser, handleShowCollectionSummaryWithID, barMode], ); const handleSidebarShowCollectionSummary = showCollectionSummary; diff --git a/web/packages/gallery/components/utils/save-groups.ts b/web/packages/gallery/components/utils/save-groups.ts index 5dcfd8aa7d..ace1c98b8b 100644 --- a/web/packages/gallery/components/utils/save-groups.ts +++ b/web/packages/gallery/components/utils/save-groups.ts @@ -71,8 +71,6 @@ export interface SaveGroup { canceller: AbortController; } -export const isSaveStarted = (group: SaveGroup) => group.total > 0; - /** * Return `true` if there are no files in this save group that are pending. */ From e07fdfb5e1bd2d7dbacaf2dcd20522e64b09789e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 12:39:50 +0530 Subject: [PATCH 071/109] sudo-like --- web/apps/photos/src/pages/gallery.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 132a7754a3..a0db54a16a 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -194,6 +194,15 @@ const Page: React.FC = () => { const [collectionSelectorAttributes, setCollectionSelectorAttributes] = useState(); + /** + * The last time (epoch milliseconds) when we prompted the user for their + * password when opening the hidden section. + * + * This is used to implement a grace window, where we don't reprompt them + * for their password for the same purpose again and again. + */ + const lastAuthenticationForHiddenTimestamp = useRef(0); + const { show: showSidebar, props: sidebarVisibilityProps } = useModalVisibility(); const { show: showPlanSelector, props: planSelectorVisibilityProps } = @@ -829,8 +838,14 @@ const Page: React.FC = () => { collectionSummaryID: number | undefined, isHiddenCollectionSummary: boolean | undefined, ) => { - if (isHiddenCollectionSummary && barMode != "hidden-albums") { + const lastAuthAt = lastAuthenticationForHiddenTimestamp.current; + if ( + isHiddenCollectionSummary && + barMode != "hidden-albums" && + Date.now() - lastAuthAt > 5 * 60 * 1e3 /* 5 minutes */ + ) { await authenticateUser(); + lastAuthenticationForHiddenTimestamp.current = Date.now(); } handleShowCollectionSummaryWithID(collectionSummaryID); }, From b9d9c6f65a0edc77b629bd3ddcb0becffa983886 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 13:16:01 +0530 Subject: [PATCH 072/109] Another --- web/apps/photos/src/components/Sidebar.tsx | 8 ++++--- web/packages/base/components/utils/theme.ts | 21 +++++++++++++++---- web/packages/gallery/components/FileInfo.tsx | 4 ++-- .../gallery/components/viewer/photoswipe.ts | 4 ++-- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index ce9ae1c2ba..082f48e9dd 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -109,6 +109,7 @@ import { } from "ente-new/photos/services/user-details"; import { usePhotosAppContext } from "ente-new/photos/types/context"; import { initiateEmail, openURL } from "ente-new/photos/utils/web"; +import { wait } from "ente-utils/promise"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { @@ -488,9 +489,10 @@ const ShortcutSection: React.FC = ({ ); const handleOpenHiddenSection = () => - void onShowCollectionSummary(PseudoCollectionID.hiddenItems, true).then( - onCloseSidebar, - ); + void onShowCollectionSummary(PseudoCollectionID.hiddenItems, true) + // See: [Note: Workarounds for unactionable ARIA warnings] + .then(() => wait(10)) + .then(onCloseSidebar); const summaryCaption = (summaryID: number) => normalCollectionSummaries.get(summaryID)?.fileCount.toString(); diff --git a/web/packages/base/components/utils/theme.ts b/web/packages/base/components/utils/theme.ts index 6962c90617..2c1ea3aa14 100644 --- a/web/packages/base/components/utils/theme.ts +++ b/web/packages/base/components/utils/theme.ts @@ -499,13 +499,26 @@ const components: Components = { MuiDialog: { defaultProps: { - // [Note: Overzealous Chrome? Complicated ARIA?] + // [Note: Workarounds for unactionable ARIA warnings] // - // This is required to prevent console errors about aria-hiding a - // focused button when the dialog is closed. + // This is required to prevent console warnings about aria-hiding a + // focused button when the dialog is closed. e.g. Select a file, + // delete it. On closing the confirmation dialog, the error appears. + // + // The default is supposed to already be false, but setting this + // again seems to help. But sometimes we need to set this to `true` + // to prevent the warning. And sometimes neither helps, and we need + // to add random setTimeouts. + // + // Angular, Bootstrap, MUI, shadcn: all seem to be emitting these + // warning (just search the web). I'm don't know if this is just + // someone at Chrome deciding to emit spurious warnings without + // understanding the flow, or if none of these libraries have + // managed to implement the ARIA spec properly yet (which says more + // about the spec than about the libraries). // - // - https://github.com/mui/material-ui/issues/43106#issuecomment-2314809028 // - https://issues.chromium.org/issues/392121909 + // - https://github.com/mui/material-ui/issues/43106#issuecomment-2314809028 closeAfterTransition: false, }, styleOverrides: { diff --git a/web/packages/gallery/components/FileInfo.tsx b/web/packages/gallery/components/FileInfo.tsx index 92f06a085e..177c2876e6 100644 --- a/web/packages/gallery/components/FileInfo.tsx +++ b/web/packages/gallery/components/FileInfo.tsx @@ -451,8 +451,8 @@ const FileInfoSidebar = styled( Date: Mon, 7 Jul 2025 13:20:33 +0530 Subject: [PATCH 073/109] Inline --- .../Collections/GalleryBarAndListHeader.tsx | 43 +++++++++++++++++- .../photos/src/services/collectionService.ts | 44 ------------------- 2 files changed, 42 insertions(+), 45 deletions(-) delete mode 100644 web/apps/photos/src/services/collectionService.ts diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 5ce5eddfd8..514abab78b 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -10,12 +10,15 @@ import { isSaveComplete, type SaveGroup, } from "ente-gallery/components/utils/save-groups"; +import { sortFiles } from "ente-gallery/utils/file"; import type { Collection } from "ente-media/collection"; +import type { EnteFile } from "ente-media/file"; import { GalleryBarImpl, type GalleryBarImplProps, } from "ente-new/photos/components/gallery/BarImpl"; import { PeopleHeader } from "ente-new/photos/components/gallery/PeopleHeader"; +import type { CollectionSummary } from "ente-new/photos/services/collection-summary"; import { collectionsSortBy, haveOnlySystemCollections, @@ -25,12 +28,12 @@ import { } from "ente-new/photos/services/collection-summary"; import { includes } from "ente-utils/type-guards"; import React, { useCallback, useEffect, useMemo, useState } from "react"; -import { sortCollectionSummaries } from "services/collectionService"; import { AlbumCastDialog } from "./AlbumCastDialog"; import { CollectionHeader, type CollectionHeaderProps, } from "./CollectionHeader"; + type GalleryBarAndListHeaderProps = Omit< GalleryBarImplProps, | "collectionSummaries" @@ -250,3 +253,41 @@ const useCollectionsSortByLocalState = (initialValue: CollectionsSortBy) => { return [value, setter] as const; }; + +const sortCollectionSummaries = ( + collectionSummaries: CollectionSummary[], + by: CollectionsSortBy, +) => + collectionSummaries + .sort((a, b) => { + switch (by) { + case "name": + return a.name.localeCompare(b.name); + case "creation-time-asc": + return ( + -1 * + compareCollectionsLatestFile(b.latestFile, a.latestFile) + ); + case "updation-time-desc": + return (b.updationTime ?? 0) - (a.updationTime ?? 0); + } + }) + .sort((a, b) => b.sortPriority - a.sortPriority); + +const compareCollectionsLatestFile = ( + first: EnteFile | undefined, + second: EnteFile | undefined, +) => { + if (!first) { + return 1; + } else if (!second) { + return -1; + } else { + const sortedFiles = sortFiles([first, second]); + if (sortedFiles[0].id !== first.id) { + return 1; + } else { + return -1; + } + } +}; diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts deleted file mode 100644 index 81bd297b57..0000000000 --- a/web/apps/photos/src/services/collectionService.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { sortFiles } from "ente-gallery/utils/file"; -import type { EnteFile } from "ente-media/file"; -import type { - CollectionsSortBy, - CollectionSummary, -} from "ente-new/photos/services/collection-summary"; - -export const sortCollectionSummaries = ( - collectionSummaries: CollectionSummary[], - by: CollectionsSortBy, -) => - collectionSummaries - .sort((a, b) => { - switch (by) { - case "name": - return a.name.localeCompare(b.name); - case "creation-time-asc": - return ( - -1 * - compareCollectionsLatestFile(b.latestFile, a.latestFile) - ); - case "updation-time-desc": - return (b.updationTime ?? 0) - (a.updationTime ?? 0); - } - }) - .sort((a, b) => b.sortPriority - a.sortPriority); - -function compareCollectionsLatestFile( - first: EnteFile | undefined, - second: EnteFile | undefined, -) { - if (!first) { - return 1; - } else if (!second) { - return -1; - } else { - const sortedFiles = sortFiles([first, second]); - if (sortedFiles[0].id !== first.id) { - return 1; - } else { - return -1; - } - } -} From db30b8cfe858e1942c1cf8afcdbdde5e4f3e9ff5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 13:27:30 +0530 Subject: [PATCH 074/109] Move --- web/apps/photos/src/components/{pages/gallery => }/Avatar.tsx | 0 web/apps/photos/src/components/Collections/CollectionShare.tsx | 2 +- web/apps/photos/src/components/FileList.tsx | 2 +- web/apps/photos/src/components/Upload/UploadTypeSelector.tsx | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename web/apps/photos/src/components/{pages/gallery => }/Avatar.tsx (100%) delete mode 100644 web/apps/photos/src/components/Upload/UploadTypeSelector.tsx diff --git a/web/apps/photos/src/components/pages/gallery/Avatar.tsx b/web/apps/photos/src/components/Avatar.tsx similarity index 100% rename from web/apps/photos/src/components/pages/gallery/Avatar.tsx rename to web/apps/photos/src/components/Avatar.tsx diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 9986932cb9..245be674b7 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -15,7 +15,7 @@ import WorkspacesIcon from "@mui/icons-material/Workspaces"; import { Dialog, Stack, styled, Typography } from "@mui/material"; import NumberAvatar from "@mui/material/Avatar"; import TextField from "@mui/material/TextField"; -import Avatar from "components/pages/gallery/Avatar"; +import Avatar from "components/Avatar"; import { type LocalUser } from "ente-accounts/services/user"; import { LoadingButton } from "ente-base/components/mui/LoadingButton"; import { diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 46c3375d01..d9a0670301 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -4,7 +4,7 @@ import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined"; import FavoriteRoundedIcon from "@mui/icons-material/FavoriteRounded"; import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined"; import { Box, Checkbox, Link, Typography, styled } from "@mui/material"; -import Avatar from "components/pages/gallery/Avatar"; +import Avatar from "components/Avatar"; import type { LocalUser } from "ente-accounts/services/user"; import { assertionFailed } from "ente-base/assert"; import { Overlay } from "ente-base/components/containers"; diff --git a/web/apps/photos/src/components/Upload/UploadTypeSelector.tsx b/web/apps/photos/src/components/Upload/UploadTypeSelector.tsx deleted file mode 100644 index e69de29bb2..0000000000 From 8335c9ac7efb74cf8e3609d06d17c4a00c584bc9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 13:31:21 +0530 Subject: [PATCH 075/109] Inline --- web/apps/photos/src/components/FileList.tsx | 3 +-- web/apps/photos/src/components/Upload.tsx | 3 +-- web/apps/photos/src/pages/gallery.tsx | 7 ++++-- web/apps/photos/src/pages/shared-albums.tsx | 3 +-- web/apps/photos/src/types/gallery/index.ts | 23 ------------------- web/apps/photos/src/utils/file/index.ts | 18 ++++++++++++++- web/apps/photos/src/utils/photoFrame/index.ts | 2 +- 7 files changed, 26 insertions(+), 33 deletions(-) delete mode 100644 web/apps/photos/src/types/gallery/index.ts diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index d9a0670301..4d2120d8cd 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -37,8 +37,7 @@ import { type ListChildComponentProps, areEqual, } from "react-window"; -import type { SelectedState } from "types/gallery"; -import { shouldShowAvatar } from "utils/file"; +import { type SelectedState, shouldShowAvatar } from "utils/file"; import { handleSelectCreator, handleSelectCreatorMulti, diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 1264eae2c4..6888c92b90 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -83,7 +83,6 @@ import type { } from "services/upload-manager"; import { uploadManager } from "services/upload-manager"; import watcher from "services/watch"; -import type { SetLoading } from "types/gallery"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; import { UploadProgress } from "./UploadProgress"; @@ -103,7 +102,7 @@ interface UploadProps { uploadTypeSelectorIntent: UploadTypeSelectorIntent; activeCollection?: Collection; closeUploadTypeSelector: () => void; - setLoading: SetLoading; + setLoading: (loading: boolean) => void; setShouldDisableDropzone: (value: boolean) => void; showCollectionSelector?: () => void; /** diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index a0db54a16a..1d4512b489 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -121,8 +121,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { FileWithPath } from "react-dropzone"; import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; -import type { SelectedState } from "types/gallery"; -import { getSelectedFiles, performFileOp } from "utils/file"; +import { + getSelectedFiles, + performFileOp, + type SelectedState, +} from "utils/file"; /** * The default view for logged in users. diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 9b2fe11689..1681367a9a 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -87,8 +87,7 @@ import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; import { uploadManager } from "services/upload-manager"; -import type { SelectedState } from "types/gallery"; -import { getSelectedFiles } from "utils/file"; +import { getSelectedFiles, type SelectedState } from "utils/file"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; export default function PublicCollectionGallery() { diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts deleted file mode 100644 index 47f1f1c1b7..0000000000 --- a/web/apps/photos/src/types/gallery/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { type SelectionContext } from "ente-new/photos/components/gallery"; - -export interface SelectedState { - [k: number]: boolean; - ownCount: number; - count: number; - collectionID: number; - /** - * The context in which the selection was made. Only set by newer code if - * there is an active selection (older code continues to rely on the - * {@link collectionID} logic). - */ - context: SelectionContext | undefined; -} -export type SetSelectedState = React.Dispatch< - React.SetStateAction ->; -export type SetLoading = React.Dispatch>; - -export interface MergedSourceURL { - original: string; - converted: string; -} diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index b2d3199bb7..aefa842787 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -3,6 +3,7 @@ import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; import { downloadAndSaveFiles } from "ente-gallery/services/save"; import type { EnteFile } from "ente-media/file"; import { ItemVisibility } from "ente-media/file-metadata"; +import { type SelectionContext } from "ente-new/photos/components/gallery"; import { type FileOp } from "ente-new/photos/components/SelectedFileOptions"; import { addToFavoritesCollection, @@ -12,7 +13,22 @@ import { } from "ente-new/photos/services/collection"; import { updateFilesVisibility } from "ente-new/photos/services/file"; import { t } from "i18next"; -import type { SelectedState } from "types/gallery"; + +export interface SelectedState { + [k: number]: boolean; + ownCount: number; + count: number; + collectionID: number; + /** + * The context in which the selection was made. Only set by newer code if + * there is an active selection (older code continues to rely on the + * {@link collectionID} logic). + */ + context: SelectionContext | undefined; +} +export type SetSelectedState = React.Dispatch< + React.SetStateAction +>; export function getSelectedFiles( selected: SelectedState, diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index 33e902cc6f..d5157febef 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -1,6 +1,6 @@ import type { SelectionContext } from "ente-new/photos/components/gallery"; import type { GalleryBarMode } from "ente-new/photos/components/gallery/reducer"; -import type { SelectedState, SetSelectedState } from "types/gallery"; +import type { SelectedState, SetSelectedState } from "utils/file"; // TODO: All this is unnecessarily complex, and needs reworking. export const handleSelectCreator = From 72c45bd7066a70344533d243575d2751b88ced9e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 14:09:53 +0530 Subject: [PATCH 076/109] Special case for Twitter --- web/packages/base/components/Head.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/packages/base/components/Head.tsx b/web/packages/base/components/Head.tsx index 4e7b7ae502..9096d4a19d 100644 --- a/web/packages/base/components/Head.tsx +++ b/web/packages/base/components/Head.tsx @@ -50,7 +50,12 @@ export const CustomHeadAlbums: React.FC = () => ( content="Safely store and share your best moments" /> + {/* Twitter wants its own thing. */} + From 486409120257ac3a1edc2c343701e4bef08c234a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 14:19:54 +0530 Subject: [PATCH 077/109] Serial --- web/packages/new/photos/services/file.ts | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/web/packages/new/photos/services/file.ts b/web/packages/new/photos/services/file.ts index b25e8c782c..2c62346027 100644 --- a/web/packages/new/photos/services/file.ts +++ b/web/packages/new/photos/services/file.ts @@ -27,13 +27,13 @@ import { savedCollectionFiles } from "./photos-fdb"; const requestBatchSize = 1000; /** - * Perform an operation on batches, concurrently. + * Perform an operation on batches, serially. * * The given {@link items} are split into batches, each of * {@link requestBatchSize}. The provided operation is called on all these - * batches, in parallel, by using `Promise.all`. When all the operations are - * complete, the function returns with an array of results (one from each batch - * promise resolution). + * batches, one after the other. When all the operations are complete, the + * function returns with an array of results (one from each batch promise + * resolution). * * @param items The arbitrary items to break into {@link requestBatchSize} * batches. @@ -41,15 +41,16 @@ const requestBatchSize = 1000; * @param op The operation to perform on each batch. * * @returns A promise for an array of results, one from each batch operation. If - * any operations fails, then the promise rejects with the first failure reason. - * - * For more details see the documentation for the `Promise.all` primitive which - * this function uses. + * any operations fails, then the promise rejects with its failure reason. */ -export const batched = ( +export const batched = async ( items: T[], op: (batchItems: T[]) => Promise, -): Promise => Promise.all(batch(items, requestBatchSize).map(op)); +): Promise => { + const result: U[] = []; + for (const b of batch(items, requestBatchSize)) result.push(await op(b)); + return result; +}; /** * Return all normal (non-hidden) files present in our local database. From ba94427b36a4dd8ea6c7c51a0430884254639f3f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 15:02:52 +0530 Subject: [PATCH 078/109] Reword comment --- web/apps/photos/src/pages/gallery.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 1d4512b489..7b0de0c920 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -721,16 +721,16 @@ const Page: React.FC = () => { void (async () => { showLoadingBar(); try { - const selectedFiles = getSelectedFiles( - selected, + // When hiding use all non-hidden files instead of the filtered + // files since we want to move all files copies to the hidden + // collection. + const opFiles = op == "hide" - ? // passing files here instead of filteredData for hide since - // we want to move all files copies to hidden collection - state.collectionFiles.filter( + ? state.collectionFiles.filter( (f) => !state.hiddenFileIDs.has(f.id), ) - : filteredFiles, - ); + : filteredFiles; + const selectedFiles = getSelectedFiles(selected, opFiles); const toProcessFiles = op == "download" ? selectedFiles From f1cc16ddaed529a2cd956482513f58345ceb81cd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 16:08:43 +0530 Subject: [PATCH 079/109] Pass directly --- web/apps/photos/src/components/FileList.tsx | 44 +------ .../src/components/FileListWithViewer.tsx | 3 + web/apps/photos/src/pages/gallery.tsx | 22 ++-- web/apps/photos/src/pages/shared-albums.tsx | 109 +++++++++--------- .../utils/publicCollectionGallery/index.ts | 5 - 5 files changed, 74 insertions(+), 109 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 4d2120d8cd..cf2314ef3f 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -245,12 +245,6 @@ export const FileList: React.FC = ({ if (header) { timeStampList.push(asFullSpanListItem(header)); - } else if (publicCollectionGalleryContext.photoListHeader) { - timeStampList.push( - getPhotoListHeader( - publicCollectionGalleryContext.photoListHeader, - ), - ); } if (isMagicSearchResult) { noGrouping(timeStampList); @@ -267,19 +261,14 @@ export const FileList: React.FC = ({ timeStampList.push(getVacuumItem(timeStampList)); if (footer) { timeStampList.push(asFullSpanListItem(footer)); - } else if (publicCollectionGalleryContext.credentials) { - if (publicCollectionGalleryContext.photoListFooter) { - timeStampList.push( - getPhotoListFooter( - publicCollectionGalleryContext.photoListFooter, - ), - ); - } - timeStampList.push(getAlbumsFooter()); } else if (showAppDownloadBanner) { timeStampList.push(getAppDownloadFooter()); } + if (publicCollectionGalleryContext.credentials) { + timeStampList.push(getAlbumsFooter()); + } + setTimeStampList(timeStampList); refreshInProgress.current = false; if (shouldRefresh.current) { @@ -293,8 +282,9 @@ export const FileList: React.FC = ({ height, annotatedFiles, header, - publicCollectionGalleryContext.photoListHeader, + footer, isMagicSearchResult, + publicCollectionGalleryContext.credentials, ]); useEffect(() => { @@ -354,28 +344,6 @@ export const FileList: React.FC = ({ }); }; - const getPhotoListHeader = (photoListHeader) => { - return { - ...photoListHeader, - item: ( - - {photoListHeader.item} - - ), - }; - }; - - const getPhotoListFooter = (photoListFooter) => { - return { - ...photoListFooter, - item: ( - - {photoListFooter.item} - - ), - }; - }; - const getEmptyListItem = () => { return { item: ( diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 0d72cf0c69..3386588ec6 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -57,6 +57,7 @@ export type FileListWithViewerProps = { | "mode" | "modePlus" | "header" + | "footer" | "showAppDownloadBanner" | "isMagicSearchResult" | "selectable" @@ -93,6 +94,7 @@ export const FileListWithViewer: React.FC = ({ mode, modePlus, header, + footer, user, files, enableDownload, @@ -182,6 +184,7 @@ export const FileListWithViewer: React.FC = ({ mode, modePlus, header, + footer, user, showAppDownloadBanner, isMagicSearchResult, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 7b0de0c920..3cbaaf12ac 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -180,23 +180,23 @@ const Page: React.FC = () => { const [fixCreationTimeFiles, setFixCreationTimeFiles] = useState< EnteFile[] >([]); + // The (non-sticky) header shown at the top of the gallery items. + const [fileListHeader, setFileListHeader] = useState< + TimeStampListItem | undefined + >(undefined); + + const [openCollectionSelector, setOpenCollectionSelector] = useState(false); + const [collectionSelectorAttributes, setCollectionSelectorAttributes] = + useState(); const userDetails = useUserDetailsSnapshot(); const peopleState = usePeopleStateSnapshot(); - // The (non-sticky) header shown at the top of the gallery items. - const [photoListHeader, setPhotoListHeader] = - useState(null); - const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); const [, setPostCreateAlbumOp] = useState( undefined, ); - const [openCollectionSelector, setOpenCollectionSelector] = useState(false); - const [collectionSelectorAttributes, setCollectionSelectorAttributes] = - useState(); - /** * The last time (epoch milliseconds) when we prompted the user for their * password when opening the hidden section. @@ -408,7 +408,7 @@ const Page: React.FC = () => { useEffect(() => { if (isInSearchMode && state.searchSuggestion) { - setPhotoListHeader({ + setFileListHeader({ height: 104, item: ( { activeCollection, activeCollectionID, activePerson, - setPhotoListHeader, + setPhotoListHeader: setFileListHeader, saveGroups, onAddSaveGroup, }} @@ -1147,7 +1147,7 @@ const Page: React.FC = () => { (undefined); - const collectionKey = useRef(null); - const url = useRef(null); - const referralCode = useRef(""); const [publicCollection, setPublicCollection] = useState< Collection | undefined >(undefined); @@ -107,15 +101,6 @@ export default function PublicCollectionGallery() { const [errorMessage, setErrorMessage] = useState(null); const [loading, setLoading] = useState(true); const [isPasswordProtected, setIsPasswordProtected] = useState(false); - - const router = useRouter(); - - const [photoListHeader, setPhotoListHeader] = - useState(null); - - const [photoListFooter, setPhotoListFooter] = - useState(null); - const [uploadTypeSelectorView, setUploadTypeSelectorView] = useState(false); const [blockingLoad, setBlockingLoad] = useState(false); const [shouldDisableDropzone, setShouldDisableDropzone] = useState(false); @@ -128,17 +113,15 @@ export default function PublicCollectionGallery() { collectionID: 0, context: undefined, }); + + const credentials = useRef(undefined); + const collectionKey = useRef(null); + const url = useRef(null); + const referralCode = useRef(""); + const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); - const onAddPhotos = useMemo(() => { - return publicCollection?.publicURLs[0]?.enableCollect - ? () => setUploadTypeSelectorView(true) - : undefined; - }, [publicCollection]); - - const closeUploadTypeSelectorView = () => { - setUploadTypeSelectorView(false); - }; + const router = useRouter(); const showPublicLinkExpiredMessage = () => showMiniDialog({ @@ -229,35 +212,6 @@ export default function PublicCollectionGallery() { const downloadEnabled = publicCollection?.publicURLs?.[0]?.enableDownload ?? true; - useEffect(() => { - publicCollection && - publicFiles && - setPhotoListHeader({ - item: ( - - ), - tag: "header", - height: 68, - }); - }, [publicCollection, publicFiles]); - - useEffect(() => { - setPhotoListFooter( - onAddPhotos - ? { - item: ( - - - - ), - height: 104, - } - : null, - ); - }, [onAddPhotos]); - /** * Pull the latest data related to the public album from remote, updating * both our local database and component state. @@ -421,6 +375,51 @@ export default function PublicCollectionGallery() { } }; + const onAddPhotos = useMemo(() => { + return publicCollection?.publicURLs[0]?.enableCollect + ? () => setUploadTypeSelectorView(true) + : undefined; + }, [publicCollection]); + + const closeUploadTypeSelectorView = () => { + setUploadTypeSelectorView(false); + }; + + const fileListHeader = useMemo( + () => + publicCollection && publicFiles + ? { + item: ( + + ), + tag: "header" as const, + height: 68, + } + : undefined, + [onAddSaveGroup, publicCollection, publicFiles], + ); + + const fileListFooter = useMemo( + () => + onAddPhotos + ? { + item: ( + + + + ), + height: 104, + } + : undefined, + [onAddPhotos], + ); + if (loading && (!publicFiles || !credentials.current)) { return ; } else if (errorMessage) { @@ -464,8 +463,6 @@ export default function PublicCollectionGallery() { const context = { credentials: credentials.current, referralCode: referralCode.current, - photoListHeader, - photoListFooter, }; return ( @@ -503,6 +500,8 @@ export default function PublicCollectionGallery() { ({ credentials: undefined, referralCode: null, - photoListHeader: null, - photoListFooter: null, }); From 6a5e0122368897a700c987d2828c42c09d774a7a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 17:11:57 +0530 Subject: [PATCH 080/109] Fix more type errors --- .../Collections/GalleryBarAndListHeader.tsx | 2 +- .../photos/src/components/FixCreationTime.tsx | 4 +-- web/apps/photos/src/components/Sidebar.tsx | 14 ++++---- web/apps/photos/src/pages/gallery.tsx | 36 +++++++++---------- web/apps/photos/src/pages/index.tsx | 2 +- web/apps/photos/src/utils/file/index.ts | 2 +- .../components/utils/use-file-input.ts | 2 +- .../new/photos/components/SearchBar.tsx | 4 +-- .../new/photos/components/gallery/helpers.ts | 19 +++++++--- .../components/utils/use-loading-bar.ts | 2 +- 10 files changed, 48 insertions(+), 39 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 514abab78b..96b36a5a02 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -284,7 +284,7 @@ const compareCollectionsLatestFile = ( return -1; } else { const sortedFiles = sortFiles([first, second]); - if (sortedFiles[0].id !== first.id) { + if (sortedFiles[0]?.id !== first.id) { return 1; } else { return -1; diff --git a/web/apps/photos/src/components/FixCreationTime.tsx b/web/apps/photos/src/components/FixCreationTime.tsx index 62a578a6c1..4dfe1d2cc4 100644 --- a/web/apps/photos/src/components/FixCreationTime.tsx +++ b/web/apps/photos/src/components/FixCreationTime.tsx @@ -153,7 +153,7 @@ const Progress: React.FC = ({ completed, total }) => ( ); interface OptionsFormProps { - step: Step; + step: Step | undefined; onSubmit: (values: FormValues) => Promise; onClose: () => void; } @@ -226,7 +226,7 @@ const OptionsForm: React.FC = ({ }; interface FooterProps { - step: Step; + step: Step | undefined; onSubmit: () => void; onClose: () => void; } diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 082f48e9dd..eeb8a1f2ca 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -324,8 +324,8 @@ const SubscriptionStatus: React.FC = ({ return true; }, [userDetails]); - const handleClick = useMemo(() => { - const eventHandler: MouseEventHandler = (e) => { + const handleClick: MouseEventHandler = useCallback( + (e) => { e.stopPropagation(); if (isSubscriptionActive(userDetails.subscription)) { @@ -342,9 +342,9 @@ const SubscriptionStatus: React.FC = ({ onShowPlanSelector(); } } - }; - return eventHandler; - }, [userDetails]); + }, + [userDetails], + ); if (!hasAMessage) { return <>; @@ -387,8 +387,8 @@ const SubscriptionStatus: React.FC = ({ {message} diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 3cbaaf12ac..949b6e43a4 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -671,7 +671,8 @@ const Page: React.FC = () => { filteredFiles, ); const userFiles = selectedFiles.filter( - (f) => f.ownerID == user.id, + // If a selection is happening, there must be a user. + (f) => f.ownerID == user!.id, ); const sourceCollectionID = selected.collectionID; if (userFiles.length > 0) { @@ -735,7 +736,8 @@ const Page: React.FC = () => { op == "download" ? selectedFiles : selectedFiles.filter( - (file) => file.ownerID == user.id, + // There'll be a user if files are being selected. + (file) => file.ownerID == user!.id, ); if (toProcessFiles.length > 0) { await performFileOp( @@ -772,24 +774,24 @@ const Page: React.FC = () => { const handleSelectSearchOption = ( searchOption: SearchOption | undefined, ) => { - const type = searchOption?.suggestion.type; - if (type == "collection" || type == "person") { + if (searchOption) { + const type = searchOption.suggestion.type; if (type == "collection") { dispatch({ type: "showCollectionSummary", collectionSummaryID: searchOption.suggestion.collectionID, }); - } else { + } else if (type == "person") { dispatch({ type: "showPerson", personID: searchOption.suggestion.person.id, }); + } else { + dispatch({ + type: "enterSearchMode", + searchSuggestion: searchOption.suggestion, + }); } - } else if (searchOption) { - dispatch({ - type: "enterSearchMode", - searchSuggestion: searchOption.suggestion, - }); } else { dispatch({ type: "exitSearch" }); } @@ -914,12 +916,13 @@ const Page: React.FC = () => { // 3. The caller (eventually) triggers a remote pull in the // background, but meanwhile uses this updated metadata. // - // TODO(RE): Replace with file fetch? + // TODO: Replace with files pull? dispatch({ type: "unsyncedPrivateMagicMetadataUpdate", fileID, privateMagicMetadata: { ...file.magicMetadata, + count: file.magicMetadata?.count ?? 0, version: (file.magicMetadata?.version ?? 0) + 1, data: { ...file.magicMetadata?.data, visibility }, }, @@ -992,13 +995,10 @@ const Page: React.FC = () => { attributes={collectionSelectorAttributes} collectionSummaries={normalCollectionSummaries} collectionForCollectionSummaryID={(id) => - // Null assert since the collection selector should only - // show "selectable" normalCollectionSummaries. See: - // [Note: Picking from selectable collection summaries]. findCollectionCreatingUncategorizedIfNeeded( state.collections, id, - )! + ) } /> { emailByUserID={state.emailByUserID} shareSuggestionEmails={state.shareSuggestionEmails} people={ - (state.view.type == "people" + (state?.view?.type == "people" ? state.view.visiblePeople : undefined) ?? [] } @@ -1140,7 +1140,7 @@ const Page: React.FC = () => { /> ) : !isInSearchMode && !isFirstLoad && - state.view.type == "people" && + state?.view?.type == "people" && !state.view.activePerson ? ( ) : ( @@ -1225,7 +1225,7 @@ const OfflineMessage: React.FC = () => ( * Preload all three variants of a responsive image. */ const preloadImage = (imgBasePath: string) => { - const srcset = []; + const srcset: string[] = []; for (let i = 1; i <= 3; i++) srcset.push(`${imgBasePath}/${i}x.png ${i}x`); new Image().srcset = srcset.join(","); }; diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index d452129a18..d276aad67b 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -284,7 +284,7 @@ const DesktopBox = styled(CenteredRow)` const Slideshow: React.FC = () => { const [selectedIndex, setSelectedIndex] = useState(0); - const containerRef = useRef(undefined); + const containerRef = useRef(null); useEffect(() => { const intervalID = setInterval(() => { diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index aefa842787..326cfc7ad1 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -18,7 +18,7 @@ export interface SelectedState { [k: number]: boolean; ownCount: number; count: number; - collectionID: number; + collectionID: number | undefined; /** * The context in which the selection was made. Only set by newer code if * there is an active selection (older code continues to rely on the diff --git a/web/packages/gallery/components/utils/use-file-input.ts b/web/packages/gallery/components/utils/use-file-input.ts index 7aec8d3d6e..a137b34ad3 100644 --- a/web/packages/gallery/components/utils/use-file-input.ts +++ b/web/packages/gallery/components/utils/use-file-input.ts @@ -62,7 +62,7 @@ export const useFileInput = ({ onSelect, onCancel, }: UseFileInputParams): UseFileInputResult => { - const inputRef = useRef(undefined); + const inputRef = useRef(null); useEffect(() => { // React (as of 19) doesn't support attaching the onCancel event handler diff --git a/web/packages/new/photos/components/SearchBar.tsx b/web/packages/new/photos/components/SearchBar.tsx index f242aee020..2c97d044ec 100644 --- a/web/packages/new/photos/components/SearchBar.tsx +++ b/web/packages/new/photos/components/SearchBar.tsx @@ -83,7 +83,7 @@ export interface SearchBarProps { /** * Called when the user selects a person shown in the empty state view. */ - onSelectPerson: (personID: string | undefined) => void; + onSelectPerson: (personID: string) => void; } /** @@ -210,7 +210,7 @@ const SearchInput: React.FC> = ({ onSelectPeople(); }; - const handleSelectPerson = (personID: string | undefined) => { + const handleSelectPerson = (personID: string) => { resetSearch(); onSelectPerson(personID); }; diff --git a/web/packages/new/photos/components/gallery/helpers.ts b/web/packages/new/photos/components/gallery/helpers.ts index b3d69197fc..749393ad08 100644 --- a/web/packages/new/photos/components/gallery/helpers.ts +++ b/web/packages/new/photos/components/gallery/helpers.ts @@ -46,17 +46,26 @@ export const validateKey = async () => { /** * Return the {@link Collection} (from amongst {@link collections}) with the - * given {@link collectionSummaryID}. As a special case, if the given - * {@link collectionSummaryID} is the ID of the placeholder uncategorized - * collection, create a new uncategorized collection and then return it. + * given {@link collectionSummaryID}. + * + * As a special case, if the given {@link collectionSummaryID} is the ID of the + * placeholder uncategorized collection, create a new uncategorized collection + * and then return it. + * + * This is used in the context of the collection summary, so one of the two + * cases must be true. */ export const findCollectionCreatingUncategorizedIfNeeded = async ( collections: Collection[], collectionSummaryID: number, -): Promise => +): Promise => collectionSummaryID == PseudoCollectionID.uncategorizedPlaceholder ? createUncategorizedCollection() - : collections.find(({ id }) => id == collectionSummaryID); + : // Null assert since the collection selector should only + // show "selectable" normalCollectionSummaries. + // + // See: [Note: Picking from selectable collection summaries]. + collections.find(({ id }) => id == collectionSummaryID)!; /** * Perform a "collection operation" on the selected file(s). diff --git a/web/packages/new/photos/components/utils/use-loading-bar.ts b/web/packages/new/photos/components/utils/use-loading-bar.ts index 6297443e73..c5c7152fed 100644 --- a/web/packages/new/photos/components/utils/use-loading-bar.ts +++ b/web/packages/new/photos/components/utils/use-loading-bar.ts @@ -12,7 +12,7 @@ import { type LoadingBarRef } from "react-top-loading-bar"; * loading bar. This hook returns these functions (and the ref). */ export const useLoadingBar = () => { - const loadingBarRef = useRef(undefined); + const loadingBarRef = useRef(null); const showLoadingBar = useCallback(() => { loadingBarRef.current?.continuousStart(); From 1d3b3eb1cf52dea8dfb5786d5c89abd53a7d54c9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 17:40:37 +0530 Subject: [PATCH 081/109] More tsc fixes --- web/apps/photos/src/components/Avatar.tsx | 16 +- .../Collections/CollectionShare.tsx | 206 ++++++++++-------- .../DownloadStatusNotifications.tsx | 2 +- .../photos/src/components/WatchFolder.tsx | 8 +- 4 files changed, 124 insertions(+), 108 deletions(-) diff --git a/web/apps/photos/src/components/Avatar.tsx b/web/apps/photos/src/components/Avatar.tsx index 0ee92ff32b..866c11fdc6 100644 --- a/web/apps/photos/src/components/Avatar.tsx +++ b/web/apps/photos/src/components/Avatar.tsx @@ -20,7 +20,7 @@ interface AvatarProps { const AvatarBase = styled("div")<{ colorCode: string; size: number; - opacity: number; + opacity: number | undefined; }>` width: ${({ size }) => `${size}px`}; height: ${({ size }) => `${size}px`}; @@ -43,21 +43,21 @@ const Avatar: React.FC = ({ emailByUserID, }) => { const [colorCode, setColorCode] = useState(""); - const [userLetter, setUserLetter] = useState(""); + const [userLetter, setUserLetter] = useState(""); useLayoutEffect(() => { try { if (!file) { return; } - if (file.ownerID !== user.id) { + if (file.ownerID !== user?.id) { // getting email from in-memory id-email map - const email = emailByUserID.get(file.ownerID); + const email = emailByUserID?.get(file.ownerID); if (!email) { log.error("email not found in userIDToEmailMap"); return; } - setUserLetter(email[0].toUpperCase()); + setUserLetter(email[0]?.toUpperCase()); setColorCode(avatarBackgroundColor(file.ownerID)); } else if (file.ownerID === user.id) { const uploaderName = file.pubMagicMetadata?.data.uploaderName; @@ -67,7 +67,7 @@ const Avatar: React.FC = ({ ); return; } - setUserLetter(uploaderName[0].toUpperCase()); + setUserLetter(uploaderName[0]?.toUpperCase()); setColorCode(avatarBackgroundColorPublicCollectedFile); } } catch (e) { @@ -82,7 +82,7 @@ const Avatar: React.FC = ({ } if (user?.email === email) { - setUserLetter(email[0].toUpperCase()); + setUserLetter(email[0]?.toUpperCase()); setColorCode(avatarBackgroundColorPublicCollectedFile); return; } @@ -94,7 +94,7 @@ const Avatar: React.FC = ({ log.error(`ID not found for email: ${email}`); return; } - setUserLetter(email[0].toUpperCase()); + setUserLetter(email[0]?.toUpperCase()); setColorCode(avatarBackgroundColor(id)); } catch (e) { log.error("AvatarIcon.tsx - useLayoutEffect email failed", e); diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 245be674b7..fd17723720 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -71,13 +71,7 @@ import { usePhotosAppContext } from "ente-new/photos/types/context"; import { wait } from "ente-utils/promise"; import { useFormik } from "formik"; import { t } from "i18next"; -import React, { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Trans } from "react-i18next"; import { z } from "zod/v4"; @@ -217,11 +211,13 @@ const SharingDetails: React.FC = ({ const collaborators = collection.sharees .filter((sharee) => sharee.role == "COLLABORATOR") - .map((sharee) => sharee.email); + .map((sharee) => sharee.email) + .filter((email) => email !== undefined); const viewers = collection.sharees .filter((sharee) => sharee.role == "VIEWER") - .map((sharee) => sharee.email); + .map((sharee) => sharee.email) + .filter((email) => email !== undefined); const userOrEmail = (email: string) => email == user.email ? t("you") : email; @@ -240,7 +236,7 @@ const SharingDetails: React.FC = ({ {...{ user, emailByUserID }} /> } - label={isOwner ? t("you") : ownerEmail} + label={isOwner ? t("you") : (ownerEmail ?? "")} /> @@ -325,19 +321,20 @@ const EmailShare: React.FC = ({ const { show: showManageEmail, props: manageEmailVisibilityProps } = useModalVisibility(); - const [participantRole, setParticipantRole] = useState< - CollectionNewParticipantRole | undefined - >(undefined); + const [participantRole, setParticipantRole] = + // Initial value is arbitrary, it always gets reset before + // `showAddParticipant` is called. + useState("VIEWER"); const showAddViewer = useCallback(() => { setParticipantRole("VIEWER"); showAddParticipant(); }, [showAddParticipant]); - const showAddCollaborator = () => { + const showAddCollaborator = useCallback(() => { setParticipantRole("COLLABORATOR"); showAddParticipant(); - }; + }, [showAddParticipant]); const participantCount = collection.sharees.length; @@ -607,7 +604,7 @@ const AddParticipantForm: React.FC = ({ onSubmit, }) => { const formik = useFormik({ - initialValues: { email: "", selectedEmails: [] }, + initialValues: { email: "", selectedEmails: new Array() }, onSubmit: async ({ email, selectedEmails }, { setFieldError }) => { const setEmailFieldError = (message: string) => setFieldError("email", message); @@ -745,19 +742,31 @@ const ManageEmailShare: React.FC = ({ props: manageParticipantVisibilityProps, } = useModalVisibility(); - const participantType = useRef<"COLLABORATOR" | "VIEWER">(null); + const [participantRole, setParticipantRole] = + useState("VIEWER"); + const [selectedParticipant, setSelectedParticipant] = useState< + CollectionUser | undefined + >(undefined); - const selectedParticipant = useRef(null); - - const openAddCollab = () => { - participantType.current = "COLLABORATOR"; + const showAddViewer = useCallback(() => { + setParticipantRole("VIEWER"); showAddParticipant(); - }; + }, [showAddParticipant]); - const openAddViewer = () => { - participantType.current = "VIEWER"; + const showAddCollaborator = useCallback(() => { + setParticipantRole("COLLABORATOR"); showAddParticipant(); - }; + }, [showAddParticipant]); + + const selectAndManageParticipant = useCallback( + (email: string) => { + setSelectedParticipant( + collection.sharees.find((sharee) => sharee.email === email), + ); + showManageParticipant(); + }, + [showManageParticipant], + ); const handleRootClose = () => { onClose(); @@ -770,20 +779,14 @@ const ManageEmailShare: React.FC = ({ const isOwner = user.id == collection.owner?.id; const collaborators = collection.sharees - ?.filter((sharee) => sharee.role == "COLLABORATOR") - .map((sharee) => sharee.email); + .filter((sharee) => sharee.role == "COLLABORATOR") + .map((sharee) => sharee.email) + .filter((email) => email !== undefined); - const viewers = - collection.sharees - ?.filter((sharee) => sharee.role == "VIEWER") - .map((sharee) => sharee.email) || []; - - const openManageParticipant = (email) => { - selectedParticipant.current = collection.sharees.find( - (sharee) => sharee.email === email, - ); - showManageParticipant(); - }; + const viewers = collection.sharees + .filter((sharee) => sharee.role == "VIEWER") + .map((sharee) => sharee.email) + .filter((email) => email !== undefined); return ( <> @@ -807,7 +810,7 @@ const ManageEmailShare: React.FC = ({ {...{ user, emailByUserID }} /> } - label={isOwner ? t("you") : ownerEmail} + label={isOwner ? t("you") : (ownerEmail ?? "")} /> @@ -821,7 +824,7 @@ const ManageEmailShare: React.FC = ({ - openManageParticipant(item) + selectAndManageParticipant(item) } label={item} startIcon={ @@ -838,7 +841,7 @@ const ManageEmailShare: React.FC = ({ } - onClick={openAddCollab} + onClick={showAddCollaborator} label={ collaborators?.length ? t("add_more") @@ -857,7 +860,7 @@ const ManageEmailShare: React.FC = ({ - openManageParticipant(item) + selectAndManageParticipant(item) } label={item} startIcon={ @@ -873,7 +876,7 @@ const ManageEmailShare: React.FC = ({ ))} } - onClick={openAddViewer} + onClick={showAddViewer} label={ viewers?.length ? t("add_more") @@ -894,12 +897,12 @@ const ManageEmailShare: React.FC = ({ onRootClose, onRemotePull, }} - role={participantType.current} + role={participantRole} /> ); @@ -908,7 +911,13 @@ const ManageEmailShare: React.FC = ({ type ManageParticipantProps = ModalVisibilityProps & { onRootClose: () => void; wrap: (f: () => Promise) => () => void; - selectedParticipant: CollectionUser; + /** + * The participant in the collection who we're trying to manage. + * + * The caller semantically guarantees that participant will always be set + * when {@link open} is `true`, but the types don't reflect this. + */ + participant: CollectionUser | undefined; } & Pick; const ManageParticipant: React.FC = ({ @@ -916,7 +925,7 @@ const ManageParticipant: React.FC = ({ onClose, onRootClose, collection, - selectedParticipant, + participant, wrap, onRemotePull, }) => { @@ -928,7 +937,9 @@ const ManageParticipant: React.FC = ({ }; const unshare = wrap(() => - unshareCollection(collection.id, selectedParticipant.email), + // We should have a participant (with a valid email) if this ends up + // being called. + unshareCollection(collection.id, participant!.email!), ); const handleRemove = () => { @@ -952,9 +963,8 @@ const ManageParticipant: React.FC = ({ values={{ selectedEmail }} /> ); - buttonText = t("confirm_convert_to_viewer"); - } else if (newRole == "COLLABORATOR") { + } else { message = t("change_permission_to_collaborator", { selectedEmail, }); @@ -975,13 +985,13 @@ const ManageParticipant: React.FC = ({ newRole: CollectionNewParticipantRole, ) => { await shareCollection(collection, selectedEmail, newRole); - selectedParticipant.role = newRole; + participant!.role = newRole; await onRemotePull({ silent: true }); }; const createOnRoleChange = (role: CollectionNewParticipantRole) => () => { - if (role == selectedParticipant.role) return; - const { email } = selectedParticipant; + if (role == participant!.role) return; + const email = participant!.email!; confirmChangeRolePermission(email, role, () => updateCollectionRole(email, role), ); @@ -993,7 +1003,7 @@ const ManageParticipant: React.FC = ({ message: ( ), continue: { @@ -1004,7 +1014,7 @@ const ManageParticipant: React.FC = ({ }); }; - if (!selectedParticipant) { + if (!participant) { return <>; } @@ -1014,7 +1024,7 @@ const ManageParticipant: React.FC = ({ {...{ open, onClose }} onRootClose={handleRootClose} title={t("manage")} - caption={selectedParticipant.email} + caption={participant.email} > @@ -1032,7 +1042,7 @@ const ManageParticipant: React.FC = ({ label={"Collaborator"} startIcon={} endIcon={ - selectedParticipant.role === "COLLABORATOR" && ( + participant.role === "COLLABORATOR" && ( ) } @@ -1045,9 +1055,7 @@ const ManageParticipant: React.FC = ({ label={"Viewer"} startIcon={} endIcon={ - selectedParticipant.role == "VIEWER" && ( - - ) + participant.role == "VIEWER" && } /> @@ -1094,15 +1102,18 @@ const PublicShare: React.FC = ({ setBlockingLoad, onRemotePull, }) => { - const [publicShareUrl, setPublicShareUrl] = useState(null); - const [publicURL, setPublicURL] = useState( - undefined, - ); const { show: showPublicLinkCreated, props: publicLinkCreatedVisibilityProps, } = useModalVisibility(); + const [publicURL, setPublicURL] = useState( + undefined, + ); + const [resolvedURL, setResolvedURL] = useState( + undefined, + ); + useEffect(() => { setPublicURL(collection.publicURLs[0]); }, [collection]); @@ -1110,27 +1121,27 @@ const PublicShare: React.FC = ({ useEffect(() => { if (publicURL?.url) { appendCollectionKeyToShareURL(publicURL.url, collection.key).then( - (url) => setPublicShareUrl(url), + (url) => setResolvedURL(url), ); } else { - setPublicShareUrl(null); + setResolvedURL(undefined); } }, [publicURL]); const handleCopyLink = () => { - navigator.clipboard.writeText(publicShareUrl); + if (resolvedURL) navigator.clipboard.writeText(resolvedURL); }; return ( <> - {publicURL ? ( + {publicURL && resolvedURL ? ( = ({ ); }; -type ManagePublicShareProps = { - onRootClose: () => void; - collection: Collection; - publicURL: PublicURL; - setPublicURL: (publicURL: PublicURL | undefined) => void; - publicShareUrl: string; -} & Pick< - CollectionShareProps, - "collection" | "setBlockingLoad" | "onRemotePull" ->; +type ManagePublicShareProps = { onRootClose: () => void } & Pick< + ManagePublicShareOptionsProps, + "publicURL" | "setPublicURL" | "resolvedURL" +> & + Pick< + CollectionShareProps, + "collection" | "setBlockingLoad" | "onRemotePull" + >; const ManagePublicShare: React.FC = ({ onRootClose, collection, publicURL, setPublicURL, - publicShareUrl, + resolvedURL, setBlockingLoad, onRemotePull, }) => { @@ -1254,7 +1263,7 @@ const ManagePublicShare: React.FC = ({ props: managePublicShareVisibilityProps, } = useModalVisibility(); - const [copied, handleCopyLink] = useClipboardCopy(publicShareUrl); + const [copied, handleCopyLink] = useClipboardCopy(resolvedURL); return ( <> @@ -1300,7 +1309,7 @@ const ManagePublicShare: React.FC = ({ onRootClose, collection, publicURL, - publicShareUrl, + resolvedURL, setPublicURL, setBlockingLoad, onRemotePull, @@ -1310,15 +1319,18 @@ const ManagePublicShare: React.FC = ({ ); }; -const isLinkExpired = (validTill: number) => { - return validTill && validTill < Date.now() * 1000; -}; +const isLinkExpired = (validTill: number) => + validTill > 0 && validTill < Date.now() * 1000; type ManagePublicShareOptionsProps = ModalVisibilityProps & { onRootClose: () => void; publicURL: PublicURL; setPublicURL: (publicURL: PublicURL | undefined) => void; - publicShareUrl: string; + /** + * The "resolved" publicURL, with both the full origin and the secret + * fragment appended to it. + */ + resolvedURL: string; } & Pick< CollectionShareProps, "collection" | "setBlockingLoad" | "onRemotePull" @@ -1331,13 +1343,15 @@ const ManagePublicShareOptions: React.FC = ({ collection, publicURL, setPublicURL, - publicShareUrl, + resolvedURL, setBlockingLoad, onRemotePull, }) => { - const [sharableLinkError, setSharableLinkError] = useState(null); + const [errorMessage, setErrorMessage] = useState( + undefined, + ); - const [copied, handleCopyLink] = useClipboardCopy(publicShareUrl); + const [copied, handleCopyLink] = useClipboardCopy(resolvedURL); const handleRootClose = () => { onClose(); @@ -1347,27 +1361,29 @@ const ManagePublicShareOptions: React.FC = ({ const handlePublicURLUpdate = async ( updates: UpdatePublicURLAttributes, ) => { + setBlockingLoad(true); + setErrorMessage(undefined); try { - setBlockingLoad(true); setPublicURL(await updatePublicURL(collection.id, updates)); void onRemotePull({ silent: true }); } catch (e) { log.error("Could not update public link", e); - setSharableLinkError(t("generic_error")); + setErrorMessage(t("generic_error")); } finally { setBlockingLoad(false); } }; const handleRemovePublicLink = async () => { + setBlockingLoad(true); + setErrorMessage(undefined); try { - setBlockingLoad(true); await deleteShareURL(collection.id); setPublicURL(undefined); void onRemotePull({ silent: true }); onClose(); } catch (e) { log.error("Failed to remove public link", e); - setSharableLinkError(t("generic_error")); + setErrorMessage(t("generic_error")); } finally { setBlockingLoad(false); } @@ -1426,12 +1442,12 @@ const ManagePublicShareOptions: React.FC = ({ label={t("remove_link")} /> - {sharableLinkError && ( + {errorMessage && ( - {sharableLinkError} + {errorMessage} )} diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index cc69d2027e..f7a667463e 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -74,7 +74,7 @@ export const DownloadStatusNotifications: React.FC< const createOnClick = (group: SaveGroup) => () => { const electron = globalThis.electron; - if (electron) { + if (electron && group.downloadDirPath) { electron.openDirectory(group.downloadDirPath); } else if (onShowCollectionSummary) { onShowCollectionSummary( diff --git a/web/apps/photos/src/components/WatchFolder.tsx b/web/apps/photos/src/components/WatchFolder.tsx index d403997626..f7c46085be 100644 --- a/web/apps/photos/src/components/WatchFolder.tsx +++ b/web/apps/photos/src/components/WatchFolder.tsx @@ -63,7 +63,7 @@ export const WatchFolder: React.FC = ({ e.preventDefault(); e.stopPropagation(); - for (const file of e.dataTransfer.files) { + for (const file of e.dataTransfer?.files ?? []) { void selectCollectionMappingAndAddWatchIfDirectory(file); } }; @@ -156,9 +156,7 @@ interface WatchList { } const WatchList: React.FC = ({ watches, removeWatch }) => - (watches ?? []).length === 0 ? ( - - ) : ( + watches?.length ? ( {watches.map((watch) => ( = ({ watches, removeWatch }) => /> ))} + ) : ( + ); const NoWatches: React.FC = () => ( From 1dc83b272a28e6474321bf53dff0a66a38353fcf Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 18:30:05 +0530 Subject: [PATCH 082/109] More tsc errors --- web/apps/photos/src/services/watch.ts | 39 ++++++++++++++------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index 38fcf838d1..536805347b 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -61,14 +61,16 @@ class FolderWatcher { * * This is passed as a param to {@link init}. */ - private upload: (collectionName: string, filePaths: string[]) => void; + private upload: + | ((collectionName: string, filePaths: string[]) => void) + | undefined; /** * A function to call when we want to trigger a full remote pull. It will * initiate the pull but will not await its completion. * * This is passed as a param to {@link init}. */ - private onTriggerRemotePull: () => void; + private onTriggerRemotePull: (() => void) | undefined; /** A helper function that debounces invocations of {@link runNextEvent}. */ private debouncedRunNextEvent: () => void; @@ -225,16 +227,17 @@ class FolderWatcher { if (this.eventQueue.length == 0 || this.activeWatch || this.isPaused) return; + const event = this.dequeueClubbedEvent(); + if (!event) return; + log.info( + `Processing ${event.action} event for folder watch ${event.folderPath} (collectionName ${event.collectionName}, ${event.filePaths.length} files)`, + ); + const skip = (reason: string) => { log.info(`Ignoring event since ${reason}`); this.debouncedRunNextEvent(); }; - const event = this.dequeueClubbedEvent(); - log.info( - `Processing ${event.action} event for folder watch ${event.folderPath} (collectionName ${event.collectionName}, ${event.filePaths.length} files)`, - ); - const watch = (await this.getWatches()).find( (watch) => watch.folderPath == event.folderPath, ); @@ -262,7 +265,7 @@ class FolderWatcher { `Folder watch requested upload of ${paths.length} files to collection ${collectionName}`, ); - this.upload(collectionName, paths); + this.upload!(collectionName, paths); } else { if (this.pruneFileEventsFromDeletedFolderPaths()) { skip("event was from a deleted folder path"); @@ -277,7 +280,7 @@ class FolderWatcher { ).push(syncedFile); return [removed, rest]; }, - [[], []], + [new Array(), []], ); this.activeWatch = watch; @@ -307,7 +310,7 @@ class FolderWatcher { const filePaths = [event.filePath]; while ( this.eventQueue.length > 0 && - event.action === this.eventQueue[0].action && + event.action === this.eventQueue[0]?.action && event.folderPath === this.eventQueue[0].folderPath && event.collectionName === this.eventQueue[0].collectionName ) { @@ -334,11 +337,11 @@ class FolderWatcher { // Done. if (item.isLivePhoto) { this.uploadedFileForPath.set( - ensureString(item.livePhotoAssets.image), + ensureString(item.livePhotoAssets?.image), uploadResult.file, ); this.uploadedFileForPath.set( - ensureString(item.livePhotoAssets.video), + ensureString(item.livePhotoAssets?.video), uploadResult.file, ); } else { @@ -355,10 +358,10 @@ class FolderWatcher { // Non-retriable error. if (item.isLivePhoto) { this.unUploadableFilePaths.add( - ensureString(item.livePhotoAssets.image), + ensureString(item.livePhotoAssets?.image), ); this.unUploadableFilePaths.add( - ensureString(item.livePhotoAssets.video), + ensureString(item.livePhotoAssets?.video), ); } else { this.unUploadableFilePaths.add( @@ -376,7 +379,7 @@ class FolderWatcher { */ async allFileUploadsDone(uploadedItems: UploadAsset[]) { const electron = ensureElectron(); - const watch = this.activeWatch; + const watch = this.activeWatch!; log.debug(() => [ "watch/allFileUploadsDone", @@ -428,8 +431,8 @@ class FolderWatcher { // possibility for a UploadItem is for it to be a string (the // absolute path to a file on disk). if (item.isLivePhoto) { - const imagePath = ensureString(item.livePhotoAssets.image); - const videoPath = ensureString(item.livePhotoAssets.video); + const imagePath = ensureString(item.livePhotoAssets?.image); + const videoPath = ensureString(item.livePhotoAssets?.video); const imageFile = this.uploadedFileForPath.get(imagePath); const videoFile = this.uploadedFileForPath.get(videoPath); @@ -491,7 +494,7 @@ class FolderWatcher { await removeFromOwnCollection(id, files); } - this.onTriggerRemotePull(); + this.onTriggerRemotePull!(); } } From bafa3b5c5b082bab0952a4562c6cf40f16908bec Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 18:39:34 +0530 Subject: [PATCH 083/109] More tsc fixes --- web/apps/photos/src/components/Upload.tsx | 47 ++++++++------- .../photos/src/services/upload-manager.ts | 60 ++++++++++--------- 2 files changed, 60 insertions(+), 47 deletions(-) diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 6888c92b90..105d55a610 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -1,3 +1,4 @@ +// TODO: Too many null assertions in this file. The types need reworking. // TODO: Audit this file /* eslint-disable @typescript-eslint/no-misused-promises */ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; @@ -272,7 +273,9 @@ export const Upload: React.FC = ({ * If set, this will be the name of the collection that our desktop app * wishes for us to upload into. */ - const pendingDesktopUploadCollectionName = useRef(""); + const pendingDesktopUploadCollectionName = useRef( + undefined, + ); /** * This is set to thue user's choice when the user chooses one of the @@ -368,6 +371,8 @@ export const Upload: React.FC = ({ setInProgressUploads, setFinishedUploads, setUploadPhase, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore setUploadFilenames: setUploadFileNames, setHasLivePhotos, setUploadProgressView, @@ -503,7 +508,7 @@ export const Upload: React.FC = ({ ); uploadItemsAndPaths.current = prunedItemAndPaths; - if (uploadItemsAndPaths.current.length === 0) { + if (uploadItemsAndPaths.current.length == 0) { props.setLoading(false); return; } @@ -518,15 +523,15 @@ export const Upload: React.FC = ({ log.debug(() => ["Import suggestion", importSuggestion]); const _selectedUploadType = selectedUploadType.current; - selectedUploadType.current = null; + selectedUploadType.current = undefined; props.setLoading(false); (async () => { if (publicCollectionGalleryContext.credentials) { setUploaderName( - await savedPublicCollectionUploaderName( + (await savedPublicCollectionUploaderName( publicCollectionGalleryContext.credentials.accessToken, - ), + )) ?? "", ); showUploaderNameInput(); return; @@ -539,7 +544,7 @@ export const Upload: React.FC = ({ "root", pendingDesktopUploadCollectionName.current, ); - pendingDesktopUploadCollectionName.current = null; + pendingDesktopUploadCollectionName.current = undefined; } else { uploadFilesToNewCollections("parent"); } @@ -579,7 +584,7 @@ export const Upload: React.FC = ({ }; } - onOpenCollectionSelector({ + onOpenCollectionSelector?.({ action: "upload", onSelectCollection: uploadFilesToExistingCollection, onCreateCollection: showNextModal, @@ -613,7 +618,7 @@ export const Upload: React.FC = ({ [collection], uploaderName, ); - uploadItemsAndPaths.current = null; + uploadItemsAndPaths.current = []; }; const uploadFilesToNewCollections = async ( @@ -628,7 +633,9 @@ export const Upload: React.FC = ({ >(); if (mapping == "root") { collectionNameToUploadItems.set( - collectionName, + // Un-enforced convention is that collectionName is always set + // when mapping is "root". TODO: Reflect this in types. + collectionName!, uploadItemsAndPaths.current, ); } else { @@ -668,7 +675,7 @@ export const Upload: React.FC = ({ return; } await waitInQueueAndUploadFiles(uploadItemsWithCollection, collections); - uploadItemsAndPaths.current = null; + uploadItemsAndPaths.current = []; }; const waitInQueueAndUploadFiles = async ( @@ -719,7 +726,7 @@ export const Upload: React.FC = ({ collections, uploadItemsWithCollection .map(({ uploadItem }) => uploadItem) - .filter((x) => x), + .filter((x) => x !== undefined), ); } const wereFilesProcessed = await uploadManager.uploadItems( @@ -825,14 +832,14 @@ export const Upload: React.FC = ({ const handlePublicUpload = (uploaderName: string) => { savePublicCollectionUploaderName( - publicCollectionGalleryContext.credentials.accessToken, + publicCollectionGalleryContext.credentials!.accessToken, uploaderName, ); // Do not keep the uploader name input dialog open while the upload is // progressing (the upload progress indicator will take out now). void uploadFilesToExistingCollection( - props.uploadCollection, + props.uploadCollection!, uploaderName, ); }; @@ -871,7 +878,7 @@ export const Upload: React.FC = ({ open={uploadProgressView} onClose={closeUploadProgress} percentComplete={percentComplete} - uploadFileNames={uploadFileNames} + uploadFileNames={uploadFileNames!} uploadCounter={uploadCounter} uploadPhase={uploadPhase} inProgressUploads={inProgressUploads} @@ -892,7 +899,7 @@ export const Upload: React.FC = ({ open={uploaderNameInputVisibilityProps.open} onClose={handleUploaderNameInputClose} uploaderName={uploaderName} - uploadFileCount={uploadItemsAndPaths.current?.length ?? 0} + uploadFileCount={uploadItemsAndPaths.current.length} onSubmit={handlePublicUpload} /> @@ -984,7 +991,7 @@ const defaultImportSuggestion: ImportSuggestion = { }; const deriveImportSuggestion = ( - uploadType: UploadType, + uploadType: UploadType | undefined, paths: string[], ): ImportSuggestion => { if (isDesktop && uploadType == "files") { @@ -996,8 +1003,8 @@ const deriveImportSuggestion = ( ); const separatorCount = (s: string) => separatorCounts.get(s)!; paths.sort((path1, path2) => separatorCount(path1) - separatorCount(path2)); - const firstPath = paths[0]; - const lastPath = paths[paths.length - 1]; + const firstPath = paths[0]!; + const lastPath = paths[paths.length - 1]!; const L = firstPath.length; let i = 0; @@ -1020,7 +1027,7 @@ const deriveImportSuggestion = ( } return { - rootFolderName: commonPathPrefix || null, + rootFolderName: commonPathPrefix || "", hasNestedFolders: firstFileFolder !== lastFileFolder, }; }; @@ -1074,7 +1081,7 @@ const setPendingUploads = async ( and on next upload we can directly start uploading to this collection */ if (collections.length == 1) { - collectionName = collections[0].name; + collectionName = collections[0]!.name; } const filePaths: string[] = []; diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index e5d61f6170..e4d0d4e6a1 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -1,3 +1,4 @@ +// TODO: Too many null assertions in this file. The types need reworking. import { ensureLocalUser } from "ente-accounts/services/user"; import { isDesktop } from "ente-base/app"; import { createComlinkCryptoWorker } from "ente-base/crypto"; @@ -96,6 +97,8 @@ export type UploadItemWithCollection = UploadAsset & { }; class UIService { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore private progressUpdater: ProgressUpdater; // UPLOAD LEVEL STATES @@ -105,9 +108,9 @@ class UIService { private uploadProgressView = false; // STAGE LEVEL STATES - private perFileProgress: number; - private filesUploadedCount: number; - private totalFilesCount: number; + private perFileProgress = 0; + private filesUploadedCount = 0; + private totalFilesCount = 0; private inProgressUploads: InProgressUploads = new Map(); private finishedUploads: FinishedUploads = new Map(); @@ -243,7 +246,7 @@ const groupByResult = (finishedUploads: FinishedUploads) => { const groups: SegregatedFinishedUploads = new Map(); for (const [localID, result] of finishedUploads) { if (!groups.has(result)) groups.set(result, []); - groups.get(result).push(localID); + groups.get(result)!.push(localID); } return groups; }; @@ -251,13 +254,13 @@ const groupByResult = (finishedUploads: FinishedUploads) => { class UploadManager { private comlinkCryptoWorkers: ComlinkWorker[] = new Array(maxConcurrentUploads); - private parsedMetadataJSONMap: Map; - private itemsToBeUploaded: ClusteredUploadItem[]; - private failedItems: ClusteredUploadItem[]; - private existingFiles: EnteFile[]; - private onUploadFile: (file: EnteFile) => void; - private collections: Map; - private uploadInProgress: boolean; + private parsedMetadataJSONMap = new Map(); + private itemsToBeUploaded: ClusteredUploadItem[] = []; + private failedItems: ClusteredUploadItem[] = []; + private existingFiles: EnteFile[] = []; + private onUploadFile: ((file: EnteFile) => void) | undefined; + private collections = new Map(); + private uploadInProgress = false; private publicAlbumsCredentials: PublicAlbumsCredentials | undefined; private uploaderName: string | undefined; /** @@ -481,14 +484,14 @@ class UploadManager { await UploadService.setFileCount(mediaItems.length); this.uiService.setUploadPhase("uploading"); - const uploadProcesses = []; + const uploadProcesses = new Array>(); for ( let i = 0; i < maxConcurrentUploads && this.itemsToBeUploaded.length > 0; i++ ) { this.comlinkCryptoWorkers[i] = createComlinkCryptoWorker(); - const worker = await this.comlinkCryptoWorkers[i].remote; + const worker = await this.comlinkCryptoWorkers[i]!.remote; uploadProcesses.push(this.uploadNextItemInQueue(worker)); } await Promise.all(uploadProcesses); @@ -508,9 +511,9 @@ class UploadManager { this.abortIfCancelled(); logAboutMemoryPressureIfNeeded(); - const clusteredItem = this.itemsToBeUploaded.pop(); + const clusteredItem = this.itemsToBeUploaded.pop()!; const { localID, collectionID } = clusteredItem; - const collection = this.collections.get(collectionID); + const collection = this.collections.get(collectionID)!; const uploadableItem = { ...clusteredItem, collection }; uiService.setFileProgress(localID, 0); @@ -605,7 +608,7 @@ class UploadManager { private updateExistingFiles(file: EnteFile) { this.existingFiles.push(file); - this.onUploadFile(file); + this.onUploadFile!(file); } /** @@ -668,8 +671,8 @@ const makeUploadItemWithCollectionIDAndName = ( localID: f.localID!, collectionID: f.collectionID!, fileName: (f.isLivePhoto - ? uploadItemFileName(f.livePhotoAssets.image) - : uploadItemFileName(f.uploadItem))!, + ? uploadItemFileName(f.livePhotoAssets!.image) + : uploadItemFileName(f.uploadItem!))!, isLivePhoto: f.isLivePhoto, uploadItem: f.uploadItem, pathPrefix: f.pathPrefix, @@ -689,7 +692,10 @@ const splitMetadataAndMediaItems = ( else media.push(f); return [metadata, media]; }, - [[], []], + [ + new Array(), + new Array(), + ], ); /** @@ -710,22 +716,22 @@ const clusterLivePhotos = async ( .sort((f, g) => f.collectionID - g.collectionID); let index = 0; while (index < items.length - 1) { - const f = items[index]; - const g = items[index + 1]; - const fFileType = potentialFileTypeFromExtension(f.fileName); - const gFileType = potentialFileTypeFromExtension(g.fileName); + const f = items[index]!; + const g = items[index + 1]!; + const fFileType = potentialFileTypeFromExtension(f.fileName)!; + const gFileType = potentialFileTypeFromExtension(g.fileName)!; const fa: PotentialLivePhotoAsset = { fileName: f.fileName, fileType: fFileType, collectionID: f.collectionID, - uploadItem: f.uploadItem, + uploadItem: f.uploadItem!, pathPrefix: f.pathPrefix, }; const ga: PotentialLivePhotoAsset = { fileName: g.fileName, fileType: gFileType, collectionID: g.collectionID, - uploadItem: g.uploadItem, + uploadItem: g.uploadItem!, pathPrefix: g.pathPrefix, }; if (await areLivePhotoAssets(fa, ga, parsedMetadataJSONMap)) { @@ -738,8 +744,8 @@ const clusterLivePhotos = async ( isLivePhoto: true, pathPrefix: image.pathPrefix, livePhotoAssets: { - image: image.uploadItem, - video: video.uploadItem, + image: image.uploadItem!, + video: video.uploadItem!, }, }); index += 2; From 4c5d340b571f81bdbf97fc88a74067f5c10c8726 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 7 Jul 2025 20:38:00 +0530 Subject: [PATCH 084/109] More tsc --- web/apps/cast/src/pages/slideshow.tsx | 2 +- .../photos/src/components/Collections/CollectionShare.tsx | 8 +++----- web/apps/photos/src/pages/shared-albums.tsx | 4 ++-- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/web/apps/cast/src/pages/slideshow.tsx b/web/apps/cast/src/pages/slideshow.tsx index 2bc1b32eff..4ecdb61e2a 100644 --- a/web/apps/cast/src/pages/slideshow.tsx +++ b/web/apps/cast/src/pages/slideshow.tsx @@ -9,7 +9,7 @@ import { imageURLGenerator } from "services/render"; const Page: React.FC = () => { const [isEmpty, setIsEmpty] = useState(false); - const [imageURL, setImageURL] = useState(); + const [imageURL, setImageURL] = useState(""); const router = useRouter(); diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index fd17723720..e09fe29fb4 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -1347,9 +1347,7 @@ const ManagePublicShareOptions: React.FC = ({ setBlockingLoad, onRemotePull, }) => { - const [errorMessage, setErrorMessage] = useState( - undefined, - ); + const [errorMessage, setErrorMessage] = useState(""); const [copied, handleCopyLink] = useClipboardCopy(resolvedURL); @@ -1362,7 +1360,7 @@ const ManagePublicShareOptions: React.FC = ({ updates: UpdatePublicURLAttributes, ) => { setBlockingLoad(true); - setErrorMessage(undefined); + setErrorMessage(""); try { setPublicURL(await updatePublicURL(collection.id, updates)); void onRemotePull({ silent: true }); @@ -1375,7 +1373,7 @@ const ManagePublicShareOptions: React.FC = ({ }; const handleRemovePublicLink = async () => { setBlockingLoad(true); - setErrorMessage(undefined); + setErrorMessage(""); try { await deleteShareURL(collection.id); setPublicURL(undefined); diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index b3427da707..ac6ffd7759 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -98,7 +98,7 @@ export default function PublicCollectionGallery() { const [publicFiles, setPublicFiles] = useState( undefined, ); - const [errorMessage, setErrorMessage] = useState(null); + const [errorMessage, setErrorMessage] = useState(""); const [loading, setLoading] = useState(true); const [isPasswordProtected, setIsPasswordProtected] = useState(false); const [uploadTypeSelectorView, setUploadTypeSelectorView] = useState(false); @@ -229,7 +229,7 @@ export default function PublicCollectionGallery() { const isPasswordProtected = !!collection.publicURLs[0]?.passwordEnabled; setIsPasswordProtected(isPasswordProtected); - setErrorMessage(null); + setErrorMessage(""); // Remove the locally cached accessTokenJWT if the sharer has // disabled password protection on the link. From 236c6f612b0d44f366605e66e872871fdd8e81ff Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 07:07:04 +0530 Subject: [PATCH 085/109] Separate internal and external interfaces --- .../Collections/GalleryBarAndListHeader.tsx | 11 ++--- web/apps/photos/src/components/FileList.tsx | 47 ++++++++++++------- web/apps/photos/src/pages/gallery.tsx | 13 ++--- web/apps/photos/src/pages/shared-albums.tsx | 1 - 4 files changed, 42 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 96b36a5a02..48e32abce6 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -3,7 +3,7 @@ import { CollectionShare, type CollectionShareProps, } from "components/Collections/CollectionShare"; -import type { TimeStampListItem } from "components/FileList"; +import type { FileListHeaderOrFooter } from "components/FileList"; import { useModalVisibility } from "ente-base/components/utils/modal"; import { isSaveCancelled, @@ -49,7 +49,7 @@ type GalleryBarAndListHeaderProps = Omit< barCollectionSummaries: CollectionSummaries; activeCollection: Collection; setActiveCollectionID: (collectionID: number) => void; - setPhotoListHeader: (value: TimeStampListItem) => void; + setFileListHeader: (header: FileListHeaderOrFooter) => void; saveGroups: SaveGroup[]; } & Pick & Pick< @@ -66,7 +66,7 @@ type GalleryBarAndListHeaderProps = Omit< * of the actual list of items. * * These are disparate views - indeed, the list header is not even a child of - * this component but is instead proxied via {@link setPhotoListHeader}. Still, + * this component but is instead proxied via {@link setFileListHeader}. Still, * having this intermediate wrapper component allows us to move some of the * common concerns shared by both the gallery bar and list header (e.g. some * dialogs that can be invoked from both places) into this file instead of @@ -95,7 +95,7 @@ export const GalleryBarAndListHeader: React.FC< onRemotePull, onAddSaveGroup, onSelectPerson, - setPhotoListHeader, + setFileListHeader, }) => { const { show: showAllAlbums, props: allAlbumsVisibilityProps } = useModalVisibility(); @@ -134,7 +134,7 @@ export const GalleryBarAndListHeader: React.FC< useEffect(() => { if (shouldHide) return; - setPhotoListHeader({ + setFileListHeader({ item: mode != "people" ? ( ), - tag: "header", height: 68, }); }, [ diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index cf2314ef3f..7d0737d79c 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -53,14 +53,27 @@ const FOOTER_HEIGHT = 90; const ALBUM_FOOTER_HEIGHT = 75; const ALBUM_FOOTER_HEIGHT_WITH_REFERRAL = 113; -export type FileListItemTag = "header" | "publicAlbumsFooter" | "date" | "file"; +/** + * A component with an explicit height suitable for being plugged in as the + * {@link header} or {@link footer} of the {@link FileList}. + */ +export interface FileListHeaderOrFooter { + /** + * The component itself. + */ + item: React.ReactNode; + /** + * The height of the component (in px). + */ + height: number; +} -export interface TimeStampListItem { +interface TimeStampListItem { /** * An optional {@link FileListItemTag} that can be used to identify item * types for conditional behaviour. */ - tag?: FileListItemTag; + tag?: "date" | "file"; items?: FileListAnnotatedFile[]; itemStartIndex?: number; date?: string; @@ -122,6 +135,19 @@ export interface FileListProps { * another mode in which the gallery operates. */ modePlus?: GalleryBarMode | "search"; + /** + * An optional component shown before all the items in the list. + * + * It is not sticky, and scrolls along with the content of the list. + */ + header?: FileListHeaderOrFooter; + /** + * An optional component shown after all the items in the list. + * + * It is not sticky, and scrolls along with the content of the list. + */ + footer?: FileListHeaderOrFooter; + showAppDownloadBanner?: boolean; /** * The logged in user, if any. * @@ -130,7 +156,6 @@ export interface FileListProps { * omit this prop. */ user?: LocalUser; - showAppDownloadBanner?: boolean; /** * If `true`, then the current listing is showing magic search results. */ @@ -157,16 +182,6 @@ export interface FileListProps { * omitted when running in the public albums app. */ emailByUserID?: Map; - /** - * An optional {@link TimeStampListItem} shown before all the items in the - * list. It is not sticky, and scrolls along with the content of the list. - */ - header?: TimeStampListItem; - /** - * An optional {@link TimeStampListItem} shown after all the items in the - * list. It is not sticky, and scrolls along with the content of the list. - */ - footer?: TimeStampListItem; /** * Called when the user activates the thumbnail at the given {@link index}. * @@ -185,6 +200,7 @@ export const FileList: React.FC = ({ mode, modePlus, header, + footer, user, annotatedFiles, showAppDownloadBanner, @@ -196,7 +212,6 @@ export const FileList: React.FC = ({ activePersonID, favoriteFileIDs, emailByUserID, - footer, onItemClick, }) => { const publicCollectionGalleryContext = useContext( @@ -385,7 +400,6 @@ export const FileList: React.FC = ({ }; const getAppDownloadFooter = (): TimeStampListItem => ({ - tag: "publicAlbumsFooter", height: FOOTER_HEIGHT, item: ( @@ -415,7 +429,6 @@ export const FileList: React.FC = ({ }); const getAlbumsFooter = (): TimeStampListItem => ({ - tag: "publicAlbumsFooter", height: publicCollectionGalleryContext.referralCode ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL : ALBUM_FOOTER_HEIGHT, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 949b6e43a4..7e3d14bc72 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -5,7 +5,7 @@ import { IconButton, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; -import { type TimeStampListItem } from "components/FileList"; +import type { FileListHeaderOrFooter } from "components/FileList"; import { FileListWithViewer } from "components/FileListWithViewer"; import { FixCreationTime } from "components/FixCreationTime"; import { Sidebar } from "components/Sidebar"; @@ -180,9 +180,11 @@ const Page: React.FC = () => { const [fixCreationTimeFiles, setFixCreationTimeFiles] = useState< EnteFile[] >([]); - // The (non-sticky) header shown at the top of the gallery items. + /** + * The (non-sticky) header shown at the top of the gallery items. + */ const [fileListHeader, setFileListHeader] = useState< - TimeStampListItem | undefined + FileListHeaderOrFooter | undefined >(undefined); const [openCollectionSelector, setOpenCollectionSelector] = useState(false); @@ -409,14 +411,13 @@ const Page: React.FC = () => { useEffect(() => { if (isInSearchMode && state.searchSuggestion) { setFileListHeader({ - height: 104, item: ( ), - tag: "header", + height: 104, }); } }, [isInSearchMode, state.searchSuggestion, state.searchResults]); @@ -1072,7 +1073,7 @@ const Page: React.FC = () => { activeCollection, activeCollectionID, activePerson, - setPhotoListHeader: setFileListHeader, + setFileListHeader, saveGroups, onAddSaveGroup, }} diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index ac6ffd7759..52a2aabc8e 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -398,7 +398,6 @@ export default function PublicCollectionGallery() { }} /> ), - tag: "header" as const, height: 68, } : undefined, From f4b909f4a79091e8dbc61b34a8845187977c61a1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 07:39:55 +0530 Subject: [PATCH 086/109] Cleanup --- web/apps/photos/src/components/FileList.tsx | 45 ----------------- .../src/components/FileListWithViewer.tsx | 3 -- web/apps/photos/src/pages/gallery.tsx | 50 +++++++++++++++++-- 3 files changed, 46 insertions(+), 52 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 7d0737d79c..905e81d227 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -147,7 +147,6 @@ export interface FileListProps { * It is not sticky, and scrolls along with the content of the list. */ footer?: FileListHeaderOrFooter; - showAppDownloadBanner?: boolean; /** * The logged in user, if any. * @@ -203,7 +202,6 @@ export const FileList: React.FC = ({ footer, user, annotatedFiles, - showAppDownloadBanner, isMagicSearchResult, selectable, selected, @@ -276,8 +274,6 @@ export const FileList: React.FC = ({ timeStampList.push(getVacuumItem(timeStampList)); if (footer) { timeStampList.push(asFullSpanListItem(footer)); - } else if (showAppDownloadBanner) { - timeStampList.push(getAppDownloadFooter()); } if (publicCollectionGalleryContext.credentials) { @@ -399,35 +395,6 @@ export const FileList: React.FC = ({ }; }; - const getAppDownloadFooter = (): TimeStampListItem => ({ - height: FOOTER_HEIGHT, - item: ( - - - - ), - b: ( - - ), - }} - /> - - - ), - }); - const getAlbumsFooter = (): TimeStampListItem => ({ height: publicCollectionGalleryContext.referralCode ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL @@ -966,18 +933,6 @@ const DateContainer = styled(ListItemContainer)( `, ); -const FooterContainer = styled(ListItemContainer)` - margin-bottom: 0.75rem; - @media (max-width: 540px) { - font-size: 12px; - margin-bottom: 0.5rem; - } - text-align: center; - justify-content: center; - align-items: flex-end; - margin-top: calc(2rem + 20px); -`; - const AlbumFooterContainer = styled(ListItemContainer, { shouldForwardProp: (propName) => propName != "hasReferral", })<{ hasReferral: boolean }>` diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 3386588ec6..a332c70364 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -58,7 +58,6 @@ export type FileListWithViewerProps = { | "modePlus" | "header" | "footer" - | "showAppDownloadBanner" | "isMagicSearchResult" | "selectable" | "selected" @@ -98,7 +97,6 @@ export const FileListWithViewer: React.FC = ({ user, files, enableDownload, - showAppDownloadBanner, isMagicSearchResult, selectable, selected, @@ -186,7 +184,6 @@ export const FileListWithViewer: React.FC = ({ header, footer, user, - showAppDownloadBanner, isMagicSearchResult, selectable, selected, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 7e3d14bc72..0cd16af4ee 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1,7 +1,7 @@ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; import MenuIcon from "@mui/icons-material/Menu"; -import { IconButton, Stack, Typography } from "@mui/material"; +import { IconButton, Link, Stack, Typography } from "@mui/material"; import { AuthenticateUser } from "components/AuthenticateUser"; import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader"; import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; @@ -967,6 +967,14 @@ const Page: React.FC = () => { [], ); + const showAppDownloadFooter = + state.collectionFiles.length < 30 && !isInSearchMode; + + const fileListFooter = useMemo( + () => (showAppDownloadFooter ? createAppDownloadFooter() : undefined), + [showAppDownloadFooter], + ); + const showSelectionBar = selected.count > 0 && selected.collectionID === activeCollectionID; @@ -1149,12 +1157,10 @@ const Page: React.FC = () => { mode={barMode} modePlus={isInSearchMode ? "search" : barMode} header={fileListHeader} + footer={fileListFooter} user={user} files={filteredFiles} enableDownload={true} - showAppDownloadBanner={ - state.collectionFiles.length < 30 && !isInSearchMode - } isMagicSearchResult={state.searchSuggestion?.type == "clip"} selectable={true} selected={selected} @@ -1383,3 +1389,39 @@ const handleSubscriptionCompletionRedirectIfNeeded = async ( } } }; + +const createAppDownloadFooter = (): FileListHeaderOrFooter => ({ + item: ( + + + ), + b: ( + + ), + }} + /> + + ), + height: 90, +}); From ef1a5358fde8d5eced4543783e0ebec7fbb8aa0c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 07:55:49 +0530 Subject: [PATCH 087/109] Rename --- web/apps/photos/src/components/FileList.tsx | 13 ++++++++----- .../photos/src/components/FileListWithViewer.tsx | 6 +++--- web/apps/photos/src/pages/gallery.tsx | 2 +- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 905e81d227..16ba5b41a9 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -156,9 +156,12 @@ export interface FileListProps { */ user?: LocalUser; /** - * If `true`, then the current listing is showing magic search results. + * If `true`, then the default behaviour of grouping files by their date is + * suppressed. + * + * This behaviour is used when showing magic search results. */ - isMagicSearchResult?: boolean; + disableGrouping?: boolean; selectable?: boolean; setSelected: ( selected: SelectedState | ((selected: SelectedState) => SelectedState), @@ -202,7 +205,7 @@ export const FileList: React.FC = ({ footer, user, annotatedFiles, - isMagicSearchResult, + disableGrouping, selectable, selected, setSelected, @@ -259,7 +262,7 @@ export const FileList: React.FC = ({ if (header) { timeStampList.push(asFullSpanListItem(header)); } - if (isMagicSearchResult) { + if (disableGrouping) { noGrouping(timeStampList); } else { groupByTime(timeStampList); @@ -294,7 +297,7 @@ export const FileList: React.FC = ({ annotatedFiles, header, footer, - isMagicSearchResult, + disableGrouping, publicCollectionGalleryContext.credentials, ]); diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index a332c70364..92c81df25f 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -58,7 +58,7 @@ export type FileListWithViewerProps = { | "modePlus" | "header" | "footer" - | "isMagicSearchResult" + | "disableGrouping" | "selectable" | "selected" | "setSelected" @@ -97,7 +97,7 @@ export const FileListWithViewer: React.FC = ({ user, files, enableDownload, - isMagicSearchResult, + disableGrouping, selectable, selected, setSelected, @@ -184,7 +184,7 @@ export const FileListWithViewer: React.FC = ({ header, footer, user, - isMagicSearchResult, + disableGrouping, selectable, selected, setSelected, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 0cd16af4ee..6fb596b991 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1161,7 +1161,7 @@ const Page: React.FC = () => { user={user} files={filteredFiles} enableDownload={true} - isMagicSearchResult={state.searchSuggestion?.type == "clip"} + disableGrouping={state.searchSuggestion?.type == "clip"} selectable={true} selected={selected} setSelected={setSelected} From d327eb027c4f52e7642b041527e9fab91f001f28 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 08:23:39 +0530 Subject: [PATCH 088/109] Extract and merge, part 1 --- .../Collections/GalleryBarAndListHeader.tsx | 4 +- web/apps/photos/src/pages/gallery.tsx | 3 - web/apps/photos/src/pages/shared-albums.tsx | 125 +++++++++++++++--- .../photos/components/gallery/ListHeader.tsx | 4 +- 4 files changed, 111 insertions(+), 25 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 48e32abce6..1bf7574f06 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -62,8 +62,8 @@ type GalleryBarAndListHeaderProps = Omit< * dialogs that might be triggered by actions on either the bar or the header.. * * This component manages the sticky horizontally scrollable bar shown at the - * top of the gallery, AND the non-sticky header shown below the bar, at the top - * of the actual list of items. + * top of the gallery, AND the (non-sticky) header shown below the bar, at the + * top of the actual list of items. * * These are disparate views - indeed, the list header is not even a child of * this component but is instead proxied via {@link setFileListHeader}. Still, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 6fb596b991..dbb6f1425c 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -180,9 +180,6 @@ const Page: React.FC = () => { const [fixCreationTimeFiles, setFixCreationTimeFiles] = useState< EnteFile[] >([]); - /** - * The (non-sticky) header shown at the top of the gallery items. - */ const [fileListHeader, setFileListHeader] = useState< FileListHeaderOrFooter | undefined >(undefined); diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 52a2aabc8e..ea565c7adb 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -390,7 +390,7 @@ export default function PublicCollectionGallery() { publicCollection && publicFiles ? { item: ( - ), - height: 68, + height: fileListHeaderHeight, } : undefined, [onAddSaveGroup, publicCollection, publicFiles], ); - const fileListFooter = useMemo( - () => - onAddPhotos - ? { - item: ( - - - - ), - height: 104, - } - : undefined, - [onAddPhotos], - ); + const fileListFooter = useMemo(() => { + const props = { referralCode: referralCode.current, onAddPhotos }; + return { + item: , + height: fileListFooterHeightForProps(props), + }; + }, [referralCode.current, onAddPhotos]); if (loading && (!publicFiles || !credentials.current)) { return ; @@ -626,13 +619,24 @@ const SelectedFileOptions: React.FC = ({ ); -interface ListHeaderProps { +interface FileListHeaderProps { publicCollection: Collection; publicFiles: EnteFile[]; onAddSaveGroup: AddSaveGroup; } -const ListHeader: React.FC = ({ +/** + * The fixed height (in px) of {@link FileListHeader}. + */ +const fileListHeaderHeight = 68; + +/** + * A header shown before the listing of files. + * + * It scrolls along with the content. It has a fixed height, + * {@link fileListHeaderHeight}. + */ +const FileListHeader: React.FC = ({ publicCollection, publicFiles, onAddSaveGroup, @@ -670,3 +674,88 @@ const ListHeader: React.FC = ({ ); }; + +interface FileListFooterProps { + referralCode?: string; + onAddPhotos?: () => void; +} + +/** + * The dynamic (prop-depedent) height of {@link FileListFooter}. + */ +const fileListFooterHeightForProps = ({ + referralCode, + onAddPhotos, +}: FileListFooterProps) => (onAddPhotos ? 104 : 0) + (referralCode ? 113 : 75); + +/** + * A footer shown after the listing of files. + * + * It scrolls along with the content. It has a dynamic height, dependent on the + * props, calculated using {@link fileListFooterHeightForProps}. + */ + +const FileListFooter: React.FC = ({ + referralCode, + onAddPhotos, +}) => { + if (onAddPhotos) + return ( + + + + ); + + return ( + + {/* Make the entire area tappable, otherwise it is hard to + get at on mobile devices. */} + + + + + ), + }} + values={{ url: "ente.io" }} + /> + + + {publicCollectionGalleryContext.referralCode ? ( + + + + + + ) : null} + + + ); +}; diff --git a/web/packages/new/photos/components/gallery/ListHeader.tsx b/web/packages/new/photos/components/gallery/ListHeader.tsx index 91838f3852..1979ff5fe5 100644 --- a/web/packages/new/photos/components/gallery/ListHeader.tsx +++ b/web/packages/new/photos/components/gallery/ListHeader.tsx @@ -38,8 +38,8 @@ interface GalleryItemsSummaryProps { } /** - * A component suitable for being used as a (non-sticky) summary displayed on - * top of the of a list of photos (or other items) shown in the gallery. + * A component suitable for being used as a summary displayed on top of the of a + * list of photos (or other items) shown in the gallery. */ export const GalleryItemsSummary: React.FC = ({ name, From f9adbdf639ad255c9eb57cbcb645ef1ab5836735 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 08:32:19 +0530 Subject: [PATCH 089/109] Extract and merge, part 2 --- web/apps/photos/src/components/FileList.tsx | 105 +-------------- web/apps/photos/src/pages/shared-albums.tsx | 136 ++++++++++++-------- 2 files changed, 87 insertions(+), 154 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 16ba5b41a9..e7eaa88228 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -3,7 +3,7 @@ import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined"; import FavoriteRoundedIcon from "@mui/icons-material/FavoriteRounded"; import PlayCircleOutlineOutlinedIcon from "@mui/icons-material/PlayCircleOutlineOutlined"; -import { Box, Checkbox, Link, Typography, styled } from "@mui/material"; +import { Box, Checkbox, Typography, styled } from "@mui/material"; import Avatar from "components/Avatar"; import type { LocalUser } from "ente-accounts/services/user"; import { assertionFailed } from "ente-base/assert"; @@ -31,7 +31,6 @@ import { PseudoCollectionID } from "ente-new/photos/services/collection-summary" import { t } from "i18next"; import memoize from "memoize-one"; import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; -import { Trans } from "react-i18next"; import { VariableSizeList as List, type ListChildComponentProps, @@ -49,10 +48,6 @@ export const SPACE_BTW_DATES = 44; const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244; -const FOOTER_HEIGHT = 90; -const ALBUM_FOOTER_HEIGHT = 75; -const ALBUM_FOOTER_HEIGHT_WITH_REFERRAL = 113; - /** * A component with an explicit height suitable for being plugged in as the * {@link header} or {@link footer} of the {@link FileList}. @@ -274,15 +269,12 @@ export const FileList: React.FC = ({ if (timeStampList.length === 1) { timeStampList.push(getEmptyListItem()); } - timeStampList.push(getVacuumItem(timeStampList)); + const footerHeight = footer?.height ?? 0; + timeStampList.push(getVacuumItem(timeStampList, footerHeight)); if (footer) { timeStampList.push(asFullSpanListItem(footer)); } - if (publicCollectionGalleryContext.credentials) { - timeStampList.push(getAlbumsFooter()); - } - setTimeStampList(timeStampList); refreshInProgress.current = false; if (shouldRefresh.current) { @@ -372,15 +364,7 @@ export const FileList: React.FC = ({ }; }; - const getVacuumItem = (timeStampList) => { - let footerHeight; - if (publicCollectionGalleryContext.credentials) { - footerHeight = publicCollectionGalleryContext.referralCode - ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL - : ALBUM_FOOTER_HEIGHT; - } else { - footerHeight = FOOTER_HEIGHT; - } + const getVacuumItem = (timeStampList, footerHeight: number) => { const fileListHeight = (() => { let sum = 0; const getCurrentItemSize = getItemSize(timeStampList); @@ -398,64 +382,6 @@ export const FileList: React.FC = ({ }; }; - const getAlbumsFooter = (): TimeStampListItem => ({ - height: publicCollectionGalleryContext.referralCode - ? ALBUM_FOOTER_HEIGHT_WITH_REFERRAL - : ALBUM_FOOTER_HEIGHT, - item: ( - - {/* Make the entire area tappable, otherwise it is hard to - get at on mobile devices. */} - - - - - ), - }} - values={{ url: "ente.io" }} - /> - - - {publicCollectionGalleryContext.referralCode ? ( - - - - - - ) : null} - - - ), - }); - /** * Checks and merge multiple dates into a single row. */ @@ -936,29 +862,6 @@ const DateContainer = styled(ListItemContainer)( `, ); -const AlbumFooterContainer = styled(ListItemContainer, { - shouldForwardProp: (propName) => propName != "hasReferral", -})<{ hasReferral: boolean }>` - margin-top: 48px; - margin-bottom: ${({ hasReferral }) => (!hasReferral ? `10px` : "0px")}; - text-align: center; - justify-content: center; -`; - -const FullStretchContainer = styled("div")( - ({ theme }) => ` - margin: 0 -24px; - width: calc(100% + 46px); - left: -24px; - @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { - margin: 0 -4px; - width: calc(100% + 6px); - left: -4px; - } - background-color: ${theme.vars.palette.accent.main}; -`, -); - const NothingContainer = styled(ListItemContainer)` text-align: center; justify-content: center; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index ea565c7adb..b69f07e549 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -3,7 +3,15 @@ import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternate import CloseIcon from "@mui/icons-material/Close"; import DownloadIcon from "@mui/icons-material/Download"; import FileDownloadOutlinedIcon from "@mui/icons-material/FileDownloadOutlined"; -import { Box, Button, IconButton, Stack, styled, Tooltip } from "@mui/material"; +import { + Box, + Button, + IconButton, + Link, + Stack, + styled, + Tooltip, +} from "@mui/material"; import Typography from "@mui/material/Typography"; import { DownloadStatusNotifications } from "components/DownloadStatusNotifications"; import { FileListWithViewer } from "components/FileListWithViewer"; @@ -74,6 +82,10 @@ import { removePublicCollectionFileData, verifyPublicAlbumPassword, } from "ente-new/albums/services/public-collection"; +import { + IMAGE_CONTAINER_MAX_WIDTH, + MIN_COLUMNS, +} from "ente-new/photos/components/FileList"; import { GalleryItemsHeaderAdapter, GalleryItemsSummary, @@ -84,6 +96,7 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { type FileWithPath } from "react-dropzone"; +import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; import { getSelectedFiles, type SelectedState } from "utils/file"; import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; @@ -699,63 +712,80 @@ const FileListFooter: React.FC = ({ referralCode, onAddPhotos, }) => { - if (onAddPhotos) - return ( - - - - ); - return ( - - {/* Make the entire area tappable, otherwise it is hard to + + {onAddPhotos && ( + + + + )} + + {/* Make the entire area tappable, otherwise it is hard to get at on mobile devices. */} - - - - - ), - }} - values={{ url: "ente.io" }} - /> - - - {publicCollectionGalleryContext.referralCode ? ( - - + + + + ), }} + values={{ url: "ente.io" }} /> - - ) : null} - - + + {referralCode ? ( + + + + + + ) : null} + + + ); }; + +const AlbumFooterContainer = styled("div", { + shouldForwardProp: (propName) => propName != "hasReferral", +})<{ hasReferral: boolean }>` + margin-top: 48px; + margin-bottom: ${({ hasReferral }) => (!hasReferral ? `10px` : "0px")}; + text-align: center; + justify-content: center; +`; + +const FullStretchContainer = styled("div")( + ({ theme }) => ` + margin: 0 -24px; + width: calc(100% + 46px); + left: -24px; + @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { + margin: 0 -4px; + width: calc(100% + 6px); + left: -4px; + } + background-color: ${theme.vars.palette.accent.main}; +`, +); From 1d7f9522e3c2318fa71a3dcf9c670d0d671290c9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 09:25:50 +0530 Subject: [PATCH 090/109] Fin --- web/apps/photos/src/pages/shared-albums.tsx | 154 ++++++++---------- .../utils/publicCollectionGallery/index.ts | 2 - 2 files changed, 64 insertions(+), 92 deletions(-) diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index b69f07e549..4ec23bae9f 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -82,10 +82,6 @@ import { removePublicCollectionFileData, verifyPublicAlbumPassword, } from "ente-new/albums/services/public-collection"; -import { - IMAGE_CONTAINER_MAX_WIDTH, - MIN_COLUMNS, -} from "ente-new/photos/components/FileList"; import { GalleryItemsHeaderAdapter, GalleryItemsSummary, @@ -111,6 +107,7 @@ export default function PublicCollectionGallery() { const [publicFiles, setPublicFiles] = useState( undefined, ); + const [referralCode, setReferralCode] = useState(""); const [errorMessage, setErrorMessage] = useState(""); const [loading, setLoading] = useState(true); const [isPasswordProtected, setIsPasswordProtected] = useState(false); @@ -130,7 +127,6 @@ export default function PublicCollectionGallery() { const credentials = useRef(undefined); const collectionKey = useRef(null); const url = useRef(null); - const referralCode = useRef(""); const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); @@ -193,8 +189,9 @@ export default function PublicCollectionGallery() { const accessToken = t; let accessTokenJWT: string | undefined; if (collection) { - referralCode.current = - await savedLastPublicCollectionReferralCode(); + setReferralCode( + (await savedLastPublicCollectionReferralCode()) ?? "", + ); setPublicCollection(collection); setIsPasswordProtected( !!collection.publicURLs[0]?.passwordEnabled, @@ -236,7 +233,7 @@ export default function PublicCollectionGallery() { try { const { collection, referralCode: userReferralCode } = await pullCollection(accessToken, collectionKey.current); - referralCode.current = userReferralCode; + setReferralCode(userReferralCode); setPublicCollection(collection); const isPasswordProtected = @@ -418,12 +415,12 @@ export default function PublicCollectionGallery() { ); const fileListFooter = useMemo(() => { - const props = { referralCode: referralCode.current, onAddPhotos }; + const props = { referralCode, onAddPhotos }; return { item: , height: fileListFooterHeightForProps(props), }; - }, [referralCode.current, onAddPhotos]); + }, [referralCode, onAddPhotos]); if (loading && (!publicFiles || !credentials.current)) { return ; @@ -465,10 +462,7 @@ export default function PublicCollectionGallery() { } // TODO: memo this (after the dependencies are traceable). - const context = { - credentials: credentials.current, - referralCode: referralCode.current, - }; + const context = { credentials: credentials.current }; return ( @@ -711,81 +705,61 @@ const fileListFooterHeightForProps = ({ const FileListFooter: React.FC = ({ referralCode, onAddPhotos, -}) => { - return ( - - {onAddPhotos && ( - - - - )} - - {/* Make the entire area tappable, otherwise it is hard to - get at on mobile devices. */} - - - - - ), - }} - values={{ url: "ente.io" }} - /> - - - {referralCode ? ( - +}) => ( + + {onAddPhotos && ( + + + + )} + {/* Make the entire area tappable, otherwise it is hard to + get at on mobile devices. */} + + + - - - - ) : null} - - - - ); -}; - -const AlbumFooterContainer = styled("div", { - shouldForwardProp: (propName) => propName != "hasReferral", -})<{ hasReferral: boolean }>` - margin-top: 48px; - margin-bottom: ${({ hasReferral }) => (!hasReferral ? `10px` : "0px")}; - text-align: center; - justify-content: center; -`; - -const FullStretchContainer = styled("div")( - ({ theme }) => ` - margin: 0 -24px; - width: calc(100% + 46px); - left: -24px; - @media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * MIN_COLUMNS}px) { - margin: 0 -4px; - width: calc(100% + 6px); - left: -4px; - } - background-color: ${theme.vars.palette.accent.main}; -`, + variant="small" + component="span" + sx={{ color: "accent.main" }} + /> + ), + }} + values={{ url: "ente.io" }} + /> + + + {referralCode && ( + + + + )} + ); diff --git a/web/apps/photos/src/utils/publicCollectionGallery/index.ts b/web/apps/photos/src/utils/publicCollectionGallery/index.ts index 38b29a9cf1..828134098c 100644 --- a/web/apps/photos/src/utils/publicCollectionGallery/index.ts +++ b/web/apps/photos/src/utils/publicCollectionGallery/index.ts @@ -8,11 +8,9 @@ export interface PublicCollectionGalleryContextType { * undefined when we're in the default photos app context. */ credentials: PublicAlbumsCredentials | undefined; - referralCode: string | null; } export const PublicCollectionGalleryContext = createContext({ credentials: undefined, - referralCode: null, }); From 68d831ef3d356ea424539a7a30aa22992cf58463 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 10:37:40 +0530 Subject: [PATCH 091/109] Update --- web/apps/photos/src/components/FileList.tsx | 17 +-- web/apps/photos/src/components/Upload.tsx | 57 ++++---- web/apps/photos/src/pages/shared-albums.tsx | 127 +++++++++--------- .../utils/publicCollectionGallery/index.ts | 16 --- 4 files changed, 91 insertions(+), 126 deletions(-) delete mode 100644 web/apps/photos/src/utils/publicCollectionGallery/index.ts diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index e7eaa88228..a113a8adcf 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -30,7 +30,7 @@ import { TileBottomTextOverlay } from "ente-new/photos/components/Tiles"; import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; import { t } from "i18next"; import memoize from "memoize-one"; -import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { VariableSizeList as List, type ListChildComponentProps, @@ -41,7 +41,6 @@ import { handleSelectCreator, handleSelectCreatorMulti, } from "utils/photoFrame"; -import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; export const DATE_CONTAINER_HEIGHT = 48; export const SPACE_BTW_DATES = 44; @@ -210,10 +209,6 @@ export const FileList: React.FC = ({ emailByUserID, onItemClick, }) => { - const publicCollectionGalleryContext = useContext( - PublicCollectionGalleryContext, - ); - const [timeStampList, setTimeStampList] = useState([]); const refreshInProgress = useRef(false); const shouldRefresh = useRef(false); @@ -283,15 +278,7 @@ export const FileList: React.FC = ({ } }; main(); - }, [ - width, - height, - annotatedFiles, - header, - footer, - disableGrouping, - publicCollectionGalleryContext.credentials, - ]); + }, [width, height, header, footer, annotatedFiles, disableGrouping]); useEffect(() => { refreshList(); diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 105d55a610..610df422da 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -30,6 +30,7 @@ import { } from "ente-base/components/utils/modal"; import { useBaseContext } from "ente-base/context"; import { basename, dirname, joinPath } from "ente-base/file-name"; +import type { PublicAlbumsCredentials } from "ente-base/http"; import log from "ente-base/log"; import type { CollectionMapping, Electron, ZipItem } from "ente-base/types/ipc"; import { type UploadTypeSelectorIntent } from "ente-gallery/components/Upload"; @@ -68,13 +69,7 @@ import { redirectToCustomerPortal } from "ente-new/photos/services/user-details" import { usePhotosAppContext } from "ente-new/photos/types/context"; import { firstNonEmpty } from "ente-utils/array"; import { t } from "i18next"; -import React, { - useCallback, - useContext, - useEffect, - useRef, - useState, -} from "react"; +import React, { useCallback, useEffect, useRef, useState } from "react"; import type { InProgressUpload, SegregatedFinishedUploads, @@ -84,18 +79,24 @@ import type { } from "services/upload-manager"; import { uploadManager } from "services/upload-manager"; import watcher from "services/watch"; -import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; import { UploadProgress } from "./UploadProgress"; interface UploadProps { /** - * The currently logged in user, if any. + * The logged in user, if any. * * This is only expected to be present when we're running it the context of * the photos app, where there is a logged in user. When used by the public * albums app, this prop can be omitted. */ user?: LocalUser; + /** + * The {@link PublicAlbumsCredentials} to use, if any. + * + * These are expected to be set if we are in the context of the public + * albums app, and should be undefined when we're in the photos app context. + */ + publicAlbumsCredentials?: PublicAlbumsCredentials; isFirstUpload?: boolean; uploadTypeSelectorView: boolean; dragAndDropFiles: File[]; @@ -164,6 +165,7 @@ type UploadType = "files" | "folders" | "zips"; */ export const Upload: React.FC = ({ user, + publicAlbumsCredentials, isFirstUpload, dragAndDropFiles, onRemotePull, @@ -177,9 +179,6 @@ export const Upload: React.FC = ({ }) => { const { showMiniDialog, onGenericError } = useBaseContext(); const { showNotification, watchFolderView } = usePhotosAppContext(); - const publicCollectionGalleryContext = useContext( - PublicCollectionGalleryContext, - ); const [uploadProgressView, setUploadProgressView] = useState(false); const [uploadPhase, setUploadPhase] = useState("preparing"); @@ -378,7 +377,7 @@ export const Upload: React.FC = ({ setUploadProgressView, }, onUploadFile, - publicCollectionGalleryContext.credentials, + publicAlbumsCredentials, ); if (uploadManager.isUploadRunning()) { @@ -408,7 +407,7 @@ export const Upload: React.FC = ({ setDesktopZipItems(zipItems); }); } - }, [publicCollectionGalleryContext.credentials]); + }, [publicAlbumsCredentials]); // Handle selected files when user selects files for upload through the open // file / open folder selection dialog, or drag-and-drops them. @@ -527,10 +526,10 @@ export const Upload: React.FC = ({ props.setLoading(false); (async () => { - if (publicCollectionGalleryContext.credentials) { + if (publicAlbumsCredentials) { setUploaderName( (await savedPublicCollectionUploaderName( - publicCollectionGalleryContext.credentials.accessToken, + publicAlbumsCredentials.accessToken, )) ?? "", ); showUploaderNameInput(); @@ -591,7 +590,13 @@ export const Upload: React.FC = ({ onCancel: handleCollectionSelectorCancel, }); })(); - }, [webFiles, desktopFiles, desktopFilePaths, desktopZipItems]); + }, [ + publicAlbumsCredentials, + webFiles, + desktopFiles, + desktopFilePaths, + desktopZipItems, + ]); const preCollectionCreationAction = () => { onCloseCollectionSelector?.(); @@ -832,7 +837,7 @@ export const Upload: React.FC = ({ const handlePublicUpload = (uploaderName: string) => { savePublicCollectionUploaderName( - publicCollectionGalleryContext.credentials!.accessToken, + publicAlbumsCredentials!.accessToken, uploaderName, ); @@ -868,6 +873,7 @@ export const Upload: React.FC = ({ void; -}; +} & Pick; /** * Request the user to specify which type of file / folder / zip it is that they @@ -1129,28 +1135,21 @@ type UploadTypeSelectorProps = ModalVisibilityProps & { const UploadTypeSelector: React.FC = ({ open, onClose, + publicAlbumsCredentials, intent, pendingUploadType, onSelect, }) => { - const publicCollectionGalleryContext = useContext( - PublicCollectionGalleryContext, - ); - // Directly show the file selector for the public albums app on likely // mobile devices. const directlyShowUploadFiles = useIsTouchscreen(); useEffect(() => { - if ( - open && - directlyShowUploadFiles && - publicCollectionGalleryContext.credentials - ) { + if (open && directlyShowUploadFiles && publicAlbumsCredentials) { onSelect("files"); onClose(); } - }, [open]); + }, [open, publicAlbumsCredentials]); const handleClose: DialogProps["onClose"] = (_, reason) => { // Disable backdrop clicks and esc keypresses if a selection is pending diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 4ec23bae9f..8c3b4e648f 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -95,7 +95,6 @@ import { type FileWithPath } from "react-dropzone"; import { Trans } from "react-i18next"; import { uploadManager } from "services/upload-manager"; import { getSelectedFiles, type SelectedState } from "utils/file"; -import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery"; export default function PublicCollectionGallery() { const { showMiniDialog, onGenericError } = useBaseContext(); @@ -461,73 +460,69 @@ export default function PublicCollectionGallery() { ); } - // TODO: memo this (after the dependencies are traceable). - const context = { credentials: credentials.current }; - return ( - - + - - {selected.count > 0 ? ( - - ) : ( - - - - - {onAddPhotos ? ( - - ) : ( - - )} - - )} - + {selected.count > 0 ? ( + + ) : ( + + + + + {onAddPhotos ? ( + + ) : ( + + )} + + )} + - - {blockingLoad && } - - - - + + {blockingLoad && } + + + ); } @@ -688,7 +683,7 @@ interface FileListFooterProps { } /** - * The dynamic (prop-depedent) height of {@link FileListFooter}. + * The dynamic (prop-dependent) height of {@link FileListFooter}. */ const fileListFooterHeightForProps = ({ referralCode, diff --git a/web/apps/photos/src/utils/publicCollectionGallery/index.ts b/web/apps/photos/src/utils/publicCollectionGallery/index.ts deleted file mode 100644 index 828134098c..0000000000 --- a/web/apps/photos/src/utils/publicCollectionGallery/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { PublicAlbumsCredentials } from "ente-base/http"; -import { createContext } from "react"; - -export interface PublicCollectionGalleryContextType { - /** - * The {@link PublicAlbumsCredentials} to use. These are guaranteed to be - * set if we are in the context of the public albums app, and will be - * undefined when we're in the default photos app context. - */ - credentials: PublicAlbumsCredentials | undefined; -} - -export const PublicCollectionGalleryContext = - createContext({ - credentials: undefined, - }); From 2a0795dd47460d7b32260459e360330c5a3bf329 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 10:50:35 +0530 Subject: [PATCH 092/109] null assertions meanwhile --- web/apps/photos/src/pages/shared-albums.tsx | 40 ++++++++++----------- web/packages/gallery/services/download.ts | 4 ++- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 8c3b4e648f..5ae52db264 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -1,4 +1,4 @@ -// TODO: Audit this file +// TODO: Audit this file (too many null assertions) import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; import CloseIcon from "@mui/icons-material/Close"; import DownloadIcon from "@mui/icons-material/Download"; @@ -123,9 +123,9 @@ export default function PublicCollectionGallery() { context: undefined, }); + // TODO: Can we convert these to state const credentials = useRef(undefined); - const collectionKey = useRef(null); - const url = useRef(null); + const collectionKey = useRef(undefined); const { saveGroups, onAddSaveGroup, onRemoveSaveGroup } = useSaveGroups(); @@ -169,8 +169,7 @@ export default function PublicCollectionGallery() { const main = async () => { let redirectingToWebsite = false; try { - url.current = window.location.href; - const currentURL = new URL(url.current); + const currentURL = new URL(window.location.href); const t = currentURL.searchParams.get("t"); const ck = await extractCollectionKeyFromShareURL(currentURL); if (!t && !ck) { @@ -181,10 +180,7 @@ export default function PublicCollectionGallery() { return; } collectionKey.current = ck; - url.current = window.location.href; - const collection = await savedPublicCollectionByKey( - collectionKey.current, - ); + const collection = await savedPublicCollectionByKey(ck); const accessToken = t; let accessTokenJWT: string | undefined; if (collection) { @@ -226,12 +222,12 @@ export default function PublicCollectionGallery() { * both our local database and component state. */ const publicAlbumsRemotePull = useCallback(async () => { - const accessToken = credentials.current.accessToken; + const accessToken = credentials.current!.accessToken; showLoadingBar(); setLoading(true); try { const { collection, referralCode: userReferralCode } = - await pullCollection(accessToken, collectionKey.current); + await pullCollection(accessToken, collectionKey.current!); setReferralCode(userReferralCode); setPublicCollection(collection); @@ -242,18 +238,18 @@ export default function PublicCollectionGallery() { // Remove the locally cached accessTokenJWT if the sharer has // disabled password protection on the link. - if (!isPasswordProtected && credentials.current.accessTokenJWT) { + if (!isPasswordProtected && credentials.current?.accessTokenJWT) { credentials.current.accessTokenJWT = undefined; downloadManager.setPublicAlbumsCredentials(credentials.current); removePublicCollectionAccessTokenJWT(accessToken); } - if (isPasswordProtected && !credentials.current.accessTokenJWT) { + if (isPasswordProtected && !credentials.current?.accessTokenJWT) { await removePublicCollectionFileData(accessToken); } else { try { await pullPublicCollectionFiles( - credentials.current, + credentials.current!, collection, (files) => setPublicFiles( @@ -272,7 +268,7 @@ export default function PublicCollectionGallery() { // Clear the locally cached accessTokenJWT and ask the user // to reenter the password. if (isHTTP401Error(e)) { - credentials.current.accessTokenJWT = undefined; + credentials.current!.accessTokenJWT = undefined; downloadManager.setPublicAlbumsCredentials( credentials.current, ); @@ -303,7 +299,7 @@ export default function PublicCollectionGallery() { ); // Sharing has been disabled. Clear out local cache. await removePublicCollectionFileData(accessToken); - await removePublicCollectionByKey(collectionKey.current); + await removePublicCollectionByKey(collectionKey.current!); setPublicCollection(undefined); setPublicFiles(undefined); } else { @@ -329,13 +325,13 @@ export default function PublicCollectionGallery() { setFieldError, ) => { try { - const accessToken = credentials.current.accessToken; + const accessToken = credentials.current!.accessToken; const accessTokenJWT = await verifyPublicAlbumPassword( - publicCollection.publicURLs[0]!, + publicCollection!.publicURLs[0]!, password, accessToken, ); - credentials.current.accessTokenJWT = accessTokenJWT; + credentials.current!.accessTokenJWT = accessTokenJWT; downloadManager.setPublicAlbumsCredentials(credentials.current); await savePublicCollectionAccessTokenJWT( accessToken, @@ -367,12 +363,12 @@ export default function PublicCollectionGallery() { const handleUploadFile = (file: EnteFile) => setPublicFiles( - sortFilesForCollection([...publicFiles, file], publicCollection), + sortFilesForCollection([...publicFiles!, file], publicCollection), ); const downloadFilesHelper = async () => { try { - const selectedFiles = getSelectedFiles(selected, publicFiles); + const selectedFiles = getSelectedFiles(selected, publicFiles!); await downloadAndSaveFiles( selectedFiles, t("files_count", { count: selectedFiles.length }), @@ -431,7 +427,7 @@ export default function PublicCollectionGallery() { ); - } else if (isPasswordProtected && !credentials.current.accessTokenJWT) { + } else if (isPasswordProtected && !credentials.current?.accessTokenJWT) { return ( {t("password")} diff --git a/web/packages/gallery/services/download.ts b/web/packages/gallery/services/download.ts index fc90ed99fb..a4fb2e1b02 100644 --- a/web/packages/gallery/services/download.ts +++ b/web/packages/gallery/services/download.ts @@ -198,7 +198,9 @@ class DownloadManager { * Set the credentials that should be used for download files when we're * running in the context of the public albums app. */ - setPublicAlbumsCredentials(credentials: PublicAlbumsCredentials) { + setPublicAlbumsCredentials( + credentials: PublicAlbumsCredentials | undefined, + ) { this.publicAlbumsCredentials = credentials; } From 786620a5aca4ee820cc8e87b712b2ceda4bc9e48 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 11:09:35 +0530 Subject: [PATCH 093/109] More null handling --- .../photos/src/components/UploadProgress.tsx | 68 +++++++++---------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/web/apps/photos/src/components/UploadProgress.tsx b/web/apps/photos/src/components/UploadProgress.tsx index 1afa305ace..aaeafb6342 100644 --- a/web/apps/photos/src/components/UploadProgress.tsx +++ b/web/apps/photos/src/components/UploadProgress.tsx @@ -32,6 +32,7 @@ import { t } from "i18next"; import memoize from "memoize-one"; import React, { createContext, + useCallback, useContext, useEffect, useState, @@ -87,7 +88,7 @@ export const UploadProgress: React.FC = ({ if (open) setExpanded(false); }, [open]); - const handleClose = () => { + const handleClose = useCallback(() => { if (uploadPhase == "done") { onClose(); } else { @@ -102,7 +103,7 @@ export const UploadProgress: React.FC = ({ cancel: t("no"), }); } - }; + }, [uploadPhase, onClose, cancelUploads, showMiniDialog]); if (!open) { return <>; @@ -130,6 +131,9 @@ export const UploadProgress: React.FC = ({ ); }; +/** + * A context internal to the components of this file. + */ interface UploadProgressContextT { open: boolean; onClose: () => void; @@ -145,20 +149,19 @@ interface UploadProgressContextT { setExpanded: React.Dispatch>; } -const UploadProgressContext = createContext({ - open: null, - onClose: () => null, - uploadCounter: null, - uploadPhase: undefined, - percentComplete: null, - retryFailed: () => null, - inProgressUploads: null, - uploadFileNames: null, - finishedUploads: null, - hasLivePhotos: null, - expanded: null, - setExpanded: () => null, -}); +const UploadProgressContext = createContext( + undefined, +); + +/** + * Convenience hook to obtain the non-null asserted + * {@link UploadProgressContext}. + * + * The non-null assertion is reasonable since we provide it to the tree always + * in an invariant that is local to this file (and thus has less chance of being + * invalid in the future). + */ +const useUploadProgressContext = () => useContext(UploadProgressContext)!; const MinimizedUploadProgress: React.FC = () => ( @@ -176,9 +179,7 @@ const UploadProgressHeader: React.FC = () => ( ); const UploadProgressTitle: React.FC = () => { - const { setExpanded, onClose, expanded } = useContext( - UploadProgressContext, - ); + const { setExpanded, onClose, expanded } = useUploadProgressContext(); const toggleExpanded = () => setExpanded((expanded) => !expanded); return ( @@ -202,9 +203,8 @@ const UploadProgressTitle: React.FC = () => { }; const UploadProgressSubtitleText: React.FC = () => { - const { uploadPhase, uploadCounter, finishedUploads } = useContext( - UploadProgressContext, - ); + const { uploadPhase, uploadCounter, finishedUploads } = + useUploadProgressContext(); return ( { - const { uploadPhase, percentComplete } = useContext(UploadProgressContext); + const { uploadPhase, percentComplete } = useUploadProgressContext(); return ( @@ -300,9 +300,8 @@ const UploadProgressBar: React.FC = () => { }; function UploadProgressDialog() { - const { open, onClose, uploadPhase, finishedUploads } = useContext( - UploadProgressContext, - ); + const { open, onClose, uploadPhase, finishedUploads } = + useUploadProgressContext(); const [hasUnUploadedFiles, setHasUnUploadedFiles] = useState(false); @@ -377,7 +376,8 @@ function UploadProgressDialog() { const InProgressSection: React.FC = () => { const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadPhase } = - useContext(UploadProgressContext); + useUploadProgressContext(); + const fileList = inProgressUploads ?? []; const renderListItem = ({ localFileID, progress }) => { @@ -492,9 +492,8 @@ const ResultSection: React.FC = ({ sectionTitle, sectionInfo, }) => { - const { finishedUploads, uploadFileNames } = useContext( - UploadProgressContext, - ); + const { finishedUploads, uploadFileNames } = useUploadProgressContext(); + const fileList = finishedUploads.get(resultType); if (!fileList?.length) { @@ -657,15 +656,14 @@ function ItemList(props: ItemListProps) { } const DoneFooter: React.FC = () => { - const { uploadPhase, finishedUploads, retryFailed, onClose } = useContext( - UploadProgressContext, - ); + const { uploadPhase, finishedUploads, retryFailed, onClose } = + useUploadProgressContext(); return ( {uploadPhase == "done" && - (finishedUploads?.get("failed")?.length > 0 || - finishedUploads?.get("blocked")?.length > 0 ? ( + ((finishedUploads.get("failed")?.length ?? 0) > 0 || + (finishedUploads.get("blocked")?.length ?? 0) > 0 ? ( From be7b57f3d5a2fd9506f7f361e85aee529cb98fb4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 11:31:32 +0530 Subject: [PATCH 094/109] Update --- web/apps/photos/src/components/FileList.tsx | 78 +++++++++++++-------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index a113a8adcf..e60ee9c149 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -42,11 +42,8 @@ import { handleSelectCreatorMulti, } from "utils/photoFrame"; -export const DATE_CONTAINER_HEIGHT = 48; export const SPACE_BTW_DATES = 44; -const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244; - /** * A component with an explicit height suitable for being plugged in as the * {@link header} or {@link footer} of the {@link FileList}. @@ -252,6 +249,7 @@ export const FileList: React.FC = ({ if (header) { timeStampList.push(asFullSpanListItem(header)); } + if (disableGrouping) { noGrouping(timeStampList); } else { @@ -261,9 +259,21 @@ export const FileList: React.FC = ({ if (!skipMerge) { timeStampList = mergeTimeStampList(timeStampList, columns); } - if (timeStampList.length === 1) { - timeStampList.push(getEmptyListItem()); + + if (timeStampList.length == 1) { + timeStampList.push({ + item: ( + + + {t("nothing_here")} + + + ), + id: "empty-list-banner", + height: height - 48, + }); } + const footerHeight = footer?.height ?? 0; timeStampList.push(getVacuumItem(timeStampList, footerHeight)); if (footer) { @@ -278,7 +288,15 @@ export const FileList: React.FC = ({ } }; main(); - }, [width, height, header, footer, annotatedFiles, disableGrouping]); + }, [ + width, + height, + header, + footer, + annotatedFiles, + disableGrouping, + columns, + ]); useEffect(() => { refreshList(); @@ -337,20 +355,6 @@ export const FileList: React.FC = ({ }); }; - const getEmptyListItem = () => { - return { - item: ( - - - {t("nothing_here")} - - - ), - id: "empty-list-banner", - height: height - 48, - }; - }; - const getVacuumItem = (timeStampList, footerHeight: number) => { const fileListHeight = (() => { let sum = 0; @@ -387,6 +391,7 @@ export const FileList: React.FC = ({ // If new list pointer is not at the end of list then // we can add more items to the same list. if (newList[newIndex]) { + const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244; // Check if items can be added to same list if ( newList[newIndex + 1].items.length + @@ -451,7 +456,7 @@ export const FileList: React.FC = ({ const getItemSize = (timeStampList) => (index) => { switch (timeStampList[index].tag) { case "date": - return DATE_CONTAINER_HEIGHT; + return dateContainerHeight; case "file": return listItemHeight; default: @@ -822,34 +827,47 @@ const ListContainer = styled(Box, { } `; +/** + * An grid item, spanning {@link span} columns. + */ const ListItemContainer = styled("div")<{ span: number }>` - grid-column: span ${(props) => props.span}; + grid-column: span ${({ span }) => span}; display: flex; align-items: center; `; +/** + * A grid items that spans all columns. + */ const FullSpanListItemContainer = styled("div")` grid-column: 1 / -1; display: flex; align-items: center; `; -const asFullSpanListItem = ({ item, ...rest }: TimeStampListItem) => ({ +/** + * Convert a {@link FileListHeaderOrFooter} into a {@link TimeStampListItem} + * that spans all columns. + */ +const asFullSpanListItem = ({ item, ...rest }: FileListHeaderOrFooter) => ({ ...rest, item: {item}, }); -const DateContainer = styled(ListItemContainer)( - ({ theme }) => ` +/** + * The fixed height (in px) of {@link DateContainer}. + */ +const dateContainerHeight = 48; + +const DateContainer = styled(ListItemContainer)` white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - height: ${DATE_CONTAINER_HEIGHT}px; - color: ${theme.vars.palette.text.muted}; -`, -); + height: ${dateContainerHeight}px; + color: "text.muted"; +`; -const NothingContainer = styled(ListItemContainer)` +const NoFilesContainer = styled(ListItemContainer)` text-align: center; justify-content: center; `; From 01d3c802402cbad526dc334234a1d28f8f6afbdd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 12:13:07 +0530 Subject: [PATCH 095/109] useDeferredValue --- web/apps/photos/src/components/FileList.tsx | 99 +++++++++++---------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index e60ee9c149..d3637d6766 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -30,7 +30,13 @@ import { TileBottomTextOverlay } from "ente-new/photos/components/Tiles"; import { PseudoCollectionID } from "ente-new/photos/services/collection-summary"; import { t } from "i18next"; import memoize from "memoize-one"; -import React, { useEffect, useMemo, useRef, useState } from "react"; +import React, { + useDeferredValue, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { VariableSizeList as List, type ListChildComponentProps, @@ -206,9 +212,11 @@ export const FileList: React.FC = ({ emailByUserID, onItemClick, }) => { - const [timeStampList, setTimeStampList] = useState([]); - const refreshInProgress = useRef(false); - const shouldRefresh = useRef(false); + const [_timeStampList, setTimeStampList] = useState( + new Array(), + ); + const timeStampList = useDeferredValue(_timeStampList); + const listRef = useRef(null); // Timeline date strings for which all photos have been selected. @@ -238,56 +246,51 @@ export const FileList: React.FC = ({ }; useEffect(() => { - const main = () => { - if (refreshInProgress.current) { - shouldRefresh.current = true; - return; - } - refreshInProgress.current = true; - let timeStampList: TimeStampListItem[] = []; + // Since width and height are dependencies, there might be too many + // updates to the list during a resize. The list computation too, while + // fast, is non-trivial. + // + // To avoid these issues, the we use `useDeferredValue`: if it gets + // another update when processing one, React will restart the background + // rerender from scratch. - if (header) { - timeStampList.push(asFullSpanListItem(header)); - } + let timeStampList: TimeStampListItem[] = []; - if (disableGrouping) { - noGrouping(timeStampList); - } else { - groupByTime(timeStampList); - } + if (header) { + timeStampList.push(asFullSpanListItem(header)); + } - if (!skipMerge) { - timeStampList = mergeTimeStampList(timeStampList, columns); - } + if (disableGrouping) { + noGrouping(timeStampList); + } else { + groupByTime(timeStampList); + } - if (timeStampList.length == 1) { - timeStampList.push({ - item: ( - - - {t("nothing_here")} - - - ), - id: "empty-list-banner", - height: height - 48, - }); - } + if (!skipMerge) { + timeStampList = mergeTimeStampList(timeStampList, columns); + } - const footerHeight = footer?.height ?? 0; - timeStampList.push(getVacuumItem(timeStampList, footerHeight)); - if (footer) { - timeStampList.push(asFullSpanListItem(footer)); - } + if (timeStampList.length == 1) { + timeStampList.push({ + item: ( + + + {t("nothing_here")} + + + ), + id: "empty-list-banner", + height: height - 48, + }); + } - setTimeStampList(timeStampList); - refreshInProgress.current = false; - if (shouldRefresh.current) { - shouldRefresh.current = false; - setTimeout(main, 0); - } - }; - main(); + const footerHeight = footer?.height ?? 0; + timeStampList.push(getVacuumItem(timeStampList, footerHeight)); + if (footer) { + timeStampList.push(asFullSpanListItem(footer)); + } + + setTimeStampList(timeStampList); }, [ width, height, From 67b9ba09fa65484a4bf8f3bce4e4be98a149537e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 12:21:23 +0530 Subject: [PATCH 096/109] Update --- web/apps/photos/src/components/FileList.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index d3637d6766..bcf9d34246 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -38,8 +38,8 @@ import React, { useState, } from "react"; import { - VariableSizeList as List, type ListChildComponentProps, + VariableSizeList, areEqual, } from "react-window"; import { type SelectedState, shouldShowAvatar } from "utils/file"; @@ -217,7 +217,7 @@ export const FileList: React.FC = ({ ); const timeStampList = useDeferredValue(_timeStampList); - const listRef = useRef(null); + const listRef = useRef(null); // Timeline date strings for which all photos have been selected. // @@ -747,7 +747,7 @@ export const FileList: React.FC = ({ } return ( - = ({ useIsScrolling > {PhotoListRow} - + ); }; From a34a07644e4415f67babf2ed5c7ca39ad31bfae4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 12:30:26 +0530 Subject: [PATCH 097/109] tsc --- web/apps/photos/src/components/FileList.tsx | 104 ++++++++++---------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index bcf9d34246..aa05c2d6d8 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -73,7 +73,7 @@ interface TimeStampListItem { tag?: "date" | "file"; items?: FileListAnnotatedFile[]; itemStartIndex?: number; - date?: string; + date?: string | null; dates?: { date: string; span: number }[]; groups?: number[]; item?: any; @@ -225,8 +225,8 @@ export const FileList: React.FC = ({ const [checkedTimelineDateStrings, setCheckedTimelineDateStrings] = useState(new Set()); - const [rangeStart, setRangeStart] = useState(null); - const [currentHover, setCurrentHover] = useState(null); + const [rangeStart, setRangeStart] = useState(null); + const [currentHover, setCurrentHover] = useState(null); const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false); const fittableColumns = getFractionFittableColumns(width); @@ -237,14 +237,11 @@ export const FileList: React.FC = ({ columns = MIN_COLUMNS; skipMerge = true; } + const shrinkRatio = getShrinkRatio(width, columns); const listItemHeight = IMAGE_CONTAINER_MAX_HEIGHT * shrinkRatio + GAP_BTW_TILES; - const refreshList = () => { - listRef.current?.resetAfterIndex(0); - }; - useEffect(() => { // Since width and height are dependencies, there might be too many // updates to the list during a resize. The list computation too, while @@ -302,9 +299,12 @@ export const FileList: React.FC = ({ ]); useEffect(() => { - refreshList(); + // Refresh list. + listRef.current?.resetAfterIndex(0); }, [timeStampList]); + // TODO: Too many non-null assertions + const groupByTime = (timeStampList: TimeStampListItem[]) => { let listItemIndex = 0; let lastCreationTime: number | undefined; @@ -328,7 +328,7 @@ export const FileList: React.FC = ({ }); listItemIndex = 1; } else if (listItemIndex < columns) { - timeStampList[timeStampList.length - 1].items.push(item); + timeStampList[timeStampList.length - 1]!.items!.push(item); listItemIndex++; } else { listItemIndex = 1; @@ -345,7 +345,7 @@ export const FileList: React.FC = ({ let listItemIndex = columns; annotatedFiles.forEach((item, index) => { if (listItemIndex < columns) { - timeStampList[timeStampList.length - 1].items.push(item); + timeStampList[timeStampList.length - 1]!.items!.push(item); listItemIndex++; } else { listItemIndex = 1; @@ -387,7 +387,7 @@ export const FileList: React.FC = ({ let index = 0; let newIndex = 0; while (index < items.length) { - const currItem = items[index]; + const currItem = items[index]!; // If the current item is of type time, then it is not part of an ongoing date. // So, there is a possibility of merge. if (currItem.tag == "date") { @@ -397,21 +397,21 @@ export const FileList: React.FC = ({ const SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO = 0.244; // Check if items can be added to same list if ( - newList[newIndex + 1].items.length + - items[index + 1].items.length + + newList[newIndex + 1]!.items!.length + + items[index + 1]!.items!.length + Math.ceil( - newList[newIndex].dates.length * + newList[newIndex]!.dates!.length * SPACE_BTW_DATES_TO_IMAGE_CONTAINER_WIDTH_RATIO, ) <= columns ) { - newList[newIndex].dates.push({ - date: currItem.date, - span: items[index + 1].items.length, + newList[newIndex]!.dates!.push({ + date: currItem.date!, + span: items[index + 1]!.items!.length, }); - newList[newIndex + 1].items = [ - ...newList[newIndex + 1].items, - ...items[index + 1].items, + newList[newIndex + 1]!.items = [ + ...newList[newIndex + 1]!.items!, + ...items[index + 1]!.items!, ]; index += 2; } else { @@ -427,12 +427,12 @@ export const FileList: React.FC = ({ date: null, dates: [ { - date: currItem.date, - span: items[index + 1].items.length, + date: currItem.date!, + span: items[index + 1]!.items!.length, }, ], }); - newList.push(items[index + 1]); + newList.push(items[index + 1]!); index += 2; } } else { @@ -444,11 +444,11 @@ export const FileList: React.FC = ({ } } for (let i = 0; i < newList.length; i++) { - const currItem = newList[i]; - const nextItem = newList[i + 1]; + const currItem = newList[i]!; + const nextItem = newList[i + 1]!; if (currItem.tag == "date") { - if (currItem.dates.length > 1) { - currItem.groups = currItem.dates.map((item) => item.span); + if (currItem.dates!.length > 1) { + currItem.groups = currItem.dates!.map((item) => item.span); nextItem.groups = currItem.groups; } } @@ -467,14 +467,14 @@ export const FileList: React.FC = ({ } }; - const generateKey = (index) => { - switch (timeStampList[index].tag) { + const generateKey = (index: number) => { + switch (timeStampList[index]!.tag) { case "file": - return `${timeStampList[index].items[0].file.id}-${ - timeStampList[index].items.slice(-1)[0].file.id + return `${timeStampList[index]!.items![0]!.file.id}-${ + timeStampList[index]!.items!.slice(-1)[0]!.file.id }`; default: - return `${timeStampList[index].id}-${index}`; + return `${timeStampList[index]!.id}-${index}`; } }; @@ -558,23 +558,23 @@ export const FileList: React.FC = ({ const handleRangeSelect = (index: number) => () => { if (typeof rangeStart != "undefined" && rangeStart !== index) { const direction = - (index - rangeStart) / Math.abs(index - rangeStart); + (index - rangeStart!) / Math.abs(index - rangeStart!); let checked = true; for ( - let i = rangeStart; + let i = rangeStart!; (index - i) * direction >= 0; i += direction ) { - checked = checked && !!selected[annotatedFiles[i].file.id]; + checked = checked && !!selected[annotatedFiles[i]!.file.id]; } for ( - let i = rangeStart; + let i = rangeStart!; (index - i) * direction > 0; i += direction ) { - handleSelect(annotatedFiles[i].file)(!checked); + handleSelect(annotatedFiles[i]!.file)(!checked); } - handleSelect(annotatedFiles[index].file, index)(!checked); + handleSelect(annotatedFiles[index]!.file, index)(!checked); } }; @@ -616,7 +616,7 @@ export const FileList: React.FC = ({ {...{ user, emailByUserID }} file={file} onClick={() => onItemClick(index)} - selectable={selectable} + selectable={selectable!} onSelect={handleSelect(file, index)} selected={ (!mode @@ -632,8 +632,8 @@ export const FileList: React.FC = ({ onRangeSelect={handleRangeSelect(index)} isRangeSelectActive={isShiftKeyPressed && selected.count > 0} isInsSelectRange={ - (index >= rangeStart && index <= currentHover) || - (index >= currentHover && index <= rangeStart) + (index >= rangeStart! && index <= currentHover!) || + (index >= currentHover! && index <= rangeStart!) } activeCollectionID={activeCollectionID} showPlaceholder={isScrolling} @@ -643,7 +643,7 @@ export const FileList: React.FC = ({ const renderListItem = ( listItem: TimeStampListItem, - isScrolling: boolean, + isScrolling: boolean | undefined, ) => { const haveSelection = (selected.count ?? 0) > 0; switch (listItem.tag) { @@ -676,12 +676,12 @@ export const FileList: React.FC = ({ {haveSelection && ( - onChangeSelectAllCheckBox(listItem.date) + onChangeSelectAllCheckBox(listItem.date!) } size="small" sx={{ pl: 0 }} @@ -691,22 +691,22 @@ export const FileList: React.FC = ({ ); case "file": { - const ret = listItem.items.map((item, idx) => + const ret = listItem.items!.map((item, idx) => getThumbnail( item, - listItem.itemStartIndex + idx, - isScrolling, + listItem.itemStartIndex! + idx, + !!isScrolling, ), ); if (listItem.groups) { let sum = 0; for (let i = 0; i < listItem.groups.length - 1; i++) { - sum = sum + listItem.groups[i]; + sum = sum + listItem.groups[i]!; ret.splice( sum, 0,
      , ); sum += 1; @@ -911,10 +911,10 @@ const PhotoListRow = React.memo( gridTemplateColumns={getTemplateColumns( columns, shrinkRatio, - timeStampList[index].groups, + timeStampList[index]!.groups, )} > - {renderListItem(timeStampList[index], isScrolling)} + {renderListItem(timeStampList[index]!, isScrolling)} ); @@ -935,7 +935,7 @@ type FileThumbnailProps = { isInsSelectRange: boolean; activeCollectionID: number; showPlaceholder: boolean; - isFav: boolean; + isFav: boolean | undefined; } & Pick; const FileThumbnail: React.FC = ({ From 6d20b9cd5516b0a8f8c350ba4da2d376629dc57e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 12:35:20 +0530 Subject: [PATCH 098/109] Elsewhere --- .../components/Collections/AlbumCastDialog.tsx | 1 + .../Collections/GalleryBarAndListHeader.tsx | 10 +++++----- .../photos/src/components/UploadProgress.tsx | 17 ++++++++++------- web/apps/photos/src/pages/gallery.tsx | 10 +++++++--- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index aa1e972358..915fb693fb 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -87,6 +87,7 @@ export const AlbumCastDialogContents: React.FC = ({ // (effectively, only Chrome). // // Override, otherwise tsc complains about unknown property `chrome`. + // @ts-expect-error TODO why is this needed // eslint-disable-next-line @typescript-eslint/dot-notation setBrowserCanCast(typeof window["chrome"] != "undefined"); }, []); diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 1bf7574f06..65c0aeb2bd 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -128,7 +128,7 @@ export const GalleryBarAndListHeader: React.FC< const group = saveGroups.find( (g) => g.collectionSummaryID === activeCollectionID, ); - return group && !isSaveComplete(group) && !isSaveCancelled(group); + return !!group && !isSaveComplete(group) && !isSaveCancelled(group); }, [saveGroups, activeCollectionID]); useEffect(() => { @@ -146,8 +146,8 @@ export const GalleryBarAndListHeader: React.FC< onAddSaveGroup, }} collectionSummary={toShowCollectionSummaries.get( - activeCollectionID, - )} + activeCollectionID!, + )!} onCollectionShare={showCollectionShare} onCollectionCast={showCollectionCast} /> @@ -212,8 +212,8 @@ export const GalleryBarAndListHeader: React.FC< = ({ }; const getItemTitle = (fileID) => { - return uploadFileNames.get(fileID); + return uploadFileNames.get(fileID)!; }; const generateItemKey = (fileID) => { @@ -593,6 +594,8 @@ const createItemData: ( items, })); +// TODO: Too many non-null assertions + // @ts-expect-error "TODO: Understand and fix the type error here" const Row: ({ index, @@ -612,12 +615,12 @@ const Row: ({ ], }, }} - title={getItemTitle(items[index])} + title={getItemTitle(items[index]!)} placement="bottom-start" enterDelay={300} enterNextDelay={100} > -
      {renderListItem(items[index])}
      +
      {renderListItem(items[index]!)}
      ); }, @@ -633,7 +636,7 @@ function ItemList(props: ItemListProps) { const getItemKey: ListItemKeySelector> = (index, data) => { const { items } = data; - return props.generateItemKey(items[index]); + return props.generateItemKey(items[index]!); }; return ( @@ -641,11 +644,11 @@ function ItemList(props: ItemListProps) { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index dbb6f1425c..9a47329755 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -483,6 +483,7 @@ const Page: React.FC = () => { selected.ownCount++; } selected.count++; + // @ts-expect-error Selection code needs type fixing selected[item.id] = true; }); setSelected(selected); @@ -1075,8 +1076,10 @@ const Page: React.FC = () => { { selectable={true} selected={selected} setSelected={setSelected} - activeCollectionID={activeCollectionID} + // TODO: Incorrect assertion, need to update the type + activeCollectionID={activeCollectionID!} activePersonID={activePerson?.id} isInIncomingSharedCollection={activeCollectionSummary?.attributes.has( "sharedIncoming", From 3fb02cf343595e0e46dee58ff5d61f1e0eb29b4b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 12:49:18 +0530 Subject: [PATCH 099/109] Remove overrides --- .../src/components/Collections/AllAlbums.tsx | 10 ++++++- .../Collections/GalleryBarAndListHeader.tsx | 12 ++++---- web/apps/photos/src/components/FileList.tsx | 29 ++++++++++--------- .../photos/src/components/UploadProgress.tsx | 6 ++++ web/apps/photos/src/pages/_app.tsx | 3 +- .../photos/src/services/upload-manager.ts | 1 + web/apps/photos/src/utils/photoFrame/index.ts | 2 ++ web/apps/photos/tsconfig.json | 6 +--- web/packages/accounts/tsconfig.json | 4 --- 9 files changed, 43 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index e0b6bf710f..be7f7e7921 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -101,7 +101,15 @@ const AllAlbumsDialog = styled(Dialog)(({ theme }) => ({ }, })); -const Title = ({ +type TitleProps = { collectionCount: number } & Pick< + AllAlbums, + | "onClose" + | "collectionsSortBy" + | "onChangeCollectionsSortBy" + | "isInHiddenSection" +>; + +const Title: React.FC = ({ onClose, collectionCount, collectionsSortBy, diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 65c0aeb2bd..053e67aee9 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -145,9 +145,9 @@ export const GalleryBarAndListHeader: React.FC< onRemotePull, onAddSaveGroup, }} - collectionSummary={toShowCollectionSummaries.get( - activeCollectionID!, - )!} + collectionSummary={ + toShowCollectionSummaries.get(activeCollectionID!)! + } onCollectionShare={showCollectionShare} onCollectionCast={showCollectionCast} /> @@ -211,9 +211,9 @@ export const GalleryBarAndListHeader: React.FC< /> = ({ }); }; - const getVacuumItem = (timeStampList, footerHeight: number) => { + const getVacuumItem = ( + timeStampList: TimeStampListItem[], + footerHeight: number, + ) => { const fileListHeight = (() => { let sum = 0; const getCurrentItemSize = getItemSize(timeStampList); for (let i = 0; i < timeStampList.length; i++) { - sum += getCurrentItemSize(i); + sum += getCurrentItemSize(i)!; if (height - sum <= footerHeight) { break; } @@ -456,16 +458,17 @@ export const FileList: React.FC = ({ return newList; }; - const getItemSize = (timeStampList) => (index) => { - switch (timeStampList[index].tag) { - case "date": - return dateContainerHeight; - case "file": - return listItemHeight; - default: - return timeStampList[index].height; - } - }; + const getItemSize = + (timeStampList: TimeStampListItem[]) => (index: number) => { + switch (timeStampList[index]!.tag) { + case "date": + return dateContainerHeight; + case "file": + return listItemHeight; + default: + return timeStampList[index]!.height!; + } + }; const generateKey = (index: number) => { switch (timeStampList[index]!.tag) { diff --git a/web/apps/photos/src/components/UploadProgress.tsx b/web/apps/photos/src/components/UploadProgress.tsx index 100cd2b9b8..29a4c436ab 100644 --- a/web/apps/photos/src/components/UploadProgress.tsx +++ b/web/apps/photos/src/components/UploadProgress.tsx @@ -381,6 +381,7 @@ const InProgressSection: React.FC = () => { const fileList = inProgressUploads ?? []; + // @ts-expect-error Need to add types const renderListItem = ({ localFileID, progress }) => { return ( @@ -396,10 +397,12 @@ const InProgressSection: React.FC = () => { ); }; + // @ts-expect-error Need to add types const getItemTitle = ({ localFileID, progress }) => { return `${uploadFileNames.get(localFileID)} - ${progress}%`; }; + // @ts-expect-error Need to add types const generateItemKey = ({ localFileID, progress }) => { return `${localFileID}-${progress}`; }; @@ -501,6 +504,7 @@ const ResultSection: React.FC = ({ return <>; } + // @ts-expect-error Need to add types const renderListItem = (fileID) => { return ( @@ -509,10 +513,12 @@ const ResultSection: React.FC = ({ ); }; + // @ts-expect-error Need to add types const getItemTitle = (fileID) => { return uploadFileNames.get(fileID)!; }; + // @ts-expect-error Need to add types const generateItemKey = (fileID) => { return fileID; }; diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 6531ecd47d..9d7a2b4dcc 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -72,7 +72,8 @@ const App: React.FC = ({ Component, pageProps }) => { void isLocalStorageAndIndexedDBMismatch().then((mismatch) => { if (mismatch) { log.error("Logging out (IndexedDB and local storage mismatch)"); - return logout(); + logout(); + return; } else { return runMigrations(); } diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index e4d0d4e6a1..07ed999501 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -235,6 +235,7 @@ class UIService { } } +// @ts-expect-error Need to add types function convertInProgressUploadsToList(inProgressUploads) { return [...inProgressUploads.entries()].map( ([localFileID, progress]) => diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index d5157febef..e5a73dde20 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -1,3 +1,4 @@ +// TODO: Audit this file import type { SelectionContext } from "ente-new/photos/components/gallery"; import type { GalleryBarMode } from "ente-new/photos/components/gallery/reducer"; import type { SelectedState, SetSelectedState } from "utils/file"; @@ -10,6 +11,7 @@ export const handleSelectCreator = userID: number | undefined, activeCollectionID: number, activePersonID: string | undefined, + // @ts-expect-error Need to add types setRangeStart?, ) => ({ id, ownerID }: { id: number; ownerID: number }, index?: number) => diff --git a/web/apps/photos/tsconfig.json b/web/apps/photos/tsconfig.json index 4b0f70e320..6360403f00 100644 --- a/web/apps/photos/tsconfig.json +++ b/web/apps/photos/tsconfig.json @@ -2,11 +2,7 @@ "extends": "ente-build-config/tsconfig-next.json", "compilerOptions": { /* Set the base directory from which to resolve bare module names. */ - "baseUrl": "./src", - - /* Override tsconfig-next.json (TODO: Remove all of us) */ - "noImplicitAny": false, - "strictNullChecks": false + "baseUrl": "./src" }, "include": [ "next-env.d.ts", diff --git a/web/packages/accounts/tsconfig.json b/web/packages/accounts/tsconfig.json index 032194f233..15dcfedc2b 100644 --- a/web/packages/accounts/tsconfig.json +++ b/web/packages/accounts/tsconfig.json @@ -1,9 +1,5 @@ { "extends": "ente-build-config/tsconfig-next.json", - "compilerOptions": { - /* MUI doesn't work with exactOptionalPropertyTypes yet. */ - "exactOptionalPropertyTypes": false - }, "include": [ ".", "../base/global-electron.d.ts", From 6e025945ae9089c97b078e185e035f5fde800b9c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 13:05:37 +0530 Subject: [PATCH 100/109] es --- web/apps/photos/eslint.config.mjs | 3 --- .../photos/src/components/Collections/AlbumCastDialog.tsx | 3 +++ web/apps/photos/src/components/Collections/AllAlbums.tsx | 5 ++++- web/apps/photos/src/components/UploadProgress.tsx | 7 +++++++ web/apps/photos/src/services/upload-manager.ts | 7 +++++-- web/apps/photos/src/utils/photoFrame/index.ts | 2 ++ web/apps/photos/tests/upload.test.ts | 3 +++ 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index 76d9b9a84c..55cdf1eea7 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -11,12 +11,9 @@ export default [ */ "@typescript-eslint/no-unnecessary-boolean-literal-compare": "off", "@typescript-eslint/no-unnecessary-condition": "off", - "@typescript-eslint/no-unsafe-assignment": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-member-access": "off", - "@typescript-eslint/no-unsafe-argument": "off", - "@typescript-eslint/no-unsafe-call": "off", /** TODO: Disabled as we migrate, try to prune these again */ "@typescript-eslint/no-floating-promises": "off", "@typescript-eslint/no-unnecessary-type-assertion": "off", diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 915fb693fb..9e255bdab5 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -128,7 +128,10 @@ export const AlbumCastDialogContents: React.FC = ({ "urn:x-cast:pair-request", (_, message) => { const data = message; + // TODO: + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const obj = JSON.parse(data); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const code = obj.code; if (code) { diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index be7f7e7921..f7d54286ca 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -162,7 +162,10 @@ interface ItemData { // If we were only passing a single, stable value (e.g. items), // We could just pass the value directly. const createItemData = memoize((collectionRowList, onCollectionClick) => ({ + // TODO: + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment collectionRowList, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment onCollectionClick, })); @@ -182,7 +185,7 @@ const AlbumsRow = React.memo( return (
      - {collectionRow.map((item: any) => ( + {collectionRow.map((item) => ( { // @ts-expect-error Need to add types const renderListItem = ({ localFileID, progress }) => { return ( + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment {uploadFileNames.get(localFileID)} {uploadPhase == "uploading" && ( @@ -507,6 +509,7 @@ const ResultSection: React.FC = ({ // @ts-expect-error Need to add types const renderListItem = (fileID) => { return ( + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment {uploadFileNames.get(fileID)} @@ -595,8 +598,12 @@ const createItemData: ( getItemTitle: (item: T) => string, items: T[], ) => ItemData = memoize((renderListItem, getItemTitle, items) => ({ + // TODO + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment renderListItem, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment getItemTitle, + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment items, })); diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index 07ed999501..7df566064a 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -235,8 +235,7 @@ class UIService { } } -// @ts-expect-error Need to add types -function convertInProgressUploadsToList(inProgressUploads) { +function convertInProgressUploadsToList(inProgressUploads: InProgressUploads) { return [...inProgressUploads.entries()].map( ([localFileID, progress]) => ({ localFileID, progress }) as InProgressUpload, @@ -253,6 +252,7 @@ const groupByResult = (finishedUploads: FinishedUploads) => { }; class UploadManager { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment private comlinkCryptoWorkers: ComlinkWorker[] = new Array(maxConcurrentUploads); private parsedMetadataJSONMap = new Map(); @@ -298,6 +298,7 @@ class UploadManager { ) { this.itemsToBeUploaded = []; this.failedItems = []; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment this.parsedMetadataJSONMap = parsedMetadataJSONMap ?? new Map(); this.uploaderName = undefined; this.shouldUploadBeCancelled = false; @@ -775,7 +776,9 @@ const logAboutMemoryPressureIfNeeded = () => { // is the method recommended by the Electron team (see the link about the V8 // memory cage). The embedded Chromium supports it fine though, we just need // to goad TypeScript to accept the type. + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const heapSize = (performance as any).memory.totalJSHeapSize; + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const heapLimit = (performance as any).memory.jsHeapSizeLimit; if (heapSize / heapLimit > 0.7) { log.info( diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index e5a73dde20..c32a382df1 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -18,8 +18,10 @@ export const handleSelectCreator = (checked: boolean) => { if (typeof index != "undefined") { if (checked) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call setRangeStart(index); } else { + // eslint-disable-next-line @typescript-eslint/no-unsafe-call setRangeStart(undefined); } } diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index 7f1bbcfd7e..87bc447531 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -1,4 +1,7 @@ // TODO: Audit this file +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-unsafe-argument */ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-base-to-string */ /* eslint-disable @typescript-eslint/restrict-template-expressions */ From 96276a123411ff530b34bbf0d044ba64d337f688 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 13:20:11 +0530 Subject: [PATCH 101/109] es --- web/apps/photos/eslint.config.mjs | 4 ---- .../src/components/Collections/AlbumCastDialog.tsx | 4 ++-- .../Collections/GalleryBarAndListHeader.tsx | 2 +- web/apps/photos/src/components/FileList.tsx | 4 +++- web/apps/photos/src/components/UploadProgress.tsx | 1 + web/apps/photos/src/pages/_app.tsx | 9 +++++++-- web/apps/photos/src/services/upload-manager.ts | 12 ++++++------ web/apps/photos/src/utils/photoFrame/index.ts | 8 ++++---- web/apps/photos/tests/upload.test.ts | 1 + web/packages/accounts/pages/credentials.tsx | 2 +- 10 files changed, 26 insertions(+), 21 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index 55cdf1eea7..8765bfa62e 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -9,14 +9,10 @@ export default [ * "This rule requires the `strictNullChecks` compiler option to be * turned on to function correctly" */ - "@typescript-eslint/no-unnecessary-boolean-literal-compare": "off", "@typescript-eslint/no-unnecessary-condition": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-unsafe-return": "off", - "@typescript-eslint/no-unsafe-member-access": "off", /** TODO: Disabled as we migrate, try to prune these again */ "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-unnecessary-type-assertion": "off", "react-hooks/exhaustive-deps": "off", }, }, diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index 9e255bdab5..f1e300335f 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -87,7 +87,7 @@ export const AlbumCastDialogContents: React.FC = ({ // (effectively, only Chrome). // // Override, otherwise tsc complains about unknown property `chrome`. - // @ts-expect-error TODO why is this needed + // @ts-expect-error TODO: why is this needed // eslint-disable-next-line @typescript-eslint/dot-notation setBrowserCanCast(typeof window["chrome"] != "undefined"); }, []); @@ -131,7 +131,7 @@ export const AlbumCastDialogContents: React.FC = ({ // TODO: // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment const obj = JSON.parse(data); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const code = obj.code; if (code) { diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index 053e67aee9..b6be9a1ad9 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -170,7 +170,7 @@ export const GalleryBarAndListHeader: React.FC< activePerson, showCollectionShare, showCollectionCast, - // TODO-Cluster + // TODO: Cluster // This causes a loop since it is an array dep // people, ]); diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index e0f25a98ae..f75bb4da11 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -365,7 +365,7 @@ export const FileList: React.FC = ({ let sum = 0; const getCurrentItemSize = getItemSize(timeStampList); for (let i = 0; i < timeStampList.length; i++) { - sum += getCurrentItemSize(i)!; + sum += getCurrentItemSize(i); if (height - sum <= footerHeight) { break; } @@ -718,6 +718,8 @@ export const FileList: React.FC = ({ return ret; } default: + // TODO: + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return listItem.item; } }; diff --git a/web/apps/photos/src/components/UploadProgress.tsx b/web/apps/photos/src/components/UploadProgress.tsx index e8a4355ac3..d2a2aec71b 100644 --- a/web/apps/photos/src/components/UploadProgress.tsx +++ b/web/apps/photos/src/components/UploadProgress.tsx @@ -523,6 +523,7 @@ const ResultSection: React.FC = ({ // @ts-expect-error Need to add types const generateItemKey = (fileID) => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-return return fileID; }; diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 9d7a2b4dcc..3ae9eb6abe 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -131,9 +131,14 @@ const App: React.FC = ({ Component, pageProps }) => { if (needsFamilyRedirect && savedPartialLocalUser()?.token) redirectToFamilyPortal(); - router.events.on("routeChangeStart", (url) => { + // Creating this inline, we need this on debug only and temporarily. Can + // remove the debug print itself after a while. + interface NROptions { + shallow: boolean; + } + router.events.on("routeChangeStart", (url: string, o: NROptions) => { if (process.env.NEXT_PUBLIC_ENTE_TRACE_RT) { - log.debug(() => ["route", url]); + log.debug(() => [o?.shallow ? "route-shallow" : "route", url]); } if (needsFamilyRedirect && savedPartialLocalUser()?.token) { diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index 7df566064a..f4ff2d8e1c 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -670,11 +670,11 @@ type UploadItemWithCollectionIDAndName = UploadAsset & { const makeUploadItemWithCollectionIDAndName = ( f: UploadItemWithCollection, ): UploadItemWithCollectionIDAndName => ({ - localID: f.localID!, - collectionID: f.collectionID!, - fileName: (f.isLivePhoto + localID: f.localID, + collectionID: f.collectionID, + fileName: f.isLivePhoto ? uploadItemFileName(f.livePhotoAssets!.image) - : uploadItemFileName(f.uploadItem!))!, + : uploadItemFileName(f.uploadItem!), isLivePhoto: f.isLivePhoto, uploadItem: f.uploadItem, pathPrefix: f.pathPrefix, @@ -776,9 +776,9 @@ const logAboutMemoryPressureIfNeeded = () => { // is the method recommended by the Electron team (see the link about the V8 // memory cage). The embedded Chromium supports it fine though, we just need // to goad TypeScript to accept the type. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const heapSize = (performance as any).memory.totalJSHeapSize; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const heapLimit = (performance as any).memory.jsHeapSizeLimit; if (heapSize / heapLimit > 0.7) { log.info( diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index c32a382df1..96ed15fa6c 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -139,7 +139,7 @@ const createSelectedAndContext = ( context: mode == "people" ? { mode, personID: activePersonID! } - : { mode, collectionID: activeCollectionID! }, + : { mode, collectionID: activeCollectionID }, }; } else { // Both mode and context are defined. @@ -152,7 +152,7 @@ const createSelectedAndContext = ( context: mode == "people" ? { mode, personID: activePersonID! } - : { mode, collectionID: activeCollectionID! }, + : { mode, collectionID: activeCollectionID }, }; } else { if (selected.context?.mode == "people") { @@ -177,7 +177,7 @@ const createSelectedAndContext = ( collectionID: 0, context: { mode: selected.context?.mode, - collectionID: activeCollectionID!, + collectionID: activeCollectionID, }, }; } @@ -189,7 +189,7 @@ const createSelectedAndContext = ( ? undefined : mode == "people" ? { mode, personID: activePersonID! } - : { mode, collectionID: activeCollectionID! }; + : { mode, collectionID: activeCollectionID }; return { selected, newContext }; }; diff --git a/web/apps/photos/tests/upload.test.ts b/web/apps/photos/tests/upload.test.ts index 87bc447531..9a2d2ddfe0 100644 --- a/web/apps/photos/tests/upload.test.ts +++ b/web/apps/photos/tests/upload.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ // TODO: Audit this file /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 2fa37c73c4..aab0ae6d1b 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -202,7 +202,7 @@ const Page: React.FC = () => { // generated interactive key attributes to verify password. if (keyAttributes) { if (!user.token && !user.encryptedToken) { - // TODO(RE): Why? For now, add a dev mode circuit breaker. + // TODO: Why? For now, add a dev mode circuit breaker. if (isDevBuild) throw new Error("Unexpected case reached"); clearLocalStorage(); void router.replace("/"); From 6221f904e4e3210e3f04b17e6250124af2572e42 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 14:25:50 +0530 Subject: [PATCH 102/109] lint --- web/apps/photos/eslint.config.mjs | 1 - web/apps/photos/src/components/FileList.tsx | 1 + web/apps/photos/src/services/upload-manager.ts | 12 ++++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index 8765bfa62e..d17d12eed0 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -10,7 +10,6 @@ export default [ * turned on to function correctly" */ "@typescript-eslint/no-unnecessary-condition": "off", - "@typescript-eslint/no-explicit-any": "off", /** TODO: Disabled as we migrate, try to prune these again */ "@typescript-eslint/no-floating-promises": "off", "react-hooks/exhaustive-deps": "off", diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index f75bb4da11..f8071fd598 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -75,6 +75,7 @@ interface TimeStampListItem { date?: string | null; dates?: { date: string; span: number }[]; groups?: number[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any item?: any; id?: string; height?: number; diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index f4ff2d8e1c..34680f1873 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -771,15 +771,19 @@ const clusterLivePhotos = async ( */ const logAboutMemoryPressureIfNeeded = () => { if (!globalThis.electron) return; + // performance.memory is deprecated in general as a Web standard, and is // also not available in the DOM types provided by TypeScript. However, it // is the method recommended by the Electron team (see the link about the V8 // memory cage). The embedded Chromium supports it fine though, we just need // to goad TypeScript to accept the type. - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const heapSize = (performance as any).memory.totalJSHeapSize; - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const heapLimit = (performance as any).memory.jsHeapSizeLimit; + + const { memory } = performance as unknown as { + memory: { totalJSHeapSize: number; jsHeapSizeLimit: number }; + }; + + const heapSize = memory.totalJSHeapSize; + const heapLimit = memory.jsHeapSizeLimit; if (heapSize / heapLimit > 0.7) { log.info( `Memory usage (${heapSize} bytes of ${heapLimit} bytes) exceeds the high water mark`, From aa80f86a7a9ea2aa96589d36519e62a583b5ece8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 14:31:29 +0530 Subject: [PATCH 103/109] More lints --- web/apps/photos/eslint.config.mjs | 1 - .../Collections/AlbumCastDialog.tsx | 39 ++++++++-------- .../Collections/CollectionShare.tsx | 46 ++++++++++--------- .../DownloadStatusNotifications.tsx | 2 +- .../src/components/FileListWithViewer.tsx | 2 +- web/apps/photos/src/components/Sidebar.tsx | 35 ++++++++------ web/apps/photos/src/components/Upload.tsx | 1 + .../photos/src/components/WatchFolder.tsx | 6 +-- web/apps/photos/src/pages/_app.tsx | 7 ++- web/apps/photos/src/pages/gallery.tsx | 3 ++ web/apps/photos/src/pages/shared-albums.tsx | 3 +- web/apps/photos/src/services/watch.ts | 10 ++-- 12 files changed, 90 insertions(+), 65 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index d17d12eed0..3da791ba73 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -11,7 +11,6 @@ export default [ */ "@typescript-eslint/no-unnecessary-condition": "off", /** TODO: Disabled as we migrate, try to prune these again */ - "@typescript-eslint/no-floating-promises": "off", "react-hooks/exhaustive-deps": "off", }, }, diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index f1e300335f..d81658cac5 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -20,6 +20,7 @@ import { loadCast } from "ente-new/photos/utils/chromecast-sender"; import { t } from "i18next"; import React, { useCallback, useEffect, useState } from "react"; import { Trans } from "react-i18next"; +import { z } from "zod/v4"; type AlbumCastDialogProps = ModalVisibilityProps & { /** The collection that we want to cast. */ @@ -114,7 +115,7 @@ export const AlbumCastDialogContents: React.FC = ({ useEffect(() => { if (view == "auto") { - loadCast().then(async (cast) => { + void loadCast().then(async (cast) => { const instance = cast.framework.CastContext.getInstance(); try { await instance.requestSession(); @@ -127,29 +128,24 @@ export const AlbumCastDialogContents: React.FC = ({ session.addMessageListener( "urn:x-cast:pair-request", (_, message) => { - const data = message; - // TODO: - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const obj = JSON.parse(data); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access - const code = obj.code; + const { code } = CastPairRequest.parse( + JSON.parse(message), + ); - if (code) { - publishCastPayload(`${code}`, collection) - .then(() => { - setView("choose"); - onClose(); - }) - .catch((e: unknown) => { - log.error("Error casting to TV", e); - setView("auto-cast-error"); - }); - } + void publishCastPayload(code, collection) + .then(() => { + setView("choose"); + onClose(); + }) + .catch((e: unknown) => { + log.error("Error casting to TV", e); + setView("auto-cast-error"); + }); }, ); const collectionID = collection.id; - session + void session .sendMessage("urn:x-cast:pair-request", { collectionID }) .then(() => { log.debug(() => "urn:x-cast:pair-request sent"); @@ -252,3 +248,8 @@ export const AlbumCastDialogContents: React.FC = ({ ); }; + +/** + * Zod schema for the "x-cast:pair-request" payload sent by the cast app. + */ +const CastPairRequest = z.object({ code: z.string() }); diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index e09fe29fb4..8e3a05ae8c 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -627,7 +627,19 @@ const AddParticipantForm: React.FC = ({ }); const resetExistingSelection = () => - formik.setFieldValue("selectedEmails", []); + void formik.setFieldValue("selectedEmails", []); + + const createToggleEmail = (email: string) => { + return () => { + const emails = formik.values.selectedEmails; + void formik.setFieldValue( + "selectedEmails", + emails.includes(email) + ? emails.filter((e) => e != email) + : emails.concat(email), + ); + }; + }; return (
      @@ -661,18 +673,7 @@ const AddParticipantForm: React.FC = ({ { - const emails = - formik.values.selectedEmails; - formik.setFieldValue( - "selectedEmails", - emails.includes(email) - ? emails.filter( - (e) => e != email, - ) - : emails.concat(email), - ); - }} + onClick={createToggleEmail(email)} label={email} startIcon={ = ({ const selectAndManageParticipant = useCallback( (email: string) => { setSelectedParticipant( - collection.sharees.find((sharee) => sharee.email === email), + collection.sharees.find((sharee) => sharee.email == email), ); showManageParticipant(); }, - [showManageParticipant], + [collection, showManageParticipant], ); const handleRootClose = () => { @@ -1120,16 +1121,17 @@ const PublicShare: React.FC = ({ useEffect(() => { if (publicURL?.url) { - appendCollectionKeyToShareURL(publicURL.url, collection.key).then( - (url) => setResolvedURL(url), - ); + void appendCollectionKeyToShareURL( + publicURL.url, + collection.key, + ).then((url) => setResolvedURL(url)); } else { setResolvedURL(undefined); } }, [publicURL]); const handleCopyLink = () => { - if (resolvedURL) navigator.clipboard.writeText(resolvedURL); + if (resolvedURL) void navigator.clipboard.writeText(resolvedURL); }; return ( @@ -1475,7 +1477,7 @@ const ManagePublicCollect: React.FC = ({ onUpdate, }) => { const handleFileDownloadSetting = () => { - onUpdate({ enableCollect: !publicURL.enableCollect }); + void onUpdate({ enableCollect: !publicURL.enableCollect }); }; return ( @@ -1666,7 +1668,9 @@ const ManageDownloadAccess: React.FC = ({ }, }); } else { - onUpdate({ enableDownload: true }); + // TODO: Various calls to onUpdate return promises. The UI should + // handle the in-progress states where needed. + void onUpdate({ enableDownload: true }); } }; diff --git a/web/apps/photos/src/components/DownloadStatusNotifications.tsx b/web/apps/photos/src/components/DownloadStatusNotifications.tsx index f7a667463e..40d8f7a28d 100644 --- a/web/apps/photos/src/components/DownloadStatusNotifications.tsx +++ b/web/apps/photos/src/components/DownloadStatusNotifications.tsx @@ -75,7 +75,7 @@ export const DownloadStatusNotifications: React.FC< const createOnClick = (group: SaveGroup) => () => { const electron = globalThis.electron; if (electron && group.downloadDirPath) { - electron.openDirectory(group.downloadDirPath); + void electron.openDirectory(group.downloadDirPath); } else if (onShowCollectionSummary) { onShowCollectionSummary( group.collectionSummaryID, diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 92c81df25f..13ebc7c5c9 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -167,7 +167,7 @@ export const FileListWithViewer: React.FC = ({ (editedFile: File, collection: Collection, enteFile: EnteFile) => { uploadManager.prepareForNewUpload(); uploadManager.showUploadProgressDialog(); - uploadManager.uploadFile(editedFile, collection, enteFile); + void uploadManager.uploadFile(editedFile, collection, enteFile); }, [], ); diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index eeb8a1f2ca..308334a4ac 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -265,6 +265,10 @@ const UserDetailsSection: React.FC = ({ isSubscriptionStripe(userDetails.subscription) && isSubscriptionPastDue(userDetails.subscription) ) { + // TODO: This makes an API request, so the UI should indicate + // the await. + // + // eslint-disable-next-line @typescript-eslint/no-floating-promises redirectToCustomerPortal(); } else { onShowPlanSelector(); @@ -337,6 +341,7 @@ const SubscriptionStatus: React.FC = ({ isSubscriptionStripe(userDetails.subscription) && isSubscriptionPastDue(userDetails.subscription) ) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises redirectToCustomerPortal(); } else { onShowPlanSelector(); @@ -851,16 +856,17 @@ const LanguageSelector = () => { const locale = getLocaleInUse(); const updateCurrentLocale = (newLocale: SupportedLocale) => { - setLocaleInUse(newLocale); - // [Note: Changing locale causes a full reload] - // - // A full reload is needed because we use the global `t` instance - // instead of the useTranslation hook. - // - // We also rely on this behaviour by caching various formatters in - // module static variables that not get updated if the i18n.language - // changes unless there is a full reload. - window.location.reload(); + void setLocaleInUse(newLocale).then(() => { + // [Note: Changing locale causes a full reload] + // + // A full reload is needed because we use the global `t` instance + // instead of the useTranslation hook. + // + // We also rely on this behaviour by caching various formatters in + // module static variables that not get updated if the i18n.language + // changes unless there is a full reload. + window.location.reload(); + }); }; const options = supportedLocales.map((locale) => ({ @@ -1088,11 +1094,14 @@ const Help: React.FC = ({ continue: { text: t("view_logs"), action: viewLogs }, }); - const viewLogs = () => { + const viewLogs = async () => { log.info("Viewing logs"); const electron = globalThis.electron; - if (electron) electron.openLogDirectory(); - else saveStringAsFile(savedLogs(), `ente-web-logs-${Date.now()}.txt`); + if (electron) { + await electron.openLogDirectory(); + } else { + saveStringAsFile(savedLogs(), `ente-web-logs-${Date.now()}.txt`); + } }; return ( diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 610df422da..30b31cc8d3 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -1,6 +1,7 @@ // TODO: Too many null assertions in this file. The types need reworking. // TODO: Audit this file /* eslint-disable @typescript-eslint/no-misused-promises */ +/* eslint-disable @typescript-eslint/no-floating-promises */ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import DiscFullIcon from "@mui/icons-material/DiscFull"; import GoogleIcon from "@mui/icons-material/Google"; diff --git a/web/apps/photos/src/components/WatchFolder.tsx b/web/apps/photos/src/components/WatchFolder.tsx index f7c46085be..164db56c89 100644 --- a/web/apps/photos/src/components/WatchFolder.tsx +++ b/web/apps/photos/src/components/WatchFolder.tsx @@ -53,7 +53,7 @@ export const WatchFolder: React.FC = ({ useModalVisibility(); useEffect(() => { - watcher.getWatches().then((ws) => setWatches(ws)); + void watcher.getWatches().then((ws) => setWatches(ws)); }, []); useEffect(() => { @@ -87,7 +87,7 @@ export const WatchFolder: React.FC = ({ const selectCollectionMappingAndAddWatch = async (path: string) => { const filePaths = await ensureElectron().fs.findFiles(path); if (areAllInSameDirectory(filePaths)) { - addWatch(path, "root"); + await addWatch(path, "root"); } else { setSavedFolderPath(path); showMappingChoice(); @@ -109,7 +109,7 @@ export const WatchFolder: React.FC = ({ const handleCollectionMappingSelect = (mapping: CollectionMapping) => { setSavedFolderPath(undefined); - addWatch(savedFolderPath!, mapping); + void addWatch(savedFolderPath!, mapping); }; return ( diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 3ae9eb6abe..9310e89365 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -89,8 +89,11 @@ const App: React.FC = ({ Component, pageProps }) => { // the user is logged in. const handleOpenEnteURL = (url: string) => { - if (url.startsWith("ente://app")) router.push(url); - else log.info(`Ignoring unhandled open request for URL ${url}`); + if (url.startsWith("ente://app")) { + void router.push(url); + } else { + log.info(`Ignoring unhandled open request for URL ${url}`); + } }; const showUpdateDialog = (update: AppUpdate) => { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 9a47329755..b471f67846 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1,3 +1,6 @@ +// TODO: Audit this file (the code here is mostly fine, but needs revisiting +// the file it depends on have been audited and their interfaces fixed). +/* eslint-disable @typescript-eslint/no-floating-promises */ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; import MenuIcon from "@mui/icons-material/Menu"; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 5ae52db264..cd6b842e7f 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -1,4 +1,5 @@ -// TODO: Audit this file (too many null assertions) +// TODO: Audit this file (too many null assertions + other issues) +/* eslint-disable @typescript-eslint/no-floating-promises */ import AddPhotoAlternateOutlinedIcon from "@mui/icons-material/AddPhotoAlternateOutlined"; import CloseIcon from "@mui/icons-material/Close"; import DownloadIcon from "@mui/icons-material/Download"; diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index 536805347b..f2fec3a5dd 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -96,7 +96,7 @@ class FolderWatcher { this.upload = upload; this.onTriggerRemotePull = onTriggerRemotePull; this.registerListeners(); - this.syncWithDisk(); + this.triggerSyncWithDisk(); } /** Return `true` if we are currently using the uploader. */ @@ -126,7 +126,7 @@ class FolderWatcher { */ resumePausedSync() { this.isPaused = false; - this.syncWithDisk(); + this.triggerSyncWithDisk(); } /** Return the list of folders we are watching for changes. */ @@ -152,7 +152,7 @@ class FolderWatcher { */ async addWatch(folderPath: string, mapping: CollectionMapping) { const watches = await ensureElectron().watch.add(folderPath, mapping); - this.syncWithDisk(); + this.triggerSyncWithDisk(); return watches; } @@ -165,6 +165,10 @@ class FolderWatcher { return await ensureElectron().watch.remove(folderPath); } + private triggerSyncWithDisk() { + void this.syncWithDisk(); + } + private async syncWithDisk() { try { const watches = await this.getWatches(); From ae28125a7aede812b145eba5b2b880ab31e46391 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 15:13:43 +0530 Subject: [PATCH 104/109] Update more --- web/apps/photos/eslint.config.mjs | 1 - .../src/components/Collections/AllAlbums.tsx | 4 +-- .../Collections/CollectionHeader.tsx | 10 +++--- .../Collections/CollectionShare.tsx | 2 ++ .../DownloadStatusNotifications.tsx | 6 +--- web/apps/photos/src/components/FileList.tsx | 35 +++++++++++++------ .../src/components/FileListWithViewer.tsx | 2 +- web/apps/photos/src/components/Sidebar.tsx | 2 +- .../src/components/SubscriptionCard.tsx | 4 +-- web/apps/photos/src/components/Upload.tsx | 17 ++++----- .../photos/src/components/UploadProgress.tsx | 6 ++-- web/apps/photos/src/pages/_app.tsx | 2 +- web/apps/photos/src/pages/gallery.tsx | 10 +++--- web/apps/photos/src/pages/shared-albums.tsx | 12 +++---- .../photos/src/services/upload-manager.ts | 16 ++++----- web/apps/photos/src/services/watch.ts | 1 - web/apps/photos/src/utils/file/index.ts | 23 ------------ web/apps/photos/src/utils/photoFrame/index.ts | 6 ++-- web/packages/new/photos/services/export.ts | 4 +-- 19 files changed, 70 insertions(+), 93 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index 3da791ba73..c4e97c3d6a 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -9,7 +9,6 @@ export default [ * "This rule requires the `strictNullChecks` compiler option to be * turned on to function correctly" */ - "@typescript-eslint/no-unnecessary-condition": "off", /** TODO: Disabled as we migrate, try to prune these again */ "react-hooks/exhaustive-deps": "off", }, diff --git a/web/apps/photos/src/components/Collections/AllAlbums.tsx b/web/apps/photos/src/components/Collections/AllAlbums.tsx index f7d54286ca..aeb1797e35 100644 --- a/web/apps/photos/src/components/Collections/AllAlbums.tsx +++ b/web/apps/photos/src/components/Collections/AllAlbums.tsx @@ -1,3 +1,4 @@ +// TODO: Audit this file. import CloseIcon from "@mui/icons-material/Close"; import { Box, @@ -225,9 +226,6 @@ const AllAlbumsContent: React.FC = ({ 32; /* padding above first and below last row */ useEffect(() => { - if (!collectionSummaries) { - return; - } const main = () => { if (refreshInProgress.current) { shouldRefresh.current = true; diff --git a/web/apps/photos/src/components/Collections/CollectionHeader.tsx b/web/apps/photos/src/components/Collections/CollectionHeader.tsx index 91790a6a97..8ed21cdab9 100644 --- a/web/apps/photos/src/components/Collections/CollectionHeader.tsx +++ b/web/apps/photos/src/components/Collections/CollectionHeader.tsx @@ -15,7 +15,6 @@ import UnarchiveIcon from "@mui/icons-material/Unarchive"; import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import { Box, IconButton, Menu, Stack, Tooltip } from "@mui/material"; -import { assertionFailed } from "ente-base/assert"; import { SpacedRow } from "ente-base/components/containers"; import { ActivityIndicator } from "ente-base/components/mui/ActivityIndicator"; import { @@ -63,6 +62,7 @@ import { Trans } from "react-i18next"; export interface CollectionHeaderProps { collectionSummary: CollectionSummary; + // TODO: This can be undefined activeCollection: Collection; setActiveCollectionID: (collectionID: number) => void; isActiveCollectionDownloadInProgress: () => boolean; @@ -86,10 +86,6 @@ export interface CollectionHeaderProps { */ export const CollectionHeader: React.FC = (props) => { const { collectionSummary } = props; - if (!collectionSummary) { - assertionFailed("Gallery/CollectionHeader without a collection"); - return <>; - } const { name, type, attributes, fileCount } = collectionSummary; @@ -548,6 +544,8 @@ const CollectionHeaderOptions: React.FC = ({ {...albumNameInputVisibilityProps} title={t("rename_album")} label={t("album_name")} + // TODO: Need to ensure this cannot be undefined when we reach here + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition initialValue={activeCollection?.name} submitButtonColor="primary" submitButtonTitle={t("rename")} @@ -735,7 +733,7 @@ const CollectionSortOrderMenu: React.FC = ({ return ( { - group?.canceller.abort(); + group.canceller.abort(); onRemoveSaveGroup(group); }, }, @@ -86,10 +86,6 @@ export const DownloadStatusNotifications: React.FC< } }; - if (!saveGroups) { - return <>; - } - return saveGroups.map((group, index) => ( = ({ }; useEffect(() => { - // Nothing to do here if nothing is selected. - if (!selected) return; - - const notSelectedFiles = (annotatedFiles ?? []).filter( + const notSelectedFiles = annotatedFiles.filter( (item) => !selected[item.file.id], ); @@ -494,7 +491,7 @@ export const FileList: React.FC = ({ notSelectedFiles.map((item) => item.timelineDateString), ); // to get file's date which were manually unselected - const localSelectedFiles = (annotatedFiles ?? []).filter( + const localSelectedFiles = annotatedFiles.filter( // to get files which were manually selected (item) => !unselectedDates.has(item.timelineDateString), ); @@ -535,7 +532,7 @@ export const FileList: React.FC = ({ } setCheckedTimelineDateStrings(next); - const filesOnADay = annotatedFiles?.filter( + const filesOnADay = annotatedFiles.filter( (item) => item.timelineDateString === date, ); // all files on a checked/unchecked day @@ -649,7 +646,7 @@ export const FileList: React.FC = ({ listItem: TimeStampListItem, isScrolling: boolean | undefined, ) => { - const haveSelection = (selected.count ?? 0) > 0; + const haveSelection = selected.count > 0; switch (listItem.tag) { case "date": return listItem.dates ? ( @@ -725,7 +722,7 @@ export const FileList: React.FC = ({ } }; - if (!timeStampList?.length) { + if (!timeStampList.length) { return <>; } @@ -1007,13 +1004,13 @@ const FileThumbnail: React.FC = ({ onSelect(!selected); } } else if (imageURL) { - onClick?.(); + onClick(); } }; const handleSelect: React.ChangeEventHandler = (e) => { if (isRangeSelectActive) { - onRangeSelect?.(); + onRangeSelect(); } else { onSelect(e.target.checked); } @@ -1270,3 +1267,19 @@ const VideoDurationOverlay: React.FC = ({ )} ); + +/** + * Return `true` if the owner or uploader name avatar indicator should be shown + * for the given {@link file}. + */ +const shouldShowAvatar = (file: EnteFile, user: LocalUser | undefined) => { + // Public albums app. + if (!user) return false; + // A file shared with the user. + if (file.ownerID != user.id) return true; + // A public collected file (i.e. a file owned by the user, uploaded by an + // named guest via a public collect link) + if (file.pubMagicMetadata?.data.uploaderName) return true; + // Regular file. + return false; +}; diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index 13ebc7c5c9..df253a1a62 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -159,7 +159,7 @@ export const FileListWithViewer: React.FC = ({ const handleDelete = useMemo(() => { return onMarkTempDeleted ? (file: EnteFile) => - moveToTrash([file]).then(() => onMarkTempDeleted?.([file])) + moveToTrash([file]).then(() => onMarkTempDeleted([file])) : undefined; }, [onMarkTempDeleted]); diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index 308334a4ac..ed2ee646e4 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -364,7 +364,7 @@ const SubscriptionStatus: React.FC = ({ message = t("subscription_info_free"); } else if (isSubscriptionCancelled(userDetails.subscription)) { message = t("subscription_info_renewal_cancelled", { - date: userDetails.subscription?.expiryTime, + date: userDetails.subscription.expiryTime, }); } } else { diff --git a/web/apps/photos/src/components/SubscriptionCard.tsx b/web/apps/photos/src/components/SubscriptionCard.tsx index abf26b8190..f695207da4 100644 --- a/web/apps/photos/src/components/SubscriptionCard.tsx +++ b/web/apps/photos/src/components/SubscriptionCard.tsx @@ -233,7 +233,7 @@ const IndividualUsageSection: React.FC = ({ {`${formattedStorageByteSize(storage - usage)} ${t("free")}`} - {t("photos_count", { count: fileCount ?? 0 })} + {t("photos_count", { count: fileCount })} @@ -326,7 +326,7 @@ const FamilyUsageSection: React.FC = ({ - {t("photos_count", { count: fileCount ?? 0 })} + {t("photos_count", { count: fileCount })} diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 30b31cc8d3..1626d770df 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -188,9 +188,9 @@ export const Upload: React.FC = ({ finished: 0, total: 0, }); - const [inProgressUploads, setInProgressUploads] = useState< - InProgressUpload[] - >([]); + const [inProgressUploads, setInProgressUploads] = useState( + new Array(), + ); const [finishedUploads, setFinishedUploads] = useState(new Map()); const [percentComplete, setPercentComplete] = useState(0); @@ -853,7 +853,7 @@ export const Upload: React.FC = ({ const handleCollectionMappingSelect = (mapping: CollectionMapping) => uploadFilesToNewCollections( mapping, - importSuggestion.rootFolderName ?? + importSuggestion.rootFolderName || t("autogenerated_default_album_name"), ); @@ -1152,15 +1152,10 @@ const UploadTypeSelector: React.FC = ({ } }, [open, publicAlbumsCredentials]); - const handleClose: DialogProps["onClose"] = (_, reason) => { + const handleClose: DialogProps["onClose"] = () => { // Disable backdrop clicks and esc keypresses if a selection is pending // processing so that the user doesn't inadvertently close the dialog. - if ( - pendingUploadType && - (reason == "backdropClick" || reason == "escapeKeyDown") - ) { - return; - } + if (pendingUploadType) return; onClose(); }; diff --git a/web/apps/photos/src/components/UploadProgress.tsx b/web/apps/photos/src/components/UploadProgress.tsx index d2a2aec71b..f5366faeeb 100644 --- a/web/apps/photos/src/components/UploadProgress.tsx +++ b/web/apps/photos/src/components/UploadProgress.tsx @@ -380,7 +380,7 @@ const InProgressSection: React.FC = () => { const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadPhase } = useUploadProgressContext(); - const fileList = inProgressUploads ?? []; + const fileList = inProgressUploads; // @ts-expect-error Need to add types const renderListItem = ({ localFileID, progress }) => { @@ -414,7 +414,7 @@ const InProgressSection: React.FC = () => { }> @@ -530,7 +530,7 @@ const ResultSection: React.FC = ({ return ( }> - + {sectionInfo && {sectionInfo}} diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index 9310e89365..ae009c7ec8 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -141,7 +141,7 @@ const App: React.FC = ({ Component, pageProps }) => { } router.events.on("routeChangeStart", (url: string, o: NROptions) => { if (process.env.NEXT_PUBLIC_ENTE_TRACE_RT) { - log.debug(() => [o?.shallow ? "route-shallow" : "route", url]); + log.debug(() => [o.shallow ? "route-shallow" : "route", url]); } if (needsFamilyRedirect && savedPartialLocalUser()?.token) { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index b471f67846..81b8bd509e 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -452,7 +452,7 @@ const Page: React.FC = () => { // - We haven't fetched the user yet; !user || // - There is nothing to select; - !filteredFiles?.length || + !filteredFiles.length || // - Any of the modals are open. uploadTypeSelectorView || openCollectionSelector || @@ -493,7 +493,7 @@ const Page: React.FC = () => { }; const clearSelection = () => { - if (!selected?.count) { + if (!selected.count) { return; } setSelected({ @@ -1094,7 +1094,7 @@ const Page: React.FC = () => { emailByUserID={state.emailByUserID} shareSuggestionEmails={state.shareSuggestionEmails} people={ - (state?.view?.type == "people" + (state.view?.type == "people" ? state.view.visiblePeople : undefined) ?? [] } @@ -1152,7 +1152,7 @@ const Page: React.FC = () => { /> ) : !isInSearchMode && !isFirstLoad && - state?.view?.type == "people" && + state.view?.type == "people" && !state.view.activePerson ? ( ) : ( @@ -1338,7 +1338,7 @@ const handleSubscriptionCompletionRedirectIfNeeded = async ( message: ( ), continue: { text: t("ok") }, diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index cd6b842e7f..911d48e312 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -216,7 +216,7 @@ export default function PublicCollectionGallery() { }, []); const downloadEnabled = - publicCollection?.publicURLs?.[0]?.enableDownload ?? true; + publicCollection?.publicURLs[0]?.enableDownload ?? true; /** * Pull the latest data related to the public album from remote, updating @@ -351,7 +351,7 @@ export default function PublicCollectionGallery() { }; const clearSelection = () => { - if (!selected?.count) { + if (!selected.count) { return; } setSelected({ @@ -400,6 +400,7 @@ export default function PublicCollectionGallery() { {...{ publicCollection, publicFiles, + downloadEnabled, onAddSaveGroup, }} /> @@ -407,7 +408,7 @@ export default function PublicCollectionGallery() { height: fileListHeaderHeight, } : undefined, - [onAddSaveGroup, publicCollection, publicFiles], + [onAddSaveGroup, publicCollection, publicFiles, downloadEnabled], ); const fileListFooter = useMemo(() => { @@ -621,6 +622,7 @@ const SelectedFileOptions: React.FC = ({ interface FileListHeaderProps { publicCollection: Collection; publicFiles: EnteFile[]; + downloadEnabled: boolean; onAddSaveGroup: AddSaveGroup; } @@ -638,11 +640,9 @@ const fileListHeaderHeight = 68; const FileListHeader: React.FC = ({ publicCollection, publicFiles, + downloadEnabled, onAddSaveGroup, }) => { - const downloadEnabled = - publicCollection.publicURLs?.[0]?.enableDownload ?? true; - const downloadAllFiles = () => downloadAndSaveCollectionFiles( publicCollection.name, diff --git a/web/apps/photos/src/services/upload-manager.ts b/web/apps/photos/src/services/upload-manager.ts index 34680f1873..b8ef90991b 100644 --- a/web/apps/photos/src/services/upload-manager.ts +++ b/web/apps/photos/src/services/upload-manager.ts @@ -1,3 +1,4 @@ +// TODO: Audit this file // TODO: Too many null assertions in this file. The types need reworking. import { ensureLocalUser } from "ente-accounts/services/user"; import { isDesktop } from "ente-base/app"; @@ -204,15 +205,14 @@ class UIService { let percentComplete = this.perFileProgress * (this.finishedUploads.size || this.filesUploadedCount); - if (this.inProgressUploads) { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for (const [_, progress] of this.inProgressUploads) { - // filter negative indicator values during percentComplete calculation - if (progress < 0) { - continue; - } - percentComplete += (this.perFileProgress * progress) / 100; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for (const [_, progress] of this.inProgressUploads) { + // filter negative indicator values during percentComplete calculation + if (progress < 0) { + continue; } + percentComplete += (this.perFileProgress * progress) / 100; } setPercentComplete(percentComplete); diff --git a/web/apps/photos/src/services/watch.ts b/web/apps/photos/src/services/watch.ts index f2fec3a5dd..45a66c9bbf 100644 --- a/web/apps/photos/src/services/watch.ts +++ b/web/apps/photos/src/services/watch.ts @@ -172,7 +172,6 @@ class FolderWatcher { private async syncWithDisk() { try { const watches = await this.getWatches(); - if (!watches) return; this.eventQueue = []; const events = await deduceEvents(watches); diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 326cfc7ad1..6482c3dd63 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -1,4 +1,3 @@ -import type { LocalUser } from "ente-accounts/services/user"; import type { AddSaveGroup } from "ente-gallery/components/utils/save-groups"; import { downloadAndSaveFiles } from "ente-gallery/services/save"; import type { EnteFile } from "ente-media/file"; @@ -44,28 +43,6 @@ export function getSelectedFiles( return files.filter((file) => selectedFilesIDs.has(file.id)); } -export const shouldShowAvatar = ( - file: EnteFile, - user: LocalUser | undefined, -) => { - if (!file || !user) { - return false; - } - // is Shared file - else if (file.ownerID !== user.id) { - return true; - } - // is public collected file - else if ( - file.ownerID === user.id && - file.pubMagicMetadata?.data?.uploaderName - ) { - return true; - } else { - return false; - } -}; - export const performFileOp = async ( op: FileOp, files: EnteFile[], diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index 96ed15fa6c..5680bd0309 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -155,7 +155,7 @@ const createSelectedAndContext = ( : { mode, collectionID: activeCollectionID }, }; } else { - if (selected.context?.mode == "people") { + if (selected.context.mode == "people") { if (selected.context.personID != activePersonID) { // Clear selection if person has changed. selected = { @@ -163,7 +163,7 @@ const createSelectedAndContext = ( count: 0, collectionID: 0, context: { - mode: selected.context?.mode, + mode: selected.context.mode, personID: activePersonID!, }, }; @@ -176,7 +176,7 @@ const createSelectedAndContext = ( count: 0, collectionID: 0, context: { - mode: selected.context?.mode, + mode: selected.context.mode, collectionID: activeCollectionID, }, }; diff --git a/web/packages/new/photos/services/export.ts b/web/packages/new/photos/services/export.ts index 33d24d9779..4654866fdb 100644 --- a/web/packages/new/photos/services/export.ts +++ b/web/packages/new/photos/services/export.ts @@ -172,7 +172,7 @@ interface CancellationStatus { class ExportService { private exportSettings: ExportSettings | undefined; // @ts-ignore - private exportInProgress: RequestCanceller = null; + private exportInProgress: RequestCanceller | null = null; private resync = true; private reRunNeeded = false; private exportRecordUpdater = new PromiseQueue(); @@ -336,7 +336,7 @@ class ExportService { async stopRunningExport() { try { log.info("user requested export cancellation"); - this.exportInProgress.exec(); + this.exportInProgress?.exec(); // @ts-ignore this.exportInProgress = null; this.reRunNeeded = false; From bfd0bc116daac6d09a1fd8a8da865f5e9dbb30fb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 16:09:22 +0530 Subject: [PATCH 105/109] exhaustive deps --- web/apps/photos/eslint.config.mjs | 15 +-------------- .../photos/src/components/AuthenticateUser.tsx | 2 +- web/apps/photos/src/components/Avatar.tsx | 2 ++ .../components/Collections/AlbumCastDialog.tsx | 2 +- .../components/Collections/CollectionShare.tsx | 4 ++-- .../Collections/GalleryBarAndListHeader.tsx | 2 ++ web/apps/photos/src/components/FileList.tsx | 4 ++++ .../photos/src/components/FileListWithViewer.tsx | 4 ++-- web/apps/photos/src/components/Sidebar.tsx | 8 ++++---- web/apps/photos/src/components/Upload.tsx | 3 ++- web/apps/photos/src/components/WatchFolder.tsx | 2 ++ web/apps/photos/src/pages/_app.tsx | 4 +++- web/apps/photos/src/pages/gallery.tsx | 1 + web/apps/photos/src/pages/shared-albums.tsx | 4 +++- web/packages/new/README.md | 7 +++---- 15 files changed, 33 insertions(+), 31 deletions(-) diff --git a/web/apps/photos/eslint.config.mjs b/web/apps/photos/eslint.config.mjs index c4e97c3d6a..a1fe1f9296 100644 --- a/web/apps/photos/eslint.config.mjs +++ b/web/apps/photos/eslint.config.mjs @@ -1,16 +1,3 @@ import config from "ente-build-config/eslintrc-next-app.mjs"; -export default [ - ...config, - { ignores: [".next-desktop"] }, - { - rules: { - /* TODO: - * "This rule requires the `strictNullChecks` compiler option to be - * turned on to function correctly" - */ - /** TODO: Disabled as we migrate, try to prune these again */ - "react-hooks/exhaustive-deps": "off", - }, - }, -]; +export default [...config, { ignores: [".next-desktop"] }]; diff --git a/web/apps/photos/src/components/AuthenticateUser.tsx b/web/apps/photos/src/components/AuthenticateUser.tsx index 8c3b813c4c..2fabc25d89 100644 --- a/web/apps/photos/src/components/AuthenticateUser.tsx +++ b/web/apps/photos/src/components/AuthenticateUser.tsx @@ -80,7 +80,7 @@ const AuthenticateUserDialogContents: React.FC = ({ // potentially transient issues. log.warn("Ignoring error when determining session validity", e); } - }, [logout, showMiniDialog]); + }, [logout, showMiniDialog, onClose]); useEffect(() => { setUser(ensureLocalUser()); diff --git a/web/apps/photos/src/components/Avatar.tsx b/web/apps/photos/src/components/Avatar.tsx index 866c11fdc6..05feac15bf 100644 --- a/web/apps/photos/src/components/Avatar.tsx +++ b/web/apps/photos/src/components/Avatar.tsx @@ -1,3 +1,5 @@ +// TODO: Audit this file +/* eslint-disable react-hooks/exhaustive-deps */ import { styled } from "@mui/material"; import type { LocalUser } from "ente-accounts/services/user"; import log from "ente-base/log"; diff --git a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx index d81658cac5..672ab6b9ba 100644 --- a/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx +++ b/web/apps/photos/src/components/Collections/AlbumCastDialog.tsx @@ -152,7 +152,7 @@ export const AlbumCastDialogContents: React.FC = ({ }); }); } - }, [view, collection]); + }, [onClose, view, collection]); useEffect(() => { // Make API call to clear all previous sessions (if any) whenever the diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index 9679f04c04..e22740e1eb 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -497,7 +497,7 @@ const AddParticipant: React.FC = ({ email != user.email && !collection?.sharees?.find((value) => value.email == email), ); - }, [shareSuggestionEmails, collection.sharees]); + }, [user.email, shareSuggestionEmails, collection.sharees]); const handleRootClose = () => { onClose(); @@ -1130,7 +1130,7 @@ const PublicShare: React.FC = ({ } else { setResolvedURL(undefined); } - }, [publicURL]); + }, [collection.key, publicURL]); const handleCopyLink = () => { if (resolvedURL) void navigator.clipboard.writeText(resolvedURL); diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index b6be9a1ad9..ae6f126397 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -1,3 +1,4 @@ +// TODO: Audit this file import { AllAlbums } from "components/Collections/AllAlbums"; import { CollectionShare, @@ -161,6 +162,7 @@ export const GalleryBarAndListHeader: React.FC< ), height: 68, }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ shouldHide, mode, diff --git a/web/apps/photos/src/components/FileList.tsx b/web/apps/photos/src/components/FileList.tsx index 9bc755283f..b419a81296 100644 --- a/web/apps/photos/src/components/FileList.tsx +++ b/web/apps/photos/src/components/FileList.tsx @@ -288,6 +288,8 @@ export const FileList: React.FC = ({ } setTimeStampList(timeStampList); + // TODO: + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ width, height, @@ -510,6 +512,8 @@ export const FileList: React.FC = ({ localSelectedDates.forEach((date) => checked.add(date)); return checked; }); + // TODO: + // eslint-disable-next-line react-hooks/exhaustive-deps }, [selected]); const handleSelectMulti = handleSelectCreatorMulti( diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index df253a1a62..ab08d4c27c 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -138,12 +138,12 @@ export const FileListWithViewer: React.FC = ({ setCurrentIndex(index); setOpenFileViewer(true); onSetOpenFileViewer?.(true); - }, []); + }, [onSetOpenFileViewer]); const handleCloseFileViewer = useCallback(() => { onSetOpenFileViewer?.(false); setOpenFileViewer(false); - }, []); + }, [onSetOpenFileViewer]); const handleTriggerRemotePull = useCallback( () => void onRemotePull(), diff --git a/web/apps/photos/src/components/Sidebar.tsx b/web/apps/photos/src/components/Sidebar.tsx index ed2ee646e4..8c9769d0c5 100644 --- a/web/apps/photos/src/components/Sidebar.tsx +++ b/web/apps/photos/src/components/Sidebar.tsx @@ -348,7 +348,7 @@ const SubscriptionStatus: React.FC = ({ } } }, - [userDetails], + [onShowPlanSelector, userDetails], ); if (!hasAMessage) { @@ -651,13 +651,13 @@ const ExitSection: React.FC = () => { }; const InfoSection: React.FC = () => { - const [appVersion, setAppVersion] = useState(); - const [host, setHost] = useState(); + const [appVersion, setAppVersion] = useState(""); + const [host, setHost] = useState(""); useEffect(() => { void globalThis.electron?.appVersion().then(setAppVersion); void customAPIHost().then(setHost); - }); + }, []); return ( <> diff --git a/web/apps/photos/src/components/Upload.tsx b/web/apps/photos/src/components/Upload.tsx index 1626d770df..e43e1f2867 100644 --- a/web/apps/photos/src/components/Upload.tsx +++ b/web/apps/photos/src/components/Upload.tsx @@ -1,5 +1,6 @@ -// TODO: Too many null assertions in this file. The types need reworking. // TODO: Audit this file +// TODO: Too many null assertions in this file. The types need reworking. +/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-misused-promises */ /* eslint-disable @typescript-eslint/no-floating-promises */ import ChevronRightIcon from "@mui/icons-material/ChevronRight"; diff --git a/web/apps/photos/src/components/WatchFolder.tsx b/web/apps/photos/src/components/WatchFolder.tsx index 164db56c89..fd7627b30a 100644 --- a/web/apps/photos/src/components/WatchFolder.tsx +++ b/web/apps/photos/src/components/WatchFolder.tsx @@ -72,6 +72,8 @@ export const WatchFolder: React.FC = ({ return () => { removeEventListener("drop", handleWatchFolderDrop); }; + // TODO: + // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); const selectCollectionMappingAndAddWatchIfDirectory = async ( diff --git a/web/apps/photos/src/pages/_app.tsx b/web/apps/photos/src/pages/_app.tsx index ae009c7ec8..018e98efbb 100644 --- a/web/apps/photos/src/pages/_app.tsx +++ b/web/apps/photos/src/pages/_app.tsx @@ -122,7 +122,7 @@ const App: React.FC = ({ Component, pageProps }) => { electron.onOpenEnteURL(undefined); electron.onAppUpdateAvailable(undefined); }; - }, []); + }, [router, showMiniDialog, showNotification]); useEffect(() => { if (isDesktop) void resumeExportsIfNeeded(); @@ -152,6 +152,8 @@ const App: React.FC = ({ Component, pageProps }) => { throw "Aborting route change, redirection in process...."; } }); + // TODO: + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const baseContext = useMemo( diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 81b8bd509e..5e1437d64b 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1,5 +1,6 @@ // TODO: Audit this file (the code here is mostly fine, but needs revisiting // the file it depends on have been audited and their interfaces fixed). +/* eslint-disable react-hooks/exhaustive-deps */ /* eslint-disable @typescript-eslint/no-floating-promises */ import ArrowBackIcon from "@mui/icons-material/ArrowBack"; import FileUploadOutlinedIcon from "@mui/icons-material/FileUploadOutlined"; diff --git a/web/apps/photos/src/pages/shared-albums.tsx b/web/apps/photos/src/pages/shared-albums.tsx index 911d48e312..bd65bc12f4 100644 --- a/web/apps/photos/src/pages/shared-albums.tsx +++ b/web/apps/photos/src/pages/shared-albums.tsx @@ -213,6 +213,8 @@ export default function PublicCollectionGallery() { } }; main(); + // TODO: + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const downloadEnabled = @@ -313,7 +315,7 @@ export default function PublicCollectionGallery() { hideLoadingBar(); setLoading(false); } - }, [showLoadingBar, hideLoadingBar]); + }, [showLoadingBar, hideLoadingBar, onGenericError]); // See: [Note: Visual feedback to acknowledge user actions] const handleVisualFeedback = useCallback(() => { diff --git a/web/packages/new/README.md b/web/packages/new/README.md index 5e375b7597..820f68831f 100644 --- a/web/packages/new/README.md +++ b/web/packages/new/README.md @@ -1,9 +1,8 @@ ## ente-new -This package only exists so that we can write code that works with TypeScript -strict mode. This provides a gradual way of migrating the existing apps code to -strict mode. Once there is sufficient gravity here, we can flip the switch and -move these back to where they came from. +This package is only transient. Once the remaining files in apps/photos have +been revisited for clarity, and the albums app has been split into a separate +folder, this package will not exist. ### Packaging From 6f15b4178b2bdd3002885bf13b3d8dd8411e0006 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 8 Jul 2025 16:29:17 +0530 Subject: [PATCH 106/109] Cleanup --- .../Collections/GalleryBarAndListHeader.tsx | 2 +- .../photos/src/components/FileListWithViewer.tsx | 13 ++++++++----- web/packages/accounts/services/session.ts | 5 +---- web/packages/gallery/services/video.ts | 9 +-------- web/packages/media/collection.ts | 8 +------- web/packages/media/file.ts | 4 ---- .../new/albums/services/public-albums-fdb.ts | 8 +------- web/packages/new/photos/services/collection.ts | 8 -------- web/packages/new/photos/services/ml/ml-data.ts | 12 ------------ web/packages/new/photos/services/photos-fdb.ts | 10 +--------- web/packages/new/photos/services/search/worker.ts | 4 ---- web/packages/new/photos/services/user-entity/db.ts | 4 ---- .../new/photos/services/user-entity/index.ts | 4 ---- 13 files changed, 14 insertions(+), 77 deletions(-) diff --git a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx index ae6f126397..d7cddb8291 100644 --- a/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx +++ b/web/apps/photos/src/components/Collections/GalleryBarAndListHeader.tsx @@ -162,7 +162,7 @@ export const GalleryBarAndListHeader: React.FC< ), height: 68, }); - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ shouldHide, mode, diff --git a/web/apps/photos/src/components/FileListWithViewer.tsx b/web/apps/photos/src/components/FileListWithViewer.tsx index ab08d4c27c..0e8a35656b 100644 --- a/web/apps/photos/src/components/FileListWithViewer.tsx +++ b/web/apps/photos/src/components/FileListWithViewer.tsx @@ -134,11 +134,14 @@ export const FileListWithViewer: React.FC = ({ [files], ); - const handleThumbnailClick = useCallback((index: number) => { - setCurrentIndex(index); - setOpenFileViewer(true); - onSetOpenFileViewer?.(true); - }, [onSetOpenFileViewer]); + const handleThumbnailClick = useCallback( + (index: number) => { + setCurrentIndex(index); + setOpenFileViewer(true); + onSetOpenFileViewer?.(true); + }, + [onSetOpenFileViewer], + ); const handleCloseFileViewer = useCallback(() => { onSetOpenFileViewer?.(false); diff --git a/web/packages/accounts/services/session.ts b/web/packages/accounts/services/session.ts index c2e6f76f35..7d5eeeb22c 100644 --- a/web/packages/accounts/services/session.ts +++ b/web/packages/accounts/services/session.ts @@ -117,10 +117,7 @@ export const checkSessionValidity = async (): Promise => { // changed. return { status: "validButPasswordChanged", - // TODO: - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - updatedKeyAttributes: remoteKeyAttributes as KeyAttributes, + updatedKeyAttributes: remoteKeyAttributes, updatedSRPAttributes: remoteSRPAttributes, }; } diff --git a/web/packages/gallery/services/video.ts b/web/packages/gallery/services/video.ts index 68e129099b..61561ffc72 100644 --- a/web/packages/gallery/services/video.ts +++ b/web/packages/gallery/services/video.ts @@ -349,14 +349,7 @@ export const hlsPlaylistDataForFile = async ( playlist: playlistTemplate, width, height, - } = await decryptPlaylistJSON( - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - playlistFileData, - file, - ); + } = await decryptPlaylistJSON(playlistFileData, file); // A playlist format the current client does not understand. if (type != "hls_video") return undefined; diff --git a/web/packages/media/collection.ts b/web/packages/media/collection.ts index 81d90909e6..b8412a6d65 100644 --- a/web/packages/media/collection.ts +++ b/web/packages/media/collection.ts @@ -529,13 +529,7 @@ export const decryptRemoteCollection = async ( owner: parseRemoteCollectionUser(owner), name, sharees: sharees.map(parseRemoteCollectionUser), - // TODO: - // - // See: [Note: strict mode migration] - // - // We need to add the cast here, otherwise we get a tsc error when this - // file is imported in the photos app. - publicURLs: rest.publicURLs as PublicURL[], + publicURLs: rest.publicURLs, magicMetadata, pubMagicMetadata, sharedMagicMetadata, diff --git a/web/packages/media/file.ts b/web/packages/media/file.ts index 12ab7bbed4..9ede63ab24 100644 --- a/web/packages/media/file.ts +++ b/web/packages/media/file.ts @@ -381,10 +381,6 @@ export const decryptRemoteFile = async ( collectionKey, ); - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore const metadataJSON = await decryptMetadataJSON(encryptedMetadata, key); const metadata = FileMetadata.parse( transformDecryptedMetadataJSON(id, metadataJSON), diff --git a/web/packages/new/albums/services/public-albums-fdb.ts b/web/packages/new/albums/services/public-albums-fdb.ts index 5e940ced6a..429a294901 100644 --- a/web/packages/new/albums/services/public-albums-fdb.ts +++ b/web/packages/new/albums/services/public-albums-fdb.ts @@ -20,15 +20,9 @@ import { z } from "zod/v4"; * Use {@link savePublicCollections} to update the database. */ const savedPublicCollections = async (): Promise => - // TODO: - // - // See: [Note: strict mode migration] - // - // We need to add the cast here, otherwise we get a tsc error when this - // file is imported in the photos app. LocalCollections.parse( (await localForage.getItem("public-collections")) ?? [], - ) as Collection[]; + ); /** * Replace the list of public collections stored in our local database. diff --git a/web/packages/new/photos/services/collection.ts b/web/packages/new/photos/services/collection.ts index 6eeb0b1b5a..f231c64f70 100644 --- a/web/packages/new/photos/services/collection.ts +++ b/web/packages/new/photos/services/collection.ts @@ -1390,10 +1390,6 @@ export const createPublicURL = async ( body: JSON.stringify({ collectionID, ...attributes }), }); ensureOk(res); - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore return z.object({ result: RemotePublicURL }).parse(await res.json()).result; }; @@ -1429,10 +1425,6 @@ export const updatePublicURL = async ( body: JSON.stringify({ collectionID, ...updates }), }); ensureOk(res); - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore return z.object({ result: RemotePublicURL }).parse(await res.json()).result; }; diff --git a/web/packages/new/photos/services/ml/ml-data.ts b/web/packages/new/photos/services/ml/ml-data.ts index 28271ec65e..370e4002f9 100644 --- a/web/packages/new/photos/services/ml/ml-data.ts +++ b/web/packages/new/photos/services/ml/ml-data.ts @@ -171,10 +171,6 @@ export const fetchMLData = async ( try { const decryptedBytes = await decryptBlobBytes( remoteFileData, - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore file.key, ); const jsonString = await gunzip(decryptedBytes); @@ -199,14 +195,6 @@ const remoteMLDataFromJSONString = ( ) => { const raw = RawRemoteMLData.parse(JSON.parse(jsonString)); const parseResult = ParsedRemoteMLData.safeParse(raw); - // TODO: [Note: strict mode migration] - // - // This code is included in apps/photos where it causes spurious tsc failure - // since the photos app currently does not have the TypeScript strict mode - // enabled (unlike the current file). - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore const parsed = parseResult.success ? (parseResult.data as ParsedRemoteMLData) : undefined; diff --git a/web/packages/new/photos/services/photos-fdb.ts b/web/packages/new/photos/services/photos-fdb.ts index 357a30d909..a02b21a0a3 100644 --- a/web/packages/new/photos/services/photos-fdb.ts +++ b/web/packages/new/photos/services/photos-fdb.ts @@ -22,15 +22,7 @@ import type { TrashItem } from "./trash"; * Use {@link saveCollections} to update the database. */ export const savedCollections = async (): Promise => - // TODO: - // - // See: [Note: strict mode migration] - // - // We need to add the cast here, otherwise we get a tsc error when this - // file is imported in the photos app. - LocalCollections.parse( - (await localForage.getItem("collections")) ?? [], - ) as Collection[]; + LocalCollections.parse((await localForage.getItem("collections")) ?? []); /** * Replace the list of collections stored in our local database. diff --git a/web/packages/new/photos/services/search/worker.ts b/web/packages/new/photos/services/search/worker.ts index b088eef50f..8a3bee99cb 100644 --- a/web/packages/new/photos/services/search/worker.ts +++ b/web/packages/new/photos/services/search/worker.ts @@ -431,10 +431,6 @@ const defaultCityRadius = 10; const kmsPerDegree = 111.16; const isInsideLocationTag = (location: Location, locationTag: LocationTag) => - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore isWithinRadius(location, locationTag.centerPoint, locationTag.radius); const isInsideCity = (location: Location, city: City) => diff --git a/web/packages/new/photos/services/user-entity/db.ts b/web/packages/new/photos/services/user-entity/db.ts index bc3e870c04..f9fa86ce39 100644 --- a/web/packages/new/photos/services/user-entity/db.ts +++ b/web/packages/new/photos/services/user-entity/db.ts @@ -53,10 +53,6 @@ export const saveEntities = (type: EntityType, items: LocalUserEntity[]) => export const savedEntities = async ( type: EntityType, ): Promise => - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore LocalUserEntity.array().parse((await getKV(entitiesKey(type))) ?? []); /** diff --git a/web/packages/new/photos/services/user-entity/index.ts b/web/packages/new/photos/services/user-entity/index.ts index ca9f50ac27..dd4de714fa 100644 --- a/web/packages/new/photos/services/user-entity/index.ts +++ b/web/packages/new/photos/services/user-entity/index.ts @@ -102,10 +102,6 @@ export type CGroup = Omit & { * Return the list of locally available cgroup user entities. */ export const savedCGroups = (): Promise => - // See: [Note: strict mode migration] - // - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore savedEntities("cgroup").then((es) => es.map((e) => ({ ...e, data: RemoteCGroupData.parse(e.data) })), ); From fddbbe51351ffdedc50eac416d571670a9cb43cc Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 8 Jul 2025 18:34:22 +0530 Subject: [PATCH 107/109] Prefer b2 for reading metadata --- server/pkg/controller/filedata/controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/pkg/controller/filedata/controller.go b/server/pkg/controller/filedata/controller.go index ec269b690c..65cbef0033 100644 --- a/server/pkg/controller/filedata/controller.go +++ b/server/pkg/controller/filedata/controller.go @@ -267,9 +267,9 @@ func (c *Controller) fetchS3FileMetadata(ctx context.Context, row fileData.Row, // :todo:neeraj make it configurable to // specify preferred dc to read from // and fallback logic to read from different bucket when we fail to read from preferred dc - if dc == "b6" { - if array.StringInList("b5", row.ReplicatedBuckets) { - dc = "b5" + if dc == "b5" { + if array.StringInList("b6", row.ReplicatedBuckets) { + dc = "b6" } } opt := _defaultFetchConfig From 9938db6af4b83e28acd232ac668202c7ea29221c Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Wed, 9 Jul 2025 21:27:44 +0530 Subject: [PATCH 108/109] Fix hero animation --- mobile/apps/photos/lib/ui/collections/flex_grid_view.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/mobile/apps/photos/lib/ui/collections/flex_grid_view.dart b/mobile/apps/photos/lib/ui/collections/flex_grid_view.dart index 2267833b1a..aa66ae5d46 100644 --- a/mobile/apps/photos/lib/ui/collections/flex_grid_view.dart +++ b/mobile/apps/photos/lib/ui/collections/flex_grid_view.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import "package:flutter/services.dart"; import "package:logging/logging.dart"; +import "package:photos/core/configuration.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/events/clear_album_selections_event.dart"; import "package:photos/generated/l10n.dart"; @@ -98,10 +99,16 @@ class _CollectionsFlexiGridViewWidgetState Future _navigateToCollectionPage(Collection c) async { final thumbnail = await CollectionsService.instance.getCover(c); + final bool isOwner = c.isOwner(Configuration.instance.getUserID()!); + final String tagPrefix = (isOwner ? "collection" : "shared_collection") + + widget.tag + + "_" + + c.id.toString(); // ignore: unawaited_futures routeToPage( context, CollectionPage( + tagPrefix: tagPrefix, CollectionWithThumbnail(c, thumbnail), ), ); From 2595fdebae392c6716225900678be37aede6fb4f Mon Sep 17 00:00:00 2001 From: AmanRajSinghMourya Date: Wed, 9 Jul 2025 21:47:10 +0530 Subject: [PATCH 109/109] Reduce corner smoothing from 1.0 -> 0.6 --- mobile/apps/photos/lib/ui/collections/album/row_item.dart | 2 +- .../photos/lib/ui/collections/device/device_folder_item.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile/apps/photos/lib/ui/collections/album/row_item.dart b/mobile/apps/photos/lib/ui/collections/album/row_item.dart index ffaaf04421..48aff48c6a 100644 --- a/mobile/apps/photos/lib/ui/collections/album/row_item.dart +++ b/mobile/apps/photos/lib/ui/collections/album/row_item.dart @@ -27,7 +27,7 @@ class AlbumRowItemWidget extends StatelessWidget { final SelectedAlbums? selectedAlbums; static const _borderWidth = 1.0; static const _cornerRadius = 12.0; - static const _cornerSmoothing = 1.0; + static const _cornerSmoothing = 0.6; const AlbumRowItemWidget( this.c, diff --git a/mobile/apps/photos/lib/ui/collections/device/device_folder_item.dart b/mobile/apps/photos/lib/ui/collections/device/device_folder_item.dart index e7817b169e..cf5809dc71 100644 --- a/mobile/apps/photos/lib/ui/collections/device/device_folder_item.dart +++ b/mobile/apps/photos/lib/ui/collections/device/device_folder_item.dart @@ -13,7 +13,7 @@ class DeviceFolderItem extends StatelessWidget { final double sideOfThumbnail; static const _cornerRadius = 12.0; - static const _cornerSmoothing = 1.0; + static const _cornerSmoothing = 0.6; static const _borderWidth = 1.0; const DeviceFolderItem(
  • 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