feat(report): Added attach log file functionality

This commit is contained in:
Damir Mihaljinec 2024-04-03 18:00:32 +02:00 committed by MargeBot
parent b448b939f3
commit b3f95c2594
22 changed files with 223 additions and 108 deletions

View File

@ -20,14 +20,17 @@ package me.proton.core.report.dagger
import android.os.Build
import dagger.Binds
import dagger.BindsOptionalOf
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import dagger.multibindings.ElementsIntoSet
import me.proton.core.domain.entity.Product
import me.proton.core.report.data.SendBugReportImpl
import me.proton.core.report.data.repository.ReportRepositoryImpl
import me.proton.core.report.domain.entity.BugReportMeta
import me.proton.core.report.domain.provider.BugReportLogProvider
import me.proton.core.report.domain.repository.ReportRepository
import me.proton.core.report.domain.usecase.SendBugReport
@ -54,4 +57,7 @@ internal interface CoreReportBindModule {
@Binds
fun provideSendBugReportUseCase(useCase: SendBugReportImpl): SendBugReport
@BindsOptionalOf
fun optionalBugReportLogProvider(): BugReportLogProvider
}

View File

@ -24,16 +24,16 @@ public final class me/proton/core/report/data/SendBugReportImpl_Factory : dagger
}
public final class me/proton/core/report/data/repository/ReportRepositoryImpl : me/proton/core/report/domain/repository/ReportRepository {
public fun <init> (Lme/proton/core/network/data/ApiProvider;)V
public fun <init> (Lme/proton/core/network/data/ApiProvider;Ljava/util/Optional;)V
public fun sendReport (Lme/proton/core/report/domain/entity/BugReport;Lme/proton/core/report/domain/entity/BugReportMeta;Lme/proton/core/report/domain/entity/BugReportExtra;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/report/data/repository/ReportRepositoryImpl_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lme/proton/core/report/data/repository/ReportRepositoryImpl_Factory;
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/report/data/repository/ReportRepositoryImpl_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/report/data/repository/ReportRepositoryImpl;
public static fun newInstance (Lme/proton/core/network/data/ApiProvider;)Lme/proton/core/report/data/repository/ReportRepositoryImpl;
public static fun newInstance (Lme/proton/core/network/data/ApiProvider;Ljava/util/Optional;)Lme/proton/core/report/data/repository/ReportRepositoryImpl;
}
public abstract interface class me/proton/core/report/data/work/BugReportWorker_AssistedFactory : androidx/hilt/work/WorkerAssistedFactory {

View File

@ -25,8 +25,8 @@ plugins {
}
protonCoverage {
branchCoveragePercentage.set(76)
lineCoveragePercentage.set(91)
branchCoveragePercentage.set(47)
lineCoveragePercentage.set(60)
}
publishOption.shouldBePublishedAsLib = true
@ -85,4 +85,4 @@ dependencyAnalysis {
exclude("com.google.guava:guava")
}
}
}
}

View File

@ -18,13 +18,13 @@
package me.proton.core.report.data.api
import me.proton.core.report.data.api.request.BugReportRequest
import me.proton.core.network.data.protonApi.BaseRetrofitApi
import me.proton.core.network.data.protonApi.GenericResponse
import okhttp3.RequestBody
import retrofit2.http.Body
import retrofit2.http.POST
internal interface ReportApi : BaseRetrofitApi {
@POST("reports/bug")
suspend fun sendBugReport(@Body body: BugReportRequest): GenericResponse
suspend fun sendBugReport(@Body body: RequestBody): GenericResponse
}

View File

@ -1,60 +0,0 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.report.data.api.request
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
internal data class BugReportRequest(
@SerialName("OS")
val osName: String,
@SerialName("OSVersion")
val osVersion: String,
@SerialName("Client")
val client: String,
@SerialName("ClientType")
val clientType: Int,
@SerialName("ClientVersion")
val appVersionName: String,
@SerialName("Title")
val title: String,
@SerialName("Description")
val description: String,
@SerialName("Username")
val username: String,
@SerialName("Email")
val email: String,
/** VPN-only */
@SerialName("Country")
val country: String? = null,
/** VPN-only */
@SerialName("ISP")
val isp: String? = null
)

View File

@ -18,20 +18,28 @@
package me.proton.core.report.data.repository
import android.webkit.MimeTypeMap
import me.proton.core.domain.entity.Product
import me.proton.core.network.data.ApiProvider
import me.proton.core.network.data.protonApi.isSuccess
import me.proton.core.network.domain.onSuccess
import me.proton.core.report.data.api.ReportApi
import me.proton.core.report.data.api.request.BugReportRequest
import me.proton.core.report.domain.entity.BugReport
import me.proton.core.report.domain.entity.BugReportExtra
import me.proton.core.report.domain.entity.BugReportMeta
import me.proton.core.report.domain.provider.BugReportLogProvider
import me.proton.core.report.domain.repository.ReportRepository
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import java.io.File
import java.util.Optional
import javax.inject.Inject
import kotlin.jvm.optionals.getOrNull
public class ReportRepositoryImpl @Inject constructor(
private val apiProvider: ApiProvider,
private val bugReportLogProvider: Optional<BugReportLogProvider>,
) : ReportRepository {
override suspend fun sendReport(
bugReport: BugReport,
@ -45,24 +53,72 @@ public class ReportRepositoryImpl @Inject constructor(
Product.Drive -> 4
Product.Pass -> 5
}
val logProvider = bugReportLogProvider.getOrNull()
val logFile = takeIf { bugReport.shouldAttachLog }
?.let { logProvider?.getLog() }
val request = BugReportRequest(
osName = meta.osName,
osVersion = meta.osVersion,
client = meta.clientName,
clientType = clientType,
appVersionName = meta.appVersionName,
title = bugReport.title,
description = bugReport.description,
username = bugReport.username,
email = bugReport.email,
country = extra?.country,
isp = extra?.isp
)
runCatching {
apiProvider.get<ReportApi>()
.invoke {
sendBugReport(
getMultipartBodyBuilder(
bugReport = bugReport,
meta = meta,
clientType = clientType,
country = extra?.country,
isp = extra?.isp,
logFile = logFile,
).build()
)
}
.onSuccess { check(it.isSuccess()) }
.valueOrThrow
}
.apply { logFile?.let { logProvider?.releaseLog(logFile) } }
.getOrThrow()
}
apiProvider.get<ReportApi>()
.invoke { sendBugReport(request) }
.onSuccess { check(it.isSuccess()) }
.valueOrThrow
private fun getMultipartBodyBuilder(
bugReport: BugReport,
meta: BugReportMeta,
clientType: Int,
country: String?,
isp: String?,
logFile: File?,
): MultipartBody.Builder = MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart(name = "OS", value = meta.osName)
.addFormDataPart(name = "OSVersion", value = meta.osVersion)
.addFormDataPart(name = "Client", value = meta.clientName)
.addFormDataPart(name = "ClientVersion", value = meta.appVersionName)
.addFormDataPart(name = "ClientType",value = "$clientType")
.addFormDataPart(name = "Title", value = bugReport.title)
.addFormDataPart(name = "Description", value = bugReport.description)
.addFormDataPart(name = "Username", value = bugReport.username)
.addFormDataPart(name = "Email", value = bugReport.email)
.apply {
country?.let {
addFormDataPart(name = "Country", value = country)
}
isp?.let {
addFormDataPart(name = "ISP", value = isp)
}
logFile?.takeIf { file -> file.exists() && file.length() > 0 }?.let { file ->
addFormDataPart(
name = ATTACHMENT,
filename = file.name,
body = file.asRequestBody(file.mimeType?.toMediaTypeOrNull()),
)
}
}
private val File.mimeType: String? get() = name
.substringAfterLast('.', "")
.let { extension ->
MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
}
private companion object {
private const val ATTACHMENT = "Attachment"
}
}

View File

@ -38,6 +38,7 @@ import me.proton.core.test.kotlin.TestDispatcherProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import java.util.Optional
import kotlin.test.assertFailsWith
internal class ReportRepositoryImplTest {
@ -51,7 +52,8 @@ internal class ReportRepositoryImplTest {
title = "title",
description = "description",
username = "username",
email = "email@test"
email = "email@test",
shouldAttachLog = false,
)
private val testBugReportMeta = BugReportMeta(
appVersionName = "android-mail@1.2.3",
@ -71,7 +73,7 @@ internal class ReportRepositoryImplTest {
every { create(any(), ReportApi::class) } returns mockApiManager
}
mockApiProvider = ApiProvider(mockApiManagerFactory, mockSessionProvider, dispatcherProvider)
tested = ReportRepositoryImpl(mockApiProvider)
tested = ReportRepositoryImpl(mockApiProvider, Optional.empty())
}
@Test

View File

@ -27,7 +27,8 @@ internal val testBugReport = BugReport(
title = "Title",
description = "Bug report description",
username = "username",
email = "test@email"
email = "test@email",
shouldAttachLog = false,
)
internal val testBugReportMeta = BugReportMeta(
appVersionName = "android-mail@1.2.3",

View File

@ -3,16 +3,18 @@ public final class me/proton/core/report/domain/entity/BugReport {
public static final field DescriptionMaxLength I
public static final field DescriptionMinLength I
public static final field SubjectMaxLength I
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lme/proton/core/report/domain/entity/BugReport;
public static synthetic fun copy$default (Lme/proton/core/report/domain/entity/BugReport;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lme/proton/core/report/domain/entity/BugReport;
public final fun component5 ()Z
public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Z)Lme/proton/core/report/domain/entity/BugReport;
public static synthetic fun copy$default (Lme/proton/core/report/domain/entity/BugReport;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lme/proton/core/report/domain/entity/BugReport;
public fun equals (Ljava/lang/Object;)Z
public final fun getDescription ()Ljava/lang/String;
public final fun getEmail ()Ljava/lang/String;
public final fun getShouldAttachLog ()Z
public final fun getTitle ()Ljava/lang/String;
public final fun getUsername ()Ljava/lang/String;
public fun hashCode ()I
@ -124,6 +126,11 @@ public final class me/proton/core/report/domain/entity/BugReportValidationError
public static fun values ()[Lme/proton/core/report/domain/entity/BugReportValidationError;
}
public abstract interface class me/proton/core/report/domain/provider/BugReportLogProvider {
public abstract fun getLog (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun releaseLog (Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class me/proton/core/report/domain/repository/ReportRepository {
public abstract fun sendReport (Lme/proton/core/report/domain/entity/BugReport;Lme/proton/core/report/domain/entity/BugReportMeta;Lme/proton/core/report/domain/entity/BugReportExtra;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

View File

@ -27,7 +27,8 @@ public data class BugReport(
val title: String,
val description: String,
val username: String,
val email: String
val email: String,
val shouldAttachLog: Boolean,
) {
public companion object {
public const val DescriptionMinLength: Int = 10

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2024 Proton AG.
* This file is part of Proton Drive.
*
* Proton Drive is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Proton Drive is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.report.domain.provider
import java.io.File
public interface BugReportLogProvider {
/**
* Provides a log file which is sent with bug report. If possible it should be zip.
*/
public suspend fun getLog(): File?
/**
* Once report a bug does not need a log file anymore it releases it. It's safe to delete a log file at this point.
*/
public suspend fun releaseLog(log: File)
}

View File

@ -30,7 +30,8 @@ internal class BugReportValidationTest {
title = "Title",
description = "Description...",
username = "username",
email = "test@user"
email = "test@user",
shouldAttachLog = false,
).validate()
assertTrue(errors.isEmpty())
}
@ -41,7 +42,8 @@ internal class BugReportValidationTest {
title = "",
description = "",
username = "",
email = ""
email = "",
shouldAttachLog = false,
).validate()
assertNotNull(errors)
assertEquals(2, errors.size)
@ -55,7 +57,8 @@ internal class BugReportValidationTest {
title = " ".repeat(20),
description = " ".repeat(20),
username = "",
email = ""
email = "",
shouldAttachLog = false,
).validate()
assertNotNull(errors)
assertEquals(2, errors.size)
@ -69,7 +72,8 @@ internal class BugReportValidationTest {
title = "Title",
description = "Test",
username = "",
email = ""
email = "",
shouldAttachLog = false,
).validate()
assertNotNull(errors)
assertEquals(1, errors.size)
@ -82,7 +86,8 @@ internal class BugReportValidationTest {
title = "a".repeat(BugReport.SubjectMaxLength + 1),
description = "b".repeat(BugReport.DescriptionMaxLength + 1),
username = "",
email = ""
email = "",
shouldAttachLog = false,
).validate()
assertNotNull(errors)
assertEquals(2, errors.size)

View File

@ -35,6 +35,8 @@ public final class me/proton/core/report/presentation/ReportOrchestrator_Factory
}
public final class me/proton/core/report/presentation/databinding/CoreReportActivityBugReportBinding : androidx/viewbinding/ViewBinding {
public final field bugReportAttachLog Lme/proton/core/presentation/ui/view/ProtonCheckbox;
public final field bugReportAttachLogLayout Landroid/widget/LinearLayout;
public final field bugReportDescription Lcom/google/android/material/textfield/TextInputEditText;
public final field bugReportDescriptionLayout Lcom/google/android/material/textfield/TextInputLayout;
public final field bugReportSubject Lcom/google/android/material/textfield/TextInputEditText;
@ -103,11 +105,11 @@ public abstract interface class me/proton/core/report/presentation/ui/BugReportA
}
public final class me/proton/core/report/presentation/viewmodel/BugReportViewModel_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lme/proton/core/report/presentation/viewmodel/BugReportViewModel_Factory;
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/report/presentation/viewmodel/BugReportViewModel_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/report/presentation/viewmodel/BugReportViewModel;
public static fun newInstance (Lme/proton/core/report/domain/usecase/SendBugReport;)Lme/proton/core/report/presentation/viewmodel/BugReportViewModel;
public static fun newInstance (Lme/proton/core/report/domain/usecase/SendBugReport;Ljava/util/Optional;)Lme/proton/core/report/presentation/viewmodel/BugReportViewModel;
}
public final class me/proton/core/report/presentation/viewmodel/BugReportViewModel_HiltModules {

View File

@ -28,7 +28,7 @@ plugins {
protonCoverage {
branchCoveragePercentage.set(42)
lineCoveragePercentage.set(65)
lineCoveragePercentage.set(67)
}
publishOption.shouldBePublishedAsLib = true

View File

@ -30,7 +30,8 @@ internal sealed class BugReportFormState {
internal data class ReportFormData(
val subject: String,
val description: String
val description: String,
val attachLog: Boolean = false,
)
internal enum class ExitSignal {

View File

@ -28,6 +28,7 @@ import android.view.View
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.viewModels
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -112,6 +113,8 @@ internal class BugReportActivity : ProtonViewBindingActivity<CoreReportActivityB
showKeyboard(binding.bugReportDescription)
}
}
binding.bugReportAttachLogLayout.visibility = if (viewModel.shouldShowAttachLog) View.VISIBLE else View.GONE
}
private fun initToolbar() {
@ -151,6 +154,7 @@ internal class BugReportActivity : ProtonViewBindingActivity<CoreReportActivityB
return ReportFormData(
subject = binding.bugReportSubject.text.toString(),
description = binding.bugReportDescription.text.toString(),
attachLog = binding.bugReportAttachLog.isVisible && binding.bugReportAttachLog.isChecked,
)
}

View File

@ -27,21 +27,23 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import me.proton.core.report.domain.entity.BugReport
import me.proton.core.report.domain.entity.BugReportExtra
import me.proton.core.report.domain.entity.BugReportField
import me.proton.core.report.domain.entity.validate
import me.proton.core.report.domain.provider.BugReportLogProvider
import me.proton.core.report.domain.usecase.SendBugReport
import me.proton.core.report.presentation.entity.BugReportFormState
import me.proton.core.report.presentation.entity.ExitSignal
import me.proton.core.report.presentation.entity.ReportFormData
import java.util.Optional
import javax.inject.Inject
@HiltViewModel
internal class BugReportViewModel @Inject constructor(
private val sendBugReport: SendBugReport
private val sendBugReport: SendBugReport,
bugReportLogProvider: Optional<BugReportLogProvider>,
) : ViewModel() {
private val _bugReportFormState = MutableStateFlow<BugReportFormState>(BugReportFormState.Idle)
val bugReportFormState: StateFlow<BugReportFormState> = _bugReportFormState.asStateFlow()
@ -52,6 +54,8 @@ internal class BugReportViewModel @Inject constructor(
private val _hideKeyboardSignal = MutableSharedFlow<Unit>()
val hideKeyboardSignal: Flow<Unit> = _hideKeyboardSignal.asSharedFlow()
val shouldShowAttachLog = bugReportLogProvider.isPresent
/** Re-validates the description, and preserves other errors (if any). */
suspend fun revalidateDescription(description: String) {
val errors = (_bugReportFormState.value as? BugReportFormState.FormError)?.errors.orEmpty()
@ -115,6 +119,7 @@ internal class BugReportViewModel @Inject constructor(
title = data.subject,
description = data.description,
email = email,
username = username
username = username,
shouldAttachLog = data.attachLog,
)
}

View File

@ -85,6 +85,30 @@
android:layout_marginBottom="8dp"
android:background="?proton_separator_norm" />
<LinearLayout
android:id="@+id/bug_report_attach_log_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<me.proton.core.presentation.ui.view.ProtonCheckbox
android:id="@+id/bug_report_attach_log"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:checked="true"
android:text="@string/core_report_bug_attach_log" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="?proton_separator_norm" />
</LinearLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/bug_report_description_layout"
android:layout_width="match_parent"

View File

@ -38,4 +38,5 @@
<string name="core_report_bug_general_error">Something went wrong. Please try again in a few minutes.</string>
<string name="core_report_bug_send">Send report</string>
<string name="core_report_bug_success">Thank you for your report</string>
<string name="core_report_bug_attach_log">Attach log</string>
</resources>

View File

@ -33,6 +33,7 @@ import me.proton.core.test.kotlin.CoroutinesTest
import me.proton.core.test.kotlin.flowTest
import org.junit.Before
import org.junit.Test
import java.util.Optional
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertIs
@ -51,7 +52,7 @@ internal class BugReportViewModelTest : CoroutinesTest by CoroutinesTest() {
@Before
fun setUp() {
sendBugReport = mockk()
tested = BugReportViewModel(sendBugReport)
tested = BugReportViewModel(sendBugReport, Optional.empty())
}
@Test

View File

@ -37,8 +37,11 @@ public final class me/proton/core/report/test/flow/ReportFlow {
public final class me/proton/core/report/test/robot/ReportRobot {
public static final field INSTANCE Lme/proton/core/report/test/robot/ReportRobot;
public final fun checkAttachLog ()V
public final fun clickAttachLog ()V
public final fun clickSend ()V
public final fun fillDescription (Ljava/lang/String;)Lme/proton/core/report/test/robot/ReportRobot;
public final fun fillSubject (Ljava/lang/String;)Lme/proton/core/report/test/robot/ReportRobot;
public final fun uncheckAttachLog ()V
}

View File

@ -18,6 +18,10 @@
package me.proton.core.report.test.robot
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isChecked
import androidx.test.espresso.matcher.ViewMatchers.isNotChecked
import me.proton.core.report.presentation.R
import me.proton.core.test.android.instrumented.matchers.inputFieldMatcher
import me.proton.test.fusion.Fusion.view
@ -26,6 +30,7 @@ import me.proton.test.fusion.Fusion.view
public object ReportRobot {
private val subjectInput = view.withCustomMatcher(inputFieldMatcher(R.id.bug_report_subject))
private val descriptionInput = view.withCustomMatcher(inputFieldMatcher(R.id.bug_report_description))
private val attachLogCheckBox = view.withId(R.id.bug_report_attach_log)
private val sendButton = view.withId(R.id.bug_report_send)
public fun fillSubject(subject: String): ReportRobot = apply {
@ -36,6 +41,24 @@ public object ReportRobot {
descriptionInput.typeText(description)
}
public fun checkAttachLog() {
attachLogCheckBox
.interaction
.check(matches(isNotChecked()))
.perform(click())
}
public fun uncheckAttachLog() {
attachLogCheckBox
.interaction
.check(matches(isChecked()))
.perform(click())
}
public fun clickAttachLog() {
attachLogCheckBox.click()
}
public fun clickSend() {
sendButton.click()
}