feat: set status text

Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
Infi 2024-10-31 21:55:22 +01:00
parent c0af196da7
commit 828f7bc420
4 changed files with 190 additions and 3 deletions

View File

@ -37,6 +37,9 @@
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
<option name="previewFile" value="true" />

View File

@ -476,7 +476,7 @@ fun ChatRouterScreen(
}
if (showStatusSheet) {
val statusSheetState = rememberModalBottomSheetState()
val statusSheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
ModalBottomSheet(
sheetState = statusSheetState,

View File

@ -1,24 +1,45 @@
package chat.revolt.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.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
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.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import chat.revolt.R
import chat.revolt.api.RevoltAPI
import chat.revolt.api.routes.user.patchSelf
import chat.revolt.api.schemas.User
import chat.revolt.components.generic.SheetButton
import chat.revolt.components.generic.asApiName
import chat.revolt.components.generic.presenceFromStatus
@ -26,18 +47,141 @@ import chat.revolt.components.screens.settings.UserOverview
import chat.revolt.components.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 = {
TextButton(
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@TextButton
} else if (fieldState.text.isBlank()) {
if (selfUser.status?.text == null) {
onDismiss()
return@TextButton
}
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 = {
TextButton(
onClick = onDismiss
) {
Text(
text = stringResource(id = R.string.cancel)
)
}
}
)
}
@Composable
fun StatusSheet(onBeforeNavigation: () -> Unit, onGoSettings: () -> Unit) {
val selfUser = RevoltAPI.userCache[RevoltAPI.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)
UserOverview(selfUser, internalPadding = false)
Spacer(modifier = Modifier.height(16.dp))
@ -51,7 +195,41 @@ fun StatusSheet(onBeforeNavigation: () -> Unit, onGoSettings: () -> Unit) {
}
)
Spacer(modifier = Modifier.height(8.dp))
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(
imageVector = Icons.Default.Edit,
contentDescription = null,
modifier = Modifier.padding(16.dp)
)
}
}
SheetButton(

View File

@ -250,6 +250,12 @@
<string name="status_offline">Offline</string>
<string name="status_invisible">Invisible</string>
<string name="status_invisible_explainer">You will be able to use Revolt as usual, but you will not appear as online to other people.</string>
<string name="status_text_none">What\'s up?</string>
<string name="status_text">Status text</string>
<string name="status_text_explainer">Your status text will be visible to everyone. Keep it short and sweet.</string>
<string name="status_text_placeholder">Tell the world what you\'re up to</string>
<string name="status_text_error_too_long">Status text is too long. Try to keep it under %1$d characters.</string>
<string name="status_text_error_other">An error occurred while updating your status text. Please try again later.</string>
<string name="no_connection">No connection</string>
<string name="no_connection_message">You are not connected to the internet. Please check your connection and try again.</string>