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
No related branches found
No related tags found
No related merge requests found
Showing
with 211 additions and 41 deletions
package de.justjanne.quasseldroid package de.justjanne.quasseldroid
import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import de.justjanne.quasseldroid.service.QuasselBackend import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.ui.CoreInfoRoute import de.justjanne.quasseldroid.ui.routes.CoreInfoRoute
import de.justjanne.quasseldroid.ui.HomeView import de.justjanne.quasseldroid.ui.routes.HomeRoute
import de.justjanne.quasseldroid.ui.LoginRoute import de.justjanne.quasseldroid.ui.routes.LoginRoute
@Composable @Composable
fun QuasseldroidRouter(backend: QuasselBackend) { fun QuasseldroidRouter(backend: QuasselBackend) {
val navController = rememberNavController() val navController = rememberNavController()
NavHost(navController = navController, startDestination = "login") { NavHost(navController = navController, startDestination = "login") {
composable("login") { LoginRoute(backend, navController) } composable("login") {
composable("home") { HomeView(backend, navController) } LoginRoute(backend, navController)
composable("coreInfo") { CoreInfoRoute(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 ...@@ -15,7 +15,7 @@ import de.justjanne.quasseldroid.util.lifecycle.LifecycleStatus
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
class QuasselBackend : DefaultContextualLifecycleObserver(), ServiceConnection, class QuasselBackend : DefaultContextualLifecycleObserver(), ServiceConnection,
StateHolder<ClientSession?> { StateHolder<ClientSessionWrapper?> {
private var connectionData: ConnectionData? = null private var connectionData: ConnectionData? = null
override fun flow() = state.flatMap() override fun flow() = state.flatMap()
......
...@@ -6,8 +6,8 @@ import de.justjanne.libquassel.protocol.util.StateHolder ...@@ -6,8 +6,8 @@ import de.justjanne.libquassel.protocol.util.StateHolder
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
class QuasselBinder( class QuasselBinder(
private val state: StateFlow<ClientSession?> private val state: StateFlow<ClientSessionWrapper?>
) : Binder(), StateHolder<ClientSession?> { ) : Binder(), StateHolder<ClientSessionWrapper?> {
constructor(runner: QuasselRunner) : this(runner.flow()) constructor(runner: QuasselRunner) : this(runner.flow())
override fun flow() = state override fun flow() = state
......
...@@ -8,6 +8,7 @@ import de.justjanne.libquassel.protocol.connection.ProtocolVersion ...@@ -8,6 +8,7 @@ import de.justjanne.libquassel.protocol.connection.ProtocolVersion
import de.justjanne.libquassel.protocol.features.FeatureSet import de.justjanne.libquassel.protocol.features.FeatureSet
import de.justjanne.libquassel.protocol.io.CoroutineChannel import de.justjanne.libquassel.protocol.io.CoroutineChannel
import de.justjanne.libquassel.protocol.util.StateHolder import de.justjanne.libquassel.protocol.util.StateHolder
import de.justjanne.quasseldroid.messages.MessageStore
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
...@@ -20,16 +21,16 @@ import javax.net.ssl.SSLContext ...@@ -20,16 +21,16 @@ import javax.net.ssl.SSLContext
class QuasselRunner( class QuasselRunner(
private val address: InetSocketAddress, private val address: InetSocketAddress,
private val auth: Pair<String, String> private val auth: Pair<String, String>
) : Thread("Quassel Runner"), Closeable, StateHolder<ClientSession?> { ) : Thread("Quassel Runner"), Closeable, StateHolder<ClientSessionWrapper?> {
private val channel = CoroutineChannel() private val channel = CoroutineChannel()
override fun state(): ClientSession? = state.value override fun state(): ClientSessionWrapper? = state.value
override fun flow(): StateFlow<ClientSession?> = state override fun flow(): StateFlow<ClientSessionWrapper?> = state
private val state = MutableStateFlow<ClientSession?>(null) private val state = MutableStateFlow<ClientSessionWrapper?>(null)
init { init {
start() start()
} }
override fun run() { override fun run() {
...@@ -49,7 +50,14 @@ class QuasselRunner( ...@@ -49,7 +50,14 @@ class QuasselRunner(
) )
), ),
SSLContext.getDefault() SSLContext.getDefault()
).also { state.value = it } )
state.value = ClientSessionWrapper(
session,
messages = MessageStore(
session.rpcHandler.messages(),
session.backlogManager
)
)
session.handshakeHandler.init( session.handshakeHandler.init(
"Quasseltest v0.1", "Quasseltest v0.1",
"2022-02-24", "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.Column
import androidx.compose.foundation.layout.Row 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.Column
import androidx.compose.foundation.layout.padding 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.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
...@@ -15,28 +15,15 @@ import androidx.compose.runtime.mutableStateOf ...@@ -15,28 +15,15 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import de.justjanne.quasseldroid.service.ConnectionData import de.justjanne.quasseldroid.service.ConnectionData
import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.util.TextFieldValueSaver import de.justjanne.quasseldroid.util.TextFieldValueSaver
import java.net.InetSocketAddress 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) @Preview(name = "Login", showBackground = true)
@Composable @Composable
fun LoginView(onLogin: (ConnectionData) -> Unit = {}) { 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.interaction.MutableInteractionSource
import androidx.compose.foundation.text.KeyboardActions 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.Column
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
...@@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp ...@@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp
import androidx.navigation.NavController import androidx.navigation.NavController
import de.justjanne.libquassel.protocol.util.flatMap import de.justjanne.libquassel.protocol.util.flatMap
import de.justjanne.quasseldroid.service.QuasselBackend import de.justjanne.quasseldroid.service.QuasselBackend
import de.justjanne.quasseldroid.ui.components.CoreInfoView
import de.justjanne.quasseldroid.util.mapNullable import de.justjanne.quasseldroid.util.mapNullable
import de.justjanne.quasseldroid.util.rememberFlow import de.justjanne.quasseldroid.util.rememberFlow
...@@ -17,6 +18,7 @@ import de.justjanne.quasseldroid.util.rememberFlow ...@@ -17,6 +18,7 @@ import de.justjanne.quasseldroid.util.rememberFlow
fun CoreInfoRoute(backend: QuasselBackend, navController: NavController) { fun CoreInfoRoute(backend: QuasselBackend, navController: NavController) {
val coreInfo = rememberFlow(null) { val coreInfo = rememberFlow(null) {
backend.flow() backend.flow()
.mapNullable { it.session }
.flatMap() .flatMap()
.mapNullable { it.coreInfo } .mapNullable { it.coreInfo }
.flatMap() .flatMap()
......
package de.justjanne.quasseldroid.ui package de.justjanne.quasseldroid.ui.routes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
...@@ -7,11 +7,8 @@ import androidx.compose.foundation.lazy.items ...@@ -7,11 +7,8 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.material.Button import androidx.compose.material.Button
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.navigation.NavController 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.syncables.state.BufferViewConfigState
import de.justjanne.libquassel.protocol.util.combineLatest import de.justjanne.libquassel.protocol.util.combineLatest
import de.justjanne.libquassel.protocol.util.flatMap import de.justjanne.libquassel.protocol.util.flatMap
...@@ -20,11 +17,15 @@ import de.justjanne.quasseldroid.util.mapNullable ...@@ -20,11 +17,15 @@ import de.justjanne.quasseldroid.util.mapNullable
import de.justjanne.quasseldroid.util.rememberFlow import de.justjanne.quasseldroid.util.rememberFlow
@Composable @Composable
fun HomeView(backend: QuasselBackend, navController: NavController) { fun HomeRoute(backend: QuasselBackend, navController: NavController) {
val session = rememberFlow(null) { backend.flow() } val session = rememberFlow(null) {
backend.flow()
.mapNullable { it.session }
}
val bufferViewConfigs: List<BufferViewConfigState> = rememberFlow(emptyList()) { val bufferViewConfigs: List<BufferViewConfigState> = rememberFlow(emptyList()) {
backend.flow() backend.flow()
.mapNullable { it.session }
.flatMap() .flatMap()
.mapNullable { it.bufferViewManager } .mapNullable { it.bufferViewManager }
.flatMap() .flatMap()
...@@ -34,6 +35,7 @@ fun HomeView(backend: QuasselBackend, navController: NavController) { ...@@ -34,6 +35,7 @@ fun HomeView(backend: QuasselBackend, navController: NavController) {
val initStatus = rememberFlow(null) { val initStatus = rememberFlow(null) {
backend.flow() backend.flow()
.mapNullable { it.session }
.mapNullable { it.baseInitHandler } .mapNullable { it.baseInitHandler }
.flatMap() .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 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins { plugins {
...@@ -8,7 +10,10 @@ plugins { ...@@ -8,7 +10,10 @@ plugins {
} }
dependencies { 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> { tasks.withType<KotlinCompile> {
......
...@@ -10,6 +10,8 @@ plugins { ...@@ -10,6 +10,8 @@ plugins {
dependencies { 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")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.0")
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {
......
[versions] [versions]
libquassel = "0.8.1" libquassel = "0.9.0"
androidx-activity = "1.4.0" androidx-activity = "1.4.0"
androidx-appcompat = "1.4.1" androidx-appcompat = "1.4.1"
androidx-compose = "1.1.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