From 3c38cac8a058acccfcb4c42d01963a6390286d1f Mon Sep 17 00:00:00 2001 From: Hossain Khan Date: Fri, 2 May 2025 19:36:47 -0400 Subject: [PATCH] [REFACTOR] Moved fake API config from `DevConfig` to `BuildConfig` Fixes #16 --- .gitignore | 1 + app/build.gradle.kts | 19 ++++++++----------- .../java/ink/trmnl/android/data/DevConfig.kt | 12 ------------ .../android/data/RepositoryConfigProvider.kt | 7 +++++-- .../android/data/TrmnlDisplayRepository.kt | 8 ++++---- .../data/log/TrmnlRefreshLogSerializer.kt | 7 +++++-- .../android/ui/settings/AppSettingsScreen.kt | 19 ++++++++++++++----- .../java/ink/trmnl/android/data/DevConfig.kt | 12 ------------ 8 files changed, 37 insertions(+), 48 deletions(-) delete mode 100644 app/src/debug/java/ink/trmnl/android/data/DevConfig.kt delete mode 100644 app/src/release/java/ink/trmnl/android/data/DevConfig.kt diff --git a/.gitignore b/.gitignore index b4d6b5d..68ba552 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ google-services.json .DS_Store app/debug/ app/release/ +*.salive diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ee1897..b3313bd 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,5 +1,3 @@ -import java.util.Properties - plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) @@ -21,15 +19,6 @@ android { targetSdk = 35 versionCode = 2 versionName = "1.1.0" - - // Read key or other properties from local.properties - val localProperties = - project.rootProject.file("local.properties").takeIf { it.exists() }?.inputStream()?.use { - Properties().apply { load(it) } - } - val apiKey = localProperties?.getProperty("SERVICE_API_KEY") ?: "MISSING-KEY" - buildConfigField("String", "SERVICE_API_KEY", "\"$apiKey\"") - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } @@ -57,6 +46,10 @@ android { buildTypes { release { + // Always force `USE_FAKE_API` to `false` for release builds + // See https://github.com/usetrmnl/trmnl-android/issues/16 + buildConfigField("Boolean", "USE_FAKE_API", "false") + isMinifyEnabled = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), @@ -66,6 +59,10 @@ android { } debug { + // Allow developers to configure this value for debug builds + // Use fake API response for local development and testing purposes. + buildConfigField("Boolean", "USE_FAKE_API", "true") + signingConfig = signingConfigs.getByName("debug") } } diff --git a/app/src/debug/java/ink/trmnl/android/data/DevConfig.kt b/app/src/debug/java/ink/trmnl/android/data/DevConfig.kt deleted file mode 100644 index bb24e34..0000000 --- a/app/src/debug/java/ink/trmnl/android/data/DevConfig.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ink.trmnl.android.data - -/** - * Development configuration for the TRMNL app. - */ -object DevConfig { - /** - * Fake API response for local development and testing purposes. - * In debug builds, we use fake API responses. - */ - const val FAKE_API_RESPONSE = true -} diff --git a/app/src/main/java/ink/trmnl/android/data/RepositoryConfigProvider.kt b/app/src/main/java/ink/trmnl/android/data/RepositoryConfigProvider.kt index ff982f1..5f46308 100644 --- a/app/src/main/java/ink/trmnl/android/data/RepositoryConfigProvider.kt +++ b/app/src/main/java/ink/trmnl/android/data/RepositoryConfigProvider.kt @@ -1,5 +1,6 @@ package ink.trmnl.android.data +import ink.trmnl.android.BuildConfig import javax.inject.Inject /** @@ -12,10 +13,12 @@ class RepositoryConfigProvider /** * Indicates if the app should use fake data instead of real API responses. * - * @return Boolean value from [DevConfig.FAKE_API_RESPONSE] + * @return Boolean value from [BuildConfig.USE_FAKE_API] */ val shouldUseFakeData: Boolean get() { - return DevConfig.FAKE_API_RESPONSE + // To change this value, update the `buildConfigField` in the app's build.gradle file + // Or, change the value here for local development. Do not commit this change. + return BuildConfig.USE_FAKE_API } } diff --git a/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt b/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt index f4cdc00..cbea7f8 100644 --- a/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt +++ b/app/src/main/java/ink/trmnl/android/data/TrmnlDisplayRepository.kt @@ -2,7 +2,7 @@ package ink.trmnl.android.data import com.slack.eithernet.successOrNull import com.squareup.anvil.annotations.optional.SingleIn -import ink.trmnl.android.data.DevConfig.FAKE_API_RESPONSE +import ink.trmnl.android.BuildConfig.USE_FAKE_API import ink.trmnl.android.di.AppScope import ink.trmnl.android.network.TrmnlApiService import ink.trmnl.android.util.HTTP_200 @@ -14,11 +14,11 @@ import javax.inject.Inject /** * Repository class responsible for fetching and mapping display data. * - * ⚠️ NOTE: [FAKE_API_RESPONSE] is set to `true` in debug builds, meaning it will + * ⚠️ NOTE: [USE_FAKE_API] is set to `true` in debug builds, meaning it will * use mock data and avoid network calls. In release builds, it is set to `false` * to enable real API calls. * - * You can override this behavior by updating [DevConfig.FAKE_API_RESPONSE] for local development. + * You can override this behavior by updating [RepositoryConfigProvider.shouldUseFakeData] for local development. */ @SingleIn(AppScope::class) class TrmnlDisplayRepository @@ -118,7 +118,7 @@ class TrmnlDisplayRepository /** * Generates fake display info for debugging purposes without wasting an API request. * - * ℹ️ This is only used when [FAKE_API_RESPONSE] is set to `true`. + * ℹ️ This is only used when [RepositoryConfigProvider.shouldUseFakeData] is returns `true`. */ private suspend fun fakeTrmnlDisplayInfo(apiUsed: String): TrmnlDisplayInfo { Timber.d("DEBUG: Using mock data for display info") diff --git a/app/src/main/java/ink/trmnl/android/data/log/TrmnlRefreshLogSerializer.kt b/app/src/main/java/ink/trmnl/android/data/log/TrmnlRefreshLogSerializer.kt index 7ef4c11..495998c 100644 --- a/app/src/main/java/ink/trmnl/android/data/log/TrmnlRefreshLogSerializer.kt +++ b/app/src/main/java/ink/trmnl/android/data/log/TrmnlRefreshLogSerializer.kt @@ -8,6 +8,9 @@ import timber.log.Timber import java.io.InputStream import java.io.OutputStream +/** + * Serializer for [TrmnlRefreshLogs] using Moshi. + */ object TrmnlRefreshLogSerializer : Serializer { private val moshi = Moshi.Builder().build() private val adapter = moshi.adapter(TrmnlRefreshLogs::class.java) @@ -27,12 +30,12 @@ object TrmnlRefreshLogSerializer : Serializer { } override suspend fun writeTo( - t: TrmnlRefreshLogs, + refreshLogs: TrmnlRefreshLogs, output: OutputStream, ) { withContext(Dispatchers.IO) { try { - val jsonString = adapter.toJson(t) + val jsonString = adapter.toJson(refreshLogs) output.write(jsonString.toByteArray()) } catch (e: Exception) { Timber.e(e, "Error writing activity logs") diff --git a/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt b/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt index 2750602..f96b721 100644 --- a/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt +++ b/app/src/main/java/ink/trmnl/android/ui/settings/AppSettingsScreen.kt @@ -20,7 +20,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.DateRange -import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Warning import androidx.compose.material3.Button @@ -82,7 +81,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import ink.trmnl.android.R import ink.trmnl.android.data.AppConfig.DEFAULT_REFRESH_INTERVAL_SEC -import ink.trmnl.android.data.DevConfig +import ink.trmnl.android.data.RepositoryConfigProvider import ink.trmnl.android.data.TrmnlDisplayRepository import ink.trmnl.android.data.TrmnlTokenDataStore import ink.trmnl.android.di.AppScope @@ -123,6 +122,7 @@ data class AppSettingsScreen( ) : Screen { data class State( val accessToken: String, + val usesFakeApiData: Boolean, val isLoading: Boolean = false, val validationResult: ValidationResult? = null, val nextRefreshJobInfo: NextImageRefreshDisplayInfo? = null, @@ -186,12 +186,14 @@ class AppSettingsPresenter private val trmnlTokenDataStore: TrmnlTokenDataStore, private val trmnlWorkScheduler: TrmnlWorkScheduler, private val trmnlImageUpdateManager: TrmnlImageUpdateManager, + private val repositoryConfigProvider: RepositoryConfigProvider, ) : Presenter { @Composable override fun present(): AppSettingsScreen.State { var accessToken by remember { mutableStateOf("") } var isLoading by remember { mutableStateOf(false) } var validationResult by remember { mutableStateOf(null) } + val usesFakeApiData by remember { mutableStateOf(repositoryConfigProvider.shouldUseFakeData) } val scope = rememberCoroutineScope() val focusManager = LocalFocusManager.current @@ -212,6 +214,7 @@ class AppSettingsPresenter return AppSettingsScreen.State( accessToken = accessToken, + usesFakeApiData = usesFakeApiData, isLoading = isLoading, validationResult = validationResult, nextRefreshJobInfo = nextRefreshInfo, @@ -352,8 +355,7 @@ fun AppSettingsContent( horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center, ) { - if (DevConfig.FAKE_API_RESPONSE) { - // Show fake API banner when DevConfig.FAKE_API_RESPONSE is `true` + if (state.usesFakeApiData) { FakeApiInfoBanner( modifier = Modifier @@ -655,7 +657,7 @@ private fun FakeApiInfoBanner(modifier: Modifier = Modifier) { ) Text( - text = "Set `DevConfig.FAKE_API_RESPONSE` to `false` to use real API.", + text = "Set `BuildConfig.USE_FAKE_API` to `false` using build.gradle to use real API.", style = MaterialTheme.typography.bodySmall, fontStyle = FontStyle.Italic, ) @@ -727,6 +729,7 @@ private fun PreviewAppSettingsContentInitial() { state = AppSettingsScreen.State( accessToken = "", + usesFakeApiData = true, isLoading = false, validationResult = null, nextRefreshJobInfo = null, @@ -744,6 +747,7 @@ private fun PreviewAppSettingsContentLoading() { state = AppSettingsScreen.State( accessToken = "some-token", + usesFakeApiData = false, isLoading = true, validationResult = null, nextRefreshJobInfo = null, @@ -761,6 +765,7 @@ private fun PreviewAppSettingsContentSuccess() { state = AppSettingsScreen.State( accessToken = "valid-token-123", + usesFakeApiData = false, isLoading = false, validationResult = ValidationResult.Success( @@ -782,6 +787,7 @@ private fun PreviewAppSettingsContentFailure() { state = AppSettingsScreen.State( accessToken = "invalid-token", + usesFakeApiData = false, isLoading = false, validationResult = ValidationResult.Failure( @@ -806,6 +812,7 @@ private fun PreviewAppSettingsContentWithWork() { state = AppSettingsScreen.State( accessToken = "valid-token-123", + usesFakeApiData = false, isLoading = false, validationResult = null, // Can also be Success state nextRefreshJobInfo = @@ -833,6 +840,7 @@ private fun PreviewWorkScheduleStatusCardScheduled() { state = AppSettingsScreen.State( accessToken = "some-token", + usesFakeApiData = false, nextRefreshJobInfo = NextImageRefreshDisplayInfo( workerState = WorkInfo.State.ENQUEUED, @@ -854,6 +862,7 @@ private fun PreviewWorkScheduleStatusCardNoWork() { state = AppSettingsScreen.State( accessToken = "some-token", + usesFakeApiData = false, nextRefreshJobInfo = null, eventSink = {}, ), diff --git a/app/src/release/java/ink/trmnl/android/data/DevConfig.kt b/app/src/release/java/ink/trmnl/android/data/DevConfig.kt deleted file mode 100644 index 792bbd4..0000000 --- a/app/src/release/java/ink/trmnl/android/data/DevConfig.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ink.trmnl.android.data - -/** - * Development configuration for the TRMNL app. - */ -object DevConfig { - /** - * Fake API response for local development and testing purposes. - * In release builds, we use real API responses. - */ - const val FAKE_API_RESPONSE = false -}