Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into item-long-press-delete
Browse files Browse the repository at this point in the history
# Conflicts:
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt
#	app/src/main/kotlin/com/x8bit/bitwarden/authenticator/ui/authenticator/feature/itemlisting/VaultVerificationCodeItem.kt
#	app/src/main/res/values/strings.xml
  • Loading branch information
SaintPatrck committed Apr 16, 2024
2 parents 727ae72 + 8da98f9 commit ccdbd04
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.x8bit.bitwarden.authenticator.data.platform.manager.clipboard

import androidx.compose.ui.text.AnnotatedString
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.Text

/**
* Wrapper class for using the clipboard.
*/
interface BitwardenClipboardManager {

/**
* Places the given [text] into the device's clipboard. Setting the data to [isSensitive] will
* obfuscate the displayed data on the default popup (true by default). A toast will be
* displayed on devices that do not have a default popup (pre-API 32) and will not be displayed
* on newer APIs. If a toast is displayed, it will be formatted as "[text] copied" or if a
* [toastDescriptorOverride] is provided, it will be formatted as
* "[toastDescriptorOverride] copied".
*/
fun setText(
text: AnnotatedString,
isSensitive: Boolean = true,
toastDescriptorOverride: String? = null,
)

/**
* See [setText] for more details.
*/
fun setText(
text: String,
isSensitive: Boolean = true,
toastDescriptorOverride: String? = null,
)

/**
* See [setText] for more details.
*/
fun setText(
text: Text,
isSensitive: Boolean = true,
toastDescriptorOverride: String? = null,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.x8bit.bitwarden.authenticator.data.platform.manager.clipboard

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.os.Build
import android.widget.Toast
import androidx.compose.ui.text.AnnotatedString
import androidx.core.content.getSystemService
import androidx.core.os.persistableBundleOf
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import com.x8bit.bitwarden.authenticator.R
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.Text
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.toAnnotatedString
import com.x8bit.bitwarden.data.platform.manager.clipboard.ClearClipboardWorker

/**
* Default implementation of the [BitwardenClipboardManager] interface.
*/
class BitwardenClipboardManagerImpl(
private val context: Context,
) : BitwardenClipboardManager {
private val clipboardManager: ClipboardManager = requireNotNull(context.getSystemService())

override fun setText(
text: AnnotatedString,
isSensitive: Boolean,
toastDescriptorOverride: String?,
) {
clipboardManager.setPrimaryClip(
ClipData
.newPlainText("", text)
.apply {
description.extras = persistableBundleOf(
"android.content.extra.IS_SENSITIVE" to isSensitive,
)
},
)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
val descriptor = toastDescriptorOverride ?: text
Toast
.makeText(
context,
context.resources.getString(R.string.value_has_been_copied, descriptor),
Toast.LENGTH_SHORT,
)
.show()
}

val clearClipboardRequest: OneTimeWorkRequest =
OneTimeWorkRequest
.Builder(ClearClipboardWorker::class.java)
.build()

WorkManager.getInstance(context).enqueueUniqueWork(
"ClearClipboard",
ExistingWorkPolicy.REPLACE,
clearClipboardRequest,
)
}

override fun setText(text: String, isSensitive: Boolean, toastDescriptorOverride: String?) {
setText(text.toAnnotatedString(), isSensitive, toastDescriptorOverride)
}

override fun setText(text: Text, isSensitive: Boolean, toastDescriptorOverride: String?) {
setText(text.toString(context.resources), isSensitive, toastDescriptorOverride)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.x8bit.bitwarden.data.platform.manager.clipboard

import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE
import androidx.work.Worker
import androidx.work.WorkerParameters

/**
* A worker to clear the clipboard manager.
*/
class ClearClipboardWorker(appContext: Context, workerParams: WorkerParameters) :
Worker(appContext, workerParams) {

private val clipboardManager =
appContext.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager

override fun doWork(): Result {
clipboardManager.clearPrimaryClip()
return Result.success()
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package com.x8bit.bitwarden.authenticator.data.platform.manager.di

import android.content.Context
import com.x8bit.bitwarden.authenticator.data.platform.manager.DispatcherManager
import com.x8bit.bitwarden.authenticator.data.platform.manager.DispatcherManagerImpl
import com.x8bit.bitwarden.authenticator.data.platform.manager.SdkClientManager
import com.x8bit.bitwarden.authenticator.data.platform.manager.SdkClientManagerImpl
import com.x8bit.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManagerImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import java.time.Clock
import javax.inject.Singleton
Expand All @@ -18,6 +22,12 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
object PlatformManagerModule {

@Provides
@Singleton
fun provideBitwardenClipboardManager(
@ApplicationContext context: Context,
): BitwardenClipboardManager = BitwardenClipboardManagerImpl(context)

@Provides
@Singleton
fun provideBitwardenDispatchers(): DispatcherManager = DispatcherManagerImpl()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.x8bit.bitwarden.authenticator.data.authenticator.repository.Authentic
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult
import com.x8bit.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.authenticator.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.authenticator.data.platform.repository.model.DataState
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem
Expand All @@ -36,6 +37,7 @@ import javax.inject.Inject
@HiltViewModel
class ItemListingViewModel @Inject constructor(
private val authenticatorRepository: AuthenticatorRepository,
private val clipboardManager: BitwardenClipboardManager,
settingsRepository: SettingsRepository,
) : BaseViewModel<ItemListingState, ItemListingEvent, ItemListingAction>(
initialState = ItemListingState(
Expand Down Expand Up @@ -93,7 +95,7 @@ class ItemListingViewModel @Inject constructor(
}

is ItemListingAction.ItemClick -> {
sendEvent(ItemListingEvent.NavigateToEditItem(action.id))
handleItemClick(action)
}

is ItemListingAction.DialogDismiss -> {
Expand All @@ -106,6 +108,15 @@ class ItemListingViewModel @Inject constructor(
}
}

private fun handleItemClick(action: ItemListingAction.ItemClick) {
clipboardManager.setText(action.authCode)
sendEvent(
ItemListingEvent.ShowToast(
message = R.string.value_has_been_copied.asText(action.authCode)
)
)
}

private fun handleDeleteItemClick(action: ItemListingAction.DeleteItemClick) {
mutableStateFlow.update {
it.copy(
Expand Down Expand Up @@ -551,7 +562,7 @@ sealed class ItemListingAction {
/**
* The user clicked a list item.
*/
data class ItemClick(val id: String) : ItemListingAction()
data class ItemClick(val authCode: String) : ItemListingAction()

/**
* The user dismissed the dialog.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
Expand Down Expand Up @@ -43,7 +42,6 @@ import com.x8bit.bitwarden.authenticator.ui.platform.theme.AuthenticatorTheme
* @param periodSeconds The times span where the code is valid.
* @param timeLeftSeconds The seconds remaining until a new code is needed.
* @param startIcon The leading icon for the item.
* @param onCopyClick The lambda function to be invoked when the copy button is clicked.
* @param onItemClick The lambda function to be invoked when the item is clicked.
* @param modifier The modifier for the item.
* @param supportingLabel The supporting label for the item.
Expand All @@ -58,7 +56,6 @@ fun VaultVerificationCodeItem(
timeLeftSeconds: Int,
alertThresholdSeconds: Int,
startIcon: IconData,
onCopyClick: () -> Unit,
onItemClick: () -> Unit,
onEditItemClick: () -> Unit,
onDeleteItemClick: () -> Unit,
Expand Down Expand Up @@ -126,17 +123,6 @@ fun VaultVerificationCodeItem(
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)

IconButton(
onClick = onCopyClick,
) {
Icon(
painter = painterResource(id = R.drawable.ic_copy),
contentDescription = stringResource(id = R.string.copy),
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp),
)
}
}

DropdownMenu(
Expand Down Expand Up @@ -189,12 +175,11 @@ private fun VerificationCodeItem_preview() {
timeLeftSeconds = 15,
alertThresholdSeconds = 7,
startIcon = IconData.Local(R.drawable.ic_login_item),
onCopyClick = {},
onItemClick = {},
onEditItemClick = {},
onDeleteItemClick = {},
modifier = Modifier.padding(horizontal = 16.dp),
supportingLabel = "Supporting Label"
supportingLabel = "Supporting Label",
)
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<string name="unique_codes">Uniqe codes</string>
<string name="help">Help</string>
<string name="tutorial">Tutorial</string>
<string name="value_has_been_copied">%1$s copied</string>
<string name="delete_item">Delete item</string>
<string name="item_deleted">Item deleted</string>
<string name="delete">Delete</string>
Expand Down

0 comments on commit ccdbd04

Please sign in to comment.