Improve feedback screen layout and UX

- Restructure FeedbackScreen to use a cleaner card-based layout
- Add proper padding and spacing between feedback items
- Group related actions together for better visual hierarchy
- Add missing string resources for feedback UI elements
- Remove redundant code and simplify composable structure

https://claude.ai/code/session_01EYDStguSpVg95qJckEdtPc
This commit is contained in:
Claude
2026-03-13 19:46:10 +00:00
parent 0114167eb4
commit 8c0fde6d58
2 changed files with 79 additions and 122 deletions
@@ -1,47 +1,40 @@
package com.greenart7c3.nostrsigner.ui
import android.annotation.SuppressLint
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.RadioButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.greenart7c3.nostrsigner.Amber
import com.greenart7c3.nostrsigner.R
import com.greenart7c3.nostrsigner.models.Account
import com.greenart7c3.nostrsigner.models.FeedbackType
import com.greenart7c3.nostrsigner.ui.components.AmberButton
import com.greenart7c3.nostrsigner.ui.theme.light
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.launch
@SuppressLint("UnusedBoxWithConstraintsScope")
@Composable
fun FeedbackScreen(
modifier: Modifier = Modifier,
@@ -53,90 +46,87 @@ fun FeedbackScreen(
var body by remember { mutableStateOf(TextFieldValue("")) }
var feedbackType by remember { mutableStateOf(FeedbackType.BUG_REPORT) }
BoxWithConstraints(
Column(
modifier = modifier
.fillMaxSize(),
.fillMaxSize()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalArrangement = Arrangement.SpaceBetween,
) {
val maxHeight = maxHeight
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween,
modifier = Modifier
.weight(1f)
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Column(
modifier = Modifier.weight(1f, fill = true),
) {
LazyRow(
modifier = Modifier.fillMaxWidth(),
) {
item {
NamedRadio(
isSelected = feedbackType == FeedbackType.BUG_REPORT,
name = stringResource(R.string.bug_report),
onClick = {
feedbackType = FeedbackType.BUG_REPORT
},
)
}
item { Spacer(modifier = Modifier.width(8.dp)) }
item {
NamedRadio(
isSelected = feedbackType == FeedbackType.ENHANCEMENT_REQUEST,
name = stringResource(id = R.string.enhancement_request),
onClick = {
feedbackType = FeedbackType.ENHANCEMENT_REQUEST
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Text(
text = stringResource(R.string.feedback_type),
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
SingleChoiceSegmentedButtonRow(modifier = Modifier.fillMaxWidth()) {
FeedbackType.entries.forEachIndexed { index, type ->
SegmentedButton(
selected = feedbackType == type,
onClick = { feedbackType = type },
shape = SegmentedButtonDefaults.itemShape(
index = index,
count = FeedbackType.entries.size,
),
label = {
Text(
text = stringResource(
when (type) {
FeedbackType.BUG_REPORT -> R.string.bug_report
FeedbackType.ENHANCEMENT_REQUEST -> R.string.enhancement_request
},
),
)
},
)
}
}
TextField(
header,
onValueChange = { header = it },
modifier = Modifier
.fillMaxWidth(),
maxLines = 3,
placeholder = {
Text(
stringResource(id = R.string.subject),
style = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground.light(),
)
},
textStyle = MaterialTheme.typography.titleLarge.copy(fontWeight = FontWeight.Bold),
colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
),
)
val scrollState = rememberScrollState()
TextField(
value = body,
onValueChange = { body = it },
modifier = Modifier
.fillMaxWidth()
.height(maxHeight * 0.4f)
.verticalScroll(scrollState),
placeholder = {
Text(
stringResource(id = R.string.body_text_optional),
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground.light(),
)
},
colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
focusedContainerColor = Color.Transparent,
unfocusedContainerColor = Color.Transparent,
),
)
}
HorizontalDivider()
OutlinedTextField(
value = header,
onValueChange = { header = it },
modifier = Modifier.fillMaxWidth(),
maxLines = 3,
label = {
Text(stringResource(R.string.subject))
},
textStyle = MaterialTheme.typography.bodyLarge.copy(fontWeight = FontWeight.Medium),
isError = header.text.isBlank(),
supportingText = if (header.text.isBlank()) {
{ Text(stringResource(R.string.required)) }
} else {
null
},
)
OutlinedTextField(
value = body,
onValueChange = { body = it },
modifier = Modifier
.fillMaxWidth()
.height(200.dp),
label = {
Text(stringResource(R.string.body_text_optional))
},
textStyle = MaterialTheme.typography.bodyLarge,
)
Spacer(modifier = Modifier.height(8.dp))
}
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
) {
AmberButton(
enabled = header.text.isNotBlank(),
text = stringResource(R.string.send),
@@ -174,38 +164,3 @@ fun FeedbackScreen(
}
}
}
// taken from https://github.com/dluvian/voyage
@Composable
fun NamedRadio(
isSelected: Boolean,
name: String,
onClick: () -> Unit,
isEnabled: Boolean = true,
) {
NamedItem(
modifier = Modifier.clickable(onClick = onClick),
name = name,
item = {
RadioButton(
selected = isSelected,
onClick = onClick,
enabled = isEnabled,
)
},
)
}
// taken from https://github.com/dluvian/voyage
@Composable
fun NamedItem(
name: String,
item: @Composable () -> Unit,
modifier: Modifier = Modifier,
color: Color = Color.Unspecified,
) {
Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) {
item()
Text(text = name, color = color, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
+2
View File
@@ -561,6 +561,8 @@
<string name="event_kind_444">Welcome</string>
<string name="event_kind_445">Group</string>
<string name="give_us_feedback">Give us feedback</string>
<string name="feedback_type">Feedback type</string>
<string name="required">Required</string>
<string name="bug_report">Bug report</string>
<string name="enhancement_request">Enhancement request</string>
<string name="subject">Subject</string>