From e90c41a3b7f3310fbcbef4e8102399245e8b278a Mon Sep 17 00:00:00 2001 From: Patrick Honkonen <1883101+SaintPatrck@users.noreply.github.com> Date: Tue, 3 Sep 2024 09:05:20 -0400 Subject: [PATCH] [BWA-61] Fix pre-existing detekt issues (#179) --- app/build.gradle.kts | 2 +- .../authenticator/AuthenticatorApplication.kt | 3 + .../bitwarden/authenticator/MainActivity.kt | 13 +- .../bitwarden/authenticator/MainViewModel.kt | 5 +- .../auth/repository/AuthRepositoryImpl.kt | 3 + .../repository/di/AuthRepositoryModule.kt | 2 +- .../disk/AuthenticatorDiskSource.kt | 1 - .../disk/AuthenticatorDiskSourceImpl.kt | 5 +- .../disk/database/AuthenticatorDatabase.kt | 3 +- .../disk/di/AuthenticatorDiskModule.kt | 2 +- .../disk/entity/AuthenticatorItemAlgorithm.kt | 3 + .../disk/entity/AuthenticatorItemEntity.kt | 4 + .../disk/entity/AuthenticatorItemType.kt | 6 +- .../datasource/sdk/AuthenticatorSdkSource.kt | 3 + .../sdk/AuthenticatorSdkSourceImpl.kt | 8 +- .../sdk/di/AuthenticatorSdkModule.kt | 1 - .../authenticator/manager/FileManagerImpl.kt | 3 + .../authenticator/manager/TotpCodeManager.kt | 3 + .../manager/di/AuthenticatorManagerModule.kt | 5 +- .../repository/AuthenticatorRepository.kt | 14 +- .../repository/AuthenticatorRepositoryImpl.kt | 50 +-- .../di/AuthenticatorRepositoryModule.kt | 1 - .../repository/model/ExportDataResult.kt | 7 +- .../datasource/disk/SettingsDiskSourceImpl.kt | 9 +- .../datasource/disk/di/PlatformDiskModule.kt | 3 + .../network/di/PlatformNetworkModule.kt | 4 + .../platform/manager/imports/ImportManager.kt | 1 - .../manager/imports/ImportManagerImpl.kt | 18 +- .../manager/imports/model/AegisJsonExport.kt | 15 + .../manager/imports/model/ImportDataResult.kt | 8 + .../imports/model/LastPassJsonExport.kt | 15 + .../manager/imports/model/TwoFasJsonExport.kt | 27 ++ .../imports/parsers/AegisExportParser.kt | 4 + .../imports/parsers/BitwardenExportParser.kt | 6 +- .../imports/parsers/TwoFasExportParser.kt | 3 +- .../platform/manager/model/FeatureFlag.kt | 4 +- .../repository/FeatureFlagRepositoryImpl.kt | 6 +- .../ui/auth/unlock/UnlockScreen.kt | 10 +- .../ui/auth/unlock/UnlockViewModel.kt | 5 +- .../authenticator/AuthenticatorNavigation.kt | 6 +- .../feature/edititem/EditItemScreen.kt | 60 +-- .../feature/edititem/EditItemViewModel.kt | 48 ++- .../itemlisting/ItemListingGraphNavigation.kt | 9 +- .../feature/itemlisting/ItemListingScreen.kt | 37 +- .../itemlisting/ItemListingViewModel.kt | 125 +++--- .../itemlisting/VaultVerificationCodeItem.kt | 12 +- .../model/ItemListingExpandableFabAction.kt | 14 +- .../util/VerificationCodeItemExtensions.kt | 5 +- .../manualcodeentry/ManualCodeEntryScreen.kt | 8 +- .../ManualCodeEntryViewModel.kt | 78 ++-- .../navbar/AuthenticatorNavBarNavigation.kt | 3 +- .../navbar/AuthenticatorNavBarScreen.kt | 3 +- .../qrcodescan/util/QrCodeAnalyzerImpl.kt | 3 +- .../feature/search/ItemSearchNavigation.kt | 2 +- .../feature/search/ItemSearchScreen.kt | 6 +- .../feature/search/ItemSearchViewModel.kt | 20 +- .../search/VaultVerificationCodeItem.kt | 4 +- .../feature/search/handlers/SearchHandlers.kt | 6 +- .../fab/ExpandableFloatingActionButton.kt | 34 +- .../platform/feature/rootnav/RootNavScreen.kt | 3 +- .../feature/rootnav/RootNavViewModel.kt | 17 +- .../feature/settings/SettingsNavigation.kt | 10 +- .../feature/settings/SettingsScreen.kt | 31 +- .../feature/settings/SettingsViewModel.kt | 22 +- .../settings/export/ExportNavigation.kt | 9 + .../feature/settings/export/ExportScreen.kt | 24 +- .../settings/export/ExportViewModel.kt | 70 +++- .../export/model/ExportVaultFormat.kt | 2 +- .../settings/importing/ImportingScreen.kt | 16 +- .../settings/importing/ImportingViewModel.kt | 6 +- .../feature/tutorial/TutorialNavigation.kt | 12 + .../feature/tutorial/TutorialScreen.kt | 27 +- .../feature/tutorial/TutorialViewModel.kt | 6 +- .../ui/platform/theme/AuthenticatorTheme.kt | 3 + .../platform/util/ExportFormatExtensions.kt | 14 +- .../authenticator/MainViewModelTest.kt | 5 - .../disk/FeatureFlagDiskSourceTest.kt | 9 +- .../serializer/ZonedDateTimeSerializerTest.kt | 1 - .../manager/FeatureFlagManagerTest.kt | 4 +- .../repository/FeatureFlagRepositoryTest.kt | 4 +- .../util/FakeFeatureFlagRepository.kt | 4 +- .../ui/platform/base/BaseViewModelTest.kt | 1 - detekt-baseline.xml | 373 ------------------ 83 files changed, 654 insertions(+), 787 deletions(-) delete mode 100644 detekt-baseline.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9878691fb8..1a53bbb800 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -151,8 +151,8 @@ dependencies { } detekt { + autoCorrect = true config.from(files("$rootDir/detekt-config.yml")) - baseline = file("$rootDir/detekt-baseline.xml") } kover { diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/AuthenticatorApplication.kt b/app/src/main/kotlin/com/bitwarden/authenticator/AuthenticatorApplication.kt index 84de75544a..a3bad42e55 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/AuthenticatorApplication.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/AuthenticatorApplication.kt @@ -5,6 +5,9 @@ import com.bitwarden.authenticator.data.platform.manager.CrashLogsManager import dagger.hilt.android.HiltAndroidApp import javax.inject.Inject +/** + * Custom application class. + */ @HiltAndroidApp class AuthenticatorApplication : Application() { // Inject classes here that must be triggered on startup but are not otherwise consumed by diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt b/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt index 28ca0d1dcb..c27d56d309 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/MainActivity.kt @@ -17,6 +17,9 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +/** + * Primary entry point for the application. + */ @AndroidEntryPoint class MainActivity : AppCompatActivity() { @@ -33,8 +36,8 @@ class MainActivity : AppCompatActivity() { if (savedInstanceState == null) { mainViewModel.trySendAction( MainAction.ReceiveFirstIntent( - intent = intent - ) + intent = intent, + ), ) } @@ -42,11 +45,11 @@ class MainActivity : AppCompatActivity() { val state by mainViewModel.stateFlow.collectAsStateWithLifecycle() AuthenticatorTheme( - theme = state.theme + theme = state.theme, ) { RootNavScreen( onSplashScreenRemoved = { shouldShowSplashScreen = false }, - onExitApplication = { finishAffinity() } + onExitApplication = { finishAffinity() }, ) } } @@ -56,7 +59,7 @@ class MainActivity : AppCompatActivity() { super.onNewIntent(intent) sanitizeIntent() mainViewModel.trySendAction( - MainAction.ReceiveNewIntent(intent = intent) + MainAction.ReceiveNewIntent(intent = intent), ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt index 67b92cc96d..7174ee9747 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/MainViewModel.kt @@ -21,8 +21,8 @@ class MainViewModel @Inject constructor( settingsRepository: SettingsRepository, ) : BaseViewModel( MainState( - theme = settingsRepository.appTheme - ) + theme = settingsRepository.appTheme, + ), ) { init { @@ -71,7 +71,6 @@ class MainViewModel @Inject constructor( ) { // RFU } - } /** diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/AuthRepositoryImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/AuthRepositoryImpl.kt index c34425f5f3..a40ef11792 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/AuthRepositoryImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/AuthRepositoryImpl.kt @@ -4,6 +4,9 @@ import android.os.SystemClock import com.bitwarden.authenticator.data.auth.datasource.disk.AuthDiskSource import javax.inject.Inject +/** + * Default implementation of [AuthRepository]. + */ class AuthRepositoryImpl @Inject constructor( private val authDiskSource: AuthDiskSource, private val elapsedRealtimeMillisProvider: () -> Long = { SystemClock.elapsedRealtime() }, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/di/AuthRepositoryModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/di/AuthRepositoryModule.kt index b6939bee2f..96bfb403a1 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/di/AuthRepositoryModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/auth/repository/di/AuthRepositoryModule.kt @@ -19,6 +19,6 @@ object AuthRepositoryModule { fun provideAuthRepository( authDiskSource: AuthDiskSource, ): AuthRepository = AuthRepositoryImpl( - authDiskSource = authDiskSource + authDiskSource = authDiskSource, ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSource.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSource.kt index b1c3933c3b..c768879952 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSource.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSource.kt @@ -22,5 +22,4 @@ interface AuthenticatorDiskSource { * Deletes an authenticator item from the data source with the given [itemId]. */ suspend fun deleteItem(itemId: String) - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSourceImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSourceImpl.kt index 36cddaa773..031aad8344 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/AuthenticatorDiskSourceImpl.kt @@ -7,6 +7,9 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.merge import javax.inject.Inject +/** + * Default implementation of [AuthenticatorDiskSource]. + */ class AuthenticatorDiskSourceImpl @Inject constructor( private val itemDao: ItemDao, ) : AuthenticatorDiskSource { @@ -19,7 +22,7 @@ class AuthenticatorDiskSourceImpl @Inject constructor( override fun getItems(): Flow> = merge( forceItemsFlow, - itemDao.getAllItems() + itemDao.getAllItems(), ) override suspend fun deleteItem(itemId: String) { diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt index be886d7c44..c5a7a41555 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/database/AuthenticatorDatabase.kt @@ -14,7 +14,7 @@ import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.Aut */ @Database( entities = [ - AuthenticatorItemEntity::class + AuthenticatorItemEntity::class, ], autoMigrations = [ AutoMigration(from = 1, to = 2), @@ -32,5 +32,4 @@ abstract class AuthenticatorDatabase : RoomDatabase() { * Provide the DAO for accessing authenticator item data. */ abstract fun itemDao(): ItemDao - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt index 7f0db020e2..0deba43630 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/di/AuthenticatorDiskModule.kt @@ -28,7 +28,7 @@ object AuthenticatorDiskModule { .databaseBuilder( context = app, klass = AuthenticatorDatabase::class.java, - name = "authenticator_database" + name = "authenticator_database", ) .fallbackToDestructiveMigration() .addTypeConverter(AuthenticatorItemTypeConverter()) diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemAlgorithm.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemAlgorithm.kt index 5a36af1017..b9a06c3b58 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemAlgorithm.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemAlgorithm.kt @@ -22,6 +22,9 @@ enum class AuthenticatorItemAlgorithm { ; companion object { + /** + * Returns a [AuthenticatorItemAlgorithm] with a name matching [value], or null. + */ fun fromStringOrNull(value: String): AuthenticatorItemAlgorithm? = entries.find { it.name.equals(value, ignoreCase = true) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt index 67d449ee4e..d761faed53 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemEntity.kt @@ -42,6 +42,10 @@ data class AuthenticatorItemEntity( @ColumnInfo(name = "favorite", defaultValue = "0") val favorite: Boolean, ) { + /** + * Returns the OTP data in a string formatted to match the Google Authenticator specification, + * https://github.com/google/google-authenticator/wiki/Key-Uri-Format + */ fun toOtpAuthUriString(): String { return when (type) { AuthenticatorItemType.TOTP -> { diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemType.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemType.kt index 7e8fd89fd1..7d6a7be7f0 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemType.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/disk/entity/AuthenticatorItemType.kt @@ -1,7 +1,7 @@ package com.bitwarden.authenticator.data.authenticator.datasource.disk.entity /** - * Enum representing the main type options for authenticator items. + * Enum representing the supported "type" options for authenticator items. */ enum class AuthenticatorItemType { @@ -17,6 +17,10 @@ enum class AuthenticatorItemType { ; companion object { + + /** + * Returns the [AuthenticatorItemType] matching [value], or null. + */ fun fromStringOrNull(value: String): AuthenticatorItemType? = entries.find { it.name.equals(value, ignoreCase = true) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSource.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSource.kt index fbf3df5ab4..42042b17e1 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSource.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSource.kt @@ -16,5 +16,8 @@ interface AuthenticatorSdkSource { time: DateTime, ): Result + /** + * Generate a random key for seeding biometrics. + */ suspend fun generateBiometricsKey(): Result } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSourceImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSourceImpl.kt index 1e7c3f676a..b795a5821c 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSourceImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/AuthenticatorSdkSourceImpl.kt @@ -7,6 +7,9 @@ import com.bitwarden.generators.PasswordGeneratorRequest import com.bitwarden.sdk.Client import javax.inject.Inject +/** + * Default implementation of [AuthenticatorSdkSource]. + */ class AuthenticatorSdkSourceImpl @Inject constructor( private val sdkClientManager: SdkClientManager, ) : AuthenticatorSdkSource { @@ -38,11 +41,10 @@ class AuthenticatorSdkSourceImpl @Inject constructor( minLowercase = null, minUppercase = null, minNumber = null, - minSpecial = null - ) + minSpecial = null, + ), ) } private suspend fun getClient(): Client = sdkClientManager.getOrCreateClient() - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/di/AuthenticatorSdkModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/di/AuthenticatorSdkModule.kt index 7dd0d581cc..0557fd33a3 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/di/AuthenticatorSdkModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/datasource/sdk/di/AuthenticatorSdkModule.kt @@ -21,5 +21,4 @@ object AuthenticatorSdkModule { fun provideAuthenticatorSdkSource( sdkClientManager: SdkClientManager, ): AuthenticatorSdkSource = AuthenticatorSdkSourceImpl(sdkClientManager) - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/FileManagerImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/FileManagerImpl.kt index 3d3cecb214..821c5ebf64 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/FileManagerImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/FileManagerImpl.kt @@ -11,6 +11,9 @@ import java.io.ByteArrayOutputStream */ private const val BUFFER_SIZE: Int = 1024 +/** + * Manages reading and writing files. + */ class FileManagerImpl( private val context: Context, private val dispatcherManager: DispatcherManager, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt index 03226dda24..c626c0712d 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/TotpCodeManager.kt @@ -40,8 +40,11 @@ interface TotpCodeManager { const val STEAM_CODE_PREFIX = "steam://" const val GOOGLE_EXPORT_PREFIX = "otpauth-migration://" const val TOTP_DIGITS_DEFAULT = 6 + const val TOTP_DIGITS_MIN = 5 + const val TOTP_DIGITS_MAX = 10 const val STEAM_DIGITS_DEFAULT = 5 const val PERIOD_SECONDS_DEFAULT = 30 + val TOTP_DIGITS_RANGE = TOTP_DIGITS_MIN..TOTP_DIGITS_MAX val ALGORITHM_DEFAULT = AuthenticatorItemAlgorithm.SHA1 } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/di/AuthenticatorManagerModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/di/AuthenticatorManagerModule.kt index b9d7af15c7..8c2518bb22 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/di/AuthenticatorManagerModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/manager/di/AuthenticatorManagerModule.kt @@ -27,7 +27,7 @@ object AuthenticatorManagerModule { fun providerFileManager( @ApplicationContext context: Context, dispatcherManager: DispatcherManager, - ) : FileManager = FileManagerImpl( + ): FileManager = FileManagerImpl( context = context, dispatcherManager = dispatcherManager, ) @@ -41,7 +41,6 @@ object AuthenticatorManagerModule { ): TotpCodeManager = TotpCodeManagerImpl( authenticatorSdkSource = authenticatorSdkSource, dispatcherManager = dispatcherManager, - clock = clock + clock = clock, ) - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt index 2408d722d3..ba2600ff53 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepository.kt @@ -8,12 +8,10 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.CreateIte import com.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult import com.bitwarden.authenticator.data.authenticator.repository.model.ExportDataResult import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult -import com.bitwarden.authenticator.data.authenticator.repository.model.UpdateItemRequest -import com.bitwarden.authenticator.data.authenticator.repository.model.UpdateItemResult import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportDataResult import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportFileFormat import com.bitwarden.authenticator.data.platform.repository.model.DataState -import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportFormat +import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -82,18 +80,10 @@ interface AuthenticatorRepository { */ suspend fun hardDeleteItem(itemId: String): DeleteItemResult - /** - * Attempt to update a cipher. - */ - suspend fun updateItem( - itemId: String, - updateItemRequest: UpdateItemRequest, - ): UpdateItemResult - /** * Attempt to get the user's data for export. */ - suspend fun exportVaultData(format: ExportFormat, fileUri: Uri): ExportDataResult + suspend fun exportVaultData(format: ExportVaultFormat, fileUri: Uri): ExportDataResult /** * Attempt to read the user's data from a file diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt index 9570a2d144..08497cde9f 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt @@ -12,8 +12,6 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.CreateIte import com.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult import com.bitwarden.authenticator.data.authenticator.repository.model.ExportDataResult import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult -import com.bitwarden.authenticator.data.authenticator.repository.model.UpdateItemRequest -import com.bitwarden.authenticator.data.authenticator.repository.model.UpdateItemResult import com.bitwarden.authenticator.data.platform.manager.DispatcherManager import com.bitwarden.authenticator.data.platform.manager.imports.ImportManager import com.bitwarden.authenticator.data.platform.manager.imports.model.ImportDataResult @@ -22,7 +20,7 @@ import com.bitwarden.authenticator.data.platform.repository.model.DataState import com.bitwarden.authenticator.data.platform.repository.util.bufferedMutableSharedFlow import com.bitwarden.authenticator.data.platform.repository.util.combineDataStates import com.bitwarden.authenticator.data.platform.repository.util.map -import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportFormat +import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -50,6 +48,10 @@ import javax.inject.Inject */ private const val STOP_TIMEOUT_DELAY_MS: Long = 5_000L +/** + * Default implementation of [AuthenticatorRepository]. + */ +@Suppress("TooManyFunctions") class AuthenticatorRepositoryImpl @Inject constructor( private val authenticatorDiskSource: AuthenticatorDiskSource, private val totpCodeManager: TotpCodeManager, @@ -188,6 +190,7 @@ class AuthenticatorRepositoryImpl @Inject constructor( mutableTotpCodeResultFlow.tryEmit(totpCodeResult) } + @Suppress("TooGenericExceptionCaught") override suspend fun createItem(item: AuthenticatorItemEntity): CreateItemResult { return try { authenticatorDiskSource.saveItem(item) @@ -197,6 +200,7 @@ class AuthenticatorRepositoryImpl @Inject constructor( } } + @Suppress("TooGenericExceptionCaught") override suspend fun addItems(vararg items: AuthenticatorItemEntity): CreateItemResult { return try { authenticatorDiskSource.saveItem(*items) @@ -206,6 +210,7 @@ class AuthenticatorRepositoryImpl @Inject constructor( } } + @Suppress("TooGenericExceptionCaught") override suspend fun hardDeleteItem(itemId: String): DeleteItemResult { return try { authenticatorDiskSource.deleteItem(itemId) @@ -215,34 +220,13 @@ class AuthenticatorRepositoryImpl @Inject constructor( } } - override suspend fun updateItem( - itemId: String, - updateItemRequest: UpdateItemRequest, - ): UpdateItemResult { - return try { - authenticatorDiskSource.saveItem( - AuthenticatorItemEntity( - id = itemId, - key = updateItemRequest.key, - accountName = updateItemRequest.accountName, - type = updateItemRequest.type, - period = updateItemRequest.period, - digits = updateItemRequest.digits, - issuer = updateItemRequest.issuer, - userId = null, - favorite = updateItemRequest.favorite - ), - ) - UpdateItemResult.Success - } catch (e: Exception) { - UpdateItemResult.Error(e.message) - } - } - - override suspend fun exportVaultData(format: ExportFormat, fileUri: Uri): ExportDataResult { + override suspend fun exportVaultData( + format: ExportVaultFormat, + fileUri: Uri, + ): ExportDataResult { return when (format) { - ExportFormat.JSON -> encodeVaultDataToJson(fileUri) - ExportFormat.CSV -> encodeVaultDataToCsv(fileUri) + ExportVaultFormat.JSON -> encodeVaultDataToJson(fileUri) + ExportVaultFormat.CSV -> encodeVaultDataToCsv(fileUri) } } @@ -254,12 +238,12 @@ class AuthenticatorRepositoryImpl @Inject constructor( importManager .import( importFileFormat = format, - byteArray = it + byteArray = it, ) } .fold( onSuccess = { it }, - onFailure = { ImportDataResult.Error() } + onFailure = { ImportDataResult.Error() }, ) private suspend fun encodeVaultDataToCsv(fileUri: Uri): ExportDataResult { @@ -292,7 +276,7 @@ class AuthenticatorRepositoryImpl @Inject constructor( .firstOrNull() .orEmpty() .map { it.toExportJsonItem() }, - ) + ), ) return if ( diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/di/AuthenticatorRepositoryModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/di/AuthenticatorRepositoryModule.kt index 59c9581f94..4611f36543 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/di/AuthenticatorRepositoryModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/di/AuthenticatorRepositoryModule.kt @@ -35,5 +35,4 @@ object AuthenticatorRepositoryModule { importManager, dispatcherManager, ) - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/ExportDataResult.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/ExportDataResult.kt index b3149c2de0..55f35ee16f 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/ExportDataResult.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/model/ExportDataResult.kt @@ -5,8 +5,13 @@ package com.bitwarden.authenticator.data.authenticator.repository.model */ sealed class ExportDataResult { + /** + * Data has been successfully exported. + */ data object Success : ExportDataResult() + /** + * Data could not be exported. + */ data object Error : ExportDataResult() - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt index a003b9654d..c90570237b 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/SettingsDiskSourceImpl.kt @@ -16,6 +16,7 @@ private const val ALERT_THRESHOLD_SECONDS_KEY = "$BASE_KEY:alertThresholdSeconds private const val FIRST_LAUNCH_KEY = "$BASE_KEY:hasSeenWelcomeTutorial" private const val CRASH_LOGGING_ENABLED_KEY = "$BASE_KEY:crashLoggingEnabled" private const val SCREEN_CAPTURE_ALLOW_KEY = "screenCaptureAllowed" +private const val DEFAULT_ALERT_THRESHOLD_SECONDS = 7 /** * Primary implementation of [SettingsDiskSource]. @@ -99,14 +100,14 @@ class SettingsDiskSourceImpl( override fun storeAlertThresholdSeconds(thresholdSeconds: Int) { putInt( ALERT_THRESHOLD_SECONDS_KEY, - thresholdSeconds + thresholdSeconds, ) mutableAlertThresholdSecondsFlow.tryEmit(thresholdSeconds) } - override fun getAlertThresholdSeconds(): Int { - return getInt(ALERT_THRESHOLD_SECONDS_KEY, default = 7) ?: 7 - } + override fun getAlertThresholdSeconds() = + getInt(ALERT_THRESHOLD_SECONDS_KEY, default = DEFAULT_ALERT_THRESHOLD_SECONDS) + ?: DEFAULT_ALERT_THRESHOLD_SECONDS override fun getAlertThresholdSecondsFlow(): Flow = mutableAlertThresholdSecondsFlow .onSubscription { emit(getAlertThresholdSeconds()) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/di/PlatformDiskModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/di/PlatformDiskModule.kt index a42e4be54f..4530311b14 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/di/PlatformDiskModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/disk/di/PlatformDiskModule.kt @@ -15,6 +15,9 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) +/** + * Provides persistence-related dependencies in the platform package. + */ object PlatformDiskModule { @Provides diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/di/PlatformNetworkModule.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/di/PlatformNetworkModule.kt index ea50af46a2..caceb8dd4a 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/di/PlatformNetworkModule.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/datasource/network/di/PlatformNetworkModule.kt @@ -12,6 +12,10 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) +/** + * This class provides network-related functionality for the application. + * It initializes and configures the networking components. + */ object PlatformNetworkModule { @Provides @Singleton diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManager.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManager.kt index 5f0bd2d239..6a58e1b624 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManager.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManager.kt @@ -15,5 +15,4 @@ interface ImportManager { importFileFormat: ImportFileFormat, byteArray: ByteArray, ): ImportDataResult - } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManagerImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManagerImpl.kt index f96db0ad22..e178d6e06a 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManagerImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/ImportManagerImpl.kt @@ -29,19 +29,15 @@ class ImportManagerImpl( ImportFileFormat.AEGIS -> AegisExportParser() } - return try { - when (val parseResult = parser.parse(byteArray)) { - is ExportParseResult.Error -> { - ImportDataResult.Error(parseResult.message) - } + return when (val parseResult = parser.parse(byteArray)) { + is ExportParseResult.Error -> { + ImportDataResult.Error(parseResult.message) + } - is ExportParseResult.Success -> { - authenticatorDiskSource.saveItem(*parseResult.items.toTypedArray()) - ImportDataResult.Success - } + is ExportParseResult.Success -> { + authenticatorDiskSource.saveItem(*parseResult.items.toTypedArray()) + ImportDataResult.Success } - } catch (e: Throwable) { - ImportDataResult.Error() } } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/AegisJsonExport.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/AegisJsonExport.kt index 2ac9452b8b..fcaf15c04c 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/AegisJsonExport.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/AegisJsonExport.kt @@ -2,12 +2,18 @@ package com.bitwarden.authenticator.data.platform.manager.imports.model import kotlinx.serialization.Serializable +/** + * Models the Aegis JSON export file. + */ @Serializable data class AegisJsonExport( val version: Int, val db: Database, ) { + /** + * Models the Aegis database in JSON format. + */ @Serializable data class Database( val version: Int, @@ -15,6 +21,9 @@ data class AegisJsonExport( val groups: List, ) { + /** + * Models an Aegis database entry. + */ @Serializable data class Entry( val type: String, @@ -27,6 +36,9 @@ data class AegisJsonExport( val groups: List, ) { + /** + * Models key information for an [Entry]. + */ @Serializable data class Info( val secret: String, @@ -36,6 +48,9 @@ data class AegisJsonExport( ) } + /** + * Models a collection that can be associated with an [Entry]. + */ @Serializable data class Group( val uuid: String, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/ImportDataResult.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/ImportDataResult.kt index 9c21236b72..6ab5dc7406 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/ImportDataResult.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/ImportDataResult.kt @@ -6,7 +6,15 @@ import com.bitwarden.authenticator.ui.platform.base.util.Text * Represents the result of a data import operation. */ sealed class ImportDataResult { + /** + * Indicates import was successful. + */ data object Success : ImportDataResult() + /** + * Indicates import was not successful. + * + * @property message Optional [Text] indicating why the import failed. + */ data class Error(val message: Text? = null) : ImportDataResult() } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/LastPassJsonExport.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/LastPassJsonExport.kt index 23f3aed329..0fc0afe3dd 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/LastPassJsonExport.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/LastPassJsonExport.kt @@ -3,6 +3,9 @@ package com.bitwarden.authenticator.data.platform.manager.imports.model import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +/** + * Models the JSON export file from LastPass. + */ @Serializable data class LastPassJsonExport( val deviceId: String, @@ -13,6 +16,9 @@ data class LastPassJsonExport( val accounts: List, val folders: List, ) { + /** + * Models an account contained within a [LastPassJsonExport]. + */ @Serializable data class Account( @SerialName("accountID") @@ -31,12 +37,18 @@ data class LastPassJsonExport( val folderData: FolderData?, val backupInfo: BackupInfo?, ) { + /** + * Models metadata for a [Folder]. + */ @Serializable data class FolderData( val folderId: String, val position: Int, ) + /** + * Models backup file information for an [Account]. + */ @Serializable data class BackupInfo( val creationDate: String, @@ -45,6 +57,9 @@ data class LastPassJsonExport( ) } + /** + * Models a collection of [Account] objects. + */ @Serializable data class Folder( val id: Int, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/TwoFasJsonExport.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/TwoFasJsonExport.kt index c3b0311865..476bb26326 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/TwoFasJsonExport.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/model/TwoFasJsonExport.kt @@ -2,6 +2,9 @@ package com.bitwarden.authenticator.data.platform.manager.imports.model import kotlinx.serialization.Serializable +/** + * Models the JSON export file from 2FAS. + */ @Serializable data class TwoFasJsonExport( val schemaVersion: Int?, @@ -11,6 +14,9 @@ data class TwoFasJsonExport( val servicesEncrypted: String?, val groups: List?, ) { + /** + * Models a service account contained within a [TwoFasJsonExport]. + */ @Serializable data class Service( val otp: Otp, @@ -22,6 +28,9 @@ data class TwoFasJsonExport( val badge: Badge?, val serviceTypeId: String?, ) { + /** + * Models OTP auth data for a 2fas [Service]. + */ @Serializable data class Otp( val counter: Int?, @@ -35,22 +44,34 @@ data class TwoFasJsonExport( val issuer: String?, ) + /** + * Models ordinal information for a 2fas [Service]. + */ @Serializable data class Order( val position: Int?, ) + /** + * Models the icon for a 2fas [Service]. + */ @Serializable data class Icon( val iconCollection: IconCollection?, val label: Label?, val selected: String?, ) { + /** + * Models a collection that can be associated to a 2fas [Icon]. + */ @Serializable data class IconCollection( val id: String?, ) + /** + * Models label data for a 2fas [Icon]. + */ @Serializable data class Label( val backgroundColor: String?, @@ -58,12 +79,18 @@ data class TwoFasJsonExport( ) } + /** + * Models badge data about a 2fas [Service]. + */ @Serializable data class Badge( val color: String, ) } + /** + * Models a collection of 2fas [Service] objects. + */ @Serializable data class Group( val id: String, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/AegisExportParser.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/AegisExportParser.kt index 2b67db9e8b..25e446af28 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/AegisExportParser.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/AegisExportParser.kt @@ -13,6 +13,9 @@ import java.io.ByteArrayInputStream import java.io.IOException import java.util.UUID +/** + * Implementation of [ExportParser] responsible for parsing exports from the Aegis application. + */ class AegisExportParser : ExportParser { @OptIn(ExperimentalSerializationApi::class) override fun parse(byteArray: ByteArray): ExportParseResult { @@ -43,6 +46,7 @@ class AegisExportParser : ExportParser { private fun List.toAuthenticatorItemEntities() = map { it.toAuthenticatorItemEntity() } + @Suppress("MaxLineLength") private fun AegisJsonExport.Database.Entry.toAuthenticatorItemEntity(): AuthenticatorItemEntity { // Aegis only supports TOTP codes. diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt index 1ca2cd85b1..47e6f4f4aa 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt @@ -17,6 +17,9 @@ import kotlinx.serialization.json.decodeFromStream import java.io.ByteArrayInputStream import java.io.IOException +/** + * Implementation of [ExportParser] responsible for parsing exports from the Bitwarden application. + */ class BitwardenExportParser( private val fileFormat: ImportFileFormat, ) : ExportParser { @@ -42,7 +45,7 @@ class BitwardenExportParser( items = exportData .items .filter { it.login?.totp != null } - .toAuthenticatorItemEntities() + .toAuthenticatorItemEntities(), ) } catch (e: SerializationException) { ExportParseResult.Error() @@ -56,6 +59,7 @@ class BitwardenExportParser( private fun List.toAuthenticatorItemEntities() = map { it.toAuthenticatorItemEntity() } + @Suppress("MaxLineLength", "CyclomaticComplexMethod", "LongMethod") private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity { val otpString = requireNotNull(login?.totp) diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/TwoFasExportParser.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/TwoFasExportParser.kt index 8f8a676980..78dc5aa1c4 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/TwoFasExportParser.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/TwoFasExportParser.kt @@ -45,7 +45,7 @@ class TwoFasExportParser : ExportParser { ) } else { ExportParseResult.Success( - items = exportData.services.toAuthenticatorItemEntities() + items = exportData.services.toAuthenticatorItemEntities(), ) } } catch (e: SerializationException) { @@ -60,6 +60,7 @@ class TwoFasExportParser : ExportParser { private fun List.toAuthenticatorItemEntities() = mapNotNull { it.toAuthenticatorItemEntityOrNull() } + @Suppress("MaxLineLength") private fun TwoFasJsonExport.Service.toAuthenticatorItemEntityOrNull(): AuthenticatorItemEntity { val type = otp.tokenType diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/model/FeatureFlag.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/model/FeatureFlag.kt index 0f2a259316..6309efad8f 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/model/FeatureFlag.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/model/FeatureFlag.kt @@ -4,7 +4,8 @@ package com.bitwarden.authenticator.data.platform.manager.model * Represents feature flags used by the application. * * @property name The string value of the given key. - * @property defaultValue The value to be used if the flags value cannot be determined or is not remotely configured. + * @property defaultValue The value to be used if the flags value cannot be determined or is not + * remotely configured. * @property isRemotelyConfigured Indicates if the flag should respect the network value or not. */ sealed class FeatureFlag( @@ -37,4 +38,3 @@ sealed class RemoteFeatureFlag( name: String, defaultValue: T, ) : FeatureFlag(name, defaultValue, isRemotelyConfigured = true) - diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryImpl.kt index 333fd410a9..32363ad301 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryImpl.kt @@ -37,9 +37,9 @@ class FeatureFlagRepositoryImpl( val config = FeatureFlagsConfiguration( mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive( - LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue - ) - ) + LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue, + ), + ), ) featureFlagDiskSource.featureFlagsConfiguration = config return config diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt index 1f80ebc999..e1f73479ca 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockScreen.kt @@ -39,6 +39,10 @@ import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaf import com.bitwarden.authenticator.ui.platform.manager.biometrics.BiometricsManager import com.bitwarden.authenticator.ui.platform.theme.LocalBiometricsManager +/** + * Top level composable for the unlock screen. + */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun UnlockScreen( @@ -66,7 +70,7 @@ fun UnlockScreen( is UnlockState.Dialog.Error -> BitwardenBasicDialog( visibilityState = BasicDialogState.Shown( title = R.string.an_error_has_occurred.asText(), - message = dialog.message + message = dialog.message, ), onDismissRequest = remember(viewModel) { { @@ -76,7 +80,7 @@ fun UnlockScreen( ) UnlockState.Dialog.Loading -> BitwardenLoadingDialog( - visibilityState = LoadingDialogState.Shown(R.string.loading.asText()) + visibilityState = LoadingDialogState.Shown(R.string.loading.asText()), ) null -> Unit @@ -118,7 +122,7 @@ fun UnlockScreen( .padding(innerPadding) .fillMaxSize(), verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally + horizontalAlignment = Alignment.CenterHorizontally, ) { Image( modifier = Modifier diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockViewModel.kt index 433aeaab80..1bcb32f2d3 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/auth/unlock/UnlockViewModel.kt @@ -33,7 +33,7 @@ class UnlockViewModel @Inject constructor( isBiometricsValid = biometricsEncryptionManager.isBiometricIntegrityValid(), dialog = null, ) - } + }, ) { init { @@ -74,7 +74,7 @@ class UnlockViewModel @Inject constructor( it.copy( dialog = UnlockState.Dialog.Error( message = R.string.too_many_failed_biometric_attempts.asText(), - ) + ), ) } } @@ -125,7 +125,6 @@ sealed class UnlockEvent { data class ShowToast( val message: Text, ) : UnlockEvent() - } /** diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt index df22230e6c..683b6de821 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/authenticator/AuthenticatorNavigation.kt @@ -33,7 +33,7 @@ fun NavGraphBuilder.authenticatorGraph( ) { navigation( startDestination = AUTHENTICATOR_NAV_BAR_ROUTE, - route = AUTHENTICATOR_GRAPH_ROUTE + route = AUTHENTICATOR_GRAPH_ROUTE, ) { authenticatorNavBarDestination( onNavigateBack = onNavigateBack, @@ -43,7 +43,7 @@ fun NavGraphBuilder.authenticatorGraph( onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) }, onNavigateToExport = { navController.navigateToExport() }, onNavigateToImport = { navController.navigateToImporting() }, - onNavigateToTutorial = { navController.navigateToSettingsTutorial() } + onNavigateToTutorial = { navController.navigateToSettingsTutorial() }, ) itemListingGraph( navController = navController, @@ -62,7 +62,7 @@ fun NavGraphBuilder.authenticatorGraph( }, navigateToExport = { navController.navigateToExport() }, navigateToImport = { navController.navigateToImporting() }, - navigateToTutorial = { navController.navigateToSettingsTutorial() } + navigateToTutorial = { navController.navigateToSettingsTutorial() }, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt index 09d0d5fd4a..4d4f1923a8 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemScreen.kt @@ -72,6 +72,7 @@ import kotlinx.collections.immutable.toImmutableList /** * Displays the edit authenticator item screen. */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun EditItemScreen( @@ -94,7 +95,7 @@ fun EditItemScreen( .makeText( context, event.message(resources), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() } @@ -103,7 +104,7 @@ fun EditItemScreen( EditItemDialogs( dialogState = state.dialog, - onDismissRequest = { viewModel.trySendAction(EditItemAction.DismissDialog) } + onDismissRequest = { viewModel.trySendAction(EditItemAction.DismissDialog) }, ) BitwardenScaffold( @@ -131,7 +132,7 @@ fun EditItemScreen( }, modifier = Modifier.semantics { testTag = "SaveButton" }, ) - } + }, ) }, floatingActionButtonPosition = FabPosition.EndOverlay, @@ -146,66 +147,66 @@ fun EditItemScreen( onIssuerNameTextChange = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.IssuerNameTextChange(it) + EditItemAction.IssuerNameTextChange(it), ) } }, onUsernameTextChange = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.UsernameTextChange(it) + EditItemAction.UsernameTextChange(it), ) } }, onToggleFavorite = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.FavoriteToggleClick(it) + EditItemAction.FavoriteToggleClick(it), ) } }, onTypeOptionClicked = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.TypeOptionClick(it) + EditItemAction.TypeOptionClick(it), ) } }, onTotpCodeTextChange = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.TotpCodeTextChange(it) + EditItemAction.TotpCodeTextChange(it), ) } }, onAlgorithmOptionClicked = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.AlgorithmOptionClick(it) + EditItemAction.AlgorithmOptionClick(it), ) } }, onRefreshPeriodOptionClicked = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.RefreshPeriodOptionClick(it) + EditItemAction.RefreshPeriodOptionClick(it), ) } }, onNumberOfDigitsChanged = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.NumberOfDigitsOptionClick(it) + EditItemAction.NumberOfDigitsOptionClick(it), ) } }, onExpandAdvancedOptionsClicked = remember(viewModel) { { viewModel.trySendAction( - EditItemAction.ExpandAdvancedOptionsClick + EditItemAction.ExpandAdvancedOptionsClick, ) } - } + }, ) } @@ -218,6 +219,10 @@ fun EditItemScreen( } } +/** + * The top level content UI state for the [EditItemScreen]. + */ +@Suppress("LongMethod") @Composable fun EditItemContent( modifier: Modifier = Modifier, @@ -307,11 +312,12 @@ fun EditItemContent( onAlgorithmOptionClicked = onAlgorithmOptionClicked, onTypeOptionClicked = onTypeOptionClicked, onRefreshPeriodOptionClicked = onRefreshPeriodOptionClicked, - onNumberOfDigitsChanged = onNumberOfDigitsChanged + onNumberOfDigitsChanged = onNumberOfDigitsChanged, ) } } +@Suppress("LongMethod") @Composable private fun AdvancedOptions( modifier: Modifier = Modifier, @@ -332,7 +338,7 @@ private fun AdvancedOptions( bounded = true, color = MaterialTheme.colorScheme.primary, ), - interactionSource = remember { MutableInteractionSource() } + interactionSource = remember { MutableInteractionSource() }, ) { onExpandStateChange() }, @@ -350,21 +356,21 @@ private fun AdvancedOptions( R.drawable.ic_chevron_up } else { R.drawable.ic_chevron_down - } + }, ), contentDescription = stringResource( - id = R.string.collapse_advanced_options + id = R.string.collapse_advanced_options, ), - tint = MaterialTheme.colorScheme.primary + tint = MaterialTheme.colorScheme.primary, ) } AnimatedVisibility( visible = viewState.isAdvancedOptionsExpanded, - enter = fadeIn(tween(DEFAULT_FADE_TRANSITION_TIME_MS)) - + expandVertically(tween(DEFAULT_STAY_TRANSITION_TIME_MS)), - exit = fadeOut(tween(DEFAULT_FADE_TRANSITION_TIME_MS)) - + shrinkVertically(tween(DEFAULT_STAY_TRANSITION_TIME_MS)), + enter = fadeIn(tween(DEFAULT_FADE_TRANSITION_TIME_MS)) + + expandVertically(tween(DEFAULT_STAY_TRANSITION_TIME_MS)), + exit = fadeOut(tween(DEFAULT_FADE_TRANSITION_TIME_MS)) + + shrinkVertically(tween(DEFAULT_STAY_TRANSITION_TIME_MS)), ) { LazyColumn { item { @@ -418,7 +424,7 @@ private fun AdvancedOptions( .associateWith { stringResource( id = R.string.refresh_period_seconds, - it.seconds + it.seconds, ) } Spacer(modifier = Modifier.height(8.dp)) @@ -440,7 +446,7 @@ private fun AdvancedOptions( onRefreshPeriodOptionClicked(selectedOptionName) } - } + }, ) } @@ -468,7 +474,7 @@ private fun EditItemDialogs( BitwardenBasicDialog( visibilityState = BasicDialogState.Shown( title = dialogState.title, - message = dialogState.message + message = dialogState.message, ), onDismissRequest = onDismissRequest, ) @@ -520,7 +526,7 @@ private fun EditItemContentExpandedOptionsPreview() { ), minDigitsAllowed = 5, maxDigitsAllowed = 10, - ) + ), ) } @@ -542,6 +548,6 @@ private fun EditItemContentCollapsedOptionsPreview() { ), minDigitsAllowed = 5, maxDigitsAllowed = 10, - ) + ), ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt index c2dabce67f..11bc0925a0 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/edititem/EditItemViewModel.kt @@ -35,6 +35,7 @@ private const val KEY_STATE = "state" /** * View model responsible for handling user interaction with the edit authenticator item screen. */ +@Suppress("TooManyFunctions") @HiltViewModel class EditItemViewModel @Inject constructor( private val authenticatorRepository: AuthenticatorRepository, @@ -44,7 +45,7 @@ class EditItemViewModel @Inject constructor( itemId = EditItemArgs(savedStateHandle).itemId, viewState = EditItemState.ViewState.Loading, dialog = null, - ) + ), ) { init { @@ -76,7 +77,7 @@ class EditItemViewModel @Inject constructor( private fun handleExpandAdvancedOptionsClick() { updateContent { currentContent -> currentContent.copy( - isAdvancedOptionsExpanded = currentContent.isAdvancedOptionsExpanded.not() + isAdvancedOptionsExpanded = currentContent.isAdvancedOptionsExpanded.not(), ) } } @@ -88,7 +89,7 @@ class EditItemViewModel @Inject constructor( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), message = R.string.validation_field_required.asText(R.string.name.asText()), - ) + ), ) } return@onContent @@ -98,7 +99,7 @@ class EditItemViewModel @Inject constructor( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), message = R.string.validation_field_required.asText(R.string.key.asText()), - ) + ), ) } return@onContent @@ -107,8 +108,8 @@ class EditItemViewModel @Inject constructor( it.copy( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), - message = R.string.key_is_invalid.asText() - ) + message = R.string.key_is_invalid.asText(), + ), ) } return@onContent @@ -117,8 +118,8 @@ class EditItemViewModel @Inject constructor( mutableStateFlow.update { it.copy( dialog = EditItemState.DialogState.Loading( - R.string.saving.asText() - ) + R.string.saving.asText(), + ), ) } viewModelScope.launch { @@ -133,7 +134,7 @@ class EditItemViewModel @Inject constructor( digits = content.itemData.digits, issuer = content.itemData.issuer.trim(), favorite = content.itemData.favorite, - ) + ), ) trySendAction(EditItemAction.Internal.UpdateItemResult(result)) } @@ -142,7 +143,7 @@ class EditItemViewModel @Inject constructor( private fun handleNumberOfDigitsOptionChange(action: EditItemAction.NumberOfDigitsOptionClick) { updateItemData { currentItemData -> currentItemData.copy( - digits = action.digits + digits = action.digits, ) } } @@ -150,7 +151,7 @@ class EditItemViewModel @Inject constructor( private fun handleIssuerNameTextChange(action: EditItemAction.IssuerNameTextChange) { updateItemData { currentItemData -> currentItemData.copy( - issuer = action.issuerName + issuer = action.issuerName, ) } } @@ -158,7 +159,7 @@ class EditItemViewModel @Inject constructor( private fun handleIssuerTextChange(action: EditItemAction.UsernameTextChange) { updateItemData { currentItemData -> currentItemData.copy( - username = action.username + username = action.username, ) } } @@ -166,7 +167,7 @@ class EditItemViewModel @Inject constructor( private fun handleFavoriteToggleClick(action: EditItemAction.FavoriteToggleClick) { updateItemData { currentItemData -> currentItemData.copy( - favorite = action.favorite + favorite = action.favorite, ) } } @@ -174,7 +175,7 @@ class EditItemViewModel @Inject constructor( private fun handleTotpCodeTextChange(action: EditItemAction.TotpCodeTextChange) { updateItemData { currentItemData -> currentItemData.copy( - totpCode = action.totpCode + totpCode = action.totpCode, ) } } @@ -182,7 +183,7 @@ class EditItemViewModel @Inject constructor( private fun handlePeriodTextChange(action: EditItemAction.RefreshPeriodOptionClick) { updateItemData { currentItemData -> currentItemData.copy( - refreshPeriod = action.period + refreshPeriod = action.period, ) } } @@ -190,7 +191,7 @@ class EditItemViewModel @Inject constructor( private fun handleAlgorithmOptionClick(action: EditItemAction.AlgorithmOptionClick) { updateItemData { currentItemData -> currentItemData.copy( - algorithm = action.algorithmOption + algorithm = action.algorithmOption, ) } } @@ -202,7 +203,7 @@ class EditItemViewModel @Inject constructor( private fun handleTypeOptionClick(action: EditItemAction.TypeOptionClick) { updateItemData { currentItemData -> currentItemData.copy( - type = action.typeOption + type = action.typeOption, ) } } @@ -236,6 +237,7 @@ class EditItemViewModel @Inject constructor( } } + @Suppress("LongMethod") private fun handleItemDataReceive(action: EditItemAction.Internal.EditItemDataReceive) { when (val itemState = action.itemDataState) { is DataState.Error -> { @@ -258,7 +260,7 @@ class EditItemViewModel @Inject constructor( .data ?.toViewState(expandAdvancedOptions) ?: EditItemState.ViewState.Error( - message = R.string.generic_error_message.asText() + message = R.string.generic_error_message.asText(), ), ) } @@ -267,7 +269,7 @@ class EditItemViewModel @Inject constructor( DataState.Loading -> { mutableStateFlow.update { it.copy( - viewState = EditItemState.ViewState.Loading + viewState = EditItemState.ViewState.Loading, ) } } @@ -294,7 +296,7 @@ class EditItemViewModel @Inject constructor( .data ?.toViewState(expandAdvancedOptions) ?: EditItemState.ViewState.Error( - message = R.string.generic_error_message.asText() + message = R.string.generic_error_message.asText(), ), ) } @@ -512,6 +514,9 @@ sealed class EditItemAction { val digits: Int, ) : EditItemAction() + /** + * The user has clicked to expand advanced OTP options. + */ data object ExpandAdvancedOptionsClick : EditItemAction() /** @@ -543,6 +548,9 @@ enum class AuthenticatorRefreshPeriodOption(val seconds: Int) { ; companion object { + /** + * Returns a [AuthenticatorRefreshPeriodOption] with the provided [seconds], or null. + */ fun fromSeconds(seconds: Int) = entries.find { it.seconds == seconds } } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingGraphNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingGraphNavigation.kt index 39ba6307b9..be8e0767c2 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingGraphNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingGraphNavigation.kt @@ -17,6 +17,7 @@ const val ITEM_LISTING_GRAPH_ROUTE = "item_listing_graph" /** * Add the item listing graph to the nav graph. */ +@Suppress("LongParameterList") fun NavGraphBuilder.itemListingGraph( navController: NavController, navigateBack: () -> Unit, @@ -43,7 +44,7 @@ fun NavGraphBuilder.itemListingGraph( onNavigateBack = { navController.popBackStack() }, ) itemSearchDestination( - onNavigateBack = { navController.popBackStack() } + onNavigateBack = { navController.popBackStack() }, ) qrCodeScanDestination( onNavigateBack = { navController.popBackStack() }, @@ -57,13 +58,13 @@ fun NavGraphBuilder.itemListingGraph( onNavigateToQrCodeScreen = { navController.popBackStack() navController.navigateToQrCodeScanScreen() - } + }, ) settingsGraph( navController = navController, onNavigateToExport = navigateToExport, onNavigateToImport = navigateToImport, - onNavigateToTutorial = navigateToTutorial + onNavigateToTutorial = navigateToTutorial, ) } } @@ -76,6 +77,6 @@ fun NavController.navigateToItemListGraph( ) { navigate( route = ITEM_LISTING_GRAPH_ROUTE, - navOptions = navOptions + navOptions = navOptions, ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt index 6e0f22fc33..4bdb2e94d0 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt @@ -72,6 +72,7 @@ import com.bitwarden.authenticator.ui.platform.theme.Typography /** * Displays the item listing screen. */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemListingScreen( @@ -109,7 +110,7 @@ fun ItemListingScreen( .makeText( context, event.message(context.resources), - Toast.LENGTH_LONG + Toast.LENGTH_LONG, ) .show() } @@ -164,7 +165,7 @@ fun ItemListingScreen( onNavigateToSearch = remember(viewModel) { { viewModel.trySendAction( - ItemListingAction.SearchClick + ItemListingAction.SearchClick, ) } }, @@ -181,24 +182,24 @@ fun ItemListingScreen( onItemClick = remember(viewModel) { { viewModel.trySendAction( - ItemListingAction.ItemClick(it) + ItemListingAction.ItemClick(it), ) } }, onEditItemClick = remember(viewModel) { { viewModel.trySendAction( - ItemListingAction.EditItemClick(it) + ItemListingAction.EditItemClick(it), ) } }, onDeleteItemClick = remember(viewModel) { { viewModel.trySendAction( - ItemListingAction.DeleteItemClick(it) + ItemListingAction.DeleteItemClick(it), ) } - } + }, ) } @@ -229,7 +230,7 @@ fun ItemListingScreen( { viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) } - } + }, ) } } @@ -270,7 +271,7 @@ private fun ItemListingDialogs( onConfirmDeleteClick(dialog.itemId) }, onDismissClick = onDismissRequest, - onDismissRequest = onDismissRequest + onDismissRequest = onDismissRequest, ) } @@ -278,6 +279,7 @@ private fun ItemListingDialogs( } } +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable private fun ItemListingContent( @@ -303,7 +305,7 @@ private fun ItemListingContent( contentDescription = stringResource(id = R.string.search_codes), onClick = onNavigateToSearch, ) - } + }, ) }, floatingActionButton = { @@ -329,8 +331,8 @@ private fun ItemListingContent( contentDescription = stringResource(id = R.string.enter_key_manually), testTag = "EnterSetupKeyButton", ), - onEnterSetupKeyClick = onEnterSetupKeyClick - ) + onEnterSetupKeyClick = onEnterSetupKeyClick, + ), ), expandableFabIcon = ExpandableFabIcon( iconData = IconResource( @@ -421,13 +423,14 @@ private fun ItemListingContent( /** * Displays the item listing screen with no existing items. */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun EmptyItemListingContent( modifier: Modifier = Modifier, appTheme: AppTheme, scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior( - rememberTopAppBarState() + rememberTopAppBarState(), ), onAddCodeClick: () -> Unit, onScanQuCodeClick: () -> Unit, @@ -442,7 +445,7 @@ fun EmptyItemListingContent( title = stringResource(id = R.string.verification_codes), scrollBehavior = scrollBehavior, navigationIcon = null, - actions = { } + actions = { }, ) }, floatingActionButton = { @@ -459,7 +462,7 @@ fun EmptyItemListingContent( contentDescription = stringResource(id = R.string.scan_a_qr_code), testTag = "ScanQRCodeButton", ), - onScanQrCodeClick = onScanQuCodeClick + onScanQrCodeClick = onScanQuCodeClick, ), ItemListingExpandableFabAction.EnterSetupKey( label = R.string.enter_key_manually.asText(), @@ -469,7 +472,7 @@ fun EmptyItemListingContent( testTag = "EnterSetupKeyButton", ), onEnterSetupKeyClick = onEnterSetupKeyClick, - ) + ), ), expandableFabIcon = ExpandableFabIcon( iconData = IconResource( @@ -497,7 +500,7 @@ fun EmptyItemListingContent( AppTheme.DARK -> R.drawable.ic_empty_vault_dark AppTheme.LIGHT -> R.drawable.ic_empty_vault_light AppTheme.DEFAULT -> R.drawable.ic_empty_vault - } + }, ), contentDescription = stringResource( id = R.string.empty_item_list, @@ -530,7 +533,7 @@ fun EmptyItemListingContent( @OptIn(ExperimentalMaterial3Api::class) @Composable @Preview(showBackground = true) -fun EmptyListingContentPreview() { +private fun EmptyListingContentPreview() { EmptyItemListingContent( modifier = Modifier.padding(horizontal = 16.dp), appTheme = AppTheme.DEFAULT, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt index 0323a49241..7227f22024 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt @@ -38,6 +38,7 @@ import javax.inject.Inject /** * View model responsible for handling user interactions with the item listing screen. */ +@Suppress("TooManyFunctions") @HiltViewModel class ItemListingViewModel @Inject constructor( private val authenticatorRepository: AuthenticatorRepository, @@ -49,8 +50,8 @@ class ItemListingViewModel @Inject constructor( settingsRepository.appTheme, settingsRepository.authenticatorAlertThresholdSeconds, viewState = ItemListingState.ViewState.Loading, - dialog = null - ) + dialog = null, + ), ) { init { @@ -136,8 +137,8 @@ class ItemListingViewModel @Inject constructor( clipboardManager.setText(action.authCode) sendEvent( ItemListingEvent.ShowToast( - message = R.string.value_has_been_copied.asText(action.authCode) - ) + message = R.string.value_has_been_copied.asText(action.authCode), + ), ) } @@ -151,7 +152,7 @@ class ItemListingViewModel @Inject constructor( dialog = ItemListingState.DialogState.DeleteConfirmationPrompt( message = R.string.do_you_really_want_to_permanently_delete_cipher.asText(), itemId = action.itemId, - ) + ), ) } } @@ -166,8 +167,8 @@ class ItemListingViewModel @Inject constructor( viewModelScope.launch { trySendAction( ItemListingAction.Internal.DeleteItemReceive( - authenticatorRepository.hardDeleteItem(action.itemId) - ) + authenticatorRepository.hardDeleteItem(action.itemId), + ), ) } } @@ -214,7 +215,7 @@ class ItemListingViewModel @Inject constructor( dialog = ItemListingState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.generic_error_message.asText(), - ) + ), ) } } @@ -244,7 +245,7 @@ class ItemListingViewModel @Inject constructor( dialog = ItemListingState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.authenticator_key_read_error.asText(), - ) + ), ) } } @@ -310,7 +311,7 @@ class ItemListingViewModel @Inject constructor( .otpParametersList .mapNotNull { otpParam -> val secret = encodingManager.base32Encode( - byteArray = otpParam.secret.toByteArray() + byteArray = otpParam.secret.toByteArray(), ) // Google Authenticator only supports TOTP and HOTP codes. We do not support HOTP @@ -325,7 +326,7 @@ class ItemListingViewModel @Inject constructor( // Google Authenticator does not always provide a valid digits value so we double // check it and fallback to the default value if it is not within our valid range. - val digits = if (otpParam.digits in 5..10) { + val digits = if (otpParam.digits in TotpCodeManager.TOTP_DIGITS_RANGE) { otpParam.digits } else { TotpCodeManager.TOTP_DIGITS_DEFAULT @@ -411,11 +412,13 @@ class ItemListingViewModel @Inject constructor( } } - private fun authenticatorErrorReceive(authenticatorData: DataState.Error>) { + private fun authenticatorErrorReceive( + authenticatorData: DataState.Error>, + ) { if (authenticatorData.data != null) { updateStateWithVerificationCodeItems( authenticatorData = authenticatorData.data, - clearDialogState = true + clearDialogState = true, ) } else { mutableStateFlow.update { @@ -434,14 +437,14 @@ class ItemListingViewModel @Inject constructor( ) { updateStateWithVerificationCodeItems( authenticatorData = authenticatorData.data, - clearDialogState = false + clearDialogState = false, ) } private fun authenticatorDataLoadingReceive() { mutableStateFlow.update { it.copy( - viewState = ItemListingState.ViewState.Loading + viewState = ItemListingState.ViewState.Loading, ) } } @@ -452,7 +455,7 @@ class ItemListingViewModel @Inject constructor( if (state.data != null) { updateStateWithVerificationCodeItems( authenticatorData = state.data, - clearDialogState = true + clearDialogState = true, ) } else { mutableStateFlow.update { @@ -460,7 +463,7 @@ class ItemListingViewModel @Inject constructor( viewState = ItemListingState.ViewState.Error( message = R.string.internet_connection_required_title .asText() - .concat(R.string.internet_connection_required_message.asText()) + .concat(R.string.internet_connection_required_message.asText()), ), dialog = null, ) @@ -473,7 +476,7 @@ class ItemListingViewModel @Inject constructor( ) { updateStateWithVerificationCodeItems( authenticatorData = action.data, - clearDialogState = false + clearDialogState = false, ) } @@ -492,51 +495,50 @@ class ItemListingViewModel @Inject constructor( } private fun String.toAuthenticatorEntityOrNull(): AuthenticatorItemEntity? { - try { - val uri = Uri.parse(this) + val uri = Uri.parse(this) - val type = AuthenticatorItemType - .entries - .find { it.name.lowercase() == uri.host } - ?: return null + val type = AuthenticatorItemType + .entries + .find { it.name.lowercase() == uri.host } + ?: return null - val label = uri.pathSegments.firstOrNull() ?: return null - val accountName = if (label.contains(":")) { - label - .split(":") - .last() - } else { - label - } - - val key = uri.getQueryParameter(SECRET) ?: return null - - val algorithm = AuthenticatorItemAlgorithm - .entries - .find { it.name == uri.getQueryParameter(ALGORITHM) } - ?: AuthenticatorItemAlgorithm.SHA1 - - val digits = uri.getQueryParameter(DIGITS)?.toInt() ?: 6 - - val issuer = uri.getQueryParameter(ISSUER) ?: label - - val period = uri.getQueryParameter(PERIOD)?.toInt() ?: 30 - - return AuthenticatorItemEntity( - id = UUID.randomUUID().toString(), - key = key, - accountName = accountName, - type = type, - algorithm = algorithm, - period = period, - digits = digits, - issuer = issuer, - userId = null, - favorite = false, - ) - } catch (e: Throwable) { - return null + val label = uri.pathSegments.firstOrNull() ?: return null + val accountName = if (label.contains(":")) { + label + .split(":") + .last() + } else { + label } + + val key = uri.getQueryParameter(SECRET) ?: return null + + val algorithm = AuthenticatorItemAlgorithm + .entries + .find { it.name == uri.getQueryParameter(ALGORITHM) } + ?: AuthenticatorItemAlgorithm.SHA1 + + val digits = uri.getQueryParameter(DIGITS)?.toIntOrNull() + ?: TotpCodeManager.TOTP_DIGITS_DEFAULT + + val issuer = uri.getQueryParameter(ISSUER) + ?: label + + val period = uri.getQueryParameter(PERIOD)?.toIntOrNull() + ?: TotpCodeManager.PERIOD_SECONDS_DEFAULT + + return AuthenticatorItemEntity( + id = UUID.randomUUID().toString(), + key = key, + accountName = accountName, + type = type, + algorithm = algorithm, + period = period, + digits = digits, + issuer = issuer, + userId = null, + favorite = false, + ) } } @@ -617,6 +619,9 @@ data class ItemListingState( val message: Text, ) : DialogState() + /** + * Displays a prompt to confirm item deletion. + */ @Parcelize data class DeleteConfirmationPrompt( val message: Text, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt index f7496e5c5d..813a08eb5e 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt @@ -73,12 +73,12 @@ fun VaultVerificationCodeItem( onClick = onItemClick, onLongClick = { shouldShowDropdownMenu = true - } + }, ) .defaultMinSize(minHeight = 72.dp) .padding( vertical = 8.dp, - horizontal = 16.dp + horizontal = 16.dp, ) .then(modifier), verticalAlignment = Alignment.CenterVertically, @@ -120,7 +120,7 @@ fun VaultVerificationCodeItem( BitwardenCircularCountdownIndicator( timeLeftSeconds = timeLeftSeconds, periodSeconds = periodSeconds, - alertThresholdSeconds = alertThresholdSeconds + alertThresholdSeconds = alertThresholdSeconds, ) Text( @@ -145,9 +145,9 @@ fun VaultVerificationCodeItem( leadingIcon = { Icon( painter = painterResource(id = R.drawable.ic_edit_item), - contentDescription = stringResource(R.string.edit_item) + contentDescription = stringResource(R.string.edit_item), ) - } + }, ) HorizontalDivider() DropdownMenuItem( @@ -163,7 +163,7 @@ fun VaultVerificationCodeItem( painter = painterResource(id = R.drawable.ic_delete_item), contentDescription = stringResource(id = R.string.delete_item), ) - } + }, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt index 03f3529568..ec4d301f05 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/model/ItemListingExpandableFabAction.kt @@ -1,15 +1,22 @@ package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model +import androidx.compose.material3.ExtendedFloatingActionButton import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.components.fab.ExpandableFabOption import com.bitwarden.authenticator.ui.platform.components.model.IconResource +/** + * Models [ExpandableFabOption]s that can be triggered by the [ExtendedFloatingActionButton]. + */ sealed class ItemListingExpandableFabAction( label: Text?, icon: IconResource, onFabOptionClick: () -> Unit, ) : ExpandableFabOption(label, icon, onFabOptionClick) { + /** + * Indicates the Scan QR code button was clicked. + */ class ScanQrCode( label: Text?, icon: IconResource, @@ -17,9 +24,12 @@ sealed class ItemListingExpandableFabAction( ) : ItemListingExpandableFabAction( label, icon, - onScanQrCodeClick + onScanQrCodeClick, ) + /** + * Indicates the Enter Key button was clicked. + */ class EnterSetupKey( label: Text?, icon: IconResource, @@ -27,6 +37,6 @@ sealed class ItemListingExpandableFabAction( ) : ItemListingExpandableFabAction( label, icon, - onEnterSetupKeyClick + onEnterSetupKeyClick, ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/util/VerificationCodeItemExtensions.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/util/VerificationCodeItemExtensions.kt index 599fc37e51..034424df79 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/util/VerificationCodeItemExtensions.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/util/VerificationCodeItemExtensions.kt @@ -4,6 +4,9 @@ import com.bitwarden.authenticator.data.authenticator.manager.model.Verification import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.ItemListingState import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem +/** + * Transform a list of [VerificationCodeItem] into [ItemListingState.ViewState]. + */ fun List.toViewState( alertThresholdSeconds: Int, ): ItemListingState.ViewState = @@ -25,7 +28,7 @@ fun List.toViewState( ) } -fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int) = +private fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int) = VerificationCodeDisplayItem( id = id, issuer = issuer, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt index 968b5a97fd..2a03544d27 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryScreen.kt @@ -120,19 +120,19 @@ fun ManualCodeEntryScreen( BitwardenBasicDialog( visibilityState = BasicDialogState.Shown( title = dialog.title, - message = dialog.message + message = dialog.message, ), onDismissRequest = remember(state) { { viewModel.trySendAction(ManualCodeEntryAction.DismissDialog) } - } + }, ) } is ManualCodeEntryState.DialogState.Loading -> { BitwardenLoadingDialog( visibilityState = LoadingDialogState.Shown( - dialog.message - ) + dialog.message, + ), ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt index a3aafbdea6..48d220e949 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/manualcodeentry/ManualCodeEntryViewModel.kt @@ -7,7 +7,6 @@ import com.bitwarden.authenticator.R import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemType import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository -import com.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult import com.bitwarden.authenticator.ui.platform.base.BaseViewModel import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.base.util.asText @@ -41,10 +40,6 @@ class ManualCodeEntryViewModel @Inject constructor( is ManualCodeEntryAction.CodeSubmit -> handleCodeSubmit() is ManualCodeEntryAction.ScanQrCodeTextClick -> handleScanQrCodeTextClick() is ManualCodeEntryAction.SettingsClick -> handleSettingsClick() - is ManualCodeEntryAction.Internal.CreateItemResultReceive -> { - handleCreateItemReceive(action) - } - ManualCodeEntryAction.DismissDialog -> { handleDialogDismiss() } @@ -76,8 +71,8 @@ class ManualCodeEntryViewModel @Inject constructor( mutableStateFlow.update { it.copy( dialog = ManualCodeEntryState.DialogState.Error( - message = R.string.key_is_required.asText() - ) + message = R.string.key_is_required.asText(), + ), ) } return @@ -87,8 +82,8 @@ class ManualCodeEntryViewModel @Inject constructor( mutableStateFlow.update { it.copy( dialog = ManualCodeEntryState.DialogState.Error( - message = R.string.key_is_invalid.asText() - ) + message = R.string.key_is_invalid.asText(), + ), ) } return @@ -98,15 +93,15 @@ class ManualCodeEntryViewModel @Inject constructor( mutableStateFlow.update { it.copy( dialog = ManualCodeEntryState.DialogState.Error( - message = R.string.name_is_required.asText() - ) + message = R.string.name_is_required.asText(), + ), ) } return } viewModelScope.launch { - val result = authenticatorRepository.createItem( + authenticatorRepository.createItem( AuthenticatorItemEntity( id = UUID.randomUUID().toString(), key = state.code, @@ -119,9 +114,16 @@ class ManualCodeEntryViewModel @Inject constructor( AuthenticatorItemType.TOTP }, favorite = false, - ) + ), + ) + sendEvent( + event = ManualCodeEntryEvent.ShowToast( + message = R.string.verification_code_added.asText(), + ), + ) + sendEvent( + event = ManualCodeEntryEvent.NavigateBack, ) - sendAction(ManualCodeEntryAction.Internal.CreateItemResultReceive(result)) } } @@ -132,34 +134,6 @@ class ManualCodeEntryViewModel @Inject constructor( private fun handleSettingsClick() { sendEvent(ManualCodeEntryEvent.NavigateToAppSettings) } - - private fun handleCreateItemReceive( - action: ManualCodeEntryAction.Internal.CreateItemResultReceive, - ) { - when (action.result) { - CreateItemResult.Error -> { - mutableStateFlow.update { - it.copy( - dialog = ManualCodeEntryState.DialogState.Error( - title = R.string.an_error_has_occurred.asText(), - message = R.string.generic_error_message.asText(), - ) - ) - } - } - - CreateItemResult.Success -> { - sendEvent( - event = ManualCodeEntryEvent.ShowToast( - message = R.string.verification_code_added.asText(), - ), - ) - sendEvent( - event = ManualCodeEntryEvent.NavigateBack, - ) - } - } - } } /** @@ -172,15 +146,24 @@ data class ManualCodeEntryState( val dialog: DialogState?, ) : Parcelable { + /** + * Models dialog states for [ManualCodeEntryViewModel]. + */ @Parcelize sealed class DialogState : Parcelable { + /** + * Show an error dialog with an optional [title], and a [message]. + */ @Parcelize data class Error( val title: Text? = null, val message: Text, ) : DialogState() + /** + * Show a loading dialog. + */ @Parcelize data class Loading( val message: Text, @@ -239,17 +222,6 @@ sealed class ManualCodeEntryAction { */ data class IssuerTextChange(val issuer: String) : ManualCodeEntryAction() - /** - * Models actions that the [ManualCodeEntryViewModel] itself might send. - */ - sealed class Internal : ManualCodeEntryAction() { - - /** - * Indicates a result for creating an item has been received. - */ - data class CreateItemResultReceive(val result: CreateItemResult) : Internal() - } - /** * The text to switch to QR code scanning is clicked. */ diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt index 64c92b5fa7..4cd01caba0 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarNavigation.kt @@ -8,6 +8,7 @@ const val AUTHENTICATOR_NAV_BAR_ROUTE: String = "AuthenticatorNavBarRoute" /** * Add the authenticator nav bar to the nav graph. */ +@Suppress("LongParameterList") fun NavGraphBuilder.authenticatorNavBarDestination( onNavigateBack: () -> Unit, onNavigateToSearch: () -> Unit, @@ -29,7 +30,7 @@ fun NavGraphBuilder.authenticatorNavBarDestination( onNavigateToEditItem = onNavigateToEditItem, onNavigateToExport = onNavigateToExport, onNavigateToImport = onNavigateToImport, - onNavigateToTutorial = onNavigateToTutorial + onNavigateToTutorial = onNavigateToTutorial, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt index 744ddcdf4a..ba16fc7da1 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/navbar/AuthenticatorNavBarScreen.kt @@ -154,7 +154,7 @@ private fun AuthenticatorNavBarScaffold( }, modifier = Modifier .fillMaxWidth() - .height(appBarHeightPx.toDp()) + .height(appBarHeightPx.toDp()), ) } }, @@ -186,6 +186,7 @@ private fun AuthenticatorNavBarScaffold( } } +@Suppress("LongMethod") @Composable private fun AuthenticatorBottomAppBar( navController: NavController, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt index fba887d4ff..2decaeeec9 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/qrcodescan/util/QrCodeAnalyzerImpl.kt @@ -47,14 +47,13 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer { /* hints = */ mapOf( DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE), - DecodeHintType.ALSO_INVERTED to true + DecodeHintType.ALSO_INVERTED to true, ), ) qrCodeRead = true onQrCodeScanned(result.text) } catch (ignored: NotFoundException) { - } finally { image.close() } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchNavigation.kt index 24ccdee1aa..929c9b3d10 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchNavigation.kt @@ -16,7 +16,7 @@ fun NavGraphBuilder.itemSearchDestination( route = ITEM_SEARCH_ROUTE, ) { ItemSearchScreen( - onNavigateBack = onNavigateBack + onNavigateBack = onNavigateBack, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt index a101bd19c9..9a5c009471 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchScreen.kt @@ -37,6 +37,7 @@ import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaf /** * The search screen for authenticator items. */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemSearchScreen( @@ -84,8 +85,7 @@ fun ItemSearchScreen( ), ) }, - ) - { innerPadding -> + ) { innerPadding -> Column( modifier = Modifier .fillMaxSize() @@ -107,7 +107,7 @@ fun ItemSearchScreen( is ItemSearchState.ViewState.Empty -> { ItemSearchEmptyContent( viewState = viewState, - modifier = innerModifier + modifier = innerModifier, ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModel.kt index 7c5e38bb3d..e2a5566a9c 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/ItemSearchViewModel.kt @@ -24,6 +24,7 @@ import javax.inject.Inject /** * View model for the item search screen. */ +@Suppress("TooManyFunctions") @HiltViewModel class ItemSearchViewModel @Inject constructor( private val clipboardManager: BitwardenClipboardManager, @@ -34,7 +35,7 @@ class ItemSearchViewModel @Inject constructor( searchTerm = "", viewState = ItemSearchState.ViewState.Loading, dialogState = null, - ) + ), ) { init { @@ -75,7 +76,9 @@ class ItemSearchViewModel @Inject constructor( } } - private fun handleAuthenticatorDataReceive(action: ItemSearchAction.Internal.AuthenticatorDataReceive) { + private fun handleAuthenticatorDataReceive( + action: ItemSearchAction.Internal.AuthenticatorDataReceive, + ) { when (val data = action.dataState) { is DataState.Error -> authenticatorErrorReceive(authenticatorData = data) is DataState.Loaded -> authenticatorLoadedReceive(authenticatorData = data) @@ -85,7 +88,9 @@ class ItemSearchViewModel @Inject constructor( } } - private fun authenticatorErrorReceive(authenticatorData: DataState>) { + private fun authenticatorErrorReceive( + authenticatorData: DataState>, + ) { authenticatorData .data ?.let { @@ -157,7 +162,7 @@ class ItemSearchViewModel @Inject constructor( authenticatorRepository.getAuthCodesFlow().value.data?.let { authenticatorData -> updateStateWithAuthenticatorData( authenticatorData = authenticatorData, - clearDialogState = false + clearDialogState = false, ) } } @@ -172,7 +177,7 @@ class ItemSearchViewModel @Inject constructor( viewState = authenticatorData .filterAndOrganize(state.searchTerm) .toViewState(searchTerm = state.searchTerm), - dialogState = currentState.dialogState.takeUnless { clearDialogState } + dialogState = currentState.dialogState.takeUnless { clearDialogState }, ) } } @@ -192,6 +197,7 @@ class ItemSearchViewModel @Inject constructor( } } + @Suppress("MagicNumber") private fun VerificationCodeItem.matchedSearch(searchTerm: String): SortPriority? { val term = searchTerm.removeDiacritics() val itemName = label.removeDiacritics() @@ -221,7 +227,7 @@ class ItemSearchViewModel @Inject constructor( else -> { ItemSearchState.ViewState.Empty( - message = R.string.there_are_no_items_that_match_the_search.asText() + message = R.string.there_are_no_items_that_match_the_search.asText(), ) } } @@ -382,5 +388,5 @@ sealed class ItemSearchEvent { private enum class SortPriority { HIGH, - LOW + LOW, } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt index b83ef9d80a..579deb226d 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/VaultVerificationCodeItem.kt @@ -104,7 +104,7 @@ fun VaultVerificationCodeItem( BitwardenCircularCountdownIndicator( timeLeftSeconds = timeLeftSeconds, periodSeconds = periodSeconds, - alertThresholdSeconds = alertThresholdSeconds + alertThresholdSeconds = alertThresholdSeconds, ) Text( @@ -141,7 +141,7 @@ private fun VerificationCodeItem_preview() { onCopyClick = {}, onItemClick = {}, modifier = Modifier.padding(horizontal = 16.dp), - alertThresholdSeconds = 7 + alertThresholdSeconds = 7, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/handlers/SearchHandlers.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/handlers/SearchHandlers.kt index 1fc7acc50b..cc3eaf96de 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/handlers/SearchHandlers.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/search/handlers/SearchHandlers.kt @@ -17,6 +17,10 @@ class SearchHandlers( * [ItemSearchViewModel]. */ companion object { + /** + * Creates an instance of [SearchHandlers] by binding actions to the provided + * [ItemSearchViewModel]. + */ fun create(viewModel: ItemSearchViewModel): SearchHandlers = SearchHandlers( onBackClick = { viewModel.trySendAction(ItemSearchAction.BackClick) }, @@ -24,7 +28,7 @@ class SearchHandlers( onItemClick = { viewModel.trySendAction(ItemSearchAction.ItemClick(it)) }, onSearchTermChange = { viewModel.trySendAction(ItemSearchAction.SearchTermChange(it)) - } + }, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt index 0d95adf6fa..0adaba7ac8 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/components/fab/ExpandableFloatingActionButton.kt @@ -38,6 +38,15 @@ import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.components.model.IconResource import com.bitwarden.authenticator.ui.platform.theme.Typography +/** + * A FAB that expands, when clicked, to display a collection of options that can be clicked. + * + * @param label [Text] displayed when the FAB is expanded. + * @param items [ExpandableFabOption] buttons displayed when the FAB is expanded. + * @param expandableFabState [ExpandableFabIcon] displayed in the FAB. + * @param onStateChange Lambda invoked when the FAB expanded state changes. + */ +@Suppress("LongMethod") @Composable fun ExpandableFloatingActionButton( modifier: Modifier = Modifier, @@ -78,7 +87,7 @@ fun ExpandableFloatingActionButton( onStateChange(expandableFabState.value) expandableFabOption.onFabOptionClick() }, - expandableFabOption = expandableFabOption + expandableFabOption = expandableFabOption, ) } } @@ -150,31 +159,52 @@ private fun ExpandableFabOption( } @Composable -fun rememberExpandableFabState() = +private fun rememberExpandableFabState() = remember { mutableStateOf(ExpandableFabState.Collapsed) } +/** + * Represents options displayed when the FAB is expanded. + */ abstract class ExpandableFabOption( val label: Text?, val iconData: IconResource, val onFabOptionClick: () -> Unit, ) +/** + * Models data for an expandable FAB icon. + */ data class ExpandableFabIcon( val iconData: IconResource, val iconRotation: Float?, ) +/** + * Models the state of the expandable FAB. + */ sealed class ExpandableFabState { + /** + * Indicates if the FAB is expanded. + */ fun isExpanded() = this is Expanded + /** + * Invert the state of the FAB. + */ fun toggleValue() = if (isExpanded()) { Collapsed } else { Expanded } + /** + * Indicates the FAB is collapsed. + */ data object Collapsed : ExpandableFabState() + /** + * Indicates the FAB is expanded. + */ data object Expanded : ExpandableFabState() } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt index fae440a277..d0e7232cca 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavScreen.kt @@ -35,6 +35,7 @@ import java.util.concurrent.atomic.AtomicReference /** * Controls the root level [NavHost] for the app. */ +@Suppress("LongMethod") @Composable fun RootNavScreen( viewModel: RootNavViewModel = hiltViewModel(), @@ -77,7 +78,7 @@ fun RootNavScreen( unlockDestination( onUnlocked = { viewModel.trySendAction(RootNavAction.Internal.AppUnlocked) - } + }, ) authenticatorGraph( navController = navController, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavViewModel.kt index 687ca01025..effb02837c 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/rootnav/RootNavViewModel.kt @@ -15,6 +15,9 @@ import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize import javax.inject.Inject +/** + * Manages root level navigation state for the application. + */ @HiltViewModel class RootNavViewModel @Inject constructor( private val authRepository: AuthRepository, @@ -24,7 +27,7 @@ class RootNavViewModel @Inject constructor( initialState = RootNavState( hasSeenWelcomeGuide = settingsRepository.hasSeenWelcomeTutorial, navState = RootNavState.NavState.Splash, - ) + ), ) { init { @@ -67,8 +70,8 @@ class RootNavViewModel @Inject constructor( private fun handleHasSeenWelcomeTutorialChange(hasSeenWelcomeGuide: Boolean) { settingsRepository.hasSeenWelcomeTutorial = hasSeenWelcomeGuide if (hasSeenWelcomeGuide) { - if (settingsRepository.isUnlockWithBiometricsEnabled - && biometricsEncryptionManager.isBiometricIntegrityValid()) { + if (settingsRepository.isUnlockWithBiometricsEnabled && + biometricsEncryptionManager.isBiometricIntegrityValid()) { mutableStateFlow.update { it.copy(navState = RootNavState.NavState.Locked) } } else { mutableStateFlow.update { it.copy(navState = RootNavState.NavState.Unlocked) } @@ -99,7 +102,10 @@ class RootNavViewModel @Inject constructor( } /** - * Models root level destinations for the app. + * Models root level navigation state for the app. + * + * @property hasSeenWelcomeGuide Indicates if the user has seen the Welcome Guide screen. + * @property navState Current destination state of the app. */ @Parcelize data class RootNavState( @@ -107,6 +113,9 @@ data class RootNavState( val navState: NavState, ) : Parcelable { + /** + * Models root level destinations for the app. + */ @Parcelize sealed class NavState : Parcelable { /** diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsNavigation.kt index df1d10b82a..ad796c81d3 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsNavigation.kt @@ -23,10 +23,10 @@ fun NavGraphBuilder.settingsGraph( ) { navigation( startDestination = SETTINGS_ROUTE, - route = SETTINGS_GRAPH_ROUTE + route = SETTINGS_GRAPH_ROUTE, ) { composableWithRootPushTransitions( - route = SETTINGS_ROUTE + route = SETTINGS_ROUTE, ) { SettingsScreen( onNavigateToTutorial = onNavigateToTutorial, @@ -35,13 +35,13 @@ fun NavGraphBuilder.settingsGraph( ) } tutorialSettingsDestination( - onTutorialFinished = { navController.popBackStack() } + onTutorialFinished = { navController.popBackStack() }, ) exportDestination( - onNavigateBack = { navController.popBackStack() } + onNavigateBack = { navController.popBackStack() }, ) importingDestination( - onNavigateBack = { navController.popBackStack() } + onNavigateBack = { navController.popBackStack() }, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt index 398621a1c6..cdb1f7a560 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsScreen.kt @@ -69,6 +69,7 @@ import com.bitwarden.authenticator.ui.platform.util.displayLabel /** * Display the settings screen. */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( @@ -111,7 +112,7 @@ fun SettingsScreen( topBar = { BitwardenMediumTopAppBar( title = stringResource(id = R.string.settings), - scrollBehavior = scrollBehavior + scrollBehavior = scrollBehavior, ) }, ) { innerPadding -> @@ -119,7 +120,7 @@ fun SettingsScreen( modifier = Modifier .padding(innerPadding) .fillMaxSize() - .verticalScroll(state = rememberScrollState()) + .verticalScroll(state = rememberScrollState()), ) { SecuritySettings( state = state, @@ -127,10 +128,10 @@ fun SettingsScreen( onBiometricToggle = remember(viewModel) { { viewModel.trySendAction( - SettingsAction.SecurityClick.UnlockWithBiometricToggle(it) + SettingsAction.SecurityClick.UnlockWithBiometricToggle(it), ) } - } + }, ) Spacer(modifier = Modifier.height(16.dp)) VaultSettings( @@ -148,7 +149,7 @@ fun SettingsScreen( { viewModel.trySendAction(SettingsAction.DataClick.BackupClick) } - } + }, ) Spacer(modifier = Modifier.height(16.dp)) AppearanceSettings( @@ -208,7 +209,7 @@ fun SettingsScreen( //region Security settings @Composable -fun SecuritySettings( +private fun SecuritySettings( state: SettingsState, biometricsManager: BiometricsManager = LocalBiometricsManager.current, onBiometricToggle: (Boolean) -> Unit, @@ -217,7 +218,7 @@ fun SecuritySettings( BitwardenListHeaderText( modifier = Modifier.padding(horizontal = 16.dp), - label = stringResource(id = R.string.security) + label = stringResource(id = R.string.security), ) Spacer(modifier = Modifier.height(8.dp)) UnlockWithBiometricsRow( @@ -236,7 +237,7 @@ fun SecuritySettings( //region Data settings @Composable -fun VaultSettings( +private fun VaultSettings( modifier: Modifier = Modifier, onExportClick: () -> Unit, onImportClick: () -> Unit, @@ -244,7 +245,7 @@ fun VaultSettings( ) { BitwardenListHeaderText( modifier = Modifier.padding(horizontal = 16.dp), - label = stringResource(id = R.string.data) + label = stringResource(id = R.string.data), ) Spacer(modifier = Modifier.height(8.dp)) BitwardenTextRow( @@ -261,7 +262,7 @@ fun VaultSettings( contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, ) - } + }, ) Spacer(modifier = Modifier.height(8.dp)) BitwardenTextRow( @@ -278,7 +279,7 @@ fun VaultSettings( contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, ) - } + }, ) Spacer(modifier = Modifier.height(8.dp)) BitwardenExternalLinkRow( @@ -337,7 +338,7 @@ private fun AppearanceSettings( ) { BitwardenListHeaderText( modifier = Modifier.padding(horizontal = 16.dp), - label = stringResource(id = R.string.appearance) + label = stringResource(id = R.string.appearance), ) ThemeSelectionRow( currentSelection = state.appearance.theme, @@ -367,7 +368,7 @@ private fun ThemeSelectionRow( .mirrorIfRtl() .size(24.dp), painter = painterResource( - id = R.drawable.ic_navigate_next + id = R.drawable.ic_navigate_next, ), contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, @@ -407,7 +408,7 @@ private fun HelpSettings( ) { BitwardenListHeaderText( modifier = Modifier.padding(horizontal = 16.dp), - label = stringResource(id = R.string.help) + label = stringResource(id = R.string.help), ) BitwardenTextRow( text = stringResource(id = R.string.launch_tutorial), @@ -439,7 +440,7 @@ private fun AboutSettings( ) { BitwardenListHeaderText( modifier = modifier, - label = stringResource(id = R.string.about) + label = stringResource(id = R.string.about), ) BitwardenWideSwitch( modifier = modifier, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt index 4c8174ff62..ef28806e1d 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/SettingsViewModel.kt @@ -29,6 +29,7 @@ private const val KEY_STATE = "state" /** * View model for the settings screen. */ +@Suppress("TooManyFunctions") @HiltViewModel class SettingsViewModel @Inject constructor( savedStateHandle: SavedStateHandle, @@ -42,8 +43,8 @@ class SettingsViewModel @Inject constructor( settingsRepository.appLanguage, settingsRepository.appTheme, settingsRepository.isUnlockWithBiometricsEnabled, - settingsRepository.isCrashLoggingEnabled - ) + settingsRepository.isCrashLoggingEnabled, + ), ) { override fun handleAction(action: SettingsAction) { when (action) { @@ -160,7 +161,7 @@ class SettingsViewModel @Inject constructor( private fun handleLanguageChange(language: AppLanguage) { mutableStateFlow.update { it.copy( - appearance = it.appearance.copy(language = language) + appearance = it.appearance.copy(language = language), ) } settingsRepository.appLanguage = language @@ -173,7 +174,7 @@ class SettingsViewModel @Inject constructor( private fun handleThemeChange(theme: AppTheme) { mutableStateFlow.update { it.copy( - appearance = it.appearance.copy(theme = theme) + appearance = it.appearance.copy(theme = theme), ) } settingsRepository.appTheme = theme @@ -226,7 +227,7 @@ class SettingsViewModel @Inject constructor( } companion object { - fun createInitialState( + private fun createInitialState( clock: Clock, appLanguage: AppLanguage, appTheme: AppTheme, @@ -246,7 +247,7 @@ class SettingsViewModel @Inject constructor( version = R.string.version .asText() .concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()), - copyrightInfo = copyrightInfo + copyrightInfo = copyrightInfo, ) } } @@ -265,9 +266,15 @@ data class SettingsState( val copyrightInfo: Text, ) : Parcelable { + /** + * Models the dialog state for [SettingsViewModel]. + */ @Parcelize sealed class Dialog : Parcelable { + /** + * Displays a loading dialog with a [message]. + */ data class Loading( val message: Text, ) : Dialog() @@ -343,6 +350,9 @@ sealed class SettingsAction( * Indicates the user clicked the Unlock with biometrics button. */ sealed class SecurityClick : SettingsAction() { + /** + * Indicates the user clicked unlock with biometrics toggle. + */ data class UnlockWithBiometricToggle(val enabled: Boolean) : SecurityClick() } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportNavigation.kt index 64254d4fba..cd8c18adba 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportNavigation.kt @@ -5,8 +5,14 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions import com.bitwarden.authenticator.ui.platform.base.util.composableWithSlideTransitions +/** + * Route for the export data screen. + */ const val EXPORT_ROUTE = "export" +/** + * Add the export data destination to the nav graph. + */ fun NavGraphBuilder.exportDestination( onNavigateBack: () -> Unit, ) { @@ -17,6 +23,9 @@ fun NavGraphBuilder.exportDestination( } } +/** + * Navigate to the export data screen. + */ fun NavController.navigateToExport(navOptions: NavOptions? = null) { navigate(EXPORT_ROUTE, navOptions) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt index 6ea3b72a33..72744aed6a 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportScreen.kt @@ -40,12 +40,16 @@ import com.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoBut import com.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState import com.bitwarden.authenticator.ui.platform.components.dropdown.BitwardenMultiSelectButton import com.bitwarden.authenticator.ui.platform.components.scaffold.BitwardenScaffold -import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportFormat +import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager import com.bitwarden.authenticator.ui.platform.theme.LocalIntentManager import com.bitwarden.authenticator.ui.platform.util.displayLabel import kotlinx.collections.immutable.toImmutableList +/** + * Top level composable for the export data screen. + */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun ExportScreen( @@ -117,15 +121,15 @@ fun ExportScreen( { viewModel.trySendAction(ExportAction.DialogDismiss) } - } + }, ) } is ExportState.DialogState.Loading -> { BitwardenLoadingDialog( visibilityState = LoadingDialogState.Shown( - text = dialog.message - ) + text = dialog.message, + ), ) } @@ -161,7 +165,7 @@ fun ExportScreen( viewModel.trySendAction(ExportAction.ExportFormatOptionSelect(it)) } }, - onExportClick = { shouldShowConfirmationPrompt = true } + onExportClick = { shouldShowConfirmationPrompt = true }, ) } } @@ -170,21 +174,21 @@ fun ExportScreen( private fun ExportScreenContent( modifier: Modifier = Modifier, state: ExportState, - onExportFormatOptionSelected: (ExportFormat) -> Unit, + onExportFormatOptionSelected: (ExportVaultFormat) -> Unit, onExportClick: () -> Unit, ) { Column( modifier = modifier .imePadding() - .verticalScroll(rememberScrollState()) + .verticalScroll(rememberScrollState()), ) { val resources = LocalContext.current.resources BitwardenMultiSelectButton( label = stringResource(id = R.string.file_format), - options = ExportFormat.entries.map { it.displayLabel() }.toImmutableList(), - selectedOption = state.exportFormat.displayLabel(), + options = ExportVaultFormat.entries.map { it.displayLabel() }.toImmutableList(), + selectedOption = state.exportVaultFormat.displayLabel(), onOptionSelected = { selectedOptionLabel -> - val selectedOption = ExportFormat + val selectedOption = ExportVaultFormat .entries .first { it.displayLabel(resources) == selectedOptionLabel } onExportFormatOptionSelected(selectedOption) diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportViewModel.kt index ecae1a6e97..ba444fc25e 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/ExportViewModel.kt @@ -8,7 +8,7 @@ import com.bitwarden.authenticator.data.authenticator.repository.model.ExportDat import com.bitwarden.authenticator.ui.platform.base.BaseViewModel import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.base.util.asText -import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportFormat +import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat import com.bitwarden.authenticator.ui.platform.util.fileExtension import com.bitwarden.authenticator.ui.platform.util.toFormattedPattern import dagger.hilt.android.lifecycle.HiltViewModel @@ -18,13 +18,16 @@ import kotlinx.parcelize.IgnoredOnParcel import java.time.Clock import javax.inject.Inject +/** + * Manages state for the [ExportScreen]. + */ @HiltViewModel class ExportViewModel @Inject constructor( private val authenticatorRepository: AuthenticatorRepository, private val clock: Clock, ) : BaseViewModel( - initialState = ExportState(dialogState = null, exportFormat = ExportFormat.JSON) + initialState = ExportState(dialogState = null, exportVaultFormat = ExportVaultFormat.JSON), ) { override fun handleAction(action: ExportAction) { @@ -61,7 +64,7 @@ class ExportViewModel @Inject constructor( private fun handleExportFormatOptionSelect(action: ExportAction.ExportFormatOptionSelect) { mutableStateFlow.update { - it.copy(exportFormat = action.option) + it.copy(exportVaultFormat = action.option) } } @@ -71,7 +74,7 @@ class ExportViewModel @Inject constructor( pattern = "yyyyMMddHHmmss", clock = clock, ) - val extension = state.exportFormat.fileExtension + val extension = state.exportVaultFormat.fileExtension val fileName = "authenticator_export_$date.$extension" sendEvent( @@ -92,14 +95,14 @@ class ExportViewModel @Inject constructor( viewModelScope.launch { val result = authenticatorRepository.exportVaultData( - format = state.exportFormat, + format = state.exportVaultFormat, fileUri = action.fileUri, ) sendAction( ExportAction.Internal.SaveExportDataToUriResultReceive( - result = result - ) + result = result, + ), ) } } @@ -133,17 +136,29 @@ class ExportViewModel @Inject constructor( } } +/** + * Represents the state of the [ExportViewModel]. + */ data class ExportState( @IgnoredOnParcel val exportData: String? = null, val dialogState: DialogState? = null, - val exportFormat: ExportFormat, + val exportVaultFormat: ExportVaultFormat, ) { + /** + * Represents state of dialogs for the [ExportViewModel]. + */ sealed class DialogState { + /** + * Displays a loading dialog with an optional [message]. + */ data class Loading( val message: Text = R.string.loading.asText(), ) : DialogState() + /** + * Displays an error dialog with an optional [title], and a [message]. + */ data class Error( val title: Text? = null, val message: Text, @@ -151,27 +166,64 @@ data class ExportState( } } +/** + * Represents events for the [ExportViewModel]. + */ sealed class ExportEvent { + /** + * Navigate back. + */ data object NavigateBack : ExportEvent() + /** + * Display a toast with the provided [message]. + */ data class ShowToast(val message: Text) : ExportEvent() + /** + * Navigate to the select export destination screen. + */ data class NavigateToSelectExportDestination(val fileName: String) : ExportEvent() } +/** + * Represents actions for the [ExportViewModel]. + */ sealed class ExportAction { + + /** + * Indicates the user has clicked the close button. + */ data object CloseButtonClick : ExportAction() + /** + * Indicates the user has clicked the export confirmation button. + */ data object ConfirmExportClick : ExportAction() + /** + * Indicates the user has dismissed the dialog. + */ data object DialogDismiss : ExportAction() - data class ExportFormatOptionSelect(val option: ExportFormat) : ExportAction() + /** + * Indicates the user has selected an export format. + */ + data class ExportFormatOptionSelect(val option: ExportVaultFormat) : ExportAction() + /** + * Indicates the user has selected a location for the exported data. + */ data class ExportLocationReceive(val fileUri: Uri) : ExportAction() + /** + * Represents actions the [ExportViewModel] itself may trigger. + */ sealed class Internal : ExportAction() { + /** + * Indicates the result for saving exported data to a URI has been received. + */ data class SaveExportDataToUriResultReceive( val result: ExportDataResult, ) : Internal() diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/model/ExportVaultFormat.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/model/ExportVaultFormat.kt index f5d6c6e7d6..f88128aea1 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/model/ExportVaultFormat.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/export/model/ExportVaultFormat.kt @@ -3,7 +3,7 @@ package com.bitwarden.authenticator.ui.platform.feature.settings.export.model /** * Represents the file formats a user can select to export the vault. */ -enum class ExportFormat { +enum class ExportVaultFormat { JSON, CSV, } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt index 27ba3a1756..6406f01ed3 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingScreen.kt @@ -42,6 +42,10 @@ import com.bitwarden.authenticator.ui.platform.theme.LocalIntentManager import com.bitwarden.authenticator.ui.platform.util.displayLabel import kotlinx.collections.immutable.toImmutableList +/** + * Top level composable for the importing screen. + */ +@Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun ImportingScreen( @@ -66,7 +70,9 @@ fun ImportingScreen( when (event) { ImportEvent.NavigateBack -> onNavigateBack() is ImportEvent.NavigateToSelectImportFile -> { - launcher.launch(intentManager.createFileChooserIntent(event.importFileFormat.mimeType)) + launcher.launch( + intentManager.createFileChooserIntent(event.importFileFormat.mimeType), + ) } is ImportEvent.ShowToast -> { @@ -88,15 +94,15 @@ fun ImportingScreen( { viewModel.trySendAction(ImportAction.DialogDismiss) } - } + }, ) } is ImportState.DialogState.Loading -> { BitwardenLoadingDialog( visibilityState = LoadingDialogState.Shown( - text = dialog.message - ) + text = dialog.message, + ), ) } @@ -151,7 +157,7 @@ private fun ImportScreenContent( Column( modifier = modifier .imePadding() - .verticalScroll(rememberScrollState()) + .verticalScroll(rememberScrollState()), ) { val resources = LocalContext.current.resources BitwardenMultiSelectButton( diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingViewModel.kt index b0160bce46..60944f9f94 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/settings/importing/ImportingViewModel.kt @@ -24,7 +24,7 @@ class ImportingViewModel @Inject constructor( private val authenticatorRepository: AuthenticatorRepository, ) : BaseViewModel( - initialState = ImportState(importFileFormat = ImportFileFormat.BITWARDEN_JSON) + initialState = ImportState(importFileFormat = ImportFileFormat.BITWARDEN_JSON), ) { override fun handleAction(action: ImportAction) { @@ -97,7 +97,7 @@ class ImportingViewModel @Inject constructor( title = R.string.an_error_has_occurred.asText(), message = result.message ?: R.string.import_vault_failure.asText(), - ) + ), ) } } @@ -107,7 +107,7 @@ class ImportingViewModel @Inject constructor( sendEvent( ImportEvent.ShowToast( message = R.string.import_success.asText(), - ) + ), ) sendEvent(ImportEvent.NavigateBack) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialNavigation.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialNavigation.kt index 78fc6fa6f0..60b914abcd 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialNavigation.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialNavigation.kt @@ -8,6 +8,9 @@ import androidx.navigation.compose.composable const val TUTORIAL_ROUTE = "tutorial" const val SETTINGS_TUTORIAL_ROUTE = "settings/tutorial" +/** + * Add the top level Tutorial screen to the nav graph. + */ fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) { composable(TUTORIAL_ROUTE) { TutorialScreen( @@ -16,6 +19,9 @@ fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) { } } +/** + * Add the Settings Tutorial screen to the nav graph. + */ fun NavGraphBuilder.tutorialSettingsDestination(onTutorialFinished: () -> Unit) { composable(SETTINGS_TUTORIAL_ROUTE) { TutorialScreen( @@ -24,10 +30,16 @@ fun NavGraphBuilder.tutorialSettingsDestination(onTutorialFinished: () -> Unit) } } +/** + * Navigate to the top level Tutorial screen. + */ fun NavController.navigateToTutorial(navOptions: NavOptions? = null) { navigate(route = TUTORIAL_ROUTE, navOptions = navOptions) } +/** + * Navigate to the Tutorial screen within Settings. + */ fun NavController.navigateToSettingsTutorial(navOptions: NavOptions? = null) { navigate(route = SETTINGS_TUTORIAL_ROUTE, navOptions) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt index 4c62b29a3e..5b793a0884 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialScreen.kt @@ -44,6 +44,10 @@ private const val QR_SCANNER_PAGE = 1 private const val UNIQUE_CODES_PAGE = 2 private const val PAGE_COUNT = 3 +/** + * Top level composable for the tutorial screen. + */ +@Suppress("LongMethod") @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun TutorialScreen( @@ -75,7 +79,7 @@ fun TutorialScreen( Column( modifier = Modifier .padding(innerPadding), - verticalArrangement = Arrangement.Center + verticalArrangement = Arrangement.Center, ) { HorizontalPager( modifier = Modifier @@ -86,7 +90,7 @@ fun TutorialScreen( userScrollEnabled = true, ) { page -> viewModel.trySendAction( - TutorialAction.TutorialPageChange(pagerState.targetPage) + TutorialAction.TutorialPageChange(pagerState.targetPage), ) Column( modifier = Modifier.fillMaxWidth(), @@ -112,7 +116,7 @@ fun TutorialScreen( modifier = Modifier .height(50.dp) .fillMaxWidth(), - horizontalArrangement = Arrangement.Center + horizontalArrangement = Arrangement.Center, ) { repeat(PAGE_COUNT) { val color = if (pagerState.currentPage == it) { @@ -124,7 +128,7 @@ fun TutorialScreen( modifier = Modifier .padding(8.dp) .background(color, CircleShape) - .size(10.dp) + .size(10.dp), ) } } @@ -137,7 +141,7 @@ fun TutorialScreen( onClick = remember(viewModel) { { viewModel.trySendAction( - TutorialAction.ContinueClick + TutorialAction.ContinueClick, ) } }, @@ -203,7 +207,7 @@ private fun TutorialQrScannerScreen() { style = MaterialTheme.typography.headlineSmall, textAlign = TextAlign.Center, text = stringResource( - R.string.use_your_device_camera_to_scan_codes + R.string.use_your_device_camera_to_scan_codes, ), ) Spacer(Modifier.height(8.dp)) @@ -211,16 +215,17 @@ private fun TutorialQrScannerScreen() { style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center, text = stringResource( - R.string.scan_the_qr_code_in_your_2_step_verification_settings_for_any_account + R.string.scan_the_qr_code_in_your_2_step_verification_settings_for_any_account, ), ) } +@Suppress("MaxLineLength") @Composable private fun UniqueCodesContent() { Image( painter = painterResource(id = R.drawable.ic_tutorial_2fa), - contentDescription = stringResource(id = R.string.unique_codes) + contentDescription = stringResource(id = R.string.unique_codes), ) Spacer(Modifier.height(24.dp)) Text( @@ -233,17 +238,17 @@ private fun UniqueCodesContent() { style = MaterialTheme.typography.bodyLarge, textAlign = TextAlign.Center, text = stringResource( - R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app + R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app, ), ) } @Preview @Composable -fun TutorialScreenPreview() { +private fun TutorialScreenPreview() { Box { TutorialScreen( - onTutorialFinished = {} + onTutorialFinished = {}, ) } } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialViewModel.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialViewModel.kt index 84987f6733..1373eb668b 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialViewModel.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/feature/tutorial/TutorialViewModel.kt @@ -16,7 +16,7 @@ import javax.inject.Inject @HiltViewModel class TutorialViewModel @Inject constructor() : BaseViewModel( - initialState = TutorialState.IntroSlide + initialState = TutorialState.IntroSlide, ) { override fun handleAction(action: TutorialAction) { @@ -84,7 +84,7 @@ sealed class TutorialState( @Parcelize data object QrScannerSlide : TutorialState( continueButtonText = R.string.continue_button.asText(), - isLastPage = false + isLastPage = false, ) /** @@ -93,7 +93,7 @@ sealed class TutorialState( @Parcelize data object UniqueCodesSlide : TutorialState( continueButtonText = R.string.get_started.asText(), - isLastPage = true + isLastPage = true, ) } diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt index eace5064c1..e3a99a7a63 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/theme/AuthenticatorTheme.kt @@ -30,6 +30,9 @@ import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManagerImpl import com.bitwarden.authenticator.ui.platform.manager.permissions.PermissionsManager import com.bitwarden.authenticator.ui.platform.manager.permissions.PermissionsManagerImpl +/** + * The overall application theme. This can be configured to support a [theme] and [dynamicColor]. + */ @Composable fun AuthenticatorTheme( theme: AppTheme = AppTheme.DEFAULT, diff --git a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/util/ExportFormatExtensions.kt b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/util/ExportFormatExtensions.kt index df89efe240..7b2964fa3e 100644 --- a/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/util/ExportFormatExtensions.kt +++ b/app/src/main/kotlin/com/bitwarden/authenticator/ui/platform/util/ExportFormatExtensions.kt @@ -3,22 +3,22 @@ package com.bitwarden.authenticator.ui.platform.util import com.bitwarden.authenticator.R import com.bitwarden.authenticator.ui.platform.base.util.Text import com.bitwarden.authenticator.ui.platform.base.util.asText -import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportFormat +import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportVaultFormat /** * Provides a human-readable label for the export format. */ -val ExportFormat.displayLabel: Text +val ExportVaultFormat.displayLabel: Text get() = when (this) { - ExportFormat.JSON -> R.string.export_format_label_json.asText() - ExportFormat.CSV -> R.string.export_format_label_csv.asText() + ExportVaultFormat.JSON -> R.string.export_format_label_json.asText() + ExportVaultFormat.CSV -> R.string.export_format_label_csv.asText() } /** * Provides the file extension associated with the export format. */ -val ExportFormat.fileExtension: String +val ExportVaultFormat.fileExtension: String get() = when (this) { - ExportFormat.JSON -> "json" - ExportFormat.CSV -> "csv" + ExportVaultFormat.JSON -> "json" + ExportVaultFormat.CSV -> "csv" } diff --git a/app/src/test/java/com/bitwarden/authenticator/MainViewModelTest.kt b/app/src/test/java/com/bitwarden/authenticator/MainViewModelTest.kt index 1d8c7043bc..c904992dea 100644 --- a/app/src/test/java/com/bitwarden/authenticator/MainViewModelTest.kt +++ b/app/src/test/java/com/bitwarden/authenticator/MainViewModelTest.kt @@ -7,7 +7,6 @@ import io.mockk.every import io.mockk.mockk import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow -import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test @@ -28,10 +27,6 @@ class MainViewModelTest : BaseViewModelTest() { mainViewModel = MainViewModel(settingsRepository) } - @AfterEach - fun tearDown() { - } - @Test fun `on AppThemeChanged should update state`() { assertEquals( diff --git a/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/disk/FeatureFlagDiskSourceTest.kt b/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/disk/FeatureFlagDiskSourceTest.kt index 1911b455f5..e773d48df3 100644 --- a/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/disk/FeatureFlagDiskSourceTest.kt +++ b/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/disk/FeatureFlagDiskSourceTest.kt @@ -32,10 +32,10 @@ class FeatureFlagDiskSourceTest { featureFlagDiskSource.featureFlagsConfiguration = FEATURE_FLAGS_CONFIGURATION assertEquals( json.parseToJsonElement( - FEATURE_FLAGS_CONFIGURATION_JSON + FEATURE_FLAGS_CONFIGURATION_JSON, ), json.parseToJsonElement( - fakeSharedPreferences.getString(featureFlagsConfigKey, null)!! + fakeSharedPreferences.getString(featureFlagsConfigKey, null)!!, ), ) @@ -53,7 +53,6 @@ class FeatureFlagDiskSourceTest { assertEquals(FEATURE_FLAGS_CONFIGURATION, awaitItem()) } } - } private const val FEATURE_FLAGS_CONFIGURATION_JSON = """ @@ -67,6 +66,6 @@ private const val FEATURE_FLAGS_CONFIGURATION_JSON = """ private val FEATURE_FLAGS_CONFIGURATION = FeatureFlagsConfiguration( featureFlags = mapOf( - LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true) - ) + LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true), + ), ) diff --git a/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/serializer/ZonedDateTimeSerializerTest.kt b/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/serializer/ZonedDateTimeSerializerTest.kt index b5312b18f6..0696611710 100644 --- a/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/serializer/ZonedDateTimeSerializerTest.kt +++ b/app/src/test/java/com/bitwarden/authenticator/data/platform/datasource/network/serializer/ZonedDateTimeSerializerTest.kt @@ -97,4 +97,3 @@ private data class ZonedDateTimeData( @SerialName("dataAsZonedDateTime") val dataAsZonedDateTime: ZonedDateTime, ) - diff --git a/app/src/test/java/com/bitwarden/authenticator/data/platform/manager/FeatureFlagManagerTest.kt b/app/src/test/java/com/bitwarden/authenticator/data/platform/manager/FeatureFlagManagerTest.kt index a4909500b1..294780aa1f 100644 --- a/app/src/test/java/com/bitwarden/authenticator/data/platform/manager/FeatureFlagManagerTest.kt +++ b/app/src/test/java/com/bitwarden/authenticator/data/platform/manager/FeatureFlagManagerTest.kt @@ -49,7 +49,7 @@ class FeatureFlagManagerTest { @Test fun `getFeatureFlag should return default value if flag doesn't exist`() { fakeFeatureFlagRepository.featureFlagsConfiguration = FEATURE_FLAGS_CONFIG.copy( - featureFlags = emptyMap() + featureFlags = emptyMap(), ) val flagValue = featureFlagManager.getFeatureFlag( @@ -65,5 +65,5 @@ private val FEATURE_FLAGS_CONFIG = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true), - ) + ), ) diff --git a/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryTest.kt b/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryTest.kt index 6603ff76c8..7720d88178 100644 --- a/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryTest.kt +++ b/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/FeatureFlagRepositoryTest.kt @@ -29,7 +29,7 @@ class FeatureFlagRepositoryTest { assertEquals( FEATURE_FLAGS_CONFIG, - fakeFeatureFlagDiskSource.featureFlagsConfiguration + fakeFeatureFlagDiskSource.featureFlagsConfiguration, ) } @@ -49,5 +49,5 @@ private val FEATURE_FLAGS_CONFIG = featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue), - ) + ), ) diff --git a/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/util/FakeFeatureFlagRepository.kt b/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/util/FakeFeatureFlagRepository.kt index 0b48890a5d..532bff664c 100644 --- a/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/util/FakeFeatureFlagRepository.kt +++ b/app/src/test/java/com/bitwarden/authenticator/data/platform/repository/util/FakeFeatureFlagRepository.kt @@ -33,6 +33,6 @@ private val FEATURE_FLAGS_CONFIG = FeatureFlagsConfiguration( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to - JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue) - ) + JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue), + ), ) diff --git a/app/src/test/java/com/bitwarden/authenticator/ui/platform/base/BaseViewModelTest.kt b/app/src/test/java/com/bitwarden/authenticator/ui/platform/base/BaseViewModelTest.kt index eabadd1a50..fe67f72f89 100644 --- a/app/src/test/java/com/bitwarden/authenticator/ui/platform/base/BaseViewModelTest.kt +++ b/app/src/test/java/com/bitwarden/authenticator/ui/platform/base/BaseViewModelTest.kt @@ -26,4 +26,3 @@ abstract class BaseViewModelTest { } } } - diff --git a/detekt-baseline.xml b/detekt-baseline.xml deleted file mode 100644 index 3b7b344c6e..0000000000 --- a/detekt-baseline.xml +++ /dev/null @@ -1,373 +0,0 @@ - - - - - ChainWrapping:EditItemScreen.kt$+ - ChainWrapping:RootNavViewModel.kt$RootNavViewModel$&& - CyclomaticComplexMethod:BitwardenExportParser.kt$BitwardenExportParser$private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity - EmptyFunctionBlock:MainViewModelTest.kt$MainViewModelTest${ } - LongMethod:AuthenticatorNavBarScreen.kt$@Composable private fun AuthenticatorBottomAppBar( navController: NavController, verificationCodesTabClickedAction: () -> Unit, settingsTabClickedAction: () -> Unit, modifier: Modifier = Modifier, ) - LongMethod:BitwardenExportParser.kt$BitwardenExportParser$private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity - LongMethod:EditItemScreen.kt$@Composable fun EditItemContent( modifier: Modifier = Modifier, viewState: EditItemState.ViewState.Content, onIssuerNameTextChange: (String) -> Unit = {}, onUsernameTextChange: (String) -> Unit = {}, onToggleFavorite: (Boolean) -> Unit = {}, onTypeOptionClicked: (AuthenticatorItemType) -> Unit = {}, onTotpCodeTextChange: (String) -> Unit = {}, onAlgorithmOptionClicked: (AuthenticatorItemAlgorithm) -> Unit = {}, onRefreshPeriodOptionClicked: (AuthenticatorRefreshPeriodOption) -> Unit = {}, onNumberOfDigitsChanged: (Int) -> Unit = {}, onExpandAdvancedOptionsClicked: () -> Unit = {}, ) - LongMethod:EditItemScreen.kt$@Composable private fun AdvancedOptions( modifier: Modifier = Modifier, viewState: EditItemState.ViewState.Content, onExpandStateChange: () -> Unit, onAlgorithmOptionClicked: (AuthenticatorItemAlgorithm) -> Unit, onTypeOptionClicked: (AuthenticatorItemType) -> Unit, onRefreshPeriodOptionClicked: (AuthenticatorRefreshPeriodOption) -> Unit, onNumberOfDigitsChanged: (Int) -> Unit, ) - LongMethod:EditItemScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun EditItemScreen( viewModel: EditItemViewModel = hiltViewModel(), onNavigateBack: () -> Unit = { }, ) - LongMethod:EditItemViewModel.kt$EditItemViewModel$private fun handleItemDataReceive(action: EditItemAction.Internal.EditItemDataReceive) - LongMethod:ExpandableFloatingActionButton.kt$@Composable fun <T : ExpandableFabOption> ExpandableFloatingActionButton( modifier: Modifier = Modifier, label: Text?, items: List<T>, expandableFabState: MutableState<ExpandableFabState> = rememberExpandableFabState(), expandableFabIcon: ExpandableFabIcon, onStateChange: (expandableFabState: ExpandableFabState) -> Unit = { }, ) - LongMethod:ExportScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExportScreen( viewModel: ExportViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, ) - LongMethod:ImportingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ImportingScreen( viewModel: ImportingViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, ) - LongMethod:ItemListingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun EmptyItemListingContent( modifier: Modifier = Modifier, appTheme: AppTheme, scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior( rememberTopAppBarState() ), onAddCodeClick: () -> Unit, onScanQuCodeClick: () -> Unit, onEnterSetupKeyClick: () -> Unit, ) - LongMethod:ItemListingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemListingScreen( viewModel: ItemListingViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, permissionsManager: PermissionsManager = LocalPermissionsManager.current, onNavigateBack: () -> Unit, onNavigateToSearch: () -> Unit, onNavigateToQrCodeScanner: () -> Unit, onNavigateToManualKeyEntry: () -> Unit, onNavigateToEditItemScreen: (id: String) -> Unit, ) - LongMethod:ItemListingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable private fun ItemListingContent( state: ItemListingState.ViewState.Content, scrollBehavior: TopAppBarScrollBehavior, onNavigateToSearch: () -> Unit, onScanQrCodeClick: () -> Unit, onEnterSetupKeyClick: () -> Unit, onItemClick: (String) -> Unit, onEditItemClick: (String) -> Unit, onDeleteItemClick: (String) -> Unit, ) - LongMethod:ItemSearchScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemSearchScreen( viewModel: ItemSearchViewModel = hiltViewModel(), onNavigateBack: () -> Unit, ) - LongMethod:RootNavScreen.kt$@Composable fun RootNavScreen( viewModel: RootNavViewModel = hiltViewModel(), navController: NavHostController = rememberNavController(), onSplashScreenRemoved: () -> Unit = {}, onExitApplication: () -> Unit, ) - LongMethod:SettingsScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen( viewModel: SettingsViewModel = hiltViewModel(), biometricsManager: BiometricsManager = LocalBiometricsManager.current, intentManager: IntentManager = LocalIntentManager.current, onNavigateToTutorial: () -> Unit, onNavigateToExport: () -> Unit, onNavigateToImport: () -> Unit, ) - LongMethod:TutorialScreen.kt$@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun TutorialScreen( viewModel: TutorialViewModel = hiltViewModel(), onTutorialFinished: () -> Unit, ) - LongMethod:UnlockScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun UnlockScreen( viewModel: UnlockViewModel = hiltViewModel(), biometricsManager: BiometricsManager = LocalBiometricsManager.current, onUnlocked: () -> Unit, ) - LongParameterList:AuthenticatorNavBarNavigation.kt$( onNavigateBack: () -> Unit, onNavigateToSearch: () -> Unit, onNavigateToQrCodeScanner: () -> Unit, onNavigateToManualKeyEntry: () -> Unit, onNavigateToEditItem: (itemId: String) -> Unit, onNavigateToExport: () -> Unit, onNavigateToImport: () -> Unit, onNavigateToTutorial: () -> Unit, ) - LongParameterList:ItemListingGraphNavigation.kt$( navController: NavController, navigateBack: () -> Unit, navigateToSearch: () -> Unit, navigateToQrCodeScanner: () -> Unit, navigateToManualKeyEntry: () -> Unit, navigateToEditItem: (String) -> Unit, navigateToExport: () -> Unit, navigateToImport: () -> Unit, navigateToTutorial: () -> Unit, ) - MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$10 - MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$30 - MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$5 - MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$6 - MagicNumber:ItemSearchViewModel.kt$ItemSearchViewModel$8 - MagicNumber:SettingsDiskSourceImpl.kt$SettingsDiskSourceImpl$7 - MatchingDeclarationName:ExportVaultFormat.kt$ExportFormat - MaxLineLength:AegisExportParser.kt$AegisExportParser$private - MaxLineLength:BitwardenExportParser.kt$BitwardenExportParser$"${TotpCodeManager.TOTP_CODE_PREFIX}/$name?${TotpCodeManager.SECRET_PARAM}=$otpString" - MaxLineLength:FeatureFlag.kt$FeatureFlag$* - MaxLineLength:ImportingScreen.kt$launcher.launch(intentManager.createFileChooserIntent(event.importFileFormat.mimeType)) - MaxLineLength:ItemListingViewModel.kt$ItemListingViewModel$private - MaxLineLength:ItemSearchViewModel.kt$ItemSearchViewModel$private - MaxLineLength:TutorialScreen.kt$R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app - MaxLineLength:TwoFasExportParser.kt$TwoFasExportParser$private - MaximumLineLength:TutorialScreen.kt$ - NoBlankLineBeforeRbrace:AuthenticatorDatabase.kt$AuthenticatorDatabase$ - NoBlankLineBeforeRbrace:AuthenticatorDiskSource.kt$AuthenticatorDiskSource$ - NoBlankLineBeforeRbrace:AuthenticatorManagerModule.kt$AuthenticatorManagerModule$ - NoBlankLineBeforeRbrace:AuthenticatorRepositoryModule.kt$AuthenticatorRepositoryModule$ - NoBlankLineBeforeRbrace:AuthenticatorSdkModule.kt$AuthenticatorSdkModule$ - NoBlankLineBeforeRbrace:AuthenticatorSdkSourceImpl.kt$AuthenticatorSdkSourceImpl$ - NoBlankLineBeforeRbrace:ExportDataResult.kt$ExportDataResult$ - NoBlankLineBeforeRbrace:FeatureFlagDiskSourceTest.kt$FeatureFlagDiskSourceTest$ - NoBlankLineBeforeRbrace:ImportManager.kt$ImportManager$ - NoBlankLineBeforeRbrace:MainViewModel.kt$MainViewModel$ - NoBlankLineBeforeRbrace:QrCodeAnalyzerImpl.kt$QrCodeAnalyzerImpl$ - NoBlankLineBeforeRbrace:UnlockViewModel.kt$UnlockEvent$ - NoConsecutiveBlankLines:BaseViewModelTest.kt$ - NoConsecutiveBlankLines:FeatureFlag.kt$ - NoConsecutiveBlankLines:ZonedDateTimeSerializerTest.kt$ - SpacingAroundColon:AuthenticatorManagerModule.kt$AuthenticatorManagerModule$: - SpacingAroundCurly:ItemSearchScreen.kt${ - TooGenericExceptionCaught:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$e: Exception - TooGenericExceptionCaught:ImportManagerImpl.kt$ImportManagerImpl$e: Throwable - TooGenericExceptionCaught:ItemListingViewModel.kt$ItemListingViewModel$e: Throwable - TooManyFunctions:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl : AuthenticatorRepository - TooManyFunctions:EditItemViewModel.kt$EditItemViewModel : BaseViewModel - TooManyFunctions:ItemListingViewModel.kt$ItemListingViewModel : BaseViewModel - TooManyFunctions:ItemSearchViewModel.kt$ItemSearchViewModel : BaseViewModel - TooManyFunctions:SettingsViewModel.kt$SettingsViewModel : BaseViewModel - TrailingCommaOnCallSite:AuthRepositoryModule.kt$AuthRepositoryModule$( authDiskSource = authDiskSource ) - TrailingCommaOnCallSite:AuthenticatorDatabase.kt$AuthenticatorDatabase$[ AuthenticatorItemEntity::class ] - TrailingCommaOnCallSite:AuthenticatorDiskModule.kt$AuthenticatorDiskModule$( context = app, klass = AuthenticatorDatabase::class.java, name = "authenticator_database" ) - TrailingCommaOnCallSite:AuthenticatorDiskSourceImpl.kt$AuthenticatorDiskSourceImpl$( forceItemsFlow, itemDao.getAllItems() ) - TrailingCommaOnCallSite:AuthenticatorManagerModule.kt$AuthenticatorManagerModule$( authenticatorSdkSource = authenticatorSdkSource, dispatcherManager = dispatcherManager, clock = clock ) - TrailingCommaOnCallSite:AuthenticatorNavBarNavigation.kt$( onNavigateBack = onNavigateBack, onNavigateToSearch = onNavigateToSearch, onNavigateToQrCodeScanner = onNavigateToQrCodeScanner, onNavigateToManualKeyEntry = onNavigateToManualKeyEntry, onNavigateToEditItem = onNavigateToEditItem, onNavigateToExport = onNavigateToExport, onNavigateToImport = onNavigateToImport, onNavigateToTutorial = onNavigateToTutorial ) - TrailingCommaOnCallSite:AuthenticatorNavBarScreen.kt$( isVisible = false, onClick = { // Do nothing }, modifier = Modifier .fillMaxWidth() .height(appBarHeightPx.toDp()) ) - TrailingCommaOnCallSite:AuthenticatorNavigation.kt$( navController = navController, navigateBack = onNavigateBack, navigateToSearch = { navController.navigateToSearch() }, navigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() }, navigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() }, navigateToEditItem = { navController.navigateToEditItem(itemId = it) }, navigateToExport = { navController.navigateToExport() }, navigateToImport = { navController.navigateToImporting() }, navigateToTutorial = { navController.navigateToSettingsTutorial() } ) - TrailingCommaOnCallSite:AuthenticatorNavigation.kt$( onNavigateBack = onNavigateBack, onNavigateToSearch = { navController.navigateToSearch() }, onNavigateToQrCodeScanner = { navController.navigateToQrCodeScanScreen() }, onNavigateToManualKeyEntry = { navController.navigateToManualCodeEntryScreen() }, onNavigateToEditItem = { navController.navigateToEditItem(itemId = it) }, onNavigateToExport = { navController.navigateToExport() }, onNavigateToImport = { navController.navigateToImporting() }, onNavigateToTutorial = { navController.navigateToSettingsTutorial() } ) - TrailingCommaOnCallSite:AuthenticatorNavigation.kt$( startDestination = AUTHENTICATOR_NAV_BAR_ROUTE, route = AUTHENTICATOR_GRAPH_ROUTE ) - TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( ExportJsonData( encrypted = false, items = authenticatorDiskSource .getItems() .firstOrNull() .orEmpty() .map { it.toExportJsonItem() }, ) ) - TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( id = itemId, key = updateItemRequest.key, accountName = updateItemRequest.accountName, type = updateItemRequest.type, period = updateItemRequest.period, digits = updateItemRequest.digits, issuer = updateItemRequest.issuer, userId = null, favorite = updateItemRequest.favorite ) - TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( importFileFormat = format, byteArray = it ) - TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( onSuccess = { it }, onFailure = { ImportDataResult.Error() } ) - TrailingCommaOnCallSite:AuthenticatorSdkSourceImpl.kt$AuthenticatorSdkSourceImpl$( PasswordGeneratorRequest( lowercase = true, uppercase = true, numbers = true, special = true, length = 7.toUByte(), avoidAmbiguous = true, minLowercase = null, minUppercase = null, minNumber = null, minSpecial = null ) ) - TrailingCommaOnCallSite:AuthenticatorSdkSourceImpl.kt$AuthenticatorSdkSourceImpl$( lowercase = true, uppercase = true, numbers = true, special = true, length = 7.toUByte(), avoidAmbiguous = true, minLowercase = null, minUppercase = null, minNumber = null, minSpecial = null ) - TrailingCommaOnCallSite:BitwardenExportParser.kt$BitwardenExportParser$( items = exportData .items .filter { it.login?.totp != null } .toAuthenticatorItemEntities() ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.AlgorithmOptionClick(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.ExpandAdvancedOptionsClick ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.FavoriteToggleClick(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.IssuerNameTextChange(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.NumberOfDigitsOptionClick(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.RefreshPeriodOptionClick(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.TotpCodeTextChange(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.TypeOptionClick(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.UsernameTextChange(it) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( context, event.message(resources), Toast.LENGTH_LONG ) - TrailingCommaOnCallSite:EditItemScreen.kt$( dialogState = state.dialog, onDismissRequest = { viewModel.trySendAction(EditItemAction.DismissDialog) } ) - TrailingCommaOnCallSite:EditItemScreen.kt$( iconData = IconData.Local( iconRes = if (viewState.isAdvancedOptionsExpanded) { R.drawable.ic_chevron_up } else { R.drawable.ic_chevron_down } ), contentDescription = stringResource( id = R.string.collapse_advanced_options ), tint = MaterialTheme.colorScheme.primary ) - TrailingCommaOnCallSite:EditItemScreen.kt$( iconRes = if (viewState.isAdvancedOptionsExpanded) { R.drawable.ic_chevron_up } else { R.drawable.ic_chevron_down } ) - TrailingCommaOnCallSite:EditItemScreen.kt$( id = R.string.collapse_advanced_options ) - TrailingCommaOnCallSite:EditItemScreen.kt$( id = R.string.refresh_period_seconds, it.seconds ) - TrailingCommaOnCallSite:EditItemScreen.kt$( indication = rememberRipple( bounded = true, color = MaterialTheme.colorScheme.primary, ), interactionSource = remember { MutableInteractionSource() } ) - TrailingCommaOnCallSite:EditItemScreen.kt$( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), viewState = viewState, onExpandStateChange = onExpandAdvancedOptionsClicked, onAlgorithmOptionClicked = onAlgorithmOptionClicked, onTypeOptionClicked = onTypeOptionClicked, onRefreshPeriodOptionClicked = onRefreshPeriodOptionClicked, onNumberOfDigitsChanged = onNumberOfDigitsChanged ) - TrailingCommaOnCallSite:EditItemScreen.kt$( modifier = Modifier .fillMaxWidth(), label = stringResource(id = R.string.refresh_period), options = refreshPeriodOptionsWithStrings.values.toImmutableList(), selectedOption = stringResource( id = R.string.refresh_period_seconds, viewState.itemData.refreshPeriod.seconds, ), onOptionSelected = remember(viewState) { { selectedOption -> val selectedOptionName = refreshPeriodOptionsWithStrings .entries .first { it.value == selectedOption } .key onRefreshPeriodOptionClicked(selectedOptionName) } } ) - TrailingCommaOnCallSite:EditItemScreen.kt$( modifier = Modifier .imePadding() .padding(innerPadding), viewState = viewState, onIssuerNameTextChange = remember(viewModel) { { viewModel.trySendAction( EditItemAction.IssuerNameTextChange(it) ) } }, onUsernameTextChange = remember(viewModel) { { viewModel.trySendAction( EditItemAction.UsernameTextChange(it) ) } }, onToggleFavorite = remember(viewModel) { { viewModel.trySendAction( EditItemAction.FavoriteToggleClick(it) ) } }, onTypeOptionClicked = remember(viewModel) { { viewModel.trySendAction( EditItemAction.TypeOptionClick(it) ) } }, onTotpCodeTextChange = remember(viewModel) { { viewModel.trySendAction( EditItemAction.TotpCodeTextChange(it) ) } }, onAlgorithmOptionClicked = remember(viewModel) { { viewModel.trySendAction( EditItemAction.AlgorithmOptionClick(it) ) } }, onRefreshPeriodOptionClicked = remember(viewModel) { { viewModel.trySendAction( EditItemAction.RefreshPeriodOptionClick(it) ) } }, onNumberOfDigitsChanged = remember(viewModel) { { viewModel.trySendAction( EditItemAction.NumberOfDigitsOptionClick(it) ) } }, onExpandAdvancedOptionsClicked = remember(viewModel) { { viewModel.trySendAction( EditItemAction.ExpandAdvancedOptionsClick ) } } ) - TrailingCommaOnCallSite:EditItemScreen.kt$( title = dialogState.title, message = dialogState.message ) - TrailingCommaOnCallSite:EditItemScreen.kt$( title = stringResource( id = R.string.edit_item, ), scrollBehavior = scrollBehavior, navigationIcon = painterResource(id = R.drawable.ic_close), navigationIconContentDescription = stringResource(id = R.string.close), onNavigationIconClick = remember(viewModel) { { viewModel.trySendAction(EditItemAction.CancelClick) } }, actions = { BitwardenTextButton( label = stringResource(id = R.string.save), onClick = remember(viewModel) { { viewModel.trySendAction(EditItemAction.SaveClick) } }, modifier = Modifier.semantics { testTag = "SaveButton" }, ) } ) - TrailingCommaOnCallSite:EditItemScreen.kt$( viewState = EditItemState.ViewState.Content( isAdvancedOptionsExpanded = false, itemData = EditItemData( refreshPeriod = AuthenticatorRefreshPeriodOption.THIRTY, totpCode = "123456", type = AuthenticatorItemType.TOTP, username = "account name", issuer = "issuer", algorithm = AuthenticatorItemAlgorithm.SHA1, digits = 6, favorite = false, ), minDigitsAllowed = 5, maxDigitsAllowed = 10, ) ) - TrailingCommaOnCallSite:EditItemScreen.kt$( viewState = EditItemState.ViewState.Content( isAdvancedOptionsExpanded = true, itemData = EditItemData( refreshPeriod = AuthenticatorRefreshPeriodOption.THIRTY, totpCode = "123456", type = AuthenticatorItemType.TOTP, username = "account name", issuer = "issuer", algorithm = AuthenticatorItemAlgorithm.SHA1, digits = 6, favorite = true, ), minDigitsAllowed = 5, maxDigitsAllowed = 10, ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( AuthenticatorItemEntity( id = state.itemId, key = content.itemData.totpCode.trim(), accountName = content.itemData.username?.trim(), type = content.itemData.type, algorithm = content.itemData.algorithm, period = content.itemData.refreshPeriod.seconds, digits = content.itemData.digits, issuer = content.itemData.issuer.trim(), favorite = content.itemData.favorite, ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( R.string.saving.asText() ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( algorithm = action.algorithmOption ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), message = R.string.key_is_invalid.asText() ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), message = R.string.validation_field_required.asText(R.string.key.asText()), ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), message = R.string.validation_field_required.asText(R.string.name.asText()), ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( dialog = EditItemState.DialogState.Loading( R.string.saving.asText() ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( digits = action.digits ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( favorite = action.favorite ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( initialState = savedStateHandle[KEY_STATE] ?: EditItemState( itemId = EditItemArgs(savedStateHandle).itemId, viewState = EditItemState.ViewState.Loading, dialog = null, ) ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( isAdvancedOptionsExpanded = currentContent.isAdvancedOptionsExpanded.not() ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( issuer = action.issuerName ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( message = R.string.generic_error_message.asText() ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( refreshPeriod = action.period ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( title = R.string.an_error_has_occurred.asText(), message = R.string.key_is_invalid.asText() ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( totpCode = action.totpCode ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( type = action.typeOption ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( username = action.username ) - TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( viewState = EditItemState.ViewState.Loading ) - TrailingCommaOnCallSite:ExpandableFloatingActionButton.kt$( onFabOptionClick = { expandableFabState.value = expandableFabState.value.toggleValue() onStateChange(expandableFabState.value) expandableFabOption.onFabOptionClick() }, expandableFabOption = expandableFabOption ) - TrailingCommaOnCallSite:ExportScreen.kt$( modifier = Modifier .padding(paddingValues) .fillMaxSize(), state = state, onExportFormatOptionSelected = remember(viewModel) { { viewModel.trySendAction(ExportAction.ExportFormatOptionSelect(it)) } }, onExportClick = { shouldShowConfirmationPrompt = true } ) - TrailingCommaOnCallSite:ExportScreen.kt$( modifier = modifier .imePadding() .verticalScroll(rememberScrollState()) ) - TrailingCommaOnCallSite:ExportScreen.kt$( text = dialog.message ) - TrailingCommaOnCallSite:ExportScreen.kt$( visibilityState = BasicDialogState.Shown( title = dialog.title, message = dialog.message, ), onDismissRequest = remember(viewModel) { { viewModel.trySendAction(ExportAction.DialogDismiss) } } ) - TrailingCommaOnCallSite:ExportScreen.kt$( visibilityState = LoadingDialogState.Shown( text = dialog.message ) ) - TrailingCommaOnCallSite:ExportViewModel.kt$ExportViewModel$( ExportAction.Internal.SaveExportDataToUriResultReceive( result = result ) ) - TrailingCommaOnCallSite:ExportViewModel.kt$ExportViewModel$( initialState = ExportState(dialogState = null, exportFormat = ExportFormat.JSON) ) - TrailingCommaOnCallSite:ExportViewModel.kt$ExportViewModel$( result = result ) - TrailingCommaOnCallSite:FakeFeatureFlagRepository.kt$( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue) ) - TrailingCommaOnCallSite:FakeFeatureFlagRepository.kt$( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue) ) ) - TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true) ) - TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true) ) ) - TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$FeatureFlagDiskSourceTest$( FEATURE_FLAGS_CONFIGURATION_JSON ) - TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$FeatureFlagDiskSourceTest$( fakeSharedPreferences.getString(featureFlagsConfigKey, null)!! ) - TrailingCommaOnCallSite:FeatureFlagManagerTest.kt$( mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true), ) ) - TrailingCommaOnCallSite:FeatureFlagManagerTest.kt$FeatureFlagManagerTest$( featureFlags = emptyMap() ) - TrailingCommaOnCallSite:FeatureFlagRepositoryImpl.kt$FeatureFlagRepositoryImpl$( LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue ) - TrailingCommaOnCallSite:FeatureFlagRepositoryImpl.kt$FeatureFlagRepositoryImpl$( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive( LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue ) ) - TrailingCommaOnCallSite:FeatureFlagRepositoryImpl.kt$FeatureFlagRepositoryImpl$( mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive( LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue ) ) ) - TrailingCommaOnCallSite:FeatureFlagRepositoryTest.kt$( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue), ) ) - TrailingCommaOnCallSite:FeatureFlagRepositoryTest.kt$FeatureFlagRepositoryTest$( FEATURE_FLAGS_CONFIG, fakeFeatureFlagDiskSource.featureFlagsConfiguration ) - TrailingCommaOnCallSite:ImportingScreen.kt$( modifier = modifier .imePadding() .verticalScroll(rememberScrollState()) ) - TrailingCommaOnCallSite:ImportingScreen.kt$( text = dialog.message ) - TrailingCommaOnCallSite:ImportingScreen.kt$( visibilityState = BasicDialogState.Shown( title = dialog.title, message = dialog.message, ), onDismissRequest = remember(viewModel) { { viewModel.trySendAction(ImportAction.DialogDismiss) } } ) - TrailingCommaOnCallSite:ImportingScreen.kt$( visibilityState = LoadingDialogState.Shown( text = dialog.message ) ) - TrailingCommaOnCallSite:ImportingViewModel.kt$ImportingViewModel$( ImportEvent.ShowToast( message = R.string.import_success.asText(), ) ) - TrailingCommaOnCallSite:ImportingViewModel.kt$ImportingViewModel$( dialogState = ImportState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = result.message ?: R.string.import_vault_failure.asText(), ) ) - TrailingCommaOnCallSite:ImportingViewModel.kt$ImportingViewModel$( initialState = ImportState(importFileFormat = ImportFileFormat.BITWARDEN_JSON) ) - TrailingCommaOnCallSite:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction.EnterSetupKey$( label, icon, onEnterSetupKeyClick ) - TrailingCommaOnCallSite:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction.ScanQrCode$( label, icon, onScanQrCodeClick ) - TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( navController = navController, onNavigateToExport = navigateToExport, onNavigateToImport = navigateToImport, onNavigateToTutorial = navigateToTutorial ) - TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( onNavigateBack = { navController.popBackStack() } ) - TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( onNavigateBack = { navController.popBackStack() }, onNavigateToQrCodeScreen = { navController.popBackStack() navController.navigateToQrCodeScanScreen() } ) - TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( route = ITEM_LISTING_GRAPH_ROUTE, navOptions = navOptions ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.DeleteItemClick(it) ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.EditItemClick(it) ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.ItemClick(it) ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.SearchClick ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingExpandableFabAction.ScanQrCode( label = R.string.scan_a_qr_code.asText(), icon = IconResource( iconPainter = painterResource(id = R.drawable.ic_camera), contentDescription = stringResource(id = R.string.scan_a_qr_code), testTag = "ScanQRCodeButton", ), onScanQrCodeClick = onScanQrCodeClick, ), ItemListingExpandableFabAction.EnterSetupKey( label = R.string.enter_key_manually.asText(), icon = IconResource( iconPainter = painterResource(id = R.drawable.ic_keyboard_24px), contentDescription = stringResource(id = R.string.enter_key_manually), testTag = "EnterSetupKeyButton", ), onEnterSetupKeyClick = onEnterSetupKeyClick ) ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingExpandableFabAction.ScanQrCode( label = R.string.scan_a_qr_code.asText(), icon = IconResource( iconPainter = painterResource(id = R.drawable.ic_camera), contentDescription = stringResource(id = R.string.scan_a_qr_code), testTag = "ScanQRCodeButton", ), onScanQrCodeClick = onScanQuCodeClick ), ItemListingExpandableFabAction.EnterSetupKey( label = R.string.enter_key_manually.asText(), icon = IconResource( iconPainter = painterResource(id = R.drawable.ic_keyboard_24px), contentDescription = stringResource(id = R.string.enter_key_manually), testTag = "EnterSetupKeyButton", ), onEnterSetupKeyClick = onEnterSetupKeyClick, ) ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( appTheme = state.appTheme, scrollBehavior = scrollBehavior, onAddCodeClick = remember(viewModel) { { launcher.launch(Manifest.permission.CAMERA) } }, onScanQuCodeClick = remember(viewModel) { { launcher.launch(Manifest.permission.CAMERA) } }, onEnterSetupKeyClick = remember(viewModel) { { viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) } } ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( context, event.message(context.resources), Toast.LENGTH_LONG ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( currentState, scrollBehavior, onNavigateToSearch = remember(viewModel) { { viewModel.trySendAction( ItemListingAction.SearchClick ) } }, onScanQrCodeClick = remember(viewModel) { { launcher.launch(Manifest.permission.CAMERA) } }, onEnterSetupKeyClick = remember(viewModel) { { viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) } }, onItemClick = remember(viewModel) { { viewModel.trySendAction( ItemListingAction.ItemClick(it) ) } }, onEditItemClick = remember(viewModel) { { viewModel.trySendAction( ItemListingAction.EditItemClick(it) ) } }, onDeleteItemClick = remember(viewModel) { { viewModel.trySendAction( ItemListingAction.DeleteItemClick(it) ) } } ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( id = when (appTheme) { AppTheme.DARK -> R.drawable.ic_empty_vault_dark AppTheme.LIGHT -> R.drawable.ic_empty_vault_light AppTheme.DEFAULT -> R.drawable.ic_empty_vault } ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( label = R.string.enter_key_manually.asText(), icon = IconResource( iconPainter = painterResource(id = R.drawable.ic_keyboard_24px), contentDescription = stringResource(id = R.string.enter_key_manually), testTag = "EnterSetupKeyButton", ), onEnterSetupKeyClick = onEnterSetupKeyClick ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( label = R.string.scan_a_qr_code.asText(), icon = IconResource( iconPainter = painterResource(id = R.drawable.ic_camera), contentDescription = stringResource(id = R.string.scan_a_qr_code), testTag = "ScanQRCodeButton", ), onScanQrCodeClick = onScanQuCodeClick ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( rememberTopAppBarState() ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( title = stringResource(id = R.string.delete), message = dialog.message(), confirmButtonText = stringResource(id = R.string.ok), dismissButtonText = stringResource(id = R.string.cancel), onConfirmClick = { onConfirmDeleteClick(dialog.itemId) }, onDismissClick = onDismissRequest, onDismissRequest = onDismissRequest ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( title = stringResource(id = R.string.verification_codes), scrollBehavior = scrollBehavior, actions = { BitwardenSearchActionItem( contentDescription = stringResource(id = R.string.search_codes), onClick = onNavigateToSearch, ) } ) - TrailingCommaOnCallSite:ItemListingScreen.kt$( title = stringResource(id = R.string.verification_codes), scrollBehavior = scrollBehavior, navigationIcon = null, actions = { } ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( ItemListingAction.Internal.DeleteItemReceive( authenticatorRepository.hardDeleteItem(action.itemId) ) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( ItemListingEvent.ShowToast( message = R.string.value_has_been_copied.asText(action.authCode) ) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = action.data, clearDialogState = false ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = authenticatorData.data, clearDialogState = false ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = authenticatorData.data, clearDialogState = true ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = state.data, clearDialogState = true ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorRepository.hardDeleteItem(action.itemId) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( byteArray = otpParam.secret.toByteArray() ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( dialog = ItemListingState.DialogState.DeleteConfirmationPrompt( message = R.string.do_you_really_want_to_permanently_delete_cipher.asText(), itemId = action.itemId, ) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( dialog = ItemListingState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.authenticator_key_read_error.asText(), ) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( dialog = ItemListingState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.generic_error_message.asText(), ) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( initialState = ItemListingState( settingsRepository.appTheme, settingsRepository.authenticatorAlertThresholdSeconds, viewState = ItemListingState.ViewState.Loading, dialog = null ) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( message = R.string.internet_connection_required_title .asText() .concat(R.string.internet_connection_required_message.asText()) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( message = R.string.value_has_been_copied.asText(action.authCode) ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( settingsRepository.appTheme, settingsRepository.authenticatorAlertThresholdSeconds, viewState = ItemListingState.ViewState.Loading, dialog = null ) - TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( viewState = ItemListingState.ViewState.Loading ) - TrailingCommaOnCallSite:ItemSearchNavigation.kt$( onNavigateBack = onNavigateBack ) - TrailingCommaOnCallSite:ItemSearchScreen.kt$( viewState = viewState, modifier = innerModifier ) - TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( authenticatorData = authenticatorData, clearDialogState = false ) - TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( initialState = ItemSearchState( searchTerm = "", viewState = ItemSearchState.ViewState.Loading, dialogState = null, ) ) - TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( message = R.string.there_are_no_items_that_match_the_search.asText() ) - TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( searchTerm = currentState.searchTerm, viewState = authenticatorData .filterAndOrganize(state.searchTerm) .toViewState(searchTerm = state.searchTerm), dialogState = currentState.dialogState.takeUnless { clearDialogState } ) - TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( MainAction.ReceiveFirstIntent( intent = intent ) ) - TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( MainAction.ReceiveNewIntent(intent = intent) ) - TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( intent = intent ) - TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( onSplashScreenRemoved = { shouldShowSplashScreen = false }, onExitApplication = { finishAffinity() } ) - TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( theme = state.theme ) - TrailingCommaOnCallSite:MainViewModel.kt$MainViewModel$( MainState( theme = settingsRepository.appTheme ) ) - TrailingCommaOnCallSite:MainViewModel.kt$MainViewModel$( theme = settingsRepository.appTheme ) - TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( dialog.message ) - TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( title = dialog.title, message = dialog.message ) - TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( visibilityState = BasicDialogState.Shown( title = dialog.title, message = dialog.message ), onDismissRequest = remember(state) { { viewModel.trySendAction(ManualCodeEntryAction.DismissDialog) } } ) - TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( visibilityState = LoadingDialogState.Shown( dialog.message ) ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( AuthenticatorItemEntity( id = UUID.randomUUID().toString(), key = state.code, issuer = state.issuer, accountName = "", userId = null, type = if (state.code.startsWith("steam://")) { AuthenticatorItemType.STEAM } else { AuthenticatorItemType.TOTP }, favorite = false, ) ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( message = R.string.key_is_invalid.asText() ) ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( message = R.string.key_is_required.asText() ) ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( message = R.string.name_is_required.asText() ) ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.generic_error_message.asText(), ) ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( message = R.string.key_is_invalid.asText() ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( message = R.string.key_is_required.asText() ) - TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( message = R.string.name_is_required.asText() ) - TrailingCommaOnCallSite:QrCodeAnalyzerImpl.kt$QrCodeAnalyzerImpl$( DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE), DecodeHintType.ALSO_INVERTED to true ) - TrailingCommaOnCallSite:RootNavScreen.kt$( onUnlocked = { viewModel.trySendAction(RootNavAction.Internal.AppUnlocked) } ) - TrailingCommaOnCallSite:RootNavViewModel.kt$RootNavViewModel$( initialState = RootNavState( hasSeenWelcomeGuide = settingsRepository.hasSeenWelcomeTutorial, navState = RootNavState.NavState.Splash, ) ) - TrailingCommaOnCallSite:SearchHandlers.kt$SearchHandlers.Companion$( onBackClick = { viewModel.trySendAction(ItemSearchAction.BackClick) }, onDismissRequest = { viewModel.trySendAction(ItemSearchAction.DismissDialogClick) }, onItemClick = { viewModel.trySendAction(ItemSearchAction.ItemClick(it)) }, onSearchTermChange = { viewModel.trySendAction(ItemSearchAction.SearchTermChange(it)) } ) - TrailingCommaOnCallSite:SettingsDiskSourceImpl.kt$SettingsDiskSourceImpl$( ALERT_THRESHOLD_SECONDS_KEY, thresholdSeconds ) - TrailingCommaOnCallSite:SettingsNavigation.kt$( onNavigateBack = { navController.popBackStack() } ) - TrailingCommaOnCallSite:SettingsNavigation.kt$( onTutorialFinished = { navController.popBackStack() } ) - TrailingCommaOnCallSite:SettingsNavigation.kt$( route = SETTINGS_ROUTE ) - TrailingCommaOnCallSite:SettingsNavigation.kt$( startDestination = SETTINGS_ROUTE, route = SETTINGS_GRAPH_ROUTE ) - TrailingCommaOnCallSite:SettingsScreen.kt$( SettingsAction.SecurityClick.UnlockWithBiometricToggle(it) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( id = R.drawable.ic_navigate_next ) - TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier .padding(innerPadding) .fillMaxSize() .verticalScroll(state = rememberScrollState()) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.appearance) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.data) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.help) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.security) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = modifier, label = stringResource(id = R.string.about) ) - TrailingCommaOnCallSite:SettingsScreen.kt$( onExportClick = remember(viewModel) { { viewModel.trySendAction(SettingsAction.DataClick.ExportClick) } }, onImportClick = remember(viewModel) { { viewModel.trySendAction(SettingsAction.DataClick.ImportClick) } }, onBackupClick = remember(viewModel) { { viewModel.trySendAction(SettingsAction.DataClick.BackupClick) } } ) - TrailingCommaOnCallSite:SettingsScreen.kt$( state = state, biometricsManager = biometricsManager, onBiometricToggle = remember(viewModel) { { viewModel.trySendAction( SettingsAction.SecurityClick.UnlockWithBiometricToggle(it) ) } } ) - TrailingCommaOnCallSite:SettingsScreen.kt$( text = stringResource(id = R.string.export), onClick = onExportClick, modifier = modifier, withDivider = true, content = { Icon( modifier = Modifier .mirrorIfRtl() .size(24.dp), painter = painterResource(id = R.drawable.ic_navigate_next), contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, ) } ) - TrailingCommaOnCallSite:SettingsScreen.kt$( text = stringResource(id = R.string.import_vault), onClick = onImportClick, modifier = modifier, withDivider = true, content = { Icon( modifier = Modifier .mirrorIfRtl() .size(24.dp), painter = painterResource(id = R.drawable.ic_navigate_next), contentDescription = null, tint = MaterialTheme.colorScheme.onSurface, ) } ) - TrailingCommaOnCallSite:SettingsScreen.kt$( title = stringResource(id = R.string.settings), scrollBehavior = scrollBehavior ) - TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( appearance = it.appearance.copy(language = language) ) - TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( appearance = it.appearance.copy(theme = theme) ) - TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( clock, settingsRepository.appLanguage, settingsRepository.appTheme, settingsRepository.isUnlockWithBiometricsEnabled, settingsRepository.isCrashLoggingEnabled ) - TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( initialState = savedStateHandle[KEY_STATE] ?: createInitialState( clock, settingsRepository.appLanguage, settingsRepository.appTheme, settingsRepository.isUnlockWithBiometricsEnabled, settingsRepository.isCrashLoggingEnabled ) ) - TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel.Companion$( appearance = SettingsState.Appearance( language = appLanguage, theme = appTheme, ), isUnlockWithBiometricsEnabled = unlockWithBiometricsEnabled, isSubmitCrashLogsEnabled = isSubmitCrashLogsEnabled, dialog = null, version = R.string.version .asText() .concat(": ${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})".asText()), copyrightInfo = copyrightInfo ) - TrailingCommaOnCallSite:TutorialScreen.kt$( R.string.scan_the_qr_code_in_your_2_step_verification_settings_for_any_account ) - TrailingCommaOnCallSite:TutorialScreen.kt$( R.string.use_your_device_camera_to_scan_codes ) - TrailingCommaOnCallSite:TutorialScreen.kt$( R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app ) - TrailingCommaOnCallSite:TutorialScreen.kt$( TutorialAction.ContinueClick ) - TrailingCommaOnCallSite:TutorialScreen.kt$( TutorialAction.TutorialPageChange(pagerState.targetPage) ) - TrailingCommaOnCallSite:TutorialScreen.kt$( modifier = Modifier .height(50.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.Center ) - TrailingCommaOnCallSite:TutorialScreen.kt$( modifier = Modifier .padding(8.dp) .background(color, CircleShape) .size(10.dp) ) - TrailingCommaOnCallSite:TutorialScreen.kt$( modifier = Modifier .padding(innerPadding), verticalArrangement = Arrangement.Center ) - TrailingCommaOnCallSite:TutorialScreen.kt$( onTutorialFinished = {} ) - TrailingCommaOnCallSite:TutorialScreen.kt$( painter = painterResource(id = R.drawable.ic_tutorial_2fa), contentDescription = stringResource(id = R.string.unique_codes) ) - TrailingCommaOnCallSite:TutorialViewModel.kt$TutorialState.QrScannerSlide$( continueButtonText = R.string.continue_button.asText(), isLastPage = false ) - TrailingCommaOnCallSite:TutorialViewModel.kt$TutorialState.UniqueCodesSlide$( continueButtonText = R.string.get_started.asText(), isLastPage = true ) - TrailingCommaOnCallSite:TutorialViewModel.kt$TutorialViewModel$( initialState = TutorialState.IntroSlide ) - TrailingCommaOnCallSite:TwoFasExportParser.kt$TwoFasExportParser$( items = exportData.services.toAuthenticatorItemEntities() ) - TrailingCommaOnCallSite:UnlockScreen.kt$( modifier = Modifier .padding(innerPadding) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) - TrailingCommaOnCallSite:UnlockScreen.kt$( title = R.string.an_error_has_occurred.asText(), message = dialog.message ) - TrailingCommaOnCallSite:UnlockScreen.kt$( visibilityState = LoadingDialogState.Shown(R.string.loading.asText()) ) - TrailingCommaOnCallSite:UnlockViewModel.kt$UnlockViewModel$( dialog = UnlockState.Dialog.Error( message = R.string.too_many_failed_biometric_attempts.asText(), ) ) - TrailingCommaOnCallSite:UnlockViewModel.kt$UnlockViewModel$( initialState = savedStateHandle[KEY_STATE] ?: run { UnlockState( isBiometricsEnabled = settingsRepository.isUnlockWithBiometricsEnabled, isBiometricsValid = biometricsEncryptionManager.isBiometricIntegrityValid(), dialog = null, ) } ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(color = MaterialTheme.colorScheme.primary), onClick = onItemClick, onLongClick = { shouldShowDropdownMenu = true } ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( painter = painterResource(id = R.drawable.ic_edit_item), contentDescription = stringResource(R.string.edit_item) ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( startIcon = IconData.Local(R.drawable.ic_login_item), issuer = "Sample Label", supportingLabel = "Supporting Label", authCode = "1234567890".chunked(3).joinToString(" "), timeLeftSeconds = 15, periodSeconds = 30, onCopyClick = {}, onItemClick = {}, modifier = Modifier.padding(horizontal = 16.dp), alertThresholdSeconds = 7 ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( text = { Text(text = stringResource(id = R.string.delete_item)) }, onClick = { shouldShowDropdownMenu = false onDeleteItemClick() }, leadingIcon = { Icon( painter = painterResource(id = R.drawable.ic_delete_item), contentDescription = stringResource(id = R.string.delete_item), ) } ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( text = { Text(text = stringResource(id = R.string.edit_item)) }, onClick = { shouldShowDropdownMenu = false onEditItemClick() }, leadingIcon = { Icon( painter = painterResource(id = R.drawable.ic_edit_item), contentDescription = stringResource(R.string.edit_item) ) } ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( timeLeftSeconds = timeLeftSeconds, periodSeconds = periodSeconds, alertThresholdSeconds = alertThresholdSeconds ) - TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( vertical = 8.dp, horizontal = 16.dp ) - TrailingCommaOnDeclarationSite:ItemSearchViewModel.kt$SortPriority - UndocumentedPublicClass:AegisExportParser.kt$AegisExportParser : ExportParser - UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport - UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport$Database - UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport.Database$Entry - UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport.Database$Group - UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport.Database.Entry$Info - UndocumentedPublicClass:AuthRepositoryImpl.kt$AuthRepositoryImpl : AuthRepository - UndocumentedPublicClass:AuthenticatorApplication.kt$AuthenticatorApplication : Application - UndocumentedPublicClass:AuthenticatorDiskSourceImpl.kt$AuthenticatorDiskSourceImpl : AuthenticatorDiskSource - UndocumentedPublicClass:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl : AuthenticatorRepository - UndocumentedPublicClass:AuthenticatorSdkSourceImpl.kt$AuthenticatorSdkSourceImpl : AuthenticatorSdkSource - UndocumentedPublicClass:BitwardenExportParser.kt$BitwardenExportParser : ExportParser - UndocumentedPublicClass:EditItemViewModel.kt$EditItemAction$ExpandAdvancedOptionsClick : EditItemAction - UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabIcon - UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabOption - UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabState - UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabState$Collapsed : ExpandableFabState - UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabState$Expanded : ExpandableFabState - UndocumentedPublicClass:ExportDataResult.kt$ExportDataResult$Error : ExportDataResult - UndocumentedPublicClass:ExportDataResult.kt$ExportDataResult$Success : ExportDataResult - UndocumentedPublicClass:ExportViewModel.kt$ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction$CloseButtonClick : ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction$ConfirmExportClick : ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction$DialogDismiss : ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction$ExportFormatOptionSelect : ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction$ExportLocationReceive : ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction$Internal : ExportAction - UndocumentedPublicClass:ExportViewModel.kt$ExportAction.Internal$SaveExportDataToUriResultReceive : Internal - UndocumentedPublicClass:ExportViewModel.kt$ExportEvent - UndocumentedPublicClass:ExportViewModel.kt$ExportEvent$NavigateBack : ExportEvent - UndocumentedPublicClass:ExportViewModel.kt$ExportEvent$NavigateToSelectExportDestination : ExportEvent - UndocumentedPublicClass:ExportViewModel.kt$ExportEvent$ShowToast : ExportEvent - UndocumentedPublicClass:ExportViewModel.kt$ExportState - UndocumentedPublicClass:ExportViewModel.kt$ExportState$DialogState - UndocumentedPublicClass:ExportViewModel.kt$ExportState.DialogState$Error : DialogState - UndocumentedPublicClass:ExportViewModel.kt$ExportState.DialogState$Loading : DialogState - UndocumentedPublicClass:ExportViewModel.kt$ExportViewModel : BaseViewModel - UndocumentedPublicClass:FileManagerImpl.kt$FileManagerImpl : FileManager - UndocumentedPublicClass:ImportDataResult.kt$ImportDataResult$Error : ImportDataResult - UndocumentedPublicClass:ImportDataResult.kt$ImportDataResult$Success : ImportDataResult - UndocumentedPublicClass:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction : ExpandableFabOption - UndocumentedPublicClass:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction$EnterSetupKey : ItemListingExpandableFabAction - UndocumentedPublicClass:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction$ScanQrCode : ItemListingExpandableFabAction - UndocumentedPublicClass:ItemListingViewModel.kt$ItemListingState.DialogState$DeleteConfirmationPrompt : DialogState - UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport - UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport$Account - UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport$Folder - UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport.Account$BackupInfo - UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport.Account$FolderData - UndocumentedPublicClass:MainActivity.kt$MainActivity : AppCompatActivity - UndocumentedPublicClass:ManualCodeEntryViewModel.kt$ManualCodeEntryState$DialogState : Parcelable - UndocumentedPublicClass:ManualCodeEntryViewModel.kt$ManualCodeEntryState.DialogState$Error : DialogState - UndocumentedPublicClass:ManualCodeEntryViewModel.kt$ManualCodeEntryState.DialogState$Loading : DialogState - UndocumentedPublicClass:PlatformDiskModule.kt$PlatformDiskModule - UndocumentedPublicClass:PlatformNetworkModule.kt$PlatformNetworkModule - UndocumentedPublicClass:RootNavViewModel.kt$RootNavState$NavState : Parcelable - UndocumentedPublicClass:RootNavViewModel.kt$RootNavViewModel : BaseViewModel - UndocumentedPublicClass:SettingsViewModel.kt$SettingsAction.SecurityClick$UnlockWithBiometricToggle : SecurityClick - UndocumentedPublicClass:SettingsViewModel.kt$SettingsState$Dialog : Parcelable - UndocumentedPublicClass:SettingsViewModel.kt$SettingsState.Dialog$Loading : Dialog - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport$Group - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport$Service - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Badge - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Icon - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Order - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Otp - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service.Icon$IconCollection - UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service.Icon$Label - UndocumentedPublicFunction:AuthenticatorItemAlgorithm.kt$AuthenticatorItemAlgorithm.Companion$fun fromStringOrNull(value: String): AuthenticatorItemAlgorithm? - UndocumentedPublicFunction:AuthenticatorItemEntity.kt$AuthenticatorItemEntity$fun toOtpAuthUriString(): String - UndocumentedPublicFunction:AuthenticatorItemType.kt$AuthenticatorItemType.Companion$fun fromStringOrNull(value: String): AuthenticatorItemType? - UndocumentedPublicFunction:AuthenticatorSdkSource.kt$AuthenticatorSdkSource$suspend fun generateBiometricsKey(): Result<String> - UndocumentedPublicFunction:AuthenticatorTheme.kt$@Composable fun AuthenticatorTheme( theme: AppTheme = AppTheme.DEFAULT, dynamicColor: Boolean = false, content: @Composable () -> Unit, ) - UndocumentedPublicFunction:EditItemScreen.kt$@Composable fun EditItemContent( modifier: Modifier = Modifier, viewState: EditItemState.ViewState.Content, onIssuerNameTextChange: (String) -> Unit = {}, onUsernameTextChange: (String) -> Unit = {}, onToggleFavorite: (Boolean) -> Unit = {}, onTypeOptionClicked: (AuthenticatorItemType) -> Unit = {}, onTotpCodeTextChange: (String) -> Unit = {}, onAlgorithmOptionClicked: (AuthenticatorItemAlgorithm) -> Unit = {}, onRefreshPeriodOptionClicked: (AuthenticatorRefreshPeriodOption) -> Unit = {}, onNumberOfDigitsChanged: (Int) -> Unit = {}, onExpandAdvancedOptionsClicked: () -> Unit = {}, ) - UndocumentedPublicFunction:EditItemViewModel.kt$AuthenticatorRefreshPeriodOption.Companion$fun fromSeconds(seconds: Int) - UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$@Composable fun <T : ExpandableFabOption> ExpandableFloatingActionButton( modifier: Modifier = Modifier, label: Text?, items: List<T>, expandableFabState: MutableState<ExpandableFabState> = rememberExpandableFabState(), expandableFabIcon: ExpandableFabIcon, onStateChange: (expandableFabState: ExpandableFabState) -> Unit = { }, ) - UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$@Composable fun rememberExpandableFabState() - UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$ExpandableFabState$fun isExpanded() - UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$ExpandableFabState$fun toggleValue() - UndocumentedPublicFunction:ExportNavigation.kt$fun NavController.navigateToExport(navOptions: NavOptions? = null) - UndocumentedPublicFunction:ExportNavigation.kt$fun NavGraphBuilder.exportDestination( onNavigateBack: () -> Unit, ) - UndocumentedPublicFunction:ExportScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExportScreen( viewModel: ExportViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, ) - UndocumentedPublicFunction:ImportingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ImportingScreen( viewModel: ImportingViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, ) - UndocumentedPublicFunction:ItemListingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable @Preview(showBackground = true) fun EmptyListingContentPreview() - UndocumentedPublicFunction:SearchHandlers.kt$SearchHandlers.Companion$fun create(viewModel: ItemSearchViewModel): SearchHandlers - UndocumentedPublicFunction:SettingsScreen.kt$@Composable fun SecuritySettings( state: SettingsState, biometricsManager: BiometricsManager = LocalBiometricsManager.current, onBiometricToggle: (Boolean) -> Unit, ) - UndocumentedPublicFunction:SettingsScreen.kt$@Composable fun VaultSettings( modifier: Modifier = Modifier, onExportClick: () -> Unit, onImportClick: () -> Unit, onBackupClick: () -> Unit, ) - UndocumentedPublicFunction:SettingsViewModel.kt$SettingsViewModel.Companion$fun createInitialState( clock: Clock, appLanguage: AppLanguage, appTheme: AppTheme, unlockWithBiometricsEnabled: Boolean, isSubmitCrashLogsEnabled: Boolean, ): SettingsState - UndocumentedPublicFunction:TutorialNavigation.kt$fun NavController.navigateToSettingsTutorial(navOptions: NavOptions? = null) - UndocumentedPublicFunction:TutorialNavigation.kt$fun NavController.navigateToTutorial(navOptions: NavOptions? = null) - UndocumentedPublicFunction:TutorialNavigation.kt$fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit) - UndocumentedPublicFunction:TutorialNavigation.kt$fun NavGraphBuilder.tutorialSettingsDestination(onTutorialFinished: () -> Unit) - UndocumentedPublicFunction:TutorialScreen.kt$@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun TutorialScreen( viewModel: TutorialViewModel = hiltViewModel(), onTutorialFinished: () -> Unit, ) - UndocumentedPublicFunction:TutorialScreen.kt$@Preview @Composable fun TutorialScreenPreview() - UndocumentedPublicFunction:UnlockScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun UnlockScreen( viewModel: UnlockViewModel = hiltViewModel(), biometricsManager: BiometricsManager = LocalBiometricsManager.current, onUnlocked: () -> Unit, ) - UndocumentedPublicFunction:VerificationCodeItemExtensions.kt$fun List<VerificationCodeItem>.toViewState( alertThresholdSeconds: Int, ): ItemListingState.ViewState - UndocumentedPublicFunction:VerificationCodeItemExtensions.kt$fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int) - -