From 5bb3f73e0dddd83e3b2aa0b6d45897ff70cd09b8 Mon Sep 17 00:00:00 2001 From: Phil Oliver <3497406+poliver@users.noreply.github.com> Date: Wed, 24 Sep 2025 10:20:35 -0400 Subject: [PATCH] Modularize `CustomTileProviderRepository` (#3181) --- .../java/com/geeksville/mesh/di/MapModule.kt | 14 ---- .../geeksville/mesh/ui/map/MapViewModel.kt | 10 +-- .../CustomTileProviderManagerSheet.kt | 2 +- core/data/build.gradle.kts | 11 ++- .../core/data/di/GoogleDataModule.kt | 26 +++---- .../data/model/CustomTileProviderConfig.kt | 28 ++++++++ .../CustomTileProviderRepository.kt | 69 +++++++++++-------- 7 files changed, 96 insertions(+), 64 deletions(-) rename app/src/google/java/com/geeksville/mesh/repository/map/CustomTileProviderRepository.kt => core/data/src/google/kotlin/org/meshtastic/core/data/di/GoogleDataModule.kt (56%) create mode 100644 core/data/src/google/kotlin/org/meshtastic/core/data/model/CustomTileProviderConfig.kt rename app/src/google/java/com/geeksville/mesh/repository/map/SharedPreferencesCustomTileProviderRepository.kt => core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt (82%) diff --git a/app/src/google/java/com/geeksville/mesh/di/MapModule.kt b/app/src/google/java/com/geeksville/mesh/di/MapModule.kt index 6576f1dc1..0ef9b5c94 100644 --- a/app/src/google/java/com/geeksville/mesh/di/MapModule.kt +++ b/app/src/google/java/com/geeksville/mesh/di/MapModule.kt @@ -17,9 +17,6 @@ package com.geeksville.mesh.di -import com.geeksville.mesh.repository.map.CustomTileProviderRepository -import com.geeksville.mesh.repository.map.SharedPreferencesCustomTileProviderRepository -import dagger.Binds import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -35,14 +32,3 @@ object MapModule { @Provides @Singleton fun provideJson(): Json = Json { prettyPrint = false } } - -@Module -@InstallIn(SingletonComponent::class) -abstract class MapRepositoryModule { - - @Binds - @Singleton - abstract fun bindCustomTileProviderRepository( - impl: SharedPreferencesCustomTileProviderRepository, - ): CustomTileProviderRepository -} diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt index 3d7cb4bb3..bc8cc637b 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/MapViewModel.kt @@ -26,7 +26,6 @@ import com.geeksville.mesh.android.BuildUtils.debug import com.geeksville.mesh.database.NodeRepository import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.repository.datastore.RadioConfigRepository -import com.geeksville.mesh.repository.map.CustomTileProviderRepository import com.google.android.gms.maps.GoogleMap import com.google.android.gms.maps.model.TileProvider import com.google.android.gms.maps.model.UrlTileProvider @@ -50,6 +49,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import org.json.JSONObject +import org.meshtastic.core.data.model.CustomTileProviderConfig +import org.meshtastic.core.data.repository.CustomTileProviderRepository import org.meshtastic.core.prefs.map.GoogleMapsPrefs import org.meshtastic.core.prefs.map.MapPrefs import timber.log.Timber @@ -510,10 +511,3 @@ data class MapLayerItem( var geoJsonLayerData: GeoJsonLayer? = null, val layerType: LayerType, ) - -@Serializable -data class CustomTileProviderConfig( - val id: String = UUID.randomUUID().toString(), - val name: String, - val urlTemplate: String, -) diff --git a/app/src/google/java/com/geeksville/mesh/ui/map/components/CustomTileProviderManagerSheet.kt b/app/src/google/java/com/geeksville/mesh/ui/map/components/CustomTileProviderManagerSheet.kt index 63f0d57e3..cad2a5fdb 100644 --- a/app/src/google/java/com/geeksville/mesh/ui/map/components/CustomTileProviderManagerSheet.kt +++ b/app/src/google/java/com/geeksville/mesh/ui/map/components/CustomTileProviderManagerSheet.kt @@ -51,9 +51,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.geeksville.mesh.ui.map.CustomTileProviderConfig import com.geeksville.mesh.ui.map.MapViewModel import kotlinx.coroutines.flow.collectLatest +import org.meshtastic.core.data.model.CustomTileProviderConfig import org.meshtastic.core.strings.R @Suppress("LongMethod") diff --git a/core/data/build.gradle.kts b/core/data/build.gradle.kts index 313d80686..76c2256a4 100644 --- a/core/data/build.gradle.kts +++ b/core/data/build.gradle.kts @@ -17,9 +17,18 @@ plugins { alias(libs.plugins.meshtastic.android.library) + alias(libs.plugins.meshtastic.hilt) + alias(libs.plugins.meshtastic.kotlinx.serialization) alias(libs.plugins.kover) } android { namespace = "org.meshtastic.core.data" } -dependencies {} +dependencies { + implementation(projects.core.di) + implementation(projects.core.prefs) + + implementation(libs.kotlinx.coroutines.android) + implementation(libs.kotlinx.serialization.json) + implementation(libs.timber) +} diff --git a/app/src/google/java/com/geeksville/mesh/repository/map/CustomTileProviderRepository.kt b/core/data/src/google/kotlin/org/meshtastic/core/data/di/GoogleDataModule.kt similarity index 56% rename from app/src/google/java/com/geeksville/mesh/repository/map/CustomTileProviderRepository.kt rename to core/data/src/google/kotlin/org/meshtastic/core/data/di/GoogleDataModule.kt index fc58be2ca..ef67c2e6a 100644 --- a/app/src/google/java/com/geeksville/mesh/repository/map/CustomTileProviderRepository.kt +++ b/core/data/src/google/kotlin/org/meshtastic/core/data/di/GoogleDataModule.kt @@ -15,19 +15,21 @@ * along with this program. If not, see . */ -package com.geeksville.mesh.repository.map +package org.meshtastic.core.data.di -import com.geeksville.mesh.ui.map.CustomTileProviderConfig -import kotlinx.coroutines.flow.Flow +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.meshtastic.core.data.repository.CustomTileProviderRepository +import org.meshtastic.core.data.repository.CustomTileProviderRepositoryImpl +import javax.inject.Singleton -interface CustomTileProviderRepository { - fun getCustomTileProviders(): Flow> +@Module +@InstallIn(SingletonComponent::class) +interface GoogleDataModule { - suspend fun addCustomTileProvider(config: CustomTileProviderConfig) - - suspend fun updateCustomTileProvider(config: CustomTileProviderConfig) - - suspend fun deleteCustomTileProvider(configId: String) - - suspend fun getCustomTileProviderById(configId: String): CustomTileProviderConfig? + @Binds + @Singleton + fun bindCustomTileProviderRepository(impl: CustomTileProviderRepositoryImpl): CustomTileProviderRepository } diff --git a/core/data/src/google/kotlin/org/meshtastic/core/data/model/CustomTileProviderConfig.kt b/core/data/src/google/kotlin/org/meshtastic/core/data/model/CustomTileProviderConfig.kt new file mode 100644 index 000000000..0e0e2f388 --- /dev/null +++ b/core/data/src/google/kotlin/org/meshtastic/core/data/model/CustomTileProviderConfig.kt @@ -0,0 +1,28 @@ +/* + * 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 . + */ + +package org.meshtastic.core.data.model + +import kotlinx.serialization.Serializable +import java.util.UUID + +@Serializable +data class CustomTileProviderConfig( + val id: String = UUID.randomUUID().toString(), + val name: String, + val urlTemplate: String, +) diff --git a/app/src/google/java/com/geeksville/mesh/repository/map/SharedPreferencesCustomTileProviderRepository.kt b/core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt similarity index 82% rename from app/src/google/java/com/geeksville/mesh/repository/map/SharedPreferencesCustomTileProviderRepository.kt rename to core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt index 8c768c16e..f2c0262a8 100644 --- a/app/src/google/java/com/geeksville/mesh/repository/map/SharedPreferencesCustomTileProviderRepository.kt +++ b/core/data/src/google/kotlin/org/meshtastic/core/data/repository/CustomTileProviderRepository.kt @@ -15,9 +15,8 @@ * along with this program. If not, see . */ -package com.geeksville.mesh.repository.map +package org.meshtastic.core.data.repository -import com.geeksville.mesh.ui.map.CustomTileProviderConfig import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -25,14 +24,28 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.withContext import kotlinx.serialization.SerializationException import kotlinx.serialization.json.Json +import org.meshtastic.core.data.model.CustomTileProviderConfig import org.meshtastic.core.di.annotation.IoDispatcher import org.meshtastic.core.prefs.map.MapTileProviderPrefs import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +import kotlin.collections.plus + +interface CustomTileProviderRepository { + fun getCustomTileProviders(): Flow> + + suspend fun addCustomTileProvider(config: CustomTileProviderConfig) + + suspend fun updateCustomTileProvider(config: CustomTileProviderConfig) + + suspend fun deleteCustomTileProvider(configId: String) + + suspend fun getCustomTileProviderById(configId: String): CustomTileProviderConfig? +} @Singleton -class SharedPreferencesCustomTileProviderRepository +class CustomTileProviderRepositoryImpl @Inject constructor( private val json: Json, @@ -46,31 +59,6 @@ constructor( loadDataFromPrefs() } - private fun loadDataFromPrefs() { - val jsonString = mapTileProviderPrefs.customTileProviders - if (jsonString != null) { - try { - customTileProvidersStateFlow.value = json.decodeFromString>(jsonString) - } catch (e: SerializationException) { - Timber.tag("TileRepo").e(e, "Error deserializing tile providers") - customTileProvidersStateFlow.value = emptyList() - } - } else { - customTileProvidersStateFlow.value = emptyList() - } - } - - private suspend fun saveDataToPrefs(providers: List) { - withContext(ioDispatcher) { - try { - val jsonString = json.encodeToString(providers) - mapTileProviderPrefs.customTileProviders = jsonString - } catch (e: SerializationException) { - Timber.tag("TileRepo").e(e, "Error serializing tile providers") - } - } - } - override fun getCustomTileProviders(): Flow> = customTileProvidersStateFlow.asStateFlow() @@ -94,4 +82,29 @@ constructor( override suspend fun getCustomTileProviderById(configId: String): CustomTileProviderConfig? = customTileProvidersStateFlow.value.find { it.id == configId } + + private fun loadDataFromPrefs() { + val jsonString = mapTileProviderPrefs.customTileProviders + if (jsonString != null) { + try { + customTileProvidersStateFlow.value = json.decodeFromString>(jsonString) + } catch (e: SerializationException) { + Timber.e(e, "Error deserializing tile providers") + customTileProvidersStateFlow.value = emptyList() + } + } else { + customTileProvidersStateFlow.value = emptyList() + } + } + + private suspend fun saveDataToPrefs(providers: List) { + withContext(ioDispatcher) { + try { + val jsonString = json.encodeToString(providers) + mapTileProviderPrefs.customTileProviders = jsonString + } catch (e: SerializationException) { + Timber.e(e, "Error serializing tile providers") + } + } + } }