feat: revamp chat UI to be closer to web, add double drawer
This commit is contained in:
parent
738a2a832f
commit
e5ab9b3228
|
|
@ -70,6 +70,7 @@ dependencies {
|
||||||
// Jetpack Compose
|
// Jetpack Compose
|
||||||
implementation "androidx.compose.ui:ui"
|
implementation "androidx.compose.ui:ui"
|
||||||
implementation "androidx.compose.ui:ui-util"
|
implementation "androidx.compose.ui:ui-util"
|
||||||
|
implementation 'androidx.compose.material:material'
|
||||||
implementation 'androidx.compose.material3:material3'
|
implementation 'androidx.compose.material3:material3'
|
||||||
implementation "androidx.compose.ui:ui-tooling-preview"
|
implementation "androidx.compose.ui:ui-tooling-preview"
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package chat.revolt.components.generic
|
package chat.revolt.components.generic
|
||||||
|
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
|
@ -26,7 +28,17 @@ enum class Presence {
|
||||||
Idle,
|
Idle,
|
||||||
Dnd,
|
Dnd,
|
||||||
Focus,
|
Focus,
|
||||||
Offline,
|
Offline
|
||||||
|
}
|
||||||
|
|
||||||
|
fun presenceFromStatus(status: String): Presence {
|
||||||
|
return when (status) {
|
||||||
|
"online" -> Presence.Online
|
||||||
|
"idle" -> Presence.Idle
|
||||||
|
"dnd" -> Presence.Dnd
|
||||||
|
"focus" -> Presence.Focus
|
||||||
|
else -> Presence.Offline
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun presenceColour(presence: Presence): Color {
|
fun presenceColour(presence: Presence): Color {
|
||||||
|
|
@ -50,6 +62,7 @@ fun PresenceBadge(presence: Presence, size: Dp = 16.dp) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatar(
|
fun UserAvatar(
|
||||||
username: String,
|
username: String,
|
||||||
|
|
@ -60,6 +73,8 @@ fun UserAvatar(
|
||||||
rawUrl: String? = null,
|
rawUrl: String? = null,
|
||||||
size: Dp = 40.dp,
|
size: Dp = 40.dp,
|
||||||
presenceSize: Dp = 16.dp,
|
presenceSize: Dp = 16.dp,
|
||||||
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
|
|
@ -69,19 +84,35 @@ fun UserAvatar(
|
||||||
if (avatar != null) {
|
if (avatar != null) {
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
url = rawUrl ?: "$REVOLT_FILES/avatars/${avatar.id!!}/user.png",
|
url = rawUrl ?: "$REVOLT_FILES/avatars/${avatar.id!!}/user.png",
|
||||||
description = stringResource(id = R.string.avatar_alt, username),
|
|
||||||
contentScale = ContentScale.Crop,
|
contentScale = ContentScale.Crop,
|
||||||
|
description = stringResource(id = R.string.avatar_alt, username),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(size)
|
.size(size)
|
||||||
|
.then(
|
||||||
|
if (onLongClick != null || onClick != null) Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { onClick?.invoke() },
|
||||||
|
onLongClick = { onLongClick?.invoke() }
|
||||||
|
)
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
RemoteImage(
|
RemoteImage(
|
||||||
url = "$REVOLT_BASE/users/${userId}/default_avatar",
|
url = "$REVOLT_BASE/users/${userId}/default_avatar",
|
||||||
|
description = stringResource(id = R.string.avatar_alt, username),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(size)
|
.size(size)
|
||||||
|
.then(
|
||||||
|
if (onLongClick != null || onClick != null) Modifier
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { onClick?.invoke() },
|
||||||
|
onLongClick = { onLongClick?.invoke() }
|
||||||
|
)
|
||||||
|
else Modifier
|
||||||
|
)
|
||||||
.clip(CircleShape),
|
.clip(CircleShape),
|
||||||
description = stringResource(id = R.string.avatar_alt, username),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
package chat.revolt.components.screens.chat
|
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.expandVertically
|
|
||||||
import androidx.compose.animation.shrinkVertically
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Home
|
|
||||||
import androidx.compose.material.icons.filled.Settings
|
|
||||||
import androidx.compose.material3.BottomAppBar
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.navigation.NavController
|
|
||||||
import chat.revolt.R
|
|
||||||
import chat.revolt.RevoltTweenIntSize
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BottomNavigation(
|
|
||||||
navController: NavController,
|
|
||||||
show: Boolean,
|
|
||||||
) {
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = show,
|
|
||||||
enter = expandVertically(
|
|
||||||
animationSpec = RevoltTweenIntSize
|
|
||||||
),
|
|
||||||
exit = shrinkVertically(
|
|
||||||
animationSpec = RevoltTweenIntSize
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
BottomAppBar(
|
|
||||||
containerColor = MaterialTheme.colorScheme.background,
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
if (navController.currentDestination?.route != "chat") {
|
|
||||||
navController.navigate("chat")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Home,
|
|
||||||
contentDescription = stringResource(id = R.string.home),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
IconButton(
|
|
||||||
modifier = Modifier.weight(1f),
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
if (navController.currentDestination?.route != "settings") {
|
|
||||||
navController.navigate("settings")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Settings,
|
|
||||||
contentDescription = stringResource(id = R.string.settings),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,185 @@
|
||||||
|
package chat.revolt.components.screens.chat
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
|
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.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
enum class DoubleDrawerOpenState {
|
||||||
|
Start,
|
||||||
|
Center,
|
||||||
|
End
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
class DoubleDrawerState(
|
||||||
|
var initialValue: DoubleDrawerOpenState = DoubleDrawerOpenState.Center,
|
||||||
|
val confirmStateChange: (DoubleDrawerOpenState) -> Boolean = { true }
|
||||||
|
) {
|
||||||
|
val swipeableState = SwipeableState<DoubleDrawerOpenState>(
|
||||||
|
initialValue = initialValue,
|
||||||
|
animationSpec = spring(),
|
||||||
|
confirmStateChange = confirmStateChange
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun focusStart() {
|
||||||
|
swipeableState.animateTo(DoubleDrawerOpenState.Start)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun focusCenter() {
|
||||||
|
swipeableState.animateTo(DoubleDrawerOpenState.Center)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun focusEnd() {
|
||||||
|
swipeableState.animateTo(DoubleDrawerOpenState.End)
|
||||||
|
}
|
||||||
|
|
||||||
|
val isStart: Boolean
|
||||||
|
get() = swipeableState.currentValue == DoubleDrawerOpenState.Start
|
||||||
|
val isCenter: Boolean
|
||||||
|
get() = swipeableState.currentValue == DoubleDrawerOpenState.Center
|
||||||
|
val isEnd: Boolean
|
||||||
|
get() = swipeableState.currentValue == DoubleDrawerOpenState.End
|
||||||
|
|
||||||
|
val currentValue: DoubleDrawerOpenState
|
||||||
|
get() = swipeableState.currentValue
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun Saver(
|
||||||
|
confirmStateChange: (DoubleDrawerOpenState) -> Boolean
|
||||||
|
): Saver<DoubleDrawerState, DoubleDrawerOpenState> = Saver(
|
||||||
|
save = { it.currentValue },
|
||||||
|
restore = { DoubleDrawerState(it, confirmStateChange) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun rememberDoubleDrawerState(
|
||||||
|
initialValue: DoubleDrawerOpenState = DoubleDrawerOpenState.Center,
|
||||||
|
confirmStateChange: (DoubleDrawerOpenState) -> Boolean = { true }
|
||||||
|
): DoubleDrawerState = rememberSaveable(
|
||||||
|
saver = DoubleDrawerState.Saver(confirmStateChange)
|
||||||
|
) {
|
||||||
|
DoubleDrawerState(initialValue, confirmStateChange)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun DoubleDrawer(
|
||||||
|
state: DoubleDrawerState = rememberDoubleDrawerState(),
|
||||||
|
startPanel: @Composable () -> Unit,
|
||||||
|
endPanel: @Composable () -> Unit,
|
||||||
|
content: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val layoutDirection = LocalLayoutDirection.current
|
||||||
|
|
||||||
|
BoxWithConstraints(Modifier.fillMaxSize()) {
|
||||||
|
val isPortrait =
|
||||||
|
LocalContext.current.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||||
|
val drawerWeight =
|
||||||
|
if (isPortrait) 0.9f else 0.8f
|
||||||
|
|
||||||
|
val offsetValue =
|
||||||
|
(constraints.maxWidth * drawerWeight) + (LocalDensity.current.run { 16.dp.toPx() })
|
||||||
|
val isAtOffset = abs(state.swipeableState.offset.value) == abs(offsetValue)
|
||||||
|
|
||||||
|
val contentCornerRadius by animateDpAsState(
|
||||||
|
targetValue = if (isAtOffset) 16.dp else 0.dp,
|
||||||
|
)
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.swipeable(
|
||||||
|
state = state.swipeableState,
|
||||||
|
orientation = Orientation.Horizontal,
|
||||||
|
velocityThreshold = 500.dp,
|
||||||
|
anchors = mapOf(
|
||||||
|
offsetValue to DoubleDrawerOpenState.Start,
|
||||||
|
0f to DoubleDrawerOpenState.Center,
|
||||||
|
-offsetValue to DoubleDrawerOpenState.End
|
||||||
|
),
|
||||||
|
reverseDirection = layoutDirection == LayoutDirection.Rtl,
|
||||||
|
resistance = ResistanceConfig(0.5f, 0.5f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.fillMaxWidth(drawerWeight)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
|
topEnd = 16.dp,
|
||||||
|
bottomEnd = 16.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.align(Alignment.CenterStart)
|
||||||
|
.offset {
|
||||||
|
IntOffset(
|
||||||
|
x = state.swipeableState.offset.value.roundToInt() - offsetValue.roundToInt(),
|
||||||
|
y = 0
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
startPanel()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(contentCornerRadius)
|
||||||
|
)
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.offset {
|
||||||
|
IntOffset(
|
||||||
|
x = state.swipeableState.offset.value.roundToInt(),
|
||||||
|
y = 0
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.fillMaxWidth(drawerWeight)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
|
topStart = 16.dp,
|
||||||
|
bottomStart = 16.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.align(Alignment.CenterEnd)
|
||||||
|
.offset {
|
||||||
|
IntOffset(
|
||||||
|
x = state.swipeableState.offset.value.roundToInt() + offsetValue.roundToInt(),
|
||||||
|
y = 0
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
endPanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package chat.revolt.components.screens.chat
|
package chat.revolt.components.screens.chat.drawer.server
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -14,6 +14,7 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
|
import chat.revolt.components.screens.chat.ChannelIcon
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DrawerChannel(
|
fun DrawerChannel(
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package chat.revolt.components.screens.chat
|
package chat.revolt.components.screens.chat.drawer.server
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package chat.revolt.components.screens.chat
|
package chat.revolt.components.screens.chat.drawer.server
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package chat.revolt.components.screens.chat
|
package chat.revolt.components.screens.chat.drawer.server
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
|
|
@ -1,19 +1,24 @@
|
||||||
package chat.revolt.screens.chat
|
package chat.revolt.screens.chat
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Home
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -31,9 +36,17 @@ import chat.revolt.api.realtime.DisconnectionState
|
||||||
import chat.revolt.api.realtime.RealtimeSocket
|
import chat.revolt.api.realtime.RealtimeSocket
|
||||||
import chat.revolt.api.schemas.ChannelType
|
import chat.revolt.api.schemas.ChannelType
|
||||||
import chat.revolt.components.chat.DisconnectedNotice
|
import chat.revolt.components.chat.DisconnectedNotice
|
||||||
import chat.revolt.components.screens.chat.*
|
import chat.revolt.components.generic.UserAvatar
|
||||||
|
import chat.revolt.components.generic.presenceFromStatus
|
||||||
|
import chat.revolt.components.screens.chat.DoubleDrawer
|
||||||
|
import chat.revolt.components.screens.chat.drawer.server.DrawerChannel
|
||||||
|
import chat.revolt.components.screens.chat.drawer.server.DrawerServer
|
||||||
|
import chat.revolt.components.screens.chat.drawer.server.DrawerServerlikeIcon
|
||||||
|
import chat.revolt.components.screens.chat.drawer.server.ServerDrawerSeparator
|
||||||
|
import chat.revolt.components.screens.chat.rememberDoubleDrawerState
|
||||||
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
|
import chat.revolt.screens.chat.sheets.ChannelInfoSheet
|
||||||
import chat.revolt.screens.chat.sheets.MessageContextSheet
|
import chat.revolt.screens.chat.sheets.MessageContextSheet
|
||||||
|
import chat.revolt.screens.chat.sheets.StatusSheet
|
||||||
import chat.revolt.screens.chat.views.ChannelScreen
|
import chat.revolt.screens.chat.views.ChannelScreen
|
||||||
import chat.revolt.screens.chat.views.HomeScreen
|
import chat.revolt.screens.chat.views.HomeScreen
|
||||||
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
|
import com.google.accompanist.navigation.material.ExperimentalMaterialNavigationApi
|
||||||
|
|
@ -72,11 +85,12 @@ class ChatRouterViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialNavigationApi::class)
|
@OptIn(ExperimentalMaterialNavigationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
|
fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = viewModel()) {
|
||||||
val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
|
val drawerState = rememberDoubleDrawerState()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
val bottomSheetNavigator = rememberBottomSheetNavigator()
|
val bottomSheetNavigator = rememberBottomSheetNavigator()
|
||||||
val navController = rememberNavController(bottomSheetNavigator)
|
val navController = rememberNavController(bottomSheetNavigator)
|
||||||
|
|
@ -93,56 +107,80 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
DismissibleNavigationDrawer(
|
DoubleDrawer(
|
||||||
drawerState = channelDrawerState,
|
state = drawerState,
|
||||||
drawerContent = {
|
startPanel = {
|
||||||
ModalDrawerSheet(
|
Column(Modifier.fillMaxWidth()) {
|
||||||
drawerContainerColor = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)
|
Row {
|
||||||
) {
|
Column(
|
||||||
Column(Modifier.fillMaxWidth()) {
|
modifier = Modifier
|
||||||
Row {
|
.fillMaxHeight()
|
||||||
Column(
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
UserAvatar(
|
||||||
|
username = RevoltAPI.userCache[RevoltAPI.selfId]?.username
|
||||||
|
?: "",
|
||||||
|
presence = presenceFromStatus(
|
||||||
|
RevoltAPI.userCache[RevoltAPI.selfId]?.status?.presence
|
||||||
|
?: ""
|
||||||
|
),
|
||||||
|
userId = RevoltAPI.selfId ?: "",
|
||||||
|
avatar = RevoltAPI.userCache[RevoltAPI.selfId]?.avatar,
|
||||||
|
size = 48.dp,
|
||||||
|
presenceSize = 16.dp,
|
||||||
|
onClick = {
|
||||||
|
viewModel.navigateToServer("home", navController)
|
||||||
|
},
|
||||||
|
onLongClick = {
|
||||||
|
navController.navigate("status")
|
||||||
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxHeight()
|
.padding(8.dp)
|
||||||
.verticalScroll(rememberScrollState())
|
.size(48.dp)
|
||||||
.background(
|
)
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(
|
|
||||||
2.dp
|
ServerDrawerSeparator()
|
||||||
|
|
||||||
|
RevoltAPI.serverCache.values
|
||||||
|
.sortedBy { it.id }
|
||||||
|
.forEach { server ->
|
||||||
|
if (server.name == null) return@forEach
|
||||||
|
|
||||||
|
DrawerServer(
|
||||||
|
iconId = server.icon?.id,
|
||||||
|
serverName = server.name
|
||||||
|
) {
|
||||||
|
viewModel.navigateToServer(
|
||||||
|
server.id!!,
|
||||||
|
navController
|
||||||
)
|
)
|
||||||
)
|
|
||||||
) {
|
|
||||||
DrawerServerlikeIcon(
|
|
||||||
onClick = {
|
|
||||||
viewModel.navigateToServer("home", navController)
|
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
Icons.Default.Home,
|
|
||||||
contentDescription = stringResource(id = R.string.home),
|
|
||||||
modifier = Modifier.padding(4.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerDrawerSeparator()
|
DrawerServerlikeIcon(
|
||||||
|
onClick = {
|
||||||
RevoltAPI.serverCache.values
|
Toast.makeText(
|
||||||
.sortedBy { it.id }
|
context,
|
||||||
.forEach { server ->
|
context.getString(R.string.comingsoon_toast),
|
||||||
if (server.name == null) return@forEach
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
DrawerServer(
|
}
|
||||||
iconId = server.icon?.id,
|
) {
|
||||||
serverName = server.name
|
Icon(
|
||||||
) {
|
Icons.Default.Add,
|
||||||
viewModel.navigateToServer(
|
contentDescription = stringResource(id = R.string.server_plus_alt),
|
||||||
server.id!!,
|
modifier = Modifier.padding(4.dp)
|
||||||
navController
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Crossfade(targetState = viewModel.currentServer) {
|
Crossfade(targetState = viewModel.currentServer) {
|
||||||
|
Surface(
|
||||||
|
tonalElevation = 1.dp,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.clip(RoundedCornerShape(16.dp))
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
|
@ -165,7 +203,7 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
onClick = {
|
onClick = {
|
||||||
navController.navigate("channel/${channel.id}")
|
navController.navigate("channel/${channel.id}")
|
||||||
scope.launch {
|
scope.launch {
|
||||||
channelDrawerState.close()
|
drawerState.focusCenter()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
@ -196,13 +234,14 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
"channelId"
|
"channelId"
|
||||||
) == ch.id,
|
) == ch.id,
|
||||||
onClick = {
|
onClick = {
|
||||||
scope.launch { channelDrawerState.close() }
|
scope.launch { drawerState.focusCenter() }
|
||||||
navController.navigate("channel/${ch.id}") {
|
navController.navigate("channel/${ch.id}") {
|
||||||
popUpTo("home") {
|
popUpTo("home") {
|
||||||
inclusive = true
|
inclusive = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -213,7 +252,16 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.weight(1f),
|
endPanel = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(2.dp))
|
||||||
|
.fillMaxSize(),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(text = "👋", fontSize = 64.sp)
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxSize()) {
|
Column(Modifier.fillMaxSize()) {
|
||||||
NavHost(navController = navController, startDestination = "home") {
|
NavHost(navController = navController, startDestination = "home") {
|
||||||
|
|
@ -248,14 +296,12 @@ fun ChatRouterScreen(topNav: NavController, viewModel: ChatRouterViewModel = vie
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bottomSheet("status") {
|
||||||
|
StatusSheet(navController = navController, topNav = topNav)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
BottomNavigation(
|
|
||||||
navController = topNav,
|
|
||||||
show = channelDrawerState.currentValue == DrawerValue.Open,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
package chat.revolt.screens.chat.sheets
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
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.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import chat.revolt.R
|
||||||
|
import chat.revolt.api.RevoltAPI
|
||||||
|
import chat.revolt.components.generic.SheetClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StatusSheet(
|
||||||
|
navController: NavController,
|
||||||
|
topNav: NavController,
|
||||||
|
) {
|
||||||
|
if (RevoltAPI.selfId == null || RevoltAPI.userCache[RevoltAPI.selfId] == null) {
|
||||||
|
navController.popBackStack()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val selfUser = RevoltAPI.userCache[RevoltAPI.selfId]!!
|
||||||
|
|
||||||
|
Surface {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
Text(text = "Logged in as @${selfUser.username} (${selfUser.id})")
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
SheetClickable(
|
||||||
|
icon = { modifier ->
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Settings,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = modifier
|
||||||
|
)
|
||||||
|
},
|
||||||
|
label = { style ->
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = R.string.settings),
|
||||||
|
style = style
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
topNav.navigate("settings")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -86,6 +86,8 @@
|
||||||
<string name="home">Home</string>
|
<string name="home">Home</string>
|
||||||
<string name="logout">Log out</string>
|
<string name="logout">Log out</string>
|
||||||
|
|
||||||
|
<string name="server_plus_alt">Add server</string>
|
||||||
|
|
||||||
<string name="avatar_alt">%1$s\'s avatar</string>
|
<string name="avatar_alt">%1$s\'s avatar</string>
|
||||||
|
|
||||||
<string name="channel_dm">Direct Message</string>
|
<string name="channel_dm">Direct Message</string>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue