mirror of
https://github.com/bitwarden/android.git
synced 2024-11-27 12:00:19 +03:00
Add initial file chooser (#639)
This commit is contained in:
parent
ebd9628b02
commit
10bf584c90
10 changed files with 295 additions and 17 deletions
|
@ -46,6 +46,16 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
<service
|
||||
android:name=".data.autofill.BitwardenAutofillService"
|
||||
android:exported="true"
|
||||
|
@ -69,4 +79,10 @@
|
|||
</service>
|
||||
</application>
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.media.action.IMAGE_CAPTURE" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -2,6 +2,9 @@ package com.x8bit.bitwarden.ui.platform.manager.intent
|
|||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
/**
|
||||
* A manager class for simplifying the handling of Android Intents within a given context.
|
||||
|
@ -28,8 +31,36 @@ interface IntentManager {
|
|||
*/
|
||||
fun launchUri(uri: Uri)
|
||||
|
||||
/**
|
||||
* Start an activity using the provided [Intent] and provides a callback, via [onResult], for
|
||||
* retrieving the [ActivityResult].
|
||||
*/
|
||||
@Composable
|
||||
fun launchActivityForResult(
|
||||
onResult: (ActivityResult) -> Unit,
|
||||
): ManagedActivityResultLauncher<Intent, ActivityResult>
|
||||
|
||||
/**
|
||||
* Launches the share sheet with the given [text].
|
||||
*/
|
||||
fun shareText(text: String)
|
||||
|
||||
/**
|
||||
* Processes the [activityResult] and attempts to get the relevant file data from it.
|
||||
*/
|
||||
fun getFileDataFromIntent(activityResult: ActivityResult): FileData?
|
||||
|
||||
/**
|
||||
* Creates an intent for choosing a file saved to disk.
|
||||
*/
|
||||
fun createFileChooserIntent(withCameraIntents: Boolean): Intent
|
||||
|
||||
/**
|
||||
* Represents file information.
|
||||
*/
|
||||
data class FileData(
|
||||
val fileName: String,
|
||||
val uri: Uri,
|
||||
val sizeBytes: Long,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,18 +1,53 @@
|
|||
package com.x8bit.bitwarden.ui.platform.manager.intent
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.browser.customtabs.CustomTabsIntent
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.x8bit.bitwarden.BuildConfig
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
|
||||
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||
import java.io.File
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
* The authority used for pulling in photos from the camera.
|
||||
*
|
||||
* Note: This must match the file provider authority in the manifest.
|
||||
*/
|
||||
private const val FILE_PROVIDER_AUTHORITY: String = "${BuildConfig.APPLICATION_ID}.fileprovider"
|
||||
|
||||
/**
|
||||
* Temporary file name for a camera image.
|
||||
*/
|
||||
private const val TEMP_CAMERA_IMAGE_NAME: String = "temp_camera_image.jpg"
|
||||
|
||||
/**
|
||||
* This directory must also be declared in file_paths.xml
|
||||
*/
|
||||
private const val TEMP_CAMERA_IMAGE_DIR: String = "camera_temp"
|
||||
|
||||
/**
|
||||
* The default implementation of the [IntentManager] for simplifying the handling of Android
|
||||
* Intents within a given context.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@OmitFromCoverage
|
||||
class IntentManagerImpl(
|
||||
private val context: Context,
|
||||
private val clock: Clock = Clock.systemDefaultZone(),
|
||||
) : IntentManager {
|
||||
|
||||
override fun exitApplication() {
|
||||
|
@ -28,6 +63,15 @@ class IntentManagerImpl(
|
|||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun launchActivityForResult(
|
||||
onResult: (ActivityResult) -> Unit,
|
||||
): ManagedActivityResultLauncher<Intent, ActivityResult> =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult(),
|
||||
onResult = onResult,
|
||||
)
|
||||
|
||||
override fun startCustomTabsActivity(uri: Uri) {
|
||||
CustomTabsIntent
|
||||
.Builder()
|
||||
|
@ -51,4 +95,98 @@ class IntentManagerImpl(
|
|||
}
|
||||
startActivity(Intent.createChooser(sendIntent, null))
|
||||
}
|
||||
|
||||
override fun getFileDataFromIntent(activityResult: ActivityResult): IntentManager.FileData? {
|
||||
if (activityResult.resultCode != Activity.RESULT_OK) return null
|
||||
val uri = activityResult.data?.data
|
||||
return if (uri != null) getLocalFileData(uri) else getCameraFileData()
|
||||
}
|
||||
|
||||
override fun createFileChooserIntent(withCameraIntents: Boolean): Intent {
|
||||
val chooserIntent = Intent.createChooser(
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT)
|
||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
||||
.setType("*/*"),
|
||||
ContextCompat.getString(context, R.string.file_source),
|
||||
)
|
||||
|
||||
if (withCameraIntents) {
|
||||
val tmpDir = File(context.filesDir, TEMP_CAMERA_IMAGE_DIR)
|
||||
val file = File(tmpDir, TEMP_CAMERA_IMAGE_NAME)
|
||||
if (!file.exists()) {
|
||||
file.parentFile?.mkdirs()
|
||||
file.createNewFile()
|
||||
}
|
||||
val outputFileUri = FileProvider.getUriForFile(
|
||||
context,
|
||||
FILE_PROVIDER_AUTHORITY,
|
||||
file,
|
||||
)
|
||||
|
||||
chooserIntent.putExtra(
|
||||
Intent.EXTRA_INITIAL_INTENTS,
|
||||
getCameraIntents(outputFileUri).toTypedArray(),
|
||||
)
|
||||
}
|
||||
|
||||
return chooserIntent
|
||||
}
|
||||
|
||||
private fun getCameraFileData(): IntentManager.FileData {
|
||||
val tmpDir = File(context.filesDir, TEMP_CAMERA_IMAGE_DIR)
|
||||
val file = File(tmpDir, TEMP_CAMERA_IMAGE_NAME)
|
||||
val uri = FileProvider.getUriForFile(context, FILE_PROVIDER_AUTHORITY, file)
|
||||
val fileName = "photo_${clock.instant().toFormattedPattern(pattern = "yyyyMMddHHmmss")}.jpg"
|
||||
return IntentManager.FileData(
|
||||
fileName = fileName,
|
||||
uri = uri,
|
||||
sizeBytes = file.length(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getLocalFileData(uri: Uri): IntentManager.FileData? =
|
||||
context
|
||||
.contentResolver
|
||||
.query(
|
||||
uri,
|
||||
arrayOf(
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.SIZE,
|
||||
),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
?.use { cursor ->
|
||||
if (!cursor.moveToFirst()) return@use null
|
||||
val fileName = cursor
|
||||
.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
|
||||
.takeIf { it >= 0 }
|
||||
?.let { cursor.getString(it) }
|
||||
val fileSize = cursor
|
||||
.getColumnIndex(MediaStore.MediaColumns.SIZE)
|
||||
.takeIf { it >= 0 }
|
||||
?.let { cursor.getLong(it) }
|
||||
if (fileName == null || fileSize == null) return@use null
|
||||
IntentManager.FileData(
|
||||
fileName = fileName,
|
||||
uri = uri,
|
||||
sizeBytes = fileSize,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCameraIntents(outputUri: Uri): List<Intent> {
|
||||
val captureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
|
||||
return context
|
||||
.packageManager
|
||||
.queryIntentActivities(captureIntent, PackageManager.MATCH_ALL)
|
||||
.map {
|
||||
val packageName = it.activityInfo.packageName
|
||||
Intent(captureIntent).apply {
|
||||
component = ComponentName(packageName, it.activityInfo.name)
|
||||
setPackage(packageName)
|
||||
putExtra(MediaStore.EXTRA_OUTPUT, outputUri)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.x8bit.bitwarden.ui.tools.feature.send.addsend
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
|
@ -42,6 +43,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.SegmentedButtonState
|
||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
|
||||
|
||||
|
@ -54,8 +56,13 @@ fun AddSendContent(
|
|||
state: AddSendState.ViewState.Content,
|
||||
isAddMode: Boolean,
|
||||
addSendHandlers: AddSendHandlers,
|
||||
permissionsManager: PermissionsManager,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val chooseFileCameraPermissionLauncher = permissionsManager.getLauncher { isGranted ->
|
||||
addSendHandlers.onChooseFileClick(isGranted)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.verticalScroll(rememberScrollState()),
|
||||
|
@ -118,7 +125,13 @@ fun AddSendContent(
|
|||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.choose_file),
|
||||
onClick = addSendHandlers.onChooseFileCLick,
|
||||
onClick = {
|
||||
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
|
||||
addSendHandlers.onChooseFileClick(true)
|
||||
} else {
|
||||
chooseFileCameraPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
|
|
@ -35,7 +35,9 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
|||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalPermissionsManager
|
||||
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
|
||||
|
||||
|
@ -48,6 +50,7 @@ import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandler
|
|||
fun AddSendScreen(
|
||||
viewModel: AddSendViewModel = hiltViewModel(),
|
||||
intentManager: IntentManager = LocalIntentManager.current,
|
||||
permissionsManager: PermissionsManager = LocalPermissionsManager.current,
|
||||
onNavigateBack: () -> Unit,
|
||||
) {
|
||||
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
|
||||
|
@ -55,9 +58,22 @@ fun AddSendScreen(
|
|||
val context = LocalContext.current
|
||||
val resources = context.resources
|
||||
|
||||
val fileChooserLauncher = intentManager.launchActivityForResult { activityResult ->
|
||||
intentManager.getFileDataFromIntent(activityResult)?.let {
|
||||
viewModel.trySendAction(AddSendAction.FileChoose(it))
|
||||
}
|
||||
}
|
||||
|
||||
EventsEffect(viewModel = viewModel) { event ->
|
||||
when (event) {
|
||||
is AddSendEvent.NavigateBack -> onNavigateBack()
|
||||
|
||||
is AddSendEvent.ShowChooserSheet -> {
|
||||
fileChooserLauncher.launch(
|
||||
intentManager.createFileChooserIntent(event.withCameraOption),
|
||||
)
|
||||
}
|
||||
|
||||
is AddSendEvent.ShowShareSheet -> {
|
||||
intentManager.shareText(event.message)
|
||||
}
|
||||
|
@ -159,6 +175,7 @@ fun AddSendScreen(
|
|||
state = viewState,
|
||||
isAddMode = state.isAddMode,
|
||||
addSendHandlers = remember(viewModel) { AddSendHandlers.create(viewModel) },
|
||||
permissionsManager = permissionsManager,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toViewState
|
||||
|
@ -119,6 +120,7 @@ class AddSendViewModel @Inject constructor(
|
|||
override fun handleAction(action: AddSendAction): Unit = when (action) {
|
||||
AddSendAction.CopyLinkClick -> handleCopyLinkClick()
|
||||
AddSendAction.DeleteClick -> handleDeleteClick()
|
||||
is AddSendAction.FileChoose -> handeFileChose(action)
|
||||
AddSendAction.RemovePasswordClick -> handleRemovePasswordClick()
|
||||
AddSendAction.ShareLinkClick -> handleShareLinkClick()
|
||||
is AddSendAction.CloseClick -> handleCloseClick()
|
||||
|
@ -129,7 +131,7 @@ class AddSendViewModel @Inject constructor(
|
|||
is AddSendAction.SaveClick -> handleSaveClick()
|
||||
is AddSendAction.FileTypeClick -> handleFileTypeClick()
|
||||
is AddSendAction.TextTypeClick -> handleTextTypeClick()
|
||||
is AddSendAction.ChooseFileClick -> handleChooseFileClick()
|
||||
is AddSendAction.ChooseFileClick -> handleChooseFileClick(action)
|
||||
is AddSendAction.NameChange -> handleNameChange(action)
|
||||
is AddSendAction.MaxAccessCountChange -> handleMaxAccessCountChange(action)
|
||||
is AddSendAction.TextChange -> handleTextChange(action)
|
||||
|
@ -354,6 +356,11 @@ class AddSendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handeFileChose(action: AddSendAction.FileChoose) {
|
||||
// TODO: Process the chosen file (BIT-493)
|
||||
sendEvent(AddSendEvent.ShowToast("Not Yet Implemented".asText()))
|
||||
}
|
||||
|
||||
private fun handleRemovePasswordClick() {
|
||||
onEdit {
|
||||
mutableStateFlow.update {
|
||||
|
@ -511,9 +518,8 @@ class AddSendViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleChooseFileClick() {
|
||||
// TODO: allow for file upload: BIT-1085
|
||||
sendEvent(AddSendEvent.ShowToast("Not Implemented: File Upload".asText()))
|
||||
private fun handleChooseFileClick(action: AddSendAction.ChooseFileClick) {
|
||||
sendEvent(AddSendEvent.ShowChooserSheet(action.isCameraPermissionGranted))
|
||||
}
|
||||
|
||||
private fun handleMaxAccessCountChange(action: AddSendAction.MaxAccessCountChange) {
|
||||
|
@ -721,6 +727,11 @@ sealed class AddSendEvent {
|
|||
*/
|
||||
data object NavigateBack : AddSendEvent()
|
||||
|
||||
/**
|
||||
* Show file chooser sheet.
|
||||
*/
|
||||
data class ShowChooserSheet(val withCameraOption: Boolean) : AddSendEvent()
|
||||
|
||||
/**
|
||||
* Show share sheet.
|
||||
*/
|
||||
|
@ -737,6 +748,11 @@ sealed class AddSendEvent {
|
|||
*/
|
||||
sealed class AddSendAction {
|
||||
|
||||
/**
|
||||
* User has chosen a file to be part of the send.
|
||||
*/
|
||||
data class FileChoose(val fileData: IntentManager.FileData) : AddSendAction()
|
||||
|
||||
/**
|
||||
* User clicked the remove password button.
|
||||
*/
|
||||
|
@ -805,7 +821,9 @@ sealed class AddSendAction {
|
|||
/**
|
||||
* User clicked the choose file button.
|
||||
*/
|
||||
data object ChooseFileClick : AddSendAction()
|
||||
data class ChooseFileClick(
|
||||
val isCameraPermissionGranted: Boolean,
|
||||
) : AddSendAction()
|
||||
|
||||
/**
|
||||
* User toggled the "hide text by default" toggle.
|
||||
|
|
|
@ -12,7 +12,7 @@ data class AddSendHandlers(
|
|||
val onNamChange: (String) -> Unit,
|
||||
val onFileTypeSelect: () -> Unit,
|
||||
val onTextTypeSelect: () -> Unit,
|
||||
val onChooseFileCLick: () -> Unit,
|
||||
val onChooseFileClick: (hasPermission: Boolean) -> Unit,
|
||||
val onTextChange: (String) -> Unit,
|
||||
val onIsHideByDefaultToggle: (Boolean) -> Unit,
|
||||
val onMaxAccessCountChange: (Int) -> Unit,
|
||||
|
@ -36,7 +36,7 @@ data class AddSendHandlers(
|
|||
onNamChange = { viewModel.trySendAction(AddSendAction.NameChange(it)) },
|
||||
onFileTypeSelect = { viewModel.trySendAction(AddSendAction.FileTypeClick) },
|
||||
onTextTypeSelect = { viewModel.trySendAction(AddSendAction.TextTypeClick) },
|
||||
onChooseFileCLick = { viewModel.trySendAction(AddSendAction.ChooseFileClick) },
|
||||
onChooseFileClick = { viewModel.trySendAction(AddSendAction.ChooseFileClick(it)) },
|
||||
onTextChange = { viewModel.trySendAction(AddSendAction.TextChange(it)) },
|
||||
onIsHideByDefaultToggle = {
|
||||
viewModel.trySendAction(AddSendAction.HideByDefaultToggle(it))
|
||||
|
|
9
app/src/main/res/xml/file_paths.xml
Normal file
9
app/src/main/res/xml/file_paths.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<cache-path
|
||||
name="cache"
|
||||
path="." />
|
||||
<files-path
|
||||
name="temp_camera_images"
|
||||
path="camera_temp/" />
|
||||
</paths>
|
|
@ -24,6 +24,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl
|
|||
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.model.AddSendType
|
||||
import com.x8bit.bitwarden.ui.util.isEditableText
|
||||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||
|
@ -44,7 +45,8 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
|
||||
private var onNavigateBackCalled = false
|
||||
|
||||
private val intentManager: IntentManager = mockk {
|
||||
private val permissionsManager = FakePermissionManager()
|
||||
private val intentManager: IntentManager = mockk(relaxed = true) {
|
||||
every { shareText(any()) } just runs
|
||||
}
|
||||
private val mutableEventFlow = bufferedMutableSharedFlow<AddSendEvent>()
|
||||
|
@ -60,6 +62,7 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
AddSendScreen(
|
||||
viewModel = viewModel,
|
||||
intentManager = intentManager,
|
||||
permissionsManager = permissionsManager,
|
||||
onNavigateBack = { onNavigateBackCalled = true },
|
||||
)
|
||||
}
|
||||
|
@ -329,7 +332,8 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `Choose file button click should send ChooseFileClick`() {
|
||||
fun `Choose file button click with permission should send ChooseFileClick`() {
|
||||
permissionsManager.checkPermissionResult = true
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_VIEW_STATE.copy(
|
||||
selectedType = AddSendState.ViewState.Content.SendType.File,
|
||||
|
@ -339,7 +343,32 @@ class AddSendScreenTest : BaseComposeTest() {
|
|||
.onNodeWithText("Choose file")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify { viewModel.trySendAction(AddSendAction.ChooseFileClick) }
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
AddSendAction.ChooseFileClick(isCameraPermissionGranted = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `Choose file button click without permission should request permission and send ChooseFileClick`() {
|
||||
permissionsManager.checkPermissionResult = false
|
||||
permissionsManager.getPermissionsResult = false
|
||||
mutableStateFlow.value = DEFAULT_STATE.copy(
|
||||
viewState = DEFAULT_VIEW_STATE.copy(
|
||||
selectedType = AddSendState.ViewState.Content.SendType.File,
|
||||
),
|
||||
)
|
||||
composeTestRule
|
||||
.onNodeWithText("Choose file")
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
AddSendAction.ChooseFileClick(isCameraPermissionGranted = false),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -581,14 +581,21 @@ class AddSendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `ChooseFileClick should emit ShowToast`() = runTest {
|
||||
fun `FileChose should emit ShowToast`() = runTest {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddSendAction.ChooseFileClick)
|
||||
assertEquals(
|
||||
AddSendEvent.ShowToast("Not Implemented: File Upload".asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
viewModel.trySendAction(AddSendAction.FileChoose(fileData = mockk()))
|
||||
assertEquals(AddSendEvent.ShowToast("Not Yet Implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ChooseFileClick should emit ShowToast`() = runTest {
|
||||
val arePermissionsGranted = true
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(AddSendAction.ChooseFileClick(arePermissionsGranted))
|
||||
assertEquals(AddSendEvent.ShowChooserSheet(arePermissionsGranted), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue