From cf757078397730916c7b9cbc115a5151bf69d781 Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 19 Oct 2026 14:04:38 +0000 Subject: [PATCH 1/3] [PM-45006] chore: Migrate from glide to coil for async image loading (#7465) --- README.md | 10 +- app/build.gradle.kts | 5 +- app/proguard-rules.pro | 7 - .../x8bit/bitwarden/BitwardenApplication.kt | 11 +- .../ui/credentials/util/ContextExtensions.kt | 63 -------- .../platform/glide/BitwardenAppGlideModule.kt | 75 --------- .../ImageCookieInterceptor.kt} | 10 +- .../ui/platform/image/di/ImageModule.kt | 55 +++++++ .../credentials/util/ContextExtensionsTest.kt | 144 ------------------ .../glide/BitwardenAppGlideModuleTest.kt | 60 -------- .../ImageCookieInterceptorTest.kt} | 6 +- authenticator/build.gradle.kts | 1 - authenticator/proguard-rules.pro | 7 - gradle/libs.versions.toml | 10 +- ui/build.gradle.kts | 3 +- .../platform/components/icon/BitwardenIcon.kt | 18 +-- .../components/image/BitwardenGifImage.kt | 15 +- 17 files changed, 99 insertions(+), 401 deletions(-) delete mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensions.kt delete mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModule.kt rename app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/{glide/GlideCookieInterceptor.kt => image/ImageCookieInterceptor.kt} (83%) create mode 100644 app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/di/ImageModule.kt delete mode 100644 app/src/test/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensionsTest.kt delete mode 100644 app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModuleTest.kt rename app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/{glide/GlideCookieInterceptorTest.kt => image/ImageCookieInterceptorTest.kt} (97%) diff --git a/README.md b/README.md index d488e2a42c..4b531d958b 100644 --- a/README.md +++ b/README.md @@ -168,16 +168,16 @@ The following is a list of all third-party dependencies included as part of the - Purpose: The WorkManager is used to schedule deferrable, asynchronous tasks that must be run reliably. - License: Apache 2.0 +- **Coil** + - https://github.com/coil-kt/coil + - Purpose: Image loading and caching. + - License: Apache 2.0 + - **Dagger Hilt** - https://github.com/google/dagger - Purpose: Dependency injection framework. - License: Apache 2.0 -- **Glide** - - https://github.com/bumptech/glide - - Purpose: Image loading and caching. - - License: BSD, part MIT and Apache 2.0 - - **kotlinx.collections.immutable** - https://github.com/Kotlin/kotlinx.collections.immutable - Purpose: Immutable collection interfaces and implementation prototypes for Kotlin. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9364dbc4b5..682e66af91 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -275,9 +275,8 @@ dependencies { implementation(libs.androidx.splashscreen) implementation(libs.androidx.work.runtime.ktx) implementation(libs.bitwarden.sdk) - implementation(libs.bumptech.glide) - implementation(libs.bumptech.glide.okhttp) - ksp(libs.bumptech.glide.compiler) + implementation(libs.coil.coil) + implementation(libs.coil.coil.network.okhttp) implementation(libs.google.hilt.android) ksp(libs.google.hilt.compiler) implementation(libs.kotlinx.collections.immutable) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index d4963ef3c0..5935936883 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -55,13 +55,6 @@ kotlinx.serialization.KSerializer serializer(...); } -################################################################################ -# Glide -################################################################################ - --keep public class * implements com.bumptech.glide.module.GlideModule --keep public class * extends com.bumptech.glide.module.AppGlideModule - ################################################################################ # JNA ################################################################################ diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/BitwardenApplication.kt b/app/src/main/kotlin/com/x8bit/bitwarden/BitwardenApplication.kt index 6ce37bdf52..6e6bd75728 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/BitwardenApplication.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/BitwardenApplication.kt @@ -1,6 +1,9 @@ package com.x8bit.bitwarden import android.app.Application +import coil3.ImageLoader +import coil3.PlatformContext +import coil3.SingletonImageLoader import com.bitwarden.annotation.OmitFromCoverage import com.bitwarden.data.manager.flightrecorder.FlightRecorderManager import com.x8bit.bitwarden.data.auth.manager.AuthRequestNotificationManager @@ -11,6 +14,7 @@ import com.x8bit.bitwarden.data.platform.manager.event.OrganizationEventManager import com.x8bit.bitwarden.data.platform.manager.network.NetworkConfigManager import com.x8bit.bitwarden.data.platform.manager.restriction.RestrictionManager import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository +import dagger.Lazy import dagger.hilt.android.HiltAndroidApp import timber.log.Timber import javax.inject.Inject @@ -20,7 +24,7 @@ import javax.inject.Inject */ @OmitFromCoverage @HiltAndroidApp -class BitwardenApplication : Application() { +class BitwardenApplication : Application(), SingletonImageLoader.Factory { // Inject classes here that must be triggered on startup but are not otherwise consumed by // other callers. @Inject @@ -50,6 +54,9 @@ class BitwardenApplication : Application() { @Inject lateinit var environmentRepository: EnvironmentRepository + @Inject + lateinit var imageLoader: Lazy + override fun onCreate() { super.onCreate() // These must be initialized in order to ensure that the restrictionManager does not @@ -62,4 +69,6 @@ class BitwardenApplication : Application() { super.onLowMemory() Timber.w("onLowMemory") } + + override fun newImageLoader(context: PlatformContext): ImageLoader = imageLoader.get() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensions.kt deleted file mode 100644 index 304f3c6c38..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensions.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.x8bit.bitwarden.ui.credentials.util - -import android.content.Context -import androidx.annotation.DrawableRes -import androidx.core.graphics.drawable.IconCompat -import com.bitwarden.ui.platform.components.icon.model.IconData -import com.bumptech.glide.Glide -import timber.log.Timber -import java.util.concurrent.CancellationException -import java.util.concurrent.ExecutionException - -/** - * Creates an IconCompat from an IconData, or falls back to a default resource if IconData is null - * or is not of type Network. - * - * @param iconData The IconData to create the IconCompat from. - * @param defaultResourceId The resource ID of the default icon to use if IconData is null or not of - * type Network. - * @return An IconCompat created from the IconData or the default resource. - */ -suspend fun Context.createFido2IconCompatFromIconDataOrDefault( - iconData: IconData?, - @DrawableRes defaultResourceId: Int, -): IconCompat = if (iconData != null && iconData is IconData.Network) { - createFido2IconCompatFromRemoteUriOrDefaultResource( - uri = iconData.uri, - defaultResourceId = defaultResourceId, - ) -} else { - createFido2IconCompatFromResource(defaultResourceId) -} - -/** - * Creates an IconCompat from a drawable resource ID. - */ -fun Context.createFido2IconCompatFromResource(@DrawableRes resourceId: Int) = - IconCompat.createWithResource(this, resourceId) - -// futureTargetBitmap.get() is a blocking call so this function must be called from a coroutine. -@Suppress("RedundantSuspendModifier") -private suspend fun Context.createFido2IconCompatFromRemoteUriOrDefaultResource( - uri: String, - @DrawableRes defaultResourceId: Int, -): IconCompat { - val futureTargetBitmap = Glide - .with(this) - .asBitmap() - .load(uri) - .placeholder(defaultResourceId) - .submit() - return try { - IconCompat.createWithBitmap(futureTargetBitmap.get()) - } catch (e: CancellationException) { - Timber.e(e, "Cancellation exception while loading icon.") - IconCompat.createWithResource(this, defaultResourceId) - } catch (e: ExecutionException) { - Timber.e(e, "Execution exception while loading icon.") - IconCompat.createWithResource(this, defaultResourceId) - } catch (e: InterruptedException) { - Timber.e(e, "Interrupted while loading icon.") - IconCompat.createWithResource(this, defaultResourceId) - } -} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModule.kt deleted file mode 100644 index 9ca5654b41..0000000000 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModule.kt +++ /dev/null @@ -1,75 +0,0 @@ -package com.x8bit.bitwarden.ui.platform.glide - -import android.content.Context -import com.bitwarden.annotation.OmitFromCoverage -import com.bitwarden.network.ssl.createMtlsOkHttpClient -import com.bumptech.glide.Glide -import com.bumptech.glide.Registry -import com.bumptech.glide.annotation.GlideModule -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.module.AppGlideModule -import com.x8bit.bitwarden.data.platform.manager.CertificateManager -import com.x8bit.bitwarden.data.platform.manager.network.NetworkCookieManager -import dagger.hilt.EntryPoint -import dagger.hilt.InstallIn -import dagger.hilt.android.EntryPointAccessors -import dagger.hilt.components.SingletonComponent -import java.io.InputStream - -/** - * Custom Glide module for the Bitwarden app that configures Glide to use an OkHttpClient - * with mTLS (mutual TLS) support. - * - * This ensures that all icon/image loading requests through Glide present the client certificate - * for mutual TLS authentication, allowing them to pass through Cloudflare's mTLS checks. - * - * The configuration mirrors the SSL setup used in RetrofitsImpl for API calls. - */ -@OmitFromCoverage -@GlideModule -class BitwardenAppGlideModule : AppGlideModule() { - - /** - * Entry point to access Hilt-provided dependencies from non-Hilt managed classes. - */ - @EntryPoint - @InstallIn(SingletonComponent::class) - interface BitwardenGlideEntryPoint { - /** - * Provides access to the [CertificateManager] for mTLS certificate management. - */ - fun certificateManager(): CertificateManager - - /** - * Provides access to the [NetworkCookieManager] for cookie-based authentication. - */ - fun networkCookieManager(): NetworkCookieManager - } - - override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - // Get CertificateManager from Hilt - val entryPoint = EntryPointAccessors.fromApplication( - context = context.applicationContext, - entryPoint = BitwardenGlideEntryPoint::class.java, - ) - val certificateManager = entryPoint.certificateManager() - val networkCookieManager = entryPoint.networkCookieManager() - - // Build OkHttpClient with mTLS and cookie support - val client = certificateManager - .createMtlsOkHttpClient() - .newBuilder() - .addNetworkInterceptor(GlideCookieInterceptor(networkCookieManager)) - .build() - - // Register OkHttpUrlLoader that uses our mTLS + cookie OkHttpClient - registry.replace( - GlideUrl::class.java, - InputStream::class.java, - OkHttpUrlLoader.Factory(client), - ) - } - - override fun isManifestParsingEnabled(): Boolean = false -} diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/GlideCookieInterceptor.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/ImageCookieInterceptor.kt similarity index 83% rename from app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/GlideCookieInterceptor.kt rename to app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/ImageCookieInterceptor.kt index bbe0b8d163..855942527f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/glide/GlideCookieInterceptor.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/ImageCookieInterceptor.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.ui.platform.glide +package com.x8bit.bitwarden.ui.platform.image import com.bitwarden.network.exception.CookieRedirectException import com.bitwarden.network.provider.CookieProvider @@ -9,16 +9,16 @@ private const val HEADER_COOKIE = "Cookie" private const val HTTP_302 = 302 /** - * Interceptor that attaches cookies to Glide image requests for enterprise environments - * requiring cookie-based authentication. + * Interceptor that attaches cookies to image requests for enterprise environments requiring + * cookie-based authentication. * * Unlike [com.bitwarden.network.interceptor.CookieInterceptor], this interceptor does not * trigger cookie acquisition. It throws [CookieRedirectException] on HTTP 302 responses - * to prevent Glide from following redirects and caching invalid content. + * to prevent following redirects and caching invalid content. * * @property cookieProvider Provider for retrieving cookies by hostname. */ -class GlideCookieInterceptor( +class ImageCookieInterceptor( private val cookieProvider: CookieProvider, ) : Interceptor { diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/di/ImageModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/di/ImageModule.kt new file mode 100644 index 0000000000..32cec2cd1c --- /dev/null +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/image/di/ImageModule.kt @@ -0,0 +1,55 @@ +package com.x8bit.bitwarden.ui.platform.image.di + +import android.content.Context +import coil3.ImageLoader +import coil3.network.okhttp.OkHttpNetworkFetcherFactory +import com.bitwarden.network.ssl.createMtlsOkHttpClient +import com.x8bit.bitwarden.data.platform.manager.CertificateManager +import com.x8bit.bitwarden.data.platform.manager.network.NetworkCookieManager +import com.x8bit.bitwarden.ui.platform.image.ImageCookieInterceptor +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** + * Provides dependencies for loading images. + */ +@Module +@InstallIn(SingletonComponent::class) +object ImageModule { + /** + * Provides the [ImageLoader] used by Coil, configured to use an OkHttpClient with (mutual TLS) + * and cookie support. + * + * This ensures that all icon/image loading requests through Coil present the client + * certificate for mutual TLS authentication, allowing them to pass through Cloudflare's mTLS + * checks. + * + * The configuration mirrors the SSL setup used for non-image API calls. + */ + @Provides + @Singleton + fun provideImageLoader( + @ApplicationContext context: Context, + certificateManager: CertificateManager, + networkCookieManager: NetworkCookieManager, + ): ImageLoader = ImageLoader + .Builder(context = context) + .components { + this.add( + OkHttpNetworkFetcherFactory( + callFactory = { + certificateManager + .createMtlsOkHttpClient() + .newBuilder() + .addNetworkInterceptor(ImageCookieInterceptor(networkCookieManager)) + .build() + }, + ), + ) + } + .build() +} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensionsTest.kt deleted file mode 100644 index 1cbee345e4..0000000000 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/credentials/util/ContextExtensionsTest.kt +++ /dev/null @@ -1,144 +0,0 @@ -package com.x8bit.bitwarden.ui.credentials.util - -import android.content.Context -import android.graphics.Bitmap -import android.graphics.drawable.Icon -import android.net.Uri -import androidx.core.graphics.drawable.IconCompat -import com.bitwarden.ui.platform.components.icon.model.IconData -import com.bumptech.glide.Glide -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import io.mockk.unmockkStatic -import io.mockk.verify -import kotlinx.coroutines.test.runTest -import org.junit.jupiter.api.AfterEach -import org.junit.jupiter.api.BeforeEach -import org.junit.jupiter.api.Test -import java.util.concurrent.CancellationException -import java.util.concurrent.ExecutionException - -class ContextExtensionsTest { - - val mockContext = mockk() - - @BeforeEach - fun setUp() { - mockkStatic( - Glide::class, - Icon::class, - IconCompat::class, - ) - } - - @AfterEach - fun tearDown() { - unmockkStatic( - Glide::class, - Icon::class, - IconCompat::class, - Uri::class, - ) - } - - @Suppress("MaxLineLength") - @Test - fun `createFido2IconCompatFromIconDataOrDefault should return default icon when IconData is null`() = - runTest { - every { IconCompat.createWithResource(mockContext, 0) } returns IconCompat() - mockContext.createFido2IconCompatFromIconDataOrDefault( - iconData = null, - defaultResourceId = 0, - ) - verify { IconCompat.createWithResource(mockContext, 0) } - } - - @Suppress("MaxLineLength") - @Test - fun `createFido2IconCompatFromIconDataOrDefault should return default icon when IconData is not Network type`() = - runTest { - every { IconCompat.createWithResource(mockContext, 0) } returns IconCompat() - mockContext.createFido2IconCompatFromIconDataOrDefault( - iconData = IconData.Local(0), - defaultResourceId = 0, - ) - verify { IconCompat.createWithResource(mockContext, 0) } - } - - @Suppress("MaxLineLength") - @Test - fun `createFido2IconCompatFromIconDataOrDefault should load icon from URI when IconData is Network type`() = - runTest { - val mockBitmap = mockk() - setupMockGlide(mockBitmap) - every { IconCompat.createWithBitmap(mockBitmap) } returns mockk() - mockContext.createFido2IconCompatFromIconDataOrDefault( - iconData = IconData.Network("https://www.mockuri.com", 0), - defaultResourceId = 0, - ) - verify { IconCompat.createWithBitmap(mockBitmap) } - } - - @Suppress("MaxLineLength") - @Test - fun `createFido2IconCompatFromIconDataOrDefault should return default icon when loading from URI throws CancellationException`() = - runTest { - val mockBitmap = mockk() - setupMockGlide(mockBitmap) - every { IconCompat.createWithResource(mockContext, 0) } returns mockk() - every { IconCompat.createWithBitmap(mockBitmap) } throws CancellationException() - mockContext.createFido2IconCompatFromIconDataOrDefault( - iconData = IconData.Network("https://www.mockuri.com", 0), - defaultResourceId = 0, - ) - verify { IconCompat.createWithResource(mockContext, 0) } - } - - @Suppress("MaxLineLength") - @Test - fun `createFido2IconCompatFromIconDataOrDefault should return default icon when loading from URI throws ExecutionException`() = - runTest { - val mockBitmap = mockk() - setupMockGlide(mockBitmap) - every { IconCompat.createWithResource(mockContext, 0) } returns mockk() - every { - IconCompat.createWithBitmap(mockBitmap) - } throws ExecutionException("message", Throwable()) - - mockContext.createFido2IconCompatFromIconDataOrDefault( - iconData = IconData.Network("https://www.mockuri.com", 0), - defaultResourceId = 0, - ) - verify { IconCompat.createWithResource(mockContext, 0) } - } - - @Suppress("MaxLineLength") - @Test - fun `createFido2IconCompatFromIconDataOrDefault should return default icon when loading from URI throws InterruptedException`() = - runTest { - val mockBitmap = mockk() - setupMockGlide(mockBitmap) - every { IconCompat.createWithResource(mockContext, 0) } returns mockk() - every { IconCompat.createWithBitmap(mockBitmap) } throws InterruptedException() - mockContext.createFido2IconCompatFromIconDataOrDefault( - iconData = IconData.Network("https://www.mockuri.com", 0), - defaultResourceId = 0, - ) - verify { IconCompat.createWithResource(mockContext, 0) } - } - - private fun setupMockGlide(mockBitmap: Bitmap) { - every { Glide.with(mockContext) } returns mockk { - every { asBitmap() } returns mockk { - every { load(any()) } returns mockk { - every { placeholder(0) } returns mockk { - every { submit() } returns mockk { - every { get() } returns mockBitmap - } - } - } - } - } - } -} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModuleTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModuleTest.kt deleted file mode 100644 index b8828f4520..0000000000 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/BitwardenAppGlideModuleTest.kt +++ /dev/null @@ -1,60 +0,0 @@ -package com.x8bit.bitwarden.ui.platform.glide - -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test - -/** - * Test class for [BitwardenAppGlideModule] to verify mTLS configuration is properly applied - * to Glide without requiring a real mTLS server. - * - * These tests verify the module's structure and that it can be instantiated. - * Full integration testing requires running the app and checking logcat for - * "BitwardenGlide" logs when images are loaded. - */ -class BitwardenAppGlideModuleTest { - - @Test - fun `BitwardenAppGlideModule should be instantiable`() { - // Verify the module can be created - val module = BitwardenAppGlideModule() - - assertNotNull(module) - } - - @Test - fun `BitwardenAppGlideModule should have EntryPoint interface for Hilt dependency injection`() { - // Verify the Hilt EntryPoint interface exists for accessing CertificateManager - val entryPointInterface = BitwardenAppGlideModule::class.java - .declaredClasses - .firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" } - - assertNotNull(entryPointInterface) - } - - @Test - fun `BitwardenGlideEntryPoint should declare certificateManager method`() { - // Verify the EntryPoint has the required method to access CertificateManager - val entryPointInterface = BitwardenAppGlideModule::class.java - .declaredClasses - .firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" } - - val methods = requireNotNull(entryPointInterface).declaredMethods - val hasCertificateManagerMethod = methods.any { it.name == "certificateManager" } - - assertTrue(hasCertificateManagerMethod) - } - - @Test - fun `BitwardenGlideEntryPoint should declare networkCookieManager method`() { - val entryPointInterface = BitwardenAppGlideModule::class.java - .declaredClasses - .firstOrNull { it.simpleName == "BitwardenGlideEntryPoint" } - - val methods = requireNotNull(entryPointInterface).declaredMethods - val hasNetworkCookieManagerMethod = - methods.any { it.name == "networkCookieManager" } - - assertTrue(hasNetworkCookieManagerMethod) - } -} diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/GlideCookieInterceptorTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/image/ImageCookieInterceptorTest.kt similarity index 97% rename from app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/GlideCookieInterceptorTest.kt rename to app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/image/ImageCookieInterceptorTest.kt index 6c2767bfcc..a84402ed4e 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/glide/GlideCookieInterceptorTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/image/ImageCookieInterceptorTest.kt @@ -1,4 +1,4 @@ -package com.x8bit.bitwarden.ui.platform.glide +package com.x8bit.bitwarden.ui.platform.image import com.bitwarden.network.exception.CookieRedirectException import com.bitwarden.network.interceptor.FakeInterceptorChain @@ -15,11 +15,11 @@ import org.junit.jupiter.api.Assertions.assertNull import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows -class GlideCookieInterceptorTest { +class ImageCookieInterceptorTest { private val mockCookieProvider: CookieProvider = mockk() - private val interceptor = GlideCookieInterceptor( + private val interceptor = ImageCookieInterceptor( cookieProvider = mockCookieProvider, ) diff --git a/authenticator/build.gradle.kts b/authenticator/build.gradle.kts index 27a586467d..d54b6b9400 100644 --- a/authenticator/build.gradle.kts +++ b/authenticator/build.gradle.kts @@ -227,7 +227,6 @@ dependencies { implementation(libs.androidx.splashscreen) implementation(libs.androidx.work.runtime.ktx) implementation(libs.bitwarden.sdk) - implementation(libs.bumptech.glide) implementation(platform(libs.google.firebase.bom)) implementation(libs.google.firebase.cloud.messaging) implementation(libs.google.firebase.crashlytics) diff --git a/authenticator/proguard-rules.pro b/authenticator/proguard-rules.pro index 73aed837ee..bf1c020260 100644 --- a/authenticator/proguard-rules.pro +++ b/authenticator/proguard-rules.pro @@ -46,13 +46,6 @@ kotlinx.serialization.KSerializer serializer(...); } -################################################################################ -# Glide -################################################################################ - --keep public class * implements com.bumptech.glide.module.GlideModule --keep public class * extends com.bumptech.glide.module.AppGlideModule - ################################################################################ # Google Protobuf generated files ################################################################################ diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 908fb8b615..8dfec98215 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -30,11 +30,10 @@ androidxSecurityCrypto = "1.1.0" androidxSplash = "1.2.0" androidxWork = "2.12.0" bitwardenSdk = "4.0.0-9318-3b2c8fc3" +coil = "3.6.3" crashlytics = "3.0.8" detekt = "1.23.8" firebaseBom = "35.0.0" -glide = "5.0.9" -glideCompose = "1.0.0-beta01" googleBilling = "9.1.0" googleMlkitTextRecognition = "16.0.1" googleGuava = "33.7.2-jre" @@ -99,9 +98,10 @@ androidx-security-crypto = { module = "androidx.security:security-crypto", versi androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" } bitwarden-sdk = { module = "com.bitwarden:sdk-android", version.ref = "bitwardenSdk" } -bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glideCompose" } -bumptech-glide-okhttp = { module = "com.github.bumptech.glide:okhttp3-integration", version.ref = "glide" } -bumptech-glide-compiler = { module = "com.github.bumptech.glide:ksp", version.ref = "glide" } +coil-coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } +coil-coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } +coil-coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } +coil-coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } detekt-detekt-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", version.ref = "detekt" } detekt-detekt-rules = { module = "io.gitlab.arturbosch.detekt:detekt-rules-libraries", version.ref = "detekt" } google-billing = { module = "com.android.billingclient:billing", version.ref = "googleBilling" } diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 09a4745640..5d819095bb 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -75,7 +75,8 @@ dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.credentials) implementation(libs.androidx.navigation.compose) - implementation(libs.bumptech.glide) + implementation(libs.coil.coil.compose) + implementation(libs.coil.coil.gif) implementation(libs.kotlinx.serialization) implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.collections.immutable) diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/icon/BitwardenIcon.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/icon/BitwardenIcon.kt index 237d7295fc..7a74bf2032 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/icon/BitwardenIcon.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/icon/BitwardenIcon.kt @@ -4,21 +4,19 @@ import androidx.compose.material3.Icon import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import coil3.compose.AsyncImage import com.bitwarden.ui.platform.base.util.nullableTestTag import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.util.rememberVectorPainter -import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi -import com.bumptech.glide.integration.compose.GlideImage -import com.bumptech.glide.integration.compose.placeholder /** - * Represents a Bitwarden icon that is either locally loaded or loaded using glide. + * Represents a Bitwarden icon that is either locally loaded or loaded using Coil. * * @param iconData Label for the text field. * @param modifier A [Modifier] for the composable. * @param tint the color to be applied as the tint for the icon. */ -@OptIn(ExperimentalGlideComposeApi::class) @Composable fun BitwardenIcon( iconData: IconData, @@ -27,14 +25,14 @@ fun BitwardenIcon( ) { when (iconData) { is IconData.Network -> { - GlideImage( + val fallbackPainter = painterResource(id = iconData.fallbackIconRes) + AsyncImage( model = iconData.uri, - failure = placeholder(iconData.fallbackIconRes), contentDescription = iconData.contentDescription?.invoke(), + placeholder = fallbackPainter, + error = fallbackPainter, modifier = modifier.nullableTestTag(tag = iconData.testTag), - ) { - it.placeholder(iconData.fallbackIconRes) - } + ) } is IconData.Local -> { diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/image/BitwardenGifImage.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/image/BitwardenGifImage.kt index 05a9b61d38..bc8e185f57 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/image/BitwardenGifImage.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/image/BitwardenGifImage.kt @@ -1,32 +1,25 @@ package com.bitwarden.ui.platform.components.image import androidx.annotation.DrawableRes -import androidx.appcompat.content.res.AppCompatResources import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi -import com.bumptech.glide.integration.compose.GlideImage +import coil3.compose.AsyncImage /** * A composable that displays a gif image. * * The content will also be scaled to fit the image to the container. */ -@OptIn(ExperimentalGlideComposeApi::class) @Composable fun BitwardenGifImage( @DrawableRes resId: Int, modifier: Modifier = Modifier, contentDescription: String? = null, ) { - // The need for this workaround of loading into a drawable first is related to issue reported - // [here](https://github.com/bumptech/glide/issues/3751) - val context = LocalContext.current - val drawable = AppCompatResources.getDrawable(context, resId) - GlideImage( - model = drawable, + // Coil automatically registers the `coil-gif` decoder, which handles the animation. + AsyncImage( + model = resId, contentDescription = contentDescription, contentScale = ContentScale.Fit, modifier = modifier, From 1e36ab805d51bb5f6994530f1ab7186587eb408d Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 19 Oct 2026 14:05:20 +0000 Subject: [PATCH 2/3] [PM-45008] deps: Migrate the app from Room to Room3 (#7471) --- README.md | 4 ++-- app/build.gradle.kts | 9 ++++----- .../datasource/disk/dao/PrivilegedAppDao.kt | 8 ++++---- .../disk/database/PrivilegedAppDatabase.kt | 4 ++-- .../datasource/disk/di/CredentialsDiskModule.kt | 7 ++++--- .../disk/entity/PrivilegedAppEntity.kt | 4 ++-- .../datasource/disk/dao/OrganizationEventDao.kt | 8 ++++---- .../datasource/disk/database/PlatformDatabase.kt | 10 +++++----- .../datasource/disk/di/PlatformDiskModule.kt | 12 +++++------- .../disk/entity/OrganizationEventEntity.kt | 6 +++--- .../datasource/disk/dao/PasswordHistoryDao.kt | 6 +++--- .../disk/database/PasswordHistoryDatabase.kt | 4 ++-- .../datasource/disk/di/GeneratorDiskModule.kt | 12 ++++++------ .../disk/entity/PasswordHistoryEntity.kt | 6 +++--- .../disk/callback/DatabaseSchemeCallback.kt | 4 ++-- .../disk/convertor/InstantTypeConverter.kt | 16 ++++++++-------- .../data/vault/datasource/disk/dao/CiphersDao.kt | 10 +++++----- .../vault/datasource/disk/dao/CollectionsDao.kt | 10 +++++----- .../data/vault/datasource/disk/dao/DomainsDao.kt | 8 ++++---- .../data/vault/datasource/disk/dao/FoldersDao.kt | 12 ++++++------ .../data/vault/datasource/disk/dao/SendsDao.kt | 10 +++++----- .../datasource/disk/database/VaultDatabase.kt | 14 +++++++------- .../vault/datasource/disk/di/VaultDiskModule.kt | 12 +++++------- .../vault/datasource/disk/entity/CipherEntity.kt | 8 ++++---- .../datasource/disk/entity/CollectionEntity.kt | 6 +++--- .../datasource/disk/entity/DomainsEntity.kt | 6 +++--- .../vault/datasource/disk/entity/FolderEntity.kt | 6 +++--- .../vault/datasource/disk/entity/SendEntity.kt | 6 +++--- .../disk/callback/DatabaseSchemeCallbackTest.kt | 3 ++- .../vault/datasource/disk/dao/FakeFoldersDao.kt | 2 +- authenticator/build.gradle.kts | 9 ++++----- .../AuthenticatorItemAlgorithmConverter.kt | 16 ++++++++-------- .../convertor/AuthenticatorItemTypeConverter.kt | 16 ++++++++-------- .../authenticator/datasource/disk/dao/ItemDao.kt | 8 ++++---- .../disk/database/AuthenticatorDatabase.kt | 10 +++++----- .../disk/di/AuthenticatorDiskModule.kt | 14 ++++++-------- .../disk/entity/AuthenticatorItemEntity.kt | 6 +++--- build.gradle.kts | 2 +- buildscript-gradle.lockfile | 4 ++-- docs/ARCHITECTURE.md | 2 +- gradle/libs.versions.toml | 9 ++++----- 41 files changed, 161 insertions(+), 168 deletions(-) diff --git a/README.md b/README.md index 4b531d958b..f7cf67011a 100644 --- a/README.md +++ b/README.md @@ -153,8 +153,8 @@ The following is a list of all third-party dependencies included as part of the - Purpose: Provides a consistent API for navigating between Android components. - License: Apache 2.0 -- **AndroidX Room** - - https://developer.android.com/jetpack/androidx/releases/room +- **AndroidX Room3** + - https://developer.android.com/jetpack/androidx/releases/room3 - Purpose: A convenient SQLite-based persistence layer for Android. - License: Apache 2.0 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 682e66af91..b82ed0651e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -11,7 +11,7 @@ import java.util.Properties plugins { alias(libs.plugins.android.application) - alias(libs.plugins.androidx.room) + alias(libs.plugins.androidx.room3) // Crashlytics is enabled for all builds initially but removed for FDroid builds in gradle and // standardDebug builds in the merged manifest. alias(libs.plugins.crashlytics) @@ -49,7 +49,7 @@ base { archivesName.set("com.x8bit.bitwarden") } -room { +room3 { schemaDirectory("$projectDir/schemas") } @@ -268,9 +268,8 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.compose) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.navigation.compose) - ksp(libs.androidx.room.compiler) - implementation(libs.androidx.room.ktx) - implementation(libs.androidx.room.runtime) + ksp(libs.androidx.room3.compiler) + implementation(libs.androidx.room3.runtime) implementation(libs.androidx.security.crypto) implementation(libs.androidx.splashscreen) implementation(libs.androidx.work.runtime.ktx) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/dao/PrivilegedAppDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/dao/PrivilegedAppDao.kt index de3d94b114..9167a37389 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/dao/PrivilegedAppDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/dao/PrivilegedAppDao.kt @@ -1,9 +1,9 @@ package com.x8bit.bitwarden.data.credentials.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query import com.x8bit.bitwarden.data.credentials.datasource.disk.entity.PrivilegedAppEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/database/PrivilegedAppDatabase.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/database/PrivilegedAppDatabase.kt index 35c9a284d8..90ce4cd0de 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/database/PrivilegedAppDatabase.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/database/PrivilegedAppDatabase.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.credentials.datasource.disk.database -import androidx.room.Database -import androidx.room.RoomDatabase +import androidx.room3.Database +import androidx.room3.RoomDatabase import com.x8bit.bitwarden.data.credentials.datasource.disk.dao.PrivilegedAppDao import com.x8bit.bitwarden.data.credentials.datasource.disk.entity.PrivilegedAppEntity diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/di/CredentialsDiskModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/di/CredentialsDiskModule.kt index f7fd3cbc56..96f7c718b0 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/di/CredentialsDiskModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/di/CredentialsDiskModule.kt @@ -1,7 +1,8 @@ package com.x8bit.bitwarden.data.credentials.datasource.disk.di import android.app.Application -import androidx.room.Room +import androidx.room3.Room +import androidx.sqlite.driver.AndroidSQLiteDriver import com.x8bit.bitwarden.data.credentials.datasource.disk.PrivilegedAppDiskSource import com.x8bit.bitwarden.data.credentials.datasource.disk.PrivilegedAppDiskSourceImpl import com.x8bit.bitwarden.data.credentials.datasource.disk.dao.PrivilegedAppDao @@ -25,12 +26,12 @@ object CredentialsDiskModule { app: Application, ): PrivilegedAppDatabase = Room - .databaseBuilder( + .databaseBuilder( context = app, - klass = PrivilegedAppDatabase::class.java, name = "privileged_apps_database", ) .fallbackToDestructiveMigration(dropAllTables = true) + .setDriver(driver = AndroidSQLiteDriver()) .build() @Provides diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/entity/PrivilegedAppEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/entity/PrivilegedAppEntity.kt index 9495507baa..0898835d96 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/entity/PrivilegedAppEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/credentials/datasource/disk/entity/PrivilegedAppEntity.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.credentials.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity +import androidx.room3.ColumnInfo +import androidx.room3.Entity /** * Entity representing a trusted privileged app in the database. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/dao/OrganizationEventDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/dao/OrganizationEventDao.kt index 67524a24ba..b4f630b44b 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/dao/OrganizationEventDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/dao/OrganizationEventDao.kt @@ -1,9 +1,9 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt index 1c739c37ca..a97541e27a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/database/PlatformDatabase.kt @@ -1,9 +1,9 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.database -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.RoomDatabase -import androidx.room.TypeConverters +import androidx.room3.AutoMigration +import androidx.room3.ColumnTypeConverters +import androidx.room3.Database +import androidx.room3.RoomDatabase import com.x8bit.bitwarden.data.platform.datasource.disk.dao.OrganizationEventDao import com.x8bit.bitwarden.data.platform.datasource.disk.entity.OrganizationEventEntity import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.InstantTypeConverter @@ -21,7 +21,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.InstantTypeConve AutoMigration(from = 1, to = 2), ], ) -@TypeConverters(InstantTypeConverter::class) +@ColumnTypeConverters(InstantTypeConverter::class) abstract class PlatformDatabase : RoomDatabase() { /** * Provides the DAO for accessing organization event data. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt index 8de5326008..8e718fd97a 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/di/PlatformDiskModule.kt @@ -3,7 +3,8 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.di import android.app.Application import android.content.Context import android.content.SharedPreferences -import androidx.room.Room +import androidx.room3.Room +import androidx.sqlite.driver.AndroidSQLiteDriver import com.bitwarden.core.data.manager.dispatcher.DispatcherManager import com.bitwarden.data.datasource.disk.FlightRecorderDiskSource import com.bitwarden.data.datasource.disk.di.EncryptedPreferences @@ -69,14 +70,11 @@ object PlatformDiskModule { databaseSchemeManager: DatabaseSchemeManager, ): PlatformDatabase = Room - .databaseBuilder( - context = app, - klass = PlatformDatabase::class.java, - name = "platform_database", - ) + .databaseBuilder(context = app, name = "platform_database") .fallbackToDestructiveMigration(dropAllTables = false) - .addTypeConverter(InstantTypeConverter()) + .addColumnTypeConverter(typeConverter = InstantTypeConverter()) .addCallback(DatabaseSchemeCallback(databaseSchemeManager = databaseSchemeManager)) + .setDriver(driver = AndroidSQLiteDriver()) .build() @Provides diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt index 21b6475feb..13e5c3fc27 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/platform/datasource/disk/entity/OrganizationEventEntity.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.platform.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey import java.time.Instant /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/dao/PasswordHistoryDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/dao/PasswordHistoryDao.kt index 236aa9d2f8..ae3a944163 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/dao/PasswordHistoryDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/dao/PasswordHistoryDao.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.Query +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.Query import com.x8bit.bitwarden.data.tools.generator.datasource.disk.entity.PasswordHistoryEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/database/PasswordHistoryDatabase.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/database/PasswordHistoryDatabase.kt index 8880a6cc92..6b0c1ef232 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/database/PasswordHistoryDatabase.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/database/PasswordHistoryDatabase.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk.database -import androidx.room.Database -import androidx.room.RoomDatabase +import androidx.room3.Database +import androidx.room3.RoomDatabase import com.x8bit.bitwarden.data.tools.generator.datasource.disk.dao.PasswordHistoryDao import com.x8bit.bitwarden.data.tools.generator.datasource.disk.entity.PasswordHistoryEntity diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/di/GeneratorDiskModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/di/GeneratorDiskModule.kt index 77f6eaeab7..cb6c259f52 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/di/GeneratorDiskModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/di/GeneratorDiskModule.kt @@ -2,7 +2,8 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk.di import android.app.Application import android.content.SharedPreferences -import androidx.room.Room +import androidx.room3.Room +import androidx.sqlite.driver.AndroidSQLiteDriver import com.bitwarden.data.datasource.disk.di.UnencryptedPreferences import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource @@ -50,16 +51,15 @@ object GeneratorDiskModule { fun providePasswordHistoryDatabase( app: Application, databaseSchemeManager: DatabaseSchemeManager, - ): PasswordHistoryDatabase { - return Room - .databaseBuilder( + ): PasswordHistoryDatabase = + Room + .databaseBuilder( context = app, - klass = PasswordHistoryDatabase::class.java, name = "passcode_history_database", ) .addCallback(DatabaseSchemeCallback(databaseSchemeManager = databaseSchemeManager)) + .setDriver(driver = AndroidSQLiteDriver()) .build() - } @Provides @Singleton diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/entity/PasswordHistoryEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/entity/PasswordHistoryEntity.kt index 2864e514f4..28126152d8 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/entity/PasswordHistoryEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/tools/generator/datasource/disk/entity/PasswordHistoryEntity.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey import com.bitwarden.vault.PasswordHistory import java.time.Instant diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallback.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallback.kt index f81485ed56..44c11fcb62 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallback.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallback.kt @@ -1,6 +1,6 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.callback -import androidx.room.RoomDatabase +import androidx.room3.RoomDatabase import androidx.sqlite.SQLiteConnection import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager @@ -10,7 +10,7 @@ import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager class DatabaseSchemeCallback( private val databaseSchemeManager: DatabaseSchemeManager, ) : RoomDatabase.Callback() { - override fun onDestructiveMigration(connection: SQLiteConnection) { + override suspend fun onDestructiveMigration(connection: SQLiteConnection) { super.onDestructiveMigration(connection) databaseSchemeManager.clearSyncState() } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/convertor/InstantTypeConverter.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/convertor/InstantTypeConverter.kt index 1baaae9094..bae08d7818 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/convertor/InstantTypeConverter.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/convertor/InstantTypeConverter.kt @@ -1,26 +1,26 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.convertor -import androidx.room.ProvidedTypeConverter -import androidx.room.TypeConverter +import androidx.room3.ColumnTypeConverter +import androidx.room3.ProvidedColumnTypeConverter import java.time.Instant /** - * A [TypeConverter] to convert an [Instant] to and from a [Long]. + * A [ColumnTypeConverter] to convert an [Instant] to and from a [Long]. */ -@ProvidedTypeConverter +@ProvidedColumnTypeConverter class InstantTypeConverter { /** - * A [TypeConverter] to convert a [Long] to an [Instant]. + * A [ColumnTypeConverter] to convert a [Long] to an [Instant]. */ - @TypeConverter + @ColumnTypeConverter fun fromTimestamp( value: Long?, ): Instant? = value?.let { Instant.ofEpochSecond(it) } /** - * A [TypeConverter] to convert an [Instant] to a [Long]. + * A [ColumnTypeConverter] to convert an [Instant] to a [Long]. */ - @TypeConverter + @ColumnTypeConverter fun toTimestamp( instant: Instant?, ): Long? = instant?.epochSecond diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CiphersDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CiphersDao.kt index 832ea367bc..124a5f312f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CiphersDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CiphersDao.kt @@ -1,10 +1,10 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query +import androidx.room3.Transaction import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CipherEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CollectionsDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CollectionsDao.kt index 0119b93e78..c6e86ec313 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CollectionsDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/CollectionsDao.kt @@ -1,10 +1,10 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query +import androidx.room3.Transaction import com.x8bit.bitwarden.data.vault.datasource.disk.entity.CollectionEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/DomainsDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/DomainsDao.kt index 38f6a413c7..4e17944bf2 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/DomainsDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/DomainsDao.kt @@ -1,9 +1,9 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FoldersDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FoldersDao.kt index fb7c23c75c..9864ff09e4 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FoldersDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FoldersDao.kt @@ -1,10 +1,10 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query +import androidx.room3.Transaction import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity import kotlinx.coroutines.flow.Flow @@ -39,7 +39,7 @@ interface FoldersDao { * Retrieves all folders from the database for a given [userId]. */ @Query("SELECT * FROM folders WHERE user_id = :userId") - fun getAllFolders( + suspend fun getAllFolders( userId: String, ): List diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/SendsDao.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/SendsDao.kt index b21db7f003..b6f8286f8c 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/SendsDao.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/SendsDao.kt @@ -1,10 +1,10 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Transaction +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query +import androidx.room3.Transaction import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity import kotlinx.coroutines.flow.Flow diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt index 0dbbf2d1f4..49701af274 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/database/VaultDatabase.kt @@ -1,11 +1,11 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.database -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.DeleteColumn -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import androidx.room.migration.AutoMigrationSpec +import androidx.room3.AutoMigration +import androidx.room3.ColumnTypeConverters +import androidx.room3.Database +import androidx.room3.DeleteColumn +import androidx.room3.RoomDatabase +import androidx.room3.migration.AutoMigrationSpec import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.InstantTypeConverter import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CiphersDao import com.x8bit.bitwarden.data.vault.datasource.disk.dao.CollectionsDao @@ -38,7 +38,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity AutoMigration(from = 9, to = 10, RemoveTotpAutoMigration::class), ], ) -@TypeConverters(InstantTypeConverter::class) +@ColumnTypeConverters(InstantTypeConverter::class) abstract class VaultDatabase : RoomDatabase() { /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt index 1dcb9bdea8..c1f9e5723d 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/di/VaultDiskModule.kt @@ -1,7 +1,8 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.di import android.app.Application -import androidx.room.Room +import androidx.room3.Room +import androidx.sqlite.driver.AndroidSQLiteDriver import com.bitwarden.core.data.manager.dispatcher.DispatcherManager import com.x8bit.bitwarden.data.platform.manager.DatabaseSchemeManager import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource @@ -35,14 +36,11 @@ class VaultDiskModule { databaseSchemeManager: DatabaseSchemeManager, ): VaultDatabase = Room - .databaseBuilder( - context = app, - klass = VaultDatabase::class.java, - name = "vault_database", - ) + .databaseBuilder(context = app, name = "vault_database") .fallbackToDestructiveMigration(dropAllTables = false) .addCallback(DatabaseSchemeCallback(databaseSchemeManager = databaseSchemeManager)) - .addTypeConverter(InstantTypeConverter()) + .addColumnTypeConverter(typeConverter = InstantTypeConverter()) + .setDriver(driver = AndroidSQLiteDriver()) .build() @Provides diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CipherEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CipherEntity.kt index 5e47e5ec5a..4e464dde2f 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CipherEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CipherEntity.kt @@ -1,9 +1,9 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.Index -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.Index +import androidx.room3.PrimaryKey /** * Entity representing a cipher in the database. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CollectionEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CollectionEntity.kt index f31b19943b..49e0645b46 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CollectionEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/CollectionEntity.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey /** * Entity representing a collection in the database. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt index ee9414ba89..c3e6818c16 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/DomainsEntity.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey /** * Entity representing a set of domains in the database. diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/FolderEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/FolderEntity.kt index a4bf8377a2..11c4024db1 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/FolderEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/FolderEntity.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey import java.time.Instant /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/SendEntity.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/SendEntity.kt index dcb819ea51..602990a6e5 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/SendEntity.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/entity/SendEntity.kt @@ -1,8 +1,8 @@ package com.x8bit.bitwarden.data.vault.datasource.disk.entity -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey /** * Entity representing a send in the database. diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallbackTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallbackTest.kt index 7e9162abf0..e5603b95b5 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallbackTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/callback/DatabaseSchemeCallbackTest.kt @@ -7,6 +7,7 @@ import io.mockk.just import io.mockk.mockk import io.mockk.runs import io.mockk.verify +import kotlinx.coroutines.test.runTest import org.junit.Test class DatabaseSchemeCallbackTest { @@ -17,7 +18,7 @@ class DatabaseSchemeCallbackTest { private val callback = DatabaseSchemeCallback(databaseSchemeManager) @Test - fun `onDestructiveMigration calls clearSyncState`() { + fun `onDestructiveMigration calls clearSyncState`() = runTest { callback.onDestructiveMigration(mockk()) verify(exactly = 1) { databaseSchemeManager.clearSyncState() } } diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeFoldersDao.kt b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeFoldersDao.kt index 6aee7fbc76..57824ed000 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeFoldersDao.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/data/vault/datasource/disk/dao/FakeFoldersDao.kt @@ -54,7 +54,7 @@ class FakeFoldersDao : FoldersDao { folderId: String, ): FolderEntity? = storedFolders.find { it.userId == userId && it.id == folderId } - override fun getAllFolders( + override suspend fun getAllFolders( userId: String, ): List = storedFolders.filter { it.userId == userId } diff --git a/authenticator/build.gradle.kts b/authenticator/build.gradle.kts index d54b6b9400..40b17661a3 100644 --- a/authenticator/build.gradle.kts +++ b/authenticator/build.gradle.kts @@ -8,7 +8,7 @@ import java.util.Properties plugins { alias(libs.plugins.android.application) - alias(libs.plugins.androidx.room) + alias(libs.plugins.androidx.room3) alias(libs.plugins.crashlytics) alias(libs.plugins.hilt) alias(libs.plugins.kotlin.compose.compiler) @@ -35,7 +35,7 @@ base { archivesName.set("com.bitwarden.authenticator") } -room { +room3 { schemaDirectory("$projectDir/schemas") } @@ -220,9 +220,8 @@ dependencies { implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.navigation.compose) testImplementation(libs.testng) - ksp(libs.androidx.room.compiler) - implementation(libs.androidx.room.ktx) - implementation(libs.androidx.room.runtime) + ksp(libs.androidx.room3.compiler) + implementation(libs.androidx.room3.runtime) implementation(libs.androidx.security.crypto) implementation(libs.androidx.splashscreen) implementation(libs.androidx.work.runtime.ktx) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemAlgorithmConverter.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemAlgorithmConverter.kt index a4208a52ef..a33b3ef827 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemAlgorithmConverter.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemAlgorithmConverter.kt @@ -1,25 +1,25 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.convertor -import androidx.room.ProvidedTypeConverter -import androidx.room.TypeConverter +import androidx.room3.ColumnTypeConverter +import androidx.room3.ProvidedColumnTypeConverter import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemAlgorithm /** - * A [TypeConverter] to convert [AuthenticatorItemAlgorithm] to and from a [String]. + * A [ColumnTypeConverter] to convert [AuthenticatorItemAlgorithm] to and from a [String]. */ -@ProvidedTypeConverter +@ProvidedColumnTypeConverter class AuthenticatorItemAlgorithmConverter { /** - * A [TypeConverter] to convert an [AuthenticatorItemAlgorithm] to a [String]. + * A [ColumnTypeConverter] to convert an [AuthenticatorItemAlgorithm] to a [String]. */ - @TypeConverter + @ColumnTypeConverter fun toString(item: AuthenticatorItemAlgorithm): String = item.name /** - * A [TypeConverter] to convert a [String] to an [AuthenticatorItemAlgorithm]. + * A [ColumnTypeConverter] to convert a [String] to an [AuthenticatorItemAlgorithm]. */ - @TypeConverter + @ColumnTypeConverter fun fromString(itemName: String) = AuthenticatorItemAlgorithm .entries .find { it.name == itemName } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemTypeConverter.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemTypeConverter.kt index dba98d4f9e..78d2c8a130 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemTypeConverter.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/convertor/AuthenticatorItemTypeConverter.kt @@ -1,25 +1,25 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.convertor -import androidx.room.ProvidedTypeConverter -import androidx.room.TypeConverter +import androidx.room3.ColumnTypeConverter +import androidx.room3.ProvidedColumnTypeConverter import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemType /** - * A [TypeConverter] to convert [AuthenticatorItemType] to and from a [String]. + * A [ColumnTypeConverter] to convert [AuthenticatorItemType] to and from a [String]. */ -@ProvidedTypeConverter +@ProvidedColumnTypeConverter class AuthenticatorItemTypeConverter { /** - * A [TypeConverter] to convert an [AuthenticatorItemType] to a [String]. + * A [ColumnTypeConverter] to convert an [AuthenticatorItemType] to a [String]. */ - @TypeConverter + @ColumnTypeConverter fun toString(item: AuthenticatorItemType): String = item.name /** - * A [TypeConverter] to convert a [String] to an [AuthenticatorItemType]. + * A [ColumnTypeConverter] to convert a [String] to an [AuthenticatorItemType]. */ - @TypeConverter + @ColumnTypeConverter fun fromString(itemName: String) = AuthenticatorItemType .entries .find { it.name == itemName } diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/dao/ItemDao.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/dao/ItemDao.kt index 305f820202..9946d2dbae 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/dao/ItemDao.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/dao/ItemDao.kt @@ -1,9 +1,9 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.dao -import androidx.room.Dao -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query +import androidx.room3.Dao +import androidx.room3.Insert +import androidx.room3.OnConflictStrategy +import androidx.room3.Query import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity import kotlinx.coroutines.flow.Flow diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt index c5a7a41555..0573f5c170 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt @@ -1,9 +1,9 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.database -import androidx.room.AutoMigration -import androidx.room.Database -import androidx.room.RoomDatabase -import androidx.room.TypeConverters +import androidx.room3.AutoMigration +import androidx.room3.ColumnTypeConverters +import androidx.room3.Database +import androidx.room3.RoomDatabase import com.bitwarden.authenticator.data.authenticator.datasource.disk.convertor.AuthenticatorItemAlgorithmConverter import com.bitwarden.authenticator.data.authenticator.datasource.disk.convertor.AuthenticatorItemTypeConverter import com.bitwarden.authenticator.data.authenticator.datasource.disk.dao.ItemDao @@ -22,7 +22,7 @@ import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.Aut version = 2, exportSchema = true, ) -@TypeConverters( +@ColumnTypeConverters( AuthenticatorItemTypeConverter::class, AuthenticatorItemAlgorithmConverter::class, ) diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt index 1aad5e5cc1..ed8683b6f3 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt @@ -1,7 +1,8 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.di import android.app.Application -import androidx.room.Room +import androidx.room3.Room +import androidx.sqlite.driver.AndroidSQLiteDriver import com.bitwarden.authenticator.data.authenticator.datasource.disk.AuthenticatorDiskSource import com.bitwarden.authenticator.data.authenticator.datasource.disk.AuthenticatorDiskSourceImpl import com.bitwarden.authenticator.data.authenticator.datasource.disk.convertor.AuthenticatorItemAlgorithmConverter @@ -25,14 +26,11 @@ object AuthenticatorDiskModule { @Singleton fun provideAuthenticatorDatabase(app: Application): AuthenticatorDatabase = Room - .databaseBuilder( - context = app, - klass = AuthenticatorDatabase::class.java, - name = "authenticator_database", - ) + .databaseBuilder(context = app, name = "authenticator_database") .fallbackToDestructiveMigration(dropAllTables = true) - .addTypeConverter(AuthenticatorItemTypeConverter()) - .addTypeConverter(AuthenticatorItemAlgorithmConverter()) + .addColumnTypeConverter(typeConverter = AuthenticatorItemTypeConverter()) + .addColumnTypeConverter(typeConverter = AuthenticatorItemAlgorithmConverter()) + .setDriver(driver = AndroidSQLiteDriver()) .build() @Provides diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt index d761faed53..c1738bec41 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt @@ -2,9 +2,9 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.entity import android.net.Uri import androidx.core.text.htmlEncode -import androidx.room.ColumnInfo -import androidx.room.Entity -import androidx.room.PrimaryKey +import androidx.room3.ColumnInfo +import androidx.room3.Entity +import androidx.room3.PrimaryKey /** * Entity representing an authenticator item in the database. diff --git a/build.gradle.kts b/build.gradle.kts index 16477d1997..f98c9e1450 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import io.gitlab.arturbosch.detekt.Detekt plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false - alias(libs.plugins.androidx.room) apply false + alias(libs.plugins.androidx.room3) apply false alias(libs.plugins.detekt) apply true alias(libs.plugins.hilt) apply false alias(libs.plugins.kotlin.compose.compiler) apply false diff --git a/buildscript-gradle.lockfile b/buildscript-gradle.lockfile index 1ef124f244..eb547eb64b 100644 --- a/buildscript-gradle.lockfile +++ b/buildscript-gradle.lockfile @@ -3,8 +3,8 @@ # This file is expected to be part of source control. androidx.databinding:databinding-common:9.4.1=classpath androidx.databinding:databinding-compiler-common:9.4.1=classpath -androidx.room:androidx.room.gradle.plugin:2.8.5=classpath -androidx.room:room-gradle-plugin:2.8.5=classpath +androidx.room3:androidx.room3.gradle.plugin:3.0.3=classpath +androidx.room3:room3-gradle-plugin:3.0.3=classpath com.android.application:com.android.application.gradle.plugin:9.4.1=classpath com.android.databinding:baseLibrary:9.4.1=classpath com.android.library:com.android.library.gradle.plugin:9.4.1=classpath diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fd62779e13..839e225677 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -72,7 +72,7 @@ Note that any functions exposed by a data layer class that must perform asynchro ### Data Sources -The lowest level of the data layer are the "data source" classes. These are the raw sources of data that include data persisted in [Room](https://developer.android.com/jetpack/androidx/releases/room) / [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences), data retrieved from network requests using [Retrofit](https://github.com/square/retrofit), and data retrieved via interactions with the [Bitwarden SDK](https://github.com/bitwarden/sdk). +The lowest level of the data layer are the "data source" classes. These are the raw sources of data that include data persisted in [Room](https://developer.android.com/jetpack/androidx/releases/room3) / [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences), data retrieved from network requests using [Retrofit](https://github.com/square/retrofit), and data retrieved via interactions with the [Bitwarden SDK](https://github.com/bitwarden/sdk). Note that these data sources are constructed in a manner that adheres to a very important principle of the app: **that function calls should not throw exceptions** (see the [style and best practices documentation](STYLE_AND_BEST_PRACTICES.md#best-practices--kotlin) for more details.) In the case of data sources, this tends to mean that suspending functions like those representing network requests or Bitwarden SDK calls should return a [Result](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/) type. This is an important responsibility of the data layer as a wrapper around other third party libraries, as dependencies like Retrofit and the Bitwarden SDK tend to throw exceptions to indicate errors instead. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8dfec98215..91d7f576fa 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ androidxCredentialsProviderEvents = "1.0.0-alpha06" androidxHiltNavigationCompose = "1.4.0" androidxLifecycle = "2.11.0" androidxNavigation = "2.9.8" -androidxRoom = "2.8.5" +androidxRoom3 = "3.0.3" androidxSecurityCrypto = "1.1.0" androidxSplash = "1.2.0" androidxWork = "2.12.0" @@ -91,9 +91,8 @@ androidx-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", androidx-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidxLifecycle" } androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidxLifecycle" } androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidxNavigation" } -androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidxRoom" } -androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidxRoom" } -androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidxRoom" } +androidx-room3-compiler = { module = "androidx.room3:room3-compiler", version.ref = "androidxRoom3" } +androidx-room3-runtime = { module = "androidx.room3:room3-runtime", version.ref = "androidxRoom3" } androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "androidxSecurityCrypto" } androidx-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "androidxSplash" } androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" } @@ -144,7 +143,7 @@ testng = { group = "org.testng", name = "testng", version.ref = "testng" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } -androidx-room = { id = "androidx.room", version.ref = "androidxRoom" } +androidx-room3 = { id = "androidx.room3", version.ref = "androidxRoom3" } crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" } detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } google-protobuf = { id = "com.google.protobuf", version.ref = "googleProtoBufPlugin" } From 9bdf4d222ed0916415973418052f0bd089507875 Mon Sep 17 00:00:00 2001 From: "bw-ghapp[bot]" <178206702+bw-ghapp[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 14:27:51 +0000 Subject: [PATCH 3/3] Crowdin Pull (#7494) Co-authored-by: bitwarden-devops-bot <106330231+bitwarden-devops-bot@users.noreply.github.com> --- ui/src/main/res/values-hu-rHU/strings.xml | 2 +- ui/src/main/res/values-lb-rLU/strings.xml | 60 +++++++++++------------ ui/src/main/res/values-sv-rSE/strings.xml | 8 +-- ui/src/main/res/values-uk-rUA/strings.xml | 10 ++-- ui/src/main/res/values-zh-rCN/strings.xml | 2 +- 5 files changed, 41 insertions(+), 41 deletions(-) diff --git a/ui/src/main/res/values-hu-rHU/strings.xml b/ui/src/main/res/values-hu-rHU/strings.xml index c7aa9beddb..b7fe42bbab 100644 --- a/ui/src/main/res/values-hu-rHU/strings.xml +++ b/ui/src/main/res/values-hu-rHU/strings.xml @@ -1441,7 +1441,7 @@ Szeretnénk átváltani erre a fiókra? Jelmondat használata Jelszó használata Felhasználónév használata - Create login + Bejelentkezés létrehozása Feloldás folytatása biometrikus adatokkal Biztonsági frissítés történt. Erősítsük meg újra a biometrikus adatokat, hogy továbbra is használjuk azokat a Bitwarden feloldásához. Kikapcsolás diff --git a/ui/src/main/res/values-lb-rLU/strings.xml b/ui/src/main/res/values-lb-rLU/strings.xml index f12e2f3113..532a8e0137 100644 --- a/ui/src/main/res/values-lb-rLU/strings.xml +++ b/ui/src/main/res/values-lb-rLU/strings.xml @@ -21,47 +21,47 @@ PIN-Code aginn Favoritten Dossier - New folder created - Folder deleted. - No Folder - Folder saved - Folders + Neien Dossier erstellt. + Dossier geläscht. + Keen Dossier + Dossier gespäichert + Dossieren Verstoppen - Please connect to the internet before continuing. - Internet connection required - Invalid master password. Try again. - Invalid PIN. Try again. - Launch - Log in + Wannechgelift verbann dech mam Internet fir virunzefueren. + Internetverbindung noutwenneg + Ongëltegt Master-Passwuert. Wgl. erëm probéieren. + Ongëlteg PIN. Wgl. erëm probéieren. + Opmaachen + Umellen Login - Log out - Are you sure you want to log out? - Remove account - Are you sure you want to remove this account? - Account already added - Would you like to switch to it now? - Master password + Ofmellen + Bass du sécher, datt s du dech ofmelle wëlls? + Kont ewechhuelen + Wëlls du dëse Kont wierklech ewechhuelen? + Kont scho bäigesat + Wëlls du elo dohi wiesselen? + Master-Passwuert Master password (required) New master password (required) Méi Optiounen Mäin Dossier Meng Dossieren - My vault - Name + Mäin Tresor + Numm Item name (required) Send name (required) - No - Notes + Nee + Notizen Privat Notizen Okay - Password - Save - Move - Saving… - Settings + Passwuert + Späicheren + Réckelen + Späicheren… + Astellungen Weisen - Item deleted - Submit + Element geläscht + Ofschécken Synchroniséieren URI Website (URI) @@ -932,7 +932,7 @@ Do you want to switch to this account? Copy private key Login Credentials Autofill Options - Use this button to generate a new unique password. + Verwenn dëse Knäppchen, fir en neit, eenzegaartegt Passwuert ze generéieren. 1 OF 3 2 OF 3 You’ll only need to set up Authenticator Key for logins that require two-factor authentication with a code. The key will continuously generate six-digit codes you can use to log in. diff --git a/ui/src/main/res/values-sv-rSE/strings.xml b/ui/src/main/res/values-sv-rSE/strings.xml index ce048c1e0e..3b47229f85 100644 --- a/ui/src/main/res/values-sv-rSE/strings.xml +++ b/ui/src/main/res/values-sv-rSE/strings.xml @@ -574,8 +574,8 @@ Skanningen sker automatiskt. Skicka koden igen En verifieringskod skickades till din e-postadress Skicka kraschloggar - expand - collapse + fäll ut + fäll ihop Versaler (A till Z) Gemener (A till Z) Siffror (0 till 9) @@ -1442,10 +1442,10 @@ Vill du byta till detta konto? Använd den här lösenordsfrasen Använd det här lösenordet Använd det här användarnamnet - Create login + Skapa inloggning Behåll upplåsning med biometri Vi har gjort en säkerhetsuppdatering. Bekräfta din biometri igen för att fortsätta använda den för att låsa upp Bitwarden. Stäng av Stänga av biometri? - Unlocking will require your master password. You can turn biometrics back on in Settings. + Upplåsning kräver ditt huvudlösenord. Du kan aktivera biometri igen i Inställningar. diff --git a/ui/src/main/res/values-uk-rUA/strings.xml b/ui/src/main/res/values-uk-rUA/strings.xml index 0820dbe1ce..735fc04db5 100644 --- a/ui/src/main/res/values-uk-rUA/strings.xml +++ b/ui/src/main/res/values-uk-rUA/strings.xml @@ -1464,10 +1464,10 @@ Використати цю парольну фразу Використати цей пароль Використати це ім\'я користувача - Create login - Продовжуйте розблоковувати за допомогою біометрії - Ми внесли оновлення безпеки. Підтвердьте свої біометричні дані ще раз, щоб і надалі використовувати їх для розблокування Bitwarden. + Створити запис входу + Надалі розблоковувати за допомогою біометрії + В нещодавньому оновленні вдосконалено безпеку. Підтвердьте свої біометричні дані ще раз, щоб і надалі використовувати їх для розблокування Bitwarden. Вимкнути - Вимкнути біометрику? - Для розблокування знадобиться ваш головний пароль. Ви можете знову увімкнути біометричну аутентифікацію в розділі «Налаштування». + Вимкнути біометрію? + Для розблокування знадобиться ваш головний пароль. Ви можете знову ввімкнути біометрію в налаштуваннях. diff --git a/ui/src/main/res/values-zh-rCN/strings.xml b/ui/src/main/res/values-zh-rCN/strings.xml index 3a92454014..1d6350d698 100644 --- a/ui/src/main/res/values-zh-rCN/strings.xml +++ b/ui/src/main/res/values-zh-rCN/strings.xml @@ -838,7 +838,7 @@ 保存和保护您的数据 密码库不仅保护密码。在这里还可以安全地存储登录、ID、支付卡和笔记。 添加登录 - 在任何平台上安全地与任何人共享文件和数据。您的信息将在限制曝光的同时保持端到端加密。 + 在任何平台上与任何人安全地分享文件和数据。您的信息将在限制暴露的同时保持端到端加密。 安全地发送敏感信息 导入已保存的登录信息 使用计算机从现有密码管理器导入登录信息