From 3a1eb7fb55b3a5fd46e131fb7d089b9b3e965d3b Mon Sep 17 00:00:00 2001 From: Jonas Spekker <93268515+jo-spek@users.noreply.github.com> Date: Wed, 11 Dec 2024 19:06:46 +0100 Subject: [PATCH 1/2] Portuguese translations (#2918) * Portuguese translations Translations into Brazilian Portuguese kindly provided by Daniel Dionisio of FAO. * Copyright date back to original Copyright 2018 instead of 2024 --- ground/src/main/res/values-pt/strings.xml | 183 ++++++++++++++++++---- 1 file changed, 153 insertions(+), 30 deletions(-) diff --git a/ground/src/main/res/values-pt/strings.xml b/ground/src/main/res/values-pt/strings.xml index a07e168203..494e3a2993 100644 --- a/ground/src/main/res/values-pt/strings.xml +++ b/ground/src/main/res/values-pt/strings.xml @@ -23,51 +23,174 @@ --> - Obrigatório - - + Este campo é obrigatório - Impossível obter a localização atual sem permissão + Não é possível mostrar a localização atual sem permissão - Falha ao habilitar a atualização de local + Não foi possível ativar as atualizações de localização - Favor habilitar localização nas configurações do sistema + Por favor, ative a localização nas configurações do sistema - - - Visão do mapa + Visualização do mapa Ocorreu um erro inesperado - Google Play Services instalação falhada - Sair + A instalação do Google Play Services falhou + Terminar sessão + OK + Coletar dados + + + Selecione a área para fazer download + Selecione um mapa base para uso offline + Descarregar + Câmera + Pré-visualização + Configurações + Área descarregada + Remover do dispositivo + + Selecionar área + Nenhuma imagem de mapa descarregada para uso offline + Iniciando sessão + + Build %s Termos de serviço Concordar - Eu aceito os Termos de Serviço - + Concordo Data Hora - Selecionar área de download - Selecionar o mapa de base para uso offline - Download - Prévia - Ajustes - Visualizador offline de Títulos de Base de Mapas - Remover - Area sem nome - - Selecionar área - Nenhum mapa carregado. - Entrar - - Construir %s + + Estado da sincronização de dados Adicionar ponto - Completa + Completar + + Mapa rodoviário + Satélite + Terreno + Próximo + Selecione um inquérito + Uma vez abertos, os inquéritos ficam disponíveis offline e são sincronizados automaticamente quando online. + Concluído + Pular + + Marcar ponto + + Novo local de coleta de dados + Adicionar novo local e enviar dados relacionados + Arraste o mapa até que o ponto central esteja na localização desejada + Localização atual: + Carregando… + Este trabalho não tem mais tarefas para concluir + Este inquérito está mal configurado (não existem termos de partilha de dados disponíveis). Por favor, entre em contato com o organizador do inquérito. + Não é possível coletar dados porque o usuário é apenas VISUALIZADOR + Mapas offline + Não foi feito o download de nenhuma imagem + Ocultar ou mostrar imagens baixadas + Camadas + Mapa base + Progresso do download - %d%% + Baixando imagens de mapas offline + Múltiplas regiões + Permissão negada + Fechar aplicativo + Cancelar + Salvar + Sem imagens para esta área. Selecione uma área com imagens para fazer download para o seu dispositivo. + A área selecionada pode ocupar até %s\u00A0MB de espaço no seu dispositivo + Aumente o zoom e selecione uma área menor para fazer download para o seu dispositivo + + Cancelar + Fazer download desta área? + Capturar + Inquéritos + Ícone exibido quando o inquérito está disponível offline + Remover acesso offline + Ponto sem nome + Área sem nome + %s\u00A0MB no disco + %s\u00A0MB + Áreas descarregadas + Disponível para visualização offline + Ícone do item na lista de áreas offline + Nenhum inquérito disponível. Entre em contato com o organizador para obter acesso. + Anterior + Conecte-se à internet para entrar + Você precisa permitir o acesso à câmera para enviar fotos. + Área incompleta + Inicializando… + . Mova o mapa e toque em “Adicionar ponto” para adicionar pontos ao redor da área desejada. + Fechar + Acordo de compartilhamento de dados + Localização do mapa: + Aviso de fim de sessão + Se você terminar sua sessão:\n \u2022 Quaisquer dados não sincronizados serão descartados.\n \u2022 Você não poderá iniciar sessão se estiver offline\n \u2022 Inquéritos offline não estarão disponíveis até você iniciar sessão novamente\n\nTem certeza de que deseja sair? + + Amplie para começar a coletar dados + Inquérito apenas para leitura + Amplie até um local de coleta de dados para coletar informações + Nenhuma submissão + Ignorar + Outro + + %d submissão + %d submissões + + Nomear esta localização + Um novo local de coleta de dados será criado. + Inquérito atual + Alternar inquérito + Nenhum inquérito selecionado + Desfazer + Licenças + Sobre + Termos de serviço + Ground é um projeto comunitário de código aberto desenvolvido pelo Google e pela FAO no âmbito da “Forest Data Partnership”, com a ajuda do SIG, Ecam e dos contribuintes da comunidade de código aberto. \n\nEle é licenciado sob a Apache License, Version 2.0. \n\nVeja outras licenças + + Falhou + Sincronizado + Sincronizando + Envio pendente + Dados sincronizados\n(mídia pendente) + Dados sincronizados\n(mídia pendente de nova tentativa) + Sincronizando mídia + Estado da sincronização + Se você sair, todos os dados não salvos serão perdidos. + Para continuar, entre em contato com o administrador do sistema para solicitar acesso. + Para continuar, inscreva-se para solicitar acesso. + + + Geral + Enviar fotos + Somente via Wi-Fi + Ajuda + Visitar site + Enviar feedback + Relatar problemas técnicos ou sugerir novos locais de interesse + Ainda não implementado + Precisão: + + *Não há termos para exibir.* + + ## Compartilhamento de dados privados + \nOs dados serão compartilhados apenas com os organizadores do inquérito. + + + ## Compartilhamento de dados públicos + \nOs organizadores do inquérito podem compartilhar e usar os dados publicamente sob a licença *Creative Commons CC0 1.0*: + + Os dados não salvos foram restaurados + Não foi possível conectar, tente novamente mais tarde + Dados da imagem enviada + Coleta de dados concluída! + Seus dados foram salvos e serão sincronizados automaticamente quando você estiver online. + + Permitir localizaçã + Permitir compartilhamento de localização + Se você não permitir que o Ground acesse a localização deste dispositivo, não poderá continuar coletando dados para este local. From 9ef90adb6cc8aade4bd87350fe820fd8c0a23ace Mon Sep 17 00:00:00 2001 From: Gino Miceli <228050+gino-m@users.noreply.github.com> Date: Wed, 11 Dec 2024 21:50:10 -0500 Subject: [PATCH 2/2] Handle Play services failures more gracefully (#2920) * Tweak Exception class * Use GMS exception * Refactor API install error handling * Refactor error handling * Tweak ktdoc * Clean up error handing of Play services install * Tweak ktdoc * Tweak ktdoc --- .../android/ground/system/GoogleApiManager.kt | 56 ++++++++++++------- .../ground/ui/startup/StartupFragment.kt | 4 +- .../ground/ui/startup/StartupViewModel.kt | 2 +- 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/ground/src/main/java/com/google/android/ground/system/GoogleApiManager.kt b/ground/src/main/java/com/google/android/ground/system/GoogleApiManager.kt index 685d871705..b1758dab33 100644 --- a/ground/src/main/java/com/google/android/ground/system/GoogleApiManager.kt +++ b/ground/src/main/java/com/google/android/ground/system/GoogleApiManager.kt @@ -16,13 +16,19 @@ package com.google.android.ground.system import android.content.Context -import com.google.android.gms.common.ConnectionResult +import com.google.android.gms.common.ConnectionResult.SUCCESS import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.common.GooglePlayServicesNotAvailableException import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import javax.inject.Singleton +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlinx.coroutines.delay +import timber.log.Timber private val INSTALL_API_REQUEST_CODE = GoogleApiAvailability::class.java.hashCode() and 0xffff +private const val PLAY_SERVICES_RETRY_DELAY_MS = 2500L @Singleton class GoogleApiManager @@ -38,28 +44,36 @@ constructor( * possible or cancelled. */ suspend fun installGooglePlayServices() { - val status = googleApiAvailability.isGooglePlayServicesAvailable(context) - if (status == ConnectionResult.SUCCESS) return - - val requestCode = INSTALL_API_REQUEST_CODE - startResolution(status, requestCode, GooglePlayServicesMissingException()) - getNextResult(requestCode) - } - - private fun startResolution(status: Int, requestCode: Int, throwable: Throwable) { - if (!googleApiAvailability.isUserResolvableError(status)) throw throwable - - activityStreams.withActivity { - googleApiAvailability.showErrorDialogFragment(it, status, requestCode) { throw throwable } + val status = isGooglePlayServicesAvailable() + if (status == SUCCESS) return + if (googleApiAvailability.isUserResolvableError(status)) { + showErrorDialog(status, INSTALL_API_REQUEST_CODE) + } else { + throw GooglePlayServicesNotAvailableException(status) } - } - - private suspend fun getNextResult(requestCode: Int) { - val result = activityStreams.getNextActivityResult(requestCode) - if (!result.isOk()) { - error("Activity result failed: requestCode = $requestCode, result = $result") + // onActivityResult() is sometimes called with a failure prematurely or not at all. Instead, we + // poll for Play services. + while (isGooglePlayServicesAvailable() != SUCCESS) { + Timber.d("Waiting for Play services") + delay(PLAY_SERVICES_RETRY_DELAY_MS) } } - class GooglePlayServicesMissingException : Error("Google play services not available") + private fun isGooglePlayServicesAvailable(): Int = + googleApiAvailability.isGooglePlayServicesAvailable(context) + + /** + * Attempts to resolve the error indicated by the given `status` code, using the provided + * `requestCode` to differentiate Activity callbacks from others. Suspends until the dialog is + * dismissed. + */ + private suspend fun showErrorDialog(status: Int, requestCode: Int) = + suspendCoroutine { continuation -> + activityStreams.withActivity { activity -> + val dialog = googleApiAvailability.getErrorDialog(activity, status, requestCode) + dialog?.setCanceledOnTouchOutside(false) + dialog?.setOnDismissListener { continuation.resume(Unit) } + dialog?.show() + } + } } diff --git a/ground/src/main/java/com/google/android/ground/ui/startup/StartupFragment.kt b/ground/src/main/java/com/google/android/ground/ui/startup/StartupFragment.kt index 648118f537..b21ebe0954 100644 --- a/ground/src/main/java/com/google/android/ground/ui/startup/StartupFragment.kt +++ b/ground/src/main/java/com/google/android/ground/ui/startup/StartupFragment.kt @@ -20,8 +20,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.lifecycleScope +import com.google.android.gms.common.GooglePlayServicesNotAvailableException import com.google.android.ground.R -import com.google.android.ground.system.GoogleApiManager import com.google.android.ground.ui.common.AbstractFragment import com.google.android.ground.ui.common.EphemeralPopups import dagger.hilt.android.AndroidEntryPoint @@ -66,7 +66,7 @@ class StartupFragment : AbstractFragment() { private fun onInitFailed(t: Throwable) { Timber.e(t, "Failed to launch app") - if (t is GoogleApiManager.GooglePlayServicesMissingException) { + if (t is GooglePlayServicesNotAvailableException) { popups.ErrorPopup().show(R.string.google_api_install_failed) } requireActivity().finish() diff --git a/ground/src/main/java/com/google/android/ground/ui/startup/StartupViewModel.kt b/ground/src/main/java/com/google/android/ground/ui/startup/StartupViewModel.kt index cff41bdee6..73ec0cef28 100644 --- a/ground/src/main/java/com/google/android/ground/ui/startup/StartupViewModel.kt +++ b/ground/src/main/java/com/google/android/ground/ui/startup/StartupViewModel.kt @@ -27,7 +27,7 @@ internal constructor( private val userRepository: UserRepository, ) : AbstractViewModel() { - /** Checks & installs Google Play Services and initializes the login flow. */ + /** Initializes the login flow, installing Google Play Services if necessary. */ suspend fun initializeLogin() { googleApiManager.installGooglePlayServices() userRepository.init()