Merge branch 'main' into release/2.7.0

This commit is contained in:
James Rich
2025-08-26 18:50:37 -05:00
committed by GitHub
16 changed files with 103 additions and 96 deletions

View File

@@ -161,6 +161,13 @@ jobs:
path: app/build/outputs/apk/google/release/app-google-release.apk
retention-days: 1
- name: Upload Mapping File
uses: actions/upload-artifact@v4
with:
name: mapping
path: app/build/outputs/mapping/googleRelease/mapping.txt
retention-days: 1
publish-release:
runs-on: ubuntu-latest
needs: [prepare-build-info, build-fdroid, build-google]
@@ -194,11 +201,16 @@ jobs:
name: google-apk
path: ./build-artifacts/google/apk
- name: Download Mapping File
uses: actions/download-artifact@v5
with:
name: mapping
path: ./build-artifacts/google/mapping
- name: Generate Changelog
id: generate_changelog
uses: mikepenz/release-changelog-builder-action@v5
with:
configuration: ".github/changelog-config.json"
owner: ${{ github.repository_owner }}
repo: ${{ github.event.repository.name }}
ignorePreReleases: true
@@ -211,8 +223,7 @@ jobs:
if: steps.generate_changelog.outputs.changelog != ''
run: |
mkdir -p play_store_release_notes/en-US
echo "${{ steps.generate_changelog.outputs.changelog }}" > play_store_release_notes/en-US/default.txt
echo "${{ steps.generate_changelog.outputs.changelog }}" > changelog.txt # Also create a root changelog.txt for GitHub release
echo "${{ steps.generate_changelog.outputs.changelog }}" > whatsnew/whatsnew-en-US/default.txt
- name: Create version_info.txt
run: |
@@ -225,7 +236,7 @@ jobs:
with:
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
body: ${{ steps.generate_changelog.outputs.changelog }}
generate_release_notes: true
files: |
./build-artifacts/google/bundle/app-google-release.aab
./build-artifacts/google/apk/app-google-release.apk
@@ -269,5 +280,6 @@ jobs:
packageName: com.geeksville.mesh
releaseFiles: ./build-artifacts/google/bundle/app-google-release.aab
track: ${{ steps.get_track.outputs.track }}
status: 'draft'
whatsNewDirectory: ./play_store_release_notes/en-US/
status: ${{ steps.get_track.outputs.track == 'internal' && 'complete' || 'draft' }}
whatsNewDirectory: /whatsnew/
mappingFile: ./build-artifacts/google/mapping/mapping.txt

View File

@@ -85,14 +85,6 @@ jobs:
path: app/build/outputs/apk/google/debug/app-google-debug.apk
retention-days: 14
- name: Attest Build Provenance
if: ${{ inputs.upload_artifacts && github.ref_name == 'main' && github.repository == 'meshtastic/Meshtastic-Android' }}
uses: actions/attest-build-provenance@v2
with:
subject-path: |
app/build/outputs/apk/google/debug/app-google-debug.apk
app/build/outputs/apk/fdroid/debug/app-fdroid-debug.apk
- name: Upload reports
if: ${{ inputs.upload_artifacts }}
uses: actions/upload-artifact@v4

View File

@@ -31,7 +31,7 @@ plugins {
alias(libs.plugins.devtools.ksp)
alias(libs.plugins.detekt)
alias(libs.plugins.datadog)
alias(libs.plugins.secrets.gradle.plugin)
alias(libs.plugins.secrets)
alias(libs.plugins.spotless)
}
@@ -293,8 +293,6 @@ ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}
repositories { maven { url = uri("https://jitpack.io") } }
detekt {
config.setFrom("../config/detekt/detekt.yml")
baseline = file("../config/detekt/detekt-baseline.xml")

View File

@@ -14,7 +14,7 @@
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
@@ -27,9 +27,6 @@
# eclipse.paho.client
-keep class org.eclipse.paho.client.mqttv3.logging.JSR47Logger { *; }
# ormlite
-dontwarn com.j256.ormlite.**
# OkHttp
-dontwarn okhttp3.internal.platform.**
-dontwarn org.conscrypt.**
@@ -37,7 +34,6 @@
-dontwarn org.openjsse.**
# ?
-dontwarn java.awt.image.**
-dontwarn java.lang.reflect.**
-dontwarn com.google.errorprone.annotations.**

View File

@@ -20,7 +20,8 @@ package com.geeksville.mesh.repository.radio
import com.geeksville.mesh.android.Logging
/**
* An interface that assumes we are talking to a meshtastic device over some sort of stream connection (serial or TCP probably)
* An interface that assumes we are talking to a meshtastic device over some sort of stream connection (serial or TCP
* probably)
*/
abstract class StreamInterface(protected val service: RadioInterfaceService) :
Logging,
@@ -46,12 +47,16 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
onDeviceDisconnect(true)
}
/** Tell MeshService our device has gone away, but wait for it to come back
/**
* Tell MeshService our device has gone away, but wait for it to come back
*
* @param waitForStopped if true we should wait for the manager to finish - must be false if called from inside the manager callbacks
* */
* @param waitForStopped if true we should wait for the manager to finish - must be false if called from inside the
* manager callbacks
*/
protected open fun onDeviceDisconnect(waitForStopped: Boolean) {
service.onDisconnect(isPermanent = true) // if USB device disconnects it is definitely permanently gone, not sleeping)
service.onDisconnect(
isPermanent = true,
) // if USB device disconnects it is definitely permanently gone, not sleeping)
}
protected open fun connect() {
@@ -84,15 +89,13 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
/** Print device serial debug output somewhere */
private fun debugOut(b: Byte) {
when (val c = b.toChar()) {
'\r' -> {
} // ignore
when (val c = b.toInt().toChar()) {
'\r' -> {} // ignore
'\n' -> {
debug("DeviceLog: $debugLineBuf")
debugLineBuf.clear()
}
else ->
debugLineBuf.append(c)
else -> debugLineBuf.append(c)
}
}
@@ -133,16 +136,19 @@ abstract class StreamInterface(protected val service: RadioInterfaceService) :
// We've read our header, do one big read for the packet itself
packetLen = (msb shl 8) or lsb
if (packetLen > MAX_TO_FROM_RADIO_SIZE) {
lostSync() // If packet len is too long, the bytes must have been corrupted, start looking for START1 again
lostSync() // If packet len is too long, the bytes must have been corrupted, start looking for
// START1 again
} else if (packetLen == 0) {
deliverPacket() // zero length packets are valid and should be delivered immediately (because there won't be a next byte of payload)
deliverPacket() // zero length packets are valid and should be delivered immediately (because there
// won't be a next byte of payload)
}
}
else -> {
// We are looking at the packet bytes now
rxPacket[ptr - 4] = c
// Note: we have to check if ptr +1 is equal to packet length (for example, for a 1 byte packetlen, this code will be run with ptr of4
// Note: we have to check if ptr +1 is equal to packet length (for example, for a 1 byte packetlen, this
// code will be run with ptr of4
if (ptr - 4 + 1 == packetLen) {
deliverPacket()
}

View File

@@ -41,6 +41,7 @@ import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
@@ -268,7 +269,8 @@ fun MainScreen(
item(
icon = {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
positionProvider =
TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(

View File

@@ -162,7 +162,7 @@ fun IndoorAirQuality(iaq: Int?, displayMode: IaqDisplayMode = IaqDisplayMode.Pil
IaqDisplayMode.Gauge -> {
CircularProgressIndicator(
progress = iaq / 500f,
progress = { iaq / 500f },
modifier = Modifier.size(60.dp).clickable { isLegendOpen = true },
strokeWidth = 8.dp,
color = iaqEnum.color,
@@ -176,7 +176,7 @@ fun IndoorAirQuality(iaq: Int?, displayMode: IaqDisplayMode = IaqDisplayMode.Pil
modifier = Modifier.clickable { isLegendOpen = true },
) {
LinearProgressIndicator(
progress = iaq / 500f,
progress = { iaq / 500f },
modifier = Modifier.fillMaxWidth().height(20.dp),
color = iaqEnum.color,
)

View File

@@ -23,6 +23,7 @@ import android.provider.Settings
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Message
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.outlined.BatteryAlert
import androidx.compose.material.icons.outlined.Message
@@ -61,7 +62,7 @@ internal fun NotificationsScreen(showNextButton: Boolean, onSkip: () -> Unit, on
val features = remember {
listOf(
FeatureUIData(
icon = Icons.Outlined.Message,
icon = Icons.AutoMirrored.Outlined.Message,
titleRes = R.string.incoming_messages,
subtitleRes = R.string.notifications_for_channel_and_direct_messages,
),

View File

@@ -48,6 +48,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Reply
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.automirrored.filled.SpeakerNotes
import androidx.compose.material.icons.filled.ArrowDownward
import androidx.compose.material.icons.filled.ChatBubbleOutline
import androidx.compose.material.icons.filled.Close
@@ -672,7 +673,7 @@ private fun OverFlowMenu(
if (showQuickChat) {
Icons.Default.SpeakerNotesOff
} else {
Icons.Default.SpeakerNotes
Icons.AutoMirrored.Filled.SpeakerNotes
},
contentDescription = quickChatToggleTitle,
)

View File

@@ -31,6 +31,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.PlainTooltip
import androidx.compose.material3.Text
import androidx.compose.material3.TooltipAnchorPosition
import androidx.compose.material3.TooltipBox
import androidx.compose.material3.TooltipDefaults
import androidx.compose.material3.rememberTooltipState
@@ -51,7 +52,7 @@ fun NodeStatusIcons(isThisNode: Boolean, isUnmessageable: Boolean, isFavorite: B
Row(modifier = Modifier.padding(4.dp)) {
if (isThisNode) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = {
PlainTooltip {
Text(
@@ -88,7 +89,7 @@ fun NodeStatusIcons(isThisNode: Boolean, isUnmessageable: Boolean, isFavorite: B
if (isUnmessageable) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text(stringResource(R.string.unmonitored_or_infrastructure)) } },
state = rememberTooltipState(),
) {
@@ -103,7 +104,7 @@ fun NodeStatusIcons(isThisNode: Boolean, isUnmessageable: Boolean, isFavorite: B
}
if (isFavorite && !isThisNode) {
TooltipBox(
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(),
positionProvider = TooltipDefaults.rememberTooltipPositionProvider(TooltipAnchorPosition.Above),
tooltip = { PlainTooltip { Text(stringResource(R.string.favorite)) } },
state = rememberTooltipState(),
) {

View File

@@ -20,7 +20,6 @@ package com.geeksville.mesh.ui.settings.radio.components
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.material3.Divider
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
@@ -103,7 +102,7 @@ fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicke
onItemSelected = { audioInput = audioInput.copy { bitrate = it } },
)
}
item { Divider() }
item { HorizontalDivider() }
item {
EditTextPreference(

View File

@@ -54,7 +54,10 @@ fun <T> PacketResponseStateDialog(state: ResponseState<T>, onDismiss: () -> Unit
label = "progress",
)
Text("%.0f%%".format(progress * 100))
LinearProgressIndicator(progress = progress, modifier = Modifier.fillMaxWidth().padding(top = 8.dp))
LinearProgressIndicator(
progress = { progress },
modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
)
if (state.total == state.completed) onComplete()
}
if (state is ResponseState.Success) {

View File

@@ -654,6 +654,7 @@
<string name="overflow_menu">Überlaufmenü</string>
<string name="uv_lux">UV Lux</string>
<string name="unknown">Unbekannt</string>
<string name="message_device_managed">Dieses Radio wird verwaltet und kann nur durch Fernverwaltung geändert werden.</string>
<string name="clean_node_database_title">Knotendatenbank leeren</string>
<string name="clean_nodes_older_than">Knoten älter als %1$d Tage entfernen</string>
<string name="clean_unknown_nodes">Nur unbekannte Knoten entfernen</string>

View File

@@ -15,37 +15,21 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath(libs.agp)
classpath(libs.kotlin.gradle.plugin)
classpath(libs.kotlin.serialization)
classpath(libs.google.services)
classpath(libs.firebase.crashlytics.gradle)
classpath(libs.secrets.gradle.plugin)
classpath(libs.protobuf.gradle.plugin)
classpath(libs.hilt.android.gradle.plugin)
classpath(libs.secrets.gradle.plugin)
classpath(libs.dd.sdk.android.gradle.plugin)
}
}
plugins {
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.devtools.ksp) apply false
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
alias(libs.plugins.compose) apply false
}
allprojects {
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
alias(libs.plugins.datadog) apply false
alias(libs.plugins.devtools.ksp) apply false
alias(libs.plugins.firebase.crashlytics) apply false
alias(libs.plugins.google.services) apply false
alias(libs.plugins.hilt) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.parcelize) apply false
alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.protobuf) apply false
alias(libs.plugins.secrets) apply false
}
tasks.register<Delete>("clean") {

View File

@@ -57,7 +57,6 @@ spotless = "7.2.1"
[libraries]
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
agp = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }
activity = { group = "androidx.activity", name = "activity" }
actvity-ktx = { group = "androidx.activity", name = "activity-ktx" }
activity-compose = { group = "androidx.activity", name = "activity-compose" }
@@ -88,7 +87,6 @@ core-splashscreen = { group = "androidx.core", name = "core-splashscreen", versi
datastore = { group = "androidx.datastore", name = "datastore", version.ref = "datastore" }
datastore-preferences = { group = "androidx.datastore", name = "datastore-preferences", version.ref = "datastore" }
dd-sdk-android-compose = { group = "com.datadoghq", name = "dd-sdk-android-compose", version.ref = "dd-sdk-android" }
dd-sdk-android-gradle-plugin = { group = "com.datadoghq", name = "dd-sdk-android-gradle-plugin", version.ref = "dd-sdk-android-gradle-plugin" }
dd-sdk-android-logs = { group = "com.datadoghq", name = "dd-sdk-android-logs", version.ref = "dd-sdk-android" }
dd-sdk-android-okhttp = { group = "com.datadoghq", name = "dd-sdk-android-okhttp", version.ref = "dd-sdk-android" }
dd-sdk-android-rum = { group = "com.datadoghq", name = "dd-sdk-android-rum", version.ref = "dd-sdk-android" }
@@ -103,16 +101,11 @@ ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junit-
firebase-analytics = { group = "com.google.firebase", name = "firebase-analytics" }
firebase-bom = { group = "com.google.firebase", name = "firebase-bom", version.ref = "firebase-bom" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics" }
firebase-crashlytics-gradle = { group = "com.google.firebase", name = "firebase-crashlytics-gradle", version.ref = "crashlytics" }
google-services = { group = "com.google.gms", name = "google-services", version.ref = "google-services" }
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-android-gradle-plugin = { group = "com.google.dagger", name = "hilt-android-gradle-plugin", version.ref = "hilt" }
hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
kotlin-gradle-plugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-serialization = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" }
kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines-android" }
kotlinx-coroutines-guava = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-guava", version.ref = "kotlinx-coroutines-android" }
@@ -141,7 +134,6 @@ org-eclipse-paho-client-mqttv3 = { group = "org.eclipse.paho", name = "org.eclip
osmbonuspack = { group = "com.github.MKergall", name = "osmbonuspack", version.ref = "osmbonuspack" }
osmdroid-android = { group = "org.osmdroid", name = "osmdroid-android", version.ref = "osmdroid-android" }
osmdroid-geopackage = { group = "org.osmdroid", name = "osmdroid-geopackage", version.ref = "osmdroid-android" }
protobuf-gradle-plugin = { group = "com.google.protobuf", name = "protobuf-gradle-plugin", version.ref = "protobuf-gradle-plugin" }
protobuf-kotlin = { group = "com.google.protobuf", name = "protobuf-kotlin", version.ref = "protobuf-kotlin" }
protobuf-protoc = { group = "com.google.protobuf", name = "protoc", version.ref = "protobuf-kotlin" }
retrofit2 = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
@@ -150,7 +142,6 @@ room-compiler = { group = "androidx.room", name = "room-compiler", version.ref =
room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" }
room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" }
room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
secrets-gradle-plugin = { group = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", name = "secrets-gradle-plugin", version.ref = "secrets-gradle-plugin" }
streamsupport-minifuture = { group = "net.sourceforge.streamsupport", name = "streamsupport-minifuture", version.ref = "streamsupport-minifuture" }
timber = { group = "com.jakewharton.timber", name = "timber", version.ref = "timber" }
usb-serial-android = { group = "com.github.mik3y", name = "usb-serial-for-android", version.ref = "usb-serial-android" }
@@ -214,20 +205,19 @@ retrofit = ["retrofit2", "retrofit2-kotlin-serialization", "okhttp3", "okhttp3-l
coil = ["coil", "coil-network-core", "coil-network-okhttp", "coil-svg"]
[plugins]
android-application = { id = "com.android.application" }
android-library = { id = "com.android.library" }
android-application = { id = "com.android.application", version.ref = "agp" }
android-library = { id = "com.android.library", version.ref = "agp" }
compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
datadog = { id = "com.datadoghq.dd-sdk-android-gradle-plugin"}
datadog = { id = "com.datadoghq.dd-sdk-android-gradle-plugin", version.ref = "dd-sdk-android-gradle-plugin" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
devtools-ksp = { id = "com.google.devtools.ksp", version.ref = "devtools-ksp" }
hilt = { id = "com.google.dagger.hilt.android" }
kotlin-android = { id = "org.jetbrains.kotlin.android" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics" , version.ref = "crashlytics" }
google-services = { id = "com.google.gms.google-services", version.ref = "google-services" }
hilt = { id = "com.google.dagger.hilt.android" , version.ref = "hilt" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization" }
protobuf = { id = "com.google.protobuf" }
google-services = { id = "com.google.gms.google-services" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics" }
secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin"}
kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
protobuf = { id = "com.google.protobuf", version.ref = "protobuf-gradle-plugin" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secrets-gradle-plugin" }
spotless = { id = "com.diffplug.spotless", version .ref= "spotless" }
secrets = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin" }

View File

@@ -1,3 +1,5 @@
import org.gradle.kotlin.dsl.maven
/*
* Copyright (c) 2025 Meshtastic LLC
*
@@ -18,6 +20,25 @@
include(":app", ":network", ":mesh_service_example")
rootProject.name = "Meshtastic Android"
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
maven { url = uri("https://jitpack.io") }
}
}
@Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
plugins {
id("org.gradle.toolchains.foojay-resolver") version "1.0.0"
id("com.gradle.develocity") version("4.1.1")