feat(report): Added attach log file functionality
This commit is contained in:
parent
b448b939f3
commit
b3f95c2594
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -28,7 +28,7 @@ plugins {
|
|||
|
||||
protonCoverage {
|
||||
branchCoveragePercentage.set(42)
|
||||
lineCoveragePercentage.set(65)
|
||||
lineCoveragePercentage.set(67)
|
||||
}
|
||||
|
||||
publishOption.shouldBePublishedAsLib = true
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue