From 98b0cd71361e4c93dd36efe373c6c49b6b803b3f Mon Sep 17 00:00:00 2001 From: Janne Mareike Koschinski <janne@kuschku.de> Date: Mon, 3 Apr 2023 21:56:46 +0200 Subject: [PATCH] deps: update dependencies --- app/build.gradle.kts | 32 +++++- app/src/debug/res/values/colors.xml | 27 +++++ app/src/main/AndroidManifest.xml | 1 + .../de/justjanne/quasseldroid/MainActivity.kt | 5 + .../quasseldroid/messages/MessageBuffer.kt | 12 ++ .../quasseldroid/messages/MessageStore.kt | 104 ++++++++++++++++++ .../service/ClientSessionWrapper.kt | 9 ++ .../quasseldroid/service/QuasselBackend.kt | 4 +- .../ui/components/ConnectedClientCard.kt | 4 +- .../ui/components/CoreInfoView.kt | 4 +- .../quasseldroid/ui/components/MessageList.kt | 97 ++++++++++++++++ .../ui/components/PasswordTextField.kt | 4 + .../quasseldroid/util/FlowExtensions.kt | 4 +- app/src/main/res/values/strings.xml | 4 + build.gradle.kts | 14 +++ gradle.properties | 1 - gradle/convention/build.gradle.kts | 29 ++++- gradle/convention/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 4 +- gradle/convention/settings.gradle.kts | 14 +++ .../kotlin/AndroidApplicationConvention.kt | 71 ++++++++++++ .../main/kotlin/AndroidLibraryConvention.kt | 35 ++++++ .../main/kotlin/KotlinAndroidConvention.kt | 49 +++++++++ .../src/main/kotlin/KotlinConvention.kt | 45 ++++++++ ...roid.signing.gradle.kts => SigningData.kt} | 30 ----- .../kotlin/justjanne.android.app.gradle.kts | 95 ---------------- .../justjanne.android.library.gradle.kts | 24 ---- .../src/main/kotlin/justjanne.java.gradle.kts | 23 ---- .../justjanne.kotlin.android.gradle.kts | 47 -------- .../main/kotlin/justjanne.kotlin.gradle.kts | 29 ----- .../kotlin/justjanne.repositories.gradle.kts | 5 - .../kotlin/util/BaseExtensionExtensions.kt | 8 ++ .../src/main/kotlin/util/ProjectExtensions.kt | 79 +++++++++++++ gradle/init.gradle.kts | 55 +++++++++ gradle/libs.versions.toml | 90 +++++++++++---- gradle/spotless/copyright.kt | 18 +++ gradle/spotless/copyright.kts | 18 +++ gradle/spotless/copyright.xml | 19 ++++ gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle.kts | 17 ++- 40 files changed, 835 insertions(+), 302 deletions(-) create mode 100644 app/src/debug/res/values/colors.xml create mode 100644 app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageBuffer.kt create mode 100644 app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageStore.kt create mode 100644 app/src/main/kotlin/de/justjanne/quasseldroid/service/ClientSessionWrapper.kt create mode 100644 app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/MessageList.kt create mode 100644 app/src/main/res/values/strings.xml create mode 100644 gradle/convention/gradle.properties create mode 100644 gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt create mode 100644 gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt create mode 100644 gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt create mode 100644 gradle/convention/src/main/kotlin/KotlinConvention.kt rename gradle/convention/src/main/kotlin/{justjanne.android.signing.gradle.kts => SigningData.kt} (51%) delete mode 100644 gradle/convention/src/main/kotlin/justjanne.android.app.gradle.kts delete mode 100644 gradle/convention/src/main/kotlin/justjanne.android.library.gradle.kts delete mode 100644 gradle/convention/src/main/kotlin/justjanne.java.gradle.kts delete mode 100644 gradle/convention/src/main/kotlin/justjanne.kotlin.android.gradle.kts delete mode 100644 gradle/convention/src/main/kotlin/justjanne.kotlin.gradle.kts delete mode 100644 gradle/convention/src/main/kotlin/justjanne.repositories.gradle.kts create mode 100644 gradle/convention/src/main/kotlin/util/BaseExtensionExtensions.kt create mode 100644 gradle/convention/src/main/kotlin/util/ProjectExtensions.kt create mode 100644 gradle/init.gradle.kts create mode 100644 gradle/spotless/copyright.kt create mode 100644 gradle/spotless/copyright.kts create mode 100644 gradle/spotless/copyright.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f58f94ada..1ad4f5d11 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,3 +1,5 @@ +@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) diff --git a/app/src/debug/res/values/colors.xml b/app/src/debug/res/values/colors.xml new file mode 100644 index 000000000..90c54e64f --- /dev/null +++ b/app/src/debug/res/values/colors.xml @@ -0,0 +1,27 @@ +<?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> diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index fed896206..b7d1d488d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -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" diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/MainActivity.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/MainActivity.kt index e3fc55316..4112677fc 100644 --- a/app/src/main/kotlin/de/justjanne/quasseldroid/MainActivity.kt +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/MainActivity.kt @@ -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) diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageBuffer.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageBuffer.kt new file mode 100644 index 000000000..6df763cce --- /dev/null +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageBuffer.kt @@ -0,0 +1,12 @@ +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> +) diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageStore.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageStore.kt new file mode 100644 index 000000000..1ec026106 --- /dev/null +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/messages/MessageStore.kt @@ -0,0 +1,104 @@ +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() + } + } +} diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/service/ClientSessionWrapper.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/service/ClientSessionWrapper.kt new file mode 100644 index 000000000..428e82c52 --- /dev/null +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/service/ClientSessionWrapper.kt @@ -0,0 +1,9 @@ +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 +) diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/service/QuasselBackend.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/service/QuasselBackend.kt index 5d8ebb5b0..870249176 100644 --- a/app/src/main/kotlin/de/justjanne/quasseldroid/service/QuasselBackend.kt +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/service/QuasselBackend.kt @@ -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 { diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/ConnectedClientCard.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/ConnectedClientCard.kt index ac44730d0..2f90b1afb 100644 --- a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/ConnectedClientCard.kt +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/ConnectedClientCard.kt @@ -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 diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/CoreInfoView.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/CoreInfoView.kt index f0c494704..67bfe0336 100644 --- a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/CoreInfoView.kt +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/CoreInfoView.kt @@ -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 ) diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/MessageList.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/MessageList.kt new file mode 100644 index 000000000..c5426e5ef --- /dev/null +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/MessageList.kt @@ -0,0 +1,97 @@ +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) +} diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/PasswordTextField.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/PasswordTextField.kt index 3e4d10bec..a16592f4d 100644 --- a/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/PasswordTextField.kt +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/ui/components/PasswordTextField.kt @@ -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 diff --git a/app/src/main/kotlin/de/justjanne/quasseldroid/util/FlowExtensions.kt b/app/src/main/kotlin/de/justjanne/quasseldroid/util/FlowExtensions.kt index 9463f4a9d..c1da7450c 100644 --- a/app/src/main/kotlin/de/justjanne/quasseldroid/util/FlowExtensions.kt +++ b/app/src/main/kotlin/de/justjanne/quasseldroid/util/FlowExtensions.kt @@ -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 } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..3aaa4b660 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ +<?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> diff --git a/build.gradle.kts b/build.gradle.kts index a1699feb9..a66882dff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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 +} diff --git a/gradle.properties b/gradle.properties index 661a00443..de22634d3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -31,4 +31,3 @@ org.gradle.jvmargs=-Xmx2048m #org.gradle.caching=true # Enable AndroidX android.useAndroidX=true -android.enableJetifier=true diff --git a/gradle/convention/build.gradle.kts b/gradle/convention/build.gradle.kts index a1449bfda..8b4b26985 100644 --- a/gradle/convention/build.gradle.kts +++ b/gradle/convention/build.gradle.kts @@ -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)) } } diff --git a/gradle/convention/gradle.properties b/gradle/convention/gradle.properties new file mode 100644 index 000000000..c6cd2a7e2 --- /dev/null +++ b/gradle/convention/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.configureondemand=true diff --git a/gradle/convention/gradle/wrapper/gradle-wrapper.properties b/gradle/convention/gradle/wrapper/gradle-wrapper.properties index aa5206271..1ffa60b52 100644 --- a/gradle/convention/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/convention/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ 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 diff --git a/gradle/convention/settings.gradle.kts b/gradle/convention/settings.gradle.kts index 6ef6296c7..5bdbcb21e 100644 --- a/gradle/convention/settings.gradle.kts +++ b/gradle/convention/settings.gradle.kts @@ -1 +1,15 @@ +@file:Suppress("UnstableApiUsage") + rootProject.name = "convention" + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { + create("libs") { + from(files("../libs.versions.toml")) + } + } +} diff --git a/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt b/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt new file mode 100644 index 000000000..fd3b7f94a --- /dev/null +++ b/gradle/convention/src/main/kotlin/AndroidApplicationConvention.kt @@ -0,0 +1,71 @@ +import com.android.build.api.dsl.ApplicationExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import util.buildConfigField +import util.cmd +import util.properties +import java.util.* + +@Suppress("UnstableApiUsage") +class AndroidApplicationConvention : Plugin<Project> { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.application") + apply("justjanne.kotlin.android") + } + + extensions.configure<ApplicationExtension> { + compileSdk = 33 + + defaultConfig { + minSdk = 21 + targetSdk = 33 + + applicationId = "${rootProject.group}.${rootProject.name.lowercase(Locale.ROOT)}" + versionCode = cmd("git", "rev-list", "--count", "HEAD")?.toIntOrNull() ?: 1 + versionName = cmd("git", "describe", "--always", "--tags", "HEAD") ?: "1.0.0" + + buildConfigField("GIT_HEAD", + cmd("git", "rev-parse", "HEAD") ?: "") + buildConfigField("GIT_COMMIT_DATE", + cmd("git", "show", "-s", "--format=%ct")?.toLongOrNull() ?: 0L) + + signingConfig = signingConfigs.findByName("default") + + setProperty("archivesBaseName", "${rootProject.name}-$versionName") + + // Disable test runner analytics + testInstrumentationRunnerArguments["disableAnalytics"] = "true" + } + + signingConfigs { + SigningData.of(project.rootProject.properties("signing.properties"))?.let { + create("default") { + storeFile = file(it.storeFile) + storePassword = it.storePassword + keyAlias = it.keyAlias + keyPassword = it.keyPassword + } + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + testOptions { + unitTests.isIncludeAndroidResources = true + } + + lint { + warningsAsErrors = true + lintConfig = file("../lint.xml") + } + } + } + } +} diff --git a/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt b/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt new file mode 100644 index 000000000..8aa23650e --- /dev/null +++ b/gradle/convention/src/main/kotlin/AndroidLibraryConvention.kt @@ -0,0 +1,35 @@ +import com.android.build.api.dsl.LibraryExtension +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.configure +import java.util.* + +@Suppress("UnstableApiUsage") +class AndroidLibraryConvention : Plugin<Project> { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("com.android.library") + apply("justjanne.kotlin.android") + } + + extensions.configure<LibraryExtension> { + compileSdk = 33 + + defaultConfig { + minSdk = 21 + + consumerProguardFiles("proguard-rules.pro") + + // Disable test runner analytics + testInstrumentationRunnerArguments["disableAnalytics"] = "true" + } + + lint { + warningsAsErrors = true + lintConfig = file("../lint.xml") + } + } + } + } +} diff --git a/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt b/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt new file mode 100644 index 000000000..c81117d81 --- /dev/null +++ b/gradle/convention/src/main/kotlin/KotlinAndroidConvention.kt @@ -0,0 +1,49 @@ +import com.android.build.gradle.BaseExtension +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.testing.Test +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.withType +import util.kotlinOptions + +@Suppress("UnstableApiUsage") +class KotlinAndroidConvention : Plugin<Project> { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("org.jetbrains.kotlin.android") + apply("org.jetbrains.kotlin.kapt") + apply("com.google.devtools.ksp") + } + + extensions.configure<BaseExtension> { + kotlinOptions { + freeCompilerArgs = freeCompilerArgs + listOf( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.ExperimentalUnsignedTypes", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.coroutines.FlowPreview", + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + "-opt-in=androidx.paging.ExperimentalPagingApi", + "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", + ) + + jvmTarget = JavaVersion.VERSION_11.toString() + } + } + + tasks.withType<Test> { + useJUnitPlatform() + } + + configure<JavaPluginExtension> { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } + } + } + } +} diff --git a/gradle/convention/src/main/kotlin/KotlinConvention.kt b/gradle/convention/src/main/kotlin/KotlinConvention.kt new file mode 100644 index 000000000..8e78d83d4 --- /dev/null +++ b/gradle/convention/src/main/kotlin/KotlinConvention.kt @@ -0,0 +1,45 @@ +import org.gradle.api.JavaVersion +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.testing.Test +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.dependencies +import org.gradle.kotlin.dsl.kotlin +import org.gradle.kotlin.dsl.withType +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions + +@Suppress("UnstableApiUsage") +class KotlinConvention : Plugin<Project> { + override fun apply(target: Project) { + with(target) { + with(pluginManager) { + apply("org.jetbrains.kotlin.jvm") + apply("org.jetbrains.kotlin.kapt") + apply("com.google.devtools.ksp") + } + + extensions.configure<KotlinJvmOptions> { + freeCompilerArgs = freeCompilerArgs + listOf( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=kotlin.ExperimentalUnsignedTypes", + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", + "-opt-in=kotlinx.coroutines.FlowPreview", + ) + + jvmTarget = JavaVersion.VERSION_11.toString() + } + + tasks.withType<Test> { + useJUnitPlatform() + } + + configure<JavaPluginExtension> { + toolchain { + languageVersion.set(JavaLanguageVersion.of(11)) + } + } + } + } +} diff --git a/gradle/convention/src/main/kotlin/justjanne.android.signing.gradle.kts b/gradle/convention/src/main/kotlin/SigningData.kt similarity index 51% rename from gradle/convention/src/main/kotlin/justjanne.android.signing.gradle.kts rename to gradle/convention/src/main/kotlin/SigningData.kt index deb2731f9..606738eec 100644 --- a/gradle/convention/src/main/kotlin/justjanne.android.signing.gradle.kts +++ b/gradle/convention/src/main/kotlin/SigningData.kt @@ -1,35 +1,5 @@ import java.util.* -plugins { - id("com.android.application") -} - -android { - signingConfigs { - SigningData.of(project.rootProject.properties("signing.properties"))?.let { - create("default") { - storeFile = file(it.storeFile) - storePassword = it.storePassword - keyAlias = it.keyAlias - keyPassword = it.keyPassword - } - } - } - - defaultConfig { - signingConfig = signingConfigs.findByName("default") - } -} - -fun Project.properties(fileName: String): Properties? { - val file = file(fileName) - if (!file.exists()) - return null - val props = Properties() - props.load(file.inputStream()) - return props -} - data class SigningData( val storeFile: String, val storePassword: String, diff --git a/gradle/convention/src/main/kotlin/justjanne.android.app.gradle.kts b/gradle/convention/src/main/kotlin/justjanne.android.app.gradle.kts deleted file mode 100644 index 08512a21a..000000000 --- a/gradle/convention/src/main/kotlin/justjanne.android.app.gradle.kts +++ /dev/null @@ -1,95 +0,0 @@ -import java.io.ByteArrayOutputStream -import java.util.* - -plugins { - id("com.android.application") - id("justjanne.kotlin.android") -} - -@Suppress("UnstableApiUsage") -android { - compileSdk = 31 - - defaultConfig { - minSdk = 21 - targetSdk = 31 - - applicationId = "${rootProject.group}.${rootProject.name.toLowerCase(Locale.ROOT)}" - versionCode = cmd("git", "rev-list", "--count", "HEAD")?.toIntOrNull() ?: 1 - versionName = cmd("git", "describe", "--always", "--tags", "HEAD") ?: "1.0.0" - - buildConfigField("String", "GIT_HEAD", "\"${cmd("git", "rev-parse", "HEAD") ?: ""}\"") - buildConfigField("String", "FANCY_VERSION_NAME", "\"${fancyVersionName() ?: ""}\"") - buildConfigField("long", "GIT_COMMIT_DATE", "${cmd("git", "show", "-s", "--format=%ct") ?: 0}L") - - signingConfig = signingConfigs.findByName("default") - - setProperty("archivesBaseName", "${rootProject.name}-$versionName") - - // Disable test runner analytics - testInstrumentationRunnerArguments["disableAnalytics"] = "true" - } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - testOptions { - unitTests.isIncludeAndroidResources = true - } - - lint { - warningsAsErrors = true - lintConfig = file("../lint.xml") - } -} - -fun Project.fancyVersionName(): String? { - val commit = cmd("git", "rev-parse", "HEAD") - val name = cmd("git", "describe", "--always", "--tags", "HEAD") - - return if (commit != null && name != null) "<a href=\\\"https://git.kuschku.de/justJanne/QuasselDroid-ng/commit/$commit\\\">$name</a>" - else name -} - -fun Project.cmd(vararg command: String) = try { - val stdOut = ByteArrayOutputStream() - exec { - commandLine(*command) - standardOutput = stdOut - } - stdOut.toString(Charsets.UTF_8.name()).trim() -} catch (e: Throwable) { - e.printStackTrace() - null -} - -fun Project.properties(fileName: String): Properties? { - val file = file(fileName) - if (!file.exists()) - return null - val props = Properties() - props.load(file.inputStream()) - return props -} - -data class SigningData( - val storeFile: String, - val storePassword: String, - val keyAlias: String, - val keyPassword: String -) { - companion object { - fun of(properties: Properties?): SigningData? { - if (properties == null) return null - - val storeFile = properties.getProperty("storeFile") ?: return null - val storePassword = properties.getProperty("storePassword") ?: return null - val keyAlias = properties.getProperty("keyAlias") ?: return null - val keyPassword = properties.getProperty("keyPassword") ?: return null - - return SigningData(storeFile, storePassword, keyAlias, keyPassword) - } - } -} diff --git a/gradle/convention/src/main/kotlin/justjanne.android.library.gradle.kts b/gradle/convention/src/main/kotlin/justjanne.android.library.gradle.kts deleted file mode 100644 index 77d3ce9a5..000000000 --- a/gradle/convention/src/main/kotlin/justjanne.android.library.gradle.kts +++ /dev/null @@ -1,24 +0,0 @@ -plugins { - id("com.android.library") - id("justjanne.kotlin.android") -} - -@Suppress("UnstableApiUsage") -android { - compileSdk = 31 - - defaultConfig { - minSdk = 21 - targetSdk = 31 - - consumerProguardFiles("proguard-rules.pro") - - // Disable test runner analytics - testInstrumentationRunnerArguments["disableAnalytics"] = "true" - } - - lint { - warningsAsErrors = true - lintConfig = file("../lint.xml") - } -} diff --git a/gradle/convention/src/main/kotlin/justjanne.java.gradle.kts b/gradle/convention/src/main/kotlin/justjanne.java.gradle.kts deleted file mode 100644 index ac5f5d66c..000000000 --- a/gradle/convention/src/main/kotlin/justjanne.java.gradle.kts +++ /dev/null @@ -1,23 +0,0 @@ -import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.testImplementation -import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.testRuntimeOnly - -plugins { - java - id("justjanne.repositories") -} - -dependencies { - testImplementation("org.junit.jupiter", "junit-jupiter-api", "5.8.2") - testImplementation("org.junit.jupiter", "junit-jupiter-params", "5.8.2") - testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine") -} - -configure<JavaPluginExtension> { - toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) - } -} - -tasks.withType<Test> { - useJUnitPlatform() -} diff --git a/gradle/convention/src/main/kotlin/justjanne.kotlin.android.gradle.kts b/gradle/convention/src/main/kotlin/justjanne.kotlin.android.gradle.kts deleted file mode 100644 index 3f663fe8b..000000000 --- a/gradle/convention/src/main/kotlin/justjanne.kotlin.android.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.implementation -import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.testImplementation -import gradle.kotlin.dsl.accessors._9f9f63157b527b37420ecbe9e569524a.testRuntimeOnly -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("justjanne.repositories") - id("com.google.devtools.ksp") - kotlin("android") - kotlin("kapt") -} - -dependencies { - 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") - - testImplementation("org.junit.jupiter", "junit-jupiter-api", "5.8.2") - testImplementation("org.junit.jupiter", "junit-jupiter-params", "5.8.2") - testRuntimeOnly("org.junit.jupiter", "junit-jupiter-engine") - - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.6.10") -} - -tasks.withType<KotlinCompile> { - kotlinOptions { - freeCompilerArgs = listOf( - "-opt-in=kotlin.ExperimentalUnsignedTypes", - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", - "-opt-in=androidx.paging.ExperimentalPagingApi", - ) - jvmTarget = "1.8" - } -} - -tasks.withType<Test> { - useJUnitPlatform() -} - -configure<JavaPluginExtension> { - toolchain { - languageVersion.set(JavaLanguageVersion.of(8)) - } -} diff --git a/gradle/convention/src/main/kotlin/justjanne.kotlin.gradle.kts b/gradle/convention/src/main/kotlin/justjanne.kotlin.gradle.kts deleted file mode 100644 index 2e1834c8f..000000000 --- a/gradle/convention/src/main/kotlin/justjanne.kotlin.gradle.kts +++ /dev/null @@ -1,29 +0,0 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -plugins { - id("justjanne.java") - id("com.google.devtools.ksp") - kotlin("jvm") - kotlin("kapt") -} - -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") - - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:1.6.10") -} - -tasks.withType<KotlinCompile> { - kotlinOptions { - freeCompilerArgs = listOf( - "-opt-in=kotlin.ExperimentalUnsignedTypes", - "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", - "-opt-in=androidx.compose.ui.ExperimentalComposeUiApi", - "-opt-in=androidx.paging.ExperimentalPagingApi", - ) - jvmTarget = "1.8" - } -} diff --git a/gradle/convention/src/main/kotlin/justjanne.repositories.gradle.kts b/gradle/convention/src/main/kotlin/justjanne.repositories.gradle.kts deleted file mode 100644 index 01861dc99..000000000 --- a/gradle/convention/src/main/kotlin/justjanne.repositories.gradle.kts +++ /dev/null @@ -1,5 +0,0 @@ -repositories { - mavenCentral() - google() - maven(url = "https://jitpack.io") -} diff --git a/gradle/convention/src/main/kotlin/util/BaseExtensionExtensions.kt b/gradle/convention/src/main/kotlin/util/BaseExtensionExtensions.kt new file mode 100644 index 000000000..667f8dac0 --- /dev/null +++ b/gradle/convention/src/main/kotlin/util/BaseExtensionExtensions.kt @@ -0,0 +1,8 @@ +package util + +import com.android.build.gradle.BaseExtension +import org.gradle.api.plugins.ExtensionAware +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions + +fun BaseExtension.kotlinOptions(configure: KotlinJvmOptions.() -> Unit): Unit = + (this as ExtensionAware).extensions.configure("kotlinOptions", configure) diff --git a/gradle/convention/src/main/kotlin/util/ProjectExtensions.kt b/gradle/convention/src/main/kotlin/util/ProjectExtensions.kt new file mode 100644 index 000000000..1f0f0309e --- /dev/null +++ b/gradle/convention/src/main/kotlin/util/ProjectExtensions.kt @@ -0,0 +1,79 @@ +package util + +import com.android.build.api.dsl.VariantDimension +import org.gradle.api.Incubating +import org.gradle.api.Project +import java.io.ByteArrayOutputStream +import java.util.* + +fun Project.cmd(vararg command: String) = try { + val stdOut = ByteArrayOutputStream() + exec { + commandLine(*command) + standardOutput = stdOut + } + stdOut.toString(Charsets.UTF_8.name()).trim() +} catch (e: Throwable) { + e.printStackTrace() + null +} + +@Suppress("UnstableApiUsage") +@Incubating +inline fun <reified T> VariantDimension.buildConfigField(key: String, value: T) { + when (value) { + is String -> this.buildConfigField( + "String", + key, + "\"%s\"".format(value.replace("""\""", """\\""") + .replace(""""""", """\"""")) + ) + is Long -> this.buildConfigField( + "long", + key, + "%dL".format(value) + ) + is Int -> this.buildConfigField( + "int", + key, + "%d".format(value) + ) + is Short -> this.buildConfigField( + "short", + key, + "%d".format(value) + ) + is Byte -> this.buildConfigField( + "byte", + key, + "%d".format(value) + ) + is Char -> this.buildConfigField( + "char", + key, + "'%s'".format(value) + ) + is Double -> this.buildConfigField( + "double", + key, + "%.f".format(value) + ) + is Float -> this.buildConfigField( + "float", + key, + "%.ff".format(value) + ) + else -> throw IllegalArgumentException( + "build config cannot contain values of type " + T::class.java.canonicalName + ) + } +} + +fun Project.properties(fileName: String): Properties? { + val file = file(fileName) + if (!file.exists()) + return null + val props = Properties() + props.load(file.inputStream()) + return props +} diff --git a/gradle/init.gradle.kts b/gradle/init.gradle.kts new file mode 100644 index 000000000..7f161bac4 --- /dev/null +++ b/gradle/init.gradle.kts @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +val ktlintVersion = "0.48.1" + +initscript { + val spotlessVersion = "6.13.0" + + repositories { + mavenCentral() + } + + dependencies { + classpath("com.diffplug.spotless:spotless-plugin-gradle:$spotlessVersion") + } +} + +rootProject { + subprojects { + apply<com.diffplug.gradle.spotless.SpotlessPlugin>() + extensions.configure<com.diffplug.gradle.spotless.SpotlessExtension> { + kotlin { + target("**/*.kt") + targetExclude("**/build/**/*.kt") + ktlint(ktlintVersion).userData(mapOf("android" to "true")) + licenseHeaderFile(rootProject.file("gradle/spotless/copyright.kt")) + } + format("kts") { + target("**/*.kts") + targetExclude("**/build/**/*.kts") + // Look for the first line that doesn't have a block comment (assumed to be the license) + licenseHeaderFile(rootProject.file("gradle/spotless/copyright.kts"), "(^(?![\\/ ]\\*).*$)") + } + format("xml") { + target("**/*.xml") + targetExclude("**/build/**/*.xml") + // Look for the first XML tag that isn't a comment (<!--) or the xml declaration (<?xml) + licenseHeaderFile(rootProject.file("gradle/spotless/copyright.xml"), "(<[^!?])") + } + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index aacaa480a..08701b927 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,14 +1,25 @@ [versions] -libquassel = "0.10.1" -androidx-collection = "1.2.0" -androidx-core = "1.7.0" +libquassel = "0.10.2" +androidGradlePlugin = "7.4.2" androidx-activity = "1.4.0" androidx-appcompat = "1.4.1" -androidx-compose = "1.1.1" -androidx-material3 = "1.0.0-alpha05" -androidx-navigation = "2.4.1" -androidx-paging = "3.1.0" -androidx-room = "2.5.0-alpha01" +androidx-compose-bom = "2023.03.00" +androidx-compose-compiler = "1.4.1" +androidx-compose-material = "1.4.0" +androidx-compose-material3 = "1.1.0-alpha06" +androidx-compose-runtimetracing = "1.0.0-alpha01" +androidx-compose-tooling = "1.4.0" +androidx-collection = "1.2.0" +androidx-navigation = "2.5.3" +androidx-paging = "3.1.1" +androidx-room = "2.5.1" +kotlin = "1.8.0" +kotlinxCoroutines = "1.6.4" +kotlinxDatetime = "0.4.0" +kotlinxSerializationJson = "1.4.1" +ksp = "1.8.0-1.0.9" +junit = "5.8.2" +spotless = "6.7.0" [libraries] libquassel-protocol = { module = "de.justjanne.libquassel:libquassel-protocol", version.ref = "libquassel" } @@ -21,30 +32,61 @@ androidx-activity-compose = { module = "androidx.activity:activity-compose", ver androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-appcompat-resources = { module = "androidx.appcompat:appcompat-resources", version.ref = "androidx-appcompat" } -androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "androidx-compose" } -androidx-compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "androidx-compose" } -androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidx-compose" } -#androidx-compose-material = { module = "androidx.compose.material:material", version.ref = "androidx-compose" } -androidx-compose-material = { module = "androidx.compose.material3:material3", version.ref = "androidx-material3" } -androidx-compose-material-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "androidx-compose" } -androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "androidx-compose" } -androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidx-compose" } -androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "androidx-compose" } -androidx-compose-ui-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "androidx-compose" } -androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test", version.ref = "androidx-compose" } +androidx-compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "androidx-compose-compiler" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidx-compose-bom" } +androidx-compose-animation = { group = "androidx.compose.animation", name = "animation" } +androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation" } +androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout" } +androidx-compose-material-icons = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "androidx-compose-material3" } +androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "androidx-compose-material3" } +androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "androidx-compose-material" } +androidx-compose-material-windowSizeClass = { group = "androidx.compose.material3", name = "material3-window-size-class", version.ref = "androidx-compose-material3" } +androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime" } +androidx-compose-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata" } +androidx-compose-runtime-tracing = { group = "androidx.compose.runtime", name = "runtime-tracing", version.ref = "androidx-compose-runtimetracing" } +androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test" } +androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidx-compose-tooling" } +androidx-compose-ui-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview", version.ref = "androidx-compose-tooling" } +androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util" } -androidx-collection-ktx = { module = "androidx.collection:collection-ktx", version.ref = "androidx-collection" } -androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } +androidx-collection-ktx = { group = "androidx.collection", name = "collection-ktx", version.ref = "androidx-collection" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx" } androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "androidx-paging" } androidx-paging-test = { module = "androidx.paging:paging-common", version.ref = "androidx-paging" } -androidx-paging-compose = { module = "androidx.paging:paging-compose", version = "1.0.0-alpha14" } +androidx-paging-compose = { module = "androidx.paging:paging-compose", version = "1.0.0-alpha18" } androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } androidx-room-paging = { module = "androidx.room:room-paging", version.ref = "androidx-room" } -compose-htmltext = { module = "de.charlex.compose:html-text", version = "1.1.0" } +#compose-htmltext = { module = "de.charlex.compose:html-text", version = "1.1.0" } + +junit-api = { group = "org.junit.jupiter", name = "junit-jupiter-api", version.ref = "junit" } +junit-params = { group = "org.junit.jupiter", name = "junit-jupiter-params", version.ref = "junit" } +junit-engine = { group = "org.junit.jupiter", name = "junit-jupiter-engine" } + +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit5", version.ref = "kotlin" } +kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" } +kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } +kotlinx-datetime = { group = "org.jetbrains.kotlinx", name = "kotlinx-datetime", version.ref = "kotlinxDatetime" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } + +# Dependencies of the included build-logic +android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" } +kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } +ksp-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +spotless = { id = "com.diffplug.spotless", version.ref = "spotless" } diff --git a/gradle/spotless/copyright.kt b/gradle/spotless/copyright.kt new file mode 100644 index 000000000..c4b5f2b20 --- /dev/null +++ b/gradle/spotless/copyright.kt @@ -0,0 +1,18 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) $YEAR Janne Mareike Koschinski + * Copyright (c) $YEAR 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/>. + */ diff --git a/gradle/spotless/copyright.kts b/gradle/spotless/copyright.kts new file mode 100644 index 000000000..c4b5f2b20 --- /dev/null +++ b/gradle/spotless/copyright.kts @@ -0,0 +1,18 @@ +/* + * Quasseldroid - Quassel client for Android + * + * Copyright (c) $YEAR Janne Mareike Koschinski + * Copyright (c) $YEAR 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/>. + */ diff --git a/gradle/spotless/copyright.xml b/gradle/spotless/copyright.xml new file mode 100644 index 000000000..d786382b5 --- /dev/null +++ b/gradle/spotless/copyright.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Quasseldroid - Quassel client for Android + + Copyright (c) $YEAR Janne Mareike Koschinski + Copyright (c) $YEAR 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/>. +--> diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa5206271..1ffa60b52 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ 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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 81fea8fd3..368d018e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,18 +16,25 @@ * You should have received a copy of the GNU General Public License along * with this program. If not, see <http://www.gnu.org/licenses/>. */ -enableFeaturePreview("VERSION_CATALOGS") +@file:Suppress("UnstableApiUsage") rootProject.name = "Quasseldroid" -includeBuild("gradle/convention") - -include(":app") - pluginManagement { + includeBuild("gradle/convention") repositories { + google() + mavenCentral() gradlePluginPortal() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { google() mavenCentral() } } + +include(":app") -- GitLab