Todo lo que necesitas saber sobre tecnología

Etiqueta: aplicaciones de android Página 2 de 3

El arsenal de Android: enlaces profundos

En una aplicación de Android podemos definir patrones de URI que asignan URI a actividades utilizando filtros de intención. Para obtener más información sobre esto, debe leer esta documentación proporcionada por Google https://developer.android.com/training/app-links Una vez que hayamos definido qué URI maneja nuestra aplicación en nuestro manifiesto, podemos inspeccionar el URI de Intenciones del enlace entrante y decida cómo actuar en consecuencia. La API Deep Linking se encarga de manejar los enlaces entrantes asignando patrones de URI a los de Command. Estos comandos se pueden usar para iniciar una actividad, mostrar un fragmento, mostrar alguna otra interfaz de usuario o cualquier otra cosa que pueda hacer en el contexto de la actividad que maneja sus enlaces profundos. El enfoque está inspirado en el patrón Front Controller de Martin Fowler. Agregar dependencias Paso 1. Agregue el repositorio JitPack a su archivo de compilación allprojects { repositorios { … maven { url ‘https://jitpack.io’ } } } Paso 2. Agregue las dependencias { implementación ‘com.github.justeattakeaway :android-deep-links:1.0.0’ } Guía de uso Primero debemos designar una actividad que manejará los enlaces profundos entrantes y agregar los filtros de intención necesarios para la actividad en nuestro AndroidManifest.xml de la siguiente manera.





Con nuestro filtro de intención definido, podemos definir nuestro enrutamiento; para el enfoque más simple, usamos la función de extensión deepLinkRouter para realizar la mayor parte de la configuración por nosotros. clase EjemploDeepLinkActivity: ComponentActivity() { anular diversión onCreate(savedInstanceState: ¿Paquete?) { super.onCreate(savedInstanceState) deepLinkRouter { esquemas(«https») hosts(«simple.site.com») «/home» mapTo { HomeCommand() } «/productos/[a-zA-Z0-9]*» mapTo { ProductCommand() } }.route(intent.data ?: Uri.EMPTY) } } En el ejemplo asignamos rutas /home a un HomeCommand y también a /product/[a-zA-Z0-9]* a un ProductCommand respectivamente. La parte de la ruta es lo que sigue a la parte del host del URI, como https://simple.site.com/products/123, y en nuestro mapeo la definimos como Regex. Para ProductCommand lo asignamos a una expresión regular que coincide con un ID de producto del patrón. [a-zA-Z0-9]* Comandos Con nuestro mapeo definido y asignado a comandos, necesitamos hacer que nuestros comandos hagan algo. Normalmente, un comando simplemente iniciará una actividad, pero puede hacer más (más sobre esto más adelante). El siguiente comando es para el patrón de ruta /home. clase HomeCommand: Comando() { anular diversión ejecutar() = navegar { contexto -> contexto.startActivity(Intent(context, HomeActivity::class.java)) } } Al observar el ejemplo de HomeCommand, observe el bloque navegar { .. }. Los comandos deben terminar con un bloque de navegación {} y el bloque debe definir lo que debe suceder una vez que se complete el comando. La razón de esto es que los comandos pueden funcionar con corrutinas (más sobre esto más adelante) y, a veces, un comando puede tardar más en completarse y también pasar por cambios de configuración de Android. El bloque de navegación {} se llamará en un momento en el que sea seguro hacerlo en el ciclo de vida de la interfaz de usuario de Android y se le dará el contexto actual para que pueda realizar de forma segura cosas como la navegación por intención. Aparte de la larga explicación para el bloque de navegación, un comando es en su mayoría simple y todo lo que hace es redirigir a una actividad, en este caso HomeActivity. El siguiente ejemplo es el ProductCommand que está asignado al patrón /products/[a-zA-Z0-9]* clase ProductCommand: Command() { valor privado productId por pathSegment(1) anular diversión ejecutar() = navegar { contexto -> contexto.startActivity( Intent(context, ProductActivity::class.java) .putExtra(«productId», productId ) ) } } Este comando extrae un segmento de ruta de la posición 1 en el URI, que es la parte que coincidió [a-zA-Z0-9]*dandonos el ID del producto. Logramos esto utilizando el conveniente delegado de propiedad pathSegment(index). Al igual que con pathSegment(index), también podemos usar queryParam(name) para obtener los parámetros de consulta del URI; si eso no es suficiente, puede acceder a un uri de propiedad que le proporcionará android.net.Uri. ProductCommand concluye navegando { } construyendo un Intent para ProductActivity pasando el ID del producto extraído del Uri como un intent adicional. Probando sus enlaces profundos Para probar los enlaces, puede usar un comando de shell ADB para iniciar su aplicación y darle un enlace; el siguiente ejemplo muestra cómo iniciar con un enlace que se asigna a HomeCommand. adb shell am start -W -a android.intent.action.VIEW -d «https://simple.site.com/home» com.jet.android.links En el comando especificamos qué aplicación iniciar usando el nombre del paquete com.jet.android.links. Puede leer más sobre esto en los documentos oficiales para desarrolladores de Android https://developer.android.com/training/app-links/deep-linking#testing-filters De manera similar, para asignar a ProductCommand, podemos usar el patrón URI con la identificación del producto. como sigue. adb shell am start -W -a android.intent.action.VIEW -d «https://simple.site.com/products/abcd1234» com.jet.android.links Requisitos del comando Interceptar un enlace profundo y entregarlo en un El comando es útil, podemos inspeccionar el enlace profundo y enrutarlo a la aplicación a una actividad u otra. A veces, sin embargo, es posible que necesitemos más información del usuario que realiza un vínculo profundo a la aplicación o podemos exigirle que cumpla con un estado particular, como estar autenticado o estar ubicado geográficamente. Para manejar estas situaciones podemos usar Requisitos de comando, una manera elegante de suspender un comando hasta que se cumplan los requisitos. El siguiente ejemplo muestra cómo lograr esto utilizando las funciones require() y satisfacción(Any) de la API de enlace. clase OrderDetailsCommand: Command() { valor privado orderId por pathSegment(1) var privado loginResult: LoginResult? = nulo anular diversión ejecutar() { lanzar { loginResult = require() } navegar { contexto -> contexto.startActivity( Intent(context, OrderDetailsActivity::class.java) .putExtra(«orderId», orderId) .putExtra(«loginName «, loginResult!!.name) ) } } } En el comando anterior, cuando lleguemos a la línea loginResult = require() nuestro comando se suspenderá y esperará el valor de require(). Para que el comando continúe, debemos indicarle al enrutador/controlador que satisfaga (cualquiera) el requisito. El siguiente ejemplo muestra una configuración de enrutador de enlace profundo que asigna un enlace profundo entrante con el patrón de ruta /orders/[a-zA-Z0-9]* al comando OrderDertails. Esto coincidirá con un enlace profundo como https://requirements.site.com/orders/abcd1234 clase EjemploDeepLinkActivity: ComponentActivity() { enrutador de valor privado por lazy { deepLinkRouter { esquemas(«https») hosts(«requirements.site. com») «/home» mapTo { HomeCommand() } «/pedidos/[a-zA-Z0-9]*» mapTo { OrderDetailsCommand() } } } val privado loginForResult = RegisterForActivityResult(StartActivityForResult()) { val loginName = it.data!!.getStringExtra(«loginName»)!! router.satisfy(LoginResult(nombre = loginName)) } anular diversión onCreate(savedInstanceState: ¿Paquete?) { super.onCreate(savedInstanceState) router.onRequirement(this) { if (it == LoginResult::class.java) { startLoginActivity() } } router.route(intent.data?: Uri.EMPTY) } private fun startLoginActivity() { loginForResult.launch(Intent(this, LoginActivity::class.java)) } } Usamos la API de resultados de actividad para iniciar una nueva actividad LoginActivity, el usuario ingresa su nombre y regresa con un botón Iniciar sesión. Cuando el usuario regresa a EjemploDeepLinkActivity, extraemos el argumento loginName del resultado Intent (el nombre que ingresaron en el campo de nombre en la pantalla de inicio de sesión) y luego llamamos a router.satisfy(LoginResult(name = loginName)) pasando en el nombre de inicio de sesión. Para iniciar LoginActivity necesitamos decirle al enrutador qué hacer cuando un comando encuentra un requisito router.onRequirement(this) { if (it == LoginResult::class.java) { startLoginActivity() } } Logramos esto llamando a onRequirement y si hemos probado el requisito de LoginResult y si es verdadero, iniciamos la actividad de inicio de sesión usando startLoginActivity() que simplemente inicia LoginActivity. Finalización del comando Cuando se completa un comando, el comportamiento predeterminado al usar la función de extensión deepLinkRouter para configurar un enrutador llamará a la función de navegación (Contexto) del comando y luego llamará a terminar() en la actividad. Si desea hacer algo diferente, puede proporcionar su propia devolución de llamada de finalización de comando. router.onCommandComplete(this) { cuando (it) { es DeepLinkRouter.Result.Complete -> { // TODO hacer algo antes de navegarlo.navigate(this) // TODO hacer algo después de navegar finalizar() } es DeepLinkRouter.Result. Cancelado -> { // TODO maneja la cancelación del comando } } } Mirando el ejemplo, llamamos a onCommpandComplete(LifecycleOwner, (DeepLinkRouter.Result) -> Unit) con una devolución de llamada que puede manejar el resultado y luego debe llamarlo.navigate(this) para ejecutar los comandos de la función de navegación manualmente. Luego puedes finalizar() la actividad (el patrón habitual) o hacer otra cosa. Además de manejar la finalización del comando, también podemos definir qué sucede cuando se cancela el comando; esto ocurrirá si se cancela la rutina del comando Job. Referencias LICENCIA Copyright 2022 Just Eat Takeaway Licenciado bajo la Licencia Apache, Versión 2.0 (la «Licencia»); no puede utilizar este archivo excepto de conformidad con la Licencia. Puede obtener una copia de la Licencia en http://www.apache.org/licenses/LICENSE-2.0 A menos que lo exija la ley aplicable o se acuerde por escrito, el software distribuido bajo la Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ya sean expresas o implícitas. Consulte la Licencia para conocer el idioma específico que rige los permisos y limitaciones de la Licencia.

Source link

El arsenal de Android: registro

Esta es una biblioteca que permite que módulos o bibliotecas Java/Kotlin puros que utilizan SLF4J como marco de registro se establezcan como puente en la plataforma Google Android en combinación con la utilidad de registro Timber de Jake Wharton. Está basado en el proyecto patrickfav/slf4j-timber con una versión moderna que utiliza Kotlin y la última versión de SLF4J (actualmente v2.0.6). Atención de uso: desde la versión 0.0.3 el artefacto se migra de JitPack a Maven Central. Agregue el artefacto slf4j-timber de este repositorio como una dependencia en el módulo de su aplicación: dependencias { implementación(«com.jakewharton.timber:timber:5.0.1») implementación(«io.github.unveloper:slf4j-timber:0.0. 8») } En los módulos Java/Kotlin puros debería estar presente el artefacto SLF4J: dependencias { implementation(«org.slf4j:slf4j-api:2.0.6») } Y eso es básicamente todo. SLF4J buscará automáticamente implementaciones de ILoggerFactory en el classpath (así que no agregue este paralelo a org.slf4j:slf4j-android). Consulte el código fuente para ver una aplicación de ejemplo. Uso de artefacto obsoleto De v0.0.1 a v0.0.3 Siga las pautas de jitpack.io para agregar el repositorio JitPack a su archivo de compilación si no lo ha hecho. Normalmente, esto significa una edición de su archivo settings.gradle.kts para agregar una nueva definición de repositorio en el bloque de repositorios, como esto: dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven(«https ://jitpack.io») } } Luego agregue el antiguo artefacto slf4j-timber (com.github.unveloper:slf4j-timber) de este repositorio como una dependencia en el módulo de su aplicación: dependencias { implementación(«com.jakewharton.timber :timber:5.0.1») implementación(«com.github.unveloper:slf4j-timber:0.0.3») } Dependencias por versión slf4j-timber Timber SLF4J 0.0.8 5.0.1 2.0.6 0.0.7 5.0.1 2.0.5 0.0.6 5.0.1 2.0.4 0.0.5 5.0.1 2.0.3 0.0.4 5.0.1 2.0.2 0.0.3 5.0.1 2.0.1 0.0.2 5.0.1 2.0.0 0.0. 1 5.0.1 2.0.0 Descripción Mapeo de nivel de registro Las prioridades se convertirán al nivel de prioridad de LogCat y se pasarán a Timber.log(…);. Los Log.isLoggable() no se respetan aquí, ya que Timber debería ser responsable de decidir cuándo registrar qué. La siguiente tabla muestra la asignación de los niveles de registro SLF4J a los niveles de registro LogCat. SLF4J Android/Timber TRACE VERBOSE DEPURACIÓN INFORMACIÓN DE DEPURACIÓN INFORMACIÓN ADVERTENCIA ADVERTENCIA ERROR ERROR Asignación de nombres de registrador Las instancias de registrador creadas usando LoggerFactory se nombran según el nombre dado como parámetro o el nombre de clase completo de la clase dada como parámetro. No se producirá ningún truncamiento ya que Timber se encarga de esto por sí mismo. Limitaciones La implementación del enlace Android-Timber actualmente no admite marcadores. Todos los métodos de registro que tienen un parámetro Marcador simplemente delegan al método correspondiente sin un parámetro Marcador, es decir, el parámetro Marcador se ignora silenciosamente. Licencia Copyright (C) 2015-2022 unveloper Copyright (C) 2005-2012 El proyecto de código abierto de Android con licencia Apache, versión 2.0 (la «Licencia»); no puede utilizar este archivo excepto de conformidad con la Licencia. Puede obtener una copia de la Licencia en http://www.apache.org/licenses/LICENSE-2.0 A menos que lo exija la ley aplicable o se acuerde por escrito, el software distribuido bajo la Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ya sean expresas o implícitas. Consulte la Licencia para conocer el idioma específico que rige los permisos y limitaciones de la Licencia.

Source link

El arsenal de Android – Ubicación

Antes de comenzar: Requisitos básicos para la barra de búsqueda: antes de comenzar a usar el SDK de Places para Android, necesita un proyecto con una cuenta de facturación y la API de Places habilitada. Recomendamos crear varios propietarios de proyecto y administradores de facturación, para que siempre tengas a alguien con estos roles disponible para tu equipo. Para obtener más información, consulta Configurar en Cloud Console. *Buscar cualquier ubicación usando la biblioteca de Google Places *Elija cualquier ubicación del mapa *Editar ubicación y agregar más detalles Configurar proyecto Agregue esto a su proyecto build.gradle allprojects { repositories { maven { url «https://jitpack.io» } } text { appcompat = ‘1.3.0’ material = ‘1.3.0’ google_places = ‘2.5.0’ } } Agregue esto a su proyecto build.gradle Dependencias dependencias { implementación ‘com.github.appsfeature:location-picker:2.6’ } Dependencias de bibliotecas de soporte necesarias { implementación «androidx.appcompat:appcompat:$rootProject.ext.appcompat» implementación «com.google.android.material:material:$rootProject.ext.material» } Métodos de uso clase pública MainActivity extiende AppCompatActivity implementa LocationPickerCallback { TextView tvStatus privado; @Override protected void onCreate(Bundle saveInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tvStatus = findViewById(R.id.tv_status); } public void onPick(Ver vista) { LocationPicker.getInstance() .setLocationCallback(this) .open(this, CountryCode.India); } @Override public void onLocationSelected(Detalle de LocationPickerDetail) { tvStatus.setText(detail.getLatLong()); } @Override public void onCanceled(Exception e) { } } public class AppApplication extiende la aplicación { instancia privada estática de AppApplication; aplicación estática pública getInstance() { instancia de retorno; } @Override public void onCreate() { super.onCreate(); instancia = esto; LocationPicker.getInstance() .setProperty(LocationProperties.Builder() .setEnableSearchBar(true) .setEnableAddressLine1(true) .setEnableAddressLine2(true) .setEnableCityDetails(true) .setEnableTranslucentStatus(true) .setHintAddressLine1(«Detalle de la tienda») .setApiKey(getString (R.string.google_api_key))); } } Es necesario agregar el archivo google-services.json en el directorio raíz de la aplicación.


Personalizar recursos XML
Continuar
Mostrar selector de ubicación
Ingresar N° Piso / Piso / Punto de Referencia
Línea de dirección 2
Detalle de la ciudad


12p
16sp

Source link

El arsenal de Android: historias de usuarios

Você pode ler em português (https://github.com/welbert6/MaterialStoryView/edit/master/ReadmePT.md) Capturas de pantalla Introducción MaterialStoryView es una biblioteca de Android para implementar un carrusel de historias, similar a las que se encuentran en redes sociales como Instagram y Facebook. . Esta biblioteca facilita la adición de un componente de visualización de historias a su aplicación de Android, lo que permite una integración rápida y sencilla. ✅ Guarda la historia como se ve en SharedPreferences: garantiza que los usuarios no vean repetidamente la misma historia, lo que mejora la experiencia del usuario. ✅ Botón de acción en la historia: permite interacciones directas, como visitar un enlace o realizar una acción específica, directamente desde la historia. ✅ Navegación entre historias tocando en el lado derecho e izquierdo de la pantalla: Facilita la navegación por las historias, permitiendo a los usuarios avanzar o retroceder con un simple toque. ✅ Admite RTL y LTR: se adapta a usuarios de idiomas que se leen de derecha a izquierda (RTL) y de izquierda a derecha (LTR), lo que garantiza una experiencia de usuario global e inclusiva. ✅ Admite historias con y sin títulos: ofrece flexibilidad para mostrar historias tanto con títulos para contexto como sin títulos para un diseño más limpio. ✅ Personalización 100%: permite una personalización completa de la apariencia de la historia, incluida la duración, el color del indicador, el tamaño del radio de la imagen, el color del texto, etc., para que coincida perfectamente con la identidad visual de su aplicación. Configuración 1. Agregue la dependencia de gradle. Agregue el repositorio JitPack a su build.gradle raíz al final de los repositorios: allprojects { repositories { … maven { url ‘https://jitpack.io’ } } } Agregue la dependencia: dependencies { implementación ‘com.github.welbert6:MaterialStoryView:1.0.0’ } 2. Uso Agregue CarouselStoryView a su diseño XML:
Inicialice y configure CarouselStoryView en su actividad: val carrosselStoryView: CarouselStoryView = findViewById(R.id.carouselStoryView) //Asegúrese de llamar a init With Activity para iniciar lib carrosselStoryView.initWithActivity(this) // Puede agregarStory o addStories si tiene una lista de historias carrosselStoryView.addStory(getCopasaStories()) carrosselStoryView.addStory(getCemigStories()) carrosselStoryView.addStory(getCminStories()) carrosselStoryView.addStories(getModelStorys()) Personalización MaterialStoryView ofrece varias opciones de personalización a través de atributos XML, permitiendo modificaciones de: Texto tamaño y color Colores de los indicadores de historias visitadas y pendientes Radio de la imagen de la historia Ancho del indicador del elemento de la historia Duración de la historia Para obtener más detalles sobre las opciones de personalización, consulte las opciones disponibles en la sección de atributos XML declarables. miniStoryTextSize (Tamaño del texto de la mini historia): establece el tamaño del texto de los subtítulos de la mini historia, lo que permite personalizar la escala del texto para una mejor legibilidad. miniStoryTextColor (Color del texto de la mini historia): especifica el color del texto para los subtítulos de la mini historia, ofreciendo la posibilidad de hacer coincidir el color del texto con el tema de la aplicación. miniStoryTextFont (fuente de texto para mini historias): permite configurar la fuente del texto para mini historias a través de una referencia, lo que permite personalizar la tipografía para alinearla con la identidad visual. miniStoryVisitedIndicatorColor (Color del indicador de minihistoria visitada): define el color del indicador de las historias que ha visto el usuario, lo que ayuda a distinguir entre contenido nuevo y revisado. miniStoryPendingIndicatorColor (Color indicador pendiente de mini historia): Especifica el color del indicador para las historias que aún no han sido vistas, facilitando la identificación de contenido nuevo. miniStoryImageRadius (Radio de imagen de mini historia): determina el radio de las imágenes en mini historias, lo que permite redondear las esquinas de las imágenes para una apariencia más suave. miniStorySpaceBetweenImageAndIndicator (Espacio entre la imagen de la mini historia y el indicador): establece el espacio Créditos/StoriesProgressView: esta biblioteca se utilizó para mostrar el progreso de las historias desarrolladas por Welbert Moreira. No dudes en contribuir con mejoras, correcciones de errores o nuevas funciones. ¡Tu contribución es bienvenida! Licencia Licencia MIT Copyright (c) 2024 Welbert Moreira Por el presente se concede permiso, de forma gratuita, a cualquier persona que obtenga una copia de este software y los archivos de documentación asociados (el «Software») para operar con el Software sin restricciones, incluidas, entre otras, los derechos de usar, copiar, modificar, fusionar, publicar, distribuir, sublicenciar y/o vender copias del Software, y de permitir que las personas a quienes se les proporciona el Software lo hagan, sujeto a las siguientes condiciones: El aviso de derechos de autor anterior y este aviso de permiso se incluirá en todas las copias o partes sustanciales del Software. EL SOFTWARE SE PROPORCIONA «TAL CUAL», SIN GARANTÍA DE NINGÚN TIPO, EXPRESA O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A LAS GARANTÍAS DE COMERCIABILIDAD, IDONEIDAD PARA UN PROPÓSITO PARTICULAR Y NO INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DE DERECHOS DE AUTOR SERÁN RESPONSABLES DE NINGÚN RECLAMO, DAÑO U OTRA RESPONSABILIDAD, YA SEA EN UNA ACCIÓN CONTRACTUAL, AGRAVIO O DE OTRA MANERA, QUE SURJA DE, FUERA DE O EN RELACIÓN CON EL SOFTWARE O EL USO U OTRAS NEGOCIOS EN EL SOFTWARE.

Source link

El arsenal de Android: redes

Biblioteca de Android NetworkStateObserver Una biblioteca que le ayuda a verificar el estado de su red, si está disponible, perdida o no disponible y también verificar la accesibilidad de su red cuando su servidor no funciona o su ISP está conectado pero no tiene suscripción de datos. Demostración: 1. Agregar NetworkStateObserver a su proyecto Incluya jitpack en su archivo raíz settings.gradle. pluginManagement { repositorios { … maven { url ‘https://jitpack.io’ } } } dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositorios { … maven { url ‘https://jitpack.io’ } } } Y agregue su dependencia al archivo build.gradle a nivel de aplicación: dependencias { implementación ‘com.github.RhymezxCode:NetworkStateObserver:1.1.3’ //Implementación de Livedata ‘androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0’ implementación ‘androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0’ implementación ‘androidx.lifecycle:lifecycle-runtime-ktx:2.7.0’ implementación ‘android.arch.lifecycle:extensions:1.1.1’ } Sincronización tu proyecto, y ???? auge???? ha agregado NetworkStateObserver con éxito. ❗ 2. Uso Primero inicialice la clase de constructor: val network = NetworkStateObserver.Builder() .activity(activity = this@NetworkStateObserverExample) .build() Si solo desea verificar la conectividad, antes de realizar una tarea o trabajo(): if (CheckConnectivity.isNetworkAvailable(requireContext())){ showToast( this@NetworkStateObserverExample, «Red restaurada») } Utilice el método de datos en vivo para determinar el estado de su red y reemplace el código en lifecycleScope.launchWhenStarted { ….your codifique aquí } para hacer lo que quiera: network.callNetworkConnection().observe(this) { isConnected -> lifecycleScope.launch(Dispatchers.IO) { if (isConnected) { cuando { Reachability.hasServerConnected( contexto = this@NetworkStateObserverExample, serverUrl = «https://www.your-server-url.com» ) -> lifecycleScope.launch{ showToast( «La URL del servidor funciona») } Reachability.hasInternetConnected( contexto = this@NetworkStateObserverExample ) -> lifecycleScope.launch{ showToast( «Red restaurada» ) } else -> lifecycleScope.launch{ showToast( «La red se perdió o hay problemas con el servidor») } } } else { //verificar si hay conexión perdida lifecycleScope.launch{ showToast( «Sin conexión de red») } } } } } Utilice el método de flujo para determinar el estado de su red y también vuelva a intentarlo cuando se produzca una excepción: lifecycleScope.launch { network.callNetworkConnectionFlow() .observe() .collect { when (it) { NetworkObserver.Status.Available -> { lifecycleScope.launch { cuando { Reachability.hasServerConnectedFlow( contexto = this@NetworkStateObserverExample, serverUrl = «https://www.github.com» ).retryWhen { causa, intento -> si (la causa es IOException && intento < 3) { delay(2000) return@retryWhen true } else { return@retryWhen false } }.buffer().first() -> lifecycleScope.launch { showToast( this@NetworkStateObserverExample, «La URL del servidor funciona») } Reachability.hasInternetConnectedFlow( contexto = this@NetworkStateObserverExample ).retryWhen { causa, intento -> si (la causa es IOException && intento < 3) { delay(2000) return@retryWhen true } else { return@retryWhen false } }.buffer().first() -> lifecycleScope.launch { showToast( this@NetworkStateObserverExample, «Red restaurada») } else -> lifecycleScope.launch { showToast( this@NetworkStateObserverExample, «La red se perdió o hay problemas con el servidor») } } } } NetworkObserver.Status.Unavailable -> { showToast( «¡La red no está disponible!» ) } NetworkObserver.Status.Losing -> { showToast( «¡Estás perdiendo tu red!» ) } NetworkObserver.Status.Lost -> { showToast( «¡La red está perdida!» ) } } } } Puede verificar si su conexión a Internet es estable solo si no tiene una URL de servidor: network.callNetworkConnection().observe(this) { isConnected -> lifecycleScope.launch(Dispatchers.IO) { if (isConnected) { cuando { Reachability.hasInternetConnected( contexto = this@NetworkStateObserverExample ) -> lifecycleScope.launchW{ showToast( «Red restaurada») } else -> lifecycleScope.launch{ showToast( «La red se pierde o hay problemas con el servidor») } } } else { //comprueba si hay conexión perdida lifecycleScope.launch{ showToast( «Sin conexión de red») } } } } } 3. También puedes inyectar NetworkStateObserver y usarlo en todas partes de tu aplicación con Hilt ???? : Cree un objeto para NetworkStateModule en su paquete di: @Module @InstallIn(ActivityComponent::class) object NetworkStateModule { @Provides fun provideNetworkStateObserver( actividad: Actividad ): NetworkStateObserver { return NetworkStateObserver.Builder() .actividad(actividad = actividad) .build() } } Declara la variable en tu clase como fragmento o actividad, funciona en ambos: @AndroidEntryPoint clase myFragment : Fragment(){ @Inject lateinit var network: NetworkStateObserver private fun callNetworkConnection() { network.callNetworkConnection() .observe(this) { isConnected -> lifecycleScope.launch(Dispatchers.IO) { if (isConnected) { cuando { Reachability.hasInternetConnected( contexto = this@NetworkStateObserverExample ) -> lifecycleScope.launch{ showToast( «Red restaurada») } else -> lifecycleScope.launch{ showToast( «La red se perdió o hay problemas con el servidor») } } } else { //verificar si hay conexión perdida lifecycleScope.launch{ showToast( «No hay conexión de red» ) } } } } } } anular diversión enResume () { super.onResume() callNetworkConnection() } } Agrega el método en onResume() de tu fragmento o actividad para tener una gran experiencia: override fun onResume() { super.onResume() callNetworkConnection() } ???? Por favor, no dudes en regalarme una estrella ????, también me encantan los destellos ✨ ☺️

Source link

El arsenal de Android: notificaciones push/pull

Actualice el estado de la interfaz de usuario de sus aplicaciones de Android e iOS en tiempo de ejecución. Motivación Actualizar el estado de la interfaz de usuario en tiempo de ejecución es una herramienta muy útil para fines de validación y creación rápida de prototipos. También agrega el beneficio de que puede ser utilizado por todo el equipo de pruebas, ya sean desarrolladores, diseñadores, control de calidad, etc. demo.mov ¿Cómo funciona? Android Android Debug Bridge (ABD) se utiliza para enviar una señal de transmisión al aplicación deseada con una carga útil json como extra. adb shell estoy transmitiendo -p [package] -a [action] -mi [extra key] [extra value]

En el lado de la aplicación hay un BroadcastReceiver que escucha estas cargas útiles. Tras una deserialización exitosa, se emitirá un nuevo estado y, en consecuencia, se activará una actualización de la interfaz de usuario. Disponibilidad: todos los simuladores y/o dispositivos físicos (incluso con depuración wifi) conectados. iOS Las herramientas de desarrollo Xcode de Apple proporcionan una herramienta de línea de comandos para interactuar con el simulador de iOS. Esta herramienta le permite simular el proceso de envío de notificaciones automáticas a un dispositivo: xcrun simctl push [UDID] [bundle id] [path to .apns]

En el lado de la aplicación hay un NotificationBroadcaster que monitorea activamente las notificaciones entrantes. Luego, estas notificaciones se transmiten a los observadores internos dentro de la aplicación. Tras una deserialización exitosa, se emitirá un nuevo estado y, en consecuencia, se activará una actualización de la interfaz de usuario. Disponibilidad: todos los simuladores arrancados. Instalación Android Agregue la dependencia de la biblioteca: implementación(«com.github.guilhe:json-broadcast-handler:${LATEST_VERSION}'») Administrador de paquetes Swift Las implementaciones de Swift están disponibles a través del Administrador de paquetes Swift. En Xcode, vaya a Archivo > Agregar paquetes… y proporcione la URL https://github.com/GuilhE/JsonBroadcaster.git; Utilice el hash de confirmación de la última etiqueta JsonBroadcasterHandler-x. CocoaPods Si usa CocoaPods, agregue lo siguiente a su Podfile: pod ‘JsonBroadcasterHandler’, :git => ‘https://github.com/GuilhE/JsonBroadcaster.git’, :tag => ‘JsonBroadcasterHandler-x’ Uso: desarrolladores Android Sus clases UiState deben estar anotadas con kotlinx.serialization.Serializable (dependencia): @Serializable data class UiState(val memberA: String, val memberB: String) Cree una implementación BroadcastUiModelHost para escuchar las actualizaciones de estado, como se muestra a continuación: host val privado = objeto: BroadcastUiModelHost(coroutineScope, UiState.serializer()) { override fun updateState(new: UiState) { //… } } Agréguelo donde mejor se adapte a su proyecto, ejemplos: si está utilizando androidx.lifecycle.ViewModel, puede hacerlo lo siguiente: clase MatchViewModel : ViewModel() { valor privado _uiState = MutableStateFlow(MatchUiState(home = Team(«PRT», «?????????»), away = Team(«BRA», «??? ?????»))) val uiState: StateFlow = _uiState host val privado = objeto: BroadcastUiModelHost(viewModelScope, MatchUiState.serializer()) { override fun updateState(new: MatchUiState) { _uiState.update { new } } } } Pero en realidad no necesitas un ViewModel, simplemente puedes usar un @Composable, por ejemplo: @Composable divertido MatchScreen() { var uiState: MatchUiState por recordar { mutableStateOf(MatchUiState(casa = Equipo(«PRT», «?????????»), lejos = Equipo(«BRA», «????? ???»))) } LaunchedEffect(Unidad) { val host = objeto: BroadcastUiModelHost(this, MatchUiState.serializer()) { override fun updateState(new: MatchUiState) { uiState = new } } } Match(uiState) } Y lo bueno de esto es que puedes elegir lo que más te convenga: ViewModel, @Composable, Actividad, Fragmento, etc. Para deshabilitarlo, por ejemplo en compilaciones de lanzamiento, anule la declaración del receptor en AndroidManifest agregando una propiedad manifestPlaceholders en build.gradle: android { buildTypes { getByName(«release») { manifestPlaceholders[«enableJsonBroadcastReceiver»] = falso } getByName(«depurar») { manifestPlaceholders[«enableJsonBroadcastReceiver»] = verdadero } } }



iOS Sus clases UiState deben implementar el protocolo Codable: struct UiState: Codable { let memberA: String let memberB: String } Cree una instancia BroadcastUIModelHost dentro de una clase para escuchar las actualizaciones de estado, como se muestra a continuación: var privada uiModelHost: BroadcastUIModelHost! init() { uiModelHost = BroadcastUIModelHost(initState) { [weak self] newState en //… } } Agréguelo donde mejor se ajuste a su proyecto, ejemplo: Si está utilizando un ObservableObject, puede hacer lo siguiente: import SwiftUI import JsonBroadcasterHandler class MatchViewModel: ObservableObject { private var uiModelHost: BroadcastUIModelHost! @Estado var publicado: MatchUiState = MatchUiState(local: Equipo(país:»PRT», bandera:»?????????»), visitante: Equipo(país:»BRA», bandera:»???? ????»)) init() { uiModelHost = BroadcastUIModelHost(estado) { [weak self] newState in self?.state = newState } } } Y lo bueno de esto es que puedes elegir lo que más te convenga, SwiftUI o UIKit: struct MatchScreen: View { @StateObject private var viewModel = MatchViewModel() var body: some View { ZStack { } .onReceive(viewModel.$state) { nuevo en //… } } clase MatchScreen: UIViewController { var privada viewModel: MatchViewModel! cancelables var privados = Establecer() anular la función viewDidLoad() { super.viewDidLoad() viewModel = MatchViewModel() viewModel.$state .receive(on: DispatchQueue.main) .sink { [weak self] state in self?.updateUI(with: state) } .store(in: &cancellables) } función privada updateUI(with state: MatchUiState) { //… } } Dentro de su AppDelegate regístrese para RemoteNotifications y reenvíelos con NotificationBroadcaster: importar UIKit importar JsonBroadcasterHandler clase AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate { func aplicación(_ aplicación: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { UNUserNotificationCenter.current().delegate = self application.registerForRemoteNotifications() devuelve verdadero } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notificación: UNNotification, withCompletionHandler completeHandler: @escaping (UNNotificationPresentationOptions) -> Void) { NotificationBroadcaster .broadcast(notification) } } consejo: puede crear indicadores personalizados del compilador, DEBUG_MODE, para encapsular NotificationBroadcaster: func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notificación: UNNotification, withCompletionHandler completeHandler: @escaping (UNNotificationPresentationOptions) -> Void) { #if DEBUG_MODE NotificationBroadcaster.broadcast(notificación) #endif } Uso: equipo de pruebas Android El SDK de Android de Google debe estar instalado para poder utilizar herramientas de línea de comandos; Solicite una versión instalada de la aplicación (depuración wifi o cable conectado); Utilice la GUI de la aplicación de escritorio. Se debe instalar XCode de iOS Apple para poder utilizar herramientas de línea de comandos; Abra XCode y ejecute un simulador con la aplicación; Utilice la GUI de la aplicación de escritorio. Aplicación de escritorio Aunque podemos usar la terminal para enviar comandos, no es práctico. La aplicación de escritorio proporciona una interfaz de usuario sencilla para ayudarnos con esa tarea. Para ejecutarlo, puede: Clonar este proyecto y escribir ./gradlew :desktopApp:run en la terminal. Descargue un .dmg (solo MacOS) e instálelo. Consiguelo aqui. nota: por razones de seguridad, dado que esta aplicación no es de un Desarrollador Identificado, MacOS bloqueará su ejecución. Para evitarlo, deberá hacer clic en «Abrir de todos modos» en Configuración del sistema en Seguridad. Solo es necesario una vez: (Esto no sucederá con el primer enfoque) Patios de juegos Utilice la siguiente carga útil para comenzar: { «home»:{ «country»:»PRT», «flag»:»??????? ?» }, «lejos»:{ «país»:»BRA», «bandera»:»????????? }, «homeGoals»:0, «awayGoals»:0, «started»: false, «running»: false, «finished»: false } Android Dentro del módulo de muestra encontrarás una aplicación de juegos lista para que la pruebes . Para ejecutarlo, puede: Clonar este proyecto y escribir ./gradlew :androidApp:installDebug en la terminal. Descargue el .apk de muestra e instálelo. Consiguelo aqui. El ID de aplicación es com.jsonbroadcaster.matchday iOS. Dentro de la carpeta sample-ios encontrará una aplicación de juegos lista para que la pruebe. Para ejecutarlo: ábralo en Xcode y ejecute la configuración estándar. Importe JsonBroadcaster utilizando el método que prefiera. LICENCIA Copyright (c) 2022-presente GuilhE Licenciado bajo la Licencia Apache, Versión 2.0 (la «Licencia»); no puede utilizar este archivo excepto de conformidad con la Licencia. Puede obtener una copia de la Licencia en http://www.apache.org/licenses/LICENSE-2.0 A menos que lo exija la ley aplicable o se acuerde por escrito, el software distribuido bajo la Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ya sean expresas o implícitas. Consulte la Licencia para conocer el idioma específico que rige los permisos y limitaciones de la Licencia.

Source link

El arsenal de Android – Base de datos

Un marco de almacenamiento de objetos modular para proyectos multiplataforma de Kotlin. Uso ObjectStore proporciona una interfaz de almacenamiento de clave/valor simple que, de forma predeterminada, utiliza detalles de tipo para derivar la clave automáticamente. Para crear un ObjectStore necesita dos cosas: ObjectStoreWriter: proporciona el mecanismo de persistencia para almacenar datos para acceso posterior. ObjectStoreSerializer: proporciona el mecanismo de serialización para transformar objetos para su almacenamiento. val store = ObjectStore( storeWriter = SharedPreferencesStoreWriter(«prefs», context), storeSerializer = JsonStoreSerializer() ) // Almacenar un objeto store.put(User(«username», «email», …)) // Obtener un objeto o usuario de valor nulo: ¿Usuario? = tienda.getOrNull() // Obtener un objeto o lanzar val usuario: Usuario = store.get() // Obtener un objeto o valor predeterminado usuario: Usuario = store.get(default = Usuario(…)) // Obtener un valor StateFlow userFlow: StateFlow = tienda.getFlow() // Se emitirán llamadas para `poner` nuevos objetos de usuario userFlow.collect { println(it) } // Obtener todas las claves store.keys() // Eliminar un objeto store.remove() // Eliminar todos los objetos store.clear() Al almacenar tipos básicos como String, Boolean, etc., debe proporcionar una clave para el registro. store.put(false, clave = «mi_clave») store.get(predeterminado = falso, clave = «my_key») NOTA: Cuando se apunta a Javascript, todas las clases utilizadas con ObjectStore deben estar anotadas con @Serializable. Esto se usa para derivar claves basadas en nombres de parámetros y clases; otras plataformas no usan la biblioteca Kotlinx.serialization en objectstore-core. Serializadores Para convertir objetos en datos adecuados para el almacenamiento se requiere una implementación de ObjectStoreSerializer. Los siguientes módulos proporcionan capacidades de serialización utilizando el módulo Kotlinx.serialization correspondiente. objectstore-cbor: CborStoreSerializer() objectstore-json: JsonStoreSerializer() objectstore-protobuf: Escritores ProtoBufStoreSerializer() El almacenamiento de datos de objetos requiere una implementación de ObjectStoreWriter. Los siguientes escritores se proporcionan en el módulo objectstore-core: Android: SharedPreferencesStoreWriter(«prefs_name», context) iOS/macOS/tvOS/watchOS: UserDefaultsStoreWriter() Navegador JS: LocalStorageStoreWriter() Todos: InMemoryStoreWriter() File Writer The objectstore-fs proporciona almacenamiento basado en archivos usando okio. Todos los destinos son compatibles excepto iosArm32 y jsBrowser. val store = ObjectStore( storeWriter = FileStoreWriter(«/storage-directory») ) La ruta proporcionada no debe existir o ser un directorio existente donde se puedan almacenar archivos. Cada valor se almacenará en un archivo separado utilizando la clave codificada en hexadecimal como nombre de archivo. Escritores seguros Para almacenar datos de forma segura, el módulo objectstore-secure proporciona escritores que cifran los datos cuando se almacenan en el disco. iOS/macOS/tvOS/watchOS: KeychainStoreWritre(«com.service.name», «com.service.group») Android: EncryptedSharedPreferencesStoreWriter(«prefs_name», context) Escritores envueltos ValueTransformingStoreWriter proporciona un enlace para codificar/decodificar valores antes de que se escriben en el disco. Los métodos de transformación se definen como (tipo: KType, valor: T) -> T, cuando no se controlan debe devolver el valor original. val storeWriter = InMemoryStoreWriter().transformValue( transformGet = { _, valor -> (valor como? Cadena)?.base64Decoded() ?: valor }, transformSet = { _, valor -> (valor como? Cadena)?.base64Encoded () ?: valor } ) MemCachedStoreWriter proporciona almacenamiento en caché diferido en memoria alrededor de cualquier implementación de ObjectStoreWriter. val storeWriter = FileStoreWriter(«/data»).memCached() Descargar repositorios { mavenCentral() // O instantáneas maven(«https://s01.oss.sonatype.org/content/repositories/snapshots/») } dependencias { implementación(«org.drewcarlson:objectstore-core:$VERSION») // Serializadores implementación(«org.drewcarlson:objectstore-cbor:$VERSION») implementación(«org.drewcarlson:objectstore-json:$VERSION») implementación( «org.drewcarlson:objectstore-protobuf:$VERSION») // Implementación de escritores(«org.drewcarlson:objectstore-fs:$VERSION») implementación(«org.drewcarlson:objectstore-secure:$VERSION») } Toml (Haga clic expandir) [versions]
almacén de objetos = «1.0.0-INSTANTÁNEA»

[libraries]
objectstore-core = { module = «org.drewcarlson:objectstore-core», version.ref = «objectstore» } objectstore-fs = { module = «org.drewcarlson:objectstore-fs», version.ref = «objectstore» } objectstore-cbor = { module = «org.drewcarlson:objectstore-cbor», version.ref = «objectstore» } objectstore-json = { module = «org.drewcarlson:objectstore-json», version.ref = «objectstore» } objectstore-protobuf = { module = «org.drewcarlson:objectstore-protobuf», version.ref = «objectstore» } objectstore-secure = { module = «org.drewcarlson:objectstore-secure», version.ref = «objectstore» } Licencia Este proyecto tiene la licencia Apache-2.0, que se encuentra en LICENCIA.

Source link

El arsenal de Android: procesamiento de imágenes

Una biblioteca de Android para aplicar 62 (se agregarán más) filtros LUT fáciles, rápidos y efectivos a las fotos. Con RenderScript, el objetivo es procesar los espacios de color en la LUT y aplicarlos a la imagen en forma de filtro. ¿Qué es LUT? La traducción directa de LUT («Tabla de búsqueda») del inglés significa «tabla de búsqueda». En informática, significa datos donde se asignan y calculan los valores de entrada (entrada) y los valores de salida (salida). Instalación inicial Gradle Agregue los siguientes códigos a su archivo raíz build.gradle (no al archivo build.gradle de su módulo). allprojects { repositories { maven { url ‘https://jitpack.io’ } } } Y agregue un código de dependencia al archivo build.gradle de su módulo. dependencias { implementación ‘com.github.zekierciyas:FancyFilter:Tag’ } Uso La biblioteca Fancy Filter, que es rápida y fácil de usar, generalmente incluye un patrón de construcción simple. Se basa en proporcionar los parámetros necesarios y obtener la imagen filtrada en tipo mapa de bits. FancyFilter.Builder() .withContext(this) .filter(FancyFilters.NO_1) .bitmap(bitmap) .applyFilter { bitmap -> // Obteniendo el mapa de bits filtrado aquí } Copyright 2022 github/zekierciyas (Zeki Erciyas) Con licencia Apache , Versión 2.0 (la «Licencia»); no puede utilizar este archivo excepto de conformidad con la Licencia. Puede obtener una copia de la Licencia en http://www.apache.org/licenses/LICENSE-2.0 A menos que lo exija la ley aplicable o se acuerde por escrito, el software distribuido bajo la Licencia se distribuye «TAL CUAL», SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ya sean expresas o implícitas. Consulte la Licencia para conocer el idioma específico que rige los permisos y limitaciones de la Licencia.

Source link

El arsenal de Android – Vistas

¡Una biblioteca de Android ligera con animaciones emergentes! Fácil de usar Tamaño pequeño Altamente personalizable Primeros pasos Para usar esta biblioteca en su proyecto, agregue la siguiente dependencia a su archivo build.gradle: allprojects { repositories { … maven { url ‘https://jitpack.io’ } } } dependencias { implementación ‘com.github.gold24park:PopcornView:release-1.0.2’ } Uso Agregue PopcornView a su archivo XML de diseño:
O configure las propiedades de las palomitas de maíz e inicie la animación mediante programación: val popcornView = findViewById(R.id.popcornView) val forma = PopcornShape.DrawableShape( tamaño = 100F, dibujable = ContextCompat.getDrawable(context, R.drawable.popcorn)!!, ) popcornView.start( forma = forma, cantidad = 40, intervalo = 10, ) gravedad: La aceleración debida a la gravedad en píxeles por segundo al cuadrado. (Predeterminado: 0.37F) minVelocity: La velocidad mínima de las palomitas de maíz en píxeles por segundo. (Predeterminado: 20) maxVelocity: La velocidad máxima de las palomitas de maíz en píxeles por segundo. (Predeterminado: 40) angleRangeStart: el ángulo mínimo de lanzamiento de las palomitas de maíz en grados. (Predeterminado: 45) angleRangeEnd: El ángulo máximo de lanzamiento de las palomitas de maíz en grados. (Predeterminado: 135) elasticidad: La elasticidad de las colisiones entre las palomitas de maíz y las paredes. (Predeterminado: 0.6F) fricción: El coeficiente de fricción de las palomitas de maíz en las paredes. (Predeterminado: 0.39F) ttl: El tiempo de vida de las palomitas de maíz en milisegundos. (Predeterminado: 30,000L) desvanecimiento: si las palomitas de maíz deben desvanecerse antes de desaparecer. (Valor predeterminado: verdadero) Personalizar formas con PopcornShape TextShape TextShape representa una forma que se dibuja como texto. Tiene las siguientes propiedades: val forma = PopcornShape.TextShape( tamaño = 100F, texto = «Pop!», textColor = ContextCompat.getColor(this, R.color.amarillo) ) tamaño (obligatorio): El tamaño del texto a ser dibujado. texto (obligatorio): El texto que se va a dibujar. textColor (opcional): el color del texto que se va a dibujar. (Predeterminado: Color.NEGRO). DrawableShape val shape = PopcornShape.DrawableShape( size = 100F, drawable = ContextCompat.getDrawable(this, R.drawable.popcorn)!!, ) DrawableShape representa una forma que se dibuja como un elemento de diseño. Tiene las siguientes propiedades: tamaño (obligatorio): El tamaño del elemento de diseño que se va a dibujar. dibujable (obligatorio): El dibujable que se va a dibujar. Configuración de la posición para hacer estallar Puede especificar la posición desde donde se generarán las palomitas de maíz en el método start(). PopcornView.start(…startX: Float, startY): genera palomitas de maíz a partir de las coordenadas (startX, startY). Alternativamente, puede usar StartPosition: StartPosition StartPosition( xAxis: XAxisPosition.CENTER, yAxis: YAxisPosition.CENTER,) xAxis: XAxisPosition.LEFT | XAxisPosition.CENTRO | XAxisPosition.RIGHT Eje y: YAxisPosition.TOP | YAxisPosition.CENTRO | YAxisPosition.BOTTOM Contribuir Le invitamos a contribuir a este proyecto. Si encuentra algún error, tiene alguna solicitud o sugerencia de función, o simplemente desea mejorar el código, no dude en abrir un problema o enviar una solicitud de extracción. Se agradecen mucho sus contribuciones. Licencia Este proyecto tiene licencia según los términos de la licencia MIT. Consulte LICENCIA para obtener más información.

Source link

Actúe rápido para obtener un plan de por vida de AdGuard por solo $17

Los anuncios son esenciales para los medios gratuitos; no podríamos mantener las luces encendidas en Android Authority sin ellos. Pero también nos frustramos tanto como usted con una sobrecarga de anuncios. Un buen bloqueador es una solución y AdGuard es uno de los mejores bloqueadores de publicidad del mercado. TechDeals tiene una increíble oferta por tiempo limitado en la herramienta en este momento, lo que le brinda la oportunidad de obtener un plan de por vida con un 71% de descuento. AdGuard: suscripción de por vida por $16,97 ($43 de descuento)AdGuard tiene un nivel gratuito que quizás ya estés usando felizmente, pero que solo funciona en tu navegador web. Este nivel pago le permite agregar la práctica herramienta a hasta tres de sus dispositivos para que pueda eliminar los anuncios en todos los aspectos de su navegación. El acuerdo de por vida significa que estás realizando un pago único de $17 por esta maravillosa existencia en la web. AdGuard hace mucho más que bloquear anuncios; mantiene a raya el malware y sus datos privados lejos de miradas indiscretas, al mismo tiempo que le permite establecer límites seguros de Internet para los niños. Ha recibido críticas positivas en sitios como G2, Capterra y Trustpilot, lo que demuestra que los usuarios realmente valoran lo que ofrece. Además, funciona en una amplia gama de dispositivos, desde teléfonos inteligentes hasta computadoras de escritorio, y abarca Windows 11, iOS y Android. Al deshacerse de los anuncios, también podrá descubrir que su velocidad de navegación aumenta notablemente.

Source link

Página 2 de 3

Funciona con WordPress & Tema de Anders Norén