refactor: migrate core modules to Kotlin Multiplatform and consolidat… (#4735)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-03-06 16:06:50 -06:00 committed by GitHub
parent f3775a601c
commit cffbd08806
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
265 changed files with 1383 additions and 1340 deletions

View file

@ -43,12 +43,12 @@ import javax.net.ssl.SSLContext
import javax.net.ssl.TrustManager
@Singleton
class MQTTRepository
class MQTTRepositoryImpl
@Inject
constructor(
private val radioConfigRepository: RadioConfigRepository,
private val nodeRepository: NodeRepository,
) {
) : MQTTRepository {
companion object {
/**
@ -67,7 +67,7 @@ constructor(
private var mqttClient: MqttAsyncClient? = null
fun disconnect() {
override fun disconnect() {
Logger.i { "MQTT Disconnected" }
mqttClient?.apply {
if (isConnected) {
@ -78,7 +78,7 @@ constructor(
mqttClient = null
}
val proxyMessageFlow: Flow<MqttClientProxyMessage> = callbackFlow {
override val proxyMessageFlow: Flow<MqttClientProxyMessage> = callbackFlow {
val ownerId = "MeshtasticAndroidMqttProxy-${nodeRepository.myId.value ?: generateClientId()}"
val channelSet = radioConfigRepository.channelSetFlow.first()
val mqttConfig = radioConfigRepository.moduleConfigFlow.first().mqtt
@ -165,7 +165,7 @@ constructor(
}
@Suppress("TooGenericExceptionCaught")
fun publish(topic: String, data: ByteArray, retained: Boolean) {
override fun publish(topic: String, data: ByteArray, retained: Boolean) {
try {
val token = mqttClient?.publish(topic, data, DEFAULT_QOS, retained)
Logger.i { "MQTT Publish messageId: ${token?.messageId}" }

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.repository
import kotlinx.coroutines.flow.Flow
import org.meshtastic.proto.MqttClientProxyMessage
/** Interface defining the MQTT interactions used for proxying messages to and from the mesh. */
interface MQTTRepository {
/** Disconnects the MQTT client and cleans up resources. */
fun disconnect()
/**
* A flow of incoming messages from the subscribed MQTT topics. Connecting/subscribing is initiated when this flow
* is collected.
*/
val proxyMessageFlow: Flow<MqttClientProxyMessage>
/**
* Publishes a message to the given MQTT topic.
*
* @param topic The MQTT topic to publish to.
* @param data The binary payload.
* @param retained Whether the message should be retained by the broker.
*/
fun publish(topic: String, data: ByteArray, retained: Boolean)
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -14,7 +14,6 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.service
import io.ktor.client.HttpClient

View file

@ -1,58 +0,0 @@
/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.meshtastic.core.model.NetworkDeviceHardware
import org.meshtastic.core.model.NetworkFirmwareReleases
import org.meshtastic.core.network.BuildConfig
import org.meshtastic.core.network.service.ApiService
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
class FDroidNetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder()
.addInterceptor(
interceptor =
HttpLoggingInterceptor().apply {
if (BuildConfig.DEBUG) {
setLevel(HttpLoggingInterceptor.Level.BODY)
}
},
)
.build()
@Provides
@Singleton
fun provideApiService(): ApiService = object : ApiService {
override suspend fun getDeviceHardware(): List<NetworkDeviceHardware> =
throw NotImplementedError("API calls to getDeviceHardware are not supported on Fdroid builds.")
override suspend fun getFirmwareReleases(): NetworkFirmwareReleases =
throw NotImplementedError("API calls to getFirmwareReleases are not supported on Fdroid builds.")
}
}

View file

@ -1,69 +0,0 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.di
import android.content.Context
import com.datadog.android.okhttp.DatadogEventListener
import com.datadog.android.okhttp.DatadogInterceptor
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import okhttp3.Cache
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.meshtastic.core.network.BuildConfig
import org.meshtastic.core.network.service.ApiService
import org.meshtastic.core.network.service.ApiServiceImpl
import java.io.File
import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
interface GoogleNetworkModule {
@Binds @Singleton
fun bindApiService(apiServiceImpl: ApiServiceImpl): ApiService
companion object {
@Provides
@Singleton
fun provideOkHttpClient(@ApplicationContext context: Context): OkHttpClient = OkHttpClient.Builder()
.cache(
cache =
Cache(
directory = File(context.applicationContext.cacheDir, "http_cache"),
maxSize = 50L * 1024L * 1024L, // 50 MiB
),
)
.addInterceptor(
interceptor =
HttpLoggingInterceptor().apply {
if (BuildConfig.DEBUG) {
setLevel(HttpLoggingInterceptor.Level.BODY)
}
},
)
.addInterceptor(
interceptor = DatadogInterceptor.Builder(tracedHosts = listOf("meshtastic.org")).build(),
)
.eventListenerFactory(eventListenerFactory = DatadogEventListener.Factory())
.build()
}
}

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View file

@ -1,81 +0,0 @@
/*
* Copyright (c) 2025-2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.network.di
import android.content.Context
import coil3.ImageLoader
import coil3.disk.DiskCache
import coil3.memory.MemoryCache
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.crossfade
import coil3.svg.SvgDecoder
import coil3.util.DebugLogger
import coil3.util.Logger
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient
import org.meshtastic.core.network.BuildConfig
import javax.inject.Singleton
private const val DISK_CACHE_PERCENT = 0.02
private const val MEMORY_CACHE_PERCENT = 0.25
@InstallIn(SingletonComponent::class)
@Module
class NetworkModule {
@Provides
@Singleton
fun provideImageLoader(okHttpClient: OkHttpClient, @ApplicationContext application: Context): ImageLoader {
val sharedOkHttp = okHttpClient.newBuilder().build()
return ImageLoader.Builder(context = application)
.components {
add(OkHttpNetworkFetcherFactory(callFactory = { sharedOkHttp }))
add(SvgDecoder.Factory(scaleToDensity = true))
}
.memoryCache {
MemoryCache.Builder().maxSizePercent(context = application, percent = MEMORY_CACHE_PERCENT).build()
}
.diskCache { DiskCache.Builder().maxSizePercent(percent = DISK_CACHE_PERCENT).build() }
.logger(logger = if (BuildConfig.DEBUG) DebugLogger(minLevel = Logger.Level.Verbose) else null)
.crossfade(enable = true)
.build()
}
@Provides
@Singleton
fun provideHttpClient(okHttpClient: OkHttpClient): HttpClient = HttpClient(engineFactory = OkHttp) {
engine { preconfigured = okHttpClient }
install(plugin = ContentNegotiation) {
json(
Json {
isLenient = true
ignoreUnknownKeys = true
},
)
}
}
}