parent
ac5148e82c
commit
55e8d184f4
|
|
@ -0,0 +1,36 @@
|
|||
package chat.revolt.api.internals
|
||||
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.schemas.User
|
||||
|
||||
object FriendRequests {
|
||||
fun getIncoming(): List<User> {
|
||||
return RevoltAPI.userCache.values.filter { user ->
|
||||
user.relationship == "Incoming"
|
||||
}
|
||||
}
|
||||
|
||||
fun getIncomingCount(): Int {
|
||||
return getIncoming().size
|
||||
}
|
||||
|
||||
fun getOutgoing(): List<User> {
|
||||
return RevoltAPI.userCache.values.filter { user ->
|
||||
user.relationship == "Outgoing"
|
||||
}
|
||||
}
|
||||
|
||||
fun getOutgoingCount(): Int {
|
||||
return getOutgoing().size
|
||||
}
|
||||
|
||||
fun getBlocked(): List<User> {
|
||||
return RevoltAPI.userCache.values.filter { user ->
|
||||
user.relationship == "Blocked"
|
||||
}
|
||||
}
|
||||
|
||||
fun getBlockedCount(): Int {
|
||||
return getBlocked().size
|
||||
}
|
||||
}
|
||||
|
|
@ -23,6 +23,7 @@ import chat.revolt.api.realtime.frames.receivable.ServerMemberJoinFrame
|
|||
import chat.revolt.api.realtime.frames.receivable.ServerMemberLeaveFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.ServerMemberUpdateFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.ServerUpdateFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.UserRelationshipFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.UserUpdateFrame
|
||||
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame
|
||||
import chat.revolt.api.realtime.frames.sendable.PingFrame
|
||||
|
|
@ -262,6 +263,27 @@ object RealtimeSocket {
|
|||
existing.mergeWithPartial(userUpdateFrame.data)
|
||||
}
|
||||
|
||||
"UserRelationship" -> {
|
||||
val userRelationshipFrame =
|
||||
RevoltJson.decodeFromString(UserRelationshipFrame.serializer(), rawFrame)
|
||||
|
||||
val existing = RevoltAPI.userCache[userRelationshipFrame.user.id]
|
||||
|
||||
if (existing == null && userRelationshipFrame.user.id != null) {
|
||||
RevoltAPI.userCache[userRelationshipFrame.user.id] =
|
||||
userRelationshipFrame.user.copy(
|
||||
relationship = userRelationshipFrame.status
|
||||
)
|
||||
} else if (existing != null && userRelationshipFrame.user.id != null) {
|
||||
val merged = existing.mergeWithPartial(userRelationshipFrame.user).copy(
|
||||
relationship = userRelationshipFrame.status
|
||||
)
|
||||
RevoltAPI.userCache[userRelationshipFrame.user.id] = merged
|
||||
} else {
|
||||
Log.w("RealtimeSocket", "Invalid UserRelationship frame: $rawFrame")
|
||||
}
|
||||
}
|
||||
|
||||
"ChannelUpdate" -> {
|
||||
val channelUpdateFrame =
|
||||
RevoltJson.decodeFromString(ChannelUpdateFrame.serializer(), rawFrame)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import chat.revolt.api.RevoltJson
|
|||
import io.ktor.client.request.delete
|
||||
import io.ktor.client.request.put
|
||||
import io.ktor.client.statement.bodyAsText
|
||||
import kotlin.collections.set
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlin.collections.set
|
||||
|
||||
suspend fun blockUser(userId: String) {
|
||||
val response = RevoltHttp.put("/users/$userId/block")
|
||||
|
|
@ -39,3 +39,18 @@ suspend fun unblockUser(userId: String) {
|
|||
val user = RevoltAPI.userCache[userId] ?: return
|
||||
RevoltAPI.userCache[userId] = user.copy(relationship = "None")
|
||||
}
|
||||
|
||||
suspend fun unfriendUser(userId: String) {
|
||||
val response = RevoltHttp.delete("/users/$userId/friend")
|
||||
.bodyAsText()
|
||||
|
||||
try {
|
||||
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||
throw Error(error.type)
|
||||
} catch (e: SerializationException) {
|
||||
// Not an error
|
||||
}
|
||||
|
||||
val user = RevoltAPI.userCache[userId] ?: return
|
||||
RevoltAPI.userCache[userId] = user.copy(relationship = "None")
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ fun PageHeader(
|
|||
modifier: Modifier = Modifier,
|
||||
showBackButton: Boolean = false,
|
||||
onBackButtonClicked: () -> Unit = {},
|
||||
startButtons: @Composable () -> Unit = {},
|
||||
additionalButtons: @Composable () -> Unit = {},
|
||||
maxLines: Int = Int.MAX_VALUE
|
||||
) {
|
||||
|
|
@ -42,6 +43,7 @@ fun PageHeader(
|
|||
)
|
||||
}
|
||||
}
|
||||
startButtons()
|
||||
Text(
|
||||
text = text,
|
||||
maxLines = maxLines,
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ import chat.revolt.api.schemas.User
|
|||
import chat.revolt.api.schemas.has
|
||||
import chat.revolt.components.generic.presenceFromStatus
|
||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannelIconType
|
||||
import chat.revolt.sheets.ChannelContextSheet
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
|
|
@ -90,7 +91,7 @@ fun RowScope.ChannelList(
|
|||
val enableSmallBanner by remember {
|
||||
derivedStateOf {
|
||||
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
||||
lazyListState.firstVisibleItemIndex > 0
|
||||
lazyListState.firstVisibleItemIndex > 0
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +187,7 @@ fun RowScope.ChannelList(
|
|||
) {
|
||||
DrawerChannel(
|
||||
name = stringResource(R.string.home),
|
||||
channelType = ChannelType.TextChannel,
|
||||
iconType = DrawerChannelIconType.Painter(painterResource(R.drawable.ic_home_24dp)),
|
||||
selected = currentDestination == "home",
|
||||
hasUnread = false,
|
||||
onClick = {
|
||||
|
|
@ -196,6 +197,21 @@ fun RowScope.ChannelList(
|
|||
)
|
||||
}
|
||||
|
||||
item(
|
||||
key = "friends"
|
||||
) {
|
||||
DrawerChannel(
|
||||
name = stringResource(R.string.friends),
|
||||
iconType = DrawerChannelIconType.Painter(painterResource(R.drawable.ic_human_greeting_variant_24dp)),
|
||||
selected = currentDestination == "friends",
|
||||
hasUnread = false,
|
||||
onClick = {
|
||||
onSpecialClick("friends")
|
||||
},
|
||||
large = true
|
||||
)
|
||||
}
|
||||
|
||||
item(
|
||||
key = "notes"
|
||||
) {
|
||||
|
|
@ -204,7 +220,7 @@ fun RowScope.ChannelList(
|
|||
|
||||
DrawerChannel(
|
||||
name = stringResource(R.string.channel_notes),
|
||||
channelType = ChannelType.SavedMessages,
|
||||
iconType = DrawerChannelIconType.Channel(ChannelType.SavedMessages),
|
||||
selected = currentDestination == "channel/{channelId}" && currentChannel == notesChannelId,
|
||||
hasUnread = false,
|
||||
onClick = {
|
||||
|
|
@ -248,8 +264,10 @@ fun RowScope.ChannelList(
|
|||
|
||||
DrawerChannel(
|
||||
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
||||
?: stringResource(R.string.unknown),
|
||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
||||
?: stringResource(R.string.unknown),
|
||||
iconType = DrawerChannelIconType.Channel(
|
||||
channel.channelType ?: ChannelType.TextChannel
|
||||
),
|
||||
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||
RevoltAPI.unreads.hasUnread(
|
||||
|
|
@ -408,9 +426,9 @@ fun RowScope.ChannelList(
|
|||
|
||||
Text(
|
||||
text = (
|
||||
server?.name
|
||||
?: stringResource(R.string.unknown)
|
||||
),
|
||||
server?.name
|
||||
?: stringResource(R.string.unknown)
|
||||
),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = if (server?.banner != null) {
|
||||
bannerTextColour
|
||||
|
|
@ -513,7 +531,9 @@ fun RowScope.ChannelList(
|
|||
name = partner?.let { p -> User.resolveDefaultName(p) }
|
||||
?: channel.name
|
||||
?: stringResource(R.string.unknown),
|
||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
||||
iconType = DrawerChannelIconType.Channel(
|
||||
channel.channelType ?: ChannelType.TextChannel
|
||||
),
|
||||
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||
RevoltAPI.unreads.hasUnread(
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.offset
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -33,10 +34,16 @@ import chat.revolt.components.generic.Presence
|
|||
import chat.revolt.components.generic.UserAvatar
|
||||
import chat.revolt.components.screens.chat.ChannelIcon
|
||||
|
||||
sealed class DrawerChannelIconType {
|
||||
data class Channel(val type: ChannelType) : DrawerChannelIconType()
|
||||
data class Painter(val painter: androidx.compose.ui.graphics.painter.Painter) :
|
||||
DrawerChannelIconType()
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun DrawerChannel(
|
||||
channelType: ChannelType,
|
||||
iconType: DrawerChannelIconType,
|
||||
name: String,
|
||||
selected: Boolean,
|
||||
hasUnread: Boolean,
|
||||
|
|
@ -84,39 +91,55 @@ fun DrawerChannel(
|
|||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
when (channelType) {
|
||||
ChannelType.DirectMessage -> UserAvatar(
|
||||
username = dmPartnerName ?: "",
|
||||
avatar = dmPartnerIcon,
|
||||
userId = dmPartnerId ?: "",
|
||||
presence = dmPartnerStatus,
|
||||
size = 32.dp,
|
||||
presenceSize = 16.dp,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
when (iconType) {
|
||||
is DrawerChannelIconType.Channel -> {
|
||||
when (val channelType = iconType.type) {
|
||||
ChannelType.DirectMessage -> UserAvatar(
|
||||
username = dmPartnerName ?: "",
|
||||
avatar = dmPartnerIcon,
|
||||
userId = dmPartnerId ?: "",
|
||||
presence = dmPartnerStatus,
|
||||
size = 32.dp,
|
||||
presenceSize = 16.dp,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
|
||||
ChannelType.Group -> GroupIcon(
|
||||
name = name,
|
||||
icon = dmPartnerIcon,
|
||||
size = 32.dp,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
ChannelType.Group -> GroupIcon(
|
||||
name = name,
|
||||
icon = dmPartnerIcon,
|
||||
size = 32.dp,
|
||||
modifier = Modifier.padding(end = 8.dp)
|
||||
)
|
||||
|
||||
else -> ChannelIcon(
|
||||
channelType = channelType,
|
||||
modifier = Modifier.then(
|
||||
if (large) {
|
||||
Modifier.padding(
|
||||
end = 12.dp,
|
||||
start = 4.dp,
|
||||
top = 4.dp,
|
||||
bottom = 4.dp
|
||||
else -> ChannelIcon(
|
||||
channelType = channelType,
|
||||
modifier = Modifier.then(
|
||||
if (large) {
|
||||
Modifier.padding(
|
||||
end = 12.dp,
|
||||
start = 4.dp,
|
||||
top = 4.dp,
|
||||
bottom = 4.dp
|
||||
)
|
||||
} else {
|
||||
Modifier.padding(end = 8.dp)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Modifier.padding(end = 8.dp)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
is DrawerChannelIconType.Painter -> {
|
||||
Icon(
|
||||
painter = iconType.painter,
|
||||
contentDescription = null,
|
||||
tint = LocalContentColor.current,
|
||||
modifier = Modifier
|
||||
.padding(end = 8.dp)
|
||||
.size(32.dp)
|
||||
.padding(4.dp)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ import chat.revolt.internals.Changelogs
|
|||
import chat.revolt.ndk.Pipebomb
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
||||
import chat.revolt.screens.chat.views.FriendsScreen
|
||||
import chat.revolt.screens.chat.views.HomeScreen
|
||||
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
||||
import chat.revolt.screens.chat.views.channel.ChannelScreen
|
||||
|
|
@ -112,9 +113,9 @@ import com.airbnb.lottie.compose.rememberLottieComposition
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import io.sentry.Sentry
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
|
|
@ -304,7 +305,7 @@ fun ChatRouterScreen(
|
|||
|
||||
var useTabletAwareUI by remember { mutableStateOf(false) }
|
||||
|
||||
val drawerBackHandler = remember {
|
||||
val toggleDrawerLda = remember {
|
||||
{
|
||||
scope.launch {
|
||||
if (drawerState.isOpen) {
|
||||
|
|
@ -376,7 +377,7 @@ fun ChatRouterScreen(
|
|||
.distinctUntilChanged()
|
||||
.collect { sizeClass ->
|
||||
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
|
||||
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
||||
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -680,8 +681,8 @@ fun ChatRouterScreen(
|
|||
navController = navController,
|
||||
topNav = topNav,
|
||||
useDrawer = false,
|
||||
drawerBackHandler = {
|
||||
drawerBackHandler()
|
||||
toggleDrawer = {
|
||||
toggleDrawerLda()
|
||||
},
|
||||
onShowUserContextSheet = { target, server ->
|
||||
userContextSheetTarget = target
|
||||
|
|
@ -720,8 +721,8 @@ fun ChatRouterScreen(
|
|||
navController = navController,
|
||||
topNav = topNav,
|
||||
useDrawer = true,
|
||||
drawerBackHandler = {
|
||||
drawerBackHandler()
|
||||
toggleDrawer = {
|
||||
toggleDrawerLda()
|
||||
},
|
||||
drawerState = drawerState,
|
||||
onShowUserContextSheet = { target, server ->
|
||||
|
|
@ -857,21 +858,21 @@ fun Sidebar(
|
|||
// - Add the servers that aren't in the ordering to the end of the list.
|
||||
// - Sort the servers that aren't in the ordering by their ID (creation order).
|
||||
(
|
||||
(
|
||||
RevoltAPI.serverCache.values.filter {
|
||||
SyncedSettings.ordering.servers.contains(
|
||||
it.id
|
||||
)
|
||||
}
|
||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
||||
) + (
|
||||
RevoltAPI.serverCache.values.filter {
|
||||
!SyncedSettings.ordering.servers.contains(
|
||||
it.id
|
||||
)
|
||||
}.sortedBy { it.id }
|
||||
(
|
||||
RevoltAPI.serverCache.values.filter {
|
||||
SyncedSettings.ordering.servers.contains(
|
||||
it.id
|
||||
)
|
||||
}
|
||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
||||
) + (
|
||||
RevoltAPI.serverCache.values.filter {
|
||||
!SyncedSettings.ordering.servers.contains(
|
||||
it.id
|
||||
)
|
||||
}.sortedBy { it.id }
|
||||
)
|
||||
)
|
||||
)
|
||||
.forEach { server ->
|
||||
if (server.id == null || server.name == null) return@forEach
|
||||
|
||||
|
|
@ -935,7 +936,7 @@ fun ChannelNavigator(
|
|||
navController: NavHostController,
|
||||
topNav: NavController,
|
||||
useDrawer: Boolean,
|
||||
drawerBackHandler: () -> Unit,
|
||||
toggleDrawer: () -> Unit,
|
||||
drawerState: DrawerState? = null,
|
||||
onShowUserContextSheet: (String, String?) -> Unit
|
||||
) {
|
||||
|
|
@ -945,14 +946,28 @@ fun ChannelNavigator(
|
|||
NavHost(navController = navController, startDestination = "home") {
|
||||
composable("home") {
|
||||
BackHandler(enabled = useDrawer) {
|
||||
drawerBackHandler()
|
||||
toggleDrawer()
|
||||
}
|
||||
HomeScreen(navController = topNav)
|
||||
HomeScreen(
|
||||
navController = topNav,
|
||||
useDrawer = useDrawer,
|
||||
onDrawerClicked = toggleDrawer,
|
||||
)
|
||||
}
|
||||
|
||||
composable("friends") {
|
||||
BackHandler(enabled = useDrawer) {
|
||||
toggleDrawer()
|
||||
}
|
||||
FriendsScreen(
|
||||
useDrawer = useDrawer,
|
||||
onDrawerClicked = toggleDrawer,
|
||||
)
|
||||
}
|
||||
|
||||
composable("channel/{channelId}") { backStackEntry ->
|
||||
BackHandler(enabled = useDrawer) {
|
||||
drawerBackHandler()
|
||||
toggleDrawer()
|
||||
}
|
||||
|
||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||
|
|
@ -979,7 +994,7 @@ fun ChannelNavigator(
|
|||
|
||||
composable("no_current_channel") {
|
||||
BackHandler(enabled = useDrawer) {
|
||||
drawerBackHandler()
|
||||
toggleDrawer()
|
||||
}
|
||||
|
||||
NoCurrentChannelScreen()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,276 @@
|
|||
package chat.revolt.screens.chat.views
|
||||
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
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.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.MoreVert
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
import androidx.compose.material3.LocalTextStyle
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
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.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.internals.FriendRequests
|
||||
import chat.revolt.api.routes.user.unfriendUser
|
||||
import chat.revolt.api.schemas.User
|
||||
import chat.revolt.callbacks.Action
|
||||
import chat.revolt.callbacks.ActionChannel
|
||||
import chat.revolt.components.generic.PageHeader
|
||||
import chat.revolt.components.generic.SheetClickable
|
||||
import chat.revolt.components.generic.UserAvatar
|
||||
import chat.revolt.components.generic.presenceFromStatus
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun FriendsOptionsSheet(onDenyAll: () -> Unit) {
|
||||
SheetClickable(
|
||||
icon = { modifier ->
|
||||
Icon(
|
||||
modifier = modifier,
|
||||
painter = painterResource(R.drawable.ic_account_cancel_24dp),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
label = { style ->
|
||||
Text(
|
||||
text = stringResource(R.string.friends_deny_all_incoming),
|
||||
style = style
|
||||
)
|
||||
},
|
||||
onClick = { onDenyAll() }
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun FriendsScreen(useDrawer: Boolean, onDrawerClicked: () -> Unit) {
|
||||
var optionsSheetShown by remember { mutableStateOf(false) }
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
if (optionsSheetShown) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
|
||||
ModalBottomSheet(
|
||||
onDismissRequest = {
|
||||
optionsSheetShown = false
|
||||
},
|
||||
sheetState = sheetState
|
||||
) {
|
||||
FriendsOptionsSheet(
|
||||
onDenyAll = {
|
||||
scope.launch {
|
||||
sheetState.hide()
|
||||
}
|
||||
with(Dispatchers.IO) {
|
||||
scope.launch {
|
||||
FriendRequests.getIncoming()
|
||||
.forEach { it.id?.let { id -> unfriendUser(id) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
PageHeader(
|
||||
text = "Friends",
|
||||
startButtons = {
|
||||
if (useDrawer) {
|
||||
IconButton(onClick = onDrawerClicked) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(R.string.menu)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
additionalButtons = {
|
||||
IconButton(onClick = {
|
||||
optionsSheetShown = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = stringResource(R.string.menu)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
LazyColumn {
|
||||
stickyHeader(key = "incoming") {
|
||||
Text(
|
||||
text = AnnotatedString.Builder().apply {
|
||||
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
|
||||
append(stringResource(id = R.string.friends_incoming_requests))
|
||||
pop()
|
||||
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = LocalTextStyle.current.fontSize * 0.8,
|
||||
color = LocalContentColor.current.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
append("—${FriendRequests.getIncoming().size}")
|
||||
pop()
|
||||
}.toAnnotatedString(),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
items(FriendRequests.getIncoming().size) {
|
||||
val item = FriendRequests.getIncoming()[it]
|
||||
UserItem(item, onClick = {
|
||||
scope.launch {
|
||||
item.id?.let { userId ->
|
||||
ActionChannel.send(Action.OpenUserSheet(userId, null))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
stickyHeader(key = "outgoing") {
|
||||
Text(
|
||||
text = AnnotatedString.Builder().apply {
|
||||
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
|
||||
append(stringResource(id = R.string.friends_outgoing_requests))
|
||||
pop()
|
||||
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = LocalTextStyle.current.fontSize * 0.8,
|
||||
color = LocalContentColor.current.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
append("—${FriendRequests.getOutgoing().size}")
|
||||
pop()
|
||||
}.toAnnotatedString(),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
items(FriendRequests.getOutgoing().size) {
|
||||
val item = FriendRequests.getOutgoing()[it]
|
||||
UserItem(item, onClick = {
|
||||
scope.launch {
|
||||
item.id?.let { userId ->
|
||||
ActionChannel.send(Action.OpenUserSheet(userId, null))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
stickyHeader(key = "blocked") {
|
||||
Text(
|
||||
text = AnnotatedString.Builder().apply {
|
||||
pushStyle(SpanStyle(fontWeight = FontWeight.Bold))
|
||||
append(stringResource(id = R.string.friends_blocked))
|
||||
pop()
|
||||
|
||||
pushStyle(
|
||||
SpanStyle(
|
||||
fontWeight = FontWeight.Medium,
|
||||
fontSize = LocalTextStyle.current.fontSize * 0.8,
|
||||
color = LocalContentColor.current.copy(alpha = 0.6f)
|
||||
)
|
||||
)
|
||||
append("—${FriendRequests.getBlocked().size}")
|
||||
pop()
|
||||
}.toAnnotatedString(),
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.padding(10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
items(FriendRequests.getBlocked().size) {
|
||||
val item = FriendRequests.getBlocked()[it]
|
||||
UserItem(item, onClick = {
|
||||
scope.launch {
|
||||
item.id?.let { userId ->
|
||||
ActionChannel.send(Action.OpenUserSheet(userId, null))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun UserItem(user: User, onClick: () -> Unit = {}) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
onClick()
|
||||
}
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
UserAvatar(
|
||||
username = user.displayName
|
||||
?: user.username
|
||||
?: user.id!!,
|
||||
avatar = user.avatar,
|
||||
userId = user.id!!,
|
||||
presence = presenceFromStatus(
|
||||
user.status?.presence,
|
||||
user.online ?: false
|
||||
)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
Text(
|
||||
text = user.displayName
|
||||
?: user.username
|
||||
?: user.id,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -14,8 +14,10 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -33,7 +35,7 @@ import chat.revolt.components.generic.PageHeader
|
|||
import chat.revolt.components.screens.home.LinkOnHome
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(navController: NavController) {
|
||||
fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked: () -> Unit) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val catTransition = rememberInfiniteTransition(label = "cat")
|
||||
|
|
@ -50,7 +52,19 @@ fun HomeScreen(navController: NavController) {
|
|||
Column(
|
||||
modifier = Modifier.safeDrawingPadding()
|
||||
) {
|
||||
PageHeader(text = stringResource(id = R.string.home))
|
||||
PageHeader(
|
||||
text = stringResource(id = R.string.home),
|
||||
startButtons = {
|
||||
if (useDrawer) {
|
||||
IconButton(onClick = onDrawerClicked) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(R.string.menu)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z" />
|
||||
</vector>
|
||||
|
|
@ -123,6 +123,12 @@
|
|||
<string name="home_join_jenvolt">Join Jenvolt</string>
|
||||
<string name="home_join_jenvolt_description">Jenvolt is the developer-run space for all things Android app and more. Support, feedback go here. Maybe you will get to try out new features! 👀</string>
|
||||
|
||||
<string name="friends">Friends</string>
|
||||
<string name="friends_incoming_requests">Incoming Requests</string>
|
||||
<string name="friends_outgoing_requests">Outgoing Requests</string>
|
||||
<string name="friends_blocked">Blocked</string>
|
||||
<string name="friends_deny_all_incoming">Clear all incoming requests</string>
|
||||
|
||||
<string name="server_plus_alt">Add server</string>
|
||||
|
||||
<string name="no_channels_heading">Bit awkward.</string>
|
||||
|
|
|
|||
Loading…
Reference in New Issue