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

deps: update dependencies

parent 449c7188
Branches attachments
No related tags found
No related merge requests found
Showing
with 358 additions and 18 deletions
@file:Suppress("UnstableApiUsage")
/*
* Quasseldroid - Quassel client for Android
*
......@@ -17,14 +19,26 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import util.buildConfigField
import util.cmd
plugins {
id("justjanne.android.signing")
id("justjanne.android.app")
}
fun Project.fancyVersionName(): String? {
val name = cmd("git", "describe", "--always", "--tags", "HEAD") ?: return null
val commit = cmd("git", "rev-parse", "HEAD") ?: return name
return """<a href="https://git.kuschku.de/justJanne/QuasselDroid-ng/commit/$commit">$name</a>"""
}
android {
defaultConfig {
buildConfigField("FANCY_VERSION_NAME", fancyVersionName())
vectorDrawables.useSupportLibrary = true
testInstrumentationRunner = "de.justjanne.quasseldroid.util.TestRunner"
}
buildTypes {
......@@ -52,11 +66,21 @@ android {
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.get()
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
}
}
dependencies {
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.android)
testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.kotlin.test)
testImplementation(libs.junit.api)
testImplementation(libs.junit.params)
testRuntimeOnly(libs.junit.engine)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.appcompat.resources)
......@@ -69,7 +93,7 @@ dependencies {
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material.icons)
implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.collection.ktx)
implementation(libs.androidx.core.ktx)
......@@ -88,7 +112,7 @@ dependencies {
implementation(libs.libquassel.client)
implementation(libs.libquassel.irc)
implementation(libs.compose.htmltext)
//implementation(libs.compose.htmltext)
debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.preview)
......
<?xml version="1.0" encoding="utf-8"?><!--
Quasseldroid - Quassel client for Android
Copyright (c) 2020 Janne Mareike Koschinski
Copyright (c) 2020 The Quassel Project
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License version 3 as published
by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<color name="colorPrimary">#c0700a</color>
<color name="colorPrimaryDark">#945a10</color>
<color name="colorAccent">#ffaf3b</color>
<color name="colorIconLight">#e79102</color>
<color name="colorIconDark">#994e12</color>
</resources>
......@@ -11,6 +11,7 @@
android:icon="@mipmap/ic_launcher"
android:supportsRtl="true">
<activity
android:label="@string/app_name"
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTask"
......
......@@ -34,6 +34,11 @@ class MainActivity : ComponentActivity() {
backend.onResume(this)
}
override fun onPause() {
super.onPause()
backend.onPause(this)
}
override fun onStop() {
super.onStop()
backend.onStop(this)
......
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.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, limit: Int) {
scope.launch {
state.update { messages ->
val (before, after) = listOf(
backlogManager.backlog(bufferId, last = messageId, limit = limit)
.mapNotNull { it.into<Message>() },
backlogManager.backlogForward(bufferId, first = messageId, limit = limit - 1)
.mapNotNull { it.into<Message>() },
)
val updated = MessageBuffer(
atEnd = false,
messages = (before + after).distinct().sortedBy { it.messageId }
)
messages + Pair(bufferId, updated)
}
}
}
fun loadBefore(bufferId: BufferId, limit: Int) {
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, last = messageId, limit = limit)
.mapNotNull { it.into<Message>() }
val updated = buffer.copy(
messages = (buffer.messages + data).distinct().sortedBy { it.messageId }
)
messages + Pair(bufferId, updated)
}
}
}
fun loadAfter(bufferId: BufferId, limit: Int) {
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, first = messageId, limit = limit)
.mapNotNull { it.into<Message>() }
val updated = buffer.copy(
messages = (buffer.messages + data).distinct().sortedBy { it.messageId }
)
messages + Pair(bufferId, updated)
}
}
}
fun clear(bufferId: BufferId) {
scope.launch {
state.update { messages ->
messages - bufferId
}
}
}
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
)
......@@ -41,7 +41,9 @@ class QuasselBackend : DefaultContextualLifecycleObserver(), ServiceConnection,
override fun onStop(owner: Context) {
super.onStop(owner)
Log.d("QuasselBackend", "Unbinding Quassel Service")
owner.unbindService(this)
if (state.value != null) {
owner.unbindService(this)
}
}
fun login(context: Context, connectionData: ConnectionData): Boolean {
......
......@@ -16,7 +16,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import de.charlex.compose.HtmlText
//import de.charlex.compose.HtmlText
import de.justjanne.libquassel.protocol.models.ConnectedClient
import de.justjanne.quasseldroid.model.SecurityLevel
import de.justjanne.quasseldroid.sample.SampleConnectedClientProvider
......@@ -34,7 +34,7 @@ fun ConnectedClientCard(
Card(modifier = modifier) {
Row(modifier = Modifier.padding(16.dp)) {
Column(modifier = Modifier.weight(1.0f)) {
HtmlText(
Text(
text = client.version,
style = Typography.body1,
overflow = TextOverflow.Ellipsis
......
......@@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import de.charlex.compose.HtmlText
//import de.charlex.compose.HtmlText
import de.justjanne.libquassel.protocol.models.ConnectedClient
import de.justjanne.libquassel.protocol.syncables.state.CoreInfoState
import de.justjanne.quasseldroid.sample.SampleCoreInfoProvider
......@@ -26,7 +26,7 @@ fun CoreInfoView(
) {
Column(modifier = Modifier.padding(8.dp)) {
Column(modifier = Modifier.padding(8.dp)) {
HtmlText(
Text(
text = coreInfo.version,
style = Typography.body1
)
......
package de.justjanne.quasseldroid.ui.components
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import de.justjanne.bitflags.of
import de.justjanne.libquassel.irc.HostmaskHelper
import de.justjanne.libquassel.irc.IrcFormat
import de.justjanne.libquassel.irc.IrcFormatDeserializer
import de.justjanne.libquassel.protocol.models.Message
import de.justjanne.libquassel.protocol.models.flags.MessageType
import de.justjanne.libquassel.protocol.models.ids.MsgId
import de.justjanne.quasseldroid.R
import de.justjanne.quasseldroid.sample.SampleMessagesProvider
import de.justjanne.quasseldroid.ui.theme.QuasselTheme
import de.justjanne.quasseldroid.ui.theme.Typography
import de.justjanne.quasseldroid.util.extensions.OnBottomReached
import de.justjanne.quasseldroid.util.extensions.OnTopReached
import de.justjanne.quasseldroid.util.extensions.format
import de.justjanne.quasseldroid.util.extensions.getPrevious
import de.justjanne.quasseldroid.util.format.IrcFormatRenderer
import de.justjanne.quasseldroid.util.format.TextFormatter
import org.threeten.bp.ZoneId
@Preview(name = "Messages", showBackground = true)
@Composable
fun MessageList(
@PreviewParameter(SampleMessagesProvider::class)
messages: List<Message>,
listState: LazyListState = rememberLazyListState(),
markerLine: MsgId = MsgId(-1),
buffer: Int = 0,
onLoadAtStart: () -> Unit = { },
onLoadAtEnd: () -> Unit = { },
) {
LazyColumn(state = listState) {
itemsIndexed(messages, key = { _, item -> item.messageId }) { index, message ->
val prev = messages.getPrevious(index)
val prevDate = prev?.time?.atZone(ZoneId.systemDefault())?.toLocalDate()
val messageDate = message.time.atZone(ZoneId.systemDefault()).toLocalDate()
val followUp = prev != null &&
message.sender == prev.sender &&
message.senderPrefixes == prev.senderPrefixes &&
message.realName == prev.realName &&
message.avatarUrl == prev.avatarUrl
val isNew = (prev == null || prev.messageId <= markerLine) &&
message.messageId > markerLine
val parsed = IrcFormatDeserializer.parse(message.content)
if (prevDate == null || !messageDate.isEqual(prevDate)) {
MessageDayChangeView(messageDate, isNew)
} else if (isNew) {
NewMessageView()
}
when (message.type) {
MessageType.of(MessageType.Plain) -> {
MessageBase(message, followUp) {
Text(IrcFormatRenderer.render(parsed), style = Typography.body2)
}
}
MessageType.of(MessageType.Action) -> {
MessageBaseSmall(message, /*backgroundColor = QuasselTheme.chat.action*/) {
val nick = HostmaskHelper.nick(message.sender)
Text(
TextFormatter.format(
AnnotatedString(stringResource(R.string.message_format_action)),
buildNick(nick, message.senderPrefixes),
IrcFormatRenderer.render(
data = parsed.map { it.copy(style = it.style.flipFlag(IrcFormat.Flag.ITALIC)) },
textColor = QuasselTheme.chat.onAction,
backgroundColor = QuasselTheme.chat.action
)
),
style = Typography.body2,
color = QuasselTheme.chat.onAction
)
}
}
}
}
}
listState.OnTopReached(buffer = buffer, onLoadMore = onLoadAtStart)
listState.OnBottomReached(buffer = buffer, onLoadMore = onLoadAtEnd)
}
......@@ -54,6 +54,7 @@ fun PasswordTextField(
keyboardActions: KeyboardActions = KeyboardActions(),
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
......@@ -86,6 +87,7 @@ fun PasswordTextField(
keyboardActions,
singleLine,
maxLines,
minLines,
interactionSource,
shape,
colors
......@@ -111,6 +113,7 @@ fun PasswordTextField(
keyboardActions: KeyboardActions = KeyboardActions(),
singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
......@@ -143,6 +146,7 @@ fun PasswordTextField(
keyboardActions,
singleLine,
maxLines,
minLines,
interactionSource,
shape,
colors
......
......@@ -21,11 +21,11 @@ inline fun <T, R> Flow<T?>.flatMapLatestNullable(crossinline transform: suspend
}
@Composable
inline fun <T> rememberFlow(initial: T, calculation: @DisallowComposableCalls () -> Flow<T>): T {
inline fun <T> rememberFlow(initial: T, crossinline calculation: @DisallowComposableCalls () -> Flow<T>): T {
return remember(calculation).collectAsState(initial).value
}
@Composable
inline fun <T> rememberFlow(calculation: @DisallowComposableCalls () -> StateFlow<T>): T {
inline fun <T> rememberFlow(crossinline calculation: @DisallowComposableCalls () -> StateFlow<T>): T {
return remember(calculation).collectAsState().value
}
<?xml version="1.0" encoding="utf-8" ?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" tools:ignore="MissingTranslation">Quasseldroid</string>
</resources>
......@@ -18,3 +18,17 @@
*/
group = "com.iskrembilen"
buildscript {
repositories {
google()
mavenCentral()
}
}
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.ksp) apply false
}
......@@ -31,4 +31,3 @@ org.gradle.jvmargs=-Xmx2048m
#org.gradle.caching=true
# Enable AndroidX
android.useAndroidX=true
android.enableJetifier=true
......@@ -9,13 +9,34 @@ repositories {
}
dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.6.10-1.0.4")
implementation("com.android.tools.build:gradle:7.1.2")
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
compileOnly(libs.ksp.gradlePlugin)
}
gradlePlugin {
plugins {
register("androidApplication") {
id = "justjanne.android.app"
implementationClass = "AndroidApplicationConvention"
}
register("androidLibrary") {
id = "justjanne.android.library"
implementationClass = "AndroidLibraryConvention"
}
register("kotlinAndroid") {
id = "justjanne.kotlin.android"
implementationClass = "KotlinAndroidConvention"
}
register("kotlin") {
id = "justjanne.kotlin"
implementationClass = "KotlinConvention"
}
}
}
configure<JavaPluginExtension> {
toolchain {
languageVersion.set(JavaLanguageVersion.of(8))
languageVersion.set(JavaLanguageVersion.of(11))
}
}
org.gradle.parallel=true
org.gradle.caching=true
org.gradle.configureondemand=true
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionSha256Szm=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionSha256Szm=ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
@file:Suppress("UnstableApiUsage")
rootProject.name = "convention"
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../libs.versions.toml"))
}
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment