Design iteration on bug report screen.

This commit is contained in:
Benoit Marty 2023-06-30 17:09:38 +02:00 committed by Benoit Marty
parent c02dd599d9
commit 1c6aa43bea
4 changed files with 110 additions and 73 deletions

View file

@ -26,7 +26,6 @@ import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
import io.element.android.features.rageshake.impl.bugreport.BugReportPresenter
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
@ContributesNode(AppScope::class) @ContributesNode(AppScope::class)
@ -42,7 +41,8 @@ class BugReportNode @AssistedInject constructor(
BugReportView( BugReportView(
state = state, state = state,
modifier = modifier, modifier = modifier,
onDone = this::onDone onBackPressed = { navigateUp() },
onDone = this::onDone,
) )
} }

View file

@ -17,16 +17,11 @@
package io.element.android.features.rageshake.impl.bugreport package io.element.android.features.rageshake.impl.bugreport
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -35,21 +30,20 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import io.element.android.features.rageshake.impl.R import io.element.android.features.rageshake.impl.R
import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Async
import io.element.android.libraries.designsystem.components.LabelledCheckbox
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
import io.element.android.libraries.designsystem.components.form.textFieldState import io.element.android.libraries.designsystem.components.form.textFieldState
import io.element.android.libraries.designsystem.components.preferences.PreferenceRow
import io.element.android.libraries.designsystem.components.preferences.PreferenceSwitch
import io.element.android.libraries.designsystem.components.preferences.PreferenceView
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground import io.element.android.libraries.designsystem.preview.debugPlaceholderBackground
@ -63,8 +57,9 @@ import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun BugReportView( fun BugReportView(
state: BugReportState, state: BugReportState,
onDone: () -> Unit,
onBackPressed: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onDone: () -> Unit = { },
) { ) {
LogCompositions(tag = "Rageshake", msg = "Root") LogCompositions(tag = "Rageshake", msg = "Root")
val eventSink = state.eventSink val eventSink = state.eventSink
@ -75,56 +70,27 @@ fun BugReportView(
} }
return return
} }
Box(
modifier = modifier Box(modifier = modifier) {
.fillMaxSize() PreferenceView(
.systemBarsPadding() title = stringResource(id = CommonStrings.common_report_a_bug),
.imePadding() onBackPressed = onBackPressed
) {
Column(
modifier = Modifier
.verticalScroll(state = rememberScrollState())
.padding(horizontal = 16.dp),
) { ) {
val isError = state.sending is Async.Failure
val isFormEnabled = state.sending !is Async.Loading val isFormEnabled = state.sending !is Async.Loading
// Title
Text(
text = stringResource(id = CommonStrings.action_report_bug),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Bold,
fontSize = 24.sp,
color = MaterialTheme.colorScheme.primary,
)
// Form
Text(
text = stringResource(id = R.string.screen_bug_report_editor_description),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 16.dp),
fontSize = 16.sp,
color = MaterialTheme.colorScheme.primary,
)
var descriptionFieldState by textFieldState( var descriptionFieldState by textFieldState(
stateValue = state.formState.description stateValue = state.formState.description
) )
Column( Spacer(modifier = Modifier.height(16.dp))
// modifier = Modifier.weight(1f), PreferenceRow {
) {
OutlinedTextField( OutlinedTextField(
value = descriptionFieldState, value = descriptionFieldState,
modifier = Modifier modifier = Modifier.fillMaxWidth(),
.fillMaxWidth()
.padding(top = 16.dp),
enabled = isFormEnabled, enabled = isFormEnabled,
label = { label = {
Text(text = stringResource(id = R.string.screen_bug_report_editor_placeholder)) Text(text = stringResource(id = R.string.screen_bug_report_editor_placeholder))
}, },
supportingText = { supportingText = {
Text(text = stringResource(id = R.string.screen_bug_report_editor_supporting)) Text(text = stringResource(id = R.string.screen_bug_report_editor_description))
}, },
onValueChange = { onValueChange = {
descriptionFieldState = it descriptionFieldState = it
@ -134,35 +100,37 @@ fun BugReportView(
keyboardType = KeyboardType.Text, keyboardType = KeyboardType.Text,
imeAction = ImeAction.Next imeAction = ImeAction.Next
), ),
minLines = 3,
// TODO Error text too short // TODO Error text too short
) )
} }
LabelledCheckbox( Spacer(modifier = Modifier.height(16.dp))
checked = state.formState.sendLogs, PreferenceSwitch(
isChecked = state.formState.sendLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetSendLog(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = R.string.screen_bug_report_include_logs) title = stringResource(id = R.string.screen_bug_report_include_logs),
) )
if (state.hasCrashLogs) { if (state.hasCrashLogs) {
LabelledCheckbox( PreferenceSwitch(
checked = state.formState.sendCrashLogs, isChecked = state.formState.sendCrashLogs,
onCheckedChange = { eventSink(BugReportEvents.SetSendCrashLog(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetSendCrashLog(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = R.string.screen_bug_report_include_crash_logs) title = stringResource(id = R.string.screen_bug_report_include_crash_logs),
) )
} }
LabelledCheckbox( PreferenceSwitch(
checked = state.formState.canContact, isChecked = state.formState.canContact,
onCheckedChange = { eventSink(BugReportEvents.SetCanContact(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetCanContact(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = R.string.screen_bug_report_contact_me) title = stringResource(id = R.string.screen_bug_report_contact_me)
) )
if (state.screenshotUri != null) { if (state.screenshotUri != null) {
LabelledCheckbox( PreferenceSwitch(
checked = state.formState.sendScreenshot, isChecked = state.formState.sendScreenshot,
onCheckedChange = { eventSink(BugReportEvents.SetSendScreenshot(it)) }, onCheckedChange = { eventSink(BugReportEvents.SetSendScreenshot(it)) },
enabled = isFormEnabled, enabled = isFormEnabled,
text = stringResource(id = R.string.screen_bug_report_include_screenshot) title = stringResource(id = R.string.screen_bug_report_include_screenshot)
) )
if (state.formState.sendScreenshot) { if (state.formState.sendScreenshot) {
Box( Box(
@ -183,16 +151,19 @@ fun BugReportView(
} }
} }
// Submit // Submit
Button( PreferenceRow {
onClick = { eventSink(BugReportEvents.SendBugReport) }, Button(
enabled = state.submitEnabled, onClick = { eventSink(BugReportEvents.SendBugReport) },
modifier = Modifier enabled = state.submitEnabled,
.fillMaxWidth() modifier = Modifier
.padding(vertical = 32.dp) .fillMaxWidth()
) { .padding(top = 24.dp, bottom = 16.dp)
Text(text = stringResource(id = CommonStrings.action_send)) ) {
Text(text = stringResource(id = CommonStrings.action_send))
}
} }
} }
when (state.sending) { when (state.sending) {
is Async.Loading -> { is Async.Loading -> {
// Indeterminate indicator, to avoid the freeze effect if the connection takes time to initialize. // Indeterminate indicator, to avoid the freeze effect if the connection takes time to initialize.
@ -219,5 +190,9 @@ fun BugReportViewDarkPreview(@PreviewParameter(BugReportStateProvider::class) st
@Composable @Composable
private fun ContentToPreview(state: BugReportState) { private fun ContentToPreview(state: BugReportState) {
BugReportView(state = state) BugReportView(
state = state,
onDone = {},
onBackPressed = {},
)
} }

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.libraries.designsystem.components.preferences
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
import io.element.android.libraries.designsystem.preview.PreviewGroup
import io.element.android.libraries.designsystem.theme.components.Text
/**
* Simple Row with which follow design for preferences.
*/
@Composable
fun PreferenceRow(
modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) {
Row(
modifier = modifier
.padding(horizontal = preferencePaddingHorizontal)
.heightIn(min = preferenceMinHeight)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
) {
content()
}
}
@Preview(group = PreviewGroup.Preferences)
@Composable
internal fun PreferenceRowPreview() = ElementThemedPreview { ContentToPreview() }
@Composable
private fun ContentToPreview() {
PreferenceRow {
Text(text = "Content")
}
}

View file

@ -68,6 +68,7 @@ fun OutlinedTextField(
keyboardActions: KeyboardActions = KeyboardActions.Default, keyboardActions: KeyboardActions = KeyboardActions.Default,
singleLine: Boolean = false, singleLine: Boolean = false,
maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE, maxLines: Int = if (singleLine) 1 else Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = OutlinedTextFieldDefaults.shape, shape: Shape = OutlinedTextFieldDefaults.shape,
colors: TextFieldColors = OutlinedTextFieldDefaults.colors() colors: TextFieldColors = OutlinedTextFieldDefaults.colors()
@ -90,6 +91,7 @@ fun OutlinedTextField(
keyboardActions = keyboardActions, keyboardActions = keyboardActions,
singleLine = singleLine, singleLine = singleLine,
maxLines = maxLines, maxLines = maxLines,
minLines = minLines,
interactionSource = interactionSource, interactionSource = interactionSource,
shape = shape, shape = shape,
colors = colors, colors = colors,