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
No related branches found
No related tags found
No related merge requests found
Showing
with 358 additions and 18 deletions
@file:Suppress("UnstableApiUsage")
/* /*
* Quasseldroid - Quassel client for Android * Quasseldroid - Quassel client for Android
* *
...@@ -17,14 +19,26 @@ ...@@ -17,14 +19,26 @@
* with this program. If not, see <http://www.gnu.org/licenses/>. * with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import util.buildConfigField
import util.cmd
plugins { plugins {
id("justjanne.android.signing")
id("justjanne.android.app") 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 { android {
defaultConfig { defaultConfig {
buildConfigField("FANCY_VERSION_NAME", fancyVersionName())
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
testInstrumentationRunner = "de.justjanne.quasseldroid.util.TestRunner"
} }
buildTypes { buildTypes {
...@@ -52,11 +66,21 @@ android { ...@@ -52,11 +66,21 @@ android {
} }
composeOptions { composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidx.compose.get() kotlinCompilerExtensionVersion = libs.versions.androidx.compose.compiler.get()
} }
} }
dependencies { 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)
implementation(libs.androidx.appcompat.resources) implementation(libs.androidx.appcompat.resources)
...@@ -69,7 +93,7 @@ dependencies { ...@@ -69,7 +93,7 @@ dependencies {
implementation(libs.androidx.compose.material) implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material.icons) implementation(libs.androidx.compose.material.icons)
implementation(libs.androidx.compose.runtime) implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.collection.ktx) implementation(libs.androidx.collection.ktx)
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
...@@ -88,7 +112,7 @@ dependencies { ...@@ -88,7 +112,7 @@ dependencies {
implementation(libs.libquassel.client) implementation(libs.libquassel.client)
implementation(libs.libquassel.irc) implementation(libs.libquassel.irc)
implementation(libs.compose.htmltext) //implementation(libs.compose.htmltext)
debugImplementation(libs.androidx.compose.ui.tooling) debugImplementation(libs.androidx.compose.ui.tooling)
implementation(libs.androidx.compose.ui.preview) 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 @@ ...@@ -11,6 +11,7 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:supportsRtl="true"> android:supportsRtl="true">
<activity <activity
android:label="@string/app_name"
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTask" android:launchMode="singleTask"
......
...@@ -34,6 +34,11 @@ class MainActivity : ComponentActivity() { ...@@ -34,6 +34,11 @@ class MainActivity : ComponentActivity() {
backend.onResume(this) backend.onResume(this)
} }
override fun onPause() {
super.onPause()
backend.onPause(this)
}
override fun onStop() { override fun onStop() {
super.onStop() super.onStop()
backend.onStop(this) 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,8 +41,10 @@ class QuasselBackend : DefaultContextualLifecycleObserver(), ServiceConnection, ...@@ -41,8 +41,10 @@ class QuasselBackend : DefaultContextualLifecycleObserver(), ServiceConnection,
override fun onStop(owner: Context) { override fun onStop(owner: Context) {
super.onStop(owner) super.onStop(owner)
Log.d("QuasselBackend", "Unbinding Quassel Service") Log.d("QuasselBackend", "Unbinding Quassel Service")
if (state.value != null) {
owner.unbindService(this) owner.unbindService(this)
} }
}
fun login(context: Context, connectionData: ConnectionData): Boolean { fun login(context: Context, connectionData: ConnectionData): Boolean {
this.connectionData = connectionData this.connectionData = connectionData
......
...@@ -16,7 +16,7 @@ import androidx.compose.ui.text.style.TextOverflow ...@@ -16,7 +16,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp 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.models.ConnectedClient
import de.justjanne.quasseldroid.model.SecurityLevel import de.justjanne.quasseldroid.model.SecurityLevel
import de.justjanne.quasseldroid.sample.SampleConnectedClientProvider import de.justjanne.quasseldroid.sample.SampleConnectedClientProvider
...@@ -34,7 +34,7 @@ fun ConnectedClientCard( ...@@ -34,7 +34,7 @@ fun ConnectedClientCard(
Card(modifier = modifier) { Card(modifier = modifier) {
Row(modifier = Modifier.padding(16.dp)) { Row(modifier = Modifier.padding(16.dp)) {
Column(modifier = Modifier.weight(1.0f)) { Column(modifier = Modifier.weight(1.0f)) {
HtmlText( Text(
text = client.version, text = client.version,
style = Typography.body1, style = Typography.body1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
......
...@@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier ...@@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp 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.models.ConnectedClient
import de.justjanne.libquassel.protocol.syncables.state.CoreInfoState import de.justjanne.libquassel.protocol.syncables.state.CoreInfoState
import de.justjanne.quasseldroid.sample.SampleCoreInfoProvider import de.justjanne.quasseldroid.sample.SampleCoreInfoProvider
...@@ -26,7 +26,7 @@ fun CoreInfoView( ...@@ -26,7 +26,7 @@ fun CoreInfoView(
) { ) {
Column(modifier = Modifier.padding(8.dp)) { Column(modifier = Modifier.padding(8.dp)) {
Column(modifier = Modifier.padding(8.dp)) { Column(modifier = Modifier.padding(8.dp)) {
HtmlText( Text(
text = coreInfo.version, text = coreInfo.version,
style = Typography.body1 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( ...@@ -54,6 +54,7 @@ fun PasswordTextField(
keyboardActions: KeyboardActions = KeyboardActions(), keyboardActions: KeyboardActions = KeyboardActions(),
singleLine: Boolean = false, singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small, shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
...@@ -86,6 +87,7 @@ fun PasswordTextField( ...@@ -86,6 +87,7 @@ fun PasswordTextField(
keyboardActions, keyboardActions,
singleLine, singleLine,
maxLines, maxLines,
minLines,
interactionSource, interactionSource,
shape, shape,
colors colors
...@@ -111,6 +113,7 @@ fun PasswordTextField( ...@@ -111,6 +113,7 @@ fun PasswordTextField(
keyboardActions: KeyboardActions = KeyboardActions(), keyboardActions: KeyboardActions = KeyboardActions(),
singleLine: Boolean = false, singleLine: Boolean = false,
maxLines: Int = Int.MAX_VALUE, maxLines: Int = Int.MAX_VALUE,
minLines: Int = 1,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
shape: Shape = MaterialTheme.shapes.small, shape: Shape = MaterialTheme.shapes.small,
colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors()
...@@ -143,6 +146,7 @@ fun PasswordTextField( ...@@ -143,6 +146,7 @@ fun PasswordTextField(
keyboardActions, keyboardActions,
singleLine, singleLine,
maxLines, maxLines,
minLines,
interactionSource, interactionSource,
shape, shape,
colors colors
......
...@@ -21,11 +21,11 @@ inline fun <T, R> Flow<T?>.flatMapLatestNullable(crossinline transform: suspend ...@@ -21,11 +21,11 @@ inline fun <T, R> Flow<T?>.flatMapLatestNullable(crossinline transform: suspend
} }
@Composable @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 return remember(calculation).collectAsState(initial).value
} }
@Composable @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 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 @@ ...@@ -18,3 +18,17 @@
*/ */
group = "com.iskrembilen" 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 ...@@ -31,4 +31,3 @@ org.gradle.jvmargs=-Xmx2048m
#org.gradle.caching=true #org.gradle.caching=true
# Enable AndroidX # Enable AndroidX
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true
...@@ -9,13 +9,34 @@ repositories { ...@@ -9,13 +9,34 @@ repositories {
} }
dependencies { dependencies {
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10") compileOnly(libs.android.gradlePlugin)
implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:1.6.10-1.0.4") compileOnly(libs.kotlin.gradlePlugin)
implementation("com.android.tools.build:gradle:7.1.2") 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> { configure<JavaPluginExtension> {
toolchain { 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 distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionSha256Szm=8cc27038d5dbd815759851ba53e70cf62e481b87494cc97cfd97982ada5ba634 distributionSha256Szm=ff7bf6a86f09b9b2c40bb8f48b25fc19cf2b2664fd1d220cd7ab833ec758d0d7
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
@file:Suppress("UnstableApiUsage")
rootProject.name = "convention" 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.
Please register or to comment