Compare commits
21 Commits
translatio
...
meta_files
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f8cadc022 | ||
|
|
901bfc945e | ||
|
|
6c25b094be | ||
|
|
4f5af8dcfa | ||
|
|
8079d44c68 | ||
|
|
cd5582219c | ||
|
|
69332c78ad | ||
|
|
cba30e386d | ||
|
|
7663e76deb | ||
|
|
697d6f854d | ||
|
|
7aadb54ef1 | ||
|
|
2a2443efea | ||
|
|
d2bc2627a3 | ||
|
|
b1971810fb | ||
|
|
bd25af2b4b | ||
|
|
833b4656fe | ||
|
|
315c4ae6b7 | ||
|
|
49d9b3c928 | ||
|
|
e9970cf1f3 | ||
|
|
0ec6e2e0ae | ||
|
|
3442648826 |
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "استخدم خيار \"Export the vault\" في إعدادات Aegis.\n\nإذا كان المخزن الخاص بك مشفرًا، فستحتاج إلى إدخال كلمة مرور المخزن لفك تشفير المخزن.",
|
||||
"import2FasGuide": "استخدم خيار \"الإعدادات -> النسخ الاحتياطي - التصدير\" في 2FAS.\n\nإذا تم تشفير النسخة الاحتياطية، سوف تحتاج إلى إدخال كلمة المرور لفك تشفير النسخة الاحتياطية",
|
||||
"importLastpassGuide": "استخدم خيار \"حسابات النقل\" ضمن إعدادات مصادقة Lastpass، واضغط على \"تصدير الحسابات إلى الملف\". استيراد JSON الذي تم تنزيله.",
|
||||
"importProtonAuthGuide": "استخدم اختيار “التصدير” في إعدادات الموثق بروتون لتصدير رموزك.",
|
||||
"exportCodes": "تصدير الرموز",
|
||||
"importLabel": "استيراد",
|
||||
"importInstruction": "الرجاء تحديد ملف يحتوي على قائمة بالرموز الخاصة بك بالشكل التالي",
|
||||
@@ -520,12 +519,5 @@
|
||||
"algorithm": "الخوارزمية",
|
||||
"type": "النوع",
|
||||
"period": "المدّة",
|
||||
"digits": "الأرقام",
|
||||
"importFromGallery": "استيراد من معر الصور ",
|
||||
"errorCouldNotReadImage": "تعذر قراءة الصورة المحدد.",
|
||||
"errorInvalidQRCode": "رمز QR غير صالح",
|
||||
"errorInvalidQRCodeBody": "رمز QR الممسوح ليس حساب 2FA صحيح.",
|
||||
"errorNoQRCode": "لم يتم العثور على رمز QR",
|
||||
"errorGenericTitle": "حدث خطأ",
|
||||
"errorGenericBody": "حدث خطأ غير متوقع خلال الاستيراد."
|
||||
"digits": "الأرقام"
|
||||
}
|
||||
@@ -157,7 +157,6 @@
|
||||
"enterCodeHint": "Zadejte 6místný kód ze své autentizační aplikace",
|
||||
"lostDeviceTitle": "Ztratili jste zařízení?",
|
||||
"twoFactorAuthTitle": "Dvoufaktorové ověření",
|
||||
"passkeyAuthTitle": "Ověření pomocí přístupového klíče",
|
||||
"verifyPasskey": "Ověřit přístupový klíč",
|
||||
"loginWithTOTP": "Přihlášení s TOTP",
|
||||
"recoverAccount": "Obnovit účet",
|
||||
@@ -174,7 +173,7 @@
|
||||
"invalidQRCode": "Neplatný QR kód",
|
||||
"noRecoveryKeyTitle": "Nemáte obnovovací klíč?",
|
||||
"enterEmailHint": "Zadejte svou e-mailovou adresu",
|
||||
"enterNewEmailHint": "Zadejte novou e-mailovou adresu",
|
||||
"enterNewEmailHint": "Zadejte svou novou e-mailovou adresu",
|
||||
"invalidEmailTitle": "Neplatná e-mailová adresa",
|
||||
"invalidEmailMessage": "Prosím, zadejte platnou e-mailovou adresu.",
|
||||
"deleteAccount": "Odstranit účet",
|
||||
@@ -513,7 +512,7 @@
|
||||
"supportEnte": "Podpořte <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Dejte nám hvězdu na Githubu",
|
||||
"free5GB": "5GB zdarma na <bold-green>ente</bold-green> Fotky",
|
||||
"loginWithAuthAccount": "Přihlásit se pomocí účtu Auth",
|
||||
"loginWithAuthAccount": "Přihlaste se pomocí svého účtu Auth",
|
||||
"freeStorageOffer": "10% sleva na <bold-green>ente</bold-green> fotky",
|
||||
"freeStorageOfferDescription": "Použijte kód \"AUTH\" pro získání 10% slevy na první rok",
|
||||
"advanced": "Pokročilé",
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"changePassword": "Muuda salasõna",
|
||||
"data": "Andmed",
|
||||
"importCodes": "Impordi koode",
|
||||
"importTypePlainText": "Vormindamata tekstina",
|
||||
"importTypePlainText": "Votmindamata tekstina",
|
||||
"importTypeEnteEncrypted": "Ente krüptitud ekspordina",
|
||||
"passwordForDecryptingExport": "Salasõna eksporditud andmete dekrüptimiseks",
|
||||
"passwordEmptyError": "Salasõna väli ei saa olla tühi",
|
||||
@@ -214,40 +214,7 @@
|
||||
"@iUnderStand": {
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "Oma koodide eksportimiseks palun tuvasta end",
|
||||
"importSuccessDesc": "Sa oled importinud {count} koodi!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The number of codes imported",
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"pendingSyncsWarningBody": "Mõned sinu koodid on varundamata.\n\nEnne väljalogimist palun kontrolli, et sa oleksid nad varundanud.",
|
||||
"tapToEnterCode": "Koodi sisestamiseks klõpsa",
|
||||
"terminateSession": "Kas lõpetad sessiooni?",
|
||||
"terminate": "Lõpeta",
|
||||
"thisDevice": "See seade",
|
||||
"toResetVerifyEmail": "Salasõna lähtestamiseks palun esmalt kinnita oma e-posti aadress.",
|
||||
"thisEmailIsAlreadyInUse": "See e-posti aadress on juba kasutuses",
|
||||
"verificationFailedPleaseTryAgain": "Kinnitamine ei õnnestunud, palun proovi uuesti",
|
||||
"yourVerificationCodeHasExpired": "Sinu verifitseerimiskood on aegunud",
|
||||
"incorrectCode": "Vigane kood",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "Vabandust, sinu sisestatud kood on vigane",
|
||||
"emailChangedTo": "E-posti aadress on nüüd muudetud, uus aadress on {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "Autentimine ei õnnestunud, palun proovi uuesti",
|
||||
"authenticationSuccessful": "Autentimine õnnestus!",
|
||||
"incorrectRecoveryKey": "Vigane taasetvõti",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "Sinu sisestatud taasetvõti on vigane",
|
||||
"enterPassword": "Sisesta salasõna",
|
||||
"selectExportFormat": "Vali ekspordivorming",
|
||||
"exportDialogDesc": "Krüptitud ekspordifailid on kaitstud sinu sisestatud salasõnaga.",
|
||||
"encrypted": "Krüptitud",
|
||||
"plainText": "Vormindamata tekst",
|
||||
"pinnedCodeMessage": "{code} on tõstetud esile",
|
||||
"unpinnedCodeMessage": "{code} esiletõstmine on lõppenud",
|
||||
"createNewTag": "Lisa uus silt",
|
||||
"tag": "Silt",
|
||||
"create": "Loo",
|
||||
@@ -258,9 +225,5 @@
|
||||
"reEnterPassword": "Sisesta salasõna uuesti",
|
||||
"setNewPassword": "Sisesta uus salasõna",
|
||||
"enterPin": "Sisesta PIN-kood",
|
||||
"setNewPin": "Määra uus PIN-kood",
|
||||
"plainHTML": "Tavaline HTML",
|
||||
"errorInvalidQRCode": "Vigane QR-kood",
|
||||
"errorInvalidQRCodeBody": "Skaneeritud QR-kood pole korrektne 2FA kasutajakonto.",
|
||||
"errorNoQRCode": "QR-koodi ei leidu"
|
||||
"setNewPin": "Määra uus PIN-kood"
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "Sécurisez vos codes A2F",
|
||||
"onBoardingBody": "Sauvegarder vos codes A2F",
|
||||
"onBoardingGetStarted": "Premiers pas",
|
||||
"setupFirstAccount": "Configurez votre premier compte",
|
||||
"importScanQrCode": "Scannez un QR Code",
|
||||
|
||||
@@ -111,7 +111,6 @@
|
||||
"importAegisGuide": "Gunakan opsi \"Export the vault\" di Pengaturan Aegis.\n\nJika brankas Anda terenkripsi, Anda perlu memasukkan kata sandi brankas untuk mendekripsi brankas.",
|
||||
"import2FasGuide": "Gunakan opsi \"Settings->Backup -Export\" di 2FAS.\n\nJika cadangan Anda terenkripsi, Anda perlu memasukkan kata sandi untuk mendekripsi cadangan",
|
||||
"importLastpassGuide": "Gunakan opsi \"Transfer accounts\" di Pengaturan Lastpass Authenticator dan tekan \"Export accounts to file\". Impor file JSON yang diunduh.",
|
||||
"importProtonAuthGuide": "Gunakan opsi \"Ekspor\" di pengaturan Proton Authenticator untuk mengekspor kode anda.",
|
||||
"exportCodes": "Ekspor kode",
|
||||
"importLabel": "Impor",
|
||||
"importInstruction": "Harap pilih file yang berisi daftar kode Anda dalam format berikut",
|
||||
@@ -501,31 +500,16 @@
|
||||
"appLockOfflineModeWarning": "Anda telah memilih untuk mengunci aplikasi tanpa cadangan apa pun. Jika Anda lupa kode Pengunci Apl Anda, Anda tidak akan dapat mengakses data-data Anda.",
|
||||
"duplicateCodes": "Kode duplikat",
|
||||
"noDuplicates": "✨ Tak ada duplikat",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Anda tidak memiliki kode duplikat yang dapat dihapus",
|
||||
"deduplicateCodes": "Hapus kode duplikat",
|
||||
"deselectAll": "Batalkan semua pilihan",
|
||||
"selectAll": "Pilih semua",
|
||||
"deleteDuplicates": "Hapus duplikat",
|
||||
"plainHTML": "HTML Sederhana",
|
||||
"tellUsWhatYouThink": "Berikan pendapatmu",
|
||||
"dropReviewiOS": "Berikan ulasan di App Store",
|
||||
"dropReviewAndroid": "Berikan ulasan di Play Store",
|
||||
"supportEnte": "Dukung <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Beri kami bintang di Github",
|
||||
"free5GB": "5GB gratis di <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Masuk dengan akun Auth anda",
|
||||
"freeStorageOffer": "Potongan 10% di <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Gunakan kode \"AUTH\" untuk mendapatkan potongan 10% untuk tahun pertama",
|
||||
"advanced": "Lanjutan",
|
||||
"algorithm": "Algoritma",
|
||||
"type": "Tipe",
|
||||
"period": "Periode",
|
||||
"digits": "Digit",
|
||||
"importFromGallery": "Impor dari galeri",
|
||||
"errorCouldNotReadImage": "Tidak dapat membaca file gambar yang dipilih.",
|
||||
"errorInvalidQRCode": "Kode QR tidak valid",
|
||||
"errorInvalidQRCodeBody": "Kode QR yang dipindai bukan akun 2FA yang valid.",
|
||||
"errorNoQRCode": "Kode QR tidak ditemukan",
|
||||
"errorGenericTitle": "Terjadi kesalahan",
|
||||
"errorGenericBody": "Terjadi kesalahan yang tidak terduga saat mengimpor."
|
||||
"digits": "Digit"
|
||||
}
|
||||
@@ -519,12 +519,5 @@
|
||||
"algorithm": "Algoritmo",
|
||||
"type": "Tipo",
|
||||
"period": "Periodo",
|
||||
"digits": "Cifre",
|
||||
"importFromGallery": "Importa dalla galleria",
|
||||
"errorCouldNotReadImage": "Impossibile leggere il file immagine selezionato.",
|
||||
"errorInvalidQRCode": "Codice QR non valido",
|
||||
"errorInvalidQRCodeBody": "Il codice QR scansionato non è un account 2FA valido.",
|
||||
"errorNoQRCode": "Nessun codice QR trovato",
|
||||
"errorGenericTitle": "Si è verificato un errore",
|
||||
"errorGenericBody": "Si è verificato un errore imprevisto durante l'importazione."
|
||||
"digits": "Cifre"
|
||||
}
|
||||
BIN
mobile/apps/locker/assets/2.0x/loading_photos_background.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 39 KiB |
BIN
mobile/apps/locker/assets/3.0x/loading_photos_background.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
|
After Width: | Height: | Size: 63 KiB |
20
mobile/apps/locker/assets/icons/legacy-dark.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_34053_111310)">
|
||||
<path d="M170 66.7593C170 71.8084 169.042 76.732 167.151 81.3933C165.192 86.2233 162.316 90.5536 158.606 94.265L147.36 105.511L151.657 109.806C152.962 111.112 152.26 113.347 150.441 113.671L128.349 117.598C126.785 117.875 125.423 116.513 125.7 114.949L129.628 92.8595C129.952 91.0413 132.187 90.3393 133.492 91.6442L137.12 95.2717L148.366 84.0261C152.978 79.4144 155.519 73.282 155.519 66.7609C155.519 60.2383 152.978 54.105 148.365 49.4941C143.753 44.8815 137.621 42.3422 131.097 42.3422C124.574 42.3422 118.442 44.8815 113.83 49.4941L107.387 55.936C107.383 55.9064 107.378 55.8784 107.373 55.8488L104.299 38.5628C107.848 35.1847 111.936 32.5447 116.463 30.7097C121.123 28.818 126.048 27.8594 131.097 27.8594C136.146 27.8594 141.07 28.818 145.732 30.7089C150.564 32.667 154.894 35.5421 158.606 39.2528C162.317 42.9643 165.192 47.2946 167.151 52.1246C169.041 56.7859 169.999 61.711 170 66.7593Z" fill="#4AAF3C"/>
|
||||
<path d="M133.814 119.087L105.12 147.779C103.762 149.137 101.92 149.9 99.9998 149.9C98.0791 149.9 96.2375 149.137 94.879 147.779L70.7775 123.678L67.503 126.953C66.1972 128.259 63.9623 127.556 63.6392 125.737L59.7107 103.646C59.4332 102.083 60.7966 100.72 62.3598 100.997L84.4519 104.925C86.2702 105.249 86.9731 107.483 85.6673 108.789L81.0183 113.438L100.001 132.42L124.507 107.914L123.318 114.601C123.053 116.096 123.535 117.63 124.609 118.703C125.684 119.778 127.217 120.259 128.712 119.994L133.814 119.087H133.814Z" fill="#4AAF3C"/>
|
||||
<path d="M102.359 58.8607L80.2668 54.9325C78.4485 54.6095 77.7456 52.3748 79.0514 51.0692L83.1799 46.9412C79.0498 43.9533 74.1009 42.3406 68.9034 42.3406C62.3808 42.3406 56.2477 44.88 51.6363 49.4925C47.0232 54.1042 44.4828 60.2359 44.4828 66.7585C44.4828 73.2812 47.0232 79.4128 51.6363 84.0246L66.9435 99.3301L62.7415 98.5834C61.2462 98.3179 59.7125 98.8 58.6386 99.8738C57.5647 100.948 57.0825 102.482 57.348 103.977L58.697 111.564L41.3955 94.2635C37.6836 90.552 34.8089 86.2217 32.8499 81.3917C30.9588 76.7312 30 71.8076 30 66.7593C30 61.711 30.9588 56.7867 32.8491 52.1254C34.8081 47.2946 37.6836 42.9643 41.3947 39.2536C45.1057 35.5421 49.4365 32.6678 54.267 30.7097C58.9296 28.818 63.8537 27.8594 68.9034 27.8594C73.953 27.8594 78.8771 28.818 83.5389 30.7081C87.158 32.1753 90.4956 34.1565 93.503 36.6183L97.2157 32.9061C98.5215 31.6004 100.756 32.3032 101.079 34.1214L105.007 56.211C105.286 57.7741 103.922 59.1381 102.359 58.8607Z" fill="#4AAF3C"/>
|
||||
</g>
|
||||
<g filter="url(#filter0_f_34053_111310)">
|
||||
<ellipse cx="102.576" cy="170.536" rx="27.5758" ry="1.59085" fill="#1CA609" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_34053_111310" x="70.5" y="164.445" width="64.1515" height="12.1797" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="2.25" result="effect1_foregroundBlur_34053_111310"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_34053_111310">
|
||||
<rect width="140" height="122" fill="white" transform="translate(30 27.8828)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
20
mobile/apps/locker/assets/icons/legacy-light.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_34053_111286)">
|
||||
<path d="M170 66.7593C170 71.8084 169.042 76.732 167.151 81.3933C165.192 86.2233 162.316 90.5536 158.606 94.265L147.36 105.511L151.657 109.806C152.962 111.112 152.26 113.347 150.441 113.671L128.349 117.598C126.785 117.875 125.423 116.513 125.7 114.949L129.628 92.8594C129.952 91.0413 132.187 90.3393 133.492 91.6441L137.12 95.2717L148.366 84.0261C152.978 79.4144 155.519 73.2819 155.519 66.7609C155.519 60.2383 152.978 54.105 148.365 49.4941C143.753 44.8815 137.621 42.3422 131.097 42.3422C124.574 42.3422 118.442 44.8815 113.83 49.4941L107.387 55.936C107.383 55.9064 107.378 55.8784 107.373 55.8488L104.299 38.5628C107.848 35.1847 111.936 32.5447 116.463 30.7097C121.123 28.818 126.048 27.8594 131.097 27.8594C136.146 27.8594 141.07 28.818 145.732 30.7089C150.564 32.667 154.894 35.5421 158.606 39.2528C162.317 42.9643 165.192 47.2946 167.151 52.1246C169.041 56.7859 169.999 61.7102 170 66.7585V66.7593Z" fill="#4AAF3C"/>
|
||||
<path d="M133.814 119.087L105.12 147.779C103.762 149.137 101.92 149.9 99.9998 149.9C98.0791 149.9 96.2375 149.137 94.879 147.779L70.7775 123.678L67.503 126.953C66.1972 128.259 63.9623 127.556 63.6392 125.737L59.7107 103.646C59.4332 102.083 60.7966 100.72 62.3598 100.997L84.4519 104.925C86.2702 105.249 86.9731 107.483 85.6673 108.789L81.0183 113.438L100.001 132.42L124.507 107.914L123.318 114.601C123.053 116.096 123.535 117.63 124.609 118.703C125.684 119.778 127.217 120.259 128.712 119.994L133.814 119.087H133.814Z" fill="#4AAF3C"/>
|
||||
<path d="M102.359 58.8607L80.2668 54.9325C78.4485 54.6095 77.7456 52.3748 79.0514 51.0692L83.1799 46.9412C79.0498 43.9533 74.1009 42.3406 68.9034 42.3406C62.3808 42.3406 56.2477 44.88 51.6363 49.4925C47.0232 54.1042 44.4828 60.2359 44.4828 66.7585C44.4828 73.2812 47.0232 79.4128 51.6363 84.0246L66.9435 99.3301L62.7415 98.5834C61.2462 98.3179 59.7125 98.8 58.6386 99.8738C57.5647 100.948 57.0825 102.482 57.348 103.977L58.697 111.564L41.3955 94.2635C37.6836 90.552 34.8089 86.2217 32.8499 81.3917C30.9588 76.7312 30 71.8076 30 66.7593C30 61.711 30.9588 56.7867 32.8491 52.1254C34.8081 47.2946 37.6836 42.9643 41.3947 39.2536C45.1057 35.5421 49.4365 32.6678 54.267 30.7097C58.9296 28.818 63.8537 27.8594 68.9034 27.8594C73.953 27.8594 78.8771 28.818 83.5389 30.7081C87.158 32.1753 90.4957 34.1565 93.503 36.6183L97.2157 32.9061C98.5215 31.6004 100.756 32.3032 101.079 34.1214L105.007 56.211C105.286 57.7741 103.922 59.1373 102.359 58.8599V58.8607Z" fill="#4AAF3C"/>
|
||||
</g>
|
||||
<g filter="url(#filter0_f_34053_111286)">
|
||||
<ellipse cx="101.576" cy="170.536" rx="27.5758" ry="1.59085" fill="#E1E1E1" fill-opacity="0.5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_34053_111286" x="69.5" y="164.445" width="64.1515" height="12.1797" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="2.25" result="effect1_foregroundBlur_34053_111286"/>
|
||||
</filter>
|
||||
<clipPath id="clip0_34053_111286">
|
||||
<rect width="140" height="122" fill="white" transform="translate(30 27.8828)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
BIN
mobile/apps/locker/assets/loading_photos_background.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
mobile/apps/locker/assets/loading_photos_background_dark.png
Normal file
|
After Width: | Height: | Size: 63 KiB |
@@ -83,6 +83,7 @@ flutter:
|
||||
|
||||
assets:
|
||||
- assets/
|
||||
- assets/icons/
|
||||
|
||||
fonts:
|
||||
- family: Inter
|
||||
|
||||
@@ -31,7 +31,10 @@ The Photos app uses two types of packages:
|
||||
**CRITICAL: CI will fail if ANY of these checks fail. Run ALL commands and ensure they ALL pass.**
|
||||
|
||||
```bash
|
||||
# 1. Analyze flutter code for errors and warnings
|
||||
# 1. Format Dart code
|
||||
dart format .
|
||||
|
||||
# 2. Analyze flutter code for errors and warnings
|
||||
flutter analyze
|
||||
```
|
||||
|
||||
@@ -164,11 +167,12 @@ lib/
|
||||
## Critical Coding Requirements
|
||||
|
||||
### 1. Code Quality - MANDATORY
|
||||
**Every code change MUST pass `flutter analyze` with zero issues**
|
||||
**Every code change MUST pass `dart format .` and `flutter analyze` with zero issues**
|
||||
- Run `dart format .` first to format all Dart code
|
||||
- Run `flutter analyze` after EVERY code modification
|
||||
- Resolve ALL issues (info, warning, error) - no exceptions
|
||||
- The codebase has zero issues by default, so any issue is from your changes
|
||||
- DO NOT commit or consider work complete until `flutter analyze` passes cleanly
|
||||
- DO NOT commit or consider work complete until both commands pass cleanly
|
||||
|
||||
### 2. Component Reuse - MANDATORY
|
||||
**Always try to reuse existing components**
|
||||
@@ -201,4 +205,4 @@ lib/
|
||||
- Always follow existing code conventions and patterns in neighboring files
|
||||
|
||||
# Individual Preferences
|
||||
- @~/.claude/my-project-instructions.md
|
||||
- @~/.claude/ente-photos-instructions.md
|
||||
|
||||
@@ -228,7 +228,7 @@ extension CustomColorScheme on ColorScheme {
|
||||
Color get videoPlayerPrimaryColor => brightness == Brightness.light
|
||||
? const Color.fromRGBO(0, 179, 60, 1)
|
||||
: const Color.fromRGBO(1, 222, 77, 1);
|
||||
|
||||
|
||||
Color get videoPlayerBorderColor => brightness == Brightness.light
|
||||
? const Color(0xFF424242)
|
||||
: const Color(0xFFFFFFFF);
|
||||
|
||||
@@ -5,4 +5,4 @@ class CreateNewAlbumEvent extends Event {
|
||||
final Collection collection;
|
||||
|
||||
CreateNewAlbumEvent(this.collection);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import "package:photos/models/collection/collection_items.dart";
|
||||
import "package:photos/models/search/search_result.dart";
|
||||
import "package:photos/models/typedefs.dart";
|
||||
import "package:photos/services/collections_service.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart";
|
||||
import "package:photos/services/search_service.dart";
|
||||
import "package:photos/ui/viewer/gallery/collection_page.dart";
|
||||
import "package:photos/ui/viewer/location/add_location_sheet.dart";
|
||||
@@ -220,7 +221,12 @@ extension SectionTypeExtensions on SectionType {
|
||||
}) {
|
||||
switch (this) {
|
||||
case SectionType.face:
|
||||
return SearchService.instance.getAllFace(limit);
|
||||
return SearchService.instance.getAllFace(
|
||||
limit,
|
||||
minClusterSize: limit == null
|
||||
? kMinimumClusterSizeAllFaces
|
||||
: kMinimumClusterSizeSearchResult,
|
||||
);
|
||||
case SectionType.magic:
|
||||
return SearchService.instance.getMagicSectionResults(context!);
|
||||
case SectionType.location:
|
||||
|
||||
@@ -110,7 +110,7 @@ class DateParseService {
|
||||
|
||||
result = _parseStructuredFormats(lowerInput);
|
||||
if (!result.isEmpty) return result;
|
||||
|
||||
|
||||
final normalized = _normalizeDateString(lowerInput);
|
||||
result = _parseTokenizedDate(normalized);
|
||||
|
||||
@@ -203,7 +203,7 @@ class DateParseService {
|
||||
}
|
||||
return PartialDate.empty;
|
||||
}
|
||||
|
||||
|
||||
match = _standardFormatRegex.firstMatch(cleanInput);
|
||||
if (match != null) {
|
||||
final p1 = int.parse(match.group(1)!);
|
||||
|
||||
@@ -46,7 +46,6 @@ import 'package:photos/services/collections_service.dart';
|
||||
import "package:photos/services/date_parse_service.dart";
|
||||
import "package:photos/services/filter/db_filters.dart";
|
||||
import "package:photos/services/location_service.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/face_filtering/face_filtering_constants.dart";
|
||||
import "package:photos/services/machine_learning/face_ml/person/person_service.dart";
|
||||
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
|
||||
import "package:photos/services/memories_cache_service.dart";
|
||||
@@ -725,7 +724,7 @@ class SearchService {
|
||||
|
||||
Future<List<GenericSearchResult>> getAllFace(
|
||||
int? limit, {
|
||||
int minClusterSize = kMinimumClusterSizeSearchResult,
|
||||
required int minClusterSize,
|
||||
}) async {
|
||||
try {
|
||||
debugPrint("getting faces");
|
||||
@@ -894,13 +893,7 @@ class SearchService {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (facesResult.isEmpty) {
|
||||
if (kMinimumClusterSizeAllFaces < minClusterSize) {
|
||||
return getAllFace(limit, minClusterSize: kMinimumClusterSizeAllFaces);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
if (facesResult.isEmpty) return [];
|
||||
if (limit != null) {
|
||||
return facesResult.sublist(0, min(limit, facesResult.length));
|
||||
} else {
|
||||
|
||||
@@ -344,7 +344,8 @@ class _PasswordEntryPageState extends State<PasswordEntryPage> {
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).passwordStrength(
|
||||
passwordStrengthValue: passwordStrengthText,),
|
||||
passwordStrengthValue: passwordStrengthText,
|
||||
),
|
||||
style: TextStyle(
|
||||
color: passwordStrengthColor,
|
||||
),
|
||||
|
||||
@@ -14,7 +14,8 @@ class DeveloperSettingsWidget extends StatelessWidget {
|
||||
padding: const EdgeInsets.only(bottom: 20),
|
||||
child: Text(
|
||||
AppLocalizations.of(context).customEndpoint(
|
||||
endpoint: "${endpointURI.host}:${endpointURI.port}",),
|
||||
endpoint: "${endpointURI.host}:${endpointURI.port}",
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -283,7 +283,9 @@ class _StorageCardWidgetState extends State<StorageCardWidget> {
|
||||
: const SizedBox.shrink(),
|
||||
Text(
|
||||
AppLocalizations.of(context).availableStorageSpace(
|
||||
freeAmount: freeSpace, storageUnit: freeSpaceUnit,),
|
||||
freeAmount: freeSpace,
|
||||
storageUnit: freeSpaceUnit,
|
||||
),
|
||||
style: getEnteTextTheme(context)
|
||||
.mini
|
||||
.copyWith(color: textFaintDark),
|
||||
|
||||
@@ -180,7 +180,8 @@ class _VerifyIdentifyDialogState extends State<VerifyIdentifyDialog> {
|
||||
.shareMyVerificationID(verificationID: verificationID)
|
||||
: AppLocalizations.of(context)
|
||||
.shareTextConfirmOthersVerificationID(
|
||||
verificationID: verificationID,),
|
||||
verificationID: verificationID,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
|
||||
@@ -366,7 +366,8 @@ class _ImageEditorPageState extends State<ImageEditorPage> {
|
||||
margin: const EdgeInsets.only(bottom: 24),
|
||||
decoration: BoxDecoration(
|
||||
color: isHovered
|
||||
? colorScheme.warning400.withValues(alpha: 0.8)
|
||||
? colorScheme.warning400
|
||||
.withValues(alpha: 0.8)
|
||||
: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
|
||||
@@ -54,7 +54,9 @@ class _FreeSpacePageState extends State<FreeSpacePage> {
|
||||
? AppLocalizations.of(context)
|
||||
.filesBackedUpInAlbum(count: count, formattedNumber: formattedCount)
|
||||
: AppLocalizations.of(context).filesBackedUpFromDevice(
|
||||
count: count, formattedNumber: formattedCount,);
|
||||
count: count,
|
||||
formattedNumber: formattedCount,
|
||||
);
|
||||
final informationTextStyle = TextStyle(
|
||||
fontSize: 14,
|
||||
height: 1.3,
|
||||
@@ -121,7 +123,9 @@ class _FreeSpacePageState extends State<FreeSpacePage> {
|
||||
Expanded(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).freeUpSpaceSaving(
|
||||
count: count, formattedSize: formatBytes(status.size),),
|
||||
count: count,
|
||||
formattedSize: formatBytes(status.size),
|
||||
),
|
||||
style: informationTextStyle,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -153,8 +153,9 @@ class _DeleteEmptyAlbumsState extends State<DeleteEmptyAlbums> {
|
||||
.toString()
|
||||
.padLeft(collections.length.toString().length, '0');
|
||||
_deleteProgress.value = AppLocalizations.of(context).deleteProgress(
|
||||
currentlyDeleting: currentlyDeleting,
|
||||
totalCount: collections.length,);
|
||||
currentlyDeleting: currentlyDeleting,
|
||||
totalCount: collections.length,
|
||||
);
|
||||
try {
|
||||
await CollectionsService.instance.trashEmptyCollection(
|
||||
collections[i],
|
||||
|
||||
@@ -47,15 +47,16 @@ class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
|
||||
final status = event.status;
|
||||
|
||||
// Handle different states - will be false for different files or non-processing states
|
||||
final newProcessingState = widget.file.uploadedFileID == fileId && switch (status) {
|
||||
PreviewItemStatus.inQueue ||
|
||||
PreviewItemStatus.retry ||
|
||||
PreviewItemStatus.compressing ||
|
||||
PreviewItemStatus.uploading =>
|
||||
true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
final newProcessingState = widget.file.uploadedFileID == fileId &&
|
||||
switch (status) {
|
||||
PreviewItemStatus.inQueue ||
|
||||
PreviewItemStatus.retry ||
|
||||
PreviewItemStatus.compressing ||
|
||||
PreviewItemStatus.uploading =>
|
||||
true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Only update state if value changed
|
||||
if (isCurrentlyProcessing != newProcessingState) {
|
||||
isCurrentlyProcessing = newProcessingState;
|
||||
|
||||
@@ -224,7 +224,8 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
||||
context,
|
||||
AppLocalizations.of(context)
|
||||
.typeOfGallerGallerytypeIsNotSupportedForRename(
|
||||
galleryType: "$galleryType",),
|
||||
galleryType: "$galleryType",
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
|
||||
@@ -104,7 +104,8 @@ class _PersonClustersState extends State<PersonReviewClusterSuggestion> {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context).noSuggestionsForPerson(
|
||||
personName: widget.person.data.name,),
|
||||
personName: widget.person.data.name,
|
||||
),
|
||||
style: getEnteTextTheme(context).largeMuted,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -156,7 +156,9 @@ class _LinkContactToPersonSelectionPageState
|
||||
title: context.l10n.linkPersonToEmail(email: emailToLink),
|
||||
icon: Icons.info_outline,
|
||||
body: context.l10n.linkPersonToEmailConfirmation(
|
||||
personName: personName, email: emailToLink,),
|
||||
personName: personName,
|
||||
email: emailToLink,
|
||||
),
|
||||
isDismissible: true,
|
||||
buttons: [
|
||||
ButtonWidget(
|
||||
|
||||
@@ -252,7 +252,7 @@ class SearchWidgetState extends State<SearchWidget> {
|
||||
},
|
||||
);
|
||||
|
||||
_searchService.getAllFace(null).then(
|
||||
_searchService.getAllFace(null, minClusterSize: 10).then(
|
||||
(faceResult) {
|
||||
final List<GenericSearchResult> filteredResults = [];
|
||||
for (final result in faceResult) {
|
||||
|
||||
@@ -42,7 +42,7 @@ class LocalSettings {
|
||||
static const kCollectionViewType = "collection_view_type";
|
||||
static const kCollectionSortDirection = "collection_sort_direction";
|
||||
static const kShowLocalIDOverThumbnails = "show_local_id_over_thumbnails";
|
||||
|
||||
|
||||
// Thumbnail queue configuration keys
|
||||
static const kSmallQueueMaxConcurrent = "small_queue_max_concurrent";
|
||||
static const kSmallQueueTimeout = "small_queue_timeout_seconds";
|
||||
@@ -235,39 +235,41 @@ class LocalSettings {
|
||||
}
|
||||
|
||||
// Thumbnail queue configuration - Small queue
|
||||
int get smallQueueMaxConcurrent => _prefs.getInt(kSmallQueueMaxConcurrent) ?? 15;
|
||||
|
||||
int get smallQueueMaxConcurrent =>
|
||||
_prefs.getInt(kSmallQueueMaxConcurrent) ?? 15;
|
||||
|
||||
int get smallQueueTimeoutSeconds => _prefs.getInt(kSmallQueueTimeout) ?? 60;
|
||||
|
||||
|
||||
int get smallQueueMaxSize => _prefs.getInt(kSmallQueueMaxSize) ?? 200;
|
||||
|
||||
|
||||
Future<void> setSmallQueueMaxConcurrent(int value) async {
|
||||
await _prefs.setInt(kSmallQueueMaxConcurrent, value);
|
||||
}
|
||||
|
||||
|
||||
Future<void> setSmallQueueTimeout(int seconds) async {
|
||||
await _prefs.setInt(kSmallQueueTimeout, seconds);
|
||||
}
|
||||
|
||||
|
||||
Future<void> setSmallQueueMaxSize(int value) async {
|
||||
await _prefs.setInt(kSmallQueueMaxSize, value);
|
||||
}
|
||||
|
||||
// Thumbnail queue configuration - Large queue
|
||||
int get largeQueueMaxConcurrent => _prefs.getInt(kLargeQueueMaxConcurrent) ?? 5;
|
||||
|
||||
int get largeQueueMaxConcurrent =>
|
||||
_prefs.getInt(kLargeQueueMaxConcurrent) ?? 5;
|
||||
|
||||
int get largeQueueTimeoutSeconds => _prefs.getInt(kLargeQueueTimeout) ?? 60;
|
||||
|
||||
|
||||
int get largeQueueMaxSize => _prefs.getInt(kLargeQueueMaxSize) ?? 200;
|
||||
|
||||
|
||||
Future<void> setLargeQueueMaxConcurrent(int value) async {
|
||||
await _prefs.setInt(kLargeQueueMaxConcurrent, value);
|
||||
}
|
||||
|
||||
|
||||
Future<void> setLargeQueueTimeout(int seconds) async {
|
||||
await _prefs.setInt(kLargeQueueTimeout, seconds);
|
||||
}
|
||||
|
||||
|
||||
Future<void> setLargeQueueMaxSize(int value) async {
|
||||
await _prefs.setInt(kLargeQueueMaxSize, value);
|
||||
}
|
||||
|
||||
@@ -1303,10 +1303,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: in_app_purchase
|
||||
sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c"
|
||||
sha256: "11a40f148eeb4f681a0572003e2b33432e110c90c1bbb4f9ef83b81ec0c4f737"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.3"
|
||||
version: "3.2.1"
|
||||
in_app_purchase_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1327,10 +1327,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: in_app_purchase_storekit
|
||||
sha256: ceddd5a70d268f762d29993ed470054bc2baf8793e41800bc82cde05110260d0
|
||||
sha256: "6ce1361278cacc0481508989ba419b2c9f46a2b0dc54b3fe54f5ee63c2718fef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
version: "0.3.22+1"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
||||
@@ -12,7 +12,7 @@ description: ente photos application
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 1.2.4+1205
|
||||
version: 1.2.5+1205
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
@@ -115,7 +115,7 @@ dependencies:
|
||||
html_unescape: ^2.0.0
|
||||
http: ^1.1.0
|
||||
image: ^4.0.17
|
||||
in_app_purchase: ^3.0.7
|
||||
in_app_purchase: 3.2.1
|
||||
intl: ^0.19.0
|
||||
latlong2: ^0.9.0
|
||||
launcher_icon_switcher: ^0.0.2
|
||||
|
||||
@@ -1,20 +1 @@
|
||||
- Ashil: Changes meant for figuring out thumbnail not loading issue (this change has no scope for regressions or bugs)
|
||||
- Ashil: Add new section (show local ID & config local thumb queue) in debug section in settings to help in debugging thumbnail not loading issue.
|
||||
- Ashil: Revert diskLoadDeferDuration to 80ms (Fixes local thumbnails taking ~1 sec to load on scrolling gallery)
|
||||
- Ashil: Revert diskLoadDeferDuration to 500ms (Was 80ms before but fixes local thumbnail taking very long to load or never loading)
|
||||
- Ashil: Revert increase in cache extent for gallery - to check if thumbnail not loading regression resolves
|
||||
- Similar images design changes. Also changed the vectorDB index file name, so internal users will have another migration (long loading time).
|
||||
- Ashil: New ducky icon in icon switcher
|
||||
- Prateek: Enable immediate manual video stream processing by bypassing user interaction timer
|
||||
- Prateek: Fix multiple concurrent streaming processes bug in ComputeController
|
||||
- Prateek: Fix video streaming description text display spacing in advanced settings
|
||||
- Ashil: Render cached thumbnails faster (noticeable in gallery scrolling)
|
||||
- Similar images UI changes
|
||||
- Neeraj: Fix for double enteries for local file
|
||||
- (prtk) Fix widget initial launch on iOS
|
||||
- Similar images debug screen (Settings > Backup > Free up space > Similar images)
|
||||
- (prtk) Upgrade Flutter version to 3.32.8
|
||||
- (prtk) Run FFMpeg in an isolate
|
||||
- Neeraj: Handle custom domain links
|
||||
- Aman: Fixed bottom nav bar color in light theme, resolved paint editor's initial color, and added tap-to-reset with haptics for tune adjustments (brightness/exposure)
|
||||
- Gracefully handle heic rendering on Android
|
||||
- Neeraj: Potential fix for ios in-app payment
|
||||
@@ -1,3 +1 @@
|
||||
- Similar images detection and deletion
|
||||
- Video streaming enhancements
|
||||
- Performance improvements
|
||||
- Bug fixes & performance improvements
|
||||
@@ -1,8 +1,8 @@
|
||||
import "package:ente_configuration/base_configuration.dart";
|
||||
import "package:ente_sharing/extensions/user_extension.dart";
|
||||
import "package:ente_sharing/models/user.dart";
|
||||
import "package:ente_ui/theme/colors.dart";
|
||||
import "package:ente_ui/theme/ente_theme.dart";
|
||||
import "package:ente_utils/extensions/user_extension.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
|
||||
@@ -81,6 +81,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter_lints:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -129,6 +137,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
melos:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
|
||||
@@ -7,4 +7,6 @@ environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dev_dependencies:
|
||||
melos: ^6.0.0
|
||||
melos: ^6.0.0
|
||||
dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
|
||||
@@ -480,6 +480,7 @@ func main() {
|
||||
privateAPI.GET("/files/data/preview", fileHandler.GetPreviewURL)
|
||||
|
||||
privateAPI.POST("/files", fileHandler.CreateOrUpdate)
|
||||
privateAPI.POST("/files/meta", fileHandler.CreateMetaFile)
|
||||
privateAPI.POST("/files/copy", fileHandler.CopyFiles)
|
||||
privateAPI.PUT("/files/update", fileHandler.Update)
|
||||
privateAPI.POST("/files/trash", fileHandler.Trash)
|
||||
|
||||
@@ -26,6 +26,20 @@ type File struct {
|
||||
Info *FileInfo `json:"info,omitempty"`
|
||||
}
|
||||
|
||||
type MetaFile struct {
|
||||
ID int64 `json:"id"`
|
||||
OwnerID int64 `json:"ownerID"`
|
||||
CollectionID int64 `json:"collectionID"`
|
||||
EncryptedKey string `json:"encryptedKey"`
|
||||
KeyDecryptionNonce string `json:"keyDecryptionNonce"`
|
||||
Metadata FileAttributes `json:"metadata" binding:"required"`
|
||||
// IsDeleted is True when the file ID is removed from the CollectionID
|
||||
IsDeleted bool `json:"isDeleted"`
|
||||
UpdationTime int64 `json:"updationTime"`
|
||||
MagicMetadata *MagicMetadata `json:"magicMetadata,omitempty"`
|
||||
PubicMagicMetadata *MagicMetadata `json:"pubMagicMetadata,omitempty"`
|
||||
}
|
||||
|
||||
// FileInfo has information about storage used by the file & it's metadata(future)
|
||||
type FileInfo struct {
|
||||
FileSize int64 `json:"fileSize,omitempty"`
|
||||
|
||||
@@ -2,14 +2,15 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ente-io/museum/pkg/controller/file_copy"
|
||||
"github.com/ente-io/museum/pkg/controller/filedata"
|
||||
"github.com/ente-io/museum/pkg/controller/public"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ente-io/museum/pkg/controller/file_copy"
|
||||
"github.com/ente-io/museum/pkg/controller/filedata"
|
||||
"github.com/ente-io/museum/pkg/controller/public"
|
||||
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-contrib/requestid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -66,6 +67,32 @@ func (h *FileHandler) CreateOrUpdate(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreateMetaFile creates an entry for a file
|
||||
func (h *FileHandler) CreateMetaFile(c *gin.Context) {
|
||||
userID := auth.GetUserID(c.Request.Header)
|
||||
var file ente.MetaFile
|
||||
if err := c.ShouldBindJSON(&file); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
if file.ID != 0 {
|
||||
handler.Error(c, stacktrace.Propagate(ente.ErrBadRequest, "fileID can't be set when creating a new file"))
|
||||
return
|
||||
}
|
||||
file.UpdationTime = time.Microseconds()
|
||||
|
||||
// get an ente.App from the ?app= query parameter with a default of photos
|
||||
enteApp := auth.GetApp(c)
|
||||
file.OwnerID = userID
|
||||
file.IsDeleted = false
|
||||
resp, err := h.Controller.CreateMetaFile(c, userID, file, c.Request.UserAgent(), enteApp)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// CopyFiles copies files that are owned by another user
|
||||
func (h *FileHandler) CopyFiles(c *gin.Context) {
|
||||
var req ente.CopyFileSyncRequest
|
||||
|
||||
28
server/pkg/controller/file_meta.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"github.com/ente-io/museum/ente"
|
||||
"github.com/ente-io/stacktrace"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// CreateMetaFile adds an entry for a file in the respective tables
|
||||
func (c *FileController) CreateMetaFile(ctx *gin.Context, userID int64, file ente.MetaFile, userAgent string, app ente.App) (*ente.File, error) {
|
||||
collection, collErr := c.CollectionRepo.Get(file.CollectionID)
|
||||
if collErr != nil {
|
||||
return nil, stacktrace.Propagate(collErr, "")
|
||||
}
|
||||
// Verify that user owns the collection.
|
||||
// Warning: Do not remove this check
|
||||
if collection.Owner.ID != userID {
|
||||
return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "collection doesn't belong to user")
|
||||
}
|
||||
if collection.IsDeleted {
|
||||
return nil, stacktrace.Propagate(ente.ErrCollectionDeleted, "collection has been deleted")
|
||||
}
|
||||
if file.OwnerID != userID {
|
||||
return nil, stacktrace.Propagate(ente.ErrPermissionDenied, "file ownerID doesn't match with userID")
|
||||
}
|
||||
resp, err := c.FileRepo.CreateMetaFile(file, userID, app)
|
||||
return resp, stacktrace.Propagate(err, "failed to create meta file")
|
||||
}
|
||||
@@ -126,6 +126,73 @@ func (repo *FileRepository) Create(
|
||||
return file, usage, stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
// CreateMetaFile creates an entry in the database for the given file
|
||||
func (repo *FileRepository) CreateMetaFile(
|
||||
metaFile ente.MetaFile,
|
||||
collectionOwnerID int64,
|
||||
app ente.App,
|
||||
) (*ente.File, error) {
|
||||
ctx := context.Background()
|
||||
tx, err := repo.DB.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
if metaFile.OwnerID != collectionOwnerID {
|
||||
return nil, stacktrace.Propagate(errors.New("both file and collection should belong to same owner"), "")
|
||||
}
|
||||
|
||||
var fileID int64
|
||||
info := &ente.FileInfo{
|
||||
FileSize: 0,
|
||||
ThumbnailSize: 0,
|
||||
}
|
||||
err = tx.QueryRowContext(ctx, `INSERT INTO files
|
||||
(owner_id, encrypted_metadata,
|
||||
file_decryption_header, thumbnail_decryption_header, metadata_decryption_header,
|
||||
magic_metadata, pub_magic_metadata, info, updation_time)
|
||||
VALUES($1, $2, $3, $4, $5, $6, $7, $8, $9) RETURNING file_id`,
|
||||
metaFile.OwnerID, metaFile.Metadata.EncryptedData, "",
|
||||
"", metaFile.Metadata.DecryptionHeader,
|
||||
metaFile.MagicMetadata, metaFile.PubicMagicMetadata, info,
|
||||
metaFile.UpdationTime).Scan(&fileID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `INSERT INTO collection_files
|
||||
(collection_id, file_id, encrypted_key, key_decryption_nonce, is_deleted, updation_time, c_owner_id, f_owner_id)
|
||||
VALUES($1, $2, $3, $4, $5, $6, $7, $8)`, metaFile.CollectionID, metaFile.ID,
|
||||
metaFile.EncryptedKey, metaFile.KeyDecryptionNonce, false, metaFile.UpdationTime, metaFile.OwnerID, collectionOwnerID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
_, err = tx.ExecContext(ctx, `UPDATE collections SET updation_time = $1
|
||||
WHERE collection_id = $2`, metaFile.UpdationTime, metaFile.CollectionID)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return nil, stacktrace.Propagate(err, "")
|
||||
}
|
||||
var file ente.File = ente.File{
|
||||
ID: fileID,
|
||||
UpdationTime: metaFile.UpdationTime,
|
||||
OwnerID: metaFile.OwnerID,
|
||||
Metadata: metaFile.Metadata,
|
||||
PubicMagicMetadata: metaFile.PubicMagicMetadata,
|
||||
MagicMetadata: metaFile.MagicMetadata,
|
||||
EncryptedKey: metaFile.EncryptedKey,
|
||||
KeyDecryptionNonce: metaFile.KeyDecryptionNonce,
|
||||
Info: info,
|
||||
CollectionID: collectionOwnerID,
|
||||
}
|
||||
return &file, stacktrace.Propagate(err, "")
|
||||
}
|
||||
|
||||
// markAsNeedingReplication inserts new entries in object_copies, setting the
|
||||
// current hot DC as the source copy.
|
||||
//
|
||||
|
||||