feat: remove members from group DMs
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
8b276aade3
commit
a5b240f5fa
|
|
@ -6,3 +6,5 @@
|
||||||
/other.xml
|
/other.xml
|
||||||
# GitHub Copilot persisted chat sessions
|
# GitHub Copilot persisted chat sessions
|
||||||
/copilot/chatSessions
|
/copilot/chatSessions
|
||||||
|
# User-specific files
|
||||||
|
/deploymentTargetSelector.xml
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="deploymentTargetSelector">
|
|
||||||
<selectionStates>
|
|
||||||
<SelectionState runConfigName="app">
|
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
|
||||||
</SelectionState>
|
|
||||||
</selectionStates>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
|
|
@ -5,11 +5,14 @@ import chat.revolt.api.RevoltHttp
|
||||||
import chat.revolt.api.RevoltJson
|
import chat.revolt.api.RevoltJson
|
||||||
import chat.revolt.api.schemas.Channel
|
import chat.revolt.api.schemas.Channel
|
||||||
import chat.revolt.screens.create.MAX_ADDABLE_PEOPLE_IN_GROUP
|
import chat.revolt.screens.create.MAX_ADDABLE_PEOPLE_IN_GROUP
|
||||||
|
import io.ktor.client.request.delete
|
||||||
import io.ktor.client.request.post
|
import io.ktor.client.request.post
|
||||||
|
import io.ktor.client.request.put
|
||||||
import io.ktor.client.request.setBody
|
import io.ktor.client.request.setBody
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import io.ktor.http.ContentType
|
import io.ktor.http.ContentType
|
||||||
import io.ktor.http.contentType
|
import io.ktor.http.contentType
|
||||||
|
import io.ktor.http.isSuccess
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
|
|
||||||
|
|
@ -38,3 +41,19 @@ suspend fun createGroupDM(name: String, members: List<String>): Channel {
|
||||||
|
|
||||||
return RevoltJson.decodeFromString(Channel.serializer(), response)
|
return RevoltJson.decodeFromString(Channel.serializer(), response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun removeMember(channelId: String, userId: String) {
|
||||||
|
val response = RevoltHttp.delete("/channels/$channelId/recipients/$userId")
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw Error(response.status.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun addMember(channelId: String, userId: String) {
|
||||||
|
val response = RevoltHttp.put("/channels/$channelId/recipients/$userId")
|
||||||
|
|
||||||
|
if (!response.status.isSuccess()) {
|
||||||
|
throw Error(response.status.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
package chat.revolt.sheets
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ListItem
|
||||||
|
import androidx.compose.material3.LocalContentColor
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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.text.style.TextOverflow
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.api.routes.channel.removeMember
|
||||||
|
import chat.revolt.internals.Platform
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ColumnScope.GroupDMMemberContextSheet(
|
||||||
|
userId: String,
|
||||||
|
channelId: String,
|
||||||
|
dismissSheet: suspend () -> Unit,
|
||||||
|
onRequestUpdateMembers: suspend () -> Unit
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val channel = RevoltAPI.channelCache[channelId]
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(channel) {
|
||||||
|
if (channel == null) {
|
||||||
|
dismissSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel == null) return
|
||||||
|
|
||||||
|
if (channel.owner == RevoltAPI.selfId && userId != RevoltAPI.selfId) {
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
CompositionLocalProvider(value = LocalContentColor provides MaterialTheme.colorScheme.error) {
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.member_context_sheet_remove_from_channel,
|
||||||
|
channel.name ?: stringResource(R.string.unknown)
|
||||||
|
),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
CompositionLocalProvider(value = LocalContentColor provides MaterialTheme.colorScheme.error) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_account_cancel_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
scope.launch {
|
||||||
|
removeMember(channelId, userId)
|
||||||
|
onRequestUpdateMembers()
|
||||||
|
dismissSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO replace with something useful (currently so that your sheet is not empty if you don't have permissions)
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.user_info_sheet_copy_id))
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_content_copy_id_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
clipboardManager.setText(AnnotatedString(userId))
|
||||||
|
|
||||||
|
if (Platform.needsShowClipboardNotification()) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.copied),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ColumnScope.ServerMemberContextSheet(
|
||||||
|
userId: String,
|
||||||
|
serverId: String,
|
||||||
|
channelId: String,
|
||||||
|
dismissSheet: suspend () -> Unit,
|
||||||
|
onRequestUpdateMembers: suspend () -> Unit
|
||||||
|
) {
|
||||||
|
val server = RevoltAPI.serverCache[serverId]
|
||||||
|
val channel = RevoltAPI.channelCache[channelId]
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
LaunchedEffect(server) {
|
||||||
|
if (server == null || channel == null) {
|
||||||
|
dismissSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (server == null || channel == null) return
|
||||||
|
|
||||||
|
// TODO add something useful (moderation actions)
|
||||||
|
|
||||||
|
// TODO replace with something useful (currently so that your sheet is not empty if you don't have permissions)
|
||||||
|
ListItem(
|
||||||
|
headlineContent = {
|
||||||
|
Text(stringResource(R.string.user_info_sheet_copy_id))
|
||||||
|
},
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.ic_content_copy_id_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
clipboardManager.setText(AnnotatedString(userId))
|
||||||
|
|
||||||
|
if (Platform.needsShowClipboardNotification()) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.copied),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
|
@ -209,8 +209,10 @@ fun MemberListSheet(
|
||||||
serverId: String? = null,
|
serverId: String? = null,
|
||||||
viewModel: MemberListSheetViewModel = hiltViewModel()
|
viewModel: MemberListSheetViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
var showUserContextSheet by remember { mutableStateOf(false) }
|
var showUserInfoSheet by remember { mutableStateOf(false) }
|
||||||
var userContextSheetTarget by remember { mutableStateOf("") }
|
var userInfoSheetTarget by remember { mutableStateOf("") }
|
||||||
|
var showMemberContextSheet by remember { mutableStateOf(false) }
|
||||||
|
var memberContextSheetTarget by remember { mutableStateOf("") }
|
||||||
|
|
||||||
// We use LaunchedEffect to make sure that this is called every time any of the users status changes
|
// We use LaunchedEffect to make sure that this is called every time any of the users status changes
|
||||||
LaunchedEffect(RevoltAPI.userCache) {
|
LaunchedEffect(RevoltAPI.userCache) {
|
||||||
|
|
@ -223,26 +225,64 @@ fun MemberListSheet(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showUserContextSheet) {
|
if (showUserInfoSheet) {
|
||||||
val userContextSheetState = rememberModalBottomSheetState()
|
val userContextSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
sheetState = userContextSheetState,
|
sheetState = userContextSheetState,
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showUserContextSheet = false
|
showUserInfoSheet = false
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
UserInfoSheet(
|
UserInfoSheet(
|
||||||
userId = userContextSheetTarget,
|
userId = userInfoSheetTarget,
|
||||||
serverId = serverId,
|
serverId = serverId,
|
||||||
dismissSheet = {
|
dismissSheet = {
|
||||||
userContextSheetState.hide()
|
userContextSheetState.hide()
|
||||||
showUserContextSheet = false
|
showUserInfoSheet = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showMemberContextSheet) {
|
||||||
|
val memberContextSheetState = rememberModalBottomSheetState()
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
sheetState = memberContextSheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
showMemberContextSheet = false
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
if (serverId != null) {
|
||||||
|
ServerMemberContextSheet(
|
||||||
|
userId = memberContextSheetTarget,
|
||||||
|
serverId = serverId,
|
||||||
|
channelId = channelId,
|
||||||
|
onRequestUpdateMembers = {
|
||||||
|
viewModel.fetchServerMemberList(serverId, channelId)
|
||||||
|
},
|
||||||
|
dismissSheet = {
|
||||||
|
memberContextSheetState.hide()
|
||||||
|
showMemberContextSheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
GroupDMMemberContextSheet(
|
||||||
|
userId = memberContextSheetTarget,
|
||||||
|
channelId = channelId,
|
||||||
|
onRequestUpdateMembers = {
|
||||||
|
viewModel.fetchGroupMemberList(channelId)
|
||||||
|
},
|
||||||
|
dismissSheet = {
|
||||||
|
memberContextSheetState.hide()
|
||||||
|
showMemberContextSheet = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (viewModel.fullItemList.isEmpty()) {
|
if (viewModel.fullItemList.isEmpty()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
@ -281,10 +321,19 @@ fun MemberListSheet(
|
||||||
member = item.member,
|
member = item.member,
|
||||||
serverId = serverId,
|
serverId = serverId,
|
||||||
userId = item.member.id.user,
|
userId = item.member.id.user,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier
|
||||||
userContextSheetTarget = item.member.id.user
|
.combinedClickable(
|
||||||
showUserContextSheet = true
|
onClick = {
|
||||||
}
|
userInfoSheetTarget = item.member.id.user
|
||||||
|
showUserInfoSheet = true
|
||||||
|
},
|
||||||
|
onClickLabel = stringResource(R.string.user_info_sheet_open),
|
||||||
|
onLongClick = {
|
||||||
|
memberContextSheetTarget = item.member.id.user
|
||||||
|
showMemberContextSheet = true
|
||||||
|
},
|
||||||
|
onLongClickLabel = stringResource(R.string.member_context_sheet_open)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,10 +343,18 @@ fun MemberListSheet(
|
||||||
member = null,
|
member = null,
|
||||||
serverId = serverId,
|
serverId = serverId,
|
||||||
userId = item.user.id,
|
userId = item.user.id,
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.combinedClickable(
|
||||||
userContextSheetTarget = item.user.id
|
onClick = {
|
||||||
showUserContextSheet = true
|
userInfoSheetTarget = item.user.id
|
||||||
}
|
showUserInfoSheet = true
|
||||||
|
},
|
||||||
|
onClickLabel = stringResource(R.string.user_info_sheet_open),
|
||||||
|
onLongClick = {
|
||||||
|
memberContextSheetTarget = item.user.id
|
||||||
|
showMemberContextSheet = true
|
||||||
|
},
|
||||||
|
onLongClickLabel = stringResource(R.string.member_context_sheet_open)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -304,6 +304,7 @@
|
||||||
<string name="server_context_sheet_actions_leave_silently">Leave Silently</string>
|
<string name="server_context_sheet_actions_leave_silently">Leave Silently</string>
|
||||||
<string name="server_context_sheet_actions_report">Report</string>
|
<string name="server_context_sheet_actions_report">Report</string>
|
||||||
|
|
||||||
|
<string name="user_info_sheet_open">Open user info</string>
|
||||||
<string name="user_info_sheet_user_not_found">Can\'t resolve this user</string>
|
<string name="user_info_sheet_user_not_found">Can\'t resolve this user</string>
|
||||||
<string name="user_info_sheet_user_not_found_description">This user may have been deleted or you may not have permission to view them.</string>
|
<string name="user_info_sheet_user_not_found_description">This user may have been deleted or you may not have permission to view them.</string>
|
||||||
<string name="user_info_sheet_category_bio">Bio</string>
|
<string name="user_info_sheet_category_bio">Bio</string>
|
||||||
|
|
@ -330,6 +331,9 @@
|
||||||
<string name="user_info_sheet_user_is_bot">This is a bot.</string>
|
<string name="user_info_sheet_user_is_bot">This is a bot.</string>
|
||||||
<string name="user_info_sheet_user_is_bot_easter_egg">This is a bot. It has a plan.</string>
|
<string name="user_info_sheet_user_is_bot_easter_egg">This is a bot. It has a plan.</string>
|
||||||
|
|
||||||
|
<string name="member_context_sheet_open">Open member options</string>
|
||||||
|
<string name="member_context_sheet_remove_from_channel">Remove from %1$s</string>
|
||||||
|
|
||||||
<string name="user_badge_developer">Developer</string>
|
<string name="user_badge_developer">Developer</string>
|
||||||
<string name="user_badge_translator">Translator</string>
|
<string name="user_badge_translator">Translator</string>
|
||||||
<string name="user_badge_supporter">Supporter</string>
|
<string name="user_badge_supporter">Supporter</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue