diff --git a/app/build.gradle b/app/build.gradle
index 95bf3092e..7b0b596c7 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -13,7 +13,26 @@ Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def NATIVE_APP_KEY = properties.getProperty('NATIVE_APP_KEY')
def ADS_APPLICATION_ID = properties.getProperty('ADS_APPLICATION_ID')
+def REMOTE_CONFIG_BASE_URL_PUBLIC_KEY = properties.getProperty('REMOTE_CONFIG_BASE_URL_PUBLIC_KEY', '').trim()
def keystorePropertiesFile = rootProject.file("app/keystore-release.properties")
+def buildConfigString = { String value ->
+ def escaped = (value ?: '')
+ .replace('\\', '\\\\')
+ .replace('"', '\\"')
+ .replace('\n', '\\n')
+ return "\"${escaped}\""
+}
+def fallbackBaseUrlProperty = { String fallbackKey, String legacyKey ->
+ def value = properties.getProperty(fallbackKey) ?: properties.getProperty(legacyKey)
+ if (!value) {
+ throw new GradleException("Missing ${fallbackKey} in local.properties")
+ }
+ return value
+}
+def FALLBACK_BASE_URL_DEV = fallbackBaseUrlProperty('FALLBACK_BASE_URL_DEV', 'BASE_URL_DEV')
+def FALLBACK_BASE_URL_PROD = fallbackBaseUrlProperty('FALLBACK_BASE_URL_PROD', 'BASE_URL_PROD')
+def USES_DEV_CLEARTEXT_TRAFFIC = FALLBACK_BASE_URL_DEV?.contains('http://') == true
+def USES_PROD_CLEARTEXT_TRAFFIC = FALLBACK_BASE_URL_PROD?.contains('http://') == true
kotlin {
jvmToolchain(17)
@@ -35,9 +54,11 @@ android {
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
buildConfigField ("String", "NATIVE_APP_KEY", properties['NATIVE_APP_KEY_STR'])
+ buildConfigField("String", "REMOTE_CONFIG_BASE_URL_PUBLIC_KEY", buildConfigString(REMOTE_CONFIG_BASE_URL_PUBLIC_KEY))
manifestPlaceholders = [
- NATIVE_APP_KEY : NATIVE_APP_KEY,
- ADS_APPLICATION_ID: ADS_APPLICATION_ID
+ NATIVE_APP_KEY : NATIVE_APP_KEY,
+ ADS_APPLICATION_ID : ADS_APPLICATION_ID,
+ USES_CLEARTEXT_TRAFFIC: false
]
}
@@ -64,6 +85,7 @@ android {
applicationIdSuffix ".debug"
versionNameSuffix "-debug"
resValue "string", "app_name", "DAYO (Debug)"
+ manifestPlaceholders.USES_CLEARTEXT_TRAFFIC = true
}
release {
resValue "string", "app_name", "DAYO"
@@ -76,16 +98,40 @@ android {
productFlavors {
dev {
dimension "environment"
- buildConfigField("String", "BASE_URL", properties['BASE_URL_DEV'])
+ manifestPlaceholders.USES_CLEARTEXT_TRAFFIC = USES_DEV_CLEARTEXT_TRAFFIC
+ buildConfigField("String", "FALLBACK_BASE_URL", FALLBACK_BASE_URL_DEV)
+ buildConfigField("String", "REMOTE_CONFIG_BASE_URL_KEY", "\"api_base_url_dev\"")
+ buildConfigField("String", "REMOTE_CONFIG_BASE_URL_SIGNATURE_KEY", "\"api_base_url_dev_signature\"")
}
prod {
dimension "environment"
- buildConfigField("String", "BASE_URL", properties['BASE_URL_PROD'])
+ manifestPlaceholders.USES_CLEARTEXT_TRAFFIC = USES_PROD_CLEARTEXT_TRAFFIC
+ buildConfigField("String", "FALLBACK_BASE_URL", FALLBACK_BASE_URL_PROD)
+ buildConfigField("String", "REMOTE_CONFIG_BASE_URL_KEY", "\"api_base_url_prod\"")
+ buildConfigField("String", "REMOTE_CONFIG_BASE_URL_SIGNATURE_KEY", "\"api_base_url_prod_signature\"")
}
}
namespace 'com.daily.dayo'
}
+def validateRemoteConfigBaseUrlPublicKeyForRelease = tasks.register(
+ 'validateRemoteConfigBaseUrlPublicKeyForRelease'
+) {
+ doLast {
+ if (!REMOTE_CONFIG_BASE_URL_PUBLIC_KEY) {
+ throw new GradleException(
+ 'Missing REMOTE_CONFIG_BASE_URL_PUBLIC_KEY in local.properties for release build'
+ )
+ }
+ }
+}
+
+tasks.configureEach { task ->
+ if (task.name ==~ /^pre.*ReleaseBuild$/) {
+ task.dependsOn(validateRemoteConfigBaseUrlPublicKeyForRelease)
+ }
+}
+
dependencies {
// multiModule
implementation project(':presentation')
@@ -107,7 +153,8 @@ dependencies {
implementation platform('com.google.firebase:firebase-bom:34.6.0')
implementation "com.google.firebase:firebase-crashlytics"
implementation "com.google.firebase:firebase-analytics"
+ implementation "com.google.firebase:firebase-config"
// Google Ads
implementation 'com.google.android.gms:play-services-ads:24.7.0'
-}
\ No newline at end of file
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0976f302a..b0c441d73 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -12,7 +12,7 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/com/daily/dayo/config/RemoteConfigBaseUrlProvider.kt b/app/src/main/java/com/daily/dayo/config/RemoteConfigBaseUrlProvider.kt
new file mode 100644
index 000000000..82ec8ca50
--- /dev/null
+++ b/app/src/main/java/com/daily/dayo/config/RemoteConfigBaseUrlProvider.kt
@@ -0,0 +1,262 @@
+package com.daily.dayo.config
+
+import android.content.Context
+import android.content.SharedPreferences
+import com.daily.dayo.BuildConfig
+import com.google.android.gms.tasks.Task
+import com.google.firebase.remoteconfig.FirebaseRemoteConfig
+import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings
+import daily.dayo.domain.provider.BaseUrlSource
+import daily.dayo.domain.provider.BaseUrlProvider
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeoutOrNull
+import java.net.InetAddress
+import java.net.URI
+import java.security.KeyFactory
+import java.security.Signature
+import java.security.spec.X509EncodedKeySpec
+import java.util.Base64
+import java.util.Locale
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+
+class RemoteConfigBaseUrlProvider(
+ context: Context,
+ private val remoteConfig: FirebaseRemoteConfig
+) : BaseUrlProvider {
+
+ private val sharedPreferences: SharedPreferences = context.getSharedPreferences(
+ PREFERENCES_NAME,
+ Context.MODE_PRIVATE
+ )
+
+ @Volatile
+ private var currentBaseUrlState: BaseUrlState = getInitialBaseUrlState()
+
+ init {
+ remoteConfig.setConfigSettingsAsync(
+ FirebaseRemoteConfigSettings.Builder()
+ .setFetchTimeoutInSeconds(FETCH_TIMEOUT_SECONDS)
+ .setMinimumFetchIntervalInSeconds(MINIMUM_FETCH_INTERVAL_SECONDS)
+ .build()
+ )
+ remoteConfig.setDefaultsAsync(
+ mapOf(
+ BuildConfig.REMOTE_CONFIG_BASE_URL_KEY to BuildConfig.FALLBACK_BASE_URL,
+ BuildConfig.REMOTE_CONFIG_BASE_URL_SIGNATURE_KEY to ""
+ )
+ )
+ }
+
+ override fun getBaseUrl(): String = currentBaseUrlState.baseUrl
+
+ override fun getBaseUrlSource(): BaseUrlSource = currentBaseUrlState.source
+
+ override fun isTrustedBaseUrl(baseUrl: String): Boolean {
+ val normalizedBaseUrl = normalizeBaseUrl(baseUrl) ?: return false
+ return normalizedBaseUrl == currentBaseUrlState.baseUrl ||
+ normalizedBaseUrl == normalizeBaseUrl(BuildConfig.FALLBACK_BASE_URL)
+ }
+
+ override suspend fun refreshBaseUrl(): Boolean {
+ return try {
+ remoteConfig.fetchAndActivate().awaitWithTimeout() ?: return false
+ val remoteBaseUrlValue = remoteConfig.getValue(BuildConfig.REMOTE_CONFIG_BASE_URL_KEY)
+ if (remoteBaseUrlValue.source != FirebaseRemoteConfig.VALUE_SOURCE_REMOTE) {
+ return false
+ }
+
+ val remoteBaseUrl = remoteBaseUrlValue.asString()
+ val remoteBaseUrlSignature = remoteConfig.getString(BuildConfig.REMOTE_CONFIG_BASE_URL_SIGNATURE_KEY)
+ val normalizedBaseUrl = normalizeBaseUrl(remoteBaseUrl) ?: run {
+ return false
+ }
+ if (!isTrustedRemoteBaseUrl(normalizedBaseUrl, remoteBaseUrlSignature)) {
+ return false
+ }
+
+ currentBaseUrlState = BaseUrlState(
+ baseUrl = normalizedBaseUrl,
+ source = BaseUrlSource.REMOTE_CONFIG
+ )
+ sharedPreferences.edit()
+ .putString(KEY_LAST_SUCCESSFUL_BASE_URL, normalizedBaseUrl)
+ .putString(KEY_LAST_SUCCESSFUL_BASE_URL_SIGNATURE, remoteBaseUrlSignature)
+ .apply()
+ true
+ } catch (exception: CancellationException) {
+ throw exception
+ } catch (exception: Exception) {
+ false
+ }
+ }
+
+ private fun getInitialBaseUrlState(): BaseUrlState {
+ val cachedBaseUrl = sharedPreferences.getString(KEY_LAST_SUCCESSFUL_BASE_URL, null)
+ val cachedBaseUrlSignature = sharedPreferences.getString(KEY_LAST_SUCCESSFUL_BASE_URL_SIGNATURE, null)
+ normalizeBaseUrl(cachedBaseUrl)
+ ?.takeIf { baseUrl -> isTrustedCachedBaseUrl(baseUrl, cachedBaseUrlSignature) }
+ ?.let { baseUrl ->
+ return BaseUrlState(
+ baseUrl = baseUrl,
+ source = BaseUrlSource.CACHED_LAST_SUCCESS
+ )
+ }
+ if (cachedBaseUrl != null) {
+ sharedPreferences.edit()
+ .remove(KEY_LAST_SUCCESSFUL_BASE_URL)
+ .remove(KEY_LAST_SUCCESSFUL_BASE_URL_SIGNATURE)
+ .apply()
+ }
+
+ return BaseUrlState(
+ baseUrl = normalizeBaseUrl(BuildConfig.FALLBACK_BASE_URL) ?: BuildConfig.FALLBACK_BASE_URL,
+ source = BaseUrlSource.FALLBACK
+ )
+ }
+
+ private suspend fun Task.awaitWithTimeout(): Boolean? {
+ return withTimeoutOrNull(TASK_TIMEOUT_MILLIS) {
+ suspendCancellableCoroutine { continuation ->
+ addOnCompleteListener { task ->
+ if (!continuation.isActive) return@addOnCompleteListener
+
+ val exception = task.exception
+ if (exception != null) {
+ continuation.resumeWithException(exception)
+ } else {
+ continuation.resume(task.result == true)
+ }
+ }
+ }
+ }
+ }
+
+ private fun normalizeBaseUrl(baseUrl: String?): String? {
+ val trimmedBaseUrl = baseUrl?.trim().orEmpty()
+ if (trimmedBaseUrl.isEmpty()) return null
+
+ return try {
+ val uri = URI(trimmedBaseUrl)
+ if (uri.rawQuery != null || uri.rawFragment != null) return null
+
+ val scheme = uri.scheme?.lowercase(Locale.US) ?: return null
+ val host = uri.host?.takeIf { it.isNotBlank() }?.lowercase(Locale.US) ?: return null
+ if (scheme != SCHEME_HTTP && scheme != SCHEME_HTTPS) return null
+ if (!BuildConfig.DEBUG && scheme == SCHEME_HTTP && !isReleaseHttpAllowed()) return null
+ if (uri.userInfo != null) return null
+ if (!BuildConfig.DEBUG && isUnsafeReleaseHost(host)) return null
+
+ val path = uri.path.orEmpty()
+ if (path.isNotBlank() && path != "/") return null
+
+ val canonicalPort = when {
+ scheme == SCHEME_HTTP && uri.port == DEFAULT_HTTP_PORT -> -1
+ scheme == SCHEME_HTTPS && uri.port == DEFAULT_HTTPS_PORT -> -1
+ else -> uri.port
+ }
+
+ val normalizedUri = URI(scheme, null, host, canonicalPort, "/", null, null)
+ normalizedUri.toString().ensureTrailingSlash()
+ } catch (exception: Exception) {
+ null
+ }
+ }
+
+ private fun isTrustedRemoteBaseUrl(normalizedBaseUrl: String, signature: String): Boolean {
+ if (normalizedBaseUrl == normalizeBaseUrl(BuildConfig.FALLBACK_BASE_URL)) return true
+ if (BuildConfig.DEBUG) return true
+
+ return verifyBaseUrlSignature(
+ baseUrl = normalizedBaseUrl,
+ signature = signature
+ )
+ }
+
+ private fun isTrustedCachedBaseUrl(normalizedBaseUrl: String, signature: String?): Boolean {
+ if (normalizedBaseUrl == normalizeBaseUrl(BuildConfig.FALLBACK_BASE_URL)) return true
+ if (BuildConfig.DEBUG) return true
+
+ return verifyBaseUrlSignature(
+ baseUrl = normalizedBaseUrl,
+ signature = signature.orEmpty()
+ )
+ }
+
+ private fun verifyBaseUrlSignature(baseUrl: String, signature: String): Boolean {
+ val publicKeyText = BuildConfig.REMOTE_CONFIG_BASE_URL_PUBLIC_KEY.stripPemFormatting()
+ val signatureText = signature.stripPemFormatting()
+ if (publicKeyText.isBlank() || signatureText.isBlank()) return false
+
+ return runCatching {
+ val publicKey = KeyFactory.getInstance(KEY_ALGORITHM_RSA).generatePublic(
+ X509EncodedKeySpec(Base64.getDecoder().decode(publicKeyText))
+ )
+ val verifier = Signature.getInstance(SIGNATURE_ALGORITHM)
+ verifier.initVerify(publicKey)
+ verifier.update(baseUrl.toByteArray(Charsets.UTF_8))
+ verifier.verify(Base64.getDecoder().decode(signatureText))
+ }.getOrDefault(false)
+ }
+
+ private fun isReleaseHttpAllowed(): Boolean =
+ runCatching {
+ URI(BuildConfig.FALLBACK_BASE_URL).scheme?.lowercase(Locale.US) == SCHEME_HTTP
+ }.getOrDefault(false)
+
+ private fun isUnsafeReleaseHost(host: String): Boolean {
+ val normalizedHost = host.trim('[', ']')
+ if (normalizedHost.equals("localhost", ignoreCase = true)) return true
+ if (normalizedHost.endsWith(".localhost", ignoreCase = true)) return true
+ if (normalizedHost.endsWith(".local", ignoreCase = true)) return true
+ if (!normalizedHost.looksLikeIpLiteral()) return false
+
+ return runCatching {
+ val address = InetAddress.getByName(normalizedHost)
+ address.isAnyLocalAddress ||
+ address.isLoopbackAddress ||
+ address.isLinkLocalAddress ||
+ address.isSiteLocalAddress ||
+ address.isMulticastAddress
+ }.getOrDefault(true)
+ }
+
+ private fun String.looksLikeIpLiteral(): Boolean =
+ IPV4_ADDRESS_REGEX.matches(this) || contains(':')
+
+ private fun String.stripPemFormatting(): String =
+ replace(PEM_PUBLIC_KEY_BEGIN, "")
+ .replace(PEM_PUBLIC_KEY_END, "")
+ .replace(PEM_SIGNATURE_BEGIN, "")
+ .replace(PEM_SIGNATURE_END, "")
+ .filterNot { it.isWhitespace() }
+
+ private fun String.ensureTrailingSlash(): String =
+ if (endsWith('/')) this else "$this/"
+
+ private data class BaseUrlState(
+ val baseUrl: String,
+ val source: BaseUrlSource
+ )
+
+ companion object {
+ private const val PREFERENCES_NAME = "remote_config_base_url"
+ private const val KEY_LAST_SUCCESSFUL_BASE_URL = "last_successful_base_url"
+ private const val KEY_LAST_SUCCESSFUL_BASE_URL_SIGNATURE = "last_successful_base_url_signature"
+ private const val FETCH_TIMEOUT_SECONDS = 5L
+ private const val TASK_TIMEOUT_MILLIS = 6_000L
+ private val MINIMUM_FETCH_INTERVAL_SECONDS = if (BuildConfig.DEBUG) 0L else 3_600L
+ private const val SCHEME_HTTP = "http"
+ private const val SCHEME_HTTPS = "https"
+ private const val DEFAULT_HTTP_PORT = 80
+ private const val DEFAULT_HTTPS_PORT = 443
+ private const val KEY_ALGORITHM_RSA = "RSA"
+ private const val SIGNATURE_ALGORITHM = "SHA256withRSA"
+ private const val PEM_PUBLIC_KEY_BEGIN = "-----BEGIN PUBLIC KEY-----"
+ private const val PEM_PUBLIC_KEY_END = "-----END PUBLIC KEY-----"
+ private const val PEM_SIGNATURE_BEGIN = "-----BEGIN SIGNATURE-----"
+ private const val PEM_SIGNATURE_END = "-----END SIGNATURE-----"
+ private val IPV4_ADDRESS_REGEX = Regex("""\d{1,3}(\.\d{1,3}){3}""")
+ }
+}
diff --git a/app/src/main/java/com/daily/dayo/di/BaseUrlProviderModule.kt b/app/src/main/java/com/daily/dayo/di/BaseUrlProviderModule.kt
new file mode 100644
index 000000000..0daf63f20
--- /dev/null
+++ b/app/src/main/java/com/daily/dayo/di/BaseUrlProviderModule.kt
@@ -0,0 +1,31 @@
+package com.daily.dayo.di
+
+import android.content.Context
+import com.daily.dayo.config.RemoteConfigBaseUrlProvider
+import com.google.firebase.remoteconfig.FirebaseRemoteConfig
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import daily.dayo.domain.provider.BaseUrlProvider
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object BaseUrlProviderModule {
+
+ @Singleton
+ @Provides
+ fun provideFirebaseRemoteConfig(): FirebaseRemoteConfig = FirebaseRemoteConfig.getInstance()
+
+ @Singleton
+ @Provides
+ fun provideBaseUrlProvider(
+ @ApplicationContext context: Context,
+ remoteConfig: FirebaseRemoteConfig
+ ): BaseUrlProvider = RemoteConfigBaseUrlProvider(
+ context = context,
+ remoteConfig = remoteConfig
+ )
+}
diff --git a/data/build.gradle b/data/build.gradle
index 4d69cb1a6..585fcdc5c 100644
--- a/data/build.gradle
+++ b/data/build.gradle
@@ -16,6 +16,15 @@ kotlin {
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
+def fallbackBaseUrlProperty = { String fallbackKey, String legacyKey ->
+ def value = properties.getProperty(fallbackKey) ?: properties.getProperty(legacyKey)
+ if (!value) {
+ throw new GradleException("Missing ${fallbackKey} in local.properties")
+ }
+ return value
+}
+def FALLBACK_BASE_URL_DEV = fallbackBaseUrlProperty('FALLBACK_BASE_URL_DEV', 'BASE_URL_DEV')
+def FALLBACK_BASE_URL_PROD = fallbackBaseUrlProperty('FALLBACK_BASE_URL_PROD', 'BASE_URL_PROD')
android {
namespace 'daily.dayo.data'
@@ -43,11 +52,11 @@ android {
productFlavors {
dev {
dimension "environment"
- buildConfigField("String", "BASE_URL", properties['BASE_URL_DEV'])
+ buildConfigField("String", "FALLBACK_BASE_URL", FALLBACK_BASE_URL_DEV)
}
prod {
dimension "environment"
- buildConfigField("String", "BASE_URL", properties['BASE_URL_PROD'])
+ buildConfigField("String", "FALLBACK_BASE_URL", FALLBACK_BASE_URL_PROD)
}
}
compileOptions {
@@ -91,4 +100,4 @@ dependencies {
// paging
implementation "androidx.paging:paging-runtime-ktx:$paging_version"
-}
\ No newline at end of file
+}
diff --git a/data/src/main/java/daily/dayo/data/datasource/remote/retrofit/interceptor/BaseUrlRewriteInterceptor.kt b/data/src/main/java/daily/dayo/data/datasource/remote/retrofit/interceptor/BaseUrlRewriteInterceptor.kt
new file mode 100644
index 000000000..ae7ea4e79
--- /dev/null
+++ b/data/src/main/java/daily/dayo/data/datasource/remote/retrofit/interceptor/BaseUrlRewriteInterceptor.kt
@@ -0,0 +1,38 @@
+package daily.dayo.data.datasource.remote.retrofit.interceptor
+
+import daily.dayo.domain.provider.BaseUrlProvider
+import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
+import okhttp3.Interceptor
+import okhttp3.Response
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class BaseUrlRewriteInterceptor @Inject constructor(
+ private val baseUrlProvider: BaseUrlProvider
+) : Interceptor {
+
+ override fun intercept(chain: Interceptor.Chain): Response {
+ val request = chain.request()
+ val providerBaseUrl = baseUrlProvider.getBaseUrl()
+ if (!baseUrlProvider.isTrustedBaseUrl(providerBaseUrl)) return chain.proceed(request)
+
+ val providerUrl = providerBaseUrl.toHttpUrlOrNull()
+ ?: return chain.proceed(request)
+
+ return try {
+ val rewrittenUrl = request.url.newBuilder()
+ .scheme(providerUrl.scheme)
+ .host(providerUrl.host)
+ .port(providerUrl.port)
+ .build()
+ chain.proceed(
+ request.newBuilder()
+ .url(rewrittenUrl)
+ .build()
+ )
+ } catch (exception: IllegalArgumentException) {
+ chain.proceed(request)
+ }
+ }
+}
diff --git a/data/src/main/java/daily/dayo/data/di/NetworkModule.kt b/data/src/main/java/daily/dayo/data/di/NetworkModule.kt
index 4abf85ccd..bf6c2a0d1 100644
--- a/data/src/main/java/daily/dayo/data/di/NetworkModule.kt
+++ b/data/src/main/java/daily/dayo/data/di/NetworkModule.kt
@@ -3,12 +3,15 @@ package daily.dayo.data.di
import android.content.Context
import daily.dayo.data.BuildConfig
import daily.dayo.data.datasource.remote.retrofit.factory.NetworkResponseAdapterFactory
+import daily.dayo.data.datasource.remote.retrofit.interceptor.BaseUrlRewriteInterceptor
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import daily.dayo.data.datasource.local.SharedManager
+import daily.dayo.domain.provider.BaseUrlProvider
+import okhttp3.HttpUrl
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
@@ -23,18 +26,29 @@ object NetworkModule {
@Singleton
@Provides
- fun provideOkHttpClient(@ApplicationContext context:Context): OkHttpClient {
+ fun provideOkHttpClient(
+ @ApplicationContext context: Context,
+ baseUrlRewriteInterceptor: BaseUrlRewriteInterceptor,
+ baseUrlProvider: BaseUrlProvider
+ ): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor()
- loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
+ loggingInterceptor.redactHeader("Authorization")
+ loggingInterceptor.setLevel(
+ if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY else HttpLoggingInterceptor.Level.NONE
+ )
return OkHttpClient.Builder()
+ .addInterceptor(baseUrlRewriteInterceptor)
.addInterceptor { chain: Interceptor.Chain ->
val request = chain.request()
+ val shouldAttachAuthorization = baseUrlProvider.isTrustedBaseUrl(request.url.toBaseOrigin())
// Header에 AccessToken을 삽입하지 않는 대상
if (request.url.encodedPath.equals("/api/v1/members/kakaoOAuth", true) ||
request.url.encodedPath.equals("/api/v1/members/signIn", true) ||
request.url.encodedPath.startsWith("/api/v1/members/signUp", true)
) {
chain.proceed(request)
+ } else if (!shouldAttachAuthorization) {
+ chain.proceed(request)
} else if (request.url.encodedPath.equals("/api/v1/members/refresh", true)) {
chain.proceed(request.newBuilder().apply {
addHeader(
@@ -57,6 +71,13 @@ object NetworkModule {
.build()
}
+ private fun HttpUrl.toBaseOrigin(): String =
+ newBuilder()
+ .encodedPath("/")
+ .query(null)
+ .build()
+ .toString()
+
@Singleton
@Provides
fun provideConverterFactory(): GsonConverterFactory =
@@ -69,11 +90,11 @@ object NetworkModule {
gsonConverterFactory: GsonConverterFactory
): Retrofit {
return Retrofit.Builder()
- .baseUrl(BuildConfig.BASE_URL)
+ .baseUrl(BuildConfig.FALLBACK_BASE_URL)
.addCallAdapterFactory(NetworkResponseAdapterFactory())
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(gsonConverterFactory)
.client(okHttpClient)
.build()
}
-}
\ No newline at end of file
+}
diff --git a/domain/src/main/java/daily/dayo/domain/provider/BaseUrlProvider.kt b/domain/src/main/java/daily/dayo/domain/provider/BaseUrlProvider.kt
new file mode 100644
index 000000000..51ace9930
--- /dev/null
+++ b/domain/src/main/java/daily/dayo/domain/provider/BaseUrlProvider.kt
@@ -0,0 +1,17 @@
+package daily.dayo.domain.provider
+
+enum class BaseUrlSource {
+ FALLBACK,
+ CACHED_LAST_SUCCESS,
+ REMOTE_CONFIG
+}
+
+interface BaseUrlProvider {
+ fun getBaseUrl(): String
+
+ fun getBaseUrlSource(): BaseUrlSource
+
+ fun isTrustedBaseUrl(baseUrl: String): Boolean
+
+ suspend fun refreshBaseUrl(): Boolean
+}
diff --git a/presentation/build.gradle b/presentation/build.gradle
index 053d552fc..f79dfa115 100644
--- a/presentation/build.gradle
+++ b/presentation/build.gradle
@@ -12,6 +12,15 @@ apply plugin: "org.jetbrains.kotlin.plugin.compose"
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def NATIVE_APP_KEY = properties.getProperty('NATIVE_APP_KEY')
+def fallbackBaseUrlProperty = { String fallbackKey, String legacyKey ->
+ def value = properties.getProperty(fallbackKey) ?: properties.getProperty(legacyKey)
+ if (!value) {
+ throw new GradleException("Missing ${fallbackKey} in local.properties")
+ }
+ return value
+}
+def FALLBACK_BASE_URL_DEV = fallbackBaseUrlProperty('FALLBACK_BASE_URL_DEV', 'BASE_URL_DEV')
+def FALLBACK_BASE_URL_PROD = fallbackBaseUrlProperty('FALLBACK_BASE_URL_PROD', 'BASE_URL_PROD')
kapt {
correctErrorTypes true
@@ -57,12 +66,12 @@ android {
productFlavors {
dev {
dimension "environment"
- buildConfigField("String", "BASE_URL", properties['BASE_URL_DEV'])
+ buildConfigField("String", "FALLBACK_BASE_URL", FALLBACK_BASE_URL_DEV)
buildConfigField("String", "REWARDED_AD_UNIT_ID_FOLDER", properties['REWARDED_AD_UNIT_ID_FOLDER_DEV'])
}
prod {
dimension "environment"
- buildConfigField("String", "BASE_URL", properties['BASE_URL_PROD'])
+ buildConfigField("String", "FALLBACK_BASE_URL", FALLBACK_BASE_URL_PROD)
buildConfigField("String", "REWARDED_AD_UNIT_ID_FOLDER", properties['REWARDED_AD_UNIT_ID_FOLDER_PROD'])
}
}
@@ -240,4 +249,4 @@ dependencies {
// Google Ads
implementation 'com.google.android.gms:play-services-ads:24.7.0'
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/activity/LoginActivity.kt b/presentation/src/main/java/daily/dayo/presentation/activity/LoginActivity.kt
index e7220b946..fd10c5102 100644
--- a/presentation/src/main/java/daily/dayo/presentation/activity/LoginActivity.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/activity/LoginActivity.kt
@@ -10,22 +10,30 @@ import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
+import androidx.lifecycle.lifecycleScope
import com.google.android.play.core.appupdate.AppUpdateManager
import com.google.android.play.core.appupdate.AppUpdateManagerFactory
import com.google.android.play.core.install.model.UpdateAvailability
-import daily.dayo.presentation.viewmodel.AccountViewModel
import dagger.hilt.android.AndroidEntryPoint
+import daily.dayo.domain.provider.BaseUrlProvider
import daily.dayo.presentation.R
import daily.dayo.presentation.common.dialog.DefaultDialogAlert
+import daily.dayo.presentation.common.url.LocalBaseUrl
import daily.dayo.presentation.screen.account.AccountScreen
import daily.dayo.presentation.theme.DayoTheme
+import daily.dayo.presentation.viewmodel.AccountViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
+import javax.inject.Inject
@AndroidEntryPoint
class LoginActivity : AppCompatActivity() {
+ @Inject
+ lateinit var baseUrlProvider: BaseUrlProvider
+
private val loginViewModel by viewModels()
private var isReady = false
private lateinit var updateDialog: AlertDialog
@@ -37,12 +45,25 @@ class LoginActivity : AppCompatActivity() {
}
super.onCreate(savedInstanceState)
createDialogUpdate()
- checkUpdate()
observeNetworkException()
observeApiException()
+ refreshBaseUrlAndContinue()
+ }
+
+ private fun refreshBaseUrlAndContinue() {
+ lifecycleScope.launch {
+ baseUrlProvider.refreshBaseUrl()
+ setLoginContent()
+ checkUpdate()
+ }
+ }
+
+ private fun setLoginContent() {
setContent {
DayoTheme {
- AccountScreen()
+ CompositionLocalProvider(LocalBaseUrl provides baseUrlProvider.getBaseUrl()) {
+ AccountScreen()
+ }
}
}
}
@@ -135,4 +156,4 @@ class LoginActivity : AppCompatActivity() {
}
)
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt
index 8ab937c2f..44ea04be0 100644
--- a/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/activity/MainActivity.kt
@@ -13,40 +13,63 @@ import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
+import androidx.compose.runtime.CompositionLocalProvider
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
+import androidx.lifecycle.lifecycleScope
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
import dagger.hilt.android.AndroidEntryPoint
+import daily.dayo.domain.provider.BaseUrlProvider
import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
+import daily.dayo.presentation.common.url.LocalBaseUrl
import daily.dayo.presentation.screen.main.MainScreen
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.viewmodel.AccountViewModel
import daily.dayo.presentation.viewmodel.SettingNotificationViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
+ @Inject
+ lateinit var baseUrlProvider: BaseUrlProvider
+
private val accountViewModel by viewModels()
private val settingNotificationViewModel by viewModels()
private var rewardedAd: RewardedAd? = null
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
checkCurrentNotification()
getNotificationData()
askNotificationPermission()
loadRewardedAd()
+ refreshBaseUrlAndSetContent()
+ }
+
+ private fun refreshBaseUrlAndSetContent() {
+ lifecycleScope.launch {
+ baseUrlProvider.refreshBaseUrl()
+ setMainContent()
+ }
+ }
+
+ private fun setMainContent() {
setContent {
DayoTheme {
- MainScreen(
- onAdRequest = { onRewardSuccess ->
- showAdIfAvailable(onRewardSuccess)
- },
- onExit = { finish() }
- )
+ CompositionLocalProvider(LocalBaseUrl provides baseUrlProvider.getBaseUrl()) {
+ MainScreen(
+ onAdRequest = { onRewardSuccess ->
+ showAdIfAvailable(onRewardSuccess)
+ },
+ onExit = { finish() }
+ )
+ }
}
}
}
@@ -176,4 +199,4 @@ class MainActivity : AppCompatActivity() {
companion object {
val notificationPermission = arrayOf(Manifest.permission.POST_NOTIFICATIONS)
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/common/url/BaseUrl.kt b/presentation/src/main/java/daily/dayo/presentation/common/url/BaseUrl.kt
new file mode 100644
index 000000000..c3c8d6047
--- /dev/null
+++ b/presentation/src/main/java/daily/dayo/presentation/common/url/BaseUrl.kt
@@ -0,0 +1,27 @@
+package daily.dayo.presentation.common.url
+
+import androidx.compose.runtime.compositionLocalOf
+import daily.dayo.presentation.BuildConfig
+
+val LocalBaseUrl = compositionLocalOf { BuildConfig.FALLBACK_BASE_URL }
+
+fun remoteUrl(baseUrl: String, path: String): String {
+ val normalizedBaseUrl = baseUrl.trimEnd('/')
+ val normalizedPath = path.trimStart('/')
+
+ return when {
+ normalizedBaseUrl.isEmpty() -> normalizedPath
+ normalizedPath.isEmpty() -> normalizedBaseUrl
+ else -> "$normalizedBaseUrl/$normalizedPath"
+ }
+}
+
+fun remoteImageUrl(baseUrl: String, imageFileName: String?): String {
+ val normalizedImageFileName = imageFileName?.trim().orEmpty()
+
+ return if (normalizedImageFileName.isEmpty()) {
+ ""
+ } else {
+ remoteUrl(baseUrl = baseUrl, path = "images/$normalizedImageFileName")
+ }
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/bookmark/BookmarkScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/bookmark/BookmarkScreen.kt
index bfbcdddb7..cf2825013 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/bookmark/BookmarkScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/bookmark/BookmarkScreen.kt
@@ -33,9 +33,10 @@ import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.compose.collectAsLazyPagingItems
import daily.dayo.domain.model.BookmarkPost
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
import daily.dayo.presentation.theme.Gray2_767B83
@@ -227,10 +228,12 @@ private fun BookmarkPostItem(
onBookmarkPostClick: (BookmarkPost) -> Unit,
onBookmarkEditClick: () -> Unit
) {
+ val baseUrl = LocalBaseUrl.current
+
Box {
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${post.thumbnailImage}",
+ imageUrl = remoteImageUrl(baseUrl, post.thumbnailImage),
imageDescription = "bookmark post thumbnail",
modifier = Modifier
.fillMaxWidth()
@@ -258,4 +261,4 @@ private fun BookmarkPostItem(
@Composable
private fun PreviewBookmarkHeader() {
BookmarkHeader(0, 0, true, {})
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt
index 68d617180..7746ffc4f 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/folder/FolderScreen.kt
@@ -59,13 +59,14 @@ import daily.dayo.domain.model.FolderInfo
import daily.dayo.domain.model.FolderOrder
import daily.dayo.domain.model.FolderPost
import daily.dayo.domain.model.Privacy
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.dialog.LoadingAlertDialog.createLoadingDialog
import daily.dayo.presentation.common.dialog.LoadingAlertDialog.hideLoadingDialog
import daily.dayo.presentation.common.dialog.LoadingAlertDialog.resizeDialogFragment
import daily.dayo.presentation.common.dialog.LoadingAlertDialog.showLoadingDialog
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -549,6 +550,7 @@ private fun FolderPostItem(
onPostSelect: (Long) -> Unit
) {
val interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+ val baseUrl = LocalBaseUrl.current
Box(
modifier = Modifier.clickable(
@@ -565,7 +567,7 @@ private fun FolderPostItem(
) {
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${post.thumbnailImage}",
+ imageUrl = remoteImageUrl(baseUrl, post.thumbnailImage),
imageDescription = "bookmark post thumbnail",
modifier = Modifier
.fillMaxWidth()
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/FollowScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/FollowScreen.kt
index 0d176f589..d30a1ea42 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/FollowScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/FollowScreen.kt
@@ -52,9 +52,10 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import daily.dayo.domain.model.Follow
import daily.dayo.domain.model.Profile
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -293,6 +294,8 @@ private fun FollowUserInfo(
onProfileClick: (String) -> Unit,
onFollowClick: (Follow) -> Unit
) {
+ val baseUrl = LocalBaseUrl.current
+
Row(
modifier = Modifier
.fillMaxWidth()
@@ -308,7 +311,7 @@ private fun FollowUserInfo(
) {
RoundImageView(
context = context,
- imageUrl = "${BuildConfig.BASE_URL}/images/${follow.profileImg}",
+ imageUrl = remoteImageUrl(baseUrl, follow.profileImg),
roundSize = 18.dp,
modifier = Modifier
.size(36.dp)
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt
index e1e9aceb1..c321733bf 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageEditScreen.kt
@@ -58,7 +58,6 @@ import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import daily.dayo.domain.model.Profile
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.Resource
import daily.dayo.presentation.common.Status
@@ -67,6 +66,8 @@ import daily.dayo.presentation.common.dialog.LoadingAlertDialog.hideLoadingDialo
import daily.dayo.presentation.common.dialog.LoadingAlertDialog.resizeDialogFragment
import daily.dayo.presentation.common.dialog.LoadingAlertDialog.showLoadingDialog
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -93,6 +94,7 @@ internal fun MyPageEditScreen(
val focusManager = LocalFocusManager.current
val alertDialog = remember { mutableStateOf(createLoadingDialog(context)) }
val bottomSheetController = LocalBottomSheetController.current
+ val baseUrl = LocalBaseUrl.current
val profileUiState by profileSettingViewModel.profileInfo.observeAsState(Resource.loading(null))
val isNicknameDuplicate by profileSettingViewModel.isNicknameDuplicate.collectAsStateWithLifecycle(false)
@@ -132,9 +134,9 @@ internal fun MyPageEditScreen(
}
}
- LaunchedEffect(profileInfo.value?.profileImg, modifiedProfileImage) {
+ LaunchedEffect(profileInfo.value?.profileImg, modifiedProfileImage, baseUrl) {
profileInfo.value?.profileImg?.let { profileImg ->
- modifiedProfileImage.value = "${BuildConfig.BASE_URL}/images/${profileImg}"
+ modifiedProfileImage.value = remoteImageUrl(baseUrl, profileImg)
}
}
@@ -503,4 +505,4 @@ private fun PreviewMyPageEditScreen() {
onConfirmClick = {}
)
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageScreen.kt
index 3716b7f37..8d9eda591 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/mypage/MyPageScreen.kt
@@ -46,12 +46,13 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import daily.dayo.domain.model.Folder
import daily.dayo.domain.model.Profile
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.Status
import daily.dayo.presentation.common.constant.FolderConstants.FOLDER_AD_START_COUNT
import daily.dayo.presentation.common.constant.FolderConstants.MAX_FOLDER_COUNT
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -150,6 +151,8 @@ private fun MyPageProfile(
profile: Profile?,
onFollowButtonClick: (String, Int) -> Unit
) {
+ val baseUrl = LocalBaseUrl.current
+
Row(
modifier = Modifier
.fillMaxWidth()
@@ -160,7 +163,7 @@ private fun MyPageProfile(
// profile image
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${profile?.profileImg}",
+ imageUrl = remoteImageUrl(baseUrl, profile?.profileImg),
imageDescription = "my page profile image",
roundSize = 24.dp,
modifier = Modifier
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/notification/NotificationScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/notification/NotificationScreen.kt
index c73703b95..a4f0262d3 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/notification/NotificationScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/notification/NotificationScreen.kt
@@ -58,9 +58,10 @@ import androidx.paging.compose.collectAsLazyPagingItems
import coil.size.Size
import daily.dayo.domain.model.Notification
import daily.dayo.domain.model.Topic
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.TimeChangerUtil
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray3_9FA5AE
@@ -318,6 +319,7 @@ fun NotificationView(
onProfileClick: (String) -> Unit = {},
) {
var textLayoutResult by remember { mutableStateOf(null) }
+ val baseUrl = LocalBaseUrl.current
val notificationMessage = buildAnnotatedString {
// 1. 닉네임 포함된 메시지 인지 체크
if (!notification.nickname.isNullOrBlank()) {
@@ -355,7 +357,7 @@ fun NotificationView(
modifier = Modifier.weight(1f),
) {
RoundImageView(
- imageUrl = "${BuildConfig.BASE_URL}/images/${notification.profileImage}",
+ imageUrl = remoteImageUrl(baseUrl, notification.profileImage),
context = context,
modifier = Modifier
.size(28.dp)
@@ -428,7 +430,7 @@ fun NotificationView(
.fillMaxHeight()
)
RoundImageView(
- imageUrl = "${BuildConfig.BASE_URL}/images/${notification.image!!}",
+ imageUrl = remoteImageUrl(baseUrl, notification.image!!),
context = context,
modifier = Modifier
.size(56.dp),
@@ -482,4 +484,4 @@ fun performNotificationNavigation(
}
}
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/post/PostLikeUsersScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/post/PostLikeUsersScreen.kt
index db9ebe48c..c9c23ade5 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/post/PostLikeUsersScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/post/PostLikeUsersScreen.kt
@@ -46,9 +46,10 @@ import androidx.paging.compose.itemKey
import coil.compose.AsyncImage
import coil.request.ImageRequest
import daily.dayo.domain.model.LikeUser
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray2_767B83
@@ -197,6 +198,8 @@ private fun LikeUserItem(
onProfileClick: (String) -> Unit,
onFollowClick: (LikeUser) -> Unit
) {
+ val baseUrl = LocalBaseUrl.current
+
Surface(
color = White_FFFFFF,
modifier = Modifier
@@ -211,7 +214,7 @@ private fun LikeUserItem(
// profile
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data("${BuildConfig.BASE_URL}/images/${likeUser.profileImg}")
+ .data(remoteImageUrl(baseUrl, likeUser.profileImg))
.build(),
contentDescription = "${likeUser.nickname} + profile",
contentScale = ContentScale.Crop,
@@ -272,4 +275,4 @@ private fun PreviewPostLikeUsersScreen() {
onFollowClick = { },
onBackClick = { }
)
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/profile/ProfileScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/profile/ProfileScreen.kt
index 60df1ef55..a81e4a597 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/profile/ProfileScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/profile/ProfileScreen.kt
@@ -49,10 +49,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.flowWithLifecycle
import daily.dayo.domain.model.Folder
import daily.dayo.domain.model.Profile
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.Status
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -276,6 +277,8 @@ private fun UserProfile(
profile: Profile,
onFollowMenuClick: (String, Int) -> Unit
) {
+ val baseUrl = LocalBaseUrl.current
+
Row(
modifier = Modifier
.fillMaxWidth()
@@ -286,7 +289,7 @@ private fun UserProfile(
// profile image
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${profile.profileImg}",
+ imageUrl = remoteImageUrl(baseUrl, profile.profileImg),
imageDescription = "profile image",
roundSize = 24.dp,
modifier = Modifier
@@ -484,4 +487,3 @@ private val DEFAULT_PROFILE = Profile(
followingCount = 10,
follow = null,
)
-
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/rules/RuleScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/rules/RuleScreen.kt
index 292e35a7e..153050278 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/rules/RuleScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/rules/RuleScreen.kt
@@ -15,8 +15,9 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.viewinterop.AndroidView
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteUrl
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.view.NoRippleIconButton
import daily.dayo.presentation.view.TopNavigation
@@ -36,6 +37,8 @@ fun RuleScreen(
onBackClick: () -> Unit = {},
ruleType: RuleType = RuleType.PRIVACY_POLICY
) {
+ val baseUrl = LocalBaseUrl.current
+
Surface(
modifier = Modifier
.background(DayoTheme.colorScheme.background)
@@ -59,7 +62,7 @@ fun RuleScreen(
webViewClient = WebViewClient()
settings.javaScriptEnabled = false
overScrollMode = View.OVER_SCROLL_NEVER
- loadUrl("${BuildConfig.BASE_URL}/${ruleType.fileName}.html")
+ loadUrl(remoteUrl(baseUrl, "${ruleType.fileName}.html"))
setOnKeyListener(
View.OnKeyListener { v, keyCode, event ->
@@ -94,4 +97,4 @@ fun RuleActionbarLayout(
title = ruleType.koreanName,
titleAlignment = TopNavigationAlign.CENTER
)
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt
index 3db82539f..0d42e0a02 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchPostHashtagScreen.kt
@@ -32,9 +32,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.itemKey
import daily.dayo.domain.model.SearchOrder
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
import daily.dayo.presentation.theme.Gray2_767B83
@@ -54,6 +55,7 @@ fun SearchPostHashtagScreen(
val searchHashtagOrder by searchViewModel.searchHashtagOrder.collectAsStateWithLifecycle()
val hashtagPosts = searchViewModel.searchTagList.collectAsLazyPagingItems()
val hashtagPostsCount by searchViewModel.searchTagTotalCount.collectAsStateWithLifecycle(0)
+ val baseUrl = LocalBaseUrl.current
LaunchedEffect(Unit) {
with(searchViewModel) {
@@ -110,7 +112,7 @@ fun SearchPostHashtagScreen(
item.let { post ->
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${post?.thumbnailImage}",
+ imageUrl = remoteImageUrl(baseUrl, post?.thumbnailImage),
imageDescription = "searched Image",
modifier = Modifier
.fillMaxWidth()
@@ -175,4 +177,4 @@ private fun SearchResultDescription(
)
}
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt
index 24940a43f..0773fe2dc 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/search/SearchResultScreen.kt
@@ -77,10 +77,11 @@ import com.skydoves.landscapist.glide.GlideImage
import daily.dayo.domain.model.Search
import daily.dayo.domain.model.SearchHistoryType
import daily.dayo.domain.model.SearchUser
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.Event
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
import daily.dayo.presentation.theme.Gray2_767B83
@@ -393,6 +394,7 @@ fun SearchResultTagView(
onPostClick: (Long) -> Unit
) {
val imageInteractionSource = remember { MutableInteractionSource() }
+ val baseUrl = LocalBaseUrl.current
Box(
modifier = Modifier
.fillMaxSize()
@@ -414,7 +416,7 @@ fun SearchResultTagView(
item?.let { post ->
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${post.thumbnailImage}",
+ imageUrl = remoteImageUrl(baseUrl, post.thumbnailImage),
imageDescription = "searched Image",
modifier = Modifier
.matchParentSize()
@@ -523,8 +525,9 @@ fun SearchResultUserView(
@Composable
private fun SearchResultUserImageLayout(user: SearchUser, onClickProfile: (String) -> Unit) {
val imageInteractionSource = remember { MutableInteractionSource() }
+ val baseUrl = LocalBaseUrl.current
GlideImage(
- imageModel = { "${BuildConfig.BASE_URL}/images/${user.profileImg}" },
+ imageModel = { remoteImageUrl(baseUrl, user.profileImg) },
imageOptions = ImageOptions(
contentDescription = "image description",
contentScale = ContentScale.Crop,
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/settings/BlockedUsersScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/settings/BlockedUsersScreen.kt
index 465f48788..94ec584a6 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/settings/BlockedUsersScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/settings/BlockedUsersScreen.kt
@@ -39,9 +39,10 @@ import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.size.Size
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.Status
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray3_9FA5AE
@@ -227,6 +228,8 @@ fun BlockedUser(
onUnblockClick: (String) -> Unit = {},
context: Context = LocalContext.current,
) {
+ val baseUrl = LocalBaseUrl.current
+
Row(
modifier = Modifier
.background(DayoTheme.colorScheme.background)
@@ -237,7 +240,7 @@ fun BlockedUser(
verticalAlignment = Alignment.CenterVertically,
) {
RoundImageView(
- imageUrl = "${BuildConfig.BASE_URL}/images/${imageFileName}",
+ imageUrl = remoteImageUrl(baseUrl, imageFileName),
context = context,
modifier = Modifier
.size(36.dp)
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt
index 6dee5b312..8d0532e7e 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/settings/SettingsScreen.kt
@@ -49,11 +49,12 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import daily.dayo.domain.model.Profile
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.activity.LoginActivity
import daily.dayo.presentation.activity.MainActivity
import daily.dayo.presentation.common.Status
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -256,6 +257,8 @@ private fun SettingProfile(
profile: Profile?,
onProfileEditClick: () -> Unit
) {
+ val baseUrl = LocalBaseUrl.current
+
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
@@ -263,7 +266,7 @@ private fun SettingProfile(
// profile image
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${profile?.profileImg}",
+ imageUrl = remoteImageUrl(baseUrl, profile?.profileImg),
imageDescription = stringResource(id = R.string.setting_my_profile_image_description),
roundSize = 28.dp,
modifier = Modifier.size(56.dp)
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteFolderScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteFolderScreen.kt
index f72d9b22b..42712909f 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteFolderScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteFolderScreen.kt
@@ -41,7 +41,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.size.Size
import daily.dayo.domain.model.Folder
import daily.dayo.domain.model.Privacy
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.constant.FolderConstants.FOLDER_AD_START_COUNT
import daily.dayo.presentation.common.constant.FolderConstants.FOLDER_THUMBNAIL_RADIUS_SIZE
@@ -49,6 +48,8 @@ import daily.dayo.presentation.common.constant.FolderConstants.FOLDER_THUMBNAIL_
import daily.dayo.presentation.common.constant.FolderConstants.MAX_FOLDER_COUNT
import daily.dayo.presentation.common.extension.clickableSingle
import daily.dayo.presentation.common.extension.limitTo
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -265,9 +266,10 @@ fun WriteFolderItemLayout(
isSelected: Boolean = true,
onFolderClick: (Long, String) -> Unit = { _, _ -> },
) {
+ val baseUrl = LocalBaseUrl.current
val thumbnailModel: Any = folder.thumbnailImage
.takeIf { it.isNotBlank() }
- ?.let { "${BuildConfig.BASE_URL}/images/$it" }
+ ?.let { remoteImageUrl(baseUrl, it) }
?: R.drawable.img_default_folder_dayo_logo
Row(
diff --git a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt
index ce30ca565..eb01bd271 100644
--- a/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/screen/write/WriteScreen.kt
@@ -73,10 +73,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage
import coil.request.ImageRequest
import daily.dayo.domain.model.Category
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.Status
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.screen.home.CategoryMenu
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
@@ -403,6 +404,8 @@ fun WriteUploadImages(
deleteImage: (Int) -> Unit,
onEditImage: (Int) -> Unit,
) {
+ val baseUrl = LocalBaseUrl.current
+
LazyRow(
contentPadding = PaddingValues(start = 18.dp, end = 18.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
@@ -419,7 +422,7 @@ fun WriteUploadImages(
val imageRequest = ImageRequest.Builder(context)
.data(
if (isPostEditMode) {
- "${BuildConfig.BASE_URL}/images/${imageAsset.uriString}"
+ remoteImageUrl(baseUrl, imageAsset.uriString)
} else {
imageAsset.uriString
}
diff --git a/presentation/src/main/java/daily/dayo/presentation/view/Comment.kt b/presentation/src/main/java/daily/dayo/presentation/view/Comment.kt
index ee72340c7..0cf7ed487 100644
--- a/presentation/src/main/java/daily/dayo/presentation/view/Comment.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/view/Comment.kt
@@ -58,10 +58,11 @@ import daily.dayo.domain.model.Comment
import daily.dayo.domain.model.Comments
import daily.dayo.domain.model.MentionUser
import daily.dayo.domain.model.SearchUser
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.TimeChangerUtil
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -187,12 +188,14 @@ fun CommentView(
onClickReport: (Long) -> Unit,
modifier: Modifier
) {
+ val baseUrl = LocalBaseUrl.current
+
Column(
modifier = modifier,
) {
Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) {
RoundImageView(
- imageUrl = "${BuildConfig.BASE_URL}/images/${comment.profileImg}",
+ imageUrl = remoteImageUrl(baseUrl, comment.profileImg),
context = LocalContext.current,
modifier = Modifier
.clip(CircleShape)
@@ -322,6 +325,7 @@ fun getAnnotatedCommentContent(content: String, mentionList: List):
@Composable
fun CommentMentionSearchView(userResults: LazyPagingItems, onClickFollowUser: (SearchUser) -> Unit) {
val placeholder = AppCompatResources.getDrawable(LocalContext.current, R.drawable.ic_profile_default)
+ val baseUrl = LocalBaseUrl.current
LazyColumn(
modifier = Modifier
.background(DayoTheme.colorScheme.background)
@@ -347,7 +351,7 @@ fun CommentMentionSearchView(userResults: LazyPagingItems, onClickFo
verticalAlignment = Alignment.CenterVertically
) {
RoundImageView(
- imageUrl = "${BuildConfig.BASE_URL}/images/${user.profileImg}",
+ imageUrl = remoteImageUrl(baseUrl, user.profileImg),
context = LocalContext.current,
modifier = Modifier
.clip(CircleShape)
diff --git a/presentation/src/main/java/daily/dayo/presentation/view/DetailPostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/DetailPostView.kt
index a95193d91..70832abe4 100644
--- a/presentation/src/main/java/daily/dayo/presentation/view/DetailPostView.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/view/DetailPostView.kt
@@ -46,10 +46,11 @@ import coil.request.ImageRequest
import daily.dayo.domain.model.Category
import daily.dayo.domain.model.PostDetail
import daily.dayo.domain.model.categoryKR
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.TimeChangerUtil
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray1_50545B
@@ -87,6 +88,7 @@ fun DetailPostView(
var showDialog by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
+ val baseUrl = LocalBaseUrl.current
Column(modifier = modifier) {
// publisher info
@@ -100,7 +102,7 @@ fun DetailPostView(
// user profile image
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data("${BuildConfig.BASE_URL}/images/${post.profileImg}")
+ .data(remoteImageUrl(baseUrl, post.profileImg))
.build(),
contentDescription = "${post.nickname} profile",
contentScale = ContentScale.Crop,
@@ -179,7 +181,7 @@ fun DetailPostView(
HorizontalPager(state = pagerState) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data("${BuildConfig.BASE_URL}/images/${postImages[it]}")
+ .data(remoteImageUrl(baseUrl, postImages[it]))
.build(),
contentDescription = "post images",
contentScale = ContentScale.Crop,
diff --git a/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt
index 6c9f2e4b5..4de087aae 100644
--- a/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/view/FeedPostView.kt
@@ -61,10 +61,11 @@ import coil.request.ImageRequest
import daily.dayo.domain.model.Category
import daily.dayo.domain.model.Post
import daily.dayo.domain.model.categoryKR
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.TimeChangerUtil
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray2_767B83
@@ -98,6 +99,7 @@ fun FeedPostView(
var showDialog by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
+ val baseUrl = LocalBaseUrl.current
Column(modifier = modifier) {
// publisher info
@@ -111,7 +113,7 @@ fun FeedPostView(
// user profile image
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data("${BuildConfig.BASE_URL}/images/${post.userProfileImage}")
+ .data(remoteImageUrl(baseUrl, post.userProfileImage))
.build(),
contentDescription = "${post.nickname} + profile",
contentScale = ContentScale.Crop,
@@ -179,7 +181,7 @@ fun FeedPostView(
HorizontalPager(state = pagerState) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data("${BuildConfig.BASE_URL}/images/${postImages[it]}")
+ .data(remoteImageUrl(baseUrl, postImages[it]))
.build(),
contentDescription = "post images",
contentScale = ContentScale.Crop,
diff --git a/presentation/src/main/java/daily/dayo/presentation/view/FolderView.kt b/presentation/src/main/java/daily/dayo/presentation/view/FolderView.kt
index 6727d234a..192590316 100644
--- a/presentation/src/main/java/daily/dayo/presentation/view/FolderView.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/view/FolderView.kt
@@ -24,9 +24,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import daily.dayo.domain.model.Folder
import daily.dayo.domain.model.Privacy
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
import daily.dayo.presentation.common.extension.clickableSingle
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
import daily.dayo.presentation.theme.Gray3_9FA5AE
@@ -40,6 +41,7 @@ fun FolderView(
modifier: Modifier = Modifier,
) {
val imageInteractionSource = remember { MutableInteractionSource() }
+ val baseUrl = LocalBaseUrl.current
Column(modifier = modifier
.clickableSingle(
interactionSource = imageInteractionSource,
@@ -55,7 +57,7 @@ fun FolderView(
// thumbnail image
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${folder.thumbnailImage}",
+ imageUrl = remoteImageUrl(baseUrl, folder.thumbnailImage),
imageDescription = folder.title,
modifier = Modifier
.border(BorderStroke(1.dp, Gray5_E8EAEE), RoundedCornerShape(8.dp))
@@ -92,4 +94,4 @@ fun FolderView(
)
}
}
-}
\ No newline at end of file
+}
diff --git a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt
index e30856e9d..acd6c7114 100644
--- a/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt
+++ b/presentation/src/main/java/daily/dayo/presentation/view/HomePostView.kt
@@ -31,8 +31,9 @@ import androidx.compose.ui.unit.dp
import coil.compose.AsyncImage
import coil.request.ImageRequest
import daily.dayo.domain.model.Post
-import daily.dayo.presentation.BuildConfig
import daily.dayo.presentation.R
+import daily.dayo.presentation.common.url.LocalBaseUrl
+import daily.dayo.presentation.common.url.remoteImageUrl
import daily.dayo.presentation.common.extension.clickableSingle
import daily.dayo.presentation.theme.Dark
import daily.dayo.presentation.theme.DayoTheme
@@ -49,6 +50,7 @@ fun HomePostView(
onClickProfile: () -> Unit
) {
val imageInteractionSource = remember { MutableInteractionSource() }
+ val baseUrl = LocalBaseUrl.current
Column(modifier = modifier) {
Box(
modifier = Modifier
@@ -58,7 +60,7 @@ fun HomePostView(
// thumbnail image
RoundImageView(
context = LocalContext.current,
- imageUrl = "${BuildConfig.BASE_URL}/images/${post.thumbnailImage}",
+ imageUrl = remoteImageUrl(baseUrl, post.thumbnailImage),
imageDescription = "dayo pick image",
modifier = Modifier
.matchParentSize()
@@ -110,7 +112,7 @@ fun HomePostView(
) {
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
- .data("${BuildConfig.BASE_URL}/images/${post.userProfileImage}")
+ .data(remoteImageUrl(baseUrl, post.userProfileImage))
.build(),
contentDescription = "${post.nickname} + profile",
contentScale = ContentScale.Crop,