mirror of
https://github.com/bitwarden/android.git
synced 2026-08-30 17:33:42 -05:00
[BWA-61] Fix pre-existing detekt issues (#179)
This commit is contained in:
@@ -151,8 +151,8 @@ dependencies {
|
||||
}
|
||||
|
||||
detekt {
|
||||
autoCorrect = true
|
||||
config.from(files("$rootDir/detekt-config.yml"))
|
||||
baseline = file("$rootDir/detekt-baseline.xml")
|
||||
}
|
||||
|
||||
kover {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ class MainViewModel @Inject constructor(
|
||||
settingsRepository: SettingsRepository,
|
||||
) : BaseViewModel<MainState, MainEvent, MainAction>(
|
||||
MainState(
|
||||
theme = settingsRepository.appTheme
|
||||
)
|
||||
theme = settingsRepository.appTheme,
|
||||
),
|
||||
) {
|
||||
|
||||
init {
|
||||
@@ -71,7 +71,6 @@ class MainViewModel @Inject constructor(
|
||||
) {
|
||||
// RFU
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
@@ -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() },
|
||||
|
||||
+1
-1
@@ -19,6 +19,6 @@ object AuthRepositoryModule {
|
||||
fun provideAuthRepository(
|
||||
authDiskSource: AuthDiskSource,
|
||||
): AuthRepository = AuthRepositoryImpl(
|
||||
authDiskSource = authDiskSource
|
||||
authDiskSource = authDiskSource,
|
||||
)
|
||||
}
|
||||
|
||||
-1
@@ -22,5 +22,4 @@ interface AuthenticatorDiskSource {
|
||||
* Deletes an authenticator item from the data source with the given [itemId].
|
||||
*/
|
||||
suspend fun deleteItem(itemId: String)
|
||||
|
||||
}
|
||||
|
||||
+4
-1
@@ -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<List<AuthenticatorItemEntity>> = merge(
|
||||
forceItemsFlow,
|
||||
itemDao.getAllItems()
|
||||
itemDao.getAllItems(),
|
||||
)
|
||||
|
||||
override suspend fun deleteItem(itemId: String) {
|
||||
|
||||
+1
-2
@@ -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
|
||||
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ object AuthenticatorDiskModule {
|
||||
.databaseBuilder(
|
||||
context = app,
|
||||
klass = AuthenticatorDatabase::class.java,
|
||||
name = "authenticator_database"
|
||||
name = "authenticator_database",
|
||||
)
|
||||
.fallbackToDestructiveMigration()
|
||||
.addTypeConverter(AuthenticatorItemTypeConverter())
|
||||
|
||||
+3
@@ -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) }
|
||||
}
|
||||
|
||||
+4
@@ -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 -> {
|
||||
|
||||
+5
-1
@@ -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) }
|
||||
}
|
||||
|
||||
+3
@@ -16,5 +16,8 @@ interface AuthenticatorSdkSource {
|
||||
time: DateTime,
|
||||
): Result<TotpResponse>
|
||||
|
||||
/**
|
||||
* Generate a random key for seeding biometrics.
|
||||
*/
|
||||
suspend fun generateBiometricsKey(): Result<String>
|
||||
}
|
||||
|
||||
+5
-3
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
-1
@@ -21,5 +21,4 @@ object AuthenticatorSdkModule {
|
||||
fun provideAuthenticatorSdkSource(
|
||||
sdkClientManager: SdkClientManager,
|
||||
): AuthenticatorSdkSource = AuthenticatorSdkSourceImpl(sdkClientManager)
|
||||
|
||||
}
|
||||
|
||||
+3
@@ -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,
|
||||
|
||||
+3
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+2
-3
@@ -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,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
+2
-12
@@ -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
|
||||
|
||||
+17
-33
@@ -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 (
|
||||
|
||||
-1
@@ -35,5 +35,4 @@ object AuthenticatorRepositoryModule {
|
||||
importManager,
|
||||
dispatcherManager,
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
+6
-1
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
+5
-4
@@ -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<Int> = mutableAlertThresholdSecondsFlow
|
||||
.onSubscription { emit(getAlertThresholdSeconds()) }
|
||||
|
||||
+3
@@ -15,6 +15,9 @@ import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
/**
|
||||
* Provides persistence-related dependencies in the platform package.
|
||||
*/
|
||||
object PlatformDiskModule {
|
||||
|
||||
@Provides
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
-1
@@ -15,5 +15,4 @@ interface ImportManager {
|
||||
importFileFormat: ImportFileFormat,
|
||||
byteArray: ByteArray,
|
||||
): ImportDataResult
|
||||
|
||||
}
|
||||
|
||||
+7
-11
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+15
@@ -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<Group>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Models an Aegis database entry.
|
||||
*/
|
||||
@Serializable
|
||||
data class Entry(
|
||||
val type: String,
|
||||
@@ -27,6 +36,9 @@ data class AegisJsonExport(
|
||||
val groups: List<String>,
|
||||
) {
|
||||
|
||||
/**
|
||||
* 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,
|
||||
|
||||
+8
@@ -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()
|
||||
}
|
||||
|
||||
+15
@@ -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<Account>,
|
||||
val folders: List<Folder>,
|
||||
) {
|
||||
/**
|
||||
* 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,
|
||||
|
||||
+27
@@ -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<Group>?,
|
||||
) {
|
||||
/**
|
||||
* 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,
|
||||
|
||||
+4
@@ -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<AegisJsonExport.Database.Entry>.toAuthenticatorItemEntities() =
|
||||
map { it.toAuthenticatorItemEntity() }
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun AegisJsonExport.Database.Entry.toAuthenticatorItemEntity(): AuthenticatorItemEntity {
|
||||
|
||||
// Aegis only supports TOTP codes.
|
||||
|
||||
+5
-1
@@ -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<ExportJsonData.ExportItem>.toAuthenticatorItemEntities() =
|
||||
map { it.toAuthenticatorItemEntity() }
|
||||
|
||||
@Suppress("MaxLineLength", "CyclomaticComplexMethod", "LongMethod")
|
||||
private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity {
|
||||
val otpString = requireNotNull(login?.totp)
|
||||
|
||||
|
||||
+2
-1
@@ -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<TwoFasJsonExport.Service>.toAuthenticatorItemEntities() =
|
||||
mapNotNull { it.toAuthenticatorItemEntityOrNull() }
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun TwoFasJsonExport.Service.toAuthenticatorItemEntityOrNull(): AuthenticatorItemEntity {
|
||||
|
||||
val type = otp.tokenType
|
||||
|
||||
+2
-2
@@ -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<out T : Any>(
|
||||
@@ -37,4 +38,3 @@ sealed class RemoteFeatureFlag<out T : Any>(
|
||||
name: String,
|
||||
defaultValue: T,
|
||||
) : FeatureFlag<T>(name, defaultValue, isRemotelyConfigured = true)
|
||||
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+33
-27
@@ -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,
|
||||
)
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
+28
-20
@@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
+20
-17
@@ -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,
|
||||
|
||||
+65
-60
@@ -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<List<VerificationCodeItem>>) {
|
||||
private fun authenticatorErrorReceive(
|
||||
authenticatorData: DataState.Error<List<VerificationCodeItem>>,
|
||||
) {
|
||||
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,
|
||||
|
||||
+6
-6
@@ -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),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+12
-2
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
+4
-1
@@ -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<VerificationCodeItem>.toViewState(
|
||||
alertThresholdSeconds: Int,
|
||||
): ItemListingState.ViewState =
|
||||
@@ -25,7 +28,7 @@ fun List<VerificationCodeItem>.toViewState(
|
||||
)
|
||||
}
|
||||
|
||||
fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int) =
|
||||
private fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int) =
|
||||
VerificationCodeDisplayItem(
|
||||
id = id,
|
||||
issuer = issuer,
|
||||
|
||||
+4
-4
@@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+25
-53
@@ -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.
|
||||
*/
|
||||
|
||||
+2
-1
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+1
-2
@@ -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()
|
||||
}
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ fun NavGraphBuilder.itemSearchDestination(
|
||||
route = ITEM_SEARCH_ROUTE,
|
||||
) {
|
||||
ItemSearchScreen(
|
||||
onNavigateBack = onNavigateBack
|
||||
onNavigateBack = onNavigateBack,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+13
-7
@@ -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<List<VerificationCodeItem>>) {
|
||||
private fun authenticatorErrorReceive(
|
||||
authenticatorData: DataState<List<VerificationCodeItem>>,
|
||||
) {
|
||||
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,
|
||||
}
|
||||
|
||||
+2
-2
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -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))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+32
-2
@@ -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 <T : ExpandableFabOption> ExpandableFloatingActionButton(
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -78,7 +87,7 @@ fun <T : ExpandableFabOption> ExpandableFloatingActionButton(
|
||||
onStateChange(expandableFabState.value)
|
||||
expandableFabOption.onFabOptionClick()
|
||||
},
|
||||
expandableFabOption = expandableFabOption
|
||||
expandableFabOption = expandableFabOption,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -150,31 +159,52 @@ private fun <T : ExpandableFabOption> ExpandableFabOption(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberExpandableFabState() =
|
||||
private fun rememberExpandableFabState() =
|
||||
remember { mutableStateOf<ExpandableFabState>(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()
|
||||
}
|
||||
|
||||
+2
-1
@@ -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,
|
||||
|
||||
+13
-4
@@ -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 {
|
||||
/**
|
||||
|
||||
+5
-5
@@ -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() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-15
@@ -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,
|
||||
|
||||
+16
-6
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
+9
@@ -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)
|
||||
}
|
||||
|
||||
+14
-10
@@ -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)
|
||||
|
||||
+61
-9
@@ -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<ExportState, ExportEvent, ExportAction>(
|
||||
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()
|
||||
|
||||
+1
-1
@@ -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,
|
||||
}
|
||||
|
||||
+11
-5
@@ -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(
|
||||
|
||||
+3
-3
@@ -24,7 +24,7 @@ class ImportingViewModel @Inject constructor(
|
||||
private val authenticatorRepository: AuthenticatorRepository,
|
||||
) :
|
||||
BaseViewModel<ImportState, ImportEvent, ImportAction>(
|
||||
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)
|
||||
}
|
||||
|
||||
+12
@@ -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)
|
||||
}
|
||||
|
||||
+16
-11
@@ -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 = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -16,7 +16,7 @@ import javax.inject.Inject
|
||||
@HiltViewModel
|
||||
class TutorialViewModel @Inject constructor() :
|
||||
BaseViewModel<TutorialState, TutorialEvent, TutorialAction>(
|
||||
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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -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,
|
||||
|
||||
+7
-7
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
+4
-5
@@ -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),
|
||||
),
|
||||
)
|
||||
|
||||
-1
@@ -97,4 +97,3 @@ private data class ZonedDateTimeData(
|
||||
@SerialName("dataAsZonedDateTime")
|
||||
val dataAsZonedDateTime: ZonedDateTime,
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -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),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
+2
-2
@@ -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),
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
+2
-2
@@ -33,6 +33,6 @@ private val FEATURE_FLAGS_CONFIG =
|
||||
FeatureFlagsConfiguration(
|
||||
featureFlags = mapOf(
|
||||
LocalFeatureFlag.BitwardenAuthenticationEnabled.name to
|
||||
JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue)
|
||||
)
|
||||
JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -26,4 +26,3 @@ abstract class BaseViewModelTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
<?xml version="1.0" ?>
|
||||
<SmellBaseline>
|
||||
<ManuallySuppressedIssues></ManuallySuppressedIssues>
|
||||
<CurrentIssues>
|
||||
<ID>ChainWrapping:EditItemScreen.kt$+</ID>
|
||||
<ID>ChainWrapping:RootNavViewModel.kt$RootNavViewModel$&&</ID>
|
||||
<ID>CyclomaticComplexMethod:BitwardenExportParser.kt$BitwardenExportParser$private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity</ID>
|
||||
<ID>EmptyFunctionBlock:MainViewModelTest.kt$MainViewModelTest${ }</ID>
|
||||
<ID>LongMethod:AuthenticatorNavBarScreen.kt$@Composable private fun AuthenticatorBottomAppBar( navController: NavController, verificationCodesTabClickedAction: () -> Unit, settingsTabClickedAction: () -> Unit, modifier: Modifier = Modifier, )</ID>
|
||||
<ID>LongMethod:BitwardenExportParser.kt$BitwardenExportParser$private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity</ID>
|
||||
<ID>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 = {}, )</ID>
|
||||
<ID>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, )</ID>
|
||||
<ID>LongMethod:EditItemScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun EditItemScreen( viewModel: EditItemViewModel = hiltViewModel(), onNavigateBack: () -> Unit = { }, )</ID>
|
||||
<ID>LongMethod:EditItemViewModel.kt$EditItemViewModel$private fun handleItemDataReceive(action: EditItemAction.Internal.EditItemDataReceive)</ID>
|
||||
<ID>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 = { }, )</ID>
|
||||
<ID>LongMethod:ExportScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExportScreen( viewModel: ExportViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, )</ID>
|
||||
<ID>LongMethod:ImportingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ImportingScreen( viewModel: ImportingViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, )</ID>
|
||||
<ID>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, )</ID>
|
||||
<ID>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, )</ID>
|
||||
<ID>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, )</ID>
|
||||
<ID>LongMethod:ItemSearchScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ItemSearchScreen( viewModel: ItemSearchViewModel = hiltViewModel(), onNavigateBack: () -> Unit, )</ID>
|
||||
<ID>LongMethod:RootNavScreen.kt$@Composable fun RootNavScreen( viewModel: RootNavViewModel = hiltViewModel(), navController: NavHostController = rememberNavController(), onSplashScreenRemoved: () -> Unit = {}, onExitApplication: () -> Unit, )</ID>
|
||||
<ID>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, )</ID>
|
||||
<ID>LongMethod:TutorialScreen.kt$@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun TutorialScreen( viewModel: TutorialViewModel = hiltViewModel(), onTutorialFinished: () -> Unit, )</ID>
|
||||
<ID>LongMethod:UnlockScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun UnlockScreen( viewModel: UnlockViewModel = hiltViewModel(), biometricsManager: BiometricsManager = LocalBiometricsManager.current, onUnlocked: () -> Unit, )</ID>
|
||||
<ID>LongParameterList:AuthenticatorNavBarNavigation.kt$( onNavigateBack: () -> Unit, onNavigateToSearch: () -> Unit, onNavigateToQrCodeScanner: () -> Unit, onNavigateToManualKeyEntry: () -> Unit, onNavigateToEditItem: (itemId: String) -> Unit, onNavigateToExport: () -> Unit, onNavigateToImport: () -> Unit, onNavigateToTutorial: () -> Unit, )</ID>
|
||||
<ID>LongParameterList:ItemListingGraphNavigation.kt$( navController: NavController, navigateBack: () -> Unit, navigateToSearch: () -> Unit, navigateToQrCodeScanner: () -> Unit, navigateToManualKeyEntry: () -> Unit, navigateToEditItem: (String) -> Unit, navigateToExport: () -> Unit, navigateToImport: () -> Unit, navigateToTutorial: () -> Unit, )</ID>
|
||||
<ID>MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$10</ID>
|
||||
<ID>MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$30</ID>
|
||||
<ID>MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$5</ID>
|
||||
<ID>MagicNumber:ItemListingViewModel.kt$ItemListingViewModel$6</ID>
|
||||
<ID>MagicNumber:ItemSearchViewModel.kt$ItemSearchViewModel$8</ID>
|
||||
<ID>MagicNumber:SettingsDiskSourceImpl.kt$SettingsDiskSourceImpl$7</ID>
|
||||
<ID>MatchingDeclarationName:ExportVaultFormat.kt$ExportFormat</ID>
|
||||
<ID>MaxLineLength:AegisExportParser.kt$AegisExportParser$private</ID>
|
||||
<ID>MaxLineLength:BitwardenExportParser.kt$BitwardenExportParser$"${TotpCodeManager.TOTP_CODE_PREFIX}/$name?${TotpCodeManager.SECRET_PARAM}=$otpString"</ID>
|
||||
<ID>MaxLineLength:FeatureFlag.kt$FeatureFlag$*</ID>
|
||||
<ID>MaxLineLength:ImportingScreen.kt$launcher.launch(intentManager.createFileChooserIntent(event.importFileFormat.mimeType))</ID>
|
||||
<ID>MaxLineLength:ItemListingViewModel.kt$ItemListingViewModel$private</ID>
|
||||
<ID>MaxLineLength:ItemSearchViewModel.kt$ItemSearchViewModel$private</ID>
|
||||
<ID>MaxLineLength:TutorialScreen.kt$R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app</ID>
|
||||
<ID>MaxLineLength:TwoFasExportParser.kt$TwoFasExportParser$private</ID>
|
||||
<ID>MaximumLineLength:TutorialScreen.kt$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:AuthenticatorDatabase.kt$AuthenticatorDatabase$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:AuthenticatorDiskSource.kt$AuthenticatorDiskSource$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:AuthenticatorManagerModule.kt$AuthenticatorManagerModule$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:AuthenticatorRepositoryModule.kt$AuthenticatorRepositoryModule$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:AuthenticatorSdkModule.kt$AuthenticatorSdkModule$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:AuthenticatorSdkSourceImpl.kt$AuthenticatorSdkSourceImpl$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:ExportDataResult.kt$ExportDataResult$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:FeatureFlagDiskSourceTest.kt$FeatureFlagDiskSourceTest$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:ImportManager.kt$ImportManager$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:MainViewModel.kt$MainViewModel$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:QrCodeAnalyzerImpl.kt$QrCodeAnalyzerImpl$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:UnlockViewModel.kt$UnlockEvent$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:BaseViewModelTest.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:FeatureFlag.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:ZonedDateTimeSerializerTest.kt$ </ID>
|
||||
<ID>SpacingAroundColon:AuthenticatorManagerModule.kt$AuthenticatorManagerModule$:</ID>
|
||||
<ID>SpacingAroundCurly:ItemSearchScreen.kt${</ID>
|
||||
<ID>TooGenericExceptionCaught:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$e: Exception</ID>
|
||||
<ID>TooGenericExceptionCaught:ImportManagerImpl.kt$ImportManagerImpl$e: Throwable</ID>
|
||||
<ID>TooGenericExceptionCaught:ItemListingViewModel.kt$ItemListingViewModel$e: Throwable</ID>
|
||||
<ID>TooManyFunctions:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl : AuthenticatorRepository</ID>
|
||||
<ID>TooManyFunctions:EditItemViewModel.kt$EditItemViewModel : BaseViewModel</ID>
|
||||
<ID>TooManyFunctions:ItemListingViewModel.kt$ItemListingViewModel : BaseViewModel</ID>
|
||||
<ID>TooManyFunctions:ItemSearchViewModel.kt$ItemSearchViewModel : BaseViewModel</ID>
|
||||
<ID>TooManyFunctions:SettingsViewModel.kt$SettingsViewModel : BaseViewModel</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthRepositoryModule.kt$AuthRepositoryModule$( authDiskSource = authDiskSource )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorDatabase.kt$AuthenticatorDatabase$[ AuthenticatorItemEntity::class ]</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorDiskModule.kt$AuthenticatorDiskModule$( context = app, klass = AuthenticatorDatabase::class.java, name = "authenticator_database" )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorDiskSourceImpl.kt$AuthenticatorDiskSourceImpl$( forceItemsFlow, itemDao.getAllItems() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorManagerModule.kt$AuthenticatorManagerModule$( authenticatorSdkSource = authenticatorSdkSource, dispatcherManager = dispatcherManager, clock = clock )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorNavBarNavigation.kt$( onNavigateBack = onNavigateBack, onNavigateToSearch = onNavigateToSearch, onNavigateToQrCodeScanner = onNavigateToQrCodeScanner, onNavigateToManualKeyEntry = onNavigateToManualKeyEntry, onNavigateToEditItem = onNavigateToEditItem, onNavigateToExport = onNavigateToExport, onNavigateToImport = onNavigateToImport, onNavigateToTutorial = onNavigateToTutorial )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorNavBarScreen.kt$( isVisible = false, onClick = { // Do nothing }, modifier = Modifier .fillMaxWidth() .height(appBarHeightPx.toDp()) )</ID>
|
||||
<ID>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() } )</ID>
|
||||
<ID>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() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorNavigation.kt$( startDestination = AUTHENTICATOR_NAV_BAR_ROUTE, route = AUTHENTICATOR_GRAPH_ROUTE )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( ExportJsonData( encrypted = false, items = authenticatorDiskSource .getItems() .firstOrNull() .orEmpty() .map { it.toExportJsonItem() }, ) )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( importFileFormat = format, byteArray = it )</ID>
|
||||
<ID>TrailingCommaOnCallSite:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl$( onSuccess = { it }, onFailure = { ImportDataResult.Error() } )</ID>
|
||||
<ID>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 ) )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>TrailingCommaOnCallSite:BitwardenExportParser.kt$BitwardenExportParser$( items = exportData .items .filter { it.login?.totp != null } .toAuthenticatorItemEntities() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.AlgorithmOptionClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.ExpandAdvancedOptionsClick )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.FavoriteToggleClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.IssuerNameTextChange(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.NumberOfDigitsOptionClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.RefreshPeriodOptionClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.TotpCodeTextChange(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.TypeOptionClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( EditItemAction.UsernameTextChange(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( context, event.message(resources), Toast.LENGTH_LONG )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( dialogState = state.dialog, onDismissRequest = { viewModel.trySendAction(EditItemAction.DismissDialog) } )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( iconRes = if (viewState.isAdvancedOptionsExpanded) { R.drawable.ic_chevron_up } else { R.drawable.ic_chevron_down } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( id = R.string.collapse_advanced_options )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( id = R.string.refresh_period_seconds, it.seconds )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( indication = rememberRipple( bounded = true, color = MaterialTheme.colorScheme.primary, ), interactionSource = remember { MutableInteractionSource() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), viewState = viewState, onExpandStateChange = onExpandAdvancedOptionsClicked, onAlgorithmOptionClicked = onAlgorithmOptionClicked, onTypeOptionClicked = onTypeOptionClicked, onRefreshPeriodOptionClicked = onRefreshPeriodOptionClicked, onNumberOfDigitsChanged = onNumberOfDigitsChanged )</ID>
|
||||
<ID>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) } } )</ID>
|
||||
<ID>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 ) } } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemScreen.kt$( title = dialogState.title, message = dialogState.message )</ID>
|
||||
<ID>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" }, ) } )</ID>
|
||||
<ID>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, ) )</ID>
|
||||
<ID>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, ) )</ID>
|
||||
<ID>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, ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( R.string.saving.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( algorithm = action.algorithmOption )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( dialog = EditItemState.DialogState.Generic( title = R.string.an_error_has_occurred.asText(), message = R.string.key_is_invalid.asText() ) )</ID>
|
||||
<ID>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()), ) )</ID>
|
||||
<ID>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()), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( dialog = EditItemState.DialogState.Loading( R.string.saving.asText() ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( digits = action.digits )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( favorite = action.favorite )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( initialState = savedStateHandle[KEY_STATE] ?: EditItemState( itemId = EditItemArgs(savedStateHandle).itemId, viewState = EditItemState.ViewState.Loading, dialog = null, ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( isAdvancedOptionsExpanded = currentContent.isAdvancedOptionsExpanded.not() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( issuer = action.issuerName )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( message = R.string.generic_error_message.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( refreshPeriod = action.period )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( title = R.string.an_error_has_occurred.asText(), message = R.string.key_is_invalid.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( totpCode = action.totpCode )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( type = action.typeOption )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( username = action.username )</ID>
|
||||
<ID>TrailingCommaOnCallSite:EditItemViewModel.kt$EditItemViewModel$( viewState = EditItemState.ViewState.Loading )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExpandableFloatingActionButton.kt$( onFabOptionClick = { expandableFabState.value = expandableFabState.value.toggleValue() onStateChange(expandableFabState.value) expandableFabOption.onFabOptionClick() }, expandableFabOption = expandableFabOption )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportScreen.kt$( modifier = Modifier .padding(paddingValues) .fillMaxSize(), state = state, onExportFormatOptionSelected = remember(viewModel) { { viewModel.trySendAction(ExportAction.ExportFormatOptionSelect(it)) } }, onExportClick = { shouldShowConfirmationPrompt = true } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportScreen.kt$( modifier = modifier .imePadding() .verticalScroll(rememberScrollState()) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportScreen.kt$( text = dialog.message )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportScreen.kt$( visibilityState = BasicDialogState.Shown( title = dialog.title, message = dialog.message, ), onDismissRequest = remember(viewModel) { { viewModel.trySendAction(ExportAction.DialogDismiss) } } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportScreen.kt$( visibilityState = LoadingDialogState.Shown( text = dialog.message ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportViewModel.kt$ExportViewModel$( ExportAction.Internal.SaveExportDataToUriResultReceive( result = result ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportViewModel.kt$ExportViewModel$( initialState = ExportState(dialogState = null, exportFormat = ExportFormat.JSON) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ExportViewModel.kt$ExportViewModel$( result = result )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FakeFeatureFlagRepository.kt$( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FakeFeatureFlagRepository.kt$( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue) ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true) ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$FeatureFlagDiskSourceTest$( FEATURE_FLAGS_CONFIGURATION_JSON )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagDiskSourceTest.kt$FeatureFlagDiskSourceTest$( fakeSharedPreferences.getString(featureFlagsConfigKey, null)!! )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagManagerTest.kt$( mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(true), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagManagerTest.kt$FeatureFlagManagerTest$( featureFlags = emptyMap() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagRepositoryImpl.kt$FeatureFlagRepositoryImpl$( LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagRepositoryImpl.kt$FeatureFlagRepositoryImpl$( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive( LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagRepositoryImpl.kt$FeatureFlagRepositoryImpl$( mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive( LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue ) ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagRepositoryTest.kt$( featureFlags = mapOf( LocalFeatureFlag.BitwardenAuthenticationEnabled.name to JsonPrimitive(LocalFeatureFlag.BitwardenAuthenticationEnabled.defaultValue), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:FeatureFlagRepositoryTest.kt$FeatureFlagRepositoryTest$( FEATURE_FLAGS_CONFIG, fakeFeatureFlagDiskSource.featureFlagsConfiguration )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ImportingScreen.kt$( modifier = modifier .imePadding() .verticalScroll(rememberScrollState()) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ImportingScreen.kt$( text = dialog.message )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ImportingScreen.kt$( visibilityState = BasicDialogState.Shown( title = dialog.title, message = dialog.message, ), onDismissRequest = remember(viewModel) { { viewModel.trySendAction(ImportAction.DialogDismiss) } } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ImportingScreen.kt$( visibilityState = LoadingDialogState.Shown( text = dialog.message ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ImportingViewModel.kt$ImportingViewModel$( ImportEvent.ShowToast( message = R.string.import_success.asText(), ) )</ID>
|
||||
<ID>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(), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ImportingViewModel.kt$ImportingViewModel$( initialState = ImportState(importFileFormat = ImportFileFormat.BITWARDEN_JSON) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction.EnterSetupKey$( label, icon, onEnterSetupKeyClick )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction.ScanQrCode$( label, icon, onScanQrCodeClick )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( navController = navController, onNavigateToExport = navigateToExport, onNavigateToImport = navigateToImport, onNavigateToTutorial = navigateToTutorial )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( onNavigateBack = { navController.popBackStack() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( onNavigateBack = { navController.popBackStack() }, onNavigateToQrCodeScreen = { navController.popBackStack() navController.navigateToQrCodeScanScreen() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingGraphNavigation.kt$( route = ITEM_LISTING_GRAPH_ROUTE, navOptions = navOptions )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.DeleteItemClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.EditItemClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.ItemClick(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( ItemListingAction.SearchClick )</ID>
|
||||
<ID>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 ) )</ID>
|
||||
<ID>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, ) )</ID>
|
||||
<ID>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) } } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( context, event.message(context.resources), Toast.LENGTH_LONG )</ID>
|
||||
<ID>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) ) } } )</ID>
|
||||
<ID>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 } )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( rememberTopAppBarState() )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( title = stringResource(id = R.string.verification_codes), scrollBehavior = scrollBehavior, actions = { BitwardenSearchActionItem( contentDescription = stringResource(id = R.string.search_codes), onClick = onNavigateToSearch, ) } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingScreen.kt$( title = stringResource(id = R.string.verification_codes), scrollBehavior = scrollBehavior, navigationIcon = null, actions = { } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( ItemListingAction.Internal.DeleteItemReceive( authenticatorRepository.hardDeleteItem(action.itemId) ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( ItemListingEvent.ShowToast( message = R.string.value_has_been_copied.asText(action.authCode) ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = action.data, clearDialogState = false )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = authenticatorData.data, clearDialogState = false )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = authenticatorData.data, clearDialogState = true )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorData = state.data, clearDialogState = true )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( authenticatorRepository.hardDeleteItem(action.itemId) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( byteArray = otpParam.secret.toByteArray() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( dialog = ItemListingState.DialogState.DeleteConfirmationPrompt( message = R.string.do_you_really_want_to_permanently_delete_cipher.asText(), itemId = action.itemId, ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( dialog = ItemListingState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.authenticator_key_read_error.asText(), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( dialog = ItemListingState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.generic_error_message.asText(), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( initialState = ItemListingState( settingsRepository.appTheme, settingsRepository.authenticatorAlertThresholdSeconds, viewState = ItemListingState.ViewState.Loading, dialog = null ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( message = R.string.internet_connection_required_title .asText() .concat(R.string.internet_connection_required_message.asText()) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( message = R.string.value_has_been_copied.asText(action.authCode) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( settingsRepository.appTheme, settingsRepository.authenticatorAlertThresholdSeconds, viewState = ItemListingState.ViewState.Loading, dialog = null )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemListingViewModel.kt$ItemListingViewModel$( viewState = ItemListingState.ViewState.Loading )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemSearchNavigation.kt$( onNavigateBack = onNavigateBack )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemSearchScreen.kt$( viewState = viewState, modifier = innerModifier )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( authenticatorData = authenticatorData, clearDialogState = false )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( initialState = ItemSearchState( searchTerm = "", viewState = ItemSearchState.ViewState.Loading, dialogState = null, ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( message = R.string.there_are_no_items_that_match_the_search.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ItemSearchViewModel.kt$ItemSearchViewModel$( searchTerm = currentState.searchTerm, viewState = authenticatorData .filterAndOrganize(state.searchTerm) .toViewState(searchTerm = state.searchTerm), dialogState = currentState.dialogState.takeUnless { clearDialogState } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( MainAction.ReceiveFirstIntent( intent = intent ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( MainAction.ReceiveNewIntent(intent = intent) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( intent = intent )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( onSplashScreenRemoved = { shouldShowSplashScreen = false }, onExitApplication = { finishAffinity() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainActivity.kt$MainActivity$( theme = state.theme )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainViewModel.kt$MainViewModel$( MainState( theme = settingsRepository.appTheme ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:MainViewModel.kt$MainViewModel$( theme = settingsRepository.appTheme )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( dialog.message )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( title = dialog.title, message = dialog.message )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( visibilityState = BasicDialogState.Shown( title = dialog.title, message = dialog.message ), onDismissRequest = remember(state) { { viewModel.trySendAction(ManualCodeEntryAction.DismissDialog) } } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryScreen.kt$( visibilityState = LoadingDialogState.Shown( dialog.message ) )</ID>
|
||||
<ID>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, ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( message = R.string.key_is_invalid.asText() ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( message = R.string.key_is_required.asText() ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( message = R.string.name_is_required.asText() ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( dialog = ManualCodeEntryState.DialogState.Error( title = R.string.an_error_has_occurred.asText(), message = R.string.generic_error_message.asText(), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( message = R.string.key_is_invalid.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( message = R.string.key_is_required.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:ManualCodeEntryViewModel.kt$ManualCodeEntryViewModel$( message = R.string.name_is_required.asText() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:QrCodeAnalyzerImpl.kt$QrCodeAnalyzerImpl$( DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE), DecodeHintType.ALSO_INVERTED to true )</ID>
|
||||
<ID>TrailingCommaOnCallSite:RootNavScreen.kt$( onUnlocked = { viewModel.trySendAction(RootNavAction.Internal.AppUnlocked) } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:RootNavViewModel.kt$RootNavViewModel$( initialState = RootNavState( hasSeenWelcomeGuide = settingsRepository.hasSeenWelcomeTutorial, navState = RootNavState.NavState.Splash, ) )</ID>
|
||||
<ID>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)) } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsDiskSourceImpl.kt$SettingsDiskSourceImpl$( ALERT_THRESHOLD_SECONDS_KEY, thresholdSeconds )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsNavigation.kt$( onNavigateBack = { navController.popBackStack() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsNavigation.kt$( onTutorialFinished = { navController.popBackStack() } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsNavigation.kt$( route = SETTINGS_ROUTE )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsNavigation.kt$( startDestination = SETTINGS_ROUTE, route = SETTINGS_GRAPH_ROUTE )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( SettingsAction.SecurityClick.UnlockWithBiometricToggle(it) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( id = R.drawable.ic_navigate_next )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier .padding(innerPadding) .fillMaxSize() .verticalScroll(state = rememberScrollState()) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.appearance) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.data) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.help) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = Modifier.padding(horizontal = 16.dp), label = stringResource(id = R.string.security) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( modifier = modifier, label = stringResource(id = R.string.about) )</ID>
|
||||
<ID>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) } } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( state = state, biometricsManager = biometricsManager, onBiometricToggle = remember(viewModel) { { viewModel.trySendAction( SettingsAction.SecurityClick.UnlockWithBiometricToggle(it) ) } } )</ID>
|
||||
<ID>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, ) } )</ID>
|
||||
<ID>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, ) } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsScreen.kt$( title = stringResource(id = R.string.settings), scrollBehavior = scrollBehavior )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( appearance = it.appearance.copy(language = language) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( appearance = it.appearance.copy(theme = theme) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( clock, settingsRepository.appLanguage, settingsRepository.appTheme, settingsRepository.isUnlockWithBiometricsEnabled, settingsRepository.isCrashLoggingEnabled )</ID>
|
||||
<ID>TrailingCommaOnCallSite:SettingsViewModel.kt$SettingsViewModel$( initialState = savedStateHandle[KEY_STATE] ?: createInitialState( clock, settingsRepository.appLanguage, settingsRepository.appTheme, settingsRepository.isUnlockWithBiometricsEnabled, settingsRepository.isCrashLoggingEnabled ) )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( R.string.scan_the_qr_code_in_your_2_step_verification_settings_for_any_account )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( R.string.use_your_device_camera_to_scan_codes )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( R.string.when_using_2_step_verification_youll_enter_your_username_and_password_and_a_code_generated_in_this_app )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( TutorialAction.ContinueClick )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( TutorialAction.TutorialPageChange(pagerState.targetPage) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( modifier = Modifier .height(50.dp) .fillMaxWidth(), horizontalArrangement = Arrangement.Center )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( modifier = Modifier .padding(8.dp) .background(color, CircleShape) .size(10.dp) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( modifier = Modifier .padding(innerPadding), verticalArrangement = Arrangement.Center )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( onTutorialFinished = {} )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialScreen.kt$( painter = painterResource(id = R.drawable.ic_tutorial_2fa), contentDescription = stringResource(id = R.string.unique_codes) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialViewModel.kt$TutorialState.QrScannerSlide$( continueButtonText = R.string.continue_button.asText(), isLastPage = false )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialViewModel.kt$TutorialState.UniqueCodesSlide$( continueButtonText = R.string.get_started.asText(), isLastPage = true )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TutorialViewModel.kt$TutorialViewModel$( initialState = TutorialState.IntroSlide )</ID>
|
||||
<ID>TrailingCommaOnCallSite:TwoFasExportParser.kt$TwoFasExportParser$( items = exportData.services.toAuthenticatorItemEntities() )</ID>
|
||||
<ID>TrailingCommaOnCallSite:UnlockScreen.kt$( modifier = Modifier .padding(innerPadding) .fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally )</ID>
|
||||
<ID>TrailingCommaOnCallSite:UnlockScreen.kt$( title = R.string.an_error_has_occurred.asText(), message = dialog.message )</ID>
|
||||
<ID>TrailingCommaOnCallSite:UnlockScreen.kt$( visibilityState = LoadingDialogState.Shown(R.string.loading.asText()) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:UnlockViewModel.kt$UnlockViewModel$( dialog = UnlockState.Dialog.Error( message = R.string.too_many_failed_biometric_attempts.asText(), ) )</ID>
|
||||
<ID>TrailingCommaOnCallSite:UnlockViewModel.kt$UnlockViewModel$( initialState = savedStateHandle[KEY_STATE] ?: run { UnlockState( isBiometricsEnabled = settingsRepository.isUnlockWithBiometricsEnabled, isBiometricsValid = biometricsEncryptionManager.isBiometricIntegrityValid(), dialog = null, ) } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(color = MaterialTheme.colorScheme.primary), onClick = onItemClick, onLongClick = { shouldShowDropdownMenu = true } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( painter = painterResource(id = R.drawable.ic_edit_item), contentDescription = stringResource(R.string.edit_item) )</ID>
|
||||
<ID>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 )</ID>
|
||||
<ID>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), ) } )</ID>
|
||||
<ID>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) ) } )</ID>
|
||||
<ID>TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( timeLeftSeconds = timeLeftSeconds, periodSeconds = periodSeconds, alertThresholdSeconds = alertThresholdSeconds )</ID>
|
||||
<ID>TrailingCommaOnCallSite:VaultVerificationCodeItem.kt$( vertical = 8.dp, horizontal = 16.dp )</ID>
|
||||
<ID>TrailingCommaOnDeclarationSite:ItemSearchViewModel.kt$SortPriority</ID>
|
||||
<ID>UndocumentedPublicClass:AegisExportParser.kt$AegisExportParser : ExportParser</ID>
|
||||
<ID>UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport</ID>
|
||||
<ID>UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport$Database</ID>
|
||||
<ID>UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport.Database$Entry</ID>
|
||||
<ID>UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport.Database$Group</ID>
|
||||
<ID>UndocumentedPublicClass:AegisJsonExport.kt$AegisJsonExport.Database.Entry$Info</ID>
|
||||
<ID>UndocumentedPublicClass:AuthRepositoryImpl.kt$AuthRepositoryImpl : AuthRepository</ID>
|
||||
<ID>UndocumentedPublicClass:AuthenticatorApplication.kt$AuthenticatorApplication : Application</ID>
|
||||
<ID>UndocumentedPublicClass:AuthenticatorDiskSourceImpl.kt$AuthenticatorDiskSourceImpl : AuthenticatorDiskSource</ID>
|
||||
<ID>UndocumentedPublicClass:AuthenticatorRepositoryImpl.kt$AuthenticatorRepositoryImpl : AuthenticatorRepository</ID>
|
||||
<ID>UndocumentedPublicClass:AuthenticatorSdkSourceImpl.kt$AuthenticatorSdkSourceImpl : AuthenticatorSdkSource</ID>
|
||||
<ID>UndocumentedPublicClass:BitwardenExportParser.kt$BitwardenExportParser : ExportParser</ID>
|
||||
<ID>UndocumentedPublicClass:EditItemViewModel.kt$EditItemAction$ExpandAdvancedOptionsClick : EditItemAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabIcon</ID>
|
||||
<ID>UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabOption</ID>
|
||||
<ID>UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabState</ID>
|
||||
<ID>UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabState$Collapsed : ExpandableFabState</ID>
|
||||
<ID>UndocumentedPublicClass:ExpandableFloatingActionButton.kt$ExpandableFabState$Expanded : ExpandableFabState</ID>
|
||||
<ID>UndocumentedPublicClass:ExportDataResult.kt$ExportDataResult$Error : ExportDataResult</ID>
|
||||
<ID>UndocumentedPublicClass:ExportDataResult.kt$ExportDataResult$Success : ExportDataResult</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction$CloseButtonClick : ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction$ConfirmExportClick : ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction$DialogDismiss : ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction$ExportFormatOptionSelect : ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction$ExportLocationReceive : ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction$Internal : ExportAction</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportAction.Internal$SaveExportDataToUriResultReceive : Internal</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportEvent</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportEvent$NavigateBack : ExportEvent</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportEvent$NavigateToSelectExportDestination : ExportEvent</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportEvent$ShowToast : ExportEvent</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportState</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportState$DialogState</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportState.DialogState$Error : DialogState</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportState.DialogState$Loading : DialogState</ID>
|
||||
<ID>UndocumentedPublicClass:ExportViewModel.kt$ExportViewModel : BaseViewModel</ID>
|
||||
<ID>UndocumentedPublicClass:FileManagerImpl.kt$FileManagerImpl : FileManager</ID>
|
||||
<ID>UndocumentedPublicClass:ImportDataResult.kt$ImportDataResult$Error : ImportDataResult</ID>
|
||||
<ID>UndocumentedPublicClass:ImportDataResult.kt$ImportDataResult$Success : ImportDataResult</ID>
|
||||
<ID>UndocumentedPublicClass:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction : ExpandableFabOption</ID>
|
||||
<ID>UndocumentedPublicClass:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction$EnterSetupKey : ItemListingExpandableFabAction</ID>
|
||||
<ID>UndocumentedPublicClass:ItemListingExpandableFabAction.kt$ItemListingExpandableFabAction$ScanQrCode : ItemListingExpandableFabAction</ID>
|
||||
<ID>UndocumentedPublicClass:ItemListingViewModel.kt$ItemListingState.DialogState$DeleteConfirmationPrompt : DialogState</ID>
|
||||
<ID>UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport</ID>
|
||||
<ID>UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport$Account</ID>
|
||||
<ID>UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport$Folder</ID>
|
||||
<ID>UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport.Account$BackupInfo</ID>
|
||||
<ID>UndocumentedPublicClass:LastPassJsonExport.kt$LastPassJsonExport.Account$FolderData</ID>
|
||||
<ID>UndocumentedPublicClass:MainActivity.kt$MainActivity : AppCompatActivity</ID>
|
||||
<ID>UndocumentedPublicClass:ManualCodeEntryViewModel.kt$ManualCodeEntryState$DialogState : Parcelable</ID>
|
||||
<ID>UndocumentedPublicClass:ManualCodeEntryViewModel.kt$ManualCodeEntryState.DialogState$Error : DialogState</ID>
|
||||
<ID>UndocumentedPublicClass:ManualCodeEntryViewModel.kt$ManualCodeEntryState.DialogState$Loading : DialogState</ID>
|
||||
<ID>UndocumentedPublicClass:PlatformDiskModule.kt$PlatformDiskModule</ID>
|
||||
<ID>UndocumentedPublicClass:PlatformNetworkModule.kt$PlatformNetworkModule</ID>
|
||||
<ID>UndocumentedPublicClass:RootNavViewModel.kt$RootNavState$NavState : Parcelable</ID>
|
||||
<ID>UndocumentedPublicClass:RootNavViewModel.kt$RootNavViewModel : BaseViewModel</ID>
|
||||
<ID>UndocumentedPublicClass:SettingsViewModel.kt$SettingsAction.SecurityClick$UnlockWithBiometricToggle : SecurityClick</ID>
|
||||
<ID>UndocumentedPublicClass:SettingsViewModel.kt$SettingsState$Dialog : Parcelable</ID>
|
||||
<ID>UndocumentedPublicClass:SettingsViewModel.kt$SettingsState.Dialog$Loading : Dialog</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport$Group</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport$Service</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Badge</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Icon</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Order</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service$Otp</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service.Icon$IconCollection</ID>
|
||||
<ID>UndocumentedPublicClass:TwoFasJsonExport.kt$TwoFasJsonExport.Service.Icon$Label</ID>
|
||||
<ID>UndocumentedPublicFunction:AuthenticatorItemAlgorithm.kt$AuthenticatorItemAlgorithm.Companion$fun fromStringOrNull(value: String): AuthenticatorItemAlgorithm?</ID>
|
||||
<ID>UndocumentedPublicFunction:AuthenticatorItemEntity.kt$AuthenticatorItemEntity$fun toOtpAuthUriString(): String</ID>
|
||||
<ID>UndocumentedPublicFunction:AuthenticatorItemType.kt$AuthenticatorItemType.Companion$fun fromStringOrNull(value: String): AuthenticatorItemType?</ID>
|
||||
<ID>UndocumentedPublicFunction:AuthenticatorSdkSource.kt$AuthenticatorSdkSource$suspend fun generateBiometricsKey(): Result<String></ID>
|
||||
<ID>UndocumentedPublicFunction:AuthenticatorTheme.kt$@Composable fun AuthenticatorTheme( theme: AppTheme = AppTheme.DEFAULT, dynamicColor: Boolean = false, content: @Composable () -> Unit, )</ID>
|
||||
<ID>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 = {}, )</ID>
|
||||
<ID>UndocumentedPublicFunction:EditItemViewModel.kt$AuthenticatorRefreshPeriodOption.Companion$fun fromSeconds(seconds: Int)</ID>
|
||||
<ID>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 = { }, )</ID>
|
||||
<ID>UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$@Composable fun rememberExpandableFabState()</ID>
|
||||
<ID>UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$ExpandableFabState$fun isExpanded()</ID>
|
||||
<ID>UndocumentedPublicFunction:ExpandableFloatingActionButton.kt$ExpandableFabState$fun toggleValue()</ID>
|
||||
<ID>UndocumentedPublicFunction:ExportNavigation.kt$fun NavController.navigateToExport(navOptions: NavOptions? = null)</ID>
|
||||
<ID>UndocumentedPublicFunction:ExportNavigation.kt$fun NavGraphBuilder.exportDestination( onNavigateBack: () -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:ExportScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ExportScreen( viewModel: ExportViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:ImportingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun ImportingScreen( viewModel: ImportingViewModel = hiltViewModel(), intentManager: IntentManager = LocalIntentManager.current, onNavigateBack: () -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:ItemListingScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable @Preview(showBackground = true) fun EmptyListingContentPreview()</ID>
|
||||
<ID>UndocumentedPublicFunction:SearchHandlers.kt$SearchHandlers.Companion$fun create(viewModel: ItemSearchViewModel): SearchHandlers</ID>
|
||||
<ID>UndocumentedPublicFunction:SettingsScreen.kt$@Composable fun SecuritySettings( state: SettingsState, biometricsManager: BiometricsManager = LocalBiometricsManager.current, onBiometricToggle: (Boolean) -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:SettingsScreen.kt$@Composable fun VaultSettings( modifier: Modifier = Modifier, onExportClick: () -> Unit, onImportClick: () -> Unit, onBackupClick: () -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:SettingsViewModel.kt$SettingsViewModel.Companion$fun createInitialState( clock: Clock, appLanguage: AppLanguage, appTheme: AppTheme, unlockWithBiometricsEnabled: Boolean, isSubmitCrashLogsEnabled: Boolean, ): SettingsState</ID>
|
||||
<ID>UndocumentedPublicFunction:TutorialNavigation.kt$fun NavController.navigateToSettingsTutorial(navOptions: NavOptions? = null)</ID>
|
||||
<ID>UndocumentedPublicFunction:TutorialNavigation.kt$fun NavController.navigateToTutorial(navOptions: NavOptions? = null)</ID>
|
||||
<ID>UndocumentedPublicFunction:TutorialNavigation.kt$fun NavGraphBuilder.tutorialDestination(onTutorialFinished: () -> Unit)</ID>
|
||||
<ID>UndocumentedPublicFunction:TutorialNavigation.kt$fun NavGraphBuilder.tutorialSettingsDestination(onTutorialFinished: () -> Unit)</ID>
|
||||
<ID>UndocumentedPublicFunction:TutorialScreen.kt$@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class) @Composable fun TutorialScreen( viewModel: TutorialViewModel = hiltViewModel(), onTutorialFinished: () -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:TutorialScreen.kt$@Preview @Composable fun TutorialScreenPreview()</ID>
|
||||
<ID>UndocumentedPublicFunction:UnlockScreen.kt$@OptIn(ExperimentalMaterial3Api::class) @Composable fun UnlockScreen( viewModel: UnlockViewModel = hiltViewModel(), biometricsManager: BiometricsManager = LocalBiometricsManager.current, onUnlocked: () -> Unit, )</ID>
|
||||
<ID>UndocumentedPublicFunction:VerificationCodeItemExtensions.kt$fun List<VerificationCodeItem>.toViewState( alertThresholdSeconds: Int, ): ItemListingState.ViewState</ID>
|
||||
<ID>UndocumentedPublicFunction:VerificationCodeItemExtensions.kt$fun VerificationCodeItem.toDisplayItem(alertThresholdSeconds: Int)</ID>
|
||||
</CurrentIssues>
|
||||
</SmellBaseline>
|
||||
Reference in New Issue
Block a user