Add the call quality diagnostics fragment.

This commit is contained in:
Alex Hart
2026-01-12 14:50:04 -04:00
committed by Michelle Tang
parent 5759609a11
commit ed12a7691d
6 changed files with 163 additions and 11 deletions

View File

@@ -76,6 +76,12 @@ class CallQualityBottomSheetFragment : ComposeBottomSheetDialogFragment() {
)
}
override fun viewDiagnostics() {
CallQualityDiagnosticsFragment.create(
viewModel.getRequestSnapshot()
).show(parentFragmentManager, null)
}
override fun onUserSatisfiedWithCall(isUserSatisfiedWithCall: Boolean) {
viewModel.setUserSatisfiedWithCall(isUserSatisfiedWithCall)
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.calls.quality
import androidx.compose.runtime.Composable
import androidx.core.os.bundleOf
import org.signal.storageservice.protos.calls.quality.SubmitCallQualitySurveyRequest
import org.thoughtcrime.securesms.compose.ComposeFullScreenDialogFragment
class CallQualityDiagnosticsFragment : ComposeFullScreenDialogFragment() {
companion object {
private const val REQUEST_KEY = "CallQualityDiagnosticsRequestKey"
fun create(request: SubmitCallQualitySurveyRequest): CallQualityDiagnosticsFragment {
return CallQualityDiagnosticsFragment().apply {
arguments = bundleOf(REQUEST_KEY to request.encode())
}
}
}
private val callQualitySurveyRequest: SubmitCallQualitySurveyRequest by lazy {
val bytes = requireArguments().getByteArray(REQUEST_KEY)!!
SubmitCallQualitySurveyRequest.ADAPTER.decode(bytes)
}
@Composable
override fun DialogContent() {
CallQualityDiagnosticsScreen(
callQualitySurveyRequest = callQualitySurveyRequest,
onNavigationClick = { requireActivity().onBackPressedDispatcher.onBackPressed() }
)
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2026 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.calls.quality
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import org.signal.core.ui.compose.Buttons
import org.signal.core.ui.compose.DayNightPreviews
import org.signal.core.ui.compose.Previews
import org.signal.core.ui.compose.Scaffolds
import org.signal.core.ui.compose.horizontalGutters
import org.signal.storageservice.protos.calls.quality.SubmitCallQualitySurveyRequest
import org.thoughtcrime.securesms.R
@Composable
fun CallQualityDiagnosticsScreen(
callQualitySurveyRequest: SubmitCallQualitySurveyRequest,
onNavigationClick: () -> Unit = {}
) {
Scaffolds.Settings(
title = stringResource(R.string.CallQualityDiagnosticsScreen__diagnostic_information),
navigationIcon = ImageVector.vectorResource(R.drawable.symbol_arrow_start_24),
navigationContentDescription = stringResource(R.string.CallQualityDiagnosticsScreen__close),
onNavigationClick = onNavigationClick
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(it)
.fillMaxSize()
.horizontalGutters()
) {
Box(
modifier = Modifier
.weight(1f)
.verticalScroll(rememberScrollState())
) {
Text(
text = callQualitySurveyRequest.toString().substringAfter(SubmitCallQualitySurveyRequest::class.simpleName ?: "")
)
}
Buttons.LargeTonal(
onClick = onNavigationClick,
modifier = Modifier
.padding(top = 10.dp, bottom = 24.dp)
.widthIn(min = 256.dp)
) {
Text(text = stringResource(R.string.CallQualityDiagnosticsScreen__close))
}
}
}
}
@DayNightPreviews
@Composable
private fun CallQualityDiagnosticsScreenPreview() {
Previews.Preview {
CallQualityDiagnosticsScreen(
callQualitySurveyRequest = SubmitCallQualitySurveyRequest()
)
}
}

View File

@@ -58,6 +58,10 @@ class CallQualityScreenViewModel(
return
}
AppDependencies.jobManager.add(CallQualitySurveySubmissionJob(getRequestSnapshot(), state.value.isShareDebugLogSelected))
}
fun getRequestSnapshot(): SubmitCallQualitySurveyRequest {
val stateSnapshot = state.value
val somethingElseDescription: String? = if (stateSnapshot.selectedQualityIssues.contains(CallQualityIssue.SOMETHING_ELSE)) {
stateSnapshot.somethingElseDescription.takeIf { it.isNotEmpty() }
@@ -65,12 +69,10 @@ class CallQualityScreenViewModel(
null
}
val requestToSubmitToJob = initialRequest.newBuilder()
return initialRequest.newBuilder()
.user_satisfied(stateSnapshot.isUserSatisfiedWithCall)
.call_quality_issues(stateSnapshot.selectedQualityIssues.map { it.code })
.additional_issues_description(somethingElseDescription)
.build()
AppDependencies.jobManager.add(CallQualitySurveySubmissionJob(requestToSubmitToJob, stateSnapshot.isShareDebugLogSelected))
}
}

View File

@@ -141,6 +141,7 @@ fun CallQualitySheet(
CallQualitySheetNavEntry.HelpUsImprove -> HelpUsImprove(
isShareDebugLogSelected = state.isShareDebugLogSelected,
onViewDebugLogClick = callback::viewDebugLog,
onViewDiagnosticsClick = callback::viewDiagnostics,
onCancelClick = callback::dismiss,
onShareDebugLogChanged = callback::onShareDebugLogChanged,
onSubmitClick = callback::submit
@@ -431,6 +432,7 @@ private fun IssueChip(
private fun HelpUsImprove(
isShareDebugLogSelected: Boolean,
onShareDebugLogChanged: (Boolean) -> Unit,
onViewDiagnosticsClick: () -> Unit,
onViewDebugLogClick: () -> Unit,
onCancelClick: () -> Unit,
onSubmitClick: () -> Unit
@@ -443,12 +445,12 @@ private fun HelpUsImprove(
withLink(
link = LinkAnnotation.Clickable(
"view-your-debug-log",
linkInteractionListener = { onViewDebugLogClick() },
"view-diagnostics",
linkInteractionListener = { onViewDiagnosticsClick() },
styles = TextLinkStyles(style = SpanStyle(color = MaterialTheme.colorScheme.primary))
)
) {
append(stringResource(R.string.CallQualitySheet__view_your_debug_log))
append(stringResource(R.string.CallQualitySheet__diagnostic_information_about_your_call))
}
append(" ")
@@ -463,7 +465,20 @@ private fun HelpUsImprove(
)
Text(
text = stringResource(R.string.CallQualitySheet__debug_log_privacy_notice),
text = buildAnnotatedString {
append(stringResource(R.string.CallQualitySheet__information_shared_with_us))
append(" ")
withLink(
link = LinkAnnotation.Clickable(
"view-debug-log",
linkInteractionListener = { onViewDebugLogClick() },
styles = TextLinkStyles(style = SpanStyle(color = MaterialTheme.colorScheme.primary))
)
) {
append(stringResource(R.string.CallQualitySheet__view_debug_log))
}
},
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
@@ -688,6 +703,7 @@ private fun HelpUsImprovePreview() {
Column {
HelpUsImprove(
isShareDebugLogSelected = true,
onViewDiagnosticsClick = {},
onViewDebugLogClick = {},
onCancelClick = {},
onShareDebugLogChanged = {},
@@ -728,6 +744,7 @@ data class CallQualitySheetState(
interface CallQualitySheetCallback {
fun dismiss()
fun viewDebugLog()
fun viewDiagnostics()
fun onUserSatisfiedWithCall(isUserSatisfiedWithCall: Boolean)
fun describeYourIssue()
fun onCallQualityIssueSelectionChanged(selection: Set<CallQualityIssue>)
@@ -738,6 +755,7 @@ interface CallQualitySheetCallback {
object Empty : CallQualitySheetCallback {
override fun dismiss() = Unit
override fun viewDebugLog() = Unit
override fun viewDiagnostics() = Unit
override fun onUserSatisfiedWithCall(isUserSatisfiedWithCall: Boolean) = Unit
override fun describeYourIssue() = Unit
override fun onCallQualityIssueSelectionChanged(selection: Set<CallQualityIssue>) = Unit

View File

@@ -2705,6 +2705,12 @@
<!-- Content description for the button that switches between front and back camera during a call -->
<string name="SwitchCameraButton__switch_camera_direction">Switch camera direction</string>
<!-- CallQualityDiagnosticsScreen -->
<!-- Title for the call quality diagnostics screen -->
<string name="CallQualityDiagnosticsScreen__diagnostic_information">Diagnostic information</string>
<!-- Content description for the close/back navigation button -->
<string name="CallQualityDiagnosticsScreen__close">Close</string>
<!-- CallToastPopupWindow -->
<string name="CallToastPopupWindow__swipe_to_view_screen_share">Swipe to view screen share</string>
@@ -9037,11 +9043,15 @@
<!-- Title asking user to help improve Signal -->
<string name="CallQualitySheet__help_us_improve">Help us improve</string>
<!-- First part of description explaining diagnostics help improve call quality -->
<string name="CallQualitySheet__help_us_improve_description_prefix">Sending us your diagnostics info helps us improve call quality. You can </string>
<string name="CallQualitySheet__help_us_improve_description_prefix">Submitting will share your feedback along with</string>
<!-- Clickable text to launch diagnostic fragment -->
<string name="CallQualitySheet__diagnostic_information_about_your_call">diagnostic information about your call</string>
<!-- End of description explaining diagnostics help improve call quality -->
<string name="CallQualitySheet__help_us_improve_description_suffix">You can optionally share a debug log to help us improve call quality.</string>
<!-- Text explaining the debug log -->
<string name="CallQualitySheet__information_shared_with_us">Information shared with us contains low level app information and does not include the contents of your calls.</string>
<!-- Link text to view debug log -->
<string name="CallQualitySheet__view_your_debug_log">view your debug log</string>
<!-- Last part of description about viewing debug log before submitting -->
<string name="CallQualitySheet__help_us_improve_description_suffix"> before submitting.</string>
<string name="CallQualitySheet__view_debug_log">View debug log</string>
<!-- Toggle label for sharing debug log -->
<string name="CallQualitySheet__share_debug_log">Share debug log</string>
<!-- Privacy notice explaining what debug logs contain and why they are helpful -->