diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 728b46b91..9174e1127 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,8 +10,8 @@ jobs: steps: # Setup Java environment in order to build the Android app. - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: distribution: 'adopt' java-version: '11' @@ -47,18 +47,18 @@ jobs: run: sha256sum build/app/outputs/flutter-apk/ente.apk > build/app/outputs/flutter-apk/sha256sum # Upload generated apk to the artifacts. - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: release-apk path: build/app/outputs/flutter-apk/ente.apk - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v4 with: name: release-checksum path: build/app/outputs/flutter-apk/sha256sum # Create a pre-release - - uses: ncipollo/release-action@v1 + - uses: ncipollo/release-action@v1.14.0 with: artifacts: "build/app/outputs/flutter-apk/ente.apk,build/app/outputs/flutter-apk/sha256sum" token: ${{ secrets.GITHUB_TOKEN }} diff --git a/android/.gitignore b/android/.gitignore index f12b17c55..bc2100d8f 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -5,6 +5,3 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java - -# Signing config files -*.jks \ No newline at end of file diff --git a/fastlane/metadata/android/de/full_description.txt b/fastlane/metadata/android/de/full_description.txt index c20f3a53c..2d953ade9 100644 --- a/fastlane/metadata/android/de/full_description.txt +++ b/fastlane/metadata/android/de/full_description.txt @@ -1,36 +1,36 @@ ente ist eine einfache App, um Ihre Fotos und Videos automatisch zu sichern und zu organisieren. -Wenn Sie auf der Suche nach einer privaten Alternative sind, um Ihre Erinnerungen zu bewahren, sind Sie an der richtigen Stelle. Die müssen nicht mal ente haben. ente benötigt bestimmte Berechtigungen um als Anbieter eines Fotospeichers fungieren zu können. Mit Ente werden sie Ende-zu-Ende-verschlüsselt gespeichert (e2ee). Dies bedeutet, dass nur Sie sie sehen können. +Wenn Sie auf der Suche nach einer datenschutzfreundlichen Alternative zu Google Fotos sind, sind Sie an der richtigen Stelle. Mit Ente werden Ihre Fotos Ende-zu-Ende-verschlüsselt gespeichert (e2ee). Dies bedeutet, dass nur Sie sie sehen können. -Wir haben Open-Source Apps auf allen Plattformen, und Ihre Fotos werden nahtlos zwischen all Ihren Geräten verschlüsselt (e2ee) synchronisiert. +Ihre Fotos werden verschlüsselt (e2ee) zwischen allen Geräten synchronisiert. -ente ermöglicht es deine Alben simpel & schnell mit deinen Geliebten zu teilen. Du kannst öffentlich einsehbare Links teilen, wo sie dein Album sehen und zusammenarbeiten können, indem sie Fotos hinzufügen, sogar ohne einen Account oder eine App. +ente ermöglicht es, deine Alben simpel & schnell mit deinen Geliebten zu teilen. Sie können öffentlich einsehbare Links teilen, sodass andere sogar ohne einen Account oder eine App Ihr Album sehen und darin zusammenarbeiten können, indem sie Fotos hinzufügen. -Ihre verschlüsselten Daten werden zu 3 verschiedenen Orten repliziert, unter anderem zu einem Schutzbunker in Paris. Wir nehmen die Erhaltung der Nachwelt ernst und machen es Dir leicht, dafür zu sorgen, dass Deine Erinnerungen Dich überdauern. +Ihre verschlüsselten Daten werden an 3 verschiedenen Orten gespeichert, unter anderem in einem Schutzbunker in Paris. Wir nehmen die Erhaltung der Nachwelt ernst und machen es Ihnen leicht, dafür zu sorgen, dass Ihre Erinnerungen Sie überdauern. Wir sind hier, um die sicherste Foto-App aller Zeiten zu entwickeln, begleite uns auf unserem Weg! FEATURES -- Sicherungen in Originalqualität, jeder Pixel ist wichtig -- Familienpläne, damit Du den Speicher mit Deiner Familie teilen kannst -- Kollaborative Alben, sodass du nach einer Reise Fotos zusammenstellen kannst -- Geteilte Ordner für den Fall, dass Dein Partner Deine "Kamera" Klicks genießen soll -- Links zu einem Album, welche mit einem Passwort geschützt werden können -- Möglichkeit Speicherplatz freizugeben, indem bereits gesicherte Daten auf dem Gerät entfernt werden -- Menschliche Unterstützung, denn Sie sind es wert +- Sicherungen in Originalqualität, weil jeder Pixel zählt +- Familien-Abos, damit Sie den Speicherplatz mit Ihrer Familie teilen können +- Kollaborative Alben, sodass Sie nach einer Reise Fotos sammeln können +- Geteilte Ordner für den Fall, dass Ihr Partner Ihre "Kamera" Klicks genießen soll +- Album-Links, die mit einem Passwort geschützt werden können +- Möglichkeit, Speicherplatz freizugeben, indem bereits gesicherte Daten auf dem Gerät entfernt werden +- Menschlicher Support, denn Sie sind es wert - Beschreibungen, damit Sie Ihre Erinnerungen beschriften und leicht wiederfinden können -- Integrierte Bildbearbeitung, um den letzten Schliff zu geben -- Favorisiere, verstecke und erlebe deine Erinnerungen, denn sie sind kostbar -- Importieren Sie mit einem Klick von Google, Apple, Ihrer Festplatte und mehr +- Foto-Editor, um Ihren Fotos den Feinschliff zu verpassen +- Favorisieren, verstecken und erleben Sie Ihre Erinnerungen, denn sie sind kostbar +- Ein-Klick-Import von Google, Apple, Ihrer Festplatte und mehr - Dunkles Theme, weil Ihre Fotos darin gut aussehen - 2FA, 3FA, biometrische Authentifizierung - und noch VIELES mehr! BERECHTIGUNGEN -Diese können unter folgendem Link betrachtet werden: https://github.com/ente-io/photos-app/blob/f-droid/android/permissions.md +Diese können unter folgendem Link überprüft werden: https://github.com/ente-io/photos-app/blob/f-droid/android/permissions.md PREIS Wir bieten keine lebenslang kostenlosen Abonnements an, da es für uns wichtig ist, einen nachhaltigen Service anzubieten. Wir bieten jedoch bezahlbare Abonemments an, welche auch mit der Familie geteilt werden können. Mehr Informationen sind auf ente.io zu finden. SUPPORT -Wir sind stolz darauf einen persönlichen Support anzubieten. Falls Sie ein Abonnement besitzen, können Sie sich mit Ihrem Anliegen via E-Mail an team@ente.io wenden und erhalten eine Antwort innerhalb von 24 Stunden. +Wir sind stolz darauf, einen persönlichen Support anzubieten. Falls Sie ein Abonnement besitzen, können Sie sich mit Ihrem Anliegen via E-Mail an team@ente.io wenden und erhalten eine Antwort innerhalb von 24 Stunden. diff --git a/fastlane/metadata/android/pt/full_description.txt b/fastlane/metadata/android/pt/full_description.txt new file mode 100644 index 000000000..ceaa0825d --- /dev/null +++ b/fastlane/metadata/android/pt/full_description.txt @@ -0,0 +1,36 @@ +ente é um aplicativo simples para fazer backup e compartilhar suas fotos e vídeos. + +Se você está procurando uma alternativa ao Google Photos com foco em privacidade, veio ao lugar certo. Com ente, eles são armazenados com criptografia de ponta a ponta (e2ee). Isso significa que só você pode vê-los. + +Temos aplicativos de código aberto em todas as plataformas, Android, iOS, web e desktop, e suas fotos irão sincronizar perfeitamente entre todas elas de forma criptografada (e2ee). + +ente também torna simples compartilhar seus álbuns com seus entes queridos, mesmo que eles não estejam no ente. Você pode compartilhar links para visualização pública, onde eles podem visualizar seu álbum e colaborar adicionando fotos a ele, mesmo sem uma conta ou app. + +Seus dados criptografados são replicados em 3 locais diferentes, incluindo um abrigo avançado em Paris. Levamos a sério a nossa postura e fazemos com que seja fácil garantir que suas memórias vivam. + +Estamos aqui para se tornar o app de fotos mais seguro de todos, venha entrar em nossa jornada! + +RECURSOS +- Cópia de qualidade original, porque cada pixel é importante +- Planos de família, para que você possa compartilhar o armazenamento com sua família +- Álbuns colaborativos, para que você possa agrupar fotos após uma corrida +- Pastas compartilhadas, caso você queira que seu parceiro aproveite seus cliques da "Câmera" +- Links de álbuns, que podem ser protegidos com uma senha e definidos para expirar +- Capacidade de liberar espaço, removendo arquivos que foram salvos com segurança +- Suporte humano, porque você vale a pena +- Descrições, para que você possa captar suas memórias e encontrá-las facilmente +- Editor de imagens, para adicionar toques finais +- Favoritar, esconder e reviver suas memórias, pois elas são preciosas +- Importar com um clique do Google, Apple, seu disco rígido e muito mais +- Tema escuro, porque suas fotos parecem bem nele +- 2FA, 3FA, Autenticação biométrica +- e MUITO MAIS! + +PERMISSÕES +ente solicita certas permissões para servir o propósito de um provedor de armazenamento de fotos, que pode ser revisado aqui: https://github.com/ente-io/photos-app/blob/f-droid/android/permissions.md + +PREÇO +Não oferecemos planos gratuitos para sempre, porque é importante para nós que permaneçamos sustentáveis e resistamos à prova do tempo. Em vez disso, oferecemos planos acessíveis que você pode compartilhar livremente com sua família. Você pode encontrar mais informações em ente.io. + +SUPORTE +Temos orgulho em oferecer apoio humano. Temos orgulho em oferecer apoio humano. Se você é o nosso cliente pago, você pode entrar em contato com o team@ente.io e esperar uma resposta da nossa equipe dentro de 24 horas. diff --git a/fastlane/metadata/android/pt/short_description.txt b/fastlane/metadata/android/pt/short_description.txt new file mode 100644 index 000000000..59c457c0e --- /dev/null +++ b/fastlane/metadata/android/pt/short_description.txt @@ -0,0 +1 @@ +ente é um aplicativo de armazenamento de fotos criptografado de ponta a ponta \ No newline at end of file diff --git a/fastlane/metadata/android/pt/title.txt b/fastlane/metadata/android/pt/title.txt new file mode 100644 index 000000000..dc953a866 --- /dev/null +++ b/fastlane/metadata/android/pt/title.txt @@ -0,0 +1 @@ +ente - armazenamento criptografado de fotos \ No newline at end of file diff --git a/fastlane/metadata/ios/pt/description.txt b/fastlane/metadata/ios/pt/description.txt new file mode 100644 index 000000000..4f3453d15 --- /dev/null +++ b/fastlane/metadata/ios/pt/description.txt @@ -0,0 +1,33 @@ +Ente é um aplicativo simples para fazer backup e compartilhar suas fotos e vídeos. + +Se você está procurando uma alternativa ao Google Photos com foco em privacidade, veio ao lugar certo. Com ente, eles são armazenados com criptografia de ponta a ponta (e2ee). Isso significa que só você pode vê-los. + +Temos aplicativos de código aberto em Android, iOS, web e desktop, e suas fotos irão sincronizar perfeitamente entre todas elas de forma criptografada (e2ee). + +Ente também torna simples compartilhar seus álbuns com seus entes queridos. Você pode compartilhá-los diretamente com outros usuários do Ente, criptografados de ponta a ponta; ou com links publicamente visíveis. + +Seus dados criptografados são replicados em locais diferentes, incluindo um abrigo avançado em Paris. Levamos a sério a nossa postura e fazemos com que seja fácil garantir que suas memórias vivam. + +Estamos aqui para se tornar o app de fotos mais seguro de todos, venha entrar em nossa jornada! + +RECURSOS +- Cópia de qualidade original, porque cada pixel é importante +- Planos de família, para que você possa compartilhar o armazenamento com sua família +- Pastas compartilhadas, caso você queira que seu parceiro aproveite seus cliques da "Câmera" +- Links de álbuns, que podem ser protegidos com uma senha e definidos para expirar +- Capacidade de liberar espaço, removendo arquivos que foram salvos com segurança +- Editor de imagens, para adicionar toques finais +- Favoritar, esconder e reviver suas memórias, pois elas são preciosas +- Importar com um clique de todos os principais provedores de armazenamento +- Tema escuro, porque suas fotos parecem bem nele +- 2FA, 3FA, Autenticação biométrica +- e MUITO MAIS! + +PREÇO +Não oferecemos planos gratuitos para sempre, porque é importante para nós que permaneçamos sustentáveis e resistamos à prova do tempo. Em vez disso, oferecemos planos acessíveis que você pode compartilhar livremente com sua família. Você pode encontrar mais informações em ente.io. + +SUPORTE +Temos orgulho em oferecer apoio humano. Temos orgulho em oferecer apoio humano. Se você é o nosso cliente pago, você pode entrar em contato com o team@ente.io e esperar uma resposta da nossa equipe dentro de 24 horas. + +TERMOS +https://ente.io/terms diff --git a/fastlane/metadata/ios/pt/keywords.txt b/fastlane/metadata/ios/pt/keywords.txt new file mode 100644 index 000000000..4b98c2fdf --- /dev/null +++ b/fastlane/metadata/ios/pt/keywords.txt @@ -0,0 +1 @@ +fotos,fotografia,família,privacidade,nuvem,backup,vídeos,foto,criptografia,armazenamento,álbum,alternativa diff --git a/fastlane/metadata/ios/pt/name.txt b/fastlane/metadata/ios/pt/name.txt new file mode 100644 index 000000000..1f84427c9 --- /dev/null +++ b/fastlane/metadata/ios/pt/name.txt @@ -0,0 +1 @@ +ente Fotos diff --git a/fastlane/metadata/ios/pt/subtitle.txt b/fastlane/metadata/ios/pt/subtitle.txt new file mode 100644 index 000000000..c7fc3ef6c --- /dev/null +++ b/fastlane/metadata/ios/pt/subtitle.txt @@ -0,0 +1 @@ +Armazenamento de fotos criptografado diff --git a/fastlane/metadata/playstore/de/full_description.txt b/fastlane/metadata/playstore/de/full_description.txt index e77602eac..09fc90c31 100644 --- a/fastlane/metadata/playstore/de/full_description.txt +++ b/fastlane/metadata/playstore/de/full_description.txt @@ -2,7 +2,7 @@ Ente ist eine einfache Anwendung zur automatischen Sicherung und Organisation Ih Wenn Sie auf der Suche nach einer datenschutzfreundlichen Alternative sind, um Ihre Erinnerungen zu bewahren, sind Sie hier genau richtig. Mit Ente werden sie Ende-zu-Ende-verschlüsselt (e2ee) gespeichert. Das bedeutet, dass nur Sie sie sehen können. -Wir haben Apps für Android, iOS, Web und Desktop und Ihre Fotos werden nahtlos zwischen all Ihren Geräten auf eine Ende-zu-Ende-verschlüsselte (e2ee) Weise synchronisiert. +Wir haben Open-Source-Apps für Android, iOS, Web und Desktop. Ihre Fotos werden verschlüsselt (e2ee) zwischen allen Geräten synchronisiert. Mit Ente ist es auch ganz einfach, Ihre Alben mit Ihren Lieben zu teilen. Sie können sie entweder direkt mit anderen Ente-Benutzern teilen, Ende-zu-Ende-verschlüsselt, oder mit öffentlich einsehbaren Links. @@ -12,11 +12,11 @@ Wir sind hier, um die sicherste Foto-App aller Zeiten zu entwickeln, begleiten S ✨ FUNKTIONEN - Sicherungen in Originalqualität, denn jeder Pixel ist wichtig -- Familien-Abos, damit Sie den Speicherplatz mit Ihrer Familie teilen können +- Familien-Abos, damit Sie den Speicherplatz mit Ihrer Familie teilen können - Gemeinsame Ordner, falls Sie möchten, dass Ihr Partner Ihre „Kamera“-Klicks genießen kann - Album-Links, die mit einem Passwort geschützt werden können und deren Gültigkeit abläuft - Möglichkeit, Speicherplatz freizugeben, indem Dateien, die sicher gespeichert wurden, entfernt werden -- Bildbearbeitungsprogramm, um den letzten Schliff zu geben +- Foto-Editor, um Ihren Fotos den Feinschliff zu verpassen - Favorisieren, verstecken und erleben Sie Ihre Erinnerungen, denn sie sind kostbar - Ein-Klick-Import von Google, Apple, Ihrer Festplatte und mehr - Dunkles Theme, weil Ihre Fotos darin gut aussehen diff --git a/fastlane/metadata/playstore/pt/full_description.txt b/fastlane/metadata/playstore/pt/full_description.txt new file mode 100644 index 000000000..ba43b09f3 --- /dev/null +++ b/fastlane/metadata/playstore/pt/full_description.txt @@ -0,0 +1,30 @@ +Ente é um aplicativo simples para fazer backup e compartilhar suas fotos e vídeos. + +Se você está procurando uma alternativa ao Google Photos com foco em privacidade, veio ao lugar certo. Com ente, eles são armazenados com criptografados de ponta a ponta (e2ee). Isso significa que só você pode vê-los. + +Temos aplicativos de código aberto em todas as plataformas, Android, iOS, web e desktop, e suas fotos irão sincronizar perfeitamente entre todas elas de forma criptografada (e2ee). + +Ente também torna simples compartilhar seus álbuns com seus entes queridos. Você pode compartilhá-los diretamente com outros usuários do Ente, criptografados de ponta a ponta; ou com links publicamente visíveis. + +Seus dados criptografados são replicados em locais diferentes, incluindo um abrigo avançado em Paris. Levamos a sério a nossa postura e fazemos com que seja fácil garantir que suas memórias vivam. + +Estamos aqui para se tornar o app de fotos mais seguro de todos, venha entrar em nossa jornada! + +✨ RECURSOS +- Cópia de qualidade original, porque cada pixel é importante +- Planos de família, para que você possa compartilhar o armazenamento com sua família +- Pastas compartilhadas, caso você queira que seu parceiro aproveite seus cliques da "Câmera" +- Links de álbuns, que podem ser protegidos com uma senha e definidos para expirar +- Capacidade de liberar espaço, removendo arquivos que foram salvos com segurança +- Editor de imagens, para adicionar toques finais +- Favoritar, esconder e reviver suas memórias, pois elas são preciosas +- Importar com um clique do Google, Apple, seu disco rígido e muito mais +- Tema escuro, porque suas fotos parecem bem nele +- 2FA, 3FA, Autenticação biométrica +- e MUITO MAIS! + +💲 PREÇO +Não oferecemos planos gratuitos para sempre, porque é importante para nós que permaneçamos sustentáveis e resistamos à prova do tempo. Em vez disso, oferecemos planos acessíveis que você pode compartilhar livremente com sua família. Você pode encontrar mais informações em ente.io. + +🙋 SUPORTE +Temos orgulho em oferecer apoio humano. Temos orgulho em oferecer apoio humano. Se você é o nosso cliente pago, você pode entrar em contato com o team@ente.io e esperar uma resposta da nossa equipe dentro de 24 horas. \ No newline at end of file diff --git a/fastlane/metadata/playstore/pt/short_description.txt b/fastlane/metadata/playstore/pt/short_description.txt new file mode 100644 index 000000000..8c2471e26 --- /dev/null +++ b/fastlane/metadata/playstore/pt/short_description.txt @@ -0,0 +1 @@ +Armazenamento de fotos criptografado - backup, organize e compartilhe suas fotos e vídeos \ No newline at end of file diff --git a/fastlane/metadata/playstore/pt/title.txt b/fastlane/metadata/playstore/pt/title.txt new file mode 100644 index 000000000..785d16b51 --- /dev/null +++ b/fastlane/metadata/playstore/pt/title.txt @@ -0,0 +1 @@ +ente Fotos \ No newline at end of file diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e7046d210..8d84abb91 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,8 @@ PODS: - background_fetch (1.2.1): - Flutter + - battery_info (0.0.1): + - Flutter - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift @@ -213,6 +215,7 @@ PODS: DEPENDENCIES: - background_fetch (from `.symlinks/plugins/background_fetch/ios`) + - battery_info (from `.symlinks/plugins/battery_info/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - file_saver (from `.symlinks/plugins/file_saver/ios`) @@ -286,6 +289,8 @@ SPEC REPOS: EXTERNAL SOURCES: background_fetch: :path: ".symlinks/plugins/background_fetch/ios" + battery_info: + :path: ".symlinks/plugins/battery_info/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" device_info_plus: @@ -377,6 +382,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: background_fetch: 896944864b038d2837fc750d470e9841e1e6a363 + battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c connectivity_plus: 53efb943fc2882c8512d84c45707bcabc4c36076 device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ea4cd3588..6813056ec 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -276,6 +276,7 @@ "${BUILT_PRODUCTS_DIR}/SentryPrivate/SentryPrivate.framework", "${BUILT_PRODUCTS_DIR}/Toast/Toast.framework", "${BUILT_PRODUCTS_DIR}/background_fetch/background_fetch.framework", + "${BUILT_PRODUCTS_DIR}/battery_info/battery_info.framework", "${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework", "${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework", "${BUILT_PRODUCTS_DIR}/file_saver/file_saver.framework", @@ -357,6 +358,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SentryPrivate.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Toast.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/background_fetch.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/battery_info.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_saver.framework", diff --git a/lib/app.dart b/lib/app.dart index ca64d2e53..49e742f2f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -13,7 +13,7 @@ import 'package:photos/ente_theme_data.dart'; import "package:photos/generated/l10n.dart"; import "package:photos/l10n/l10n.dart"; import 'package:photos/services/app_lifecycle_service.dart'; -import "package:photos/services/semantic_search/semantic_search_service.dart"; +import "package:photos/services/machine_learning/machine_learning_controller.dart"; import 'package:photos/services/sync_service.dart'; import 'package:photos/ui/tabs/home_widget.dart'; import "package:photos/ui/viewer/actions/file_viewer.dart"; @@ -43,12 +43,8 @@ class EnteApp extends StatefulWidget { } class _EnteAppState extends State with WidgetsBindingObserver { - static const initialInteractionTimeout = Duration(seconds: 10); - static const defaultInteractionTimeout = Duration(seconds: 5); - final _logger = Logger("EnteAppState"); late Locale locale; - late Timer _userInteractionTimer; @override void initState() { @@ -57,7 +53,6 @@ class _EnteAppState extends State with WidgetsBindingObserver { locale = widget.locale; setupIntentAction(); WidgetsBinding.instance.addObserver(this); - _setupInteractionTimer(timeout: initialInteractionTimeout); } setLocale(Locale newLocale) { @@ -76,30 +71,12 @@ class _EnteAppState extends State with WidgetsBindingObserver { } } - void _resetTimer() { - _userInteractionTimer.cancel(); - _setupInteractionTimer(); - } - - void _setupInteractionTimer({Duration timeout = defaultInteractionTimeout}) { - if (Platform.isAndroid || kDebugMode) { - _userInteractionTimer = Timer(timeout, () { - debugPrint("user is not interacting with the app"); - SemanticSearchService.instance.resumeIndexing(); - }); - } else { - SemanticSearchService.instance.resumeIndexing(); - } - } - @override Widget build(BuildContext context) { if (Platform.isAndroid || kDebugMode) { return Listener( onPointerDown: (event) { - SemanticSearchService.instance.pauseIndexing(); - debugPrint("user is interacting with the app"); - _resetTimer(); + MachineLearningController.instance.onUserInteraction(); }, child: AdaptiveTheme( light: lightThemeData, @@ -149,7 +126,6 @@ class _EnteAppState extends State with WidgetsBindingObserver { @override void dispose() { WidgetsBinding.instance.removeObserver(this); - _userInteractionTimer.cancel(); super.dispose(); } diff --git a/lib/core/configuration.dart b/lib/core/configuration.dart index 6b4e1b0ea..647584828 100644 --- a/lib/core/configuration.dart +++ b/lib/core/configuration.dart @@ -25,9 +25,9 @@ import 'package:photos/services/billing_service.dart'; import 'package:photos/services/collections_service.dart'; import 'package:photos/services/favorites_service.dart'; import 'package:photos/services/ignored_files_service.dart'; +import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; import 'package:photos/services/memories_service.dart'; import 'package:photos/services/search_service.dart'; -import "package:photos/services/semantic_search/semantic_search_service.dart"; import 'package:photos/services/sync_service.dart'; import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/file_uploader.dart'; diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 5c70e218f..db105a11f 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -67,4 +67,30 @@ const galleryGridSpacing = 2.0; const searchSectionLimit = 7; -bool isInternalUser = false; +const blackThumbnailBase64 = '/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAEBAQEBAQEB' + + 'AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/2wBDAQEBAQEBAQ' + + 'EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQH/wAARC' + + 'ACWASwDAREAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUF' + + 'BAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk' + + '6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztL' + + 'W2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAA' + + 'AAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVY' + + 'nLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImK' + + 'kpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oAD' + + 'AMBAAIRAxEAPwD/AD/6ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKAC' + + 'gAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' + + 'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACg' + + 'AoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKA' + + 'CgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAK' + + 'ACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoA' + + 'KACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAo' + + 'AKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgAoAKACgD/9k='; diff --git a/lib/db/embeddings_db.dart b/lib/db/embeddings_db.dart index e39a10d0a..a339d4d0d 100644 --- a/lib/db/embeddings_db.dart +++ b/lib/db/embeddings_db.dart @@ -48,6 +48,19 @@ class EmbeddingsDB { return await _isar.embeddings.filter().updationTimeEqualTo(null).findAll(); } + Future deleteEmbeddings(List fileIDs) async { + await _isar.writeTxn(() async { + final embeddings = []; + for (final fileID in fileIDs) { + embeddings.addAll( + await _isar.embeddings.filter().fileIDEqualTo(fileID).findAll(), + ); + } + await _isar.embeddings.deleteAll(embeddings.map((e) => e.id).toList()); + Bus.instance.fire(EmbeddingUpdatedEvent()); + }); + } + Future deleteAllForModel(Model model) async { await _isar.writeTxn(() async { final embeddings = diff --git a/lib/events/machine_learning_control_event.dart b/lib/events/machine_learning_control_event.dart new file mode 100644 index 000000000..be39ec5e3 --- /dev/null +++ b/lib/events/machine_learning_control_event.dart @@ -0,0 +1,7 @@ +import "package:photos/events/event.dart"; + +class MachineLearningControlEvent extends Event { + final bool shouldRun; + + MachineLearningControlEvent(this.shouldRun); +} diff --git a/lib/generated/intl/messages_pt.dart b/lib/generated/intl/messages_pt.dart index fb75fba87..6c3337ba9 100644 --- a/lib/generated/intl/messages_pt.dart +++ b/lib/generated/intl/messages_pt.dart @@ -20,6 +20,24 @@ typedef String MessageIfAbsent(String messageStr, List args); class MessageLookup extends MessageLookupByLibrary { String get localeName => 'pt'; + static String m0(count) => + "${Intl.plural(count, one: 'Adicionar item', other: 'Adicionar itens')}"; + + static String m1(storageAmount, endDate) => + "Seu complemento ${storageAmount} é válido até o dia ${endDate}"; + + static String m2(emailOrName) => "Adicionado por ${emailOrName}"; + + static String m3(albumName) => "Adicionado com sucesso a ${albumName}"; + + static String m4(count) => + "${Intl.plural(count, zero: 'Nenhum Participante', one: '1 Participante', other: '${count} Participantes')}"; + + static String m5(versionValue) => "Versão: ${versionValue}"; + + static String m6(paymentProvider) => + "Por favor, cancele sua assinatura existente do ${paymentProvider} primeiro"; + static String m7(user) => "${user} Não poderá adicionar mais fotos a este álbum\n\nEles ainda poderão remover as fotos existentes adicionadas por eles"; @@ -31,37 +49,171 @@ class MessageLookup extends MessageLookupByLibrary { 'other': 'Você reeinvindicou ${storageAmountInGb} GB até agora', })}"; + static String m9(albumName) => "Link colaborativo criado para ${albumName}"; + + static String m10(familyAdminEmail) => + "Entre em contato com ${familyAdminEmail} para gerenciar sua assinatura"; + + static String m11(provider) => + "Entre em contato conosco pelo e-mail support@ente.io para gerenciar sua assinatura ${provider}."; + + static String m12(count) => + "${Intl.plural(count, one: 'Excluir ${count} item', other: 'Excluir ${count} itens')}"; + + static String m13(currentlyDeleting, totalCount) => + "Excluindo ${currentlyDeleting} / ${totalCount}"; + static String m14(albumName) => "Isso removerá o link público para acessar \"${albumName}\"."; static String m15(supportEmail) => "Por favor, envie um e-mail para ${supportEmail} a partir do seu endereço de e-mail registrado"; + static String m16(count, storageSaved) => + "Você limpou ${Intl.plural(count, one: '${count} arquivo duplicado', other: '${count} arquivos duplicados')}, salvando (${storageSaved}!)"; + + static String m17(count, formattedSize) => + "${count} Arquivos, ${formattedSize} cada"; + + static String m18(newEmail) => "E-mail alterado para ${newEmail}"; + + static String m19(email) => + "${email} Não possui uma conta Ente.\n\nEnvie um convite para compartilhar fotos."; + + static String m20(count, formattedNumber) => + "${Intl.plural(count, one: '1 arquivo', other: '${formattedNumber} arquivos')} neste dispositivo teve um backup seguro"; + + static String m21(count, formattedNumber) => + "${Intl.plural(count, one: '1 arquivo', other: '${formattedNumber} arquivos')} neste álbum teve um backup seguro"; + static String m22(storageAmountInGB) => "${storageAmountInGB} GB cada vez que alguém se inscrever para um plano pago e aplica o seu código"; + static String m23(freeAmount, storageUnit) => + "${freeAmount} ${storageUnit} grátis"; + + static String m24(endDate) => "Teste gratuito acaba em ${endDate}"; + + static String m25(count) => + "Você ainda pode acessar ${Intl.plural(count, one: 'ele', other: 'eles')} no ente contanto que você tenha uma assinatura ativa"; + + static String m26(sizeInMBorGB) => "Liberar ${sizeInMBorGB}"; + + static String m27(count, formattedSize) => + "${Intl.plural(count, one: 'Pode ser excluído do dispositivo para liberar ${formattedSize}', other: 'Eles podem ser excluídos do dispositivo para liberar ${formattedSize}')}"; + + static String m28(currentlyProcessing, totalCount) => + "Processando ${currentlyProcessing} / ${totalCount}"; + + static String m29(count) => + "${Intl.plural(count, one: '${count} item', other: '${count} items')}"; + + static String m30(expiryTime) => "O link irá expirar em ${expiryTime}"; + + static String m31(count, formattedCount) => + "${Intl.plural(count, zero: 'no memories', one: '${formattedCount} memory', other: '${formattedCount} memories')}"; + + static String m32(count) => + "${Intl.plural(count, one: 'Mover item', other: 'Mover itens')}"; + + static String m33(albumName) => "Movido com sucesso para ${albumName}"; + static String m34(passwordStrengthValue) => "Segurança da senha: ${passwordStrengthValue}"; + static String m35(providerName) => + "Por favor, fale com o suporte ${providerName} se você foi cobrado"; + + static String m36(reason) => + "Infelizmente o seu pagamento falhou devido a ${reason}"; + + static String m37(endDate) => + "Teste gratuito válido até ${endDate}.\nVocê pode escolher um plano pago depois."; + + static String m38(toEmail) => + "Por favor, envie-nos um e-mail para ${toEmail}"; + + static String m39(toEmail) => "Por favor, envie os logs para \n${toEmail}"; + + static String m40(storeName) => "Avalie-nos em ${storeName}"; + static String m41(storageInGB) => "3. Ambos ganham ${storageInGB} GB* grátis"; static String m42(userEmail) => "${userEmail} será removido deste álbum compartilhado\n\nQuaisquer fotos adicionadas por eles também serão removidas do álbum"; + static String m43(endDate) => "Renovação de assinatura em ${endDate}"; + + static String m44(count) => + "${Intl.plural(count, one: '${count} resultado encontrado', other: '${count} resultado encontrado')}"; + + static String m45(count) => "${count} Selecionados"; + + static String m46(count, yourCount) => + "${count} Selecionado (${yourCount} seus)"; + + static String m47(verificationID) => + "Aqui está meu ID de verificação para o Ente.io: ${verificationID}"; + + static String m48(verificationID) => + "Ei, você pode confirmar que este é seu ID de verificação do Ente.io? ${verificationID}"; + static String m49(referralCode, referralStorageInGB) => "Código de referência do ente: ${referralCode} \n\nAplique em Configurações → Geral → Indicações para obter ${referralStorageInGB} GB gratuitamente após a sua inscrição em um plano pago\n\nhttps://ente.io"; + static String m50(numberOfPeople) => + "${Intl.plural(numberOfPeople, zero: 'Compartilhe com pessoas específicas', one: 'Compartilhado com 1 pessoa', other: 'Compartilhado com ${numberOfPeople} pessoas')}"; + + static String m51(emailIDs) => "Compartilhado com ${emailIDs}"; + + static String m52(fileType) => + "Este ${fileType} será excluído do seu dispositivo."; + + static String m53(fileType) => + "Este ${fileType} está em ente e no seu dispositivo."; + + static String m54(fileType) => "Este ${fileType} será excluído do ente."; + static String m55(storageAmountInGB) => "${storageAmountInGB} GB"; + static String m56( + usedAmount, usedStorageUnit, totalAmount, totalStorageUnit) => + "${usedAmount} ${usedStorageUnit} de ${totalAmount} ${totalStorageUnit} usado"; + + static String m57(id) => + "Seu ${id} já está vinculado a outra conta ente.\nSe você gostaria de usar seu ${id} com esta conta, por favor contate nosso suporte\'\'"; + + static String m58(endDate) => "Sua assinatura será cancelada em ${endDate}"; + + static String m59(completed, total) => + "${completed}/${total} memórias preservadas"; + static String m60(storageAmountInGB) => "Eles também recebem ${storageAmountInGB} GB"; + static String m61(email) => "Este é o ID de verificação de ${email}"; + + static String m62(count) => + "${Intl.plural(count, zero: '', one: '1 dia', other: '${count} dias')}"; + + static String m63(endDate) => "Válido até ${endDate}"; + + static String m64(email) => "Verificar ${email}"; + static String m65(email) => "Enviamos um e-mail à ${email}"; + static String m66(count) => + "${Intl.plural(count, one: '${count} anos atrás', other: '${count} anos atrás')}"; + + static String m67(storageSaved) => + "Você liberou ${storageSaved} com sucesso!"; + final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { "aNewVersionOfEnteIsAvailable": MessageLookupByLibrary.simpleMessage( "Uma nova versão do ente está disponível."), + "about": MessageLookupByLibrary.simpleMessage("Sobre"), + "account": MessageLookupByLibrary.simpleMessage("Conta"), "accountWelcomeBack": MessageLookupByLibrary.simpleMessage("Bem-vindo de volta!"), "ackPasswordLostWarning": MessageLookupByLibrary.simpleMessage( @@ -72,87 +224,298 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Adicionar um novo email"), "addCollaborator": MessageLookupByLibrary.simpleMessage("Adicionar colaborador"), + "addFromDevice": MessageLookupByLibrary.simpleMessage( + "Adicionar a partir do dispositivo"), + "addItem": m0, + "addLocation": MessageLookupByLibrary.simpleMessage("Adicionar local"), + "addLocationButton": MessageLookupByLibrary.simpleMessage("Adicionar"), "addMore": MessageLookupByLibrary.simpleMessage("Adicione mais"), + "addNew": MessageLookupByLibrary.simpleMessage("Adicionar novo"), + "addOnPageSubtitle": + MessageLookupByLibrary.simpleMessage("Detalhes dos complementos"), + "addOnValidTill": m1, + "addOns": MessageLookupByLibrary.simpleMessage("Complementos"), + "addPhotos": MessageLookupByLibrary.simpleMessage("Adicionar fotos"), + "addSelected": + MessageLookupByLibrary.simpleMessage("Adicionar selecionado"), + "addToAlbum": + MessageLookupByLibrary.simpleMessage("Adicionar ao álbum"), + "addToEnte": MessageLookupByLibrary.simpleMessage("Adicionar ao ente"), "addToHiddenAlbum": - MessageLookupByLibrary.simpleMessage("Add to hidden album"), + MessageLookupByLibrary.simpleMessage("Adicionar a álbum oculto"), "addViewer": MessageLookupByLibrary.simpleMessage("Adicionar visualizador"), + "addYourPhotosNow": + MessageLookupByLibrary.simpleMessage("Adicione suas fotos agora"), "addedAs": MessageLookupByLibrary.simpleMessage("Adicionado como"), + "addedBy": m2, + "addedSuccessfullyTo": m3, "addingToFavorites": MessageLookupByLibrary.simpleMessage( "Adicionando aos favoritos..."), + "advanced": MessageLookupByLibrary.simpleMessage("Avançado"), + "advancedSettings": MessageLookupByLibrary.simpleMessage("Avançado"), + "after1Day": MessageLookupByLibrary.simpleMessage("Após 1 dia"), + "after1Hour": MessageLookupByLibrary.simpleMessage("Após 1 hora"), + "after1Month": MessageLookupByLibrary.simpleMessage("Após 1 mês"), + "after1Week": MessageLookupByLibrary.simpleMessage("Após 1 semana"), + "after1Year": MessageLookupByLibrary.simpleMessage("Após 1 ano"), "albumOwner": MessageLookupByLibrary.simpleMessage("Proprietário"), + "albumParticipantsCount": m4, + "albumTitle": MessageLookupByLibrary.simpleMessage("Título do álbum"), + "albumUpdated": + MessageLookupByLibrary.simpleMessage("Álbum atualizado"), + "albums": MessageLookupByLibrary.simpleMessage("Álbuns"), + "allClear": MessageLookupByLibrary.simpleMessage("✨ Tudo limpo"), + "allMemoriesPreserved": MessageLookupByLibrary.simpleMessage( + "Todas as memórias preservadas"), "allowAddPhotosDescription": MessageLookupByLibrary.simpleMessage( "Permita que as pessoas com o link também adicionem fotos ao álbum compartilhado."), "allowAddingPhotos": MessageLookupByLibrary.simpleMessage("Permitir adicionar fotos"), "allowDownloads": MessageLookupByLibrary.simpleMessage("Permitir transferências"), + "allowPeopleToAddPhotos": MessageLookupByLibrary.simpleMessage( + "Permitir que pessoas adicionem fotos"), + "androidBiometricHint": + MessageLookupByLibrary.simpleMessage("Verificar identidade"), + "androidBiometricNotRecognized": MessageLookupByLibrary.simpleMessage( + "Não reconhecido. Tente novamente."), + "androidBiometricRequiredTitle": + MessageLookupByLibrary.simpleMessage("Biométrica necessária"), + "androidBiometricSuccess": + MessageLookupByLibrary.simpleMessage("Sucesso"), + "androidCancelButton": MessageLookupByLibrary.simpleMessage("Cancelar"), + "androidDeviceCredentialsRequiredTitle": + MessageLookupByLibrary.simpleMessage( + "Credenciais do dispositivo necessárias"), + "androidDeviceCredentialsSetupDescription": + MessageLookupByLibrary.simpleMessage( + "Credenciais do dispositivo necessárias"), + "androidGoToSettingsDescription": MessageLookupByLibrary.simpleMessage( + "A autenticação biométrica não está configurada no seu dispositivo. Vá em \'Configurações > Segurança\' para adicionar autenticação biométrica."), + "androidIosWebDesktop": + MessageLookupByLibrary.simpleMessage("Android, iOS, Web, Desktop"), + "androidSignInTitle": + MessageLookupByLibrary.simpleMessage("Autenticação necessária"), + "appVersion": m5, + "appleId": MessageLookupByLibrary.simpleMessage("ID da Apple"), + "apply": MessageLookupByLibrary.simpleMessage("Aplicar"), + "applyCodeTitle": + MessageLookupByLibrary.simpleMessage("Aplicar código"), + "appstoreSubscription": + MessageLookupByLibrary.simpleMessage("Assinatura da AppStore"), + "archive": MessageLookupByLibrary.simpleMessage("Arquivo"), + "archiveAlbum": MessageLookupByLibrary.simpleMessage("Arquivar álbum"), + "archiving": MessageLookupByLibrary.simpleMessage("Arquivando..."), + "areYouSureThatYouWantToLeaveTheFamily": + MessageLookupByLibrary.simpleMessage( + "Tem certeza que deseja sair do plano familiar?"), + "areYouSureYouWantToCancel": MessageLookupByLibrary.simpleMessage( + "Tem certeza que deseja cancelar?"), + "areYouSureYouWantToChangeYourPlan": + MessageLookupByLibrary.simpleMessage( + "Tem certeza que deseja trocar de plano?"), + "areYouSureYouWantToExit": MessageLookupByLibrary.simpleMessage( + "Tem certeza de que deseja sair?"), "areYouSureYouWantToLogout": MessageLookupByLibrary.simpleMessage( "Você tem certeza que deseja encerrar a sessão?"), + "areYouSureYouWantToRenew": MessageLookupByLibrary.simpleMessage( + "Tem certeza de que deseja renovar?"), + "askCancelReason": MessageLookupByLibrary.simpleMessage( + "Sua assinatura foi cancelada. Gostaria de compartilhar o motivo?"), "askDeleteReason": MessageLookupByLibrary.simpleMessage( "Qual é o principal motivo para você excluir sua conta?"), + "askYourLovedOnesToShare": MessageLookupByLibrary.simpleMessage( + "Peça que seus entes queridos compartilhem"), + "atAFalloutShelter": + MessageLookupByLibrary.simpleMessage("em um abrigo avançado"), + "authToChangeEmailVerificationSetting": + MessageLookupByLibrary.simpleMessage( + "Por favor, autentique-se para alterar seu e-mail"), + "authToChangeLockscreenSetting": MessageLookupByLibrary.simpleMessage( + "Por favor, autentique-se para alterar a configuração da tela de bloqueio"), "authToChangeYourEmail": MessageLookupByLibrary.simpleMessage( "Por favor, autentique-se para alterar seu e-mail"), "authToChangeYourPassword": MessageLookupByLibrary.simpleMessage( "Por favor, autentique-se para alterar sua senha"), + "authToConfigureTwofactorAuthentication": + MessageLookupByLibrary.simpleMessage( + "Por favor, autentique-se para configurar a autenticação de dois fatores"), "authToInitiateAccountDeletion": MessageLookupByLibrary.simpleMessage( "Por favor, autentique-se para iniciar a exclusão de conta"), + "authToViewYourActiveSessions": MessageLookupByLibrary.simpleMessage( + "Por favor, autentique-se para ver as sessões ativas"), + "authToViewYourHiddenFiles": MessageLookupByLibrary.simpleMessage( + "Autentique-se para visualizar seus arquivos ocultos"), + "authToViewYourMemories": MessageLookupByLibrary.simpleMessage( + "Por favor, autentique para ver suas memórias"), + "authToViewYourRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Por favor, autentique-se para visualizar sua chave de recuperação"), + "authenticating": + MessageLookupByLibrary.simpleMessage("Autenticando..."), + "authenticationFailedPleaseTryAgain": + MessageLookupByLibrary.simpleMessage( + "Falha na autenticação. Por favor, tente novamente"), + "authenticationSuccessful": + MessageLookupByLibrary.simpleMessage("Autenticação bem-sucedida!"), + "available": MessageLookupByLibrary.simpleMessage("Disponível"), + "backedUpFolders": + MessageLookupByLibrary.simpleMessage("Backup de pastas concluído"), + "backup": MessageLookupByLibrary.simpleMessage("Backup"), + "backupFailed": + MessageLookupByLibrary.simpleMessage("Erro ao efetuar o backup"), + "backupOverMobileData": + MessageLookupByLibrary.simpleMessage("Backup de dados móveis"), + "backupSettings": + MessageLookupByLibrary.simpleMessage("Configurações de backup"), + "backupVideos": + MessageLookupByLibrary.simpleMessage("Backup de videos"), + "blackFridaySale": + MessageLookupByLibrary.simpleMessage("Promoção da Black Friday"), + "blog": MessageLookupByLibrary.simpleMessage("Blog"), + "cachedData": MessageLookupByLibrary.simpleMessage("Dados em cache"), + "calculating": MessageLookupByLibrary.simpleMessage("Calculando..."), + "canNotUploadToAlbumsOwnedByOthers": + MessageLookupByLibrary.simpleMessage( + "Não é possível enviar para álbuns pertencentes a outros"), + "canOnlyCreateLinkForFilesOwnedByYou": + MessageLookupByLibrary.simpleMessage( + "Só é possível criar um link para arquivos pertencentes a você"), "canOnlyRemoveFilesOwnedByYou": MessageLookupByLibrary.simpleMessage( "Só é possível remover arquivos de sua propriedade"), "cancel": MessageLookupByLibrary.simpleMessage("Cancelar"), + "cancelOtherSubscription": m6, + "cancelSubscription": + MessageLookupByLibrary.simpleMessage("Cancelar assinatura"), "cannotAddMorePhotosAfterBecomingViewer": m7, + "cannotDeleteSharedFiles": MessageLookupByLibrary.simpleMessage( + "Não é possível excluir arquivos compartilhados"), + "castInstruction": MessageLookupByLibrary.simpleMessage( + "Visite cast.ente.io no dispositivo que você deseja parear.\n\ndigite o código abaixo para reproduzir o álbum em sua TV."), + "centerPoint": MessageLookupByLibrary.simpleMessage("Ponto central"), "changeEmail": MessageLookupByLibrary.simpleMessage("Mudar e-mail"), "changeLocationOfSelectedItems": MessageLookupByLibrary.simpleMessage( - "Change location of selected items?"), + "Alterar o local dos itens selecionados?"), "changePassword": MessageLookupByLibrary.simpleMessage("Mude sua senha"), "changePasswordTitle": - MessageLookupByLibrary.simpleMessage("Mude sua senha"), + MessageLookupByLibrary.simpleMessage("Alterar senha"), "changePermissions": MessageLookupByLibrary.simpleMessage("Alterar permissões?"), + "checkForUpdates": + MessageLookupByLibrary.simpleMessage("Verificar por atualizações"), "checkInboxAndSpamFolder": MessageLookupByLibrary.simpleMessage( "Verifique sua caixa de entrada (e ‘spam’) para concluir a verificação"), + "checking": MessageLookupByLibrary.simpleMessage("Verificando..."), "claimFreeStorage": MessageLookupByLibrary.simpleMessage( - "Solicitar armazenamento gratuito"), + "Reivindicar armazenamento gratuito"), "claimMore": MessageLookupByLibrary.simpleMessage("Reivindique mais!"), "claimed": MessageLookupByLibrary.simpleMessage("Reivindicado"), "claimedStorageSoFar": m8, + "cleanUncategorized": + MessageLookupByLibrary.simpleMessage("Limpar Sem Categoria"), + "clearCaches": MessageLookupByLibrary.simpleMessage("Limpar cache"), + "clearIndexes": MessageLookupByLibrary.simpleMessage("Limpar índices"), + "click": MessageLookupByLibrary.simpleMessage("Clique"), + "clickOnTheOverflowMenu": + MessageLookupByLibrary.simpleMessage("• Clique no menu adicional"), + "close": MessageLookupByLibrary.simpleMessage("Fechar"), + "clubByCaptureTime": MessageLookupByLibrary.simpleMessage( + "Agrupar por tempo de captura"), + "clubByFileName": MessageLookupByLibrary.simpleMessage( + "Agrupar pelo nome de arquivo"), + "codeAppliedPageTitle": + MessageLookupByLibrary.simpleMessage("Código aplicado"), "codeCopiedToClipboard": MessageLookupByLibrary.simpleMessage( "Código copiado para a área de transferência"), "codeUsedByYou": MessageLookupByLibrary.simpleMessage("Código usado por você"), + "collabLinkSectionDescription": MessageLookupByLibrary.simpleMessage( + "Crie um link para permitir pessoas adicionar e ver fotos no seu álbum compartilhado sem a necessidade do aplicativo ou uma conta Ente. Ótimo para colecionar fotos de eventos."), + "collaborativeLink": + MessageLookupByLibrary.simpleMessage("Link Colaborativo"), + "collaborativeLinkCreatedFor": m9, "collaborator": MessageLookupByLibrary.simpleMessage("Colaborador"), "collaboratorsCanAddPhotosAndVideosToTheSharedAlbum": MessageLookupByLibrary.simpleMessage( "Os colaboradores podem adicionar fotos e vídeos ao álbum compartilhado."), + "collageLayout": MessageLookupByLibrary.simpleMessage("Layout"), + "collageSaved": + MessageLookupByLibrary.simpleMessage("Colagem salva na galeria"), + "collectEventPhotos": + MessageLookupByLibrary.simpleMessage("Coletar fotos do evento"), + "collectPhotos": MessageLookupByLibrary.simpleMessage("Colete fotos"), + "color": MessageLookupByLibrary.simpleMessage("Cor"), "confirm": MessageLookupByLibrary.simpleMessage("Confirme"), + "confirm2FADisable": MessageLookupByLibrary.simpleMessage( + "Você tem certeza de que deseja desativar a autenticação de dois fatores?"), "confirmAccountDeletion": MessageLookupByLibrary.simpleMessage("Confirmar exclusão da conta"), "confirmDeletePrompt": MessageLookupByLibrary.simpleMessage( "Sim, desejo excluir permanentemente esta conta e todos os seus dados."), "confirmPassword": MessageLookupByLibrary.simpleMessage("Confirme sua senha"), + "confirmPlanChange": + MessageLookupByLibrary.simpleMessage("Confirmar mudança de plano"), "confirmRecoveryKey": MessageLookupByLibrary.simpleMessage( "Confirme a chave de recuperação"), "confirmYourRecoveryKey": MessageLookupByLibrary.simpleMessage( "Confirme sua chave de recuperação"), + "contactFamilyAdmin": m10, "contactSupport": - MessageLookupByLibrary.simpleMessage("Falar com o suporte"), - "contacts": MessageLookupByLibrary.simpleMessage("Contacts"), + MessageLookupByLibrary.simpleMessage("Contate o suporte"), + "contactToManageSubscription": m11, + "contacts": MessageLookupByLibrary.simpleMessage("Contatos"), + "contents": MessageLookupByLibrary.simpleMessage("Conteúdos"), "continueLabel": MessageLookupByLibrary.simpleMessage("Continuar"), + "continueOnFreeTrial": + MessageLookupByLibrary.simpleMessage("Continuar em teste gratuito"), + "convertToAlbum": + MessageLookupByLibrary.simpleMessage("Converter para álbum"), + "copyEmailAddress": + MessageLookupByLibrary.simpleMessage("Copiar endereço de e-mail"), + "copyLink": MessageLookupByLibrary.simpleMessage("Copiar link"), "copypasteThisCodentoYourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Copie e cole este código\npara seu aplicativo autenticador"), + "couldNotBackUpTryLater": MessageLookupByLibrary.simpleMessage( + "Não foi possível fazer o backup de seus dados.\nTentaremos novamente mais tarde."), + "couldNotFreeUpSpace": MessageLookupByLibrary.simpleMessage( + "Não foi possível liberar espaço"), + "couldNotUpdateSubscription": MessageLookupByLibrary.simpleMessage( + "Não foi possível atualizar a assinatura"), + "count": MessageLookupByLibrary.simpleMessage("Contagem"), + "crashReporting": + MessageLookupByLibrary.simpleMessage("Relatório de falhas"), + "create": MessageLookupByLibrary.simpleMessage("Criar"), "createAccount": MessageLookupByLibrary.simpleMessage("Criar uma conta"), + "createAlbumActionHint": MessageLookupByLibrary.simpleMessage( + "Pressione e segure para selecionar fotos e clique em + para criar um álbum"), + "createCollage": MessageLookupByLibrary.simpleMessage("Criar colagem"), "createNewAccount": MessageLookupByLibrary.simpleMessage("Criar nova conta"), + "createOrSelectAlbum": + MessageLookupByLibrary.simpleMessage("Criar ou selecionar álbum"), + "createPublicLink": + MessageLookupByLibrary.simpleMessage("Criar link público"), "creatingLink": MessageLookupByLibrary.simpleMessage("Criando link..."), "criticalUpdateAvailable": MessageLookupByLibrary.simpleMessage( "Atualização crítica disponível"), + "currentUsageIs": + MessageLookupByLibrary.simpleMessage("O uso atual é "), + "custom": MessageLookupByLibrary.simpleMessage("Personalizado"), + "darkTheme": MessageLookupByLibrary.simpleMessage("Escuro"), + "dayToday": MessageLookupByLibrary.simpleMessage("Hoje"), + "dayYesterday": MessageLookupByLibrary.simpleMessage("Ontem"), "decrypting": MessageLookupByLibrary.simpleMessage("Descriptografando..."), - "deleteAccount": MessageLookupByLibrary.simpleMessage("Deletar conta"), + "decryptingVideo": + MessageLookupByLibrary.simpleMessage("Descriptografando vídeo..."), + "deduplicateFiles": + MessageLookupByLibrary.simpleMessage("Arquivos Deduplicados"), + "delete": MessageLookupByLibrary.simpleMessage("Apagar"), + "deleteAccount": MessageLookupByLibrary.simpleMessage("Excluir conta"), "deleteAccountFeedbackPrompt": MessageLookupByLibrary.simpleMessage( "Lamentamos ver você partir. Por favor, compartilhe seus comentários para nos ajudar a melhorar."), "deleteAccountPermanentlyButton": MessageLookupByLibrary.simpleMessage( @@ -160,11 +523,27 @@ class MessageLookup extends MessageLookupByLibrary { "deleteAlbum": MessageLookupByLibrary.simpleMessage("Excluir álbum"), "deleteAlbumDialog": MessageLookupByLibrary.simpleMessage( "Também excluir as fotos (e vídeos) presentes neste álbum de todos os outros álbuns dos quais eles fazem parte?"), + "deleteAlbumsDialogBody": MessageLookupByLibrary.simpleMessage( + "Isto irá apagar todos os álbuns vazios. Isso é útil quando você deseja reduzir a bagunça na sua lista de álbuns."), + "deleteAll": MessageLookupByLibrary.simpleMessage("Excluir Tudo"), "deleteConfirmDialogBody": MessageLookupByLibrary.simpleMessage( - "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted."), + "Esta conta está vinculada a outros aplicativos ente, se você usar algum. Seus dados enviados, em todos os aplicativos ente, serão agendados para exclusão, e sua conta será excluída permanentemente."), "deleteEmailRequest": MessageLookupByLibrary.simpleMessage( "Por favor, envie um e-mail para account-deletion@ente.io a partir do seu endereço de e-mail registrado."), + "deleteEmptyAlbums": + MessageLookupByLibrary.simpleMessage("Excluir álbuns vazios"), + "deleteEmptyAlbumsWithQuestionMark": + MessageLookupByLibrary.simpleMessage("Excluir álbuns vazios?"), + "deleteFromBoth": + MessageLookupByLibrary.simpleMessage("Excluir de ambos"), + "deleteFromDevice": + MessageLookupByLibrary.simpleMessage("Excluir do dispositivo"), + "deleteFromEnte": + MessageLookupByLibrary.simpleMessage("Excluir do ente"), + "deleteItemCount": m12, + "deleteLocation": MessageLookupByLibrary.simpleMessage("Excluir Local"), "deletePhotos": MessageLookupByLibrary.simpleMessage("Excluir fotos"), + "deleteProgress": m13, "deleteReason1": MessageLookupByLibrary.simpleMessage( "Está faltando um recurso-chave que eu preciso"), "deleteReason2": MessageLookupByLibrary.simpleMessage( @@ -179,30 +558,106 @@ class MessageLookup extends MessageLookupByLibrary { "Excluir álbum compartilhado?"), "deleteSharedAlbumDialogBody": MessageLookupByLibrary.simpleMessage( "O álbum será apagado para todos\n\nVocê perderá o acesso a fotos compartilhadas neste álbum que pertencem aos outros"), + "deselectAll": MessageLookupByLibrary.simpleMessage("Desmarcar todos"), + "designedToOutlive": + MessageLookupByLibrary.simpleMessage("Feito para ter logenvidade"), "details": MessageLookupByLibrary.simpleMessage("Detalhes"), + "devAccountChanged": MessageLookupByLibrary.simpleMessage( + "A conta de desenvolvedor que usamos para publicar o ente na App Store foi alterada. Por esse motivo, você precisará fazer login novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável."), + "deviceCodeHint": + MessageLookupByLibrary.simpleMessage("Insira o código"), + "deviceFilesAutoUploading": MessageLookupByLibrary.simpleMessage( + "Arquivos adicionados a este álbum do dispositivo serão automaticamente enviados para o ente."), + "deviceLockExplanation": MessageLookupByLibrary.simpleMessage( + "Desative o bloqueio de tela do dispositivo quando o ente estiver em primeiro plano e houver um backup em andamento. Isso normalmente não é necessário, mas pode ajudar grandes uploads e importações iniciais de grandes bibliotecas a serem concluídos mais rapidamente."), + "deviceNotFound": + MessageLookupByLibrary.simpleMessage("Dispositivo não encontrado"), + "didYouKnow": MessageLookupByLibrary.simpleMessage("Você sabia?"), + "disableAutoLock": MessageLookupByLibrary.simpleMessage( + "Desativar bloqueio automático"), "disableDownloadWarningBody": MessageLookupByLibrary.simpleMessage( "Os espectadores ainda podem tirar screenshots ou salvar uma cópia de suas fotos usando ferramentas externas"), "disableDownloadWarningTitle": MessageLookupByLibrary.simpleMessage("Observe"), "disableLinkMessage": m14, + "disableTwofactor": MessageLookupByLibrary.simpleMessage( + "Desativar autenticação de dois fatores"), + "disablingTwofactorAuthentication": + MessageLookupByLibrary.simpleMessage( + "Desativando a autenticação de dois fatores..."), + "discord": MessageLookupByLibrary.simpleMessage("Discord"), + "dismiss": MessageLookupByLibrary.simpleMessage("Descartar"), + "distanceInKMUnit": MessageLookupByLibrary.simpleMessage("km"), + "doNotSignOut": + MessageLookupByLibrary.simpleMessage("Não encerrar sessão"), "doThisLater": MessageLookupByLibrary.simpleMessage("Fazer isso mais tarde"), + "doYouWantToDiscardTheEditsYouHaveMade": + MessageLookupByLibrary.simpleMessage( + "Você quer descartar as edições que você fez?"), + "done": MessageLookupByLibrary.simpleMessage("Concluído"), + "doubleYourStorage": + MessageLookupByLibrary.simpleMessage("Dobre seu armazenamento"), + "download": MessageLookupByLibrary.simpleMessage("Baixar"), + "downloadFailed": + MessageLookupByLibrary.simpleMessage("Falha ao baixar"), + "downloading": MessageLookupByLibrary.simpleMessage("Baixando..."), "dropSupportEmail": m15, - "editLocation": MessageLookupByLibrary.simpleMessage("Edit location"), + "duplicateFileCountWithStorageSaved": m16, + "duplicateItemsGroup": m17, + "edit": MessageLookupByLibrary.simpleMessage("Editar"), + "editLocation": MessageLookupByLibrary.simpleMessage("Editar local"), + "editLocationTagTitle": + MessageLookupByLibrary.simpleMessage("Editar local"), + "editsSaved": MessageLookupByLibrary.simpleMessage("Edições salvas"), "editsToLocationWillOnlyBeSeenWithinEnte": MessageLookupByLibrary.simpleMessage( - "Edits to location will only be seen within Ente"), + "Edições para local só serão vistas dentro do Ente"), "eligible": MessageLookupByLibrary.simpleMessage("elegível"), "email": MessageLookupByLibrary.simpleMessage("E-mail"), + "emailChangedTo": m18, + "emailNoEnteAccount": m19, + "emailVerificationToggle": + MessageLookupByLibrary.simpleMessage("Verificação de e-mail"), + "emailYourLogs": + MessageLookupByLibrary.simpleMessage("Enviar por email seus logs"), + "empty": MessageLookupByLibrary.simpleMessage("Vazio"), + "emptyTrash": + MessageLookupByLibrary.simpleMessage("Esvaziar a lixeira?"), + "enableMaps": MessageLookupByLibrary.simpleMessage("Habilitar mapa"), + "enableMapsDesc": MessageLookupByLibrary.simpleMessage( + "Isto mostrará suas fotos em um mapa do mundo.\n\nEste mapa é hospedado pelo Open Street Map, e os exatos locais de suas fotos nunca são compartilhados.\n\nVocê pode desativar esse recurso a qualquer momento nas Configurações."), + "encryptingBackup": + MessageLookupByLibrary.simpleMessage("Criptografando backup..."), "encryption": MessageLookupByLibrary.simpleMessage("Criptografia"), "encryptionKeys": MessageLookupByLibrary.simpleMessage("Chaves de criptografia"), + "endtoendEncryptedByDefault": MessageLookupByLibrary.simpleMessage( + "Criptografia de ponta a ponta por padrão"), + "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": + MessageLookupByLibrary.simpleMessage( + "ente pode criptografar e preservar arquivos somente se você conceder acesso a eles"), + "entePhotosPerm": MessageLookupByLibrary.simpleMessage( + "Ente precisa de permissão para preservar suas fotos"), + "enteSubscriptionPitch": MessageLookupByLibrary.simpleMessage( + "O ente preserva suas memórias, então eles estão sempre disponíveis para você, mesmo se você perder o seu dispositivo."), + "enteSubscriptionShareWithFamily": MessageLookupByLibrary.simpleMessage( + "Sua família também pode ser adicionada ao seu plano."), + "enterAlbumName": + MessageLookupByLibrary.simpleMessage("Digite o nome do álbum"), "enterCode": MessageLookupByLibrary.simpleMessage("Coloque o código"), + "enterCodeDescription": MessageLookupByLibrary.simpleMessage( + "Digite o código fornecido pelo seu amigo para reivindicar o armazenamento gratuito para vocês dois"), "enterEmail": MessageLookupByLibrary.simpleMessage("Digite o email"), + "enterFileName": + MessageLookupByLibrary.simpleMessage("Digite o nome do arquivo"), "enterNewPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "Insira uma senha nova para criptografar seus dados"), + "enterPassword": MessageLookupByLibrary.simpleMessage("Digite a senha"), "enterPasswordToEncrypt": MessageLookupByLibrary.simpleMessage( "Insira a senha para criptografar seus dados"), + "enterReferralCode": MessageLookupByLibrary.simpleMessage( + "Insira o código de referência"), "enterThe6digitCodeFromnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Digite o código de 6 dígitos de\nseu aplicativo autenticador"), @@ -214,29 +669,123 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Insira sua senha"), "enterYourRecoveryKey": MessageLookupByLibrary.simpleMessage( "Digite sua chave de recuperação"), + "error": MessageLookupByLibrary.simpleMessage("Erro"), + "everywhere": + MessageLookupByLibrary.simpleMessage("em todos os lugares"), + "exif": MessageLookupByLibrary.simpleMessage("EXIF"), + "existingUser": + MessageLookupByLibrary.simpleMessage("Usuário existente"), + "expiredLinkInfo": MessageLookupByLibrary.simpleMessage( + "Este link expirou. Por favor, selecione um novo tempo de expiração ou desabilite a expiração do link."), + "exportLogs": MessageLookupByLibrary.simpleMessage("Exportar logs"), "exportYourData": MessageLookupByLibrary.simpleMessage("Exportar seus dados"), + "faces": MessageLookupByLibrary.simpleMessage("Rostos"), + "failedToApplyCode": + MessageLookupByLibrary.simpleMessage("Falha ao aplicar o código"), + "failedToCancel": + MessageLookupByLibrary.simpleMessage("Falha ao cancelar"), + "failedToDownloadVideo": + MessageLookupByLibrary.simpleMessage("Falha ao baixar vídeo"), + "failedToFetchOriginalForEdit": MessageLookupByLibrary.simpleMessage( + "Falha ao obter original para edição"), "failedToFetchReferralDetails": MessageLookupByLibrary.simpleMessage( "Não foi possível buscar informações do produto. Por favor, tente novamente mais tarde."), + "failedToLoadAlbums": + MessageLookupByLibrary.simpleMessage("Falha ao carregar álbuns"), + "failedToRenew": + MessageLookupByLibrary.simpleMessage("Falha ao renovar"), + "failedToVerifyPaymentStatus": MessageLookupByLibrary.simpleMessage( + "Falha ao verificar status do pagamento"), + "familyPlanOverview": MessageLookupByLibrary.simpleMessage( + "Adicione 5 membros da família ao seu plano existente sem pagar a mais.\n\nCada membro recebe seu próprio espaço privado, e nenhum membro pode ver os arquivos uns dos outros a menos que sejam compartilhados.\n\nPlanos de família estão disponíveis para os clientes que têm uma assinatura de ente paga.\n\nassine agora para começar!"), + "familyPlanPortalTitle": + MessageLookupByLibrary.simpleMessage("Família"), + "familyPlans": MessageLookupByLibrary.simpleMessage("Plano familiar"), "faq": MessageLookupByLibrary.simpleMessage("Perguntas frequentes"), - "feedback": MessageLookupByLibrary.simpleMessage("Opinião"), - "fileTypes": MessageLookupByLibrary.simpleMessage("File types"), + "faqs": MessageLookupByLibrary.simpleMessage("Perguntas frequentes"), + "favorite": MessageLookupByLibrary.simpleMessage("Favoritar"), + "feedback": MessageLookupByLibrary.simpleMessage("Comentários"), + "fileFailedToSaveToGallery": MessageLookupByLibrary.simpleMessage( + "Falha ao salvar o arquivo na galeria"), + "fileInfoAddDescHint": + MessageLookupByLibrary.simpleMessage("Adicionar descrição..."), + "fileSavedToGallery": + MessageLookupByLibrary.simpleMessage("Vídeo salvo na galeria"), + "fileTypes": MessageLookupByLibrary.simpleMessage("Tipos de arquivo"), + "fileTypesAndNames": + MessageLookupByLibrary.simpleMessage("Tipos de arquivo e nomes"), + "filesBackedUpFromDevice": m20, + "filesBackedUpInAlbum": m21, + "filesDeleted": + MessageLookupByLibrary.simpleMessage("Arquivos excluídos"), + "flip": MessageLookupByLibrary.simpleMessage("Inverter"), + "forYourMemories": + MessageLookupByLibrary.simpleMessage("para suas memórias"), "forgotPassword": MessageLookupByLibrary.simpleMessage("Esqueceu sua senha"), "freeStorageClaimed": MessageLookupByLibrary.simpleMessage( "Armazenamento gratuito reivindicado"), "freeStorageOnReferralSuccess": m22, + "freeStorageSpace": m23, "freeStorageUsable": MessageLookupByLibrary.simpleMessage( "Armazenamento livre utilizável"), + "freeTrial": MessageLookupByLibrary.simpleMessage("Teste gratuito"), + "freeTrialValidTill": m24, + "freeUpAccessPostDelete": m25, + "freeUpAmount": m26, + "freeUpDeviceSpace": MessageLookupByLibrary.simpleMessage( + "Liberar espaço no dispositivo"), + "freeUpSpace": MessageLookupByLibrary.simpleMessage("Liberar espaço"), + "freeUpSpaceSaving": m27, + "galleryMemoryLimitInfo": MessageLookupByLibrary.simpleMessage( + "Até 1000 memórias mostradas na galeria"), + "general": MessageLookupByLibrary.simpleMessage("Geral"), "generatingEncryptionKeys": MessageLookupByLibrary.simpleMessage( "Gerando chaves de criptografia..."), + "genericProgress": m28, + "goToSettings": + MessageLookupByLibrary.simpleMessage("Ir para Configurações"), + "googlePlayId": + MessageLookupByLibrary.simpleMessage("ID da Google Play"), + "grantFullAccessPrompt": MessageLookupByLibrary.simpleMessage( + "Por favor, permita o acesso a todas as fotos nas configurações do aplicativo"), + "grantPermission": + MessageLookupByLibrary.simpleMessage("Garantir permissão"), + "groupNearbyPhotos": + MessageLookupByLibrary.simpleMessage("Agrupar fotos próximas"), + "hearUsExplanation": MessageLookupByLibrary.simpleMessage( + "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!"), + "hearUsWhereTitle": MessageLookupByLibrary.simpleMessage( + "Como você ouviu sobre o Ente? (opcional)"), + "hidden": MessageLookupByLibrary.simpleMessage("Escondido"), + "hide": MessageLookupByLibrary.simpleMessage("Ocultar"), + "hiding": MessageLookupByLibrary.simpleMessage("Ocultando..."), + "hostedAtOsmFrance": + MessageLookupByLibrary.simpleMessage("Hospedado na OSM France"), "howItWorks": MessageLookupByLibrary.simpleMessage("Como funciona"), + "howToViewShareeVerificationID": MessageLookupByLibrary.simpleMessage( + "Por favor, peça-lhes para pressionar longamente o endereço de e-mail na tela de configurações e verifique se os IDs de ambos os dispositivos correspondem."), + "iOSGoToSettingsDescription": MessageLookupByLibrary.simpleMessage( + "A autenticação biométrica não está configurada no seu dispositivo. Por favor, ative o Touch ID ou o Face ID no seu telefone."), + "iOSLockOut": MessageLookupByLibrary.simpleMessage( + "A Autenticação Biométrica está desativada. Por favor, bloqueie e desbloqueie sua tela para ativá-la."), + "iOSOkButton": MessageLookupByLibrary.simpleMessage("Aceitar"), + "ignoreUpdate": MessageLookupByLibrary.simpleMessage("Ignorar"), + "ignoredFolderUploadReason": MessageLookupByLibrary.simpleMessage( + "Alguns arquivos neste álbum são ignorados do upload porque eles tinham sido anteriormente excluídos do ente."), + "importing": MessageLookupByLibrary.simpleMessage("Importando...."), + "incorrectCode": + MessageLookupByLibrary.simpleMessage("Código incorreto"), "incorrectPasswordTitle": MessageLookupByLibrary.simpleMessage("Senha incorreta"), + "incorrectRecoveryKey": MessageLookupByLibrary.simpleMessage( + "Chave de recuperação incorreta"), "incorrectRecoveryKeyBody": MessageLookupByLibrary.simpleMessage( "A chave de recuperação que você digitou está incorreta"), "incorrectRecoveryKeyTitle": MessageLookupByLibrary.simpleMessage( "Chave de recuperação incorreta"), + "indexedItems": MessageLookupByLibrary.simpleMessage("Itens indexados"), "insecureDevice": MessageLookupByLibrary.simpleMessage("Dispositivo não seguro"), "installManually": @@ -246,43 +795,195 @@ class MessageLookup extends MessageLookupByLibrary { "invalidKey": MessageLookupByLibrary.simpleMessage("Chave inválida"), "invalidRecoveryKey": MessageLookupByLibrary.simpleMessage( "A chave de recuperação que você digitou não é válida. Certifique-se de que contém 24 palavras e verifique a ortografia de cada uma.\n\nSe você inseriu um código de recuperação mais antigo, verifique se ele tem 64 caracteres e verifique cada um deles."), + "invite": MessageLookupByLibrary.simpleMessage("Convidar"), "inviteToEnte": MessageLookupByLibrary.simpleMessage("Convidar para o ente"), "inviteYourFriends": MessageLookupByLibrary.simpleMessage("Convide seus amigos"), + "inviteYourFriendsToEnte": + MessageLookupByLibrary.simpleMessage("Convide seus amigos ao ente"), + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": + MessageLookupByLibrary.simpleMessage( + "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte."), + "itemCount": m29, + "itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": + MessageLookupByLibrary.simpleMessage( + "Os itens mostram o número de dias restantes antes da exclusão permanente"), "itemsWillBeRemovedFromAlbum": MessageLookupByLibrary.simpleMessage( "Os itens selecionados serão removidos deste álbum"), - "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"), + "joinDiscord": + MessageLookupByLibrary.simpleMessage("Junte-se ao Discord"), "keepPhotos": MessageLookupByLibrary.simpleMessage("Manter fotos"), + "kiloMeterUnit": MessageLookupByLibrary.simpleMessage("km"), "kindlyHelpUsWithThisInformation": MessageLookupByLibrary.simpleMessage( "Ajude-nos com esta informação"), + "language": MessageLookupByLibrary.simpleMessage("Idioma"), + "lastUpdated": + MessageLookupByLibrary.simpleMessage("Última atualização"), + "leave": MessageLookupByLibrary.simpleMessage("Sair"), + "leaveAlbum": MessageLookupByLibrary.simpleMessage("Sair do álbum"), + "leaveFamily": MessageLookupByLibrary.simpleMessage("Sair da família"), + "leaveSharedAlbum": MessageLookupByLibrary.simpleMessage( + "Sair do álbum compartilhado?"), + "light": MessageLookupByLibrary.simpleMessage("Claro"), + "lightTheme": MessageLookupByLibrary.simpleMessage("Claro"), + "linkCopiedToClipboard": MessageLookupByLibrary.simpleMessage( + "Link copiado para a área de transferência"), "linkDeviceLimit": MessageLookupByLibrary.simpleMessage("Limite do dispositivo"), + "linkEnabled": MessageLookupByLibrary.simpleMessage("Ativado"), "linkExpired": MessageLookupByLibrary.simpleMessage("Expirado"), + "linkExpiresOn": m30, "linkExpiry": MessageLookupByLibrary.simpleMessage("Expiração do link"), + "linkHasExpired": + MessageLookupByLibrary.simpleMessage("O link expirou"), + "linkNeverExpires": MessageLookupByLibrary.simpleMessage("Nunca"), + "livePhotos": + MessageLookupByLibrary.simpleMessage("Fotos em movimento"), + "loadMessage1": MessageLookupByLibrary.simpleMessage( + "Você pode compartilhar sua assinatura com sua família"), + "loadMessage2": MessageLookupByLibrary.simpleMessage( + "Nós preservamos mais de 30 milhões de memórias até agora"), + "loadMessage3": MessageLookupByLibrary.simpleMessage( + "Mantemos 3 cópias dos seus dados, uma em um abrigo subterrâneo"), + "loadMessage4": MessageLookupByLibrary.simpleMessage( + "Todos os nossos aplicativos são de código aberto"), + "loadMessage5": MessageLookupByLibrary.simpleMessage( + "Nosso código-fonte e criptografia foram auditadas externamente"), + "loadMessage6": MessageLookupByLibrary.simpleMessage( + "Você pode compartilhar links para seus álbuns com seus entes queridos"), + "loadMessage7": MessageLookupByLibrary.simpleMessage( + "Nossos aplicativos móveis são executados em segundo plano para criptografar e fazer backup de quaisquer novas fotos que você clique"), + "loadMessage8": MessageLookupByLibrary.simpleMessage( + "web.ente.io tem um upload rápido"), + "loadMessage9": MessageLookupByLibrary.simpleMessage( + "Nós usamos Xchacha20Poly1305 para criptografar seus dados com segurança"), + "loadingExifData": + MessageLookupByLibrary.simpleMessage("Carregando dados EXIF..."), + "loadingGallery": + MessageLookupByLibrary.simpleMessage("Carregando galeria..."), + "loadingMessage": + MessageLookupByLibrary.simpleMessage("Carregando suas fotos..."), + "loadingModel": + MessageLookupByLibrary.simpleMessage("Baixando modelos..."), + "localGallery": MessageLookupByLibrary.simpleMessage("Galeria local"), + "location": MessageLookupByLibrary.simpleMessage("Local"), + "locationName": MessageLookupByLibrary.simpleMessage("Nome do Local"), + "locationTagFeatureDescription": MessageLookupByLibrary.simpleMessage( + "Uma tag em grupo de todas as fotos que foram tiradas dentro de algum raio de uma foto"), + "lockButtonLabel": MessageLookupByLibrary.simpleMessage("Bloquear"), + "lockScreenEnablePreSteps": MessageLookupByLibrary.simpleMessage( + "Para ativar o bloqueio de tela, por favor ative um método de autenticação nas configurações do sistema do seu dispositivo."), + "lockscreen": MessageLookupByLibrary.simpleMessage("Tela de bloqueio"), "logInLabel": MessageLookupByLibrary.simpleMessage("Login"), + "loggingOut": MessageLookupByLibrary.simpleMessage("Desconectando..."), "loginTerms": MessageLookupByLibrary.simpleMessage( "Ao clicar em login, eu concordo com os termos de serviço e a política de privacidade"), "logout": MessageLookupByLibrary.simpleMessage("Encerrar sessão"), + "logsDialogBody": MessageLookupByLibrary.simpleMessage( + "Isso enviará através dos logs para nos ajudar a depurar o seu problema. Por favor, note que nomes de arquivos serão incluídos para ajudar a rastrear problemas com arquivos específicos."), + "longpressOnAnItemToViewInFullscreen": + MessageLookupByLibrary.simpleMessage( + "Pressione e segure em um item para exibir em tela cheia"), "lostDevice": MessageLookupByLibrary.simpleMessage("Dispositivo perdido?"), + "machineLearning": + MessageLookupByLibrary.simpleMessage("Aprendizagem de máquina"), + "magicSearch": MessageLookupByLibrary.simpleMessage("Busca mágica"), + "magicSearchDescription": MessageLookupByLibrary.simpleMessage( + "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados."), "manage": MessageLookupByLibrary.simpleMessage("Gerenciar"), + "manageDeviceStorage": MessageLookupByLibrary.simpleMessage( + "Gerenciar o armazenamento do dispositivo"), + "manageFamily": + MessageLookupByLibrary.simpleMessage("Gerenciar Família"), + "manageLink": MessageLookupByLibrary.simpleMessage("Gerenciar link"), + "manageParticipants": MessageLookupByLibrary.simpleMessage("Gerenciar"), + "manageSubscription": + MessageLookupByLibrary.simpleMessage("Gerenciar assinatura"), + "map": MessageLookupByLibrary.simpleMessage("Mapa"), + "maps": MessageLookupByLibrary.simpleMessage("Mapas"), + "mastodon": MessageLookupByLibrary.simpleMessage("Mastodon"), + "matrix": MessageLookupByLibrary.simpleMessage("Matrix"), + "memoryCount": m31, + "merchandise": MessageLookupByLibrary.simpleMessage("Produtos"), + "mobileWebDesktop": + MessageLookupByLibrary.simpleMessage("Mobile, Web, Desktop"), "moderateStrength": MessageLookupByLibrary.simpleMessage("Moderada"), "modifyYourQueryOrTrySearchingFor": MessageLookupByLibrary.simpleMessage( - "Modify your query, or try searching for"), + "Modifique sua consulta ou tente procurar por"), + "moments": MessageLookupByLibrary.simpleMessage("Momentos"), + "monthly": MessageLookupByLibrary.simpleMessage("Mensal"), + "moveItem": m32, + "moveToAlbum": MessageLookupByLibrary.simpleMessage("Mover para álbum"), "moveToHiddenAlbum": - MessageLookupByLibrary.simpleMessage("Move to hidden album"), + MessageLookupByLibrary.simpleMessage("Mover para álbum oculto"), + "movedSuccessfullyTo": m33, + "movedToTrash": + MessageLookupByLibrary.simpleMessage("Movido para a lixeira"), + "movingFilesToAlbum": MessageLookupByLibrary.simpleMessage( + "Enviando arquivos para o álbum..."), + "name": MessageLookupByLibrary.simpleMessage("Nome"), + "networkConnectionRefusedErr": MessageLookupByLibrary.simpleMessage( + "Não foi possível conectar ao Ente, tente novamente após algum tempo. Se o erro persistir, entre em contato com o suporte."), + "networkHostLookUpErr": MessageLookupByLibrary.simpleMessage( + "Não foi possível conectar-se ao Ente, verifique suas configurações de rede e entre em contato com o suporte se o erro persistir."), + "never": MessageLookupByLibrary.simpleMessage("Nunca"), + "newAlbum": MessageLookupByLibrary.simpleMessage("Novo álbum"), + "newToEnte": MessageLookupByLibrary.simpleMessage("Novo no ente"), + "newest": MessageLookupByLibrary.simpleMessage("Mais recente"), + "no": MessageLookupByLibrary.simpleMessage("Não"), + "noAlbumsSharedByYouYet": MessageLookupByLibrary.simpleMessage( + "Nenhum álbum compartilhado por você ainda"), + "noDeviceLimit": MessageLookupByLibrary.simpleMessage("Nenhum"), + "noDeviceThatCanBeDeleted": MessageLookupByLibrary.simpleMessage( + "Você não tem nenhum arquivo neste dispositivo que pode ser excluído"), + "noDuplicates": + MessageLookupByLibrary.simpleMessage("✨ Sem duplicados"), + "noExifData": MessageLookupByLibrary.simpleMessage("Sem dados EXIF"), + "noHiddenPhotosOrVideos": MessageLookupByLibrary.simpleMessage( + "Nenhuma foto ou vídeos ocultos"), + "noImagesWithLocation": + MessageLookupByLibrary.simpleMessage("Nenhuma imagem com local"), + "noInternetConnection": + MessageLookupByLibrary.simpleMessage("Sem conexão à internet"), + "noPhotosAreBeingBackedUpRightNow": + MessageLookupByLibrary.simpleMessage( + "No momento não há backup de fotos sendo feito"), + "noPhotosFoundHere": MessageLookupByLibrary.simpleMessage( + "Nenhuma foto encontrada aqui"), "noRecoveryKey": MessageLookupByLibrary.simpleMessage( "Nenhuma chave de recuperação?"), "noRecoveryKeyNoDecryption": MessageLookupByLibrary.simpleMessage( "Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação"), + "noResults": MessageLookupByLibrary.simpleMessage("Nenhum resultado"), + "noResultsFound": + MessageLookupByLibrary.simpleMessage("Nenhum resultado encontrado"), + "nothingSharedWithYouYet": MessageLookupByLibrary.simpleMessage( + "Nada compartilhado com você ainda"), + "nothingToSeeHere": + MessageLookupByLibrary.simpleMessage("Nada para ver aqui! 👀"), + "notifications": MessageLookupByLibrary.simpleMessage("Notificações"), "ok": MessageLookupByLibrary.simpleMessage("Ok"), + "onDevice": MessageLookupByLibrary.simpleMessage("No dispositivo"), + "onEnte": MessageLookupByLibrary.simpleMessage( + "Em ente"), "oops": MessageLookupByLibrary.simpleMessage("Ops"), + "oopsCouldNotSaveEdits": MessageLookupByLibrary.simpleMessage( + "Ops, não foi possível salvar edições"), "oopsSomethingWentWrong": MessageLookupByLibrary.simpleMessage("Ops! Algo deu errado"), + "openSettings": + MessageLookupByLibrary.simpleMessage("Abrir Configurações"), + "openTheItem": MessageLookupByLibrary.simpleMessage("• Abra o item"), + "openstreetmapContributors": MessageLookupByLibrary.simpleMessage( + "Contribuidores do OpenStreetMap"), + "optionalAsShortAsYouLike": MessageLookupByLibrary.simpleMessage( + "Opcional, tão curta quanto quiser..."), "orPickAnExistingOne": MessageLookupByLibrary.simpleMessage("Ou escolha um existente"), + "pair": MessageLookupByLibrary.simpleMessage("Parear"), "password": MessageLookupByLibrary.simpleMessage("Senha"), "passwordChangedSuccessfully": MessageLookupByLibrary.simpleMessage("Senha alterada com sucesso"), @@ -291,14 +992,92 @@ class MessageLookup extends MessageLookupByLibrary { "passwordStrength": m34, "passwordWarning": MessageLookupByLibrary.simpleMessage( "Nós não salvamos essa senha, se você esquecer nós não poderemos descriptografar seus dados"), + "paymentDetails": + MessageLookupByLibrary.simpleMessage("Detalhes de pagamento"), + "paymentFailed": + MessageLookupByLibrary.simpleMessage("Falha no pagamento"), + "paymentFailedTalkToProvider": m35, + "paymentFailedWithReason": m36, + "pendingItems": MessageLookupByLibrary.simpleMessage("Itens pendentes"), + "pendingSync": + MessageLookupByLibrary.simpleMessage("Sincronização pendente"), "peopleUsingYourCode": MessageLookupByLibrary.simpleMessage("Pessoas que usam seu código"), + "permDeleteWarning": MessageLookupByLibrary.simpleMessage( + "Todos os itens na lixeira serão excluídos permanentemente\n\nEsta ação não pode ser desfeita"), + "permanentlyDelete": + MessageLookupByLibrary.simpleMessage("Excluir permanentemente"), + "permanentlyDeleteFromDevice": MessageLookupByLibrary.simpleMessage( + "Excluir permanentemente do dispositivo?"), + "photoDescriptions": + MessageLookupByLibrary.simpleMessage("Descrições das fotos"), + "photoGridSize": + MessageLookupByLibrary.simpleMessage("Tamanho da grade de fotos"), + "photoSmallCase": MessageLookupByLibrary.simpleMessage("Foto"), + "photos": MessageLookupByLibrary.simpleMessage("Fotos"), + "photosAddedByYouWillBeRemovedFromTheAlbum": + MessageLookupByLibrary.simpleMessage( + "As fotos adicionadas por você serão removidas do álbum"), + "pickCenterPoint": + MessageLookupByLibrary.simpleMessage("Escolha o ponto central"), + "pinAlbum": MessageLookupByLibrary.simpleMessage("Fixar álbum"), + "playOnTv": + MessageLookupByLibrary.simpleMessage("Reproduzir álbum na TV"), + "playStoreFreeTrialValidTill": m37, + "playstoreSubscription": + MessageLookupByLibrary.simpleMessage("Assinatura da PlayStore"), + "pleaseCheckYourInternetConnectionAndTryAgain": + MessageLookupByLibrary.simpleMessage( + "Verifique sua conexão com a internet e tente novamente."), + "pleaseContactSupportAndWeWillBeHappyToHelp": + MessageLookupByLibrary.simpleMessage( + "Por favor, entre em contato com support@ente.io e nós ficaremos felizes em ajudar!"), + "pleaseContactSupportIfTheProblemPersists": + MessageLookupByLibrary.simpleMessage( + "Por favor, contate o suporte se o problema persistir"), + "pleaseEmailUsAt": m38, + "pleaseGrantPermissions": MessageLookupByLibrary.simpleMessage( + "Por favor, conceda as permissões"), + "pleaseLoginAgain": MessageLookupByLibrary.simpleMessage( + "Por favor, faça login novamente"), + "pleaseSendTheLogsTo": m39, "pleaseTryAgain": MessageLookupByLibrary.simpleMessage("Por favor, tente novamente"), + "pleaseVerifyTheCodeYouHaveEntered": + MessageLookupByLibrary.simpleMessage( + "Por favor, verifique o código que você inseriu"), "pleaseWait": MessageLookupByLibrary.simpleMessage("Por favor, aguarde..."), + "pleaseWaitDeletingAlbum": MessageLookupByLibrary.simpleMessage( + "Por favor, aguarde, excluindo álbum"), + "pleaseWaitForSometimeBeforeRetrying": + MessageLookupByLibrary.simpleMessage( + "Por favor, aguarde algum tempo antes de tentar novamente"), + "preparingLogs": + MessageLookupByLibrary.simpleMessage("Preparando logs..."), + "preserveMore": MessageLookupByLibrary.simpleMessage("Preservar mais"), + "pressAndHoldToPlayVideo": MessageLookupByLibrary.simpleMessage( + "Pressione e segure para reproduzir o vídeo"), + "pressAndHoldToPlayVideoDetailed": MessageLookupByLibrary.simpleMessage( + "Pressione e segure na imagem para reproduzir o vídeo"), + "privacy": MessageLookupByLibrary.simpleMessage("Privacidade"), "privacyPolicyTitle": MessageLookupByLibrary.simpleMessage("Política de Privacidade"), + "privateBackups": + MessageLookupByLibrary.simpleMessage("Backups privados"), + "privateSharing": + MessageLookupByLibrary.simpleMessage("Compartilhamento privado"), + "publicLinkCreated": + MessageLookupByLibrary.simpleMessage("Link público criado"), + "publicLinkEnabled": + MessageLookupByLibrary.simpleMessage("Link público ativado"), + "quickLinks": MessageLookupByLibrary.simpleMessage("Links rápidos"), + "radius": MessageLookupByLibrary.simpleMessage("Raio"), + "raiseTicket": MessageLookupByLibrary.simpleMessage("Abrir ticket"), + "rateTheApp": + MessageLookupByLibrary.simpleMessage("Avalie o aplicativo"), + "rateUs": MessageLookupByLibrary.simpleMessage("Avalie-nos"), + "rateUsOnStore": m40, "recover": MessageLookupByLibrary.simpleMessage("Recuperar"), "recoverAccount": MessageLookupByLibrary.simpleMessage("Recuperar conta"), @@ -322,19 +1101,36 @@ class MessageLookup extends MessageLookupByLibrary { "recreatePasswordBody": MessageLookupByLibrary.simpleMessage( "O dispositivo atual não é poderoso o suficiente para verificar sua senha, mas podemos regenerar de uma forma que funcione com todos os dispositivos.\n\nPor favor, faça o login usando sua chave de recuperação e recrie sua senha (você pode usar o mesmo novamente se desejar)."), "recreatePasswordTitle": - MessageLookupByLibrary.simpleMessage("Restabeleça sua senha"), + MessageLookupByLibrary.simpleMessage("Redefinir senha"), + "reddit": MessageLookupByLibrary.simpleMessage("Reddit"), + "referFriendsAnd2xYourPlan": MessageLookupByLibrary.simpleMessage( + "Indique amigos e 2x seu plano"), "referralStep1": MessageLookupByLibrary.simpleMessage( "Envie esse código aos seus amigos"), "referralStep2": MessageLookupByLibrary.simpleMessage( - "2. Eles se inscrevem em um plano pago"), + "2. Eles se inscreveram para um plano pago"), "referralStep3": m41, + "referrals": MessageLookupByLibrary.simpleMessage("Indicações"), "referralsAreCurrentlyPaused": MessageLookupByLibrary.simpleMessage( "Referências estão atualmente pausadas"), + "remindToEmptyDeviceTrash": MessageLookupByLibrary.simpleMessage( + "Também vazio \"Excluído Recentemente\" de \"Configurações\" -> \"Armazenamento\" para reivindicar o espaço livre"), + "remindToEmptyEnteTrash": MessageLookupByLibrary.simpleMessage( + "Também esvazie sua \"Lixeira\" para reivindicar o espaço liberado"), + "remoteImages": MessageLookupByLibrary.simpleMessage("Imagens remotas"), + "remoteThumbnails": + MessageLookupByLibrary.simpleMessage("Miniaturas remotas"), + "remoteVideos": MessageLookupByLibrary.simpleMessage("Vídeos remotos"), "remove": MessageLookupByLibrary.simpleMessage("Remover"), + "removeDuplicates": + MessageLookupByLibrary.simpleMessage("Excluir duplicados"), "removeFromAlbum": MessageLookupByLibrary.simpleMessage("Remover do álbum"), "removeFromAlbumTitle": MessageLookupByLibrary.simpleMessage("Remover do álbum?"), + "removeFromFavorite": + MessageLookupByLibrary.simpleMessage("Remover dos favoritos"), + "removeLink": MessageLookupByLibrary.simpleMessage("Remover link"), "removeParticipant": MessageLookupByLibrary.simpleMessage("Remover participante"), "removeParticipantBody": m42, @@ -346,32 +1142,187 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Excluir?"), "removingFromFavorites": MessageLookupByLibrary.simpleMessage("Removendo dos favoritos..."), + "rename": MessageLookupByLibrary.simpleMessage("Renomear"), + "renameAlbum": MessageLookupByLibrary.simpleMessage("Renomear álbum"), + "renameFile": MessageLookupByLibrary.simpleMessage("Renomear arquivo"), + "renewSubscription": + MessageLookupByLibrary.simpleMessage("Renovar assinatura"), + "renewsOn": m43, + "reportABug": + MessageLookupByLibrary.simpleMessage("Reportar um problema"), + "reportBug": + MessageLookupByLibrary.simpleMessage("Reportar um problema"), "resendEmail": MessageLookupByLibrary.simpleMessage("Reenviar e-mail"), + "resetIgnoredFiles": MessageLookupByLibrary.simpleMessage( + "Redefinir arquivos ignorados"), "resetPasswordTitle": - MessageLookupByLibrary.simpleMessage("Restabeleça sua senha"), + MessageLookupByLibrary.simpleMessage("Redefinir senha"), + "resetToDefault": + MessageLookupByLibrary.simpleMessage("Redefinir para o padrão"), + "restore": MessageLookupByLibrary.simpleMessage("Restaurar"), + "restoreToAlbum": + MessageLookupByLibrary.simpleMessage("Restaurar para álbum"), + "restoringFiles": + MessageLookupByLibrary.simpleMessage("Restaurando arquivos..."), + "retry": MessageLookupByLibrary.simpleMessage("Tentar novamente"), + "reviewDeduplicateItems": MessageLookupByLibrary.simpleMessage( + "Por favor, reveja e exclua os itens que você acredita serem duplicados."), + "rotateLeft": + MessageLookupByLibrary.simpleMessage("Girar para a esquerda"), + "rotateRight": + MessageLookupByLibrary.simpleMessage("Girar para a direita"), + "safelyStored": + MessageLookupByLibrary.simpleMessage("Armazenado com segurança"), + "save": MessageLookupByLibrary.simpleMessage("Salvar"), + "saveCollage": MessageLookupByLibrary.simpleMessage("Salvar colagem"), + "saveCopy": MessageLookupByLibrary.simpleMessage("Salvar cópia"), "saveKey": MessageLookupByLibrary.simpleMessage("Salvar chave"), "saveYourRecoveryKeyIfYouHaventAlready": MessageLookupByLibrary.simpleMessage( "Salve sua chave de recuperação, caso ainda não o tenha feito"), + "saving": MessageLookupByLibrary.simpleMessage("Salvando..."), "scanCode": MessageLookupByLibrary.simpleMessage("Escanear código"), "scanThisBarcodeWithnyourAuthenticatorApp": MessageLookupByLibrary.simpleMessage( "Escaneie este código de barras com\nseu aplicativo autenticador"), + "searchAlbumsEmptySection": + MessageLookupByLibrary.simpleMessage("Álbuns"), + "searchByAlbumNameHint": + MessageLookupByLibrary.simpleMessage("Nome do álbum"), + "searchByExamples": MessageLookupByLibrary.simpleMessage( + "• Nomes de álbuns (ex: \"Câmera\")\n• Tipos de arquivos (ex.: \"Vídeos\", \".gif\")\n• Anos e meses (e.. \"2022\", \"Janeiro\")\n• Feriados (por exemplo, \"Natal\")\n• Descrições de fotos (por exemplo, \"#divertido\")"), + "searchCaptionEmptySection": MessageLookupByLibrary.simpleMessage( + "Adicione descrições como \"#trip\" nas informações das fotos para encontrá-las aqui rapidamente"), + "searchDatesEmptySection": MessageLookupByLibrary.simpleMessage( + "Pesquisar por data, mês ou ano"), + "searchFaceEmptySection": MessageLookupByLibrary.simpleMessage( + "Encontre todas as fotos de uma pessoa"), + "searchFileTypesAndNamesEmptySection": + MessageLookupByLibrary.simpleMessage("Tipos de arquivo e nomes"), + "searchHint1": MessageLookupByLibrary.simpleMessage( + "Rápido, pesquisa no dispositivo"), + "searchHint2": + MessageLookupByLibrary.simpleMessage("Datas das fotos, descrições"), + "searchHint3": MessageLookupByLibrary.simpleMessage( + "Álbuns, nomes de arquivos e tipos"), + "searchHint4": MessageLookupByLibrary.simpleMessage("Local"), + "searchHint5": MessageLookupByLibrary.simpleMessage( + "Em breve: Rostos e busca mágica ✨"), + "searchHintText": MessageLookupByLibrary.simpleMessage( + "Álbuns, meses, dias, anos, ..."), + "searchLocationEmptySection": MessageLookupByLibrary.simpleMessage( + "Fotos de grupo que estão sendo tiradas em algum raio da foto"), + "searchPeopleEmptySection": MessageLookupByLibrary.simpleMessage( + "Convide pessoas e você verá todas as fotos compartilhadas por elas aqui"), + "searchResultCount": m44, + "security": MessageLookupByLibrary.simpleMessage("Segurança"), "selectALocation": - MessageLookupByLibrary.simpleMessage("Select a location"), + MessageLookupByLibrary.simpleMessage("Selecionar um local"), "selectALocationFirst": - MessageLookupByLibrary.simpleMessage("Select a location first"), + MessageLookupByLibrary.simpleMessage("Selecione um local primeiro"), + "selectAlbum": MessageLookupByLibrary.simpleMessage("Selecionar álbum"), + "selectAll": MessageLookupByLibrary.simpleMessage("Selecionar tudo"), + "selectFoldersForBackup": MessageLookupByLibrary.simpleMessage( + "Selecione pastas para backup"), + "selectItemsToAdd": MessageLookupByLibrary.simpleMessage( + "Selecionar itens para adicionar"), + "selectLanguage": + MessageLookupByLibrary.simpleMessage("Selecionar Idioma"), + "selectMorePhotos": + MessageLookupByLibrary.simpleMessage("Selecionar mais fotos"), "selectReason": MessageLookupByLibrary.simpleMessage("Selecione o motivo"), + "selectYourPlan": + MessageLookupByLibrary.simpleMessage("Selecione seu plano"), + "selectedFilesAreNotOnEnte": MessageLookupByLibrary.simpleMessage( + "Os arquivos selecionados não estão no ente"), + "selectedFoldersWillBeEncryptedAndBackedUp": + MessageLookupByLibrary.simpleMessage( + "As pastas selecionadas serão criptografadas e armazenadas em backup"), + "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": + MessageLookupByLibrary.simpleMessage( + "Os itens selecionados serão excluídos de todos os álbuns e movidos para o lixo."), + "selectedPhotos": m45, + "selectedPhotosWithYours": m46, + "send": MessageLookupByLibrary.simpleMessage("Enviar"), "sendEmail": MessageLookupByLibrary.simpleMessage("Enviar e-mail"), - "setPasswordTitle": MessageLookupByLibrary.simpleMessage( - "Chave: definaSenha\n→ definaSenha"), + "sendInvite": MessageLookupByLibrary.simpleMessage("Enviar convite"), + "sendLink": MessageLookupByLibrary.simpleMessage("Enviar link"), + "sessionExpired": + MessageLookupByLibrary.simpleMessage("Sessão expirada"), + "setAPassword": + MessageLookupByLibrary.simpleMessage("Defina uma senha"), + "setAs": MessageLookupByLibrary.simpleMessage("Definir como"), + "setCover": MessageLookupByLibrary.simpleMessage("Definir capa"), + "setLabel": MessageLookupByLibrary.simpleMessage("Aplicar"), + "setPasswordTitle": + MessageLookupByLibrary.simpleMessage("Definir senha"), + "setRadius": MessageLookupByLibrary.simpleMessage("Definir raio"), "setupComplete": MessageLookupByLibrary.simpleMessage("Configuração concluída"), + "share": MessageLookupByLibrary.simpleMessage("Compartilhar"), + "shareALink": MessageLookupByLibrary.simpleMessage("Compartilhar link"), + "shareAlbumHint": MessageLookupByLibrary.simpleMessage( + "Abra um álbum e toque no botão compartilhar no canto superior direito para compartilhar."), + "shareAnAlbumNow": + MessageLookupByLibrary.simpleMessage("Compartilhar um álbum agora"), + "shareLink": MessageLookupByLibrary.simpleMessage("Compartilhar link"), + "shareMyVerificationID": m47, + "shareOnlyWithThePeopleYouWant": MessageLookupByLibrary.simpleMessage( + "Compartilhar apenas com as pessoas que você quiser"), + "shareTextConfirmOthersVerificationID": m48, + "shareTextRecommendUsingEnte": MessageLookupByLibrary.simpleMessage( + "Baixe o Ente para podermos compartilhar facilmente fotos e vídeos de alta qualidade\n\nhttps://ente.io"), "shareTextReferralCode": m49, + "shareWithNonenteUsers": MessageLookupByLibrary.simpleMessage( + "Compartilhar com usuários não-Ente"), + "shareWithPeopleSectionTitle": m50, + "shareYourFirstAlbum": MessageLookupByLibrary.simpleMessage( + "Compartilhar seu primeiro álbum"), + "sharedAlbumSectionDescription": MessageLookupByLibrary.simpleMessage( + "Criar álbuns compartilhados e colaborativos com outros usuários Ente, incluindo usuários em planos gratuitos."), + "sharedByMe": + MessageLookupByLibrary.simpleMessage("Compartilhada por mim"), + "sharedByYou": + MessageLookupByLibrary.simpleMessage("Compartilhado por você"), + "sharedPhotoNotifications": + MessageLookupByLibrary.simpleMessage("Novas fotos compartilhadas"), + "sharedPhotoNotificationsExplanation": MessageLookupByLibrary.simpleMessage( + "Receber notificações quando alguém adicionar uma foto em um álbum compartilhado que você faz parte"), + "sharedWith": m51, + "sharedWithMe": + MessageLookupByLibrary.simpleMessage("Compartilhado comigo"), + "sharedWithYou": + MessageLookupByLibrary.simpleMessage("Compartilhado com você"), "sharing": MessageLookupByLibrary.simpleMessage("Compartilhando..."), + "showMemories": + MessageLookupByLibrary.simpleMessage("Mostrar memórias"), + "signOutFromOtherDevices": MessageLookupByLibrary.simpleMessage( + "Terminar sessão em outros dispositivos"), + "signOutOtherBody": MessageLookupByLibrary.simpleMessage( + "Se você acha que alguém pode saber sua senha, você pode forçar todos os outros dispositivos que estão com sua conta a desconectar."), + "signOutOtherDevices": MessageLookupByLibrary.simpleMessage( + "Terminar sessão em outros dispositivos"), "signUpTerms": MessageLookupByLibrary.simpleMessage( "Eu concordo com os termos de serviço e a política de privacidade"), + "singleFileDeleteFromDevice": m52, + "singleFileDeleteHighlight": MessageLookupByLibrary.simpleMessage( + "Ele será excluído de todos os álbuns."), + "singleFileInBothLocalAndRemote": m53, + "singleFileInRemoteOnly": m54, + "skip": MessageLookupByLibrary.simpleMessage("Pular"), + "social": MessageLookupByLibrary.simpleMessage("Redes sociais"), + "someItemsAreInBothEnteAndYourDevice": + MessageLookupByLibrary.simpleMessage( + "Alguns itens estão tanto no ente quanto no seu dispositivo."), + "someOfTheFilesYouAreTryingToDeleteAre": + MessageLookupByLibrary.simpleMessage( + "Alguns dos arquivos que você está tentando excluir só estão disponíveis no seu dispositivo e não podem ser recuperados se forem excluídos"), + "someoneSharingAlbumsWithYouShouldSeeTheSameId": + MessageLookupByLibrary.simpleMessage( + "Alguém compartilhando álbuns com você deve ver o mesmo ID no dispositivo."), + "somethingWentWrong": + MessageLookupByLibrary.simpleMessage("Algo deu errado"), "somethingWentWrongPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Algo deu errado. Por favor, tente outra vez"), @@ -381,68 +1332,260 @@ class MessageLookup extends MessageLookupByLibrary { "sorryCouldNotRemoveFromFavorites": MessageLookupByLibrary.simpleMessage( "Desculpe, não foi possível remover dos favoritos!"), + "sorryTheCodeYouveEnteredIsIncorrect": + MessageLookupByLibrary.simpleMessage( + "Desculpe, o código que você inseriu está incorreto"), "sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": MessageLookupByLibrary.simpleMessage( "Desculpe, não foi possível gerar chaves seguras neste dispositivo.\n\npor favor, faça o login com um dispositivo diferente."), + "sortAlbumsBy": MessageLookupByLibrary.simpleMessage("Ordenar por"), + "sortNewestFirst": + MessageLookupByLibrary.simpleMessage("Mais recentes primeiro"), + "sortOldestFirst": + MessageLookupByLibrary.simpleMessage("Mais antigos primeiro"), + "sparkleSuccess": + MessageLookupByLibrary.simpleMessage("✨ Bem-sucedido"), + "startBackup": MessageLookupByLibrary.simpleMessage("Iniciar backup"), + "status": MessageLookupByLibrary.simpleMessage("Estado"), + "storage": MessageLookupByLibrary.simpleMessage("Armazenamento"), + "storageBreakupFamily": MessageLookupByLibrary.simpleMessage("Família"), + "storageBreakupYou": MessageLookupByLibrary.simpleMessage("Você"), "storageInGB": m55, + "storageLimitExceeded": MessageLookupByLibrary.simpleMessage( + "Limite de armazenamento excedido"), + "storageUsageInfo": m56, "strongStrength": MessageLookupByLibrary.simpleMessage("Forte"), - "subscribe": MessageLookupByLibrary.simpleMessage("Inscrever-se"), + "subAlreadyLinkedErrMessage": m57, + "subWillBeCancelledOn": m58, + "subscribe": MessageLookupByLibrary.simpleMessage("Assinar"), "subscribeToEnableSharing": MessageLookupByLibrary.simpleMessage( "Parece que sua assinatura expirou. Por favor inscreva-se para ativar o compartilhamento."), + "subscription": MessageLookupByLibrary.simpleMessage("Assinatura"), + "success": MessageLookupByLibrary.simpleMessage("Bem-sucedido"), + "successfullyArchived": + MessageLookupByLibrary.simpleMessage("Arquivado com sucesso"), + "successfullyHid": + MessageLookupByLibrary.simpleMessage("Ocultado com sucesso"), + "successfullyUnarchived": + MessageLookupByLibrary.simpleMessage("Desarquivado com sucesso"), + "successfullyUnhid": + MessageLookupByLibrary.simpleMessage("Desocultado com sucesso"), + "suggestFeatures": + MessageLookupByLibrary.simpleMessage("Sugerir funcionalidades"), + "support": MessageLookupByLibrary.simpleMessage("Suporte"), + "syncProgress": m59, + "syncStopped": + MessageLookupByLibrary.simpleMessage("Sincronização interrompida"), + "syncing": MessageLookupByLibrary.simpleMessage("Sincronizando..."), + "systemTheme": MessageLookupByLibrary.simpleMessage("Sistema"), "tapToCopy": MessageLookupByLibrary.simpleMessage("toque para copiar"), "tapToEnterCode": - MessageLookupByLibrary.simpleMessage("Clica para inserir código"), - "terminate": MessageLookupByLibrary.simpleMessage("Terminar"), + MessageLookupByLibrary.simpleMessage("Toque para inserir código"), + "tempErrorContactSupportIfPersists": MessageLookupByLibrary.simpleMessage( + "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte."), + "terminate": MessageLookupByLibrary.simpleMessage("Encerrar"), "terminateSession": MessageLookupByLibrary.simpleMessage("Encerrar sessão?"), + "terms": MessageLookupByLibrary.simpleMessage("Termos"), "termsOfServicesTitle": MessageLookupByLibrary.simpleMessage("Termos"), + "thankYou": MessageLookupByLibrary.simpleMessage("Obrigado"), + "thankYouForSubscribing": + MessageLookupByLibrary.simpleMessage("Obrigado por assinar!"), + "theDownloadCouldNotBeCompleted": MessageLookupByLibrary.simpleMessage( + "Não foi possível concluir a transferência"), + "theRecoveryKeyYouEnteredIsIncorrect": + MessageLookupByLibrary.simpleMessage( + "A chave de recuperação inserida está incorreta"), + "theme": MessageLookupByLibrary.simpleMessage("Tema"), + "theseItemsWillBeDeletedFromYourDevice": + MessageLookupByLibrary.simpleMessage( + "Estes itens serão excluídos do seu dispositivo."), "theyAlsoGetXGb": m60, + "theyWillBeDeletedFromAllAlbums": MessageLookupByLibrary.simpleMessage( + "Ele será excluído de todos os álbuns."), + "thisActionCannotBeUndone": MessageLookupByLibrary.simpleMessage( + "Esta ação não pode ser desfeita"), + "thisAlbumAlreadyHDACollaborativeLink": + MessageLookupByLibrary.simpleMessage( + "Este álbum já tem um link colaborativo"), "thisCanBeUsedToRecoverYourAccountIfYou": MessageLookupByLibrary.simpleMessage( "Isso pode ser usado para recuperar sua conta se você perder seu segundo fator"), "thisDevice": MessageLookupByLibrary.simpleMessage("Este aparelho"), + "thisEmailIsAlreadyInUse": + MessageLookupByLibrary.simpleMessage("Este e-mail já está em uso"), + "thisImageHasNoExifData": MessageLookupByLibrary.simpleMessage( + "Esta imagem não tem dados exif"), + "thisIsPersonVerificationId": m61, + "thisIsYourVerificationId": MessageLookupByLibrary.simpleMessage( + "Este é o seu ID de verificação"), "thisWillLogYouOutOfTheFollowingDevice": MessageLookupByLibrary.simpleMessage( "Isso fará com que você saia do seguinte dispositivo:"), "thisWillLogYouOutOfThisDevice": MessageLookupByLibrary.simpleMessage( "Isso fará com que você saia deste dispositivo!"), + "toHideAPhotoOrVideo": MessageLookupByLibrary.simpleMessage( + "Para ocultar uma foto ou vídeo"), + "toResetVerifyEmail": MessageLookupByLibrary.simpleMessage( + "Para redefinir a sua senha, por favor verifique o seu email primeiro."), + "todaysLogs": MessageLookupByLibrary.simpleMessage("Logs de hoje"), "total": MessageLookupByLibrary.simpleMessage("total"), + "totalSize": MessageLookupByLibrary.simpleMessage("Tamanho total"), + "trash": MessageLookupByLibrary.simpleMessage("Lixeira"), + "trashDaysLeft": m62, "tryAgain": MessageLookupByLibrary.simpleMessage("Tente novamente"), + "turnOnBackupForAutoUpload": MessageLookupByLibrary.simpleMessage( + "Ative o backup para enviar automaticamente arquivos adicionados a esta pasta do dispositivo para o ente."), + "twitter": MessageLookupByLibrary.simpleMessage("Twitter"), + "twoMonthsFreeOnYearlyPlans": MessageLookupByLibrary.simpleMessage( + "2 meses grátis em planos anuais"), + "twofactor": MessageLookupByLibrary.simpleMessage("Dois fatores"), + "twofactorAuthenticationHasBeenDisabled": + MessageLookupByLibrary.simpleMessage( + "A autenticação de dois fatores foi desativada"), "twofactorAuthenticationPageTitle": MessageLookupByLibrary.simpleMessage( "Autenticação de dois fatores"), + "twofactorAuthenticationSuccessfullyReset": + MessageLookupByLibrary.simpleMessage( + "Autenticação de dois fatores redefinida com sucesso"), "twofactorSetup": MessageLookupByLibrary.simpleMessage( - "Autenticação de dois fatores"), + "Configuração de dois fatores"), + "unarchive": MessageLookupByLibrary.simpleMessage("Desarquivar"), + "unarchiveAlbum": + MessageLookupByLibrary.simpleMessage("Desarquivar álbum"), + "unarchiving": MessageLookupByLibrary.simpleMessage("Desarquivando..."), + "uncategorized": MessageLookupByLibrary.simpleMessage("Sem categoria"), + "unhide": MessageLookupByLibrary.simpleMessage("Desocultar"), + "unhideToAlbum": + MessageLookupByLibrary.simpleMessage("Reexibir para o álbum"), + "unhiding": MessageLookupByLibrary.simpleMessage("Desocultando..."), + "unhidingFilesToAlbum": MessageLookupByLibrary.simpleMessage( + "Desocultando arquivos para o álbum"), + "unlock": MessageLookupByLibrary.simpleMessage("Desbloquear"), + "unpinAlbum": MessageLookupByLibrary.simpleMessage("Desafixar álbum"), + "unselectAll": MessageLookupByLibrary.simpleMessage("Desmarque todos"), "update": MessageLookupByLibrary.simpleMessage("Atualização"), "updateAvailable": MessageLookupByLibrary.simpleMessage("Atualização disponível"), + "updatingFolderSelection": MessageLookupByLibrary.simpleMessage( + "Atualizando seleção de pasta..."), + "upgrade": MessageLookupByLibrary.simpleMessage("Aprimorar"), + "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage( + "Enviando arquivos para o álbum..."), + "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( + "Até 50% de desconto, até 4 de dezembro."), "usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage( "Armazenamento utilizável é limitado pelo seu plano atual. O armazenamento reivindicado em excesso se tornará utilizável automaticamente quando você fizer a melhoria do seu plano."), + "usePublicLinksForPeopleNotOnEnte": + MessageLookupByLibrary.simpleMessage( + "Usar links públicos para pessoas que não estão no ente"), "useRecoveryKey": MessageLookupByLibrary.simpleMessage("Usar chave de recuperação"), + "useSelectedPhoto": + MessageLookupByLibrary.simpleMessage("Utilizar foto selecionada"), + "usedSpace": MessageLookupByLibrary.simpleMessage("Espaço em uso"), + "validTill": m63, + "verificationFailedPleaseTryAgain": + MessageLookupByLibrary.simpleMessage( + "Falha na verificação, por favor, tente novamente"), + "verificationId": + MessageLookupByLibrary.simpleMessage("ID de Verificação"), "verify": MessageLookupByLibrary.simpleMessage("Verificar"), "verifyEmail": MessageLookupByLibrary.simpleMessage("Verificar email"), + "verifyEmailID": m64, + "verifyIDLabel": MessageLookupByLibrary.simpleMessage("Verificar"), "verifyPassword": MessageLookupByLibrary.simpleMessage("Verificar senha"), + "verifying": MessageLookupByLibrary.simpleMessage("Verificando..."), "verifyingRecoveryKey": MessageLookupByLibrary.simpleMessage( "Verificando chave de recuperação..."), + "videoSmallCase": MessageLookupByLibrary.simpleMessage("Video"), + "videos": MessageLookupByLibrary.simpleMessage("Vídeos"), + "viewActiveSessions": + MessageLookupByLibrary.simpleMessage("Ver sessões ativas"), + "viewAddOnButton": + MessageLookupByLibrary.simpleMessage("Ver complementos"), + "viewAll": MessageLookupByLibrary.simpleMessage("Ver tudo"), + "viewAllExifData": + MessageLookupByLibrary.simpleMessage("Ver todos os dados EXIF"), + "viewLogs": MessageLookupByLibrary.simpleMessage("Ver logs"), "viewRecoveryKey": MessageLookupByLibrary.simpleMessage("Ver chave de recuperação"), "viewer": MessageLookupByLibrary.simpleMessage("Visualizador"), + "visitWebToManage": MessageLookupByLibrary.simpleMessage( + "Por favor visite web.ente.io para gerenciar sua assinatura"), + "waitingForWifi": + MessageLookupByLibrary.simpleMessage("Esperando por Wi-Fi..."), + "weAreOpenSource": + MessageLookupByLibrary.simpleMessage("Somos de código aberto!"), + "weDontSupportEditingPhotosAndAlbumsThatYouDont": + MessageLookupByLibrary.simpleMessage( + "Não suportamos a edição de fotos e álbuns que você ainda não possui"), "weHaveSendEmailTo": m65, "weakStrength": MessageLookupByLibrary.simpleMessage("Fraca"), "welcomeBack": MessageLookupByLibrary.simpleMessage("Bem-vindo de volta!"), + "yearly": MessageLookupByLibrary.simpleMessage("Anual"), + "yearsAgo": m66, + "yes": MessageLookupByLibrary.simpleMessage("Sim"), + "yesCancel": MessageLookupByLibrary.simpleMessage("Sim, cancelar"), "yesConvertToViewer": MessageLookupByLibrary.simpleMessage( "Sim, converter para visualizador"), + "yesDelete": MessageLookupByLibrary.simpleMessage("Sim, excluir"), + "yesDiscardChanges": + MessageLookupByLibrary.simpleMessage("Sim, descartar alterações"), "yesLogout": MessageLookupByLibrary.simpleMessage("Sim, terminar sessão"), "yesRemove": MessageLookupByLibrary.simpleMessage("Sim, excluir"), + "yesRenew": MessageLookupByLibrary.simpleMessage("Sim, Renovar"), "you": MessageLookupByLibrary.simpleMessage("Você"), + "youAreOnAFamilyPlan": MessageLookupByLibrary.simpleMessage( + "Você está em um plano familiar!"), + "youAreOnTheLatestVersion": MessageLookupByLibrary.simpleMessage( + "Você está usando a versão mais recente"), "youCanAtMaxDoubleYourStorage": MessageLookupByLibrary.simpleMessage( "* Você pode duplicar seu armazenamento no máximo"), + "youCanManageYourLinksInTheShareTab": + MessageLookupByLibrary.simpleMessage( + "Você pode gerenciar seus links na aba de compartilhamento."), + "youCanTrySearchingForADifferentQuery": + MessageLookupByLibrary.simpleMessage( + "Você pode tentar procurar uma consulta diferente."), + "youCannotDowngradeToThisPlan": MessageLookupByLibrary.simpleMessage( + "Você não pode fazer o downgrade para este plano"), + "youCannotShareWithYourself": MessageLookupByLibrary.simpleMessage( + "Você não pode compartilhar consigo mesmo"), + "youDontHaveAnyArchivedItems": MessageLookupByLibrary.simpleMessage( + "Você não tem nenhum item arquivado."), + "youHaveSuccessfullyFreedUp": m67, "yourAccountHasBeenDeleted": - MessageLookupByLibrary.simpleMessage("Sua conta foi deletada"), - "yourMap": MessageLookupByLibrary.simpleMessage("Your map") + MessageLookupByLibrary.simpleMessage("Sua conta foi excluída"), + "yourMap": MessageLookupByLibrary.simpleMessage("Seu mapa"), + "yourPlanWasSuccessfullyDowngraded": + MessageLookupByLibrary.simpleMessage( + "Seu plano foi diminuido com sucesso"), + "yourPlanWasSuccessfullyUpgraded": MessageLookupByLibrary.simpleMessage( + "Seu plano foi aumentado com sucesso"), + "yourPurchaseWasSuccessful": MessageLookupByLibrary.simpleMessage( + "Sua compra foi efetuada com sucesso"), + "yourStorageDetailsCouldNotBeFetched": + MessageLookupByLibrary.simpleMessage( + "Seus detalhes de armazenamento não puderam ser obtidos"), + "yourSubscriptionHasExpired": + MessageLookupByLibrary.simpleMessage("A sua assinatura expirou"), + "yourSubscriptionWasUpdatedSuccessfully": + MessageLookupByLibrary.simpleMessage( + "Sua assinatura foi atualizada com sucesso"), + "yourVerificationCodeHasExpired": MessageLookupByLibrary.simpleMessage( + "O código de verificação expirou"), + "youveNoDuplicateFilesThatCanBeCleared": + MessageLookupByLibrary.simpleMessage( + "Você não tem arquivos duplicados que possam ser limpos"), + "youveNoFilesInThisAlbumThatCanBeDeleted": + MessageLookupByLibrary.simpleMessage( + "Você não tem arquivos neste álbum que possam ser excluídos"), + "zoomOutToSeePhotos": MessageLookupByLibrary.simpleMessage( + "Diminuir o zoom para ver fotos") }; } diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 234b74bbc..85b784799 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -666,7 +666,7 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("项目显示永久删除前剩余的天数"), "itemsWillBeRemovedFromAlbum": MessageLookupByLibrary.simpleMessage("所选项目将从此相册中移除"), - "joinDiscord": MessageLookupByLibrary.simpleMessage("Join Discord"), + "joinDiscord": MessageLookupByLibrary.simpleMessage("加入 Discord"), "keepPhotos": MessageLookupByLibrary.simpleMessage("保留照片"), "kiloMeterUnit": MessageLookupByLibrary.simpleMessage("公里"), "kindlyHelpUsWithThisInformation": diff --git a/lib/l10n/intl_pt.arb b/lib/l10n/intl_pt.arb index 8fe4d999b..73bfcf8b6 100644 --- a/lib/l10n/intl_pt.arb +++ b/lib/l10n/intl_pt.arb @@ -6,15 +6,15 @@ "verify": "Verificar", "invalidEmailAddress": "Endereço de e-mail invalido", "enterValidEmail": "Por, favor insira um endereço de e-mail válido.", - "deleteAccount": "Deletar conta", + "deleteAccount": "Excluir conta", "askDeleteReason": "Qual é o principal motivo para você excluir sua conta?", "deleteAccountFeedbackPrompt": "Lamentamos ver você partir. Por favor, compartilhe seus comentários para nos ajudar a melhorar.", - "feedback": "Opinião", + "feedback": "Comentários", "kindlyHelpUsWithThisInformation": "Ajude-nos com esta informação", "confirmDeletePrompt": "Sim, desejo excluir permanentemente esta conta e todos os seus dados.", "confirmAccountDeletion": "Confirmar exclusão da conta", "deleteAccountPermanentlyButton": "Excluir conta permanentemente", - "yourAccountHasBeenDeleted": "Sua conta foi deletada", + "yourAccountHasBeenDeleted": "Sua conta foi excluída", "selectReason": "Selecione o motivo", "deleteReason1": "Está faltando um recurso-chave que eu preciso", "deleteReason2": "O aplicativo ou um determinado recurso não está funcionando como eu acredito que deveria", @@ -23,6 +23,7 @@ "sendEmail": "Enviar e-mail", "deleteRequestSLAText": "Sua solicitação será processada em até 72 horas.", "deleteEmailRequest": "Por favor, envie um e-mail para account-deletion@ente.io a partir do seu endereço de e-mail registrado.", + "entePhotosPerm": "Ente precisa de permissão para preservar suas fotos", "ok": "Ok", "createAccount": "Criar uma conta", "createNewAccount": "Criar nova conta", @@ -34,7 +35,7 @@ "thisWillLogYouOutOfThisDevice": "Isso fará com que você saia deste dispositivo!", "thisWillLogYouOutOfTheFollowingDevice": "Isso fará com que você saia do seguinte dispositivo:", "terminateSession": "Encerrar sessão?", - "terminate": "Terminar", + "terminate": "Encerrar", "thisDevice": "Este aparelho", "recoverButton": "Recuperar", "recoverySuccessful": "Recuperação bem sucedida!", @@ -47,8 +48,9 @@ "sorry": "Desculpe", "noRecoveryKeyNoDecryption": "Devido à natureza do nosso protocolo de criptografia de ponta a ponta, seus dados não podem ser descriptografados sem sua senha ou chave de recuperação", "verifyEmail": "Verificar email", + "toResetVerifyEmail": "Para redefinir a sua senha, por favor verifique o seu email primeiro.", "checkInboxAndSpamFolder": "Verifique sua caixa de entrada (e ‘spam’) para concluir a verificação", - "tapToEnterCode": "Clica para inserir código", + "tapToEnterCode": "Toque para inserir código", "resendEmail": "Reenviar e-mail", "weHaveSendEmailTo": "Enviamos um e-mail à {email}", "@weHaveSendEmailTo": { @@ -61,9 +63,9 @@ } } }, - "setPasswordTitle": "Chave: definaSenha\n→ definaSenha", - "changePasswordTitle": "Mude sua senha", - "resetPasswordTitle": "Restabeleça sua senha", + "setPasswordTitle": "Definir senha", + "changePasswordTitle": "Alterar senha", + "resetPasswordTitle": "Redefinir senha", "encryptionKeys": "Chaves de criptografia", "passwordWarning": "Nós não salvamos essa senha, se você esquecer nós não poderemos descriptografar seus dados", "enterPasswordToEncrypt": "Insira a senha para criptografar seus dados", @@ -100,10 +102,10 @@ "changeEmail": "Mudar e-mail", "enterYourPassword": "Insira sua senha", "welcomeBack": "Bem-vindo de volta!", - "contactSupport": "Falar com o suporte", + "contactSupport": "Contate o suporte", "incorrectPasswordTitle": "Senha incorreta", "pleaseTryAgain": "Por favor, tente novamente", - "recreatePasswordTitle": "Restabeleça sua senha", + "recreatePasswordTitle": "Redefinir senha", "useRecoveryKey": "Usar chave de recuperação", "recreatePasswordBody": "O dispositivo atual não é poderoso o suficiente para verificar sua senha, mas podemos regenerar de uma forma que funcione com todos os dispositivos.\n\nPor favor, faça o login usando sua chave de recuperação e recrie sua senha (você pode usar o mesmo novamente se desejar).", "verifyPassword": "Verificar senha", @@ -125,7 +127,7 @@ } } }, - "twofactorSetup": "Autenticação de dois fatores", + "twofactorSetup": "Configuração de dois fatores", "enterCode": "Coloque o código", "scanCode": "Escanear código", "codeCopiedToClipboard": "Código copiado para a área de transferência", @@ -186,8 +188,90 @@ "disableDownloadWarningBody": "Os espectadores ainda podem tirar screenshots ou salvar uma cópia de suas fotos usando ferramentas externas", "allowDownloads": "Permitir transferências", "linkDeviceLimit": "Limite do dispositivo", + "noDeviceLimit": "Nenhum", + "@noDeviceLimit": { + "description": "Text to indicate that there is limit on number of devices" + }, "linkExpiry": "Expiração do link", "linkExpired": "Expirado", + "linkEnabled": "Ativado", + "linkNeverExpires": "Nunca", + "expiredLinkInfo": "Este link expirou. Por favor, selecione um novo tempo de expiração ou desabilite a expiração do link.", + "setAPassword": "Defina uma senha", + "lockButtonLabel": "Bloquear", + "enterPassword": "Digite a senha", + "removeLink": "Remover link", + "manageLink": "Gerenciar link", + "linkExpiresOn": "O link irá expirar em {expiryTime}", + "albumUpdated": "Álbum atualizado", + "never": "Nunca", + "custom": "Personalizado", + "@custom": { + "description": "Label for setting custom value for link expiry" + }, + "after1Hour": "Após 1 hora", + "after1Day": "Após 1 dia", + "after1Week": "Após 1 semana", + "after1Month": "Após 1 mês", + "after1Year": "Após 1 ano", + "manageParticipants": "Gerenciar", + "albumParticipantsCount": "{count, plural, =0 {Nenhum Participante} =1 {1 Participante} other {{count} Participantes}}", + "@albumParticipantsCount": { + "placeholders": { + "count": { + "type": "int", + "example": "5" + } + }, + "description": "Number of participants in an album, including the album owner." + }, + "collabLinkSectionDescription": "Crie um link para permitir pessoas adicionar e ver fotos no seu álbum compartilhado sem a necessidade do aplicativo ou uma conta Ente. Ótimo para colecionar fotos de eventos.", + "collectPhotos": "Colete fotos", + "collaborativeLink": "Link Colaborativo", + "shareWithNonenteUsers": "Compartilhar com usuários não-Ente", + "createPublicLink": "Criar link público", + "sendLink": "Enviar link", + "copyLink": "Copiar link", + "linkHasExpired": "O link expirou", + "publicLinkEnabled": "Link público ativado", + "shareALink": "Compartilhar link", + "sharedAlbumSectionDescription": "Criar álbuns compartilhados e colaborativos com outros usuários Ente, incluindo usuários em planos gratuitos.", + "shareWithPeopleSectionTitle": "{numberOfPeople, plural, one {}=0 {Compartilhe com pessoas específicas} =1 {Compartilhado com 1 pessoa} other {Compartilhado com {numberOfPeople} pessoas}}", + "@shareWithPeopleSectionTitle": { + "placeholders": { + "numberOfPeople": { + "type": "int", + "example": "2" + } + } + }, + "thisIsYourVerificationId": "Este é o seu ID de verificação", + "someoneSharingAlbumsWithYouShouldSeeTheSameId": "Alguém compartilhando álbuns com você deve ver o mesmo ID no dispositivo.", + "howToViewShareeVerificationID": "Por favor, peça-lhes para pressionar longamente o endereço de e-mail na tela de configurações e verifique se os IDs de ambos os dispositivos correspondem.", + "thisIsPersonVerificationId": "Este é o ID de verificação de {email}", + "@thisIsPersonVerificationId": { + "placeholders": { + "email": { + "type": "String", + "example": "someone@ente.io" + } + } + }, + "verificationId": "ID de Verificação", + "verifyEmailID": "Verificar {email}", + "emailNoEnteAccount": "{email} Não possui uma conta Ente.\n\nEnvie um convite para compartilhar fotos.", + "shareMyVerificationID": "Aqui está meu ID de verificação para o Ente.io: {verificationID}", + "shareTextConfirmOthersVerificationID": "Ei, você pode confirmar que este é seu ID de verificação do Ente.io? {verificationID}", + "somethingWentWrong": "Algo deu errado", + "sendInvite": "Enviar convite", + "shareTextRecommendUsingEnte": "Baixe o Ente para podermos compartilhar facilmente fotos e vídeos de alta qualidade\n\nhttps://ente.io", + "done": "Concluído", + "applyCodeTitle": "Aplicar código", + "enterCodeDescription": "Digite o código fornecido pelo seu amigo para reivindicar o armazenamento gratuito para vocês dois", + "apply": "Aplicar", + "failedToApplyCode": "Falha ao aplicar o código", + "enterReferralCode": "Insira o código de referência", + "codeAppliedPageTitle": "Código aplicado", "storageInGB": "{storageAmountInGB} GB", "claimed": "Reivindicado", "@claimed": { @@ -198,11 +282,11 @@ "theyAlsoGetXGb": "Eles também recebem {storageAmountInGB} GB", "freeStorageOnReferralSuccess": "{storageAmountInGB} GB cada vez que alguém se inscrever para um plano pago e aplica o seu código", "shareTextReferralCode": "Código de referência do ente: {referralCode} \n\nAplique em Configurações → Geral → Indicações para obter {referralStorageInGB} GB gratuitamente após a sua inscrição em um plano pago\n\nhttps://ente.io", - "claimFreeStorage": "Solicitar armazenamento gratuito", + "claimFreeStorage": "Reivindicar armazenamento gratuito", "inviteYourFriends": "Convide seus amigos", "failedToFetchReferralDetails": "Não foi possível buscar informações do produto. Por favor, tente novamente mais tarde.", "referralStep1": "Envie esse código aos seus amigos", - "referralStep2": "2. Eles se inscrevem em um plano pago", + "referralStep2": "2. Eles se inscreveram para um plano pago", "referralStep3": "3. Ambos ganham {storageInGB} GB* grátis", "referralsAreCurrentlyPaused": "Referências estão atualmente pausadas", "youCanAtMaxDoubleYourStorage": "* Você pode duplicar seu armazenamento no máximo", @@ -237,7 +321,7 @@ "sorryCouldNotAddToFavorites": "Desculpe, não foi possível adicionar aos favoritos!", "sorryCouldNotRemoveFromFavorites": "Desculpe, não foi possível remover dos favoritos!", "subscribeToEnableSharing": "Parece que sua assinatura expirou. Por favor inscreva-se para ativar o compartilhamento.", - "subscribe": "Inscrever-se", + "subscribe": "Assinar", "canOnlyRemoveFilesOwnedByYou": "Só é possível remover arquivos de sua propriedade", "deleteSharedAlbum": "Excluir álbum compartilhado?", "deleteAlbum": "Excluir álbum", @@ -253,9 +337,128 @@ "removePublicLink": "Remover link público", "disableLinkMessage": "Isso removerá o link público para acessar \"{albumName}\".", "sharing": "Compartilhando...", + "youCannotShareWithYourself": "Você não pode compartilhar consigo mesmo", + "archive": "Arquivo", + "createAlbumActionHint": "Pressione e segure para selecionar fotos e clique em + para criar um álbum", + "importing": "Importando....", + "failedToLoadAlbums": "Falha ao carregar álbuns", + "hidden": "Escondido", + "authToViewYourHiddenFiles": "Autentique-se para visualizar seus arquivos ocultos", + "trash": "Lixeira", + "uncategorized": "Sem categoria", + "videoSmallCase": "Video", + "photoSmallCase": "Foto", + "singleFileDeleteHighlight": "Ele será excluído de todos os álbuns.", + "singleFileInBothLocalAndRemote": "Este {fileType} está em ente e no seu dispositivo.", + "singleFileInRemoteOnly": "Este {fileType} será excluído do ente.", + "singleFileDeleteFromDevice": "Este {fileType} será excluído do seu dispositivo.", + "deleteFromEnte": "Excluir do ente", + "yesDelete": "Sim, excluir", + "movedToTrash": "Movido para a lixeira", + "deleteFromDevice": "Excluir do dispositivo", + "deleteFromBoth": "Excluir de ambos", + "newAlbum": "Novo álbum", + "albums": "Álbuns", + "memoryCount": "{count, plural, zero{no memories} one{{formattedCount} memory} other{{formattedCount} memories}}", + "@memoryCount": { + "description": "The text to display the number of memories", + "type": "text", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedCount": { + "type": "String", + "example": "11.513, 11,511" + } + } + }, + "selectedPhotos": "{count} Selecionados", + "@selectedPhotos": { + "description": "Display the number of selected photos", + "type": "text", + "placeholders": { + "count": { + "example": "5", + "type": "int" + } + } + }, + "selectedPhotosWithYours": "{count} Selecionado ({yourCount} seus)", + "@selectedPhotosWithYours": { + "description": "Display the number of selected photos, including the number of selected photos owned by the user", + "type": "text", + "placeholders": { + "count": { + "example": "12", + "type": "int" + }, + "yourCount": { + "example": "2", + "type": "int" + } + } + }, + "advancedSettings": "Avançado", + "@advancedSettings": { + "description": "The text to display in the advanced settings section" + }, + "photoGridSize": "Tamanho da grade de fotos", + "manageDeviceStorage": "Gerenciar o armazenamento do dispositivo", + "machineLearning": "Aprendizagem de máquina", + "magicSearch": "Busca mágica", + "magicSearchDescription": "Por favor, note que isso resultará em uma largura de banda maior e uso de bateria até que todos os itens sejam indexados.", + "loadingModel": "Baixando modelos...", + "waitingForWifi": "Esperando por Wi-Fi...", + "status": "Estado", + "indexedItems": "Itens indexados", + "pendingItems": "Itens pendentes", + "clearIndexes": "Limpar índices", + "selectFoldersForBackup": "Selecione pastas para backup", + "selectedFoldersWillBeEncryptedAndBackedUp": "As pastas selecionadas serão criptografadas e armazenadas em backup", + "unselectAll": "Desmarque todos", + "selectAll": "Selecionar tudo", + "skip": "Pular", + "updatingFolderSelection": "Atualizando seleção de pasta...", + "itemCount": "{count, plural, one{{count} item} other{{count} items}}", + "deleteItemCount": "{count, plural, =1 {Excluir {count} item} other {Excluir {count} itens}}", + "duplicateItemsGroup": "{count} Arquivos, {formattedSize} cada", + "@duplicateItemsGroup": { + "description": "Display the number of duplicate files and their size", + "type": "text", + "placeholders": { + "count": { + "example": "12", + "type": "int" + }, + "formattedSize": { + "example": "2.3 MB", + "type": "String" + } + } + }, + "showMemories": "Mostrar memórias", + "yearsAgo": "{count, plural, one{{count} anos atrás} other{{count} anos atrás}}", + "backupSettings": "Configurações de backup", + "backupOverMobileData": "Backup de dados móveis", + "backupVideos": "Backup de videos", + "disableAutoLock": "Desativar bloqueio automático", + "deviceLockExplanation": "Desative o bloqueio de tela do dispositivo quando o ente estiver em primeiro plano e houver um backup em andamento. Isso normalmente não é necessário, mas pode ajudar grandes uploads e importações iniciais de grandes bibliotecas a serem concluídos mais rapidamente.", + "about": "Sobre", + "weAreOpenSource": "Somos de código aberto!", + "privacy": "Privacidade", + "terms": "Termos", + "checkForUpdates": "Verificar por atualizações", + "checking": "Verificando...", + "youAreOnTheLatestVersion": "Você está usando a versão mais recente", + "account": "Conta", + "manageSubscription": "Gerenciar assinatura", "authToChangeYourEmail": "Por favor, autentique-se para alterar seu e-mail", "changePassword": "Mude sua senha", "authToChangeYourPassword": "Por favor, autentique-se para alterar sua senha", + "emailVerificationToggle": "Verificação de e-mail", + "authToChangeEmailVerificationSetting": "Por favor, autentique-se para alterar seu e-mail", "exportYourData": "Exportar seus dados", "logout": "Encerrar sessão", "authToInitiateAccountDeletion": "Por favor, autentique-se para iniciar a exclusão de conta", @@ -266,17 +469,729 @@ "installManually": "Instalar manualmente", "criticalUpdateAvailable": "Atualização crítica disponível", "updateAvailable": "Atualização disponível", - "addToHiddenAlbum": "Add to hidden album", - "moveToHiddenAlbum": "Move to hidden album", - "fileTypes": "File types", - "deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.", - "yourMap": "Your map", - "modifyYourQueryOrTrySearchingFor": "Modify your query, or try searching for", - "contacts": "Contacts", - "editLocation": "Edit location", - "selectALocation": "Select a location", - "selectALocationFirst": "Select a location first", - "changeLocationOfSelectedItems": "Change location of selected items?", - "editsToLocationWillOnlyBeSeenWithinEnte": "Edits to location will only be seen within Ente", - "joinDiscord": "Join Discord" + "ignoreUpdate": "Ignorar", + "downloading": "Baixando...", + "cannotDeleteSharedFiles": "Não é possível excluir arquivos compartilhados", + "theDownloadCouldNotBeCompleted": "Não foi possível concluir a transferência", + "retry": "Tentar novamente", + "backedUpFolders": "Backup de pastas concluído", + "backup": "Backup", + "freeUpDeviceSpace": "Liberar espaço no dispositivo", + "allClear": "✨ Tudo limpo", + "noDeviceThatCanBeDeleted": "Você não tem nenhum arquivo neste dispositivo que pode ser excluído", + "removeDuplicates": "Excluir duplicados", + "noDuplicates": "✨ Sem duplicados", + "youveNoDuplicateFilesThatCanBeCleared": "Você não tem arquivos duplicados que possam ser limpos", + "success": "Bem-sucedido", + "rateUs": "Avalie-nos", + "remindToEmptyDeviceTrash": "Também vazio \"Excluído Recentemente\" de \"Configurações\" -> \"Armazenamento\" para reivindicar o espaço livre", + "youHaveSuccessfullyFreedUp": "Você liberou {storageSaved} com sucesso!", + "@youHaveSuccessfullyFreedUp": { + "description": "The text to display when the user has successfully freed up storage", + "type": "text", + "placeholders": { + "storageSaved": { + "example": "1.2 GB", + "type": "String" + } + } + }, + "remindToEmptyEnteTrash": "Também esvazie sua \"Lixeira\" para reivindicar o espaço liberado", + "sparkleSuccess": "✨ Bem-sucedido", + "duplicateFileCountWithStorageSaved": "Você limpou {count, plural, one{{count} arquivo duplicado} other{{count} arquivos duplicados}}, salvando ({storageSaved}!)", + "@duplicateFileCountWithStorageSaved": { + "description": "The text to display when the user has successfully cleaned up duplicate files", + "type": "text", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "storageSaved": { + "example": "1.2 GB", + "type": "String" + } + } + }, + "familyPlans": "Plano familiar", + "referrals": "Indicações", + "notifications": "Notificações", + "sharedPhotoNotifications": "Novas fotos compartilhadas", + "sharedPhotoNotificationsExplanation": "Receber notificações quando alguém adicionar uma foto em um álbum compartilhado que você faz parte", + "advanced": "Avançado", + "general": "Geral", + "security": "Segurança", + "authToViewYourRecoveryKey": "Por favor, autentique-se para visualizar sua chave de recuperação", + "twofactor": "Dois fatores", + "authToConfigureTwofactorAuthentication": "Por favor, autentique-se para configurar a autenticação de dois fatores", + "lockscreen": "Tela de bloqueio", + "authToChangeLockscreenSetting": "Por favor, autentique-se para alterar a configuração da tela de bloqueio", + "lockScreenEnablePreSteps": "Para ativar o bloqueio de tela, por favor ative um método de autenticação nas configurações do sistema do seu dispositivo.", + "viewActiveSessions": "Ver sessões ativas", + "authToViewYourActiveSessions": "Por favor, autentique-se para ver as sessões ativas", + "disableTwofactor": "Desativar autenticação de dois fatores", + "confirm2FADisable": "Você tem certeza de que deseja desativar a autenticação de dois fatores?", + "no": "Não", + "yes": "Sim", + "social": "Redes sociais", + "rateUsOnStore": "Avalie-nos em {storeName}", + "blog": "Blog", + "merchandise": "Produtos", + "twitter": "Twitter", + "mastodon": "Mastodon", + "matrix": "Matrix", + "discord": "Discord", + "reddit": "Reddit", + "yourStorageDetailsCouldNotBeFetched": "Seus detalhes de armazenamento não puderam ser obtidos", + "reportABug": "Reportar um problema", + "reportBug": "Reportar um problema", + "suggestFeatures": "Sugerir funcionalidades", + "support": "Suporte", + "theme": "Tema", + "lightTheme": "Claro", + "darkTheme": "Escuro", + "systemTheme": "Sistema", + "freeTrial": "Teste gratuito", + "selectYourPlan": "Selecione seu plano", + "enteSubscriptionPitch": "O ente preserva suas memórias, então eles estão sempre disponíveis para você, mesmo se você perder o seu dispositivo.", + "enteSubscriptionShareWithFamily": "Sua família também pode ser adicionada ao seu plano.", + "currentUsageIs": "O uso atual é ", + "@currentUsageIs": { + "description": "This text is followed by storage usaged", + "examples": { + "0": "Current usage is 1.2 GB" + }, + "type": "text" + }, + "faqs": "Perguntas frequentes", + "renewsOn": "Renovação de assinatura em {endDate}", + "freeTrialValidTill": "Teste gratuito acaba em {endDate}", + "validTill": "Válido até {endDate}", + "addOnValidTill": "Seu complemento {storageAmount} é válido até o dia {endDate}", + "playStoreFreeTrialValidTill": "Teste gratuito válido até {endDate}.\nVocê pode escolher um plano pago depois.", + "subWillBeCancelledOn": "Sua assinatura será cancelada em {endDate}", + "subscription": "Assinatura", + "paymentDetails": "Detalhes de pagamento", + "manageFamily": "Gerenciar Família", + "contactToManageSubscription": "Entre em contato conosco pelo e-mail support@ente.io para gerenciar sua assinatura {provider}.", + "renewSubscription": "Renovar assinatura", + "cancelSubscription": "Cancelar assinatura", + "areYouSureYouWantToRenew": "Tem certeza de que deseja renovar?", + "yesRenew": "Sim, Renovar", + "areYouSureYouWantToCancel": "Tem certeza que deseja cancelar?", + "yesCancel": "Sim, cancelar", + "failedToRenew": "Falha ao renovar", + "failedToCancel": "Falha ao cancelar", + "twoMonthsFreeOnYearlyPlans": "2 meses grátis em planos anuais", + "monthly": "Mensal", + "@monthly": { + "description": "The text to display for monthly plans", + "type": "text" + }, + "yearly": "Anual", + "@yearly": { + "description": "The text to display for yearly plans", + "type": "text" + }, + "confirmPlanChange": "Confirmar mudança de plano", + "areYouSureYouWantToChangeYourPlan": "Tem certeza que deseja trocar de plano?", + "youCannotDowngradeToThisPlan": "Você não pode fazer o downgrade para este plano", + "cancelOtherSubscription": "Por favor, cancele sua assinatura existente do {paymentProvider} primeiro", + "@cancelOtherSubscription": { + "description": "The text to display when the user has an existing subscription from a different payment provider", + "type": "text", + "placeholders": { + "paymentProvider": { + "example": "Apple", + "type": "String" + } + } + }, + "optionalAsShortAsYouLike": "Opcional, tão curta quanto quiser...", + "send": "Enviar", + "askCancelReason": "Sua assinatura foi cancelada. Gostaria de compartilhar o motivo?", + "thankYouForSubscribing": "Obrigado por assinar!", + "yourPurchaseWasSuccessful": "Sua compra foi efetuada com sucesso", + "yourPlanWasSuccessfullyUpgraded": "Seu plano foi aumentado com sucesso", + "yourPlanWasSuccessfullyDowngraded": "Seu plano foi diminuido com sucesso", + "yourSubscriptionWasUpdatedSuccessfully": "Sua assinatura foi atualizada com sucesso", + "googlePlayId": "ID da Google Play", + "appleId": "ID da Apple", + "playstoreSubscription": "Assinatura da PlayStore", + "appstoreSubscription": "Assinatura da AppStore", + "subAlreadyLinkedErrMessage": "Seu {id} já está vinculado a outra conta ente.\nSe você gostaria de usar seu {id} com esta conta, por favor contate nosso suporte''", + "visitWebToManage": "Por favor visite web.ente.io para gerenciar sua assinatura", + "couldNotUpdateSubscription": "Não foi possível atualizar a assinatura", + "pleaseContactSupportAndWeWillBeHappyToHelp": "Por favor, entre em contato com support@ente.io e nós ficaremos felizes em ajudar!", + "paymentFailed": "Falha no pagamento", + "paymentFailedTalkToProvider": "Por favor, fale com o suporte {providerName} se você foi cobrado", + "@paymentFailedTalkToProvider": { + "description": "The text to display when the payment failed", + "type": "text", + "placeholders": { + "providerName": { + "example": "AppStore|PlayStore", + "type": "String" + } + } + }, + "continueOnFreeTrial": "Continuar em teste gratuito", + "areYouSureYouWantToExit": "Tem certeza de que deseja sair?", + "thankYou": "Obrigado", + "failedToVerifyPaymentStatus": "Falha ao verificar status do pagamento", + "pleaseWaitForSometimeBeforeRetrying": "Por favor, aguarde algum tempo antes de tentar novamente", + "paymentFailedWithReason": "Infelizmente o seu pagamento falhou devido a {reason}", + "youAreOnAFamilyPlan": "Você está em um plano familiar!", + "contactFamilyAdmin": "Entre em contato com {familyAdminEmail} para gerenciar sua assinatura", + "leaveFamily": "Sair da família", + "areYouSureThatYouWantToLeaveTheFamily": "Tem certeza que deseja sair do plano familiar?", + "leave": "Sair", + "rateTheApp": "Avalie o aplicativo", + "startBackup": "Iniciar backup", + "noPhotosAreBeingBackedUpRightNow": "No momento não há backup de fotos sendo feito", + "preserveMore": "Preservar mais", + "grantFullAccessPrompt": "Por favor, permita o acesso a todas as fotos nas configurações do aplicativo", + "openSettings": "Abrir Configurações", + "selectMorePhotos": "Selecionar mais fotos", + "existingUser": "Usuário existente", + "privateBackups": "Backups privados", + "forYourMemories": "para suas memórias", + "endtoendEncryptedByDefault": "Criptografia de ponta a ponta por padrão", + "safelyStored": "Armazenado com segurança", + "atAFalloutShelter": "em um abrigo avançado", + "designedToOutlive": "Feito para ter logenvidade", + "available": "Disponível", + "everywhere": "em todos os lugares", + "androidIosWebDesktop": "Android, iOS, Web, Desktop", + "mobileWebDesktop": "Mobile, Web, Desktop", + "newToEnte": "Novo no ente", + "pleaseLoginAgain": "Por favor, faça login novamente", + "devAccountChanged": "A conta de desenvolvedor que usamos para publicar o ente na App Store foi alterada. Por esse motivo, você precisará fazer login novamente.\n\nPedimos desculpas pelo inconveniente, mas isso era inevitável.", + "yourSubscriptionHasExpired": "A sua assinatura expirou", + "storageLimitExceeded": "Limite de armazenamento excedido", + "upgrade": "Aprimorar", + "raiseTicket": "Abrir ticket", + "@raiseTicket": { + "description": "Button text for raising a support tickets in case of unhandled errors during backup", + "type": "text" + }, + "backupFailed": "Erro ao efetuar o backup", + "couldNotBackUpTryLater": "Não foi possível fazer o backup de seus dados.\nTentaremos novamente mais tarde.", + "enteCanEncryptAndPreserveFilesOnlyIfYouGrant": "ente pode criptografar e preservar arquivos somente se você conceder acesso a eles", + "pleaseGrantPermissions": "Por favor, conceda as permissões", + "grantPermission": "Garantir permissão", + "privateSharing": "Compartilhamento privado", + "shareOnlyWithThePeopleYouWant": "Compartilhar apenas com as pessoas que você quiser", + "usePublicLinksForPeopleNotOnEnte": "Usar links públicos para pessoas que não estão no ente", + "allowPeopleToAddPhotos": "Permitir que pessoas adicionem fotos", + "shareAnAlbumNow": "Compartilhar um álbum agora", + "collectEventPhotos": "Coletar fotos do evento", + "sessionExpired": "Sessão expirada", + "loggingOut": "Desconectando...", + "@onDevice": { + "description": "The text displayed above folders/albums stored on device", + "type": "text" + }, + "onDevice": "No dispositivo", + "@onEnte": { + "description": "The text displayed above albums backed up to ente", + "type": "text" + }, + "onEnte": "Em ente", + "name": "Nome", + "newest": "Mais recente", + "lastUpdated": "Última atualização", + "deleteEmptyAlbums": "Excluir álbuns vazios", + "deleteEmptyAlbumsWithQuestionMark": "Excluir álbuns vazios?", + "deleteAlbumsDialogBody": "Isto irá apagar todos os álbuns vazios. Isso é útil quando você deseja reduzir a bagunça na sua lista de álbuns.", + "deleteProgress": "Excluindo {currentlyDeleting} / {totalCount}", + "genericProgress": "Processando {currentlyProcessing} / {totalCount}", + "@genericProgress": { + "description": "Generic progress text to display when processing multiple items", + "type": "text", + "placeholders": { + "currentlyProcessing": { + "example": "1", + "type": "int" + }, + "totalCount": { + "example": "10", + "type": "int" + } + } + }, + "permanentlyDelete": "Excluir permanentemente", + "canOnlyCreateLinkForFilesOwnedByYou": "Só é possível criar um link para arquivos pertencentes a você", + "publicLinkCreated": "Link público criado", + "youCanManageYourLinksInTheShareTab": "Você pode gerenciar seus links na aba de compartilhamento.", + "linkCopiedToClipboard": "Link copiado para a área de transferência", + "restore": "Restaurar", + "@restore": { + "description": "Display text for an action which triggers a restore of item from trash", + "type": "text" + }, + "moveToAlbum": "Mover para álbum", + "unhide": "Desocultar", + "unarchive": "Desarquivar", + "favorite": "Favoritar", + "removeFromFavorite": "Remover dos favoritos", + "shareLink": "Compartilhar link", + "createCollage": "Criar colagem", + "saveCollage": "Salvar colagem", + "collageSaved": "Colagem salva na galeria", + "collageLayout": "Layout", + "addToEnte": "Adicionar ao ente", + "addToAlbum": "Adicionar ao álbum", + "delete": "Apagar", + "hide": "Ocultar", + "share": "Compartilhar", + "unhideToAlbum": "Reexibir para o álbum", + "restoreToAlbum": "Restaurar para álbum", + "moveItem": "{count, plural, one {Mover item} other {Mover itens}}", + "@moveItem": { + "description": "Page title while moving one or more items to an album" + }, + "addItem": "{count, plural, one {Adicionar item} other {Adicionar itens}}", + "@addItem": { + "description": "Page title while adding one or more items to album" + }, + "createOrSelectAlbum": "Criar ou selecionar álbum", + "selectAlbum": "Selecionar álbum", + "searchByAlbumNameHint": "Nome do álbum", + "albumTitle": "Título do álbum", + "enterAlbumName": "Digite o nome do álbum", + "restoringFiles": "Restaurando arquivos...", + "movingFilesToAlbum": "Enviando arquivos para o álbum...", + "unhidingFilesToAlbum": "Desocultando arquivos para o álbum", + "canNotUploadToAlbumsOwnedByOthers": "Não é possível enviar para álbuns pertencentes a outros", + "uploadingFilesToAlbum": "Enviando arquivos para o álbum...", + "addedSuccessfullyTo": "Adicionado com sucesso a {albumName}", + "movedSuccessfullyTo": "Movido com sucesso para {albumName}", + "thisAlbumAlreadyHDACollaborativeLink": "Este álbum já tem um link colaborativo", + "collaborativeLinkCreatedFor": "Link colaborativo criado para {albumName}", + "askYourLovedOnesToShare": "Peça que seus entes queridos compartilhem", + "invite": "Convidar", + "shareYourFirstAlbum": "Compartilhar seu primeiro álbum", + "sharedWith": "Compartilhado com {emailIDs}", + "sharedWithMe": "Compartilhado comigo", + "sharedByMe": "Compartilhada por mim", + "doubleYourStorage": "Dobre seu armazenamento", + "referFriendsAnd2xYourPlan": "Indique amigos e 2x seu plano", + "shareAlbumHint": "Abra um álbum e toque no botão compartilhar no canto superior direito para compartilhar.", + "itemsShowTheNumberOfDaysRemainingBeforePermanentDeletion": "Os itens mostram o número de dias restantes antes da exclusão permanente", + "trashDaysLeft": "{count, plural, =0 {} =1 {1 dia} other {{count} dias}}", + "@trashDaysLeft": { + "description": "Text to indicate number of days remaining before permanent deletion", + "placeholders": { + "count": { + "example": "1|2|3", + "type": "int" + } + } + }, + "deleteAll": "Excluir Tudo", + "renameAlbum": "Renomear álbum", + "convertToAlbum": "Converter para álbum", + "setCover": "Definir capa", + "@setCover": { + "description": "Text to set cover photo for an album" + }, + "sortAlbumsBy": "Ordenar por", + "sortNewestFirst": "Mais recentes primeiro", + "sortOldestFirst": "Mais antigos primeiro", + "rename": "Renomear", + "leaveSharedAlbum": "Sair do álbum compartilhado?", + "leaveAlbum": "Sair do álbum", + "photosAddedByYouWillBeRemovedFromTheAlbum": "As fotos adicionadas por você serão removidas do álbum", + "youveNoFilesInThisAlbumThatCanBeDeleted": "Você não tem arquivos neste álbum que possam ser excluídos", + "youDontHaveAnyArchivedItems": "Você não tem nenhum item arquivado.", + "ignoredFolderUploadReason": "Alguns arquivos neste álbum são ignorados do upload porque eles tinham sido anteriormente excluídos do ente.", + "resetIgnoredFiles": "Redefinir arquivos ignorados", + "deviceFilesAutoUploading": "Arquivos adicionados a este álbum do dispositivo serão automaticamente enviados para o ente.", + "turnOnBackupForAutoUpload": "Ative o backup para enviar automaticamente arquivos adicionados a esta pasta do dispositivo para o ente.", + "noHiddenPhotosOrVideos": "Nenhuma foto ou vídeos ocultos", + "toHideAPhotoOrVideo": "Para ocultar uma foto ou vídeo", + "openTheItem": "• Abra o item", + "clickOnTheOverflowMenu": "• Clique no menu adicional", + "click": "Clique", + "nothingToSeeHere": "Nada para ver aqui! 👀", + "unarchiveAlbum": "Desarquivar álbum", + "archiveAlbum": "Arquivar álbum", + "calculating": "Calculando...", + "pleaseWaitDeletingAlbum": "Por favor, aguarde, excluindo álbum", + "searchHintText": "Álbuns, meses, dias, anos, ...", + "searchByExamples": "• Nomes de álbuns (ex: \"Câmera\")\n• Tipos de arquivos (ex.: \"Vídeos\", \".gif\")\n• Anos e meses (e.. \"2022\", \"Janeiro\")\n• Feriados (por exemplo, \"Natal\")\n• Descrições de fotos (por exemplo, \"#divertido\")", + "youCanTrySearchingForADifferentQuery": "Você pode tentar procurar uma consulta diferente.", + "noResultsFound": "Nenhum resultado encontrado", + "addedBy": "Adicionado por {emailOrName}", + "loadingExifData": "Carregando dados EXIF...", + "viewAllExifData": "Ver todos os dados EXIF", + "noExifData": "Sem dados EXIF", + "thisImageHasNoExifData": "Esta imagem não tem dados exif", + "exif": "EXIF", + "noResults": "Nenhum resultado", + "weDontSupportEditingPhotosAndAlbumsThatYouDont": "Não suportamos a edição de fotos e álbuns que você ainda não possui", + "failedToFetchOriginalForEdit": "Falha ao obter original para edição", + "close": "Fechar", + "setAs": "Definir como", + "fileSavedToGallery": "Vídeo salvo na galeria", + "fileFailedToSaveToGallery": "Falha ao salvar o arquivo na galeria", + "download": "Baixar", + "pressAndHoldToPlayVideo": "Pressione e segure para reproduzir o vídeo", + "pressAndHoldToPlayVideoDetailed": "Pressione e segure na imagem para reproduzir o vídeo", + "downloadFailed": "Falha ao baixar", + "deduplicateFiles": "Arquivos Deduplicados", + "deselectAll": "Desmarcar todos", + "reviewDeduplicateItems": "Por favor, reveja e exclua os itens que você acredita serem duplicados.", + "clubByCaptureTime": "Agrupar por tempo de captura", + "clubByFileName": "Agrupar pelo nome de arquivo", + "count": "Contagem", + "totalSize": "Tamanho total", + "longpressOnAnItemToViewInFullscreen": "Pressione e segure em um item para exibir em tela cheia", + "decryptingVideo": "Descriptografando vídeo...", + "authToViewYourMemories": "Por favor, autentique para ver suas memórias", + "unlock": "Desbloquear", + "freeUpSpace": "Liberar espaço", + "freeUpSpaceSaving": "{count, plural, one {Pode ser excluído do dispositivo para liberar {formattedSize}} other {Eles podem ser excluídos do dispositivo para liberar {formattedSize}}}", + "filesBackedUpInAlbum": "{count, plural, one {1 arquivo} other {{formattedNumber} arquivos}} neste álbum teve um backup seguro", + "@filesBackedUpInAlbum": { + "description": "Text to tell user how many files have been backed up in the album", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedNumber": { + "content": "{formattedNumber}", + "example": "1,000", + "type": "String" + } + } + }, + "filesBackedUpFromDevice": "{count, plural, one {1 arquivo} other {{formattedNumber} arquivos}} neste dispositivo teve um backup seguro", + "@filesBackedUpFromDevice": { + "description": "Text to tell user how many files have been backed up from this device", + "placeholders": { + "count": { + "example": "1", + "type": "int" + }, + "formattedNumber": { + "content": "{formattedNumber}", + "example": "1,000", + "type": "String" + } + } + }, + "@freeUpSpaceSaving": { + "description": "Text to tell user how much space they can free up by deleting items from the device" + }, + "freeUpAccessPostDelete": "Você ainda pode acessar {count, plural, one {ele} other {eles}} no ente contanto que você tenha uma assinatura ativa", + "@freeUpAccessPostDelete": { + "placeholders": { + "count": { + "example": "1", + "type": "int" + } + } + }, + "freeUpAmount": "Liberar {sizeInMBorGB}", + "thisEmailIsAlreadyInUse": "Este e-mail já está em uso", + "incorrectCode": "Código incorreto", + "authenticationFailedPleaseTryAgain": "Falha na autenticação. Por favor, tente novamente", + "verificationFailedPleaseTryAgain": "Falha na verificação, por favor, tente novamente", + "authenticating": "Autenticando...", + "authenticationSuccessful": "Autenticação bem-sucedida!", + "incorrectRecoveryKey": "Chave de recuperação incorreta", + "theRecoveryKeyYouEnteredIsIncorrect": "A chave de recuperação inserida está incorreta", + "twofactorAuthenticationSuccessfullyReset": "Autenticação de dois fatores redefinida com sucesso", + "pleaseVerifyTheCodeYouHaveEntered": "Por favor, verifique o código que você inseriu", + "pleaseContactSupportIfTheProblemPersists": "Por favor, contate o suporte se o problema persistir", + "twofactorAuthenticationHasBeenDisabled": "A autenticação de dois fatores foi desativada", + "sorryTheCodeYouveEnteredIsIncorrect": "Desculpe, o código que você inseriu está incorreto", + "yourVerificationCodeHasExpired": "O código de verificação expirou", + "emailChangedTo": "E-mail alterado para {newEmail}", + "verifying": "Verificando...", + "disablingTwofactorAuthentication": "Desativando a autenticação de dois fatores...", + "allMemoriesPreserved": "Todas as memórias preservadas", + "loadingGallery": "Carregando galeria...", + "syncing": "Sincronizando...", + "encryptingBackup": "Criptografando backup...", + "syncStopped": "Sincronização interrompida", + "syncProgress": "{completed}/{total} memórias preservadas", + "@syncProgress": { + "description": "Text to tell user how many memories have been preserved", + "placeholders": { + "completed": { + "type": "String" + }, + "total": { + "type": "String" + } + } + }, + "archiving": "Arquivando...", + "unarchiving": "Desarquivando...", + "successfullyArchived": "Arquivado com sucesso", + "successfullyUnarchived": "Desarquivado com sucesso", + "renameFile": "Renomear arquivo", + "enterFileName": "Digite o nome do arquivo", + "filesDeleted": "Arquivos excluídos", + "selectedFilesAreNotOnEnte": "Os arquivos selecionados não estão no ente", + "thisActionCannotBeUndone": "Esta ação não pode ser desfeita", + "emptyTrash": "Esvaziar a lixeira?", + "permDeleteWarning": "Todos os itens na lixeira serão excluídos permanentemente\n\nEsta ação não pode ser desfeita", + "empty": "Vazio", + "couldNotFreeUpSpace": "Não foi possível liberar espaço", + "permanentlyDeleteFromDevice": "Excluir permanentemente do dispositivo?", + "someOfTheFilesYouAreTryingToDeleteAre": "Alguns dos arquivos que você está tentando excluir só estão disponíveis no seu dispositivo e não podem ser recuperados se forem excluídos", + "theyWillBeDeletedFromAllAlbums": "Ele será excluído de todos os álbuns.", + "someItemsAreInBothEnteAndYourDevice": "Alguns itens estão tanto no ente quanto no seu dispositivo.", + "selectedItemsWillBeDeletedFromAllAlbumsAndMoved": "Os itens selecionados serão excluídos de todos os álbuns e movidos para o lixo.", + "theseItemsWillBeDeletedFromYourDevice": "Estes itens serão excluídos do seu dispositivo.", + "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte.", + "error": "Erro", + "tempErrorContactSupportIfPersists": "Parece que algo deu errado. Por favor, tente novamente mais tarde. Se o erro persistir, entre em contato com nossa equipe de suporte.", + "networkHostLookUpErr": "Não foi possível conectar-se ao Ente, verifique suas configurações de rede e entre em contato com o suporte se o erro persistir.", + "networkConnectionRefusedErr": "Não foi possível conectar ao Ente, tente novamente após algum tempo. Se o erro persistir, entre em contato com o suporte.", + "cachedData": "Dados em cache", + "clearCaches": "Limpar cache", + "remoteImages": "Imagens remotas", + "remoteVideos": "Vídeos remotos", + "remoteThumbnails": "Miniaturas remotas", + "pendingSync": "Sincronização pendente", + "localGallery": "Galeria local", + "todaysLogs": "Logs de hoje", + "viewLogs": "Ver logs", + "logsDialogBody": "Isso enviará através dos logs para nos ajudar a depurar o seu problema. Por favor, note que nomes de arquivos serão incluídos para ajudar a rastrear problemas com arquivos específicos.", + "preparingLogs": "Preparando logs...", + "emailYourLogs": "Enviar por email seus logs", + "pleaseSendTheLogsTo": "Por favor, envie os logs para \n{toEmail}", + "copyEmailAddress": "Copiar endereço de e-mail", + "exportLogs": "Exportar logs", + "pleaseEmailUsAt": "Por favor, envie-nos um e-mail para {toEmail}", + "dismiss": "Descartar", + "didYouKnow": "Você sabia?", + "loadingMessage": "Carregando suas fotos...", + "loadMessage1": "Você pode compartilhar sua assinatura com sua família", + "loadMessage2": "Nós preservamos mais de 30 milhões de memórias até agora", + "loadMessage3": "Mantemos 3 cópias dos seus dados, uma em um abrigo subterrâneo", + "loadMessage4": "Todos os nossos aplicativos são de código aberto", + "loadMessage5": "Nosso código-fonte e criptografia foram auditadas externamente", + "loadMessage6": "Você pode compartilhar links para seus álbuns com seus entes queridos", + "loadMessage7": "Nossos aplicativos móveis são executados em segundo plano para criptografar e fazer backup de quaisquer novas fotos que você clique", + "loadMessage8": "web.ente.io tem um upload rápido", + "loadMessage9": "Nós usamos Xchacha20Poly1305 para criptografar seus dados com segurança", + "photoDescriptions": "Descrições das fotos", + "fileTypesAndNames": "Tipos de arquivo e nomes", + "location": "Local", + "moments": "Momentos", + "searchFaceEmptySection": "Encontre todas as fotos de uma pessoa", + "searchDatesEmptySection": "Pesquisar por data, mês ou ano", + "searchLocationEmptySection": "Fotos de grupo que estão sendo tiradas em algum raio da foto", + "searchPeopleEmptySection": "Convide pessoas e você verá todas as fotos compartilhadas por elas aqui", + "searchAlbumsEmptySection": "Álbuns", + "searchFileTypesAndNamesEmptySection": "Tipos de arquivo e nomes", + "searchCaptionEmptySection": "Adicione descrições como \"#trip\" nas informações das fotos para encontrá-las aqui rapidamente", + "language": "Idioma", + "selectLanguage": "Selecionar Idioma", + "locationName": "Nome do Local", + "addLocation": "Adicionar local", + "groupNearbyPhotos": "Agrupar fotos próximas", + "kiloMeterUnit": "km", + "addLocationButton": "Adicionar", + "radius": "Raio", + "locationTagFeatureDescription": "Uma tag em grupo de todas as fotos que foram tiradas dentro de algum raio de uma foto", + "galleryMemoryLimitInfo": "Até 1000 memórias mostradas na galeria", + "save": "Salvar", + "centerPoint": "Ponto central", + "pickCenterPoint": "Escolha o ponto central", + "useSelectedPhoto": "Utilizar foto selecionada", + "resetToDefault": "Redefinir para o padrão", + "@resetToDefault": { + "description": "Button text to reset cover photo to default" + }, + "edit": "Editar", + "deleteLocation": "Excluir Local", + "rotateLeft": "Girar para a esquerda", + "flip": "Inverter", + "rotateRight": "Girar para a direita", + "saveCopy": "Salvar cópia", + "light": "Claro", + "color": "Cor", + "yesDiscardChanges": "Sim, descartar alterações", + "doYouWantToDiscardTheEditsYouHaveMade": "Você quer descartar as edições que você fez?", + "saving": "Salvando...", + "editsSaved": "Edições salvas", + "oopsCouldNotSaveEdits": "Ops, não foi possível salvar edições", + "distanceInKMUnit": "km", + "@distanceInKMUnit": { + "description": "Unit for distance in km" + }, + "dayToday": "Hoje", + "dayYesterday": "Ontem", + "storage": "Armazenamento", + "usedSpace": "Espaço em uso", + "storageBreakupFamily": "Família", + "storageBreakupYou": "Você", + "@storageBreakupYou": { + "description": "Label to indicate how much storage you are using when you are part of a family plan" + }, + "storageUsageInfo": "{usedAmount} {usedStorageUnit} de {totalAmount} {totalStorageUnit} usado", + "@storageUsageInfo": { + "description": "Example: 1.2 GB of 2 GB used or 100 GB or 2TB used" + }, + "freeStorageSpace": "{freeAmount} {storageUnit} grátis", + "appVersion": "Versão: {versionValue}", + "verifyIDLabel": "Verificar", + "fileInfoAddDescHint": "Adicionar descrição...", + "editLocationTagTitle": "Editar local", + "setLabel": "Aplicar", + "@setLabel": { + "description": "Label of confirm button to add a new custom radius to the radius selector of a location tag" + }, + "setRadius": "Definir raio", + "familyPlanPortalTitle": "Família", + "familyPlanOverview": "Adicione 5 membros da família ao seu plano existente sem pagar a mais.\n\nCada membro recebe seu próprio espaço privado, e nenhum membro pode ver os arquivos uns dos outros a menos que sejam compartilhados.\n\nPlanos de família estão disponíveis para os clientes que têm uma assinatura de ente paga.\n\nassine agora para começar!", + "androidBiometricHint": "Verificar identidade", + "@androidBiometricHint": { + "description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricNotRecognized": "Não reconhecido. Tente novamente.", + "@androidBiometricNotRecognized": { + "description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricSuccess": "Sucesso", + "@androidBiometricSuccess": { + "description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters." + }, + "androidCancelButton": "Cancelar", + "@androidCancelButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters." + }, + "androidSignInTitle": "Autenticação necessária", + "@androidSignInTitle": { + "description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters." + }, + "androidBiometricRequiredTitle": "Biométrica necessária", + "@androidBiometricRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsRequiredTitle": "Credenciais do dispositivo necessárias", + "@androidDeviceCredentialsRequiredTitle": { + "description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters." + }, + "androidDeviceCredentialsSetupDescription": "Credenciais do dispositivo necessárias", + "@androidDeviceCredentialsSetupDescription": { + "description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side." + }, + "goToSettings": "Ir para Configurações", + "@goToSettings": { + "description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters." + }, + "androidGoToSettingsDescription": "A autenticação biométrica não está configurada no seu dispositivo. Vá em 'Configurações > Segurança' para adicionar autenticação biométrica.", + "@androidGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side." + }, + "iOSLockOut": "A Autenticação Biométrica está desativada. Por favor, bloqueie e desbloqueie sua tela para ativá-la.", + "@iOSLockOut": { + "description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side." + }, + "iOSGoToSettingsDescription": "A autenticação biométrica não está configurada no seu dispositivo. Por favor, ative o Touch ID ou o Face ID no seu telefone.", + "@iOSGoToSettingsDescription": { + "description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side." + }, + "iOSOkButton": "Aceitar", + "@iOSOkButton": { + "description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters." + }, + "openstreetmapContributors": "Contribuidores do OpenStreetMap", + "hostedAtOsmFrance": "Hospedado na OSM France", + "map": "Mapa", + "@map": { + "description": "Label for the map view" + }, + "maps": "Mapas", + "enableMaps": "Habilitar mapa", + "enableMapsDesc": "Isto mostrará suas fotos em um mapa do mundo.\n\nEste mapa é hospedado pelo Open Street Map, e os exatos locais de suas fotos nunca são compartilhados.\n\nVocê pode desativar esse recurso a qualquer momento nas Configurações.", + "quickLinks": "Links rápidos", + "selectItemsToAdd": "Selecionar itens para adicionar", + "addSelected": "Adicionar selecionado", + "addFromDevice": "Adicionar a partir do dispositivo", + "addPhotos": "Adicionar fotos", + "noPhotosFoundHere": "Nenhuma foto encontrada aqui", + "zoomOutToSeePhotos": "Diminuir o zoom para ver fotos", + "noImagesWithLocation": "Nenhuma imagem com local", + "unpinAlbum": "Desafixar álbum", + "pinAlbum": "Fixar álbum", + "create": "Criar", + "viewAll": "Ver tudo", + "nothingSharedWithYouYet": "Nada compartilhado com você ainda", + "noAlbumsSharedByYouYet": "Nenhum álbum compartilhado por você ainda", + "sharedWithYou": "Compartilhado com você", + "sharedByYou": "Compartilhado por você", + "inviteYourFriendsToEnte": "Convide seus amigos ao ente", + "failedToDownloadVideo": "Falha ao baixar vídeo", + "hiding": "Ocultando...", + "unhiding": "Desocultando...", + "successfullyHid": "Ocultado com sucesso", + "successfullyUnhid": "Desocultado com sucesso", + "crashReporting": "Relatório de falhas", + "addToHiddenAlbum": "Adicionar a álbum oculto", + "moveToHiddenAlbum": "Mover para álbum oculto", + "fileTypes": "Tipos de arquivo", + "deleteConfirmDialogBody": "Esta conta está vinculada a outros aplicativos ente, se você usar algum. Seus dados enviados, em todos os aplicativos ente, serão agendados para exclusão, e sua conta será excluída permanentemente.", + "hearUsWhereTitle": "Como você ouviu sobre o Ente? (opcional)", + "hearUsExplanation": "Não rastreamos instalações do aplicativo. Seria útil se você nos contasse onde nos encontrou!", + "viewAddOnButton": "Ver complementos", + "addOns": "Complementos", + "addOnPageSubtitle": "Detalhes dos complementos", + "yourMap": "Seu mapa", + "modifyYourQueryOrTrySearchingFor": "Modifique sua consulta ou tente procurar por", + "blackFridaySale": "Promoção da Black Friday", + "upto50OffUntil4thDec": "Até 50% de desconto, até 4 de dezembro.", + "photos": "Fotos", + "videos": "Vídeos", + "livePhotos": "Fotos em movimento", + "searchHint1": "Rápido, pesquisa no dispositivo", + "searchHint2": "Datas das fotos, descrições", + "searchHint3": "Álbuns, nomes de arquivos e tipos", + "searchHint4": "Local", + "searchHint5": "Em breve: Rostos e busca mágica ✨", + "addYourPhotosNow": "Adicione suas fotos agora", + "searchResultCount": "{count, plural, one{{count} resultado encontrado} other{{count} resultado encontrado}}", + "@searchResultCount": { + "description": "Text to tell user how many results were found for their search query", + "placeholders": { + "count": { + "example": "1|2|3", + "type": "int" + } + } + }, + "faces": "Rostos", + "contents": "Conteúdos", + "addNew": "Adicionar novo", + "@addNew": { + "description": "Text to add a new item (location tag, album, caption etc)" + }, + "contacts": "Contatos", + "noInternetConnection": "Sem conexão à internet", + "pleaseCheckYourInternetConnectionAndTryAgain": "Verifique sua conexão com a internet e tente novamente.", + "signOutFromOtherDevices": "Terminar sessão em outros dispositivos", + "signOutOtherBody": "Se você acha que alguém pode saber sua senha, você pode forçar todos os outros dispositivos que estão com sua conta a desconectar.", + "signOutOtherDevices": "Terminar sessão em outros dispositivos", + "doNotSignOut": "Não encerrar sessão", + "editLocation": "Editar local", + "selectALocation": "Selecionar um local", + "selectALocationFirst": "Selecione um local primeiro", + "changeLocationOfSelectedItems": "Alterar o local dos itens selecionados?", + "editsToLocationWillOnlyBeSeenWithinEnte": "Edições para local só serão vistas dentro do Ente", + "cleanUncategorized": "Limpar Sem Categoria", + "playOnTv": "Reproduzir álbum na TV", + "pair": "Parear", + "deviceNotFound": "Dispositivo não encontrado", + "castInstruction": "Visite cast.ente.io no dispositivo que você deseja parear.\n\ndigite o código abaixo para reproduzir o álbum em sua TV.", + "deviceCodeHint": "Insira o código", + "joinDiscord": "Junte-se ao Discord" } \ No newline at end of file diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 8bc770f2d..e3420317a 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -1193,5 +1193,5 @@ "deviceNotFound": "未发现设备", "castInstruction": "在您要配对的设备上访问 cast.ente.io。\n输入下面的代码即可在电视上播放相册。", "deviceCodeHint": "输入代码", - "joinDiscord": "Join Discord" + "joinDiscord": "加入 Discord" } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 8e553280b..91c17974a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,11 +30,12 @@ import 'package:photos/services/feature_flag_service.dart'; import 'package:photos/services/local_file_update_service.dart'; import 'package:photos/services/local_sync_service.dart'; import "package:photos/services/location_service.dart"; +import "package:photos/services/machine_learning/machine_learning_controller.dart"; +import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; import 'package:photos/services/memories_service.dart'; import 'package:photos/services/push_service.dart'; import 'package:photos/services/remote_sync_service.dart'; import 'package:photos/services/search_service.dart'; -import 'package:photos/services/semantic_search/semantic_search_service.dart'; import "package:photos/services/storage_bonus_service.dart"; import 'package:photos/services/sync_service.dart'; import 'package:photos/services/trash_sync_service.dart'; @@ -194,6 +195,7 @@ Future _init(bool isBackground, {String via = ''}) async { } unawaited(FeatureFlagService.instance.init()); unawaited(SemanticSearchService.instance.init()); + MachineLearningController.instance.init(); // Can not including existing tf/ml binaries as they are not being built // from source. // See https://gitlab.com/fdroid/fdroiddata/-/merge_requests/12671#note_1294346819 diff --git a/lib/models/metadata/file_magic.dart b/lib/models/metadata/file_magic.dart index ca08f95f8..d50dc0dd8 100644 --- a/lib/models/metadata/file_magic.dart +++ b/lib/models/metadata/file_magic.dart @@ -11,6 +11,7 @@ const heightKey = 'h'; const latKey = "lat"; const longKey = "long"; const motionVideoIndexKey = "mvi"; +const noThumbKey = "noThumb"; class MagicMetadata { // 0 -> visible @@ -47,6 +48,13 @@ class PubMagicMetadata { // photo int? mvi; + // if true, then the thumbnail is not available + // Note: desktop/web sets hasStaticThumbnail in the file metadata. + // As we don't want to support updating the og file metadata (yet), adding + // this new field to the pub metadata. For static thumbnail, all thumbnails + // should have exact same hash with should match the constant `blackThumbnailBase64` + bool? noThumb; + PubMagicMetadata({ this.editedTime, this.editedName, @@ -57,6 +65,7 @@ class PubMagicMetadata { this.lat, this.long, this.mvi, + this.noThumb, }); factory PubMagicMetadata.fromEncodedJson(String encodedJson) => @@ -77,6 +86,7 @@ class PubMagicMetadata { lat: map[latKey], long: map[longKey], mvi: map[motionVideoIndexKey], + noThumb: map[noThumbKey], ); } } diff --git a/lib/models/search/search_types.dart b/lib/models/search/search_types.dart index 9702b2e1b..92c71cd87 100644 --- a/lib/models/search/search_types.dart +++ b/lib/models/search/search_types.dart @@ -9,6 +9,7 @@ import "package:photos/events/location_tag_updated_event.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/collection/collection.dart"; import "package:photos/models/collection/collection_items.dart"; +import "package:photos/models/search/generic_search_result.dart"; import "package:photos/models/search/search_result.dart"; import "package:photos/models/typedefs.dart"; import "package:photos/services/collections_service.dart"; @@ -24,6 +25,7 @@ enum ResultType { collection, file, location, + locationSuggestion, month, year, fileType, @@ -243,10 +245,10 @@ extension SectionTypeExtensions on SectionType { }) { switch (this) { case SectionType.face: - return SearchService.instance.getAllLocationTags(limit); + return Future.value(List.empty()); case SectionType.content: - return SearchService.instance.getAllLocationTags(limit); + return Future.value(List.empty()); case SectionType.moment: return SearchService.instance.getRandomMomentsSearchResults(context); diff --git a/lib/services/feature_flag_service.dart b/lib/services/feature_flag_service.dart index 9da184a61..689c7cc40 100644 --- a/lib/services/feature_flag_service.dart +++ b/lib/services/feature_flag_service.dart @@ -71,10 +71,9 @@ class FeatureFlagService { bool isInternalUserOrDebugBuild() { final String? email = Configuration.instance.getEmail(); final userID = Configuration.instance.getUserID(); - isInternalUser = (email != null && email.endsWith("@ente.io")) || + return (email != null && email.endsWith("@ente.io")) || _internalUserIDs.contains(userID) || kDebugMode; - return isInternalUser; } Future fetchFeatureFlags() async { diff --git a/lib/services/location_service.dart b/lib/services/location_service.dart index b461d9bc0..18173febb 100644 --- a/lib/services/location_service.dart +++ b/lib/services/location_service.dart @@ -8,6 +8,7 @@ import "package:logging/logging.dart"; import "package:photos/core/constants.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/events/location_tag_updated_event.dart"; +import "package:photos/extensions/stop_watch.dart"; import "package:photos/models/api/entity/type.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/local_entity_data.dart"; @@ -46,6 +47,8 @@ class LocationService { List allFiles, String query, ) async { + final EnteWatch w = EnteWatch("cities_search")..start(); + w.log('start for files ${allFiles.length} and query $query'); final result = await _computer.compute( getCityResults, param: { @@ -54,6 +57,10 @@ class LocationService { "files": allFiles, }, ); + w.log( + 'end for query: $query on ${allFiles.length} files, found ' + '${result.length} cities', + ); return result; } @@ -236,31 +243,29 @@ Future> parseCities(Map args) async { Map> getCityResults(Map args) { final query = (args["query"] as String).toLowerCase(); - final cities = args["cities"] as List; - final files = args["files"] as List; + final List cities = args["cities"] as List; + final List files = args["files"] as List; - final matchingCities = cities.where( - (city) => city.city.toLowerCase().contains(query), - ); + final matchingCities = cities + .where( + (city) => city.city.toLowerCase().contains(query), + ) + .toList(); final Map> results = {}; - for (final city in matchingCities) { - final List matchingFiles = []; - final cityLocation = Location(latitude: city.lat, longitude: city.lng); - for (final file in files) { - if (file.hasLocation) { - if (isFileInsideLocationTag( - cityLocation, - file.location!, - defaultCityRadius, - )) { - matchingFiles.add(file); - } + for (final file in files) { + if (!file.hasLocation) continue; // Skip files without location + for (final city in matchingCities) { + final cityLocation = Location(latitude: city.lat, longitude: city.lng); + if (isFileInsideLocationTag( + cityLocation, + file.location!, + defaultCityRadius, + )) { + results.putIfAbsent(city, () => []).add(file); + break; // Stop searching once a file is matched with a city } } - if (matchingFiles.isNotEmpty) { - results[city] = matchingFiles; - } } return results; } diff --git a/lib/services/machine_learning/machine_learning_controller.dart b/lib/services/machine_learning/machine_learning_controller.dart new file mode 100644 index 000000000..a8a075ba3 --- /dev/null +++ b/lib/services/machine_learning/machine_learning_controller.dart @@ -0,0 +1,97 @@ +import "dart:async"; +import "dart:io"; + +import "package:battery_info/battery_info_plugin.dart"; +import "package:battery_info/model/android_battery_info.dart"; +import "package:logging/logging.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/machine_learning_control_event.dart"; + +class MachineLearningController { + MachineLearningController._privateConstructor(); + + static final MachineLearningController instance = + MachineLearningController._privateConstructor(); + + final _logger = Logger("MachineLearningController"); + + static const kMaximumTemperature = 42; // 42 degree celsius + static const kMinimumBatteryLevel = 20; // 20% + static const kInitialInteractionTimeout = Duration(seconds: 10); + static const kDefaultInteractionTimeout = Duration(seconds: 5); + static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"]; + + bool _isDeviceHealthy = true; + bool _isUserInteracting = true; + bool _isRunningML = false; + late Timer _userInteractionTimer; + + void init() { + if (Platform.isAndroid) { + _startInteractionTimer(timeout: kInitialInteractionTimeout); + BatteryInfoPlugin() + .androidBatteryInfoStream + .listen((AndroidBatteryInfo? batteryInfo) { + _onBatteryStateUpdate(batteryInfo); + }); + } else { + // Always run Machine Learning on iOS + Bus.instance.fire(MachineLearningControlEvent(true)); + } + } + + void onUserInteraction() { + _logger.info("User is interacting with the app"); + _isUserInteracting = true; + _fireControlEvent(); + _resetTimer(); + } + + void _fireControlEvent() { + final shouldRunML = _isDeviceHealthy && !_isUserInteracting; + if (shouldRunML != _isRunningML) { + _isRunningML = shouldRunML; + _logger.info( + "Firing event with device health: $_isDeviceHealthy and user interaction: $_isUserInteracting", + ); + Bus.instance.fire(MachineLearningControlEvent(shouldRunML)); + } + } + + void _startInteractionTimer({Duration timeout = kDefaultInteractionTimeout}) { + _userInteractionTimer = Timer(timeout, () { + _logger.info("User is not interacting with the app"); + _isUserInteracting = false; + _fireControlEvent(); + }); + } + + void _resetTimer() { + _userInteractionTimer.cancel(); + _startInteractionTimer(); + } + + void _onBatteryStateUpdate(AndroidBatteryInfo? batteryInfo) { + _logger.info("Battery info: ${batteryInfo!.toJson()}"); + _isDeviceHealthy = _computeIsDeviceHealthy(batteryInfo); + _fireControlEvent(); + } + + bool _computeIsDeviceHealthy(AndroidBatteryInfo info) { + return _hasSufficientBattery(info.batteryLevel ?? kMinimumBatteryLevel) && + _isAcceptableTemperature(info.temperature ?? kMaximumTemperature) && + _isBatteryHealthy(info.health ?? ""); + } + + bool _hasSufficientBattery(int batteryLevel) { + return batteryLevel >= kMinimumBatteryLevel; + } + + bool _isAcceptableTemperature(int temperature) { + return temperature <= kMaximumTemperature; + } + + bool _isBatteryHealthy(String health) { + return !kUnhealthyStates.contains(health); + } +} diff --git a/lib/services/semantic_search/embedding_store.dart b/lib/services/machine_learning/semantic_search/embedding_store.dart similarity index 93% rename from lib/services/semantic_search/embedding_store.dart rename to lib/services/machine_learning/semantic_search/embedding_store.dart index 45102f942..8c7d14399 100644 --- a/lib/services/semantic_search/embedding_store.dart +++ b/lib/services/machine_learning/semantic_search/embedding_store.dart @@ -9,7 +9,7 @@ import "package:photos/db/embeddings_db.dart"; import "package:photos/db/files_db.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; -import "package:photos/services/semantic_search/remote_embedding.dart"; +import 'package:photos/services/machine_learning/semantic_search/remote_embedding.dart'; import "package:photos/utils/crypto_util.dart"; import "package:photos/utils/file_download_util.dart"; import "package:shared_preferences/shared_preferences.dart"; @@ -53,13 +53,22 @@ class EmbeddingStore { final fileMap = await FilesDB.instance .getFilesFromIDs(pendingItems.map((e) => e.fileID).toList()); _logger.info("Pushing ${pendingItems.length} embeddings"); + final deletedEntries = []; for (final item in pendingItems) { try { - await _pushEmbedding(fileMap[item.fileID]!, item); + final file = fileMap[item.fileID]; + if (file != null) { + await _pushEmbedding(file, item); + } else { + deletedEntries.add(item.fileID); + } } catch (e, s) { _logger.severe(e, s); } } + if (deletedEntries.isNotEmpty) { + await EmbeddingsDB.instance.deleteEmbeddings(deletedEntries); + } } Future storeEmbedding(EnteFile file, Embedding embedding) async { diff --git a/lib/services/semantic_search/frameworks/ggml.dart b/lib/services/machine_learning/semantic_search/frameworks/ggml.dart similarity index 97% rename from lib/services/semantic_search/frameworks/ggml.dart rename to lib/services/machine_learning/semantic_search/frameworks/ggml.dart index 6ff862084..f83a2a669 100644 --- a/lib/services/semantic_search/frameworks/ggml.dart +++ b/lib/services/machine_learning/semantic_search/frameworks/ggml.dart @@ -1,7 +1,7 @@ import "package:clip_ggml/clip_ggml.dart"; import "package:computer/computer.dart"; import "package:logging/logging.dart"; -import 'package:photos/services/semantic_search/frameworks/ml_framework.dart'; +import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart'; class GGML extends MLFramework { static const kModelBucketEndpoint = "https://models.ente.io/"; diff --git a/lib/services/semantic_search/frameworks/ml_framework.dart b/lib/services/machine_learning/semantic_search/frameworks/ml_framework.dart similarity index 74% rename from lib/services/semantic_search/frameworks/ml_framework.dart rename to lib/services/machine_learning/semantic_search/frameworks/ml_framework.dart index 3523c721e..35616b1b1 100644 --- a/lib/services/semantic_search/frameworks/ml_framework.dart +++ b/lib/services/machine_learning/semantic_search/frameworks/ml_framework.dart @@ -2,15 +2,13 @@ import "dart:async"; import "dart:io"; import "package:connectivity_plus/connectivity_plus.dart"; -import "package:flutter/services.dart"; import "package:logging/logging.dart"; -import "package:path/path.dart"; -import "package:path_provider/path_provider.dart"; import "package:photos/core/errors.dart"; import "package:photos/core/event_bus.dart"; import "package:photos/core/network/network.dart"; import "package:photos/events/event.dart"; +import "package:photos/services/remote_assets_service.dart"; abstract class MLFramework { static const kImageEncoderEnabled = true; @@ -105,46 +103,20 @@ abstract class MLFramework { return; } _initState = InitializationState.initializingImageModel; - final path = await _getLocalImageModelPath(); - if (await File(path).exists()) { - await loadImageModel(path); - } else { - _initState = InitializationState.downloadingImageModel; - final tempFile = File(path + ".temp"); - await _downloadFile(getImageModelRemotePath(), tempFile.path); - await tempFile.rename(path); - await loadImageModel(path); - } + final imageModel = + await RemoteAssetsService.instance.getAsset(getImageModelRemotePath()); + await loadImageModel(imageModel.path); _initState = InitializationState.initializedImageModel; } Future _initTextModel() async { - final path = await _getLocalTextModelPath(); _initState = InitializationState.initializingTextModel; - if (await File(path).exists()) { - await loadTextModel(path); - } else { - _initState = InitializationState.downloadingTextModel; - final tempFile = File(path + ".temp"); - await _downloadFile(getTextModelRemotePath(), tempFile.path); - await tempFile.rename(path); - await loadTextModel(path); - } + final textModel = + await RemoteAssetsService.instance.getAsset(getTextModelRemotePath()); + await loadTextModel(textModel.path); _initState = InitializationState.initializedTextModel; } - Future _getLocalImageModelPath() async { - return (await getTemporaryDirectory()).path + - "/models/" + - basename(getImageModelRemotePath()); - } - - Future _getLocalTextModelPath() async { - return (await getTemporaryDirectory()).path + - "/models/" + - basename(getTextModelRemotePath()); - } - Future _downloadFile( String url, String savePath, { @@ -176,17 +148,6 @@ abstract class MLFramework { return connectivityResult != ConnectivityResult.mobile || shouldDownloadOverMobileData; } - - Future getAccessiblePathForAsset( - String assetPath, - String tempName, - ) async { - final byteData = await rootBundle.load(assetPath); - final tempDir = await getTemporaryDirectory(); - final file = await File('${tempDir.path}/$tempName') - .writeAsBytes(byteData.buffer.asUint8List()); - return file.path; - } } class MLFrameworkInitializationUpdateEvent extends Event { @@ -198,10 +159,8 @@ class MLFrameworkInitializationUpdateEvent extends Event { enum InitializationState { notInitialized, waitingForNetwork, - downloadingImageModel, initializingImageModel, initializedImageModel, - downloadingTextModel, initializingTextModel, initializedTextModel, initialized, diff --git a/lib/services/semantic_search/frameworks/onnx/onnx.dart b/lib/services/machine_learning/semantic_search/frameworks/onnx/onnx.dart similarity index 91% rename from lib/services/semantic_search/frameworks/onnx/onnx.dart rename to lib/services/machine_learning/semantic_search/frameworks/onnx/onnx.dart index 00930ccac..56b53df71 100644 --- a/lib/services/semantic_search/frameworks/onnx/onnx.dart +++ b/lib/services/machine_learning/semantic_search/frameworks/onnx/onnx.dart @@ -1,9 +1,9 @@ import "package:computer/computer.dart"; import "package:logging/logging.dart"; import "package:onnxruntime/onnxruntime.dart"; -import "package:photos/services/semantic_search/frameworks/ml_framework.dart"; -import "package:photos/services/semantic_search/frameworks/onnx/onnx_image_encoder.dart"; -import "package:photos/services/semantic_search/frameworks/onnx/onnx_text_encoder.dart"; +import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart'; +import 'package:photos/services/machine_learning/semantic_search/frameworks/onnx/onnx_image_encoder.dart'; +import 'package:photos/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_encoder.dart'; class ONNX extends MLFramework { static const kModelBucketEndpoint = "https://models.ente.io/"; diff --git a/lib/services/semantic_search/frameworks/onnx/onnx_image_encoder.dart b/lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_image_encoder.dart similarity index 100% rename from lib/services/semantic_search/frameworks/onnx/onnx_image_encoder.dart rename to lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_image_encoder.dart diff --git a/lib/services/semantic_search/frameworks/onnx/onnx_text_encoder.dart b/lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_encoder.dart similarity index 95% rename from lib/services/semantic_search/frameworks/onnx/onnx_text_encoder.dart rename to lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_encoder.dart index 664b925e7..7f954ed7f 100644 --- a/lib/services/semantic_search/frameworks/onnx/onnx_text_encoder.dart +++ b/lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_encoder.dart @@ -5,7 +5,7 @@ import "dart:typed_data"; import "package:flutter/services.dart"; import "package:logging/logging.dart"; import "package:onnxruntime/onnxruntime.dart"; -import "package:photos/services/semantic_search/frameworks/onnx/onnx_text_tokenizer.dart"; +import 'package:photos/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_tokenizer.dart'; class OnnxTextEncoder { static const kVocabFilePath = "assets/models/clip/bpe_simple_vocab_16e6.txt"; diff --git a/lib/services/semantic_search/frameworks/onnx/onnx_text_tokenizer.dart b/lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_tokenizer.dart similarity index 100% rename from lib/services/semantic_search/frameworks/onnx/onnx_text_tokenizer.dart rename to lib/services/machine_learning/semantic_search/frameworks/onnx/onnx_text_tokenizer.dart diff --git a/lib/services/semantic_search/remote_embedding.dart b/lib/services/machine_learning/semantic_search/remote_embedding.dart similarity index 100% rename from lib/services/semantic_search/remote_embedding.dart rename to lib/services/machine_learning/semantic_search/remote_embedding.dart diff --git a/lib/services/semantic_search/semantic_search_service.dart b/lib/services/machine_learning/semantic_search/semantic_search_service.dart similarity index 85% rename from lib/services/semantic_search/semantic_search_service.dart rename to lib/services/machine_learning/semantic_search/semantic_search_service.dart index 8699b0e4e..28ba0cb03 100644 --- a/lib/services/semantic_search/semantic_search_service.dart +++ b/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -1,5 +1,6 @@ import "dart:async"; import "dart:collection"; +import "dart:io"; import "package:computer/computer.dart"; import "package:logging/logging.dart"; @@ -11,14 +12,16 @@ import "package:photos/db/files_db.dart"; import "package:photos/events/diff_sync_complete_event.dart"; import 'package:photos/events/embedding_updated_event.dart'; import "package:photos/events/file_uploaded_event.dart"; +import "package:photos/events/machine_learning_control_event.dart"; import "package:photos/models/embedding.dart"; import "package:photos/models/file/file.dart"; import "package:photos/services/collections_service.dart"; -import "package:photos/services/semantic_search/embedding_store.dart"; -import "package:photos/services/semantic_search/frameworks/ggml.dart"; -import "package:photos/services/semantic_search/frameworks/ml_framework.dart"; -import 'package:photos/services/semantic_search/frameworks/onnx/onnx.dart'; +import 'package:photos/services/machine_learning/semantic_search/embedding_store.dart'; +import 'package:photos/services/machine_learning/semantic_search/frameworks/ggml.dart'; +import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart'; +import 'package:photos/services/machine_learning/semantic_search/frameworks/onnx/onnx.dart'; import "package:photos/utils/debouncer.dart"; +import "package:photos/utils/device_info.dart"; import "package:photos/utils/local_settings.dart"; import "package:photos/utils/thumbnail_util.dart"; @@ -33,7 +36,6 @@ class SemanticSearchService { static const kEmbeddingLength = 512; static const kScoreThreshold = 0.23; static const kShouldPushEmbeddings = true; - static const kCurrentModel = Model.onnxClip; static const kDebounceDuration = Duration(milliseconds: 4000); final _logger = Logger("SemanticSearchService"); @@ -42,6 +44,7 @@ class SemanticSearchService { final _embeddingLoaderDebouncer = Debouncer(kDebounceDuration, executionInterval: kDebounceDuration); + late Model _currentModel; late MLFramework _mlFramework; bool _hasInitialized = false; bool _isComputingEmbeddings = false; @@ -49,22 +52,10 @@ class SemanticSearchService { Future>? _ongoingRequest; List _cachedEmbeddings = []; PendingQuery? _nextQuery; - Completer _userInteraction = Completer(); + Completer _mlController = Completer(); get hasInitialized => _hasInitialized; - void resumeIndexing() { - _logger.info("Resuming indexing"); - _userInteraction.complete(); - } - - void pauseIndexing() { - if (_userInteraction.isCompleted) { - _logger.info("Pausing indexing"); - _userInteraction = Completer(); - } - } - Future init({bool shouldSyncImmediately = false}) async { if (!LocalSettings.instance.hasEnabledMagicSearch()) { return; @@ -76,7 +67,8 @@ class SemanticSearchService { _hasInitialized = true; final shouldDownloadOverMobileData = Configuration.instance.shouldBackupOverMobileData(); - _mlFramework = kCurrentModel == Model.onnxClip + _currentModel = await _getCurrentModel(); + _mlFramework = _currentModel == Model.onnxClip ? ONNX(shouldDownloadOverMobileData) : GGML(shouldDownloadOverMobileData); await EmbeddingsDB.instance.init(); @@ -109,6 +101,17 @@ class SemanticSearchService { if (shouldSyncImmediately) { unawaited(sync()); } + if (Platform.isAndroid) { + Bus.instance.on().listen((event) { + if (event.shouldRun) { + _startIndexing(); + } else { + _pauseIndexing(); + } + }); + } else { + _startIndexing(); + } } Future release() async { @@ -122,7 +125,7 @@ class SemanticSearchService { return; } _isSyncing = true; - await EmbeddingStore.instance.pullEmbeddings(kCurrentModel); + await EmbeddingStore.instance.pullEmbeddings(_currentModel); await _backFill(); _isSyncing = false; } @@ -171,14 +174,14 @@ class SemanticSearchService { } Future clearIndexes() async { - await EmbeddingStore.instance.clearEmbeddings(kCurrentModel); - _logger.info("Indexes cleared for $kCurrentModel"); + await EmbeddingStore.instance.clearEmbeddings(_currentModel); + _logger.info("Indexes cleared for $_currentModel"); } Future _loadEmbeddings() async { _logger.info("Pulling cached embeddings"); final startTime = DateTime.now(); - _cachedEmbeddings = await EmbeddingsDB.instance.getAll(kCurrentModel); + _cachedEmbeddings = await EmbeddingsDB.instance.getAll(_currentModel); final endTime = DateTime.now(); _logger.info( "Loading ${_cachedEmbeddings.length} took: ${(endTime.millisecondsSinceEpoch - startTime.millisecondsSinceEpoch)}ms", @@ -240,15 +243,23 @@ class SemanticSearchService { final ignoredCollections = CollectionsService.instance.getHiddenCollectionIds(); + final deletedEntries = []; for (final result in queryResults) { final file = filesMap[result.id]; if (file != null && !ignoredCollections.contains(file.collectionID)) { - results.add(filesMap[result.id]!); + results.add(file); + } + if (file == null) { + deletedEntries.add(result.id); } } _logger.info(results.length.toString() + " results"); + if (deletedEntries.isNotEmpty) { + unawaited(EmbeddingsDB.instance.deleteEmbeddings(deletedEntries)); + } + return results; } @@ -292,9 +303,9 @@ class SemanticSearchService { if (!_frameworkInitialization.isCompleted) { return; } - if (!_userInteraction.isCompleted) { - _logger.info("Waiting for user interactions to stop..."); - await _userInteraction.future; + if (!_mlController.isCompleted) { + _logger.info("Waiting for a green signal from controller..."); + await _mlController.future; } try { final thumbnail = await getThumbnailForUploadedFile(file); @@ -312,7 +323,7 @@ class SemanticSearchService { final embedding = Embedding( fileID: file.uploadedFileID!, - model: kCurrentModel, + model: _currentModel, embedding: result, ); await EmbeddingStore.instance.storeEmbedding( @@ -359,6 +370,28 @@ class SemanticSearchService { ); return queryResults; } + + Future _getCurrentModel() async { + if (await isGrapheneOS()) { + return Model.ggmlClip; + } else { + return Model.onnxClip; + } + } + + void _startIndexing() { + _logger.info("Start indexing"); + if (!_mlController.isCompleted) { + _mlController.complete(); + } + } + + void _pauseIndexing() { + if (_mlController.isCompleted) { + _logger.info("Pausing indexing"); + _mlController = Completer(); + } + } } List computeBulkScore(Map args) { diff --git a/lib/services/remote_assets_service.dart b/lib/services/remote_assets_service.dart index 8a11f84a4..0e75b983d 100644 --- a/lib/services/remote_assets_service.dart +++ b/lib/services/remote_assets_service.dart @@ -27,7 +27,7 @@ class RemoteAssetsService { } Future _getLocalPath(String remotePath) async { - return (await getTemporaryDirectory()).path + + return (await getApplicationSupportDirectory()).path + "/assets/" + _urlToFileName(remotePath); } @@ -53,5 +53,6 @@ class RemoteAssetsService { await existingFile.delete(); } await NetworkClient.instance.getDio().download(url, savePath); + _logger.info("Downloaded " + url); } } diff --git a/lib/services/search_service.dart b/lib/services/search_service.dart index e57424017..fa2317836 100644 --- a/lib/services/search_service.dart +++ b/lib/services/search_service.dart @@ -3,6 +3,7 @@ import "dart:math"; import "package:flutter/cupertino.dart"; import "package:intl/intl.dart"; import 'package:logging/logging.dart'; +import "package:photos/core/constants.dart"; import 'package:photos/core/event_bus.dart'; import 'package:photos/data/holidays.dart'; import 'package:photos/data/months.dart'; @@ -17,14 +18,16 @@ import "package:photos/models/file/extensions/file_props.dart"; import 'package:photos/models/file/file.dart'; import 'package:photos/models/file/file_type.dart'; import "package:photos/models/local_entity_data.dart"; +import "package:photos/models/location/location.dart"; import "package:photos/models/location_tag/location_tag.dart"; import 'package:photos/models/search/album_search_result.dart'; import 'package:photos/models/search/generic_search_result.dart'; import "package:photos/models/search/search_types.dart"; import 'package:photos/services/collections_service.dart'; import "package:photos/services/location_service.dart"; -import 'package:photos/services/semantic_search/semantic_search_service.dart'; +import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; import "package:photos/states/location_screen_state.dart"; +import "package:photos/ui/viewer/location/add_location_sheet.dart"; import "package:photos/ui/viewer/location/location_screen.dart"; import 'package:photos/utils/date_time_util.dart'; import "package:photos/utils/navigation_util.dart"; @@ -676,17 +679,24 @@ class SearchService { ); } } - + //todo: remove this later, this hack is for interval+external evaluation + // for suggestions + final allCitiesSearch = query == '__city'; + if (allCitiesSearch) { + query = ''; + } final results = await LocationService.instance.getFilesInCity(allFiles, query); - for (final entry in results.entries) { + final List sortedByResultCount = results.keys.toList() + ..sort((a, b) => results[b]!.length.compareTo(results[a]!.length)); + for (final city in sortedByResultCount) { // If the location tag already exists for a city, don't add it again - if (!locationTagNames.contains(entry.key.city)) { + if (!locationTagNames.contains(city.city)) { searchResults.add( GenericSearchResult( ResultType.location, - entry.key.city, - entry.value, + city.city, + results[city]!, ), ); } @@ -701,6 +711,7 @@ class SearchService { final locationTagEntities = (await LocationService.instance.getLocationTags()); final allFiles = await getAllFiles(); + final List filesWithNoLocTag = []; for (int i = 0; i < locationTagEntities.length; i++) { if (limit != null && i >= limit) break; @@ -709,15 +720,22 @@ class SearchService { for (EnteFile file in allFiles) { if (file.hasLocation) { + bool hasLocationTag = false; for (LocalEntity tag in tagToItemsMap.keys) { if (isFileInsideLocationTag( tag.item.centerPoint, file.location!, tag.item.radius, )) { + hasLocationTag = true; tagToItemsMap[tag]!.add(file); } } + // If the location tag already exists for a city, do not consider + // it for the city suggestions + if (!hasLocationTag) { + filesWithNoLocTag.add(file); + } } } @@ -746,6 +764,30 @@ class SearchService { ); } } + if (limit == null || tagSearchResults.length < limit) { + final results = await LocationService.instance + .getFilesInCity(filesWithNoLocTag, ''); + final List sortedByResultCount = results.keys.toList() + ..sort((a, b) => results[b]!.length.compareTo(results[a]!.length)); + for (final city in sortedByResultCount) { + if (results[city]!.length <= 1) continue; + tagSearchResults.add( + GenericSearchResult( + ResultType.locationSuggestion, + city.city, + results[city]!, + onResultTap: (ctx) { + showAddLocationSheet( + ctx, + Location(latitude: city.lat, longitude: city.lng), + name: city.city, + radius: defaultCityRadius, + ); + }, + ), + ); + } + } return tagSearchResults; } catch (e) { _logger.severe("Error in getAllLocationTags", e); diff --git a/lib/services/update_service.dart b/lib/services/update_service.dart index 2a63d818b..10b066390 100644 --- a/lib/services/update_service.dart +++ b/lib/services/update_service.dart @@ -73,30 +73,39 @@ class UpdateService { return _latestVersion; } - Future showUpdateNotification() async { - if (!isIndependent()) { - return; - } + Future shouldShowUpdateNoification() async { final shouldUpdate = await this.shouldUpdate(); + final lastNotificationShownTime = _prefs.getInt(kUpdateAvailableShownTimeKey) ?? 0; final now = DateTime.now().microsecondsSinceEpoch; - final hasBeen3DaysSinceLastNotification = - (now - lastNotificationShownTime) > (3 * microSecondsInDay); - if (shouldUpdate && - hasBeen3DaysSinceLastNotification && - _latestVersion!.shouldNotify) { + final hasBeenThresholdDaysSinceLastNotification = + (now - lastNotificationShownTime) > + ((_latestVersion!.shouldNotify ? 1 : 3) * microSecondsInDay); + + return shouldUpdate && hasBeenThresholdDaysSinceLastNotification; + } + + Future showUpdateNotification() async { + if (await shouldShowUpdateNoification()) { // ignore: unawaited_futures NotificationService.instance.showNotification( "Update available", "Click to install our best version yet", ); - await _prefs.setInt(kUpdateAvailableShownTimeKey, now); + await resetUpdateAvailableShownTime(); } else { _logger.info("Debouncing notification"); } } + Future resetUpdateAvailableShownTime() { + return _prefs.setInt( + kUpdateAvailableShownTimeKey, + DateTime.now().microsecondsSinceEpoch, + ); + } + Future _getLatestVersionInfo() async { final response = await NetworkClient.instance .getDio() @@ -145,9 +154,12 @@ class UpdateService { ); } return Platform.isAndroid - ? const Tuple2("play store", "market://details?id=io.ente.photos") + ? const Tuple2( + "Google Play", + "https://play.google.com/store/apps/details?id=io.ente.photos", + ) : const Tuple2( - "app store", + "App Store", "https://apps.apple.com/in/app/ente-photos/id1542026904", ); } diff --git a/lib/states/location_state.dart b/lib/states/location_state.dart index 5711b2af8..3fc773b99 100644 --- a/lib/states/location_state.dart +++ b/lib/states/location_state.dart @@ -14,11 +14,14 @@ import "package:photos/utils/debouncer.dart"; class LocationTagStateProvider extends StatefulWidget { final LocalEntity? locationTagEntity; final Location? centerPoint; + final double? radius; final Widget child; const LocationTagStateProvider( this.child, { this.centerPoint, this.locationTagEntity, + // if the locationTagEntity is null, we use the centerPoint and radius + this.radius, super.key, }); @@ -47,9 +50,12 @@ class _LocationTagStateProviderState extends State { ///If the location tag has a custom radius value, we add the custom radius ///value to the list of default radius values only for this location tag and ///keep it in the state of this widget. - _radiusValues = _getRadiusValuesOfLocTag(_locationTagEntity?.item.radius); + _radiusValues = _getRadiusValuesOfLocTag( + _locationTagEntity?.item.radius ?? widget.radius, + ); - _selectedRadius = _locationTagEntity?.item.radius ?? defaultRadiusValue; + _selectedRadius = + _locationTagEntity?.item.radius ?? widget.radius ?? defaultRadiusValue; _locTagEntityListener = Bus.instance.on().listen((event) { diff --git a/lib/ui/account/password_reentry_page.dart b/lib/ui/account/password_reentry_page.dart index 5814dce9e..20c1280ca 100644 --- a/lib/ui/account/password_reentry_page.dart +++ b/lib/ui/account/password_reentry_page.dart @@ -42,7 +42,7 @@ class _PasswordReentryPageState extends State { _passwordController.text = _volatilePassword!; Future.delayed( Duration.zero, - () => verifyPassword(_volatilePassword!), + () => verifyPassword(_volatilePassword!), ); } _passwordFocusNode.addListener(() { @@ -100,69 +100,68 @@ class _PasswordReentryPageState extends State { } Future verifyPassword(String password) async { - FocusScope.of(context).unfocus(); - final dialog = - createProgressDialog(context, S.of(context).pleaseWait); - await dialog.show(); - try { - final kek = await Configuration.instance.decryptSecretsAndGetKeyEncKey( - password, - Configuration.instance.getKeyAttributes()!, - ); - _registerSRPForExistingUsers(kek).ignore(); - } on KeyDerivationError catch (e, s) { - _logger.severe("Password verification failed", e, s); - await dialog.hide(); - final dialogChoice = await showChoiceDialog( - context, - title: S.of(context).recreatePasswordTitle, - body: S.of(context).recreatePasswordBody, - firstButtonLabel: S.of(context).useRecoveryKey, - ); - if (dialogChoice!.action == ButtonAction.first) { - // ignore: unawaited_futures - Navigator.of(context).push( - MaterialPageRoute( - builder: (BuildContext context) { - return const RecoveryPage(); - }, - ), - ); - } - return; - } catch (e, s) { - _logger.severe("Password verification failed", e, s); - await dialog.hide(); - final dialogChoice = await showChoiceDialog( - context, - title: S.of(context).incorrectPasswordTitle, - body: S.of(context).pleaseTryAgain, - firstButtonLabel: S.of(context).contactSupport, - secondButtonLabel: S.of(context).ok, - ); - if (dialogChoice!.action == ButtonAction.first) { - await sendLogs( - context, - S.of(context).contactSupport, - "support@ente.io", - postShare: () {}, - ); - } - return; - } + FocusScope.of(context).unfocus(); + final dialog = createProgressDialog(context, S.of(context).pleaseWait); + await dialog.show(); + try { + final kek = await Configuration.instance.decryptSecretsAndGetKeyEncKey( + password, + Configuration.instance.getKeyAttributes()!, + ); + _registerSRPForExistingUsers(kek).ignore(); + } on KeyDerivationError catch (e, s) { + _logger.severe("Password verification failed", e, s); await dialog.hide(); - Configuration.instance.setVolatilePassword(null); - Bus.instance.fire(SubscriptionPurchasedEvent()); - unawaited( - Navigator.of(context).pushAndRemoveUntil( + final dialogChoice = await showChoiceDialog( + context, + title: S.of(context).recreatePasswordTitle, + body: S.of(context).recreatePasswordBody, + firstButtonLabel: S.of(context).useRecoveryKey, + ); + if (dialogChoice!.action == ButtonAction.first) { + // ignore: unawaited_futures + Navigator.of(context).push( MaterialPageRoute( builder: (BuildContext context) { - return const HomeWidget(); + return const RecoveryPage(); }, ), - (route) => false, - ), + ); + } + return; + } catch (e, s) { + _logger.severe("Password verification failed", e, s); + await dialog.hide(); + final dialogChoice = await showChoiceDialog( + context, + title: S.of(context).incorrectPasswordTitle, + body: S.of(context).pleaseTryAgain, + firstButtonLabel: S.of(context).contactSupport, + secondButtonLabel: S.of(context).ok, ); + if (dialogChoice!.action == ButtonAction.first) { + await sendLogs( + context, + S.of(context).contactSupport, + "support@ente.io", + postShare: () {}, + ); + } + return; + } + await dialog.hide(); + Configuration.instance.setVolatilePassword(null); + Bus.instance.fire(SubscriptionPurchasedEvent()); + unawaited( + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) { + return const HomeWidget(); + }, + ), + (route) => false, + ), + ); } Future _registerSRPForExistingUsers(Uint8List key) async { @@ -266,8 +265,8 @@ class _PasswordReentryPageState extends State { ), Padding( padding: const EdgeInsets.symmetric(horizontal: 20), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Wrap( + alignment: WrapAlignment.spaceBetween, children: [ GestureDetector( behavior: HitTestBehavior.opaque, @@ -280,17 +279,13 @@ class _PasswordReentryPageState extends State { ), ); }, - child: Center( - child: Text( - S.of(context).forgotPassword, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), + child: Text( + S.of(context).forgotPassword, + style: + Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), ), ), GestureDetector( @@ -306,17 +301,13 @@ class _PasswordReentryPageState extends State { Navigator.of(context) .popUntil((route) => route.isFirst); }, - child: Center( - child: Text( - S.of(context).changeEmail, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - fontSize: 14, - decoration: TextDecoration.underline, - ), - ), + child: Text( + S.of(context).changeEmail, + style: + Theme.of(context).textTheme.titleMedium!.copyWith( + fontSize: 14, + decoration: TextDecoration.underline, + ), ), ), ], diff --git a/lib/ui/common/dynamic_fab.dart b/lib/ui/common/dynamic_fab.dart index 085514cdb..d1a7cf647 100644 --- a/lib/ui/common/dynamic_fab.dart +++ b/lib/ui/common/dynamic_fab.dart @@ -60,7 +60,6 @@ class DynamicFAB extends StatelessWidget { } else { return Container( width: double.infinity, - height: 56, padding: const EdgeInsets.symmetric(horizontal: 20), child: OutlinedButton( onPressed: diff --git a/lib/ui/home/memories/full_screen_memory.dart b/lib/ui/home/memories/full_screen_memory.dart index 303d64915..c8e69b58d 100644 --- a/lib/ui/home/memories/full_screen_memory.dart +++ b/lib/ui/home/memories/full_screen_memory.dart @@ -153,6 +153,7 @@ class _FullScreenMemoryState extends State { final inheritedData = FullScreenMemoryData.of(context)!; final showStepProgressIndicator = inheritedData.memories.length < 60; return Scaffold( + backgroundColor: Colors.black, extendBodyBehindAppBar: true, appBar: AppBar( toolbarHeight: 84, diff --git a/lib/ui/home/memories/memory_cover_widget.dart b/lib/ui/home/memories/memory_cover_widget.dart index d56b1a340..8c96be307 100644 --- a/lib/ui/home/memories/memory_cover_widget.dart +++ b/lib/ui/home/memories/memory_cover_widget.dart @@ -221,6 +221,7 @@ class _MemoryCoverWidgetState extends State { child: ThumbnailWidget( memory.file, shouldShowArchiveStatus: false, + shouldShowSyncStatus: false, key: Key("memories" + memory.file.tag), ), ), diff --git a/lib/ui/payment/payment_web_page.dart b/lib/ui/payment/payment_web_page.dart index e619bc0d8..39f4ea9d0 100644 --- a/lib/ui/payment/payment_web_page.dart +++ b/lib/ui/payment/payment_web_page.dart @@ -49,7 +49,11 @@ class _PaymentWebPageState extends State { @override Widget build(BuildContext context) { - _dialog = createProgressDialog(context, S.of(context).pleaseWait); + _dialog = createProgressDialog( + context, + S.of(context).pleaseWait, + isDismissible: true, + ); if (initPaymentUrl == null) { return const EnteLoadingWidget(); } diff --git a/lib/ui/payment/subscription.dart b/lib/ui/payment/subscription.dart index 0327c3ab5..067a3ae10 100644 --- a/lib/ui/payment/subscription.dart +++ b/lib/ui/payment/subscription.dart @@ -1,5 +1,4 @@ import 'package:flutter/cupertino.dart'; -import 'package:photos/core/configuration.dart'; import 'package:photos/services/feature_flag_service.dart'; import 'package:photos/services/update_service.dart'; import "package:photos/ui/payment/store_subscription_page.dart"; @@ -9,18 +8,9 @@ StatefulWidget getSubscriptionPage({bool isOnBoarding = false}) { if (UpdateService.instance.isIndependentFlavor()) { return StripeSubscriptionPage(isOnboarding: isOnBoarding); } - if (FeatureFlagService.instance.enableStripe() && - _isUserCreatedPostStripeSupport()) { + if (FeatureFlagService.instance.enableStripe()) { return StripeSubscriptionPage(isOnboarding: isOnBoarding); } else { return StoreSubscriptionPage(isOnboarding: isOnBoarding); } } - -// return true if the user was created after we added support for stripe payment -// on frame. We do this check to avoid showing Stripe payment option for earlier -// users who might have paid via playStore. This method should be removed once -// we have better handling for active play/app store subscription & stripe plans. -bool _isUserCreatedPostStripeSupport() { - return Configuration.instance.getUserID()! > 1580559962386460; -} diff --git a/lib/ui/payment/subscription_common_widgets.dart b/lib/ui/payment/subscription_common_widgets.dart index 8c743e2f6..6d3cf6659 100644 --- a/lib/ui/payment/subscription_common_widgets.dart +++ b/lib/ui/payment/subscription_common_widgets.dart @@ -36,13 +36,9 @@ class _SubscriptionHeaderWidgetState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Text( - S.of(context).selectYourPlan, - style: Theme.of(context).textTheme.headlineMedium, - ), - ], + Text( + S.of(context).selectYourPlan, + style: Theme.of(context).textTheme.headlineMedium, ), const SizedBox(height: 10), Text( diff --git a/lib/ui/settings/app_update_dialog.dart b/lib/ui/settings/app_update_dialog.dart index e8bf9e2cc..071bd362f 100644 --- a/lib/ui/settings/app_update_dialog.dart +++ b/lib/ui/settings/app_update_dialog.dart @@ -7,6 +7,8 @@ import 'package:photos/ente_theme_data.dart'; import "package:photos/generated/l10n.dart"; import 'package:photos/services/update_service.dart'; import 'package:photos/theme/ente_theme.dart'; +import "package:photos/ui/components/buttons/button_widget.dart"; +import "package:photos/ui/components/models/button_type.dart"; import 'package:photos/utils/toast_util.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -64,32 +66,28 @@ class _AppUpdateDialogState extends State { children: changelog, ), const Padding(padding: EdgeInsets.all(8)), - SizedBox( - width: double.infinity, - height: 56, - child: OutlinedButton( - style: Theme.of(context).outlinedButtonTheme.style!.copyWith( - textStyle: MaterialStateProperty.resolveWith( - (Set states) { - return enteTextTheme.bodyBold; - }, - ), - ), - onPressed: () async { - Navigator.pop(context); - // ignore: unawaited_futures - showDialog( - context: context, - builder: (BuildContext context) { - return ApkDownloaderDialog(widget.latestVersionInfo); - }, - barrierDismissible: false, - ); - }, - child: Text( - S.of(context).update, - ), - ), + ButtonWidget( + buttonType: ButtonType.primary, + labelText: S.of(context).update, + onTap: () async { + Navigator.pop(context); + // ignore: unawaited_futures + showDialog( + context: context, + builder: (BuildContext context) { + return ApkDownloaderDialog(widget.latestVersionInfo); + }, + barrierDismissible: false, + ); + }, + ), + const SizedBox(height: 6), + ButtonWidget( + buttonType: ButtonType.secondary, + labelText: S.of(context).cancel, + onTap: () async { + Navigator.of(context).pop(); + }, ), const Padding(padding: EdgeInsets.all(8)), Center( diff --git a/lib/ui/settings/machine_learning_settings_page.dart b/lib/ui/settings/machine_learning_settings_page.dart index 6afb3704a..0ad5bce31 100644 --- a/lib/ui/settings/machine_learning_settings_page.dart +++ b/lib/ui/settings/machine_learning_settings_page.dart @@ -6,8 +6,8 @@ import "package:photos/core/event_bus.dart"; import 'package:photos/events/embedding_updated_event.dart'; import "package:photos/generated/l10n.dart"; import "package:photos/services/feature_flag_service.dart"; -import "package:photos/services/semantic_search/frameworks/ml_framework.dart"; -import "package:photos/services/semantic_search/semantic_search_service.dart"; +import 'package:photos/services/machine_learning/semantic_search/frameworks/ml_framework.dart'; +import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart'; import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/common/loading_widget.dart"; import "package:photos/ui/components/buttons/icon_button_widget.dart"; diff --git a/lib/ui/tabs/home_widget.dart b/lib/ui/tabs/home_widget.dart index ca66011da..6ce7d46d1 100644 --- a/lib/ui/tabs/home_widget.dart +++ b/lib/ui/tabs/home_widget.dart @@ -194,9 +194,9 @@ class _HomeWidgetState extends State { }, ); _initDeepLinks(); - UpdateService.instance.shouldUpdate().then((shouldUpdate) { - if (shouldUpdate) { - Future.delayed(Duration.zero, () { + UpdateService.instance.shouldShowUpdateNoification().then((value) { + Future.delayed(Duration.zero, () { + if (value) { showDialog( context: context, builder: (BuildContext context) { @@ -206,9 +206,11 @@ class _HomeWidgetState extends State { }, barrierColor: Colors.black.withOpacity(0.85), ); - }); - } + UpdateService.instance.resetUpdateAvailableShownTime(); + } + }); }); + // For sharing images coming from outside the app _initMediaShareSubscription(); WidgetsBinding.instance.addPostFrameCallback( diff --git a/lib/ui/viewer/file/detail_page.dart b/lib/ui/viewer/file/detail_page.dart index 46db449c5..779243c27 100644 --- a/lib/ui/viewer/file/detail_page.dart +++ b/lib/ui/viewer/file/detail_page.dart @@ -73,6 +73,7 @@ class DetailPage extends StatefulWidget { class _DetailPageState extends State { static const kLoadLimit = 100; final _logger = Logger("DetailPageState"); + bool _shouldDisableScroll = false; List? _files; late PageController _pageController; final _selectedIndexNotifier = ValueNotifier(0); @@ -171,6 +172,14 @@ class _DetailPageState extends State { file, autoPlay: shouldAutoPlay(), tagPrefix: widget.config.tagPrefix, + shouldDisableScroll: (value) { + if (_shouldDisableScroll != value) { + setState(() { + _logger.fine('setState $_shouldDisableScroll to $value'); + _shouldDisableScroll = value; + }); + } + }, playbackCallback: (isPlaying) { Future.delayed(Duration.zero, () { _toggleFullScreen(shouldEnable: isPlaying); @@ -199,7 +208,9 @@ class _DetailPageState extends State { } _preloadEntries(); }, - physics: const FastScrollPhysics(speedFactor: 4.0), + physics: _shouldDisableScroll + ? const NeverScrollableScrollPhysics() + : const FastScrollPhysics(speedFactor: 4.0), controller: _pageController, itemCount: _files!.length, ); diff --git a/lib/ui/viewer/file/file_widget.dart b/lib/ui/viewer/file/file_widget.dart index 754cb1a67..6e6dcc7f1 100644 --- a/lib/ui/viewer/file/file_widget.dart +++ b/lib/ui/viewer/file/file_widget.dart @@ -8,6 +8,7 @@ import "package:photos/ui/viewer/file/zoomable_live_image_new.dart"; class FileWidget extends StatelessWidget { final EnteFile file; final String? tagPrefix; + final Function(bool)? shouldDisableScroll; final Function(bool)? playbackCallback; final BoxDecoration? backgroundDecoration; final bool? autoPlay; @@ -15,6 +16,7 @@ class FileWidget extends StatelessWidget { const FileWidget( this.file, { this.autoPlay, + this.shouldDisableScroll, this.playbackCallback, this.tagPrefix, this.backgroundDecoration, @@ -30,6 +32,7 @@ class FileWidget extends StatelessWidget { file.fileType == FileType.image) { return ZoomableLiveImageNew( file, + shouldDisableScroll: shouldDisableScroll, tagPrefix: tagPrefix, backgroundDecoration: backgroundDecoration, key: key ?? ValueKey(fileKey), diff --git a/lib/ui/viewer/file/zoomable_image.dart b/lib/ui/viewer/file/zoomable_image.dart index 5a1d47b29..db96554fc 100644 --- a/lib/ui/viewer/file/zoomable_image.dart +++ b/lib/ui/viewer/file/zoomable_image.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; import 'package:photo_view/photo_view.dart'; -import "package:photo_view/photo_view_gallery.dart"; import 'package:photos/core/cache/thumbnail_in_memory_cache.dart'; import 'package:photos/core/constants.dart'; import 'package:photos/core/event_bus.dart'; @@ -25,6 +24,7 @@ import 'package:photos/utils/thumbnail_util.dart'; class ZoomableImage extends StatefulWidget { final EnteFile photo; + final Function(bool)? shouldDisableScroll; final String? tagPrefix; final Decoration? backgroundDecoration; final bool shouldCover; @@ -32,6 +32,7 @@ class ZoomableImage extends StatefulWidget { const ZoomableImage( this.photo, { Key? key, + this.shouldDisableScroll, required this.tagPrefix, this.backgroundDecoration, this.shouldCover = false, @@ -51,9 +52,9 @@ class _ZoomableImageState extends State bool _loadedLargeThumbnail = false; bool _loadingFinalImage = false; bool _loadedFinalImage = false; - PhotoViewController _photoViewController = PhotoViewController(); - bool _isZooming = false; ValueChanged? _scaleStateChangedCallback; + bool _isZooming = false; + PhotoViewController _photoViewController = PhotoViewController(); @override void initState() { @@ -61,8 +62,12 @@ class _ZoomableImageState extends State _logger = Logger("ZoomableImage"); _logger.info('initState for ${_photo.generatedID} with tag ${_photo.tag}'); _scaleStateChangedCallback = (value) { + if (widget.shouldDisableScroll != null) { + widget.shouldDisableScroll!(value != PhotoViewScaleState.initial); + } _isZooming = value != PhotoViewScaleState.initial; debugPrint("isZooming = $_isZooming, currentState $value"); + // _logger.info('is reakky zooming $_isZooming with state $value'); }; super.initState(); } @@ -83,41 +88,41 @@ class _ZoomableImageState extends State Widget content; if (_imageProvider != null) { - content = PhotoViewGallery.builder( - gaplessPlayback: true, - scaleStateChangedCallback: _scaleStateChangedCallback, - backgroundDecoration: widget.backgroundDecoration as BoxDecoration?, - builder: (context, index) { - return PhotoViewGalleryPageOptions( - imageProvider: _imageProvider!, - minScale: widget.shouldCover - ? PhotoViewComputedScale.covered - : PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes( - tag: widget.tagPrefix! + _photo.tag, - ), - controller: _photoViewController, - ); - }, - itemCount: 1, + content = PhotoViewGestureDetectorScope( + axis: Axis.vertical, + child: PhotoView( + imageProvider: _imageProvider, + controller: _photoViewController, + scaleStateChangedCallback: _scaleStateChangedCallback, + minScale: widget.shouldCover + ? PhotoViewComputedScale.covered + : PhotoViewComputedScale.contained, + gaplessPlayback: true, + heroAttributes: PhotoViewHeroAttributes( + tag: widget.tagPrefix! + _photo.tag, + ), + backgroundDecoration: widget.backgroundDecoration as BoxDecoration?, + ), ); } else { content = const EnteLoadingWidget(); } - verticalDragCallback(d) => { - if (!_isZooming) - { - if (d.delta.dy > dragSensitivity) - { - {Navigator.of(context).pop()}, - } - else if (d.delta.dy < (dragSensitivity * -1)) + + final GestureDragUpdateCallback? verticalDragCallback = _isZooming + ? null + : (d) => { + if (!_isZooming) { - showDetailsSheet(context, widget.photo), + if (d.delta.dy > dragSensitivity) + { + {Navigator.of(context).pop()}, + } + else if (d.delta.dy < (dragSensitivity * -1)) + { + showDetailsSheet(context, widget.photo), + }, }, - }, - }; - + }; return GestureDetector( onVerticalDragUpdate: verticalDragCallback, child: content, @@ -258,7 +263,9 @@ class _ZoomableImageState extends State required ImageProvider? previewImageProvider, required ImageProvider finalImageProvider, }) async { - final bool shouldFixPosition = previewImageProvider != null && _isZooming; + final bool shouldFixPosition = previewImageProvider != null && + _isZooming && + _photoViewController.scale != null; ImageInfo? finalImageInfo; if (shouldFixPosition) { final prevImageInfo = await getImageInfo(previewImageProvider); diff --git a/lib/ui/viewer/file/zoomable_live_image.dart b/lib/ui/viewer/file/zoomable_live_image.dart index 28564f673..f7e2aa55a 100644 --- a/lib/ui/viewer/file/zoomable_live_image.dart +++ b/lib/ui/viewer/file/zoomable_live_image.dart @@ -16,12 +16,14 @@ import 'package:video_player/video_player.dart'; class ZoomableLiveImage extends StatefulWidget { final EnteFile enteFile; + final Function(bool)? shouldDisableScroll; final String? tagPrefix; final Decoration? backgroundDecoration; const ZoomableLiveImage( this.enteFile, { Key? key, + this.shouldDisableScroll, required this.tagPrefix, this.backgroundDecoration, }) : super(key: key); @@ -43,9 +45,8 @@ class _ZoomableLiveImageState extends State @override void initState() { _enteFile = widget.enteFile; - _logger.info( - 'initState for ${_enteFile.generatedID} with tag ${_enteFile.tag} and name ${_enteFile.displayName}', - ); + _logger.info('initState for ${_enteFile.generatedID} with tag ${_enteFile + .tag} and name ${_enteFile.displayName}'); super.initState(); } @@ -75,6 +76,7 @@ class _ZoomableLiveImageState extends State content = ZoomableImage( _enteFile, tagPrefix: widget.tagPrefix, + shouldDisableScroll: widget.shouldDisableScroll, backgroundDecoration: widget.backgroundDecoration, ); } @@ -136,8 +138,7 @@ class _ZoomableLiveImageState extends State } Future _getLivePhotoVideo() async { - if (_enteFile.isRemoteFile && - !(await isFileCached(_enteFile, liveVideo: true))) { + if (_enteFile.isRemoteFile && !(await isFileCached(_enteFile, liveVideo: true))) { showShortToast(context, S.of(context).downloading); } @@ -205,4 +206,5 @@ class _ZoomableLiveImageState extends State } }); } + } diff --git a/lib/ui/viewer/file/zoomable_live_image_new.dart b/lib/ui/viewer/file/zoomable_live_image_new.dart index 73a7f629b..598ae60e0 100644 --- a/lib/ui/viewer/file/zoomable_live_image_new.dart +++ b/lib/ui/viewer/file/zoomable_live_image_new.dart @@ -17,12 +17,14 @@ import 'package:photos/utils/toast_util.dart'; class ZoomableLiveImageNew extends StatefulWidget { final EnteFile enteFile; + final Function(bool)? shouldDisableScroll; final String? tagPrefix; final Decoration? backgroundDecoration; const ZoomableLiveImageNew( this.enteFile, { Key? key, + this.shouldDisableScroll, required this.tagPrefix, this.backgroundDecoration, }) : super(key: key); @@ -79,6 +81,7 @@ class _ZoomableLiveImageNewState extends State content = ZoomableImage( _enteFile, tagPrefix: widget.tagPrefix, + shouldDisableScroll: widget.shouldDisableScroll, backgroundDecoration: widget.backgroundDecoration, ); } diff --git a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index ab1b15e04..d1e733b49 100644 --- a/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -6,7 +6,6 @@ import "package:flutter/cupertino.dart"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; import 'package:photos/core/configuration.dart'; -import "package:photos/core/constants.dart"; import 'package:photos/core/event_bus.dart'; import "package:photos/core/network/network.dart"; import "package:photos/db/files_db.dart"; @@ -21,6 +20,7 @@ import 'package:photos/models/gallery_type.dart'; import "package:photos/models/metadata/common_keys.dart"; import 'package:photos/models/selected_files.dart'; import 'package:photos/services/collections_service.dart'; +import "package:photos/services/feature_flag_service.dart"; import 'package:photos/services/sync_service.dart'; import 'package:photos/services/update_service.dart'; import 'package:photos/ui/actions/collection/collection_sharing_actions.dart'; @@ -88,6 +88,7 @@ class _GalleryAppBarWidgetState extends State { late CollectionActions collectionActions; final GlobalKey shareButtonKey = GlobalKey(); bool isQuickLink = false; + late bool isInternalUser; late GalleryType galleryType; @override @@ -96,6 +97,7 @@ class _GalleryAppBarWidgetState extends State { _selectedFilesListener = () { setState(() {}); }; + isInternalUser = FeatureFlagService.instance.isInternalUserOrDebugBuild(); collectionActions = CollectionActions(CollectionsService.instance); widget.selectedFiles.addListener(_selectedFilesListener); _userAuthEventSubscription = diff --git a/lib/ui/viewer/location/add_location_sheet.dart b/lib/ui/viewer/location/add_location_sheet.dart index ef6d0a537..cf6629431 100644 --- a/lib/ui/viewer/location/add_location_sheet.dart +++ b/lib/ui/viewer/location/add_location_sheet.dart @@ -22,14 +22,20 @@ import "package:photos/ui/viewer/location/radius_picker_widget.dart"; showAddLocationSheet( BuildContext context, - Location coordinates, -) { + Location coordinates, { + String name = '', + double radius = defaultRadiusValue, +}) { showBarModalBottomSheet( context: context, builder: (context) { return LocationTagStateProvider( centerPoint: coordinates, - const AddLocationSheet(), + AddLocationSheet( + radius: radius, + name: name, + ), + radius: radius, ); }, shape: const RoundedRectangleBorder( @@ -45,7 +51,13 @@ showAddLocationSheet( } class AddLocationSheet extends StatefulWidget { - const AddLocationSheet({super.key}); + final double radius; + final String name; + const AddLocationSheet({ + super.key, + this.radius = defaultRadiusValue, + this.name = '', + }); @override State createState() => _AddLocationSheetState(); @@ -61,17 +73,20 @@ class _AddLocationSheetState extends State { final ValueNotifier _submitNotifer = ValueNotifier(false); final ValueNotifier _cancelNotifier = ValueNotifier(false); - final ValueNotifier _selectedRadiusNotifier = - ValueNotifier(defaultRadiusValue); + late ValueNotifier _selectedRadiusNotifier; final _focusNode = FocusNode(); final _textEditingController = TextEditingController(); - final _isEmptyNotifier = ValueNotifier(true); + late final ValueNotifier _isEmptyNotifier; Widget? _keyboardTopButtons; @override void initState() { + _textEditingController.text = widget.name; + _isEmptyNotifier = ValueNotifier(widget.name.isEmpty); _focusNode.addListener(_focusNodeListener); + _selectedRadiusNotifier = ValueNotifier(widget.radius); _selectedRadiusNotifier.addListener(_selectedRadiusListener); + super.initState(); } @@ -155,11 +170,12 @@ class _AddLocationSheetState extends State { RadiusPickerWidget( _selectedRadiusNotifier, ), - const SizedBox(height: 16), - Text( - S.of(context).locationTagFeatureDescription, - style: textTheme.smallMuted, - ), + if (widget.name.isEmpty) const SizedBox(height: 16), + if (widget.name.isEmpty) + Text( + S.of(context).locationTagFeatureDescription, + style: textTheme.smallMuted, + ), ], ), ), diff --git a/lib/ui/viewer/search/result/search_result_widget.dart b/lib/ui/viewer/search/result/search_result_widget.dart index 2a37cc789..5564af7c9 100644 --- a/lib/ui/viewer/search/result/search_result_widget.dart +++ b/lib/ui/viewer/search/result/search_result_widget.dart @@ -131,6 +131,8 @@ class SearchResultWidget extends StatelessWidget { return "Day"; case ResultType.location: return "Location"; + case ResultType.locationSuggestion: + return "Add Location"; case ResultType.fileType: return "Type"; case ResultType.fileExtension: diff --git a/lib/ui/viewer/search/result/search_section_all_page.dart b/lib/ui/viewer/search/result/search_section_all_page.dart index 65c5ec9ab..59761009a 100644 --- a/lib/ui/viewer/search/result/search_section_all_page.dart +++ b/lib/ui/viewer/search/result/search_section_all_page.dart @@ -3,6 +3,7 @@ import "dart:async"; import "package:flutter/material.dart"; import "package:flutter_animate/flutter_animate.dart"; import "package:photos/events/event.dart"; +import "package:photos/extensions/list.dart"; import "package:photos/models/search/album_search_result.dart"; import "package:photos/models/search/generic_search_result.dart"; import "package:photos/models/search/recent_searches.dart"; @@ -83,8 +84,6 @@ class _SearchSectionAllPageState extends State { builder: (context, snapshot) { if (snapshot.hasData) { final sectionResults = snapshot.data!; - sectionResults - .sort((a, b) => a.name().compareTo(b.name())); return Text(sectionResults.length.toString()) .animate() .fadeIn( @@ -109,7 +108,15 @@ class _SearchSectionAllPageState extends State { future: sectionData, builder: (context, snapshot) { if (snapshot.hasData) { - final sectionResults = snapshot.data!; + List sectionResults = snapshot.data!; + sectionResults.sort((a, b) => a.name().compareTo(b.name())); + if (widget.sectionType == SectionType.location) { + final result = sectionResults.splitMatch( + (e) => e.type() == ResultType.location, + ); + sectionResults = result.matched; + sectionResults.addAll(result.unmatched); + } return ListView.separated( itemBuilder: (context, index) { if (sectionResults.length == index) { diff --git a/lib/ui/viewer/search/result/searchable_item.dart b/lib/ui/viewer/search/result/searchable_item.dart index a8a280296..1124d925e 100644 --- a/lib/ui/viewer/search/result/searchable_item.dart +++ b/lib/ui/viewer/search/result/searchable_item.dart @@ -77,7 +77,10 @@ class SearchableItemWidget extends StatelessWidget { children: [ Text( searchResult.name(), - style: textTheme.body, + style: searchResult.type() == + ResultType.locationSuggestion + ? textTheme.bodyFaint + : textTheme.body, overflow: TextOverflow.ellipsis, ), const SizedBox( diff --git a/lib/ui/viewer/search/search_widget.dart b/lib/ui/viewer/search/search_widget.dart index 7c4b93525..2d9132875 100644 --- a/lib/ui/viewer/search/search_widget.dart +++ b/lib/ui/viewer/search/search_widget.dart @@ -129,7 +129,6 @@ class SearchWidgetState extends State { child: Container( color: colorScheme.backgroundBase, child: Container( - height: 44, color: colorScheme.fillFaint, child: TextFormField( controller: textController, diff --git a/lib/utils/device_info.dart b/lib/utils/device_info.dart index 70aae5d55..f706dbaa3 100644 --- a/lib/utils/device_info.dart +++ b/lib/utils/device_info.dart @@ -42,6 +42,14 @@ Future isLowSpecDevice() async { return false; } +Future isGrapheneOS() async { + if (Platform.isAndroid) { + final androidInfo = await deviceInfoPlugin.androidInfo; + return androidInfo.host.toLowerCase() == "grapheneos"; + } + return false; +} + Future isAndroidSDKVersionLowerThan(int inputSDK) async { if (Platform.isAndroid) { final AndroidDeviceInfo androidInfo = await deviceInfoPlugin.androidInfo; diff --git a/lib/utils/dialog_util.dart b/lib/utils/dialog_util.dart index 80e69ed57..ae4425620 100644 --- a/lib/utils/dialog_util.dart +++ b/lib/utils/dialog_util.dart @@ -2,10 +2,10 @@ import "package:dio/dio.dart"; import "package:flutter/foundation.dart"; import 'package:flutter/material.dart'; import "package:flutter/services.dart"; -import "package:photos/core/constants.dart"; import "package:photos/generated/l10n.dart"; import 'package:photos/models/button_result.dart'; import 'package:photos/models/typedefs.dart'; +import "package:photos/services/feature_flag_service.dart"; import 'package:photos/theme/colors.dart'; import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/progress_dialog.dart'; @@ -91,7 +91,8 @@ String parseErrorForUI( } } // return generic error if the user is not internal and the error is not in debug mode - if (!(isInternalUser && kDebugMode)) { + if (!(FeatureFlagService.instance.isInternalUserOrDebugBuild() && + kDebugMode)) { return genericError; } String errorInfo = ""; diff --git a/lib/utils/file_uploader.dart b/lib/utils/file_uploader.dart index 653b5076b..89a12f147 100644 --- a/lib/utils/file_uploader.dart +++ b/lib/utils/file_uploader.dart @@ -10,6 +10,7 @@ import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:logging/logging.dart'; import 'package:photos/core/configuration.dart'; +import "package:photos/core/constants.dart"; import 'package:photos/core/errors.dart'; import 'package:photos/core/event_bus.dart'; import 'package:photos/core/network/network.dart'; @@ -34,6 +35,7 @@ import "package:photos/services/user_service.dart"; import 'package:photos/utils/crypto_util.dart'; import 'package:photos/utils/file_download_util.dart'; import 'package:photos/utils/file_uploader_util.dart'; +import "package:photos/utils/file_util.dart"; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tuple/tuple.dart'; import "package:uuid/uuid.dart"; @@ -69,6 +71,7 @@ class FileUploader { late ProcessType _processType; late bool _isBackground; late SharedPreferences _prefs; + // _hasInitiatedForceUpload is used to track if user attempted force upload // where files are uploaded directly (without adding them to DB). In such // cases, we don't want to clear the stale upload files. See #removeStaleFiles @@ -307,12 +310,36 @@ class FileUploader { return file.path.contains(kUploadTempPrefix) && file.path.contains(".encrypted"); }); - if (filesToDelete.isEmpty) { - return; + if (filesToDelete.isNotEmpty) { + _logger.info('cleaning up state files ${filesToDelete.length}'); + for (final file in filesToDelete) { + await file.delete(); + } } - _logger.info('cleaning up state files ${filesToDelete.length}'); - for (final file in filesToDelete) { - await file.delete(); + + if (Platform.isAndroid) { + final sharedMediaDir = + Configuration.instance.getSharedMediaDirectory() + "/"; + final sharedFiles = await Directory(sharedMediaDir).list().toList(); + if (sharedFiles.isNotEmpty) { + _logger.info('Shared media directory cleanup ${sharedFiles.length}'); + final int ownerID = Configuration.instance.getUserID()!; + final existingLocalFileIDs = + await FilesDB.instance.getExistingLocalFileIDs(ownerID); + final Set trackedSharedFilePaths = {}; + for (String localID in existingLocalFileIDs) { + if (localID.contains(sharedMediaIdentifier)) { + trackedSharedFilePaths + .add(getSharedMediaPathFromLocalID(localID)); + } + } + for (final file in sharedFiles) { + if (!trackedSharedFilePaths.contains(file.path)) { + _logger.info('Deleting stale shared media file ${file.path}'); + await file.delete(); + } + } + } } } catch (e, s) { _logger.severe("Failed to remove stale files", e, s); @@ -431,7 +458,13 @@ class FileUploader { encryptedFilePath, key: key, ); - final thumbnailData = mediaUploadData.thumbnail; + late final Uint8List? thumbnailData; + if (mediaUploadData.thumbnail == null && + file.fileType == FileType.video) { + thumbnailData = base64Decode(blackThumbnailBase64); + } else { + thumbnailData = mediaUploadData.thumbnail; + } final EncryptionResult encryptedThumbnailData = await CryptoUtil.encryptChaCha( @@ -493,17 +526,21 @@ class FileUploader { CryptoUtil.bin2base64(encryptedFileKeyData.encryptedData!); final keyDecryptionNonce = CryptoUtil.bin2base64(encryptedFileKeyData.nonce!); + final Map pubMetadata = {}; MetadataRequest? pubMetadataRequest; if ((mediaUploadData.height ?? 0) != 0 && (mediaUploadData.width ?? 0) != 0) { - final pubMetadata = { - heightKey: mediaUploadData.height, - widthKey: mediaUploadData.width, - }; - if (mediaUploadData.motionPhotoStartIndex != null) { - pubMetadata[motionVideoIndexKey] = - mediaUploadData.motionPhotoStartIndex; - } + pubMetadata[heightKey] = mediaUploadData.height; + pubMetadata[widthKey] = mediaUploadData.width; + } + if (mediaUploadData.motionPhotoStartIndex != null) { + pubMetadata[motionVideoIndexKey] = + mediaUploadData.motionPhotoStartIndex; + } + if (mediaUploadData.thumbnail == null) { + pubMetadata[noThumbKey] = true; + } + if (pubMetadata.isNotEmpty) { pubMetadataRequest = await getPubMetadataRequest( file, pubMetadata, diff --git a/lib/utils/file_uploader_util.dart b/lib/utils/file_uploader_util.dart index f877747aa..1455ee0e9 100644 --- a/lib/utils/file_uploader_util.dart +++ b/lib/utils/file_uploader_util.dart @@ -208,6 +208,10 @@ Future _getThumbnailForUpload( quality: thumbnailQuality, ); if (thumbnailData == null) { + // allow videos to be uploaded without thumbnails + if (asset.type == AssetType.video) { + return null; + } throw InvalidFileError( "no thumbnail : ${file.fileType} ${file.tag}", InvalidReason.thumbnailMissing, @@ -227,6 +231,10 @@ Future _getThumbnailForUpload( final String errMessage = "thumbErr for ${file.fileType}, ${extension(file.displayName)} ${file.tag}"; _logger.warning(errMessage, e); + // allow videos to be uploaded without thumbnails + if (asset.type == AssetType.video) { + return null; + } throw InvalidFileError(errMessage, InvalidReason.thumbnailMissing); } } diff --git a/pubspec.lock b/pubspec.lock index 9e2e0548b..738451dbf 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + battery_info: + dependency: "direct main" + description: + name: battery_info + sha256: "5d5249c87a600a0a20d6b2f5ffdf90d711bccb1bfd3a58e5a6228f270031c680" + url: "https://pub.dev" + source: hosted + version: "1.1.1" bip39: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 1e93168d7..61e968ea9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,8 +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: 0.8.56+576 - +version: 0.8.62+582 environment: sdk: ">=3.0.0 <4.0.0" @@ -23,6 +22,7 @@ dependencies: animated_list_plus: ^0.4.5 archive: ^3.1.2 background_fetch: ^1.2.1 + battery_info: ^1.1.1 bip39: ^1.0.6 cached_network_image: ^3.0.0 chewie: