for-android/app/src/main/java/chat/peptide/sheets/StatusSheet.kt

274 lines
9.6 KiB
Kotlin

package chat.peptide.sheets
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.input.TextFieldLineLimits
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import chat.peptide.R
import chat.peptide.api.PeptideAPI
import chat.peptide.api.routes.user.patchSelf
import chat.peptide.api.schemas.User
import chat.peptide.composables.generic.PepTextButton
import chat.peptide.composables.generic.SheetButton
import chat.peptide.composables.generic.asApiName
import chat.peptide.composables.generic.presenceFromStatus
import chat.peptide.composables.screens.settings.UserOverview
import chat.peptide.composables.settings.profile.StatusPicker
import kotlinx.coroutines.launch
@Composable
fun StatusTextEditDialog(
selfUser: User,
initialStatus: String,
onDismiss: () -> Unit
) {
val fieldState = rememberTextFieldState(initialStatus)
var errorText by remember { mutableStateOf<String?>(null) }
var isConfirmEnabled by remember { mutableStateOf(false) }
val context = LocalContext.current
val scope = rememberCoroutineScope()
LaunchedEffect(fieldState.text) {
errorText = null
isConfirmEnabled = fieldState.text.length <= 128
}
AlertDialog(
onDismissRequest = onDismiss,
title = {
Text(
text = stringResource(id = R.string.status_text)
)
},
text = {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Text(
text = stringResource(id = R.string.status_text_explainer)
)
TextField(
state = fieldState,
placeholder = {
Text(
text = stringResource(id = R.string.status_text_placeholder),
style = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
)
)
},
isError = errorText != null,
supportingText = {
errorText?.let {
Text(
text = it,
color = MaterialTheme.colorScheme.error
)
}
},
lineLimits = TextFieldLineLimits.SingleLine
)
}
},
confirmButton = {
PepTextButton(
enabled = isConfirmEnabled,
onClick = {
errorText = null
if (fieldState.text.length > 128) {
errorText = context.getString(R.string.status_text_error_too_long, 128)
} else {
if (fieldState.text == initialStatus) {
onDismiss()
return@PepTextButton
} else if (fieldState.text.isBlank()) {
if (selfUser.status?.text == null) {
onDismiss()
return@PepTextButton
}
scope.launch {
try {
patchSelf(remove = listOf("StatusText"))
onDismiss()
} catch (e: Exception) {
Log.e("StatusTextEditDialog", "Failed to remove status text", e)
errorText = context.getString(R.string.status_text_error_other)
}
}
} else {
scope.launch {
try {
patchSelf(
status = selfUser.status?.copy(
text = fieldState.text.toString().trim()
)
)
onDismiss()
} catch (e: Exception) {
Log.e("StatusTextEditDialog", "Failed to update status text", e)
errorText = context.getString(R.string.status_text_error_other)
}
}
}
}
}
) {
Text(
text = stringResource(id = R.string.ok)
)
}
},
dismissButton = {
PepTextButton(
onClick = onDismiss
) {
Text(
text = stringResource(id = R.string.cancel)
)
}
}
)
}
@Composable
@Preview
fun StatusTextEditDialogPreview() {
StatusTextEditDialog(
selfUser = User.getPlaceholder("01G00000000000000000000000"),
initialStatus = "This is a test status",
onDismiss = {}
)
}
@Composable
fun StatusSheet(onBeforeNavigation: () -> Unit, onGoSettings: () -> Unit) {
val selfUser = PeptideAPI.userCache[PeptideAPI.selfId]!!
val scope = rememberCoroutineScope()
var showStatusEditDialog by remember { mutableStateOf(false) }
if (showStatusEditDialog) {
StatusTextEditDialog(
selfUser = selfUser,
initialStatus = selfUser.status?.text ?: "",
onDismiss = { showStatusEditDialog = false }
)
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(horizontal = 16.dp, vertical = 8.dp)
.verticalScroll(rememberScrollState())
) {
UserOverview(selfUser, internalPadding = false)
Spacer(modifier = Modifier.height(16.dp))
StatusPicker(
currentStatus = presenceFromStatus(selfUser.status?.presence, selfUser.online ?: false),
onStatusChange = {
onBeforeNavigation()
scope.launch {
patchSelf(status = selfUser.status?.copy(presence = it.asApiName()))
}
}
)
Spacer(modifier = Modifier.height(16.dp))
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.padding(vertical = 8.dp)
.clip(MaterialTheme.shapes.medium)
.clickable {
showStatusEditDialog = true
}
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
) {
Text(
text = selfUser.status?.text ?: stringResource(id = R.string.status_text_none),
style = MaterialTheme.typography.bodyMedium.copy(
color = if (selfUser.status?.text == null) {
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
} else {
MaterialTheme.colorScheme.onSurface
}
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.weight(1f)
.padding(start = 16.dp)
)
Icon(
painter = painterResource(R.drawable.icn_edit_24dp),
contentDescription = null,
modifier = Modifier.padding(16.dp)
)
}
}
SheetButton(
leadingContent = {
Icon(
painter = painterResource(R.drawable.icn_settings_24dp),
contentDescription = null
)
},
headlineContent = {
Text(
text = stringResource(id = R.string.settings)
)
},
onClick = {
onBeforeNavigation()
onGoSettings()
}
)
}
@Preview
@Composable
fun StatusSheetPreview() {
StatusSheet(
onBeforeNavigation = {},
onGoSettings = {}
)
}