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.ServerMemberLeaveFrame
|
||||||
import chat.revolt.api.realtime.frames.receivable.ServerMemberUpdateFrame
|
import chat.revolt.api.realtime.frames.receivable.ServerMemberUpdateFrame
|
||||||
import chat.revolt.api.realtime.frames.receivable.ServerUpdateFrame
|
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.receivable.UserUpdateFrame
|
||||||
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame
|
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame
|
||||||
import chat.revolt.api.realtime.frames.sendable.PingFrame
|
import chat.revolt.api.realtime.frames.sendable.PingFrame
|
||||||
|
|
@ -262,6 +263,27 @@ object RealtimeSocket {
|
||||||
existing.mergeWithPartial(userUpdateFrame.data)
|
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" -> {
|
"ChannelUpdate" -> {
|
||||||
val channelUpdateFrame =
|
val channelUpdateFrame =
|
||||||
RevoltJson.decodeFromString(ChannelUpdateFrame.serializer(), rawFrame)
|
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.delete
|
||||||
import io.ktor.client.request.put
|
import io.ktor.client.request.put
|
||||||
import io.ktor.client.statement.bodyAsText
|
import io.ktor.client.statement.bodyAsText
|
||||||
import kotlin.collections.set
|
|
||||||
import kotlinx.serialization.SerializationException
|
import kotlinx.serialization.SerializationException
|
||||||
|
import kotlin.collections.set
|
||||||
|
|
||||||
suspend fun blockUser(userId: String) {
|
suspend fun blockUser(userId: String) {
|
||||||
val response = RevoltHttp.put("/users/$userId/block")
|
val response = RevoltHttp.put("/users/$userId/block")
|
||||||
|
|
@ -39,3 +39,18 @@ suspend fun unblockUser(userId: String) {
|
||||||
val user = RevoltAPI.userCache[userId] ?: return
|
val user = RevoltAPI.userCache[userId] ?: return
|
||||||
RevoltAPI.userCache[userId] = user.copy(relationship = "None")
|
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,
|
modifier: Modifier = Modifier,
|
||||||
showBackButton: Boolean = false,
|
showBackButton: Boolean = false,
|
||||||
onBackButtonClicked: () -> Unit = {},
|
onBackButtonClicked: () -> Unit = {},
|
||||||
|
startButtons: @Composable () -> Unit = {},
|
||||||
additionalButtons: @Composable () -> Unit = {},
|
additionalButtons: @Composable () -> Unit = {},
|
||||||
maxLines: Int = Int.MAX_VALUE
|
maxLines: Int = Int.MAX_VALUE
|
||||||
) {
|
) {
|
||||||
|
|
@ -42,6 +43,7 @@ fun PageHeader(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
startButtons()
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
maxLines = maxLines,
|
maxLines = maxLines,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ import chat.revolt.api.schemas.User
|
||||||
import chat.revolt.api.schemas.has
|
import chat.revolt.api.schemas.has
|
||||||
import chat.revolt.components.generic.presenceFromStatus
|
import chat.revolt.components.generic.presenceFromStatus
|
||||||
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
||||||
|
import chat.revolt.components.screens.chat.drawer.server.DrawerChannelIconType
|
||||||
import chat.revolt.sheets.ChannelContextSheet
|
import chat.revolt.sheets.ChannelContextSheet
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||||
|
|
@ -90,7 +91,7 @@ fun RowScope.ChannelList(
|
||||||
val enableSmallBanner by remember {
|
val enableSmallBanner by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
lazyListState.firstVisibleItemScrollOffset > 40 ||
|
||||||
lazyListState.firstVisibleItemIndex > 0
|
lazyListState.firstVisibleItemIndex > 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,7 +187,7 @@ fun RowScope.ChannelList(
|
||||||
) {
|
) {
|
||||||
DrawerChannel(
|
DrawerChannel(
|
||||||
name = stringResource(R.string.home),
|
name = stringResource(R.string.home),
|
||||||
channelType = ChannelType.TextChannel,
|
iconType = DrawerChannelIconType.Painter(painterResource(R.drawable.ic_home_24dp)),
|
||||||
selected = currentDestination == "home",
|
selected = currentDestination == "home",
|
||||||
hasUnread = false,
|
hasUnread = false,
|
||||||
onClick = {
|
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(
|
item(
|
||||||
key = "notes"
|
key = "notes"
|
||||||
) {
|
) {
|
||||||
|
|
@ -204,7 +220,7 @@ fun RowScope.ChannelList(
|
||||||
|
|
||||||
DrawerChannel(
|
DrawerChannel(
|
||||||
name = stringResource(R.string.channel_notes),
|
name = stringResource(R.string.channel_notes),
|
||||||
channelType = ChannelType.SavedMessages,
|
iconType = DrawerChannelIconType.Channel(ChannelType.SavedMessages),
|
||||||
selected = currentDestination == "channel/{channelId}" && currentChannel == notesChannelId,
|
selected = currentDestination == "channel/{channelId}" && currentChannel == notesChannelId,
|
||||||
hasUnread = false,
|
hasUnread = false,
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -248,8 +264,10 @@ fun RowScope.ChannelList(
|
||||||
|
|
||||||
DrawerChannel(
|
DrawerChannel(
|
||||||
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
name = partner?.let { p -> User.resolveDefaultName(p) } ?: channel.name
|
||||||
?: stringResource(R.string.unknown),
|
?: stringResource(R.string.unknown),
|
||||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
iconType = DrawerChannelIconType.Channel(
|
||||||
|
channel.channelType ?: ChannelType.TextChannel
|
||||||
|
),
|
||||||
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||||
RevoltAPI.unreads.hasUnread(
|
RevoltAPI.unreads.hasUnread(
|
||||||
|
|
@ -408,9 +426,9 @@ fun RowScope.ChannelList(
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = (
|
text = (
|
||||||
server?.name
|
server?.name
|
||||||
?: stringResource(R.string.unknown)
|
?: stringResource(R.string.unknown)
|
||||||
),
|
),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelLarge,
|
||||||
color = if (server?.banner != null) {
|
color = if (server?.banner != null) {
|
||||||
bannerTextColour
|
bannerTextColour
|
||||||
|
|
@ -513,7 +531,9 @@ fun RowScope.ChannelList(
|
||||||
name = partner?.let { p -> User.resolveDefaultName(p) }
|
name = partner?.let { p -> User.resolveDefaultName(p) }
|
||||||
?: channel.name
|
?: channel.name
|
||||||
?: stringResource(R.string.unknown),
|
?: stringResource(R.string.unknown),
|
||||||
channelType = channel.channelType ?: ChannelType.TextChannel,
|
iconType = DrawerChannelIconType.Channel(
|
||||||
|
channel.channelType ?: ChannelType.TextChannel
|
||||||
|
),
|
||||||
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
selected = currentDestination == "channel/{channelId}" && currentChannel == channel.id,
|
||||||
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
hasUnread = channel.lastMessageID?.let { lastMessageID ->
|
||||||
RevoltAPI.unreads.hasUnread(
|
RevoltAPI.unreads.hasUnread(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.LocalContentColor
|
import androidx.compose.material3.LocalContentColor
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
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.generic.UserAvatar
|
||||||
import chat.revolt.components.screens.chat.ChannelIcon
|
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)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawerChannel(
|
fun DrawerChannel(
|
||||||
channelType: ChannelType,
|
iconType: DrawerChannelIconType,
|
||||||
name: String,
|
name: String,
|
||||||
selected: Boolean,
|
selected: Boolean,
|
||||||
hasUnread: Boolean,
|
hasUnread: Boolean,
|
||||||
|
|
@ -84,39 +91,55 @@ fun DrawerChannel(
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
.padding(vertical = 8.dp, horizontal = 16.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
when (channelType) {
|
when (iconType) {
|
||||||
ChannelType.DirectMessage -> UserAvatar(
|
is DrawerChannelIconType.Channel -> {
|
||||||
username = dmPartnerName ?: "",
|
when (val channelType = iconType.type) {
|
||||||
avatar = dmPartnerIcon,
|
ChannelType.DirectMessage -> UserAvatar(
|
||||||
userId = dmPartnerId ?: "",
|
username = dmPartnerName ?: "",
|
||||||
presence = dmPartnerStatus,
|
avatar = dmPartnerIcon,
|
||||||
size = 32.dp,
|
userId = dmPartnerId ?: "",
|
||||||
presenceSize = 16.dp,
|
presence = dmPartnerStatus,
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
size = 32.dp,
|
||||||
)
|
presenceSize = 16.dp,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
ChannelType.Group -> GroupIcon(
|
ChannelType.Group -> GroupIcon(
|
||||||
name = name,
|
name = name,
|
||||||
icon = dmPartnerIcon,
|
icon = dmPartnerIcon,
|
||||||
size = 32.dp,
|
size = 32.dp,
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> ChannelIcon(
|
else -> ChannelIcon(
|
||||||
channelType = channelType,
|
channelType = channelType,
|
||||||
modifier = Modifier.then(
|
modifier = Modifier.then(
|
||||||
if (large) {
|
if (large) {
|
||||||
Modifier.padding(
|
Modifier.padding(
|
||||||
end = 12.dp,
|
end = 12.dp,
|
||||||
start = 4.dp,
|
start = 4.dp,
|
||||||
top = 4.dp,
|
top = 4.dp,
|
||||||
bottom = 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(
|
Text(
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ import chat.revolt.internals.Changelogs
|
||||||
import chat.revolt.ndk.Pipebomb
|
import chat.revolt.ndk.Pipebomb
|
||||||
import chat.revolt.persistence.KVStorage
|
import chat.revolt.persistence.KVStorage
|
||||||
import chat.revolt.screens.chat.dialogs.safety.ReportMessageDialog
|
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.HomeScreen
|
||||||
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
import chat.revolt.screens.chat.views.NoCurrentChannelScreen
|
||||||
import chat.revolt.screens.chat.views.channel.ChannelScreen
|
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.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.sentry.Sentry
|
import io.sentry.Sentry
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@SuppressLint("StaticFieldLeak")
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
|
@ -304,7 +305,7 @@ fun ChatRouterScreen(
|
||||||
|
|
||||||
var useTabletAwareUI by remember { mutableStateOf(false) }
|
var useTabletAwareUI by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
val drawerBackHandler = remember {
|
val toggleDrawerLda = remember {
|
||||||
{
|
{
|
||||||
scope.launch {
|
scope.launch {
|
||||||
if (drawerState.isOpen) {
|
if (drawerState.isOpen) {
|
||||||
|
|
@ -376,7 +377,7 @@ fun ChatRouterScreen(
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.collect { sizeClass ->
|
.collect { sizeClass ->
|
||||||
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
|
useTabletAwareUI = sizeClass.widthSizeClass == WindowWidthSizeClass.Expanded &&
|
||||||
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
sizeClass.heightSizeClass != WindowHeightSizeClass.Compact
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -680,8 +681,8 @@ fun ChatRouterScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
topNav = topNav,
|
topNav = topNav,
|
||||||
useDrawer = false,
|
useDrawer = false,
|
||||||
drawerBackHandler = {
|
toggleDrawer = {
|
||||||
drawerBackHandler()
|
toggleDrawerLda()
|
||||||
},
|
},
|
||||||
onShowUserContextSheet = { target, server ->
|
onShowUserContextSheet = { target, server ->
|
||||||
userContextSheetTarget = target
|
userContextSheetTarget = target
|
||||||
|
|
@ -720,8 +721,8 @@ fun ChatRouterScreen(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
topNav = topNav,
|
topNav = topNav,
|
||||||
useDrawer = true,
|
useDrawer = true,
|
||||||
drawerBackHandler = {
|
toggleDrawer = {
|
||||||
drawerBackHandler()
|
toggleDrawerLda()
|
||||||
},
|
},
|
||||||
drawerState = drawerState,
|
drawerState = drawerState,
|
||||||
onShowUserContextSheet = { target, server ->
|
onShowUserContextSheet = { target, server ->
|
||||||
|
|
@ -857,21 +858,21 @@ fun Sidebar(
|
||||||
// - Add the servers that aren't in the ordering to the end of the list.
|
// - 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).
|
// - Sort the servers that aren't in the ordering by their ID (creation order).
|
||||||
(
|
(
|
||||||
(
|
(
|
||||||
RevoltAPI.serverCache.values.filter {
|
RevoltAPI.serverCache.values.filter {
|
||||||
SyncedSettings.ordering.servers.contains(
|
SyncedSettings.ordering.servers.contains(
|
||||||
it.id
|
it.id
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
.sortedBy { SyncedSettings.ordering.servers.indexOf(it.id) }
|
||||||
) + (
|
) + (
|
||||||
RevoltAPI.serverCache.values.filter {
|
RevoltAPI.serverCache.values.filter {
|
||||||
!SyncedSettings.ordering.servers.contains(
|
!SyncedSettings.ordering.servers.contains(
|
||||||
it.id
|
it.id
|
||||||
)
|
)
|
||||||
}.sortedBy { it.id }
|
}.sortedBy { it.id }
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
.forEach { server ->
|
.forEach { server ->
|
||||||
if (server.id == null || server.name == null) return@forEach
|
if (server.id == null || server.name == null) return@forEach
|
||||||
|
|
||||||
|
|
@ -935,7 +936,7 @@ fun ChannelNavigator(
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
topNav: NavController,
|
topNav: NavController,
|
||||||
useDrawer: Boolean,
|
useDrawer: Boolean,
|
||||||
drawerBackHandler: () -> Unit,
|
toggleDrawer: () -> Unit,
|
||||||
drawerState: DrawerState? = null,
|
drawerState: DrawerState? = null,
|
||||||
onShowUserContextSheet: (String, String?) -> Unit
|
onShowUserContextSheet: (String, String?) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
@ -945,14 +946,28 @@ fun ChannelNavigator(
|
||||||
NavHost(navController = navController, startDestination = "home") {
|
NavHost(navController = navController, startDestination = "home") {
|
||||||
composable("home") {
|
composable("home") {
|
||||||
BackHandler(enabled = useDrawer) {
|
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 ->
|
composable("channel/{channelId}") { backStackEntry ->
|
||||||
BackHandler(enabled = useDrawer) {
|
BackHandler(enabled = useDrawer) {
|
||||||
drawerBackHandler()
|
toggleDrawer()
|
||||||
}
|
}
|
||||||
|
|
||||||
val channelId = backStackEntry.arguments?.getString("channelId")
|
val channelId = backStackEntry.arguments?.getString("channelId")
|
||||||
|
|
@ -979,7 +994,7 @@ fun ChannelNavigator(
|
||||||
|
|
||||||
composable("no_current_channel") {
|
composable("no_current_channel") {
|
||||||
BackHandler(enabled = useDrawer) {
|
BackHandler(enabled = useDrawer) {
|
||||||
drawerBackHandler()
|
toggleDrawer()
|
||||||
}
|
}
|
||||||
|
|
||||||
NoCurrentChannelScreen()
|
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.padding
|
||||||
import androidx.compose.foundation.layout.safeDrawingPadding
|
import androidx.compose.foundation.layout.safeDrawingPadding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
import androidx.compose.material.icons.filled.Star
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
|
@ -33,7 +35,7 @@ import chat.revolt.components.generic.PageHeader
|
||||||
import chat.revolt.components.screens.home.LinkOnHome
|
import chat.revolt.components.screens.home.LinkOnHome
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HomeScreen(navController: NavController) {
|
fun HomeScreen(navController: NavController, useDrawer: Boolean, onDrawerClicked: () -> Unit) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val catTransition = rememberInfiniteTransition(label = "cat")
|
val catTransition = rememberInfiniteTransition(label = "cat")
|
||||||
|
|
@ -50,7 +52,19 @@ fun HomeScreen(navController: NavController) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.safeDrawingPadding()
|
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(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.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">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="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="server_plus_alt">Add server</string>
|
||||||
|
|
||||||
<string name="no_channels_heading">Bit awkward.</string>
|
<string name="no_channels_heading">Bit awkward.</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue