parent
b92d5369c1
commit
872889fefd
|
|
@ -99,6 +99,12 @@ object RevoltAPI {
|
|||
Log.e("RevoltAPI", "WebSocket error", e)
|
||||
}
|
||||
RealtimeSocket.open = false
|
||||
|
||||
Log.i("RevoltAPI", "Reconnecting in 2.5 seconds...")
|
||||
Thread.sleep(2500)
|
||||
runBlocking {
|
||||
connectWS()
|
||||
} // FIXME ASAP move this to ChatRouting (whenever that's a thing) and do it properly
|
||||
}
|
||||
}
|
||||
socketThread!!.start()
|
||||
|
|
|
|||
|
|
@ -135,8 +135,12 @@ object RealtimeSocket {
|
|||
"UserUpdate" -> {
|
||||
val userUpdateFrame =
|
||||
RevoltJson.decodeFromString(UserUpdateFrame.serializer(), rawFrame)
|
||||
// We will genuinely just ignore this frame for now, but it gets really spammy in the logs
|
||||
// FIXME handle this frame
|
||||
|
||||
val existing = RevoltAPI.userCache[userUpdateFrame.id]
|
||||
?: return // if we don't have the user no point in updating it
|
||||
|
||||
RevoltAPI.userCache[userUpdateFrame.id] =
|
||||
existing.mergeWithPartial(userUpdateFrame.data)
|
||||
}
|
||||
else -> {
|
||||
Log.i("RealtimeSocket", "Unknown frame: $rawFrame")
|
||||
|
|
|
|||
|
|
@ -28,4 +28,34 @@ suspend fun fetchSelf(): User {
|
|||
RevoltAPI.selfId = user.id
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
suspend fun fetchUser(id: String): User {
|
||||
val response = RevoltHttp.get("/users/$id") {
|
||||
headers.append(RevoltAPI.TOKEN_HEADER_NAME, RevoltAPI.sessionToken)
|
||||
}
|
||||
.bodyAsText()
|
||||
|
||||
try {
|
||||
val error = RevoltJson.decodeFromString(RevoltError.serializer(), response)
|
||||
throw Error(error.type)
|
||||
} catch (e: SerializationException) {
|
||||
// Not an error
|
||||
}
|
||||
|
||||
val user = RevoltJson.decodeFromString(User.serializer(), response)
|
||||
|
||||
RevoltAPI.userCache[user.id!!] = user
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
suspend fun getOrFetchUser(id: String): User {
|
||||
return RevoltAPI.userCache[id] ?: fetchUser(id)
|
||||
}
|
||||
|
||||
suspend fun addUserIfUnknown(id: String) {
|
||||
if (RevoltAPI.userCache[id] == null) {
|
||||
RevoltAPI.userCache[id] = fetchUser(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -27,9 +27,10 @@ data class Member(
|
|||
|
||||
@Serializable
|
||||
data class Channel(
|
||||
val channelType: ChannelType? = null,
|
||||
@SerialName("_id")
|
||||
val id: String? = null,
|
||||
@SerialName("channel_type")
|
||||
val channelType: ChannelType? = null,
|
||||
val user: String? = null,
|
||||
val name: String? = null,
|
||||
val owner: String? = null,
|
||||
|
|
@ -46,7 +47,28 @@ data class Channel(
|
|||
val defaultPermissions: DefaultPermissions? = null,
|
||||
val nsfw: Boolean? = null,
|
||||
val type: String? = null, // this is _only_ used for websocket events!
|
||||
)
|
||||
) {
|
||||
fun mergeWithPartial(partial: Channel): Channel {
|
||||
return Channel(
|
||||
channelType = partial.channelType ?: channelType,
|
||||
id = partial.id ?: id,
|
||||
user = partial.user ?: user,
|
||||
name = partial.name ?: name,
|
||||
owner = partial.owner ?: owner,
|
||||
description = partial.description ?: description,
|
||||
recipients = partial.recipients ?: recipients,
|
||||
icon = partial.icon ?: icon,
|
||||
lastMessageID = partial.lastMessageID ?: lastMessageID,
|
||||
active = partial.active ?: active,
|
||||
permissions = partial.permissions ?: permissions,
|
||||
server = partial.server ?: server,
|
||||
rolePermissions = partial.rolePermissions ?: rolePermissions,
|
||||
defaultPermissions = partial.defaultPermissions ?: defaultPermissions,
|
||||
nsfw = partial.nsfw ?: nsfw,
|
||||
type = partial.type ?: type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
enum class ChannelType(val value: String) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,23 @@ data class Message(
|
|||
fun getAuthor(): User? {
|
||||
return author?.let { RevoltAPI.userCache[it] }
|
||||
}
|
||||
|
||||
fun mergeWithPartial(partial: Message): Message {
|
||||
return Message(
|
||||
id = partial.id ?: id,
|
||||
nonce = partial.nonce ?: nonce,
|
||||
channel = partial.channel ?: channel,
|
||||
author = partial.author ?: author,
|
||||
content = partial.content ?: content,
|
||||
reactions = partial.reactions ?: reactions,
|
||||
replies = partial.replies ?: replies,
|
||||
attachments = partial.attachments ?: attachments,
|
||||
edited = partial.edited ?: edited,
|
||||
embeds = partial.embeds ?: embeds,
|
||||
mentions = partial.mentions ?: mentions,
|
||||
type = partial.type ?: type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -20,7 +20,24 @@ data class User(
|
|||
val bot: Bot? = null,
|
||||
val relationship: String? = null,
|
||||
val online: Boolean? = null
|
||||
)
|
||||
) {
|
||||
fun mergeWithPartial(partial: User): User {
|
||||
return User(
|
||||
id = partial.id ?: id,
|
||||
username = partial.username ?: username,
|
||||
avatar = partial.avatar ?: avatar,
|
||||
relations = partial.relations ?: relations,
|
||||
badges = partial.badges ?: badges,
|
||||
status = partial.status ?: status,
|
||||
profile = partial.profile ?: profile,
|
||||
flags = partial.flags ?: flags,
|
||||
privileged = partial.privileged ?: privileged,
|
||||
bot = partial.bot ?: bot,
|
||||
relationship = partial.relationship ?: relationship,
|
||||
online = partial.online ?: online
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Bot(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,60 @@
|
|||
package chat.revolt.components.chat
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.api.REVOLT_BASE
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.api.schemas.Message as MessageSchema
|
||||
|
||||
@Composable
|
||||
fun Message(
|
||||
message: MessageSchema
|
||||
) {
|
||||
val author = RevoltAPI.userCache[message.author] ?: return CircularProgressIndicator()
|
||||
|
||||
Row() {
|
||||
if (author.avatar != null) {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_FILES/avatars/${author.avatar.id!!}/user.png",
|
||||
modifier = Modifier
|
||||
.size(50.dp)
|
||||
.clip(CircleShape),
|
||||
description = "Avatar for ${author.username}"
|
||||
)
|
||||
} else {
|
||||
RemoteImage(
|
||||
url = "$REVOLT_BASE/users/${author.id}/default_avatar",
|
||||
modifier = Modifier
|
||||
.size(50.dp)
|
||||
.clip(CircleShape),
|
||||
description = "Avatar for ${author.username}"
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
author.username?.let {
|
||||
Text(
|
||||
text = it,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
message.content?.let {
|
||||
Text(
|
||||
text = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package chat.revolt.components.chat
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import chat.revolt.R
|
||||
import chat.revolt.api.schemas.ChannelType
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MessageField(
|
||||
showButtons: Boolean,
|
||||
onToggleButtons: (Boolean) -> Unit,
|
||||
messageContent: String,
|
||||
onMessageContentChange: (String) -> Unit,
|
||||
onSendMessage: () -> Unit,
|
||||
channelType: ChannelType,
|
||||
channelName: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val placeholderResource = when (channelType) {
|
||||
ChannelType.DirectMessage -> R.string.message_field_placeholder_dm
|
||||
ChannelType.Group -> R.string.message_field_placeholder_group
|
||||
ChannelType.TextChannel -> R.string.message_field_placeholder_text
|
||||
ChannelType.VoiceChannel -> R.string.message_field_placeholder_voice
|
||||
ChannelType.SavedMessages -> R.string.message_field_placeholder_notes
|
||||
}
|
||||
|
||||
Row(modifier) {
|
||||
// Additional buttons (currently adding an attachment)
|
||||
AnimatedVisibility(visible = showButtons) {
|
||||
Row(Modifier.weight(1f)) {
|
||||
ElevatedButton(
|
||||
onClick = {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Placeholder for adding an attachment",
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
},
|
||||
modifier = Modifier.size(56.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
shape = CircleShape
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Add,
|
||||
contentDescription = stringResource(id = R.string.add_attachment_alt)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The small chevron you see when the buttons are hidden
|
||||
AnimatedVisibility(visible = !showButtons) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.height(56.dp)
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.KeyboardArrowLeft,
|
||||
contentDescription = stringResource(id = R.string.show_more_alt),
|
||||
modifier = Modifier
|
||||
.size(24.dp + 8.dp)
|
||||
.padding(vertical = 4.dp)
|
||||
.clickable(onClick = {
|
||||
onToggleButtons(true)
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TextField(
|
||||
value = messageContent,
|
||||
onValueChange = onMessageContentChange,
|
||||
singleLine = false,
|
||||
shape = RoundedCornerShape(100),
|
||||
placeholder = {
|
||||
Text(
|
||||
stringResource(
|
||||
id = placeholderResource,
|
||||
channelName
|
||||
)
|
||||
)
|
||||
},
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
focusedIndicatorColor = Color.Transparent,
|
||||
unfocusedIndicatorColor = Color.Transparent,
|
||||
disabledIndicatorColor = Color.Transparent,
|
||||
errorIndicatorColor = Color.Transparent,
|
||||
placeholderColor = Color.Gray,
|
||||
),
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
|
||||
// Send button (visible when text is entered)
|
||||
AnimatedVisibility(visible = messageContent.isNotBlank()) {
|
||||
Button(
|
||||
onClick = onSendMessage,
|
||||
modifier = Modifier
|
||||
.size(56.dp),
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
shape = CircleShape
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Send,
|
||||
contentDescription = stringResource(id = R.string.send_alt)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,33 @@
|
|||
package chat.revolt.screens.chat
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelStopTypingFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.MessageFrame
|
||||
import chat.revolt.api.routes.channel.sendMessage
|
||||
import chat.revolt.api.routes.user.addUserIfUnknown
|
||||
import chat.revolt.api.schemas.Channel
|
||||
import chat.revolt.api.schemas.Message
|
||||
import chat.revolt.api.schemas.Message as MessageSchema
|
||||
import chat.revolt.components.chat.Message
|
||||
import kotlinx.coroutines.launch
|
||||
import chat.revolt.R
|
||||
import chat.revolt.components.chat.MessageField
|
||||
|
||||
class ChannelScreenViewModel : ViewModel() {
|
||||
private var _channel by mutableStateOf<Channel?>(null)
|
||||
|
|
@ -25,22 +38,59 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
val callbacks: RealtimeSocket.ChannelCallback?
|
||||
get() = _callbacks.value
|
||||
|
||||
private var _renderableMessages = mutableStateListOf<Message>()
|
||||
val renderableMessages: List<Message>
|
||||
private var _renderableMessages = mutableStateListOf<MessageSchema>()
|
||||
val renderableMessages: List<MessageSchema>
|
||||
get() = _renderableMessages
|
||||
|
||||
private var _typingUsers = mutableStateListOf<String>()
|
||||
val typingUsers: List<String>
|
||||
get() = _typingUsers
|
||||
|
||||
private var _messageContent by mutableStateOf("")
|
||||
val messageContent: String
|
||||
get() = _messageContent
|
||||
|
||||
fun setMessageContent(content: String) {
|
||||
_messageContent = content
|
||||
|
||||
if (content.isEmpty()) {
|
||||
_showButtons = true
|
||||
} else if (showButtons) {
|
||||
_showButtons = false
|
||||
}
|
||||
}
|
||||
|
||||
private var _showButtons by mutableStateOf(true)
|
||||
val showButtons: Boolean
|
||||
get() = _showButtons
|
||||
|
||||
fun setShowButtons(show: Boolean) {
|
||||
_showButtons = show
|
||||
}
|
||||
|
||||
inner class ChannelScreenCallback : RealtimeSocket.ChannelCallback {
|
||||
override fun onMessage(message: MessageFrame) {
|
||||
Log.d("ChannelScreen", "onMessage: $message")
|
||||
viewModelScope.launch {
|
||||
addUserIfUnknown(message.author!!)
|
||||
}
|
||||
|
||||
_renderableMessages.add(message)
|
||||
}
|
||||
|
||||
override fun onStartTyping(typing: ChannelStartTypingFrame) {
|
||||
Log.d("ChannelScreen", "onStartTyping: $typing")
|
||||
viewModelScope.launch {
|
||||
addUserIfUnknown(typing.user)
|
||||
}
|
||||
|
||||
if (!_typingUsers.contains(typing.user)) {
|
||||
_typingUsers.add(typing.user)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStopTyping(typing: ChannelStopTypingFrame) {
|
||||
Log.d("ChannelScreen", "onStopTyping: $typing")
|
||||
if (_typingUsers.contains(typing.user)) {
|
||||
_typingUsers.remove(typing.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -58,15 +108,40 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
|
||||
registerCallback()
|
||||
}
|
||||
|
||||
fun sendPendingMessage() {
|
||||
viewModelScope.launch {
|
||||
sendMessage(channel!!.id!!, messageContent)
|
||||
}
|
||||
_messageContent = ""
|
||||
}
|
||||
|
||||
fun typingMessageResource(): Int {
|
||||
return when (typingUsers.size) {
|
||||
0 -> R.string.typing_blank
|
||||
1 -> R.string.typing_one
|
||||
in 2..4 -> R.string.typing_many
|
||||
else -> R.string.typing_several
|
||||
}
|
||||
}
|
||||
|
||||
fun getTypingUsernames(): String {
|
||||
return typingUsers.joinToString {
|
||||
RevoltAPI.userCache[it]?.let { u ->
|
||||
u.username ?: u.id
|
||||
} ?: it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChannelScreen(
|
||||
navController: NavController,
|
||||
channelId: String,
|
||||
viewModel: ChannelScreenViewModel = hiltViewModel()
|
||||
viewModel: ChannelScreenViewModel = viewModel()
|
||||
) {
|
||||
val channel = viewModel.channel
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
LaunchedEffect(channelId) {
|
||||
viewModel.fetchChannel(channelId)
|
||||
|
|
@ -86,10 +161,45 @@ fun ChannelScreen(
|
|||
}
|
||||
|
||||
Column {
|
||||
Text(text = channel.name!!)
|
||||
|
||||
viewModel.renderableMessages.forEach {
|
||||
Text(text = "[" + it.getAuthor()!!.username + "] " + it.content!!)
|
||||
Text(text = "#" + channel.name!!)
|
||||
|
||||
Divider()
|
||||
|
||||
// Column nesting is needed to make the vertical scroll work properly
|
||||
Column(Modifier.weight(1f)) {
|
||||
Column(Modifier.verticalScroll(scrollState)) {
|
||||
viewModel.renderableMessages.forEach {
|
||||
Message(message = it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(visible = viewModel.typingUsers.isNotEmpty()) {
|
||||
Row(
|
||||
Modifier
|
||||
.padding(all = 4.dp)
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = viewModel.typingMessageResource(),
|
||||
viewModel.getTypingUsernames()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
channel.channelType?.let {
|
||||
MessageField(
|
||||
showButtons = viewModel.showButtons,
|
||||
onToggleButtons = viewModel::setShowButtons,
|
||||
messageContent = viewModel.messageContent,
|
||||
onMessageContentChange = viewModel::setMessageContent,
|
||||
onSendMessage = viewModel::sendPendingMessage,
|
||||
channelType = it,
|
||||
channelName = channel.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,15 +2,10 @@ package chat.revolt.screens.chat
|
|||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Send
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -19,11 +14,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavController
|
||||
import chat.revolt.api.REVOLT_FILES
|
||||
import chat.revolt.api.RevoltAPI
|
||||
import chat.revolt.components.generic.FormTextField
|
||||
import chat.revolt.components.generic.RemoteImage
|
||||
import chat.revolt.components.screens.home.LinkOnHome
|
||||
import chat.revolt.persistence.KVStorage
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -63,8 +54,6 @@ class HomeScreenViewModel @Inject constructor(
|
|||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hiltViewModel()) {
|
||||
val user = RevoltAPI.userCache[RevoltAPI.selfId]
|
||||
|
||||
val channelDrawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
|
|
@ -111,67 +100,7 @@ fun HomeScreen(navController: NavController, viewModel: HomeScreenViewModel = hi
|
|||
.padding(horizontal = 15.dp, vertical = 15.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(10.dp)
|
||||
.fillMaxSize()
|
||||
.weight(1f),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
user?.let {
|
||||
Row {
|
||||
RemoteImage(
|
||||
url = "${REVOLT_FILES}/avatars/${it.avatar?.id}/user.png",
|
||||
modifier = Modifier
|
||||
.size(50.dp)
|
||||
.clip(CircleShape),
|
||||
description = "Avatar for ${it.username} (placeholder!)"
|
||||
)
|
||||
|
||||
Column(modifier = Modifier.padding(start = 10.dp)) {
|
||||
it.username?.let { it1 -> Text(text = it1) }
|
||||
it.id?.let { it1 -> Text(text = it1) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "User cache",
|
||||
style = MaterialTheme.typography.displaySmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Left,
|
||||
fontSize = 24.sp
|
||||
),
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 15.dp, vertical = 15.dp)
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
Column(modifier = Modifier.height(200.dp)) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
RevoltAPI.userCache.forEach { (_, user) ->
|
||||
Text(text = user.username ?: user.id ?: "null")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column() {
|
||||
FormTextField(
|
||||
value = viewModel.messageContent,
|
||||
label = "Content",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onChange = viewModel::setMessageContent
|
||||
)
|
||||
LinkOnHome(
|
||||
heading = "Send",
|
||||
icon = Icons.Filled.Send,
|
||||
onClick = viewModel::sendMessage
|
||||
)
|
||||
}
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
viewModel.logout()
|
||||
|
|
|
|||
|
|
@ -72,11 +72,11 @@ class LoginViewModel @Inject constructor(
|
|||
} else {
|
||||
Log.d(
|
||||
"Login",
|
||||
"No MFA required. Login is complete! We have a session token: ${response.firstUserHints!!.token}"
|
||||
"No MFA required. Login is complete! We should have a session token"
|
||||
)
|
||||
|
||||
try {
|
||||
RevoltAPI.loginAs(response.firstUserHints.token)
|
||||
RevoltAPI.loginAs(response.firstUserHints!!.token)
|
||||
kvStorage.set("sessionToken", response.firstUserHints.token)
|
||||
|
||||
_navigateTo = "home"
|
||||
|
|
|
|||
|
|
@ -76,11 +76,11 @@ class MfaScreenViewModel @Inject constructor(
|
|||
} else {
|
||||
Log.d(
|
||||
"MFA",
|
||||
"Successfully authorized TOTP. Token: ${response.firstUserHints!!.token}"
|
||||
"Successfully authorized with TOTP."
|
||||
)
|
||||
|
||||
try {
|
||||
RevoltAPI.loginAs(response.firstUserHints.token)
|
||||
RevoltAPI.loginAs(response.firstUserHints!!.token)
|
||||
kvStorage.set("sessionToken", response.firstUserHints.token)
|
||||
|
||||
_navigateToHome = true
|
||||
|
|
@ -101,11 +101,11 @@ class MfaScreenViewModel @Inject constructor(
|
|||
} else {
|
||||
Log.d(
|
||||
"MFA",
|
||||
"Successfully authorized recovery code. Token: ${response.firstUserHints!!.token}"
|
||||
"Successfully authorized with a recovery code."
|
||||
)
|
||||
|
||||
try {
|
||||
RevoltAPI.loginAs(response.firstUserHints.token)
|
||||
RevoltAPI.loginAs(response.firstUserHints!!.token)
|
||||
kvStorage.set("sessionToken", response.firstUserHints.token)
|
||||
|
||||
_navigateToHome = true
|
||||
|
|
|
|||
|
|
@ -61,4 +61,26 @@
|
|||
|
||||
<string name="comingsoon_heading">Gah, you found me!</string>
|
||||
<string name="comingsoon_body">The feature you are trying to access is not ready yet, but we are steadily working on polishing it to perfection..</string>
|
||||
|
||||
<string name="typing_blank"><!-- this is a hack to prevent the typing indicator from showing typing_several when it's animating away --></string>
|
||||
<string name="typing_one">%1$s is typing…</string>
|
||||
<string name="typing_many">%1$s are typing…</string>
|
||||
<string name="typing_several">Several people are typing</string>
|
||||
|
||||
<string name="send_alt">Send</string>
|
||||
<string name="add_attachment_alt">Add attachment</string>
|
||||
<string name="show_more_alt">Show more</string>
|
||||
|
||||
<string name="tutorial">Welcome to Revolt\'s in-progress Android experience!</string>
|
||||
<string name="select_channel">Select a server and channel by swiping from the left.</string>
|
||||
|
||||
<string name="avatar_alt">%1$s\'s avatar</string>
|
||||
|
||||
<string name="message_field_placeholder_dm">Message @%1$s</string>
|
||||
<string name="message_field_placeholder_text">Message #%1$s</string>
|
||||
<string name="message_field_placeholder_voice">Message #%1$s</string>
|
||||
<string name="message_field_placeholder_group">Message %1$s</string>
|
||||
<string name="message_field_placeholder_notes">Add a note</string>
|
||||
|
||||
<string name="reply_message_not_cached">Unknown message, tap to jump</string>
|
||||
</resources>
|
||||
Loading…
Reference in New Issue