diff --git a/app/src/main/java/chat/revolt/api/routes/server/Server.kt b/app/src/main/java/chat/revolt/api/routes/server/Server.kt index 0938a454..5c4fa396 100644 --- a/app/src/main/java/chat/revolt/api/routes/server/Server.kt +++ b/app/src/main/java/chat/revolt/api/routes/server/Server.kt @@ -6,6 +6,7 @@ import chat.revolt.api.RevoltHttp import chat.revolt.api.RevoltJson import chat.revolt.api.schemas.Member import chat.revolt.api.schemas.User +import io.ktor.client.request.delete import io.ktor.client.request.get import io.ktor.client.request.parameter import io.ktor.client.request.put @@ -86,4 +87,12 @@ suspend fun fetchMember(serverId: String, userId: String, pure: Boolean = false) } return member +} + +suspend fun leaveOrDeleteServer(serverId: String, leaveSilently: Boolean = false) { + RevoltHttp.delete("/servers/$serverId") { + headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken) + + parameter("leave_silently", leaveSilently) + } } \ No newline at end of file diff --git a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt index f800a308..270ab4f3 100644 --- a/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt +++ b/app/src/main/java/chat/revolt/screens/chat/ChatRouterScreen.kt @@ -79,6 +79,7 @@ import chat.revolt.screens.chat.views.HomeScreen import chat.revolt.screens.chat.views.NoCurrentChannelScreen import chat.revolt.screens.chat.views.channel.ChannelScreen import chat.revolt.sheets.AddServerSheet +import chat.revolt.sheets.ServerContextSheet import chat.revolt.sheets.StatusSheet import chat.revolt.sheets.UserContextSheet import com.airbnb.lottie.RenderMode @@ -392,9 +393,13 @@ fun ChatRouterScreen( showServerContextSheet = false }, ) { - Column { - Text(text = "this is server context sheet for $serverContextSheetTarget") - } + ServerContextSheet( + serverId = serverContextSheetTarget, + onHideSheet = { + serverContextSheetState.hide() + showServerContextSheet = false + }, + ) } } diff --git a/app/src/main/java/chat/revolt/sheets/ServerContextSheet.kt b/app/src/main/java/chat/revolt/sheets/ServerContextSheet.kt new file mode 100644 index 00000000..75e91e6b --- /dev/null +++ b/app/src/main/java/chat/revolt/sheets/ServerContextSheet.kt @@ -0,0 +1,294 @@ +package chat.revolt.sheets + +import android.widget.Toast +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Checkbox +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +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.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.AnnotatedString +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.revolt.R +import chat.revolt.api.REVOLT_FILES +import chat.revolt.api.RevoltAPI +import chat.revolt.api.routes.server.leaveOrDeleteServer +import chat.revolt.components.generic.IconPlaceholder +import chat.revolt.components.generic.RemoteImage +import chat.revolt.components.generic.SheetClickable +import chat.revolt.components.generic.UIMarkdown +import kotlinx.coroutines.launch + +@Composable +fun ServerContextSheet( + serverId: String, + onHideSheet: suspend () -> Unit +) { + val server = RevoltAPI.serverCache[serverId] + + if (server == null) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(200.dp) + ) { + CircularProgressIndicator(modifier = Modifier.align(Alignment.Center)) + } + return + } + + val coroutineScope = rememberCoroutineScope() + val clipboardManager = LocalClipboardManager.current + val context = LocalContext.current + + var showLeaveConfirmation by remember { mutableStateOf(false) } + var leaveSilently by remember { mutableStateOf(false) } + + if (showLeaveConfirmation) { + AlertDialog(onDismissRequest = { + showLeaveConfirmation = false + }, + title = { + Text( + text = stringResource( + id = R.string.server_context_sheet_actions_leave_confirm, + server.name ?: stringResource(R.string.unknown) + ) + ) + }, + text = { + Column { + Text(text = stringResource(id = R.string.server_context_sheet_actions_leave_confirm_eyebrow)) + Row( + Modifier + .fillMaxWidth() + .padding(start = 0.dp, end = 0.dp, top = 16.dp, bottom = 0.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = leaveSilently, + onCheckedChange = { leaveSilently = it }, + ) + Text( + text = stringResource(id = R.string.server_context_sheet_actions_leave_silently), + modifier = Modifier.padding(start = 4.dp) + ) + } + } + }, + confirmButton = { + Button( + onClick = { + coroutineScope.launch { + onHideSheet() + } + coroutineScope.launch { + leaveOrDeleteServer(serverId, leaveSilently) + } + } + ) { + Text(text = stringResource(id = R.string.server_context_sheet_actions_leave_confirm_yes)) + } + }, + dismissButton = { + TextButton( + onClick = { + showLeaveConfirmation = false + } + ) { + Text(text = stringResource(id = R.string.server_context_sheet_actions_leave_confirm_no)) + } + } + ) + } + + Column( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 0.dp, bottom = 16.dp) + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large), + contentAlignment = Alignment.BottomStart + ) { + server.banner?.let { + Box( + modifier = Modifier + .background(Color.Black.copy(alpha = 0.25f)) + .height(166.dp) + .fillMaxWidth() + ) + + RemoteImage( + url = "$REVOLT_FILES/banners/${it.id}", + description = null, + modifier = Modifier + .height(166.dp) + .fillMaxWidth(), + contentScale = ContentScale.FillWidth + ) + + Box( + modifier = Modifier + .background( + Brush.verticalGradient( + listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.7f) + ) + ) + ) + .height(166.dp) + .fillMaxWidth() + ) + } + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(16.dp) + ) { + server.icon?.let { + RemoteImage( + url = "$REVOLT_FILES/icons/${it.id}/server.png?max_side=256", + description = null, + modifier = Modifier + .clip(CircleShape) + .height(48.dp) + .width(48.dp), + contentScale = ContentScale.Crop + ) + } ?: run { + IconPlaceholder( + name = server.name ?: stringResource(R.string.unknown), + modifier = Modifier + .clip(CircleShape) + .height(48.dp) + .width(48.dp) + ) + } + + Spacer(modifier = Modifier.width(12.dp)) + + Text( + text = server.name ?: stringResource(R.string.unknown), + style = MaterialTheme.typography.labelLarge, + fontSize = 16.sp + ) + } + } + + Column( + modifier = Modifier.padding(horizontal = 4.dp) + ) { + Text( + text = stringResource(id = R.string.server_context_sheet_category_description), + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(vertical = 14.dp) + ) + + UIMarkdown( + text = if (server.description?.isBlank() == false) server.description else stringResource( + R.string.server_context_sheet_description_empty + ), + ) + + Text( + text = stringResource(id = R.string.server_context_sheet_category_actions), + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.padding(top = 14.dp, bottom = 10.dp) + ) + } + + SheetClickable(icon = { + Icon( + painter = painterResource(id = R.drawable.ic_content_copy_id_24dp), + contentDescription = null, + modifier = it + ) + }, label = { + Text( + text = stringResource(id = R.string.server_context_sheet_actions_copy_id), + style = it + ) + }) { + if (server.id == null) return@SheetClickable + + clipboardManager.setText(AnnotatedString(server.id)) + Toast.makeText( + context, + context.getString(R.string.server_context_sheet_actions_copy_id_copied), + Toast.LENGTH_SHORT + ).show() + + coroutineScope.launch { + onHideSheet() + } + } + + SheetClickable(icon = { + Icon( + painter = painterResource(id = R.drawable.ic_eye_check_24dp), + contentDescription = null, + modifier = it + ) + }, label = { + Text( + text = stringResource(id = R.string.server_context_sheet_actions_mark_read), + style = it + ) + }) { + coroutineScope.launch { + server.id?.let { + RevoltAPI.unreads.markServerAsRead(it, sync = true) + } + onHideSheet() + } + } + + if (server.owner != RevoltAPI.selfId) { + SheetClickable(icon = { + Icon( + painter = painterResource(id = R.drawable.ic_arrow_left_bold_box_24dp), + contentDescription = null, + modifier = it + ) + }, label = { + Text( + text = stringResource(id = R.string.server_context_sheet_actions_leave), + style = it + ) + }, dangerous = true) { + showLeaveConfirmation = true + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_left_bold_box_24dp.xml b/app/src/main/res/drawable/ic_arrow_left_bold_box_24dp.xml new file mode 100644 index 00000000..bbaee11d --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left_bold_box_24dp.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 67efa1c7..8c5a23cc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -202,9 +202,19 @@ Copied channel ID to clipboard Mark as read + Description + There hasn\'t been a description set for this server yet. + Actions + Copy ID Copied server ID to clipboard Mark as read + Leave + Leave %1$s? + Until you get invited back, you won\'t be able to rejoin. + Leave + Stay + Leave Silently Bio This user hasn\'t set a bio yet.