Skip to content
Snippets Groups Projects
Verified Commit e4c0c7b7 authored by Janne Mareike Koschinski's avatar Janne Mareike Koschinski
Browse files

feat: prepare for supporting buffer navigation and message loading

parent 3be9694f
Branches
No related tags found
No related merge requests found
Showing
with 211 additions and 41 deletions
package de.justjanne.quasseldroid
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.ui.CoreInfoRoute
import de.justjanne.quasseldroid.ui.HomeView
import de.justjanne.quasseldroid.ui.LoginRoute
import de.justjanne.quasseldroid.ui.routes.CoreInfoRoute
import de.justjanne.quasseldroid.ui.routes.HomeRoute
import de.justjanne.quasseldroid.ui.routes.LoginRoute
@Composable
fun QuasseldroidRouter(backend: QuasselBackend) {
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "login") {
composable("login") { LoginRoute(backend, navController) }
composable("home") { HomeView(backend, navController) }
composable("coreInfo") { CoreInfoRoute(backend, navController) }
composable("login") {
LoginRoute(backend, navController)
}
composable("home") {
HomeRoute(backend, navController)
}
composable(
"buffer/{bufferId}",
listOf(navArgument("bufferId") { type = NavType.IntType })
) {
Text("Buffer ${it.arguments?.getInt("bufferId")}")
}
composable("bufferViewConfigs") {
Text("List of BufferViewConfigs")
}
composable(
"bufferViewConfigs/{bufferViewConfigId}",
listOf(navArgument("bufferViewConfigId") { type = NavType.IntType })
) {
Text("BufferViewConfig ${it.arguments?.getInt("bufferViewConfigId")}")
}
composable("coreInfo") {
CoreInfoRoute(backend, navController)
}
}
}
package de.justjanne.quasseldroid.messages
import de.justjanne.libquassel.protocol.models.Message
data class MessageBuffer(
/**
* Whether the chronologically latest message for a given buffer id is in the buffer.
* If yes, new messages that arrive for this buffer should be appened to the end.
*/
val atEnd: Boolean,
val messages: List<Message>
)
package de.justjanne.quasseldroid.messages
import de.justjanne.libquassel.client.syncables.ClientBacklogManager
import de.justjanne.libquassel.protocol.models.Message
import de.justjanne.libquassel.protocol.models.ids.BufferId
import de.justjanne.libquassel.protocol.models.ids.MsgId
import de.justjanne.libquassel.protocol.util.StateHolder
import de.justjanne.libquassel.protocol.variant.into
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import java.io.Closeable
class MessageStore(
incoming: Flow<Message>,
private val backlogManager: ClientBacklogManager
) : Closeable, StateHolder<Map<BufferId, MessageBuffer>> {
private val state = MutableStateFlow(mapOf<BufferId, MessageBuffer>())
override fun state() = state.value
override fun flow() = state
private val scope = CoroutineScope(Dispatchers.IO)
private val disposable = incoming.onEach { message ->
val bufferId = message.bufferInfo.bufferId
state.update { messages ->
val buffer = messages[bufferId] ?: MessageBuffer(true, emptyList())
if (buffer.atEnd) {
messages + Pair(bufferId, buffer.copy(messages = buffer.messages + message))
} else {
messages
}
}
}.launchIn(scope)
fun loadAround(bufferId: BufferId, messageId: MsgId) {
scope.launch {
state.update { messages ->
val (before, after) = listOf(
async { backlogManager.backlog(bufferId, messageId) },
async { backlogManager.backlogForward(bufferId, messageId) },
).awaitAll()
val updated = MessageBuffer(
atEnd = false,
messages = (before + after)
.mapNotNull { it.into<Message>() }
.sortedBy { it.messageId }
)
messages + Pair(bufferId, updated)
}
}
}
fun loadBefore(bufferId: BufferId) {
scope.launch {
state.update { messages ->
val buffer = messages[bufferId] ?: MessageBuffer(true, emptyList())
val messageId = buffer.messages.firstOrNull()?.messageId ?: MsgId(-1)
val data = backlogManager.backlog(bufferId, messageId)
.mapNotNull { it.into<Message>() }
val updated = buffer.copy(
messages = (buffer.messages + data)
.sortedBy { it.messageId }
)
messages + Pair(bufferId, updated)
}
}
}
fun loadAfter(bufferId: BufferId) {
scope.launch {
state.update { messages ->
val buffer = messages[bufferId] ?: MessageBuffer(true, emptyList())
val messageId = buffer.messages.lastOrNull()?.messageId ?: MsgId(-1)
val data = backlogManager.backlogForward(bufferId, messageId)
.mapNotNull { it.into<Message>() }
val updated = buffer.copy(
messages = (buffer.messages + data)
.sortedBy { it.messageId }
)
messages + Pair(bufferId, updated)
}
}
}
override fun close() {
runBlocking {
disposable.cancelAndJoin()
}
}
}
package de.justjanne.quasseldroid.service
import de.justjanne.libquassel.client.session.ClientSession
import de.justjanne.quasseldroid.messages.MessageStore
data class ClientSessionWrapper(
val session: ClientSession,
val messages: MessageStore
)
......@@ -15,7 +15,7 @@ import de.justjanne.quasseldroid.util.lifecycle.LifecycleStatus
import kotlinx.coroutines.flow.MutableStateFlow
class QuasselBackend : DefaultContextualLifecycleObserver(), ServiceConnection,
StateHolder<ClientSession?> {
StateHolder<ClientSessionWrapper?> {
private var connectionData: ConnectionData? = null
override fun flow() = state.flatMap()
......
......@@ -6,8 +6,8 @@ import de.justjanne.libquassel.protocol.util.StateHolder
import kotlinx.coroutines.flow.StateFlow
class QuasselBinder(
private val state: StateFlow<ClientSession?>
) : Binder(), StateHolder<ClientSession?> {
private val state: StateFlow<ClientSessionWrapper?>
) : Binder(), StateHolder<ClientSessionWrapper?> {
constructor(runner: QuasselRunner) : this(runner.flow())
override fun flow() = state
......
......@@ -8,6 +8,7 @@ import de.justjanne.libquassel.protocol.connection.ProtocolVersion
import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.io.CoroutineChannel
import de.justjanne.libquassel.protocol.util.StateHolder
import de.justjanne.quasseldroid.messages.MessageStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
......@@ -20,16 +21,16 @@ import javax.net.ssl.SSLContext
class QuasselRunner(
private val address: InetSocketAddress,
private val auth: Pair<String, String>
) : Thread("Quassel Runner"), Closeable, StateHolder<ClientSession?> {
) : Thread("Quassel Runner"), Closeable, StateHolder<ClientSessionWrapper?> {
private val channel = CoroutineChannel()
override fun state(): ClientSession? = state.value
override fun flow(): StateFlow<ClientSession?> = state
override fun state(): ClientSessionWrapper? = state.value
override fun flow(): StateFlow<ClientSessionWrapper?> = state
private val state = MutableStateFlow<ClientSession?>(null)
private val state = MutableStateFlow<ClientSessionWrapper?>(null)
init {
start()
start()
}
override fun run() {
......@@ -49,7 +50,14 @@ class QuasselRunner(
)
),
SSLContext.getDefault()
).also { state.value = it }
)
state.value = ClientSessionWrapper(
session,
messages = MessageStore(
session.rpcHandler.messages(),
session.backlogManager
)
)
session.handshakeHandler.init(
"Quasseltest v0.1",
"2022-02-24",
......
package de.justjanne.quasseldroid.ui
package de.justjanne.quasseldroid.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
......
package de.justjanne.quasseldroid.ui
package de.justjanne.quasseldroid.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
......
package de.justjanne.quasseldroid.ui
package de.justjanne.quasseldroid.ui.components
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
......@@ -15,28 +15,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import de.justjanne.quasseldroid.service.ConnectionData
import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.util.TextFieldValueSaver
import java.net.InetSocketAddress
@Composable
fun LoginRoute(backend: QuasselBackend, navController: NavController) {
val context = LocalContext.current
LoginView(onLogin = {
if (backend.login(context, it)) {
navController.navigate("home")
}
})
}
@Preview(name = "Login", showBackground = true)
@Composable
fun LoginView(onLogin: (ConnectionData) -> Unit = {}) {
......
package de.justjanne.quasseldroid.ui
package de.justjanne.quasseldroid.ui.components
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.text.KeyboardActions
......
package de.justjanne.quasseldroid.ui
package de.justjanne.quasseldroid.ui.routes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
......@@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import de.justjanne.libquassel.protocol.util.flatMap
import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.ui.components.CoreInfoView
import de.justjanne.quasseldroid.util.mapNullable
import de.justjanne.quasseldroid.util.rememberFlow
......@@ -17,6 +18,7 @@ import de.justjanne.quasseldroid.util.rememberFlow
fun CoreInfoRoute(backend: QuasselBackend, navController: NavController) {
val coreInfo = rememberFlow(null) {
backend.flow()
.mapNullable { it.session }
.flatMap()
.mapNullable { it.coreInfo }
.flatMap()
......
package de.justjanne.quasseldroid.ui
package de.justjanne.quasseldroid.ui.routes
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
......@@ -7,11 +7,8 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import de.justjanne.libquassel.protocol.models.ids.BufferId
import de.justjanne.libquassel.protocol.syncables.state.BufferViewConfigState
import de.justjanne.libquassel.protocol.util.combineLatest
import de.justjanne.libquassel.protocol.util.flatMap
......@@ -20,11 +17,15 @@ import de.justjanne.quasseldroid.util.mapNullable
import de.justjanne.quasseldroid.util.rememberFlow
@Composable
fun HomeView(backend: QuasselBackend, navController: NavController) {
val session = rememberFlow(null) { backend.flow() }
fun HomeRoute(backend: QuasselBackend, navController: NavController) {
val session = rememberFlow(null) {
backend.flow()
.mapNullable { it.session }
}
val bufferViewConfigs: List<BufferViewConfigState> = rememberFlow(emptyList()) {
backend.flow()
.mapNullable { it.session }
.flatMap()
.mapNullable { it.bufferViewManager }
.flatMap()
......@@ -34,6 +35,7 @@ fun HomeView(backend: QuasselBackend, navController: NavController) {
val initStatus = rememberFlow(null) {
backend.flow()
.mapNullable { it.session }
.mapNullable { it.baseInitHandler }
.flatMap()
}
......
package de.justjanne.quasseldroid.ui.routes
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController
import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.ui.components.LoginView
@Composable
fun LoginRoute(backend: QuasselBackend, navController: NavController) {
val context = LocalContext.current
LoginView(onLogin = {
if (backend.login(context, it)) {
navController.navigate("home")
}
})
}
import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.implementation
import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.testImplementation
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
......@@ -8,7 +10,10 @@ plugins {
}
dependencies {
"implementation"("org.jetbrains.kotlin:kotlin-stdlib:1.6.10")
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.10")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0")
}
tasks.withType<KotlinCompile> {
......
......@@ -10,6 +10,8 @@ plugins {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.6.10")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0")
}
tasks.withType<KotlinCompile> {
......
[versions]
libquassel = "0.8.1"
libquassel = "0.9.0"
androidx-activity = "1.4.0"
androidx-appcompat = "1.4.1"
androidx-compose = "1.1.1"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment