diff --git a/README.md b/README.md
index 7577d9c..94b72e2 100644
--- a/README.md
+++ b/README.md
@@ -59,6 +59,10 @@ https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_envi
# Build
+## Build debug
+
+- `./gradlew clean assembleDevDebug`
+
## Deploy debug to Firebase App Distribution
- `./gradlew clean bundleDevDebug`
@@ -78,6 +82,8 @@ https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_envi
## Code integration
+* Switch the `isProductFlavorFilterEnabled` property to `false` in the
+ [BuildTypeAndroidApplicationPlugin.kt](build-logic/convention/src/main/kotlin/com/yugyd/buildlogic/convention/buildtype/BuildTypeAndroidApplicationPlugin.kt)
* Switch the `IS_BASED_ON_PLATFORM_APP` property to `true` in the [build.gradle](app/build.gradle)
file.
* Add the path to the [google-services.json](app/src/dev/google-services.json) file to
diff --git a/app/.gitignore b/app/.gitignore
index a5252f4..9411cb7 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,3 +1,5 @@
/build
# Hide sensitive information for production ads ids
/src/**/res/values/ad-ids.xml
+!src/main/res/values/ad-ids.xml
+!src/dev/res/values/ad-ids.xml
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index 91ee00f..db4da7b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -187,6 +187,9 @@ dependencies {
// Work Manager
implementation libs.work.manager.ktx
+ // Serialization
+ implementation libs.kotlinx.serialization
+
// Logging
implementation libs.timber
}
diff --git a/app/src/main/java/com/yugyd/quiz/ContentProviderImpl.kt b/app/src/main/java/com/yugyd/quiz/ContentProviderImpl.kt
index c8d0905..02f9127 100644
--- a/app/src/main/java/com/yugyd/quiz/ContentProviderImpl.kt
+++ b/app/src/main/java/com/yugyd/quiz/ContentProviderImpl.kt
@@ -23,7 +23,6 @@ import com.yugyd.quiz.core.ResIdProvider
import com.yugyd.quiz.featuretoggle.domain.RemoteConfigRepository
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
-import javax.inject.Singleton
internal class ContentProviderImpl @Inject constructor(
@ApplicationContext private val context: Context,
@@ -40,4 +39,14 @@ internal class ContentProviderImpl @Inject constructor(
}
?.link ?: context.getString(resIdProvider.appTelegramChat())
}
+
+ override suspend fun getUpdateLink(): String? {
+ val config = remoteConfigRepository.fetchUpdateConfig()
+ return config
+ ?.links
+ ?.firstOrNull {
+ GlobalConfig.APPLICATION_ID.contains(it.packageX)
+ }
+ ?.link
+ }
}
diff --git a/app/src/main/java/com/yugyd/quiz/YandexAdProviderImpl.kt b/app/src/main/java/com/yugyd/quiz/YandexAdProviderImpl.kt
index c6fa073..9be6a9b 100644
--- a/app/src/main/java/com/yugyd/quiz/YandexAdProviderImpl.kt
+++ b/app/src/main/java/com/yugyd/quiz/YandexAdProviderImpl.kt
@@ -1,14 +1,44 @@
package com.yugyd.quiz
import com.yugyd.quiz.core.AdIdProvider
+import com.yugyd.quiz.core.GlobalConfig
import com.yugyd.quiz.core.TextModel
import javax.inject.Inject
internal class YandexAdProviderImpl @Inject constructor() : AdIdProvider {
- override fun idAdBannerGame() = R.string.id_yandex_ad_banner_game
- override fun idAdRewardedGame() = R.string.id_yandex_ad_rewarded_game
- override fun idAdInterstitialGameEnd() = R.string.id_yandex_ad_interstitial_game_end
- override fun idAdRewardedTheme() = R.string.id_yandex_ad_rewarded_theme
+
+ override fun idAdBannerGame(): Int {
+ return if (GlobalConfig.DEBUG) {
+ R.string.test_id_yandex_ad_banner_game
+ } else {
+ R.string.id_yandex_ad_banner_game
+ }
+ }
+
+ override fun idAdRewardedGame(): Int {
+ return if (GlobalConfig.DEBUG) {
+ R.string.test_id_yandex_ad_rewarded_game
+ } else {
+ R.string.id_yandex_ad_rewarded_game
+ }
+ }
+
+ override fun idAdInterstitialGameEnd(): Int {
+ return if (GlobalConfig.DEBUG) {
+ R.string.test_id_yandex_ad_interstitial_game_end
+ } else {
+ R.string.id_yandex_ad_interstitial_game_end
+ }
+ }
+
+ override fun idAdRewardedTheme(): Int {
+ return if (GlobalConfig.DEBUG) {
+ R.string.test_id_yandex_ad_rewarded_theme
+ } else {
+ R.string.id_yandex_ad_rewarded_theme
+ }
+ }
+
override fun gameBannerAdId() = TextModel.ResTextModel(res = idAdBannerGame())
override fun gameRewardedAdId() = TextModel.ResTextModel(res = idAdRewardedGame())
override fun themeRewardedAdId() = TextModel.ResTextModel(res = idAdRewardedTheme())
diff --git a/app/src/main/java/com/yugyd/quiz/di/SerializationModule.kt b/app/src/main/java/com/yugyd/quiz/di/SerializationModule.kt
new file mode 100644
index 0000000..a11f83e
--- /dev/null
+++ b/app/src/main/java/com/yugyd/quiz/di/SerializationModule.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.serialization.json.Json
+import javax.inject.Singleton
+
+@Module
+@InstallIn(SingletonComponent::class)
+object SerializationModule {
+
+ @Singleton
+ @Provides
+ fun providesJson(): Json = Json {
+ ignoreUnknownKeys = true
+ isLenient = true
+ }
+}
diff --git a/app/src/main/res/values/ad-ids.xml b/app/src/main/res/values/ad-ids.xml
new file mode 100644
index 0000000..2abf766
--- /dev/null
+++ b/app/src/main/res/values/ad-ids.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ demo-banner-yandex
+ demo-interstitial-yandex
+ demo-rewarded-yandex
+ demo-rewarded-yandex
+
diff --git a/build-logic/convention/src/main/kotlin/com/yugyd/buildlogic/convention/buildtype/BuildTypeAndroidApplicationPlugin.kt b/build-logic/convention/src/main/kotlin/com/yugyd/buildlogic/convention/buildtype/BuildTypeAndroidApplicationPlugin.kt
index 229b1ec..1a16b41 100644
--- a/build-logic/convention/src/main/kotlin/com/yugyd/buildlogic/convention/buildtype/BuildTypeAndroidApplicationPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/com/yugyd/buildlogic/convention/buildtype/BuildTypeAndroidApplicationPlugin.kt
@@ -30,6 +30,8 @@ class BuildTypeAndroidApplicationPlugin : Plugin {
private val ACTIVE_PRODUCT_FLAVOR_VARIANTS = arrayOf("devDebug", "devStaging", "devRelease")
}
+ private val isProductFlavorFilterEnabled = true
+
override fun apply(target: Project) {
with(target) {
checkPlugin(ANDROID_APPLICATION_ALIAS)
@@ -46,7 +48,10 @@ class BuildTypeAndroidApplicationPlugin : Plugin {
// https://developer.android.com/build/build-variants#filter-variants
extensions.configure {
beforeVariants { variantBuilder ->
- if (!ACTIVE_PRODUCT_FLAVOR_VARIANTS.contains(variantBuilder.name)) {
+ if (
+ isProductFlavorFilterEnabled &&
+ !ACTIVE_PRODUCT_FLAVOR_VARIANTS.contains(variantBuilder.name)
+ ) {
variantBuilder.enable = false
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index e28e5cb..d55d847 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -6,7 +6,7 @@ kotlin = '2.0.21'
# UI
appcompat = '1.7.0'
material = '1.12.0'
-compose-bom = '2024.10.01'
+compose-bom = '2024.11.00'
# DI
hilt = '2.52'
# Annotation processor
@@ -21,13 +21,13 @@ androidxTest = '1.6.1'
core-ktx = '1.15.0'
roomVersion = '2.6.1'
workManagerVersion = '2.10.0'
-firebaseVersion = '33.5.1'
+firebaseVersion = '33.6.0'
accompanistVersion = '0.36.0'
versionsUpdates = '0.51.0'
# Build logic
compile-sdk = '35'
target-sdk = '35'
-min-sdk = '24'
+min-sdk = '26'
convention = "1.0.0"
[libraries]
@@ -74,8 +74,8 @@ compose-activity = { module = 'androidx.activity:activity-compose', version = '1
compose-viewmodel = { module = 'androidx.lifecycle:lifecycle-viewmodel-compose', version = '2.8.7' }
compose-lifecycle = { module = 'androidx.lifecycle:lifecycle-runtime-compose', version = '2.8.7' }
# Navigation
-navigation-runtime = { module = 'androidx.navigation:navigation-runtime-ktx', version = '2.8.3' }
-compose-navigation = { module = 'androidx.navigation:navigation-compose', version = '2.8.3' }
+navigation-runtime = { module = 'androidx.navigation:navigation-runtime-ktx', version = '2.8.4' }
+compose-navigation = { module = 'androidx.navigation:navigation-compose', version = '2.8.4' }
compose-hilt-navigation = { module = 'androidx.hilt:hilt-navigation-compose', version = '1.2.0' }
# Accompanist
accompanist-drawablepainter = { module = 'com.google.accompanist:accompanist-drawablepainter', version.ref = 'accompanistVersion' }
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/RemoteConfigRepositoryImpl.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/RemoteConfigRepositoryImpl.kt
index f103d8f..9a36f07 100644
--- a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/RemoteConfigRepositoryImpl.kt
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/RemoteConfigRepositoryImpl.kt
@@ -18,10 +18,13 @@ package com.yugyd.quiz.featuretoggle.data
import android.content.Context
import com.yugyd.quiz.featuretoggle.data.mapper.TelegramConfigMapper
+import com.yugyd.quiz.featuretoggle.data.mapper.UpdateConfigMapper
import com.yugyd.quiz.featuretoggle.data.model.TelegramConfigDto
+import com.yugyd.quiz.featuretoggle.data.model.UpdateConfigDto
import com.yugyd.quiz.featuretoggle.domain.RemoteConfigRepository
import com.yugyd.quiz.featuretoggle.domain.model.FeatureToggle
import com.yugyd.quiz.featuretoggle.domain.model.telegram.TelegramConfig
+import com.yugyd.quiz.featuretoggle.domain.model.update.UpdateConfig
import com.yugyd.quiz.remoteconfig.api.RemoteConfig
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.serialization.json.Json
@@ -33,6 +36,8 @@ internal class RemoteConfigRepositoryImpl @Inject internal constructor(
@ApplicationContext private val context: Context,
private val remoteConfig: RemoteConfig,
private val telegramConfigMapper: TelegramConfigMapper,
+ private val updateConfigMapper: UpdateConfigMapper,
+ private val json: Json,
) : RemoteConfigRepository {
override suspend fun fetchFeatureToggle(
@@ -45,7 +50,7 @@ internal class RemoteConfigRepositoryImpl @Inject internal constructor(
override suspend fun fetchTelegramConfig(): TelegramConfig? {
return try {
val dtoList = remoteConfig.fetchStringValue(CONFIG_TELEGRAM_KEY).run {
- Json.decodeFromString>(this)
+ json.decodeFromString>(this)
}
val mappedDtoList = dtoList.map(telegramConfigMapper::map)
@@ -63,8 +68,30 @@ internal class RemoteConfigRepositoryImpl @Inject internal constructor(
}
}
+ override suspend fun fetchUpdateConfig(): UpdateConfig? {
+ return try {
+ val dtoList = remoteConfig.fetchStringValue(CONFIG_UPDATE_KEY).run {
+ json.decodeFromString>(this)
+ }
+
+ val mappedDtoList = dtoList.map(updateConfigMapper::map)
+
+ val currentLocale = context.resources.configuration.locales.get(0)
+
+ mappedDtoList.firstOrNull {
+ it.locale == currentLocale
+ } ?: mappedDtoList.firstOrNull {
+ it.locale == Locale.ENGLISH
+ }
+ } catch (expected: Throwable) {
+ Timber.e(expected)
+ null
+ }
+ }
+
companion object {
private const val FORCE_UPDATE_KEY = "force_update_version"
private const val CONFIG_TELEGRAM_KEY = "config_telegram"
+ private const val CONFIG_UPDATE_KEY = "config_update"
}
}
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/LinkMapper.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/LinkMapper.kt
new file mode 100644
index 0000000..77c3a42
--- /dev/null
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/LinkMapper.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.featuretoggle.data.mapper
+
+import com.yugyd.quiz.featuretoggle.data.model.LinkDto
+import com.yugyd.quiz.featuretoggle.domain.model.telegram.Link
+import javax.inject.Inject
+
+internal class LinkMapper @Inject constructor() {
+
+ fun mapToLinks(links: List): List {
+ return links.map { Link(link = it.link, packageX = it.packageX) }
+ }
+}
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/LocaleMapper.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/LocaleMapper.kt
new file mode 100644
index 0000000..3eb6635
--- /dev/null
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/LocaleMapper.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.featuretoggle.data.mapper
+
+import java.util.Locale
+import javax.inject.Inject
+
+internal class LocaleMapper @Inject constructor() {
+
+ fun mapValueToLocale(value: String): Locale {
+ val language = if (value.contains(LOCALE_VALUE_SEPARATOR)) {
+ value.substringBefore(LOCALE_VALUE_SEPARATOR)
+ } else {
+ value
+ }
+ return Locale(language)
+ }
+
+ companion object {
+ private const val LOCALE_VALUE_SEPARATOR = "-"
+ }
+}
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/TelegramConfigMapper.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/TelegramConfigMapper.kt
index 24712dc..155e9be 100644
--- a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/TelegramConfigMapper.kt
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/TelegramConfigMapper.kt
@@ -18,25 +18,26 @@ package com.yugyd.quiz.featuretoggle.data.mapper
import com.yugyd.quiz.featuretoggle.data.model.TelegramConfigDto
import com.yugyd.quiz.featuretoggle.domain.model.telegram.GameEnd
-import com.yugyd.quiz.featuretoggle.domain.model.telegram.Link
import com.yugyd.quiz.featuretoggle.domain.model.telegram.MainPopup
import com.yugyd.quiz.featuretoggle.domain.model.telegram.ProfileCell
import com.yugyd.quiz.featuretoggle.domain.model.telegram.TelegramConfig
import com.yugyd.quiz.featuretoggle.domain.model.telegram.TrainPopup
-import java.util.Locale
import javax.inject.Inject
-class TelegramConfigMapper @Inject constructor() {
+class TelegramConfigMapper @Inject internal constructor(
+ private val localeMapper: LocaleMapper,
+ private val linkMapper: LinkMapper,
+) {
fun map(telegramConfigDto: TelegramConfigDto): TelegramConfig = telegramConfigDto.run {
return TelegramConfig(
- locale = locale.toLocale(),
+ locale = localeMapper.mapValueToLocale(locale),
gameEnd = GameEnd(
buttonTitle = gameEnd.buttonTitle,
message = gameEnd.message,
title = gameEnd.title,
),
- links = links.map { Link(link = it.link, packageX = it.packageX) },
+ links = linkMapper.mapToLinks(links),
mainPopup = MainPopup(
buttonTitle = mainPopup.buttonTitle,
title = mainPopup.title,
@@ -54,17 +55,4 @@ class TelegramConfigMapper @Inject constructor() {
)
)
}
-
- private fun String.toLocale(): Locale {
- val language = if (contains(LOCALE_VALUE_SEPARATOR)) {
- substringBefore(LOCALE_VALUE_SEPARATOR)
- } else {
- this
- }
- return Locale(language)
- }
-
- companion object {
- private const val LOCALE_VALUE_SEPARATOR = "-"
- }
}
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/UpdateConfigMapper.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/UpdateConfigMapper.kt
new file mode 100644
index 0000000..30c88e3
--- /dev/null
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/mapper/UpdateConfigMapper.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.featuretoggle.data.mapper
+
+import com.yugyd.quiz.featuretoggle.data.model.UpdateConfigDto
+import com.yugyd.quiz.featuretoggle.domain.model.update.UpdateConfig
+import javax.inject.Inject
+
+class UpdateConfigMapper @Inject internal constructor(
+ private val localeMapper: LocaleMapper,
+ private val linkMapper: LinkMapper,
+) {
+
+ fun map(configDto: UpdateConfigDto): UpdateConfig {
+ return UpdateConfig(
+ locale = localeMapper.mapValueToLocale(configDto.locale),
+ buttonTitle = configDto.mainScreen.buttonTitle,
+ message = configDto.mainScreen.message,
+ title = configDto.mainScreen.title,
+ links = linkMapper.mapToLinks(configDto.links),
+ )
+ }
+}
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/model/UpdateConfigDto.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/model/UpdateConfigDto.kt
new file mode 100644
index 0000000..25558d1
--- /dev/null
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/model/UpdateConfigDto.kt
@@ -0,0 +1,14 @@
+package com.yugyd.quiz.featuretoggle.data.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UpdateConfigDto(
+ @SerialName("locale")
+ val locale: String,
+ @SerialName("mainScreen")
+ val mainScreen: UpdateMainScreenDto,
+ @SerialName("links")
+ val links: List,
+)
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/model/UpdateMainScreenDto.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/model/UpdateMainScreenDto.kt
new file mode 100644
index 0000000..5e0a5b2
--- /dev/null
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/data/model/UpdateMainScreenDto.kt
@@ -0,0 +1,14 @@
+package com.yugyd.quiz.featuretoggle.data.model
+
+import kotlinx.serialization.SerialName
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class UpdateMainScreenDto(
+ @SerialName("buttonTitle")
+ val buttonTitle: String,
+ @SerialName("message")
+ val message: String,
+ @SerialName("title")
+ val title: String,
+)
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/RemoteConfigRepository.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/RemoteConfigRepository.kt
index 66bf90a..e71ed66 100644
--- a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/RemoteConfigRepository.kt
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/RemoteConfigRepository.kt
@@ -18,9 +18,11 @@ package com.yugyd.quiz.featuretoggle.domain
import com.yugyd.quiz.featuretoggle.domain.model.FeatureToggle
import com.yugyd.quiz.featuretoggle.domain.model.telegram.TelegramConfig
+import com.yugyd.quiz.featuretoggle.domain.model.update.UpdateConfig
interface RemoteConfigRepository {
suspend fun fetchFeatureToggle(featureToggle: FeatureToggle): Boolean
suspend fun fetchForceUpdateVersion(): Int
suspend fun fetchTelegramConfig(): TelegramConfig?
+ suspend fun fetchUpdateConfig(): UpdateConfig?
}
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/FeatureToggle.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/FeatureToggle.kt
index 7430ae3..f7507d8 100644
--- a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/FeatureToggle.kt
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/FeatureToggle.kt
@@ -17,7 +17,10 @@
package com.yugyd.quiz.featuretoggle.domain.model
enum class FeatureToggle(val key: String, val isLocal: Boolean, val localValue: Boolean = false) {
- AD(key = "feature_ad", isLocal = true, localValue = true),
+ AD(key = "feature_ad", isLocal = false),
+ AD_INTERSTITIAL_GAME_END(key = "feature_ad_interstitial_game_end", isLocal = false),
+ AD_BANNER_GAME(key = "feature_ad_banner_game", isLocal = false),
+ AD_REWARDED_THEME(key = "feature_ad_rewarded_theme", isLocal = false),
PRO(key = "feature_pro", isLocal = false),
CORRECT(key = "feature_correct", isLocal = false),
TELEGRAM(key = "feature_telegram", isLocal = false),
diff --git a/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/update/UpdateConfig.kt b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/update/UpdateConfig.kt
new file mode 100644
index 0000000..74fa9f9
--- /dev/null
+++ b/product/core/featuretoggle/src/main/java/com/yugyd/quiz/featuretoggle/domain/model/update/UpdateConfig.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.featuretoggle.domain.model.update
+
+import com.yugyd.quiz.featuretoggle.domain.model.telegram.Link
+import java.util.Locale
+
+data class UpdateConfig(
+ val locale: Locale,
+ val buttonTitle: String,
+ val message: String,
+ val title: String,
+ val links: List?,
+)
diff --git a/product/core/featuretoggle/src/main/res/xml/remote_config_defaults.xml b/product/core/featuretoggle/src/main/res/xml/remote_config_defaults.xml
index 107c686..ef77774 100644
--- a/product/core/featuretoggle/src/main/res/xml/remote_config_defaults.xml
+++ b/product/core/featuretoggle/src/main/res/xml/remote_config_defaults.xml
@@ -29,18 +29,38 @@
feature_ad
- false
+ true
+
+
+ feature_ad_interstitial_game_end
+ true
+
+
+ feature_ad_banner_game
+ true
+
+
+ feature_ad_rewarded_theme
+ true
config_telegram
- [{"locale":"en","gameEnd":{"title":"New record!","message":"Did you like it? Subscribe to the Telegram channel, daily questions and a lot of interesting things await you!","buttonTitle":"Follow"},"profileCell":{"title":"","message":"Daily questions and feedback"},"mainPopup":{"title":"We have opened a telegram channel!","message":"Daily questions and a lot of interesting things await you!","buttonTitle":"Follow"},"trainPopup":{"title":"We have opened a telegram channel!","message":"Daily questions and a lot of interesting things await you!","positiveButtonTitle":"Follow","negativeButtonTitle":"Later"},"links":[{"package":"com.yugyd.quiz","link":"quizplatformapp"},{"package":"com.yugyd.russianlanguagequiz","link":"russian_language_quiz"},{"package":"com.yugyd.russianhistoryquiz","link":"russian_history_quiz "},{"package":"com.yugyd.sociologyquiz","link":"sociology_quiz"},{"package":"com.yugyd.biologyquiz","link":"biology_quiz_app"},{"package":"com.yugyd.geographyquiz","link":"geography_quiz_app"}]},{"locale":"ru-RU","gameEnd":{"title":"Новый рекорд!","message":"Вам понравилось? Подпишитесь на телеграм канал, вас ждут ежедневные вопросы и много интересного!","buttonTitle":"Подписаться"},"profileCell":{"title":"","message":"Ежедневные вопросы и обратная связь"},"mainPopup":{"title":"Мы открыли телеграм канал!","message":"Вас ждут ежедневные вопросы и много интересного!","buttonTitle":"Подписаться"},"trainPopup":{"title":"Мы открыли телеграм канал!","message":"Вас ждут ежедневные вопросы и много интересного!","positiveButtonTitle":"Подписаться","negativeButtonTitle":"Позже"},"links":[{"package":"com.yugyd.quiz","link":"quizplatformapp"},{"package":"com.yugyd.russianlanguagequiz","link":"russian_language_quiz"},{"package":"com.yugyd.russianhistoryquiz","link":"russian_history_quiz "},{"package":"com.yugyd.sociologyquiz","link":"sociology_quiz"},{"package":"com.yugyd.biologyquiz","link":"biology_quiz_app"},{"package":"com.yugyd.geographyquiz","link":"geography_quiz_app"}]}]
+ [{"locale":"en","gameEnd":{"title":"New record!","message":"Did you like it? Subscribe to the Telegram channel, daily questions and a lot of interesting things await you!","buttonTitle":"Follow"},"profileCell":{"title":"","message":"Daily questions and feedback"},"mainPopup":{"title":"We have opened a telegram channel!","message":"Daily questions and a lot of interesting things await you!","buttonTitle":"Follow"},"trainPopup":{"title":"We have opened a telegram channel!","message":"Daily questions and a lot of interesting things await you!","positiveButtonTitle":"Follow","negativeButtonTitle":"Later"},"links":[{"package":"com.yugyd.quiz","link":"quizplatformapp"}]},{"locale":"ru-RU","gameEnd":{"title":"Новый рекорд!","message":"Вам понравилось? Подпишитесь на телеграм канал, вас ждут ежедневные вопросы и много интересного!","buttonTitle":"Подписаться"},"profileCell":{"title":"","message":"Ежедневные вопросы и обратная связь"},"mainPopup":{"title":"Мы открыли телеграм канал!","message":"Вас ждут ежедневные вопросы и много интересного!","buttonTitle":"Подписаться"},"trainPopup":{"title":"Мы открыли телеграм канал!","message":"Вас ждут ежедневные вопросы и много интересного!","positiveButtonTitle":"Подписаться","negativeButtonTitle":"Позже"},"links":[{"package":"com.yugyd.quiz","link":"quizplatformapp"}]}]
force_update_version
- 0
+ 47
feature_telegram
true
+
+ quest_query_format
+ https://www.google.ru/search?q=%s%20%s
+
+
+ config_update
+ [{"locale":"en","mainScreen":{"title":"Update is out","message":"New features and content available to you","buttonTitle":"Update"},"links":[{"package":"com.yugyd.quiz","link":""}]},{"locale":"ru-RU","mainScreen":{"title":"Вышло обновление","message":"Новые функции и контент доступны для вас","buttonTitle":"Обновить"},"links":[{"package":"com.yugyd.quiz","link":""}]}]
+
\ No newline at end of file
diff --git a/product/core/search-utils/.gitignore b/product/core/search-utils/.gitignore
new file mode 100644
index 0000000..42afabf
--- /dev/null
+++ b/product/core/search-utils/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/product/core/search-utils/build.gradle b/product/core/search-utils/build.gradle
new file mode 100644
index 0000000..24577bf
--- /dev/null
+++ b/product/core/search-utils/build.gradle
@@ -0,0 +1,21 @@
+plugins {
+ alias(libs.plugins.convention.library)
+ alias(libs.plugins.convention.library.buildtype)
+ alias(libs.plugins.convention.library.jacoco)
+ alias(libs.plugins.convention.library.lint)
+ alias(libs.plugins.convention.library.test)
+ alias(libs.plugins.convention.hilt)
+}
+
+android {
+ namespace 'com.yugyd.quiz.core.searchutils'
+}
+
+dependencies {
+ implementation project(':product:core:coroutines-utils')
+ implementation project(':product:core')
+ implementation(project(":product:services:remoteconfig-api"))
+
+ // Logging
+ implementation libs.timber
+}
diff --git a/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryBlModule.kt b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryBlModule.kt
new file mode 100644
index 0000000..4c4c2fc
--- /dev/null
+++ b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryBlModule.kt
@@ -0,0 +1,19 @@
+package com.yugyd.quiz.core.searchutils
+
+import com.yugyd.quiz.core.searchutils.data.QueryFormatRepositoryImpl
+import com.yugyd.quiz.core.searchutils.data.QuestQueryUrlBuilderImpl
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@Module
+@InstallIn(SingletonComponent::class)
+abstract class QueryBlModule {
+
+ @Binds
+ internal abstract fun bindQueryUrlBuilder(impl: QuestQueryUrlBuilderImpl): QueryUrlBuilder
+
+ @Binds
+ internal abstract fun bindFormatRepository(impl: QueryFormatRepositoryImpl): QueryFormatRepository
+}
diff --git a/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryFormatRepository.kt b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryFormatRepository.kt
new file mode 100644
index 0000000..526d46a
--- /dev/null
+++ b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryFormatRepository.kt
@@ -0,0 +1,5 @@
+package com.yugyd.quiz.core.searchutils
+
+interface QueryFormatRepository {
+ suspend fun getFormatFromRemoteConfig(): String
+}
diff --git a/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryUrlBuilder.kt b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryUrlBuilder.kt
new file mode 100644
index 0000000..985bf1a
--- /dev/null
+++ b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/QueryUrlBuilder.kt
@@ -0,0 +1,5 @@
+package com.yugyd.quiz.core.searchutils
+
+interface QueryUrlBuilder {
+ fun buildUrl(quest: SearchQuest, queryFormat: String): String
+}
diff --git a/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/SearchQuest.kt b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/SearchQuest.kt
new file mode 100644
index 0000000..e515bf6
--- /dev/null
+++ b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/SearchQuest.kt
@@ -0,0 +1,6 @@
+package com.yugyd.quiz.core.searchutils
+
+data class SearchQuest(
+ val quest: String,
+ val trueAnswer: String,
+)
diff --git a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/data/QueryFormatRepositoryImpl.kt b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/data/QueryFormatRepositoryImpl.kt
similarity index 85%
rename from product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/data/QueryFormatRepositoryImpl.kt
rename to product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/data/QueryFormatRepositoryImpl.kt
index c53ef8c..d1c0ac7 100644
--- a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/data/QueryFormatRepositoryImpl.kt
+++ b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/data/QueryFormatRepositoryImpl.kt
@@ -1,7 +1,7 @@
-package com.yugyd.quiz.domain.tasks.data
+package com.yugyd.quiz.core.searchutils.data
import com.yugyd.quiz.core.coroutinesutils.DispatchersProvider
-import com.yugyd.quiz.domain.tasks.QueryFormatRepository
+import com.yugyd.quiz.core.searchutils.QueryFormatRepository
import com.yugyd.quiz.remoteconfig.api.RemoteConfig
import kotlinx.coroutines.withContext
import javax.inject.Inject
diff --git a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QuestQueryUrlBuilderImpl.kt b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/data/QuestQueryUrlBuilderImpl.kt
similarity index 63%
rename from product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QuestQueryUrlBuilderImpl.kt
rename to product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/data/QuestQueryUrlBuilderImpl.kt
index 7ce5ff8..01c2970 100644
--- a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QuestQueryUrlBuilderImpl.kt
+++ b/product/core/search-utils/src/main/java/com/yugyd/quiz/core/searchutils/data/QuestQueryUrlBuilderImpl.kt
@@ -1,7 +1,8 @@
-package com.yugyd.quiz.domain.tasks
+package com.yugyd.quiz.core.searchutils.data
import com.yugyd.quiz.core.Logger
-import com.yugyd.quiz.domain.game.api.model.Quest
+import com.yugyd.quiz.core.searchutils.QueryUrlBuilder
+import com.yugyd.quiz.core.searchutils.SearchQuest
import java.util.IllegalFormatException
import javax.inject.Inject
@@ -9,13 +10,13 @@ internal class QuestQueryUrlBuilderImpl @Inject constructor(
private val logger: Logger,
) : QueryUrlBuilder {
- override fun buildUrl(quest: Quest, queryFormat: String): String {
+ override fun buildUrl(quest: SearchQuest, queryFormat: String): String {
if (queryFormat.isEmpty()) {
return ""
}
return try {
- String.format(queryFormat, quest.quest)
+ String.format(queryFormat, quest.quest, quest.trueAnswer)
} catch (error: IllegalFormatException) {
logger.logError(TAG, error)
""
diff --git a/product/core/src/main/java/com/yugyd/quiz/core/ContentProvider.kt b/product/core/src/main/java/com/yugyd/quiz/core/ContentProvider.kt
index 1d48117..c3ab973 100644
--- a/product/core/src/main/java/com/yugyd/quiz/core/ContentProvider.kt
+++ b/product/core/src/main/java/com/yugyd/quiz/core/ContentProvider.kt
@@ -18,4 +18,5 @@ package com.yugyd.quiz.core
interface ContentProvider {
suspend fun getTelegramChannel(): String
+ suspend fun getUpdateLink(): String?
}
diff --git a/product/end/end-ui/src/main/java/com/yugyd/quiz/ui/end/gameend/GameEndViewModel.kt b/product/end/end-ui/src/main/java/com/yugyd/quiz/ui/end/gameend/GameEndViewModel.kt
index a531975..834b688 100644
--- a/product/end/end-ui/src/main/java/com/yugyd/quiz/ui/end/gameend/GameEndViewModel.kt
+++ b/product/end/end-ui/src/main/java/com/yugyd/quiz/ui/end/gameend/GameEndViewModel.kt
@@ -110,7 +110,8 @@ internal class GameEndViewModel @Inject constructor(
vmScopeErrorHandled.launch {
screenState = screenState.copy(isLoading = true)
- val isAdFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.AD)
+ val isAdFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.AD) &&
+ featureManager.isFeatureEnabled(FeatureToggle.AD_INTERSTITIAL_GAME_END)
runCatch(
block = {
diff --git a/product/errors/errors-bl/build.gradle b/product/errors/errors-bl/build.gradle
index 4c791f4..18e2bff 100644
--- a/product/errors/errors-bl/build.gradle
+++ b/product/errors/errors-bl/build.gradle
@@ -14,6 +14,7 @@ android {
dependencies {
// Modules
implementation project(":product:core:coroutines-utils")
+ implementation project(':product:core:search-utils')
implementation project(':product:shared:domain-api')
implementation project(':product:shared:domain-utils')
implementation project(':product:game:game-bl-api')
diff --git a/product/errors/errors-bl/src/main/java/com/yugyd/quiz/domain/errors/ErrorInteractorImpl.kt b/product/errors/errors-bl/src/main/java/com/yugyd/quiz/domain/errors/ErrorInteractorImpl.kt
index 9897725..a3e637a 100644
--- a/product/errors/errors-bl/src/main/java/com/yugyd/quiz/domain/errors/ErrorInteractorImpl.kt
+++ b/product/errors/errors-bl/src/main/java/com/yugyd/quiz/domain/errors/ErrorInteractorImpl.kt
@@ -17,6 +17,9 @@
package com.yugyd.quiz.domain.errors
import com.yugyd.quiz.core.coroutinesutils.DispatchersProvider
+import com.yugyd.quiz.core.searchutils.QueryFormatRepository
+import com.yugyd.quiz.core.searchutils.QueryUrlBuilder
+import com.yugyd.quiz.core.searchutils.SearchQuest
import com.yugyd.quiz.domain.api.model.tasks.TaskModel
import com.yugyd.quiz.domain.api.repository.ErrorSource
import com.yugyd.quiz.domain.api.repository.QuestSource
@@ -30,12 +33,14 @@ internal class ErrorInteractorImpl @Inject constructor(
private val errorSource: ErrorSource,
private val separatorParser: SeparatorParser,
private val dispatcherProvider: DispatchersProvider,
+ private val queryUrlBuilder: QueryUrlBuilder,
+ private val queryFormatRepository: QueryFormatRepository,
) : ErrorInteractor {
override suspend fun getErrorsModels(errors: List) = withContext(dispatcherProvider.io) {
- questSource
- .getQuestIdsByErrors(errors.toIntArray())
- .let(::mapQuests)
+ val queryFormat = queryFormatRepository.getFormatFromRemoteConfig()
+ val errorsModels = questSource.getQuestIdsByErrors(errors.toIntArray())
+ mapQuests(errorsModels, queryFormat)
}
override suspend fun getErrors() = withContext(dispatcherProvider.io) {
@@ -54,18 +59,19 @@ internal class ErrorInteractorImpl @Inject constructor(
errorSource.removeErrors(errors)
}
- private fun mapQuests(quests: List) = quests
+ private fun mapQuests(quests: List, queryFormat: String) = quests
.map(separatorParser::parseErrorQuest)
- .map { it.toErrorModel() }
+ .map { it.toErrorModel(queryFormat) }
- private fun Quest.toErrorModel() = TaskModel(
+ private fun Quest.toErrorModel(queryFormat: String) = TaskModel(
id = id,
quest = quest,
trueAnswer = trueAnswer,
- queryLink = "$GOOGLE_SEARCH_URL$quest$trueAnswer"
+ queryLink = queryUrlBuilder.buildUrl(this.toSearchQuest(), queryFormat),
)
- companion object {
- private const val GOOGLE_SEARCH_URL = "https://www.google.ru/search?q="
- }
+ private fun Quest.toSearchQuest() = SearchQuest(
+ quest = quest,
+ trueAnswer = trueAnswer,
+ )
}
diff --git a/product/game/game-ui/src/main/java/com/yugyd/quiz/gameui/game/GameViewModel.kt b/product/game/game-ui/src/main/java/com/yugyd/quiz/gameui/game/GameViewModel.kt
index 7780bc0..3cbd98d 100644
--- a/product/game/game-ui/src/main/java/com/yugyd/quiz/gameui/game/GameViewModel.kt
+++ b/product/game/game-ui/src/main/java/com/yugyd/quiz/gameui/game/GameViewModel.kt
@@ -232,7 +232,8 @@ internal class GameViewModel @Inject constructor(
block = {
val gameData = gameInteractor.startGame(screenState.payload)
- val isAdFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.AD)
+ val isAdFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.AD) &&
+ featureManager.isFeatureEnabled(FeatureToggle.AD_BANNER_GAME)
val isProFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.PRO)
processGameData(
diff --git a/product/main/main-ui/src/main/java/com/yugyd/quiz/ui/main/QuizNavHost.kt b/product/main/main-ui/src/main/java/com/yugyd/quiz/ui/main/QuizNavHost.kt
index 1bd29ea..7377def 100644
--- a/product/main/main-ui/src/main/java/com/yugyd/quiz/ui/main/QuizNavHost.kt
+++ b/product/main/main-ui/src/main/java/com/yugyd/quiz/ui/main/QuizNavHost.kt
@@ -76,6 +76,9 @@ internal fun QuizNavHost(
navigateToGooglePlay = {
navigateToExternalScreen(GlobalScreens.rate())
},
+ navigateToBrowser = {
+ navigateToExternalScreen(GlobalScreens.externalBrowser(it))
+ },
)
sectionScreen(
diff --git a/product/profile/profile-ui/src/main/java/com/yugyd/quiz/ui/profile/model/ProfileUiMapper.kt b/product/profile/profile-ui/src/main/java/com/yugyd/quiz/ui/profile/model/ProfileUiMapper.kt
index c0d255d..d49507c 100644
--- a/product/profile/profile-ui/src/main/java/com/yugyd/quiz/ui/profile/model/ProfileUiMapper.kt
+++ b/product/profile/profile-ui/src/main/java/com/yugyd/quiz/ui/profile/model/ProfileUiMapper.kt
@@ -52,7 +52,14 @@ internal class ProfileUiMapper @Inject constructor(
header(content, isProFeatureEnabled),
item(TypeProfile.TASKS, R.string.profile_tasks),
mapContentToValueItem(contentTitle, isContentFeatureEnabled),
- section(TypeProfile.SOCIAL_SECTION, R.string.title_social, isTelegramFeatureEnabled),
+ section(
+ type = TypeProfile.SOCIAL_SECTION,
+ titleRes = R.string.title_social,
+ isSectionEnabled = isTelegramEnabledAndTelegramConfigNotNull(
+ isTelegramFeatureEnabled = isTelegramFeatureEnabled,
+ telegramConfig = telegramConfig,
+ ),
+ ),
social(TypeProfile.TELEGRAM_SOCIAL, isTelegramFeatureEnabled, telegramConfig),
section(TypeProfile.SETTINGS_SECTION, R.string.title_settings),
value(TypeProfile.TRANSITION, R.string.title_show_answer, transition.value),
@@ -148,8 +155,10 @@ internal class ProfileUiMapper @Inject constructor(
isTelegramFeatureEnabled: Boolean,
telegramConfig: TelegramConfig?
): SocialItemProfileUiModel? {
- return if (isTelegramFeatureEnabled && telegramConfig != null) {
- val telegramTitle = telegramConfig.profileCell.title.ifBlank {
+ return if (
+ isTelegramEnabledAndTelegramConfigNotNull(isTelegramFeatureEnabled, telegramConfig)
+ ) {
+ val telegramTitle = requireNotNull(telegramConfig).profileCell.title.ifBlank {
context.getString(R.string.profile_title_telegram)
}
val telegramMsg = telegramConfig.profileCell.message.ifBlank {
@@ -167,6 +176,11 @@ internal class ProfileUiMapper @Inject constructor(
}
}
+ private fun isTelegramEnabledAndTelegramConfigNotNull(
+ isTelegramFeatureEnabled: Boolean,
+ telegramConfig: TelegramConfig?,
+ ) = isTelegramFeatureEnabled && telegramConfig != null
+
private fun getPurchaseSection(isProFeatureEnabled: Boolean): SelectItemProfileUiModel? {
return if (isProFeatureEnabled) {
item(TypeProfile.PRO, R.string.title_pro_version)
diff --git a/product/shared/data/schemas/com.yugyd.quiz.data.database.user.UserDatabase/2.json b/product/shared/data/schemas/com.yugyd.quiz.data.database.user.UserDatabase/2.json
new file mode 100644
index 0000000..f8b38da
--- /dev/null
+++ b/product/shared/data/schemas/com.yugyd.quiz.data.database.user.UserDatabase/2.json
@@ -0,0 +1,194 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "6d0f7e0684fc2e518f18996db471ae40",
+ "entities": [
+ {
+ "tableName": "error",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "mode",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, `title` TEXT NOT NULL, PRIMARY KEY(`_id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "record",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `theme_id` INTEGER NOT NULL, `mode_id` INTEGER NOT NULL, `record` INTEGER NOT NULL, `complexity` INTEGER, `total_time` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "themeId",
+ "columnName": "theme_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "modeId",
+ "columnName": "mode_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "record",
+ "columnName": "record",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "complexity",
+ "columnName": "complexity",
+ "affinity": "INTEGER",
+ "notNull": false
+ },
+ {
+ "fieldPath": "totalTime",
+ "columnName": "total_time",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "section",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "train",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER NOT NULL, PRIMARY KEY(`_id`))",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": false,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "content",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `file_path` TEXT NOT NULL, `is_checked` INTEGER NOT NULL, `content_marker` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "filePath",
+ "columnName": "file_path",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isChecked",
+ "columnName": "is_checked",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "contentMarker",
+ "columnName": "content_marker",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "_id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6d0f7e0684fc2e518f18996db471ae40')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/product/shared/data/src/main/java/com/yugyd/quiz/data/crypto/CryptoHelperImpl.kt b/product/shared/data/src/main/java/com/yugyd/quiz/data/crypto/CryptoHelperImpl.kt
index ebe5743..3f3f2df 100644
--- a/product/shared/data/src/main/java/com/yugyd/quiz/data/crypto/CryptoHelperImpl.kt
+++ b/product/shared/data/src/main/java/com/yugyd/quiz/data/crypto/CryptoHelperImpl.kt
@@ -16,17 +16,35 @@
package com.yugyd.quiz.data.crypto
+import java.util.concurrent.locks.ReentrantLock
import javax.inject.Inject
+import javax.inject.Singleton
+import kotlin.concurrent.withLock
+@Singleton
internal class CryptoHelperImpl @Inject constructor() : CryptoHelper {
+ private val lock = ReentrantLock()
+
override fun decrypt(encrypted: String?): String {
- // Add your encrypted logic
- return encrypted.orEmpty()
+ return if (encrypted?.isNotEmpty() == true) {
+ lock.withLock {
+ // Add your encrypted logic
+ encrypted
+ }
+ } else {
+ ""
+ }
}
override fun encrypt(decrypted: String?): String {
- // Add your encrypted logic
- return decrypted.orEmpty()
+ return if (decrypted?.isNotEmpty() == true) {
+ lock.withLock {
+ // Add your encrypted logic
+ decrypted
+ }
+ } else {
+ ""
+ }
}
}
diff --git a/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/UserDatabase.kt b/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/UserDatabase.kt
index 4f3def4..65f309e 100644
--- a/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/UserDatabase.kt
+++ b/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/UserDatabase.kt
@@ -15,7 +15,7 @@ import com.yugyd.quiz.data.model.RecordEntity
import com.yugyd.quiz.data.model.SectionEntity
import com.yugyd.quiz.data.model.TrainEntity
-private const val USER_DB_VERSION = 1
+private const val USER_DB_VERSION = 2
@Database(
entities = [
@@ -27,7 +27,7 @@ private const val USER_DB_VERSION = 1
ContentEntity::class,
],
version = USER_DB_VERSION,
- exportSchema = false
+ exportSchema = true
)
internal abstract class UserDatabase : RoomDatabase() {
abstract fun errorDao(): ErrorDao
diff --git a/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/migrations/Migration1To2.kt b/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/migrations/Migration1To2.kt
new file mode 100644
index 0000000..ebf77c9
--- /dev/null
+++ b/product/shared/data/src/main/java/com/yugyd/quiz/data/database/user/migrations/Migration1To2.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.data.database.user.migrations
+
+import androidx.room.migration.Migration
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+val MIGRATION_1_2 = object : Migration(1, 2) {
+ override fun migrate(db: SupportSQLiteDatabase) {
+ // Create the new 'content' table
+ db.execSQL(
+ """
+ CREATE TABLE content (
+ _id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+ name TEXT NOT NULL,
+ file_path TEXT NOT NULL,
+ is_checked INTEGER NOT NULL,
+ content_marker TEXT NOT NULL
+ )
+ """.trimIndent()
+ )
+ }
+}
diff --git a/product/shared/data/src/main/java/com/yugyd/quiz/data/di/UserDatabaseModule.kt b/product/shared/data/src/main/java/com/yugyd/quiz/data/di/UserDatabaseModule.kt
index 1de01ad..46765b0 100644
--- a/product/shared/data/src/main/java/com/yugyd/quiz/data/di/UserDatabaseModule.kt
+++ b/product/shared/data/src/main/java/com/yugyd/quiz/data/di/UserDatabaseModule.kt
@@ -9,6 +9,7 @@ import com.yugyd.quiz.data.database.user.dao.RecordDao
import com.yugyd.quiz.data.database.user.dao.SectionDao
import com.yugyd.quiz.data.database.user.dao.TrainDao
import com.yugyd.quiz.data.database.user.dao.UserResetDao
+import com.yugyd.quiz.data.database.user.migrations.MIGRATION_1_2
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -28,6 +29,7 @@ object UserDatabaseModule {
@ApplicationContext appContext: Context
) = Room
.databaseBuilder(appContext, UserDatabase::class.java, USER_DB_NAME)
+ .addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration()
.build()
diff --git a/product/tasks/tasks-bl/build.gradle b/product/tasks/tasks-bl/build.gradle
index aa17ce6..7082df2 100644
--- a/product/tasks/tasks-bl/build.gradle
+++ b/product/tasks/tasks-bl/build.gradle
@@ -19,6 +19,7 @@ dependencies {
implementation project(':product:shared:domain-utils')
implementation project(':product:services:remoteconfig-api')
implementation project(':product:game:game-bl-api')
+ implementation project(':product:core:search-utils')
// Kotlin
implementation libs.kotlinx.coroutines.android
diff --git a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QueryFormatRepository.kt b/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QueryFormatRepository.kt
deleted file mode 100644
index e2bcfe7..0000000
--- a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QueryFormatRepository.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.yugyd.quiz.domain.tasks
-
-internal interface QueryFormatRepository {
- suspend fun getFormatFromRemoteConfig(): String
-}
diff --git a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QueryUrlBuilder.kt b/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QueryUrlBuilder.kt
deleted file mode 100644
index 1366041..0000000
--- a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/QueryUrlBuilder.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.yugyd.quiz.domain.tasks
-
-import com.yugyd.quiz.domain.game.api.model.Quest
-
-internal interface QueryUrlBuilder {
- fun buildUrl(quest: Quest, queryFormat: String): String
-}
\ No newline at end of file
diff --git a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksBlModule.kt b/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksBlModule.kt
index ea0a0f3..19676b1 100644
--- a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksBlModule.kt
+++ b/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksBlModule.kt
@@ -1,6 +1,5 @@
package com.yugyd.quiz.domain.tasks
-import com.yugyd.quiz.domain.tasks.data.QueryFormatRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@@ -15,10 +14,4 @@ abstract class TasksBlModule {
@Binds
internal abstract fun bindFilterInteractor(impl: FilterInteractorImpl): FilterInteractor
-
- @Binds
- internal abstract fun bindQueryUrlBuilder(impl: QuestQueryUrlBuilderImpl): QueryUrlBuilder
-
- @Binds
- internal abstract fun bindFormatRepository(impl: QueryFormatRepositoryImpl): QueryFormatRepository
}
diff --git a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksInteractorImpl.kt b/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksInteractorImpl.kt
index adf162f..1277961 100644
--- a/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksInteractorImpl.kt
+++ b/product/tasks/tasks-bl/src/main/java/com/yugyd/quiz/domain/tasks/TasksInteractorImpl.kt
@@ -17,6 +17,9 @@
package com.yugyd.quiz.domain.tasks
import com.yugyd.quiz.core.coroutinesutils.DispatchersProvider
+import com.yugyd.quiz.core.searchutils.QueryFormatRepository
+import com.yugyd.quiz.core.searchutils.QueryUrlBuilder
+import com.yugyd.quiz.core.searchutils.SearchQuest
import com.yugyd.quiz.domain.api.repository.QuestSource
import com.yugyd.quiz.domain.game.api.model.Quest
import com.yugyd.quiz.domain.tasks.model.TaskModel
@@ -46,8 +49,13 @@ internal class TasksInteractorImpl @Inject constructor(
id = id,
quest = quest,
trueAnswer = trueAnswer,
- queryLink = queryUrlBuilder.buildUrl(this, queryFormat),
+ queryLink = queryUrlBuilder.buildUrl(this.toSearchQuest(), queryFormat),
complexity = complexity,
)
+
+ private fun Quest.toSearchQuest() = SearchQuest(
+ quest = quest,
+ trueAnswer = trueAnswer,
+ )
}
diff --git a/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/ThemeViewModel.kt b/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/ThemeViewModel.kt
index c613c7f..1b30ce3 100644
--- a/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/ThemeViewModel.kt
+++ b/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/ThemeViewModel.kt
@@ -80,7 +80,8 @@ internal class ThemeViewModel @Inject constructor(
recordController.subscribe(this)
vmScopeErrorHandled.launch {
- val isAdFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.AD)
+ val isAdFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.AD) &&
+ featureManager.isFeatureEnabled(FeatureToggle.AD_REWARDED_THEME)
val isTelegramFeatureEnabled = featureManager.isFeatureEnabled(FeatureToggle.TELEGRAM)
loadData(
diff --git a/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/model/ThemeUiMapper.kt b/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/model/ThemeUiMapper.kt
index 11789da..881cccf 100644
--- a/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/model/ThemeUiMapper.kt
+++ b/product/theme/theme-ui/src/main/java/com/yugyd/quiz/ui/theme/model/ThemeUiMapper.kt
@@ -28,8 +28,13 @@ internal class ThemeUiMapper @Inject constructor(
fun map(model: Theme) = model.run {
val progressPercent = percent(progress, count)
+
val imageUri = if (model.image != null) {
- Uri.parse(image)
+ if (model.image!!.isHttpUrl()) {
+ Uri.parse(model.image)
+ } else {
+ Uri.parse("file:///android_asset/${model.image}")
+ }
} else {
null
}
@@ -43,4 +48,8 @@ internal class ThemeUiMapper @Inject constructor(
record = progress
)
}
+
+ private fun String.isHttpUrl(): Boolean {
+ return startsWith("http://") || startsWith("https://")
+ }
}
diff --git a/product/update/update-ui/build.gradle b/product/update/update-ui/build.gradle
index 56b002c..7862074 100644
--- a/product/update/update-ui/build.gradle
+++ b/product/update/update-ui/build.gradle
@@ -5,6 +5,7 @@ plugins {
alias(libs.plugins.convention.library.jacoco)
alias(libs.plugins.convention.library.lint)
alias(libs.plugins.convention.library.test)
+ alias(libs.plugins.convention.hilt)
}
android {
@@ -13,12 +14,20 @@ android {
dependencies {
// Module
- implementation project(':product:designsystem:uikit')
+ implementation project(':product:core:featuretoggle')
+ implementation project(':product:core:common-ui')
+ implementation project(':product:core:coroutines-utils')
implementation project(':product:core:navigation')
+ implementation project(':product:core')
+ implementation project(':product:designsystem:uikit')
// UI - Compose
implementation libs.compose.material3
+ implementation libs.compose.material.icons
+ implementation libs.compose.viewmodel
+ implementation libs.compose.lifecycle
// Navigation
+ implementation libs.compose.hilt.navigation
implementation libs.compose.navigation
}
diff --git a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateNavigation.kt b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateNavigation.kt
index a67f1ae..6f260e4 100644
--- a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateNavigation.kt
+++ b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateNavigation.kt
@@ -30,12 +30,14 @@ fun NavController.navigateToUpdate() {
fun NavGraphBuilder.updateScreen(
navigateToGooglePlay: () -> Unit,
+ navigateToBrowser: (String) -> Unit,
) {
composable(
route = UPDATE_ROUTE,
arguments = listOf(hideBottomBarArgument),
) {
UpdateRoute(
+ navigateToBrowser = navigateToBrowser,
navigateToGooglePlay = navigateToGooglePlay,
)
}
diff --git a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateScreen.kt b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateScreen.kt
index 00d1ec1..3ba1a23 100644
--- a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateScreen.kt
+++ b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateScreen.kt
@@ -28,30 +28,53 @@ import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.yugyd.quiz.uikit.common.ThemePreviews
import com.yugyd.quiz.uikit.component.QuizBackground
import com.yugyd.quiz.uikit.theme.QuizApplicationTheme
+import com.yugyd.quiz.update.UpdateView.Action
+import com.yugyd.quiz.update.UpdateView.State
+import com.yugyd.quiz.update.UpdateView.State.NavigationState
+import com.yugyd.quiz.update.UpdateView.State.UpdateConfigUiModel
import com.yugyd.quiz.uikit.R as UiKitR
@Composable
-fun UpdateRoute(
+internal fun UpdateRoute(
+ viewModel: UpdateViewModel = hiltViewModel(),
navigateToGooglePlay: () -> Unit,
+ navigateToBrowser: (String) -> Unit,
) {
+ val state by viewModel.state.collectAsStateWithLifecycle()
+
UpdateScreen(
+ model = state,
navigateToGooglePlay = navigateToGooglePlay,
+ navigateToBrowser = navigateToBrowser,
+ onUpdateClicked = {
+ viewModel.onAction(Action.OnUpdateClicked)
+ },
+ onNavigationHandled = {
+ viewModel.onAction(Action.OnNavigationHandled)
+ }
)
}
@Composable
internal fun UpdateScreen(
+ model: State,
navigateToGooglePlay: () -> Unit,
+ navigateToBrowser: (String) -> Unit,
+ onUpdateClicked: () -> Unit,
+ onNavigationHandled: () -> Unit,
) {
Column(
modifier = Modifier
@@ -72,7 +95,7 @@ internal fun UpdateScreen(
Spacer(modifier = Modifier.height(32.dp))
Text(
- text = stringResource(id = R.string.title_update),
+ text = model.updateConfig.title,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onBackground,
)
@@ -80,7 +103,7 @@ internal fun UpdateScreen(
Spacer(modifier = Modifier.height(16.dp))
Text(
- text = stringResource(id = R.string.title_update_description),
+ text = model.updateConfig.message,
color = MaterialTheme.colorScheme.onBackground,
textAlign = TextAlign.Center,
style = MaterialTheme.typography.bodyLarge,
@@ -89,11 +112,43 @@ internal fun UpdateScreen(
Spacer(modifier = Modifier.height(16.dp))
Button(
- onClick = navigateToGooglePlay,
+ onClick = onUpdateClicked,
) {
- Text(text = stringResource(id = R.string.action_update))
+ Text(text = model.updateConfig.buttonTitle)
}
}
+
+ NavigationHandler(
+ navigationState = model.navigationState,
+ onNavigationHandled = onNavigationHandled,
+ navigateToGooglePlay = navigateToGooglePlay,
+ navigateToBrowser = navigateToBrowser,
+ )
+}
+
+@Composable
+internal fun NavigationHandler(
+ navigationState: NavigationState?,
+ navigateToGooglePlay: () -> Unit,
+ navigateToBrowser: (String) -> Unit,
+ onNavigationHandled: () -> Unit,
+) {
+ LaunchedEffect(key1 = navigationState) {
+ when (navigationState) {
+ is NavigationState.NavigateToGooglePlay -> {
+ if (!navigationState.storeLink.isNullOrEmpty()) {
+ navigateToBrowser(navigationState.storeLink)
+ } else {
+ navigateToGooglePlay()
+
+ }
+ }
+
+ null -> Unit
+ }
+
+ navigationState?.let { onNavigationHandled() }
+ }
}
@ThemePreviews
@@ -102,7 +157,19 @@ private fun UpdateScreenPreview() {
QuizApplicationTheme {
QuizBackground {
UpdateScreen(
- navigateToGooglePlay = {}
+ State(
+ updateConfig = UpdateConfigUiModel(
+ buttonTitle = "Button",
+ message = "Message",
+ title = "Title",
+ ),
+ isLoading = false,
+ navigationState = null,
+ ),
+ onNavigationHandled = {},
+ onUpdateClicked = {},
+ navigateToGooglePlay = {},
+ navigateToBrowser = {},
)
}
}
diff --git a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateView.kt b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateView.kt
new file mode 100644
index 0000000..8d8355c
--- /dev/null
+++ b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateView.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.update
+
+internal interface UpdateView {
+
+ data class State(
+ val updateConfig: UpdateConfigUiModel = UpdateConfigUiModel(),
+ val isLoading: Boolean = false,
+ val navigationState: NavigationState? = null,
+ ) {
+
+ data class UpdateConfigUiModel(
+ val buttonTitle: String = "",
+ val message: String = "",
+ val title: String = "",
+ )
+
+ sealed interface NavigationState {
+ data class NavigateToGooglePlay(val storeLink: String? = null) : NavigationState
+ }
+ }
+
+ sealed interface Action {
+ data object OnUpdateClicked : Action
+ data object OnNavigationHandled : Action
+ }
+}
diff --git a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateViewModel.kt b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateViewModel.kt
new file mode 100644
index 0000000..7e258b1
--- /dev/null
+++ b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/UpdateViewModel.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.update
+
+import com.yugyd.quiz.commonui.base.BaseViewModel
+import com.yugyd.quiz.core.ContentProvider
+import com.yugyd.quiz.core.Logger
+import com.yugyd.quiz.core.coroutinesutils.DispatchersProvider
+import com.yugyd.quiz.core.runCatch
+import com.yugyd.quiz.featuretoggle.domain.RemoteConfigRepository
+import com.yugyd.quiz.update.UpdateView.Action
+import com.yugyd.quiz.update.UpdateView.State
+import com.yugyd.quiz.update.UpdateView.State.NavigationState
+import com.yugyd.quiz.update.UpdateView.State.UpdateConfigUiModel
+import com.yugyd.quiz.update.model.UpdateUiMapper
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+internal class UpdateViewModel @Inject constructor(
+ private val remoteConfigRepository: RemoteConfigRepository,
+ private val updateUiMapper: UpdateUiMapper,
+ private val contentProvider: ContentProvider,
+ private val logger: Logger,
+ dispatchersProvider: DispatchersProvider,
+) :
+ BaseViewModel(
+ logger = logger,
+ dispatchersProvider = dispatchersProvider,
+ initialState = State(),
+ ) {
+
+ init {
+ loadData()
+ }
+
+ override fun handleAction(action: Action) {
+ when (action) {
+ is Action.OnUpdateClicked -> onUpdateClicked()
+
+ Action.OnNavigationHandled -> {
+ screenState = screenState.copy(navigationState = null)
+ }
+ }
+ }
+
+ private fun loadData() {
+ vmScopeErrorHandled.launch {
+ screenState = screenState.copy(
+ isLoading = true,
+ updateConfig = UpdateConfigUiModel(),
+ )
+
+ runCatch(
+ block = {
+ val updateConfig = remoteConfigRepository.fetchUpdateConfig()
+ screenState = updateUiMapper.map(updateConfig)
+ },
+ catch = ::processDataError,
+ )
+ }
+ }
+
+ private fun processDataError(error: Throwable) {
+ screenState = updateUiMapper.makeDefaultState()
+ processError(error)
+ }
+
+ private fun onUpdateClicked() {
+ vmScopeErrorHandled.launch {
+ runCatch(
+ block = {
+ val link = contentProvider.getUpdateLink()
+ screenState = screenState.copy(
+ navigationState = NavigationState.NavigateToGooglePlay(link),
+ )
+ },
+ catch = {
+ logger.logError(TAG, it)
+ screenState = screenState.copy(
+ navigationState = NavigationState.NavigateToGooglePlay(),
+ )
+ }
+ )
+ }
+ }
+
+ private companion object {
+ private const val TAG = "UpdateViewModel"
+ }
+}
diff --git a/product/update/update-ui/src/main/java/com/yugyd/quiz/update/model/UpdateUiMapper.kt b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/model/UpdateUiMapper.kt
new file mode 100644
index 0000000..10c2447
--- /dev/null
+++ b/product/update/update-ui/src/main/java/com/yugyd/quiz/update/model/UpdateUiMapper.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2023 Roman Likhachev
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.yugyd.quiz.update.model
+
+import android.content.Context
+import com.yugyd.quiz.featuretoggle.domain.model.update.UpdateConfig
+import com.yugyd.quiz.update.R
+import com.yugyd.quiz.update.UpdateView.State
+import com.yugyd.quiz.update.UpdateView.State.UpdateConfigUiModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+
+internal class UpdateUiMapper @Inject constructor(
+ @ApplicationContext private val context: Context,
+) {
+
+ fun map(updateConfig: UpdateConfig?): State {
+ return State(
+ updateConfig = UpdateConfigUiModel(
+ title = updateConfig?.title ?: context.getString(R.string.title_update),
+ message = updateConfig?.message
+ ?: context.getString(R.string.title_update_description),
+ buttonTitle = updateConfig?.buttonTitle
+ ?: context.getString(R.string.action_update),
+ ),
+ isLoading = false,
+ )
+ }
+
+ fun makeDefaultState(): State {
+ return State(
+ updateConfig = UpdateConfigUiModel(
+ buttonTitle = context.getString(R.string.action_update),
+ message = context.getString(R.string.title_update_description),
+ title = context.getString(R.string.title_update),
+ ),
+ isLoading = false,
+ )
+ }
+}
diff --git a/settings.gradle b/settings.gradle
index 958a9ff..6c1fddd 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -28,6 +28,7 @@ include ':product:core:coroutines-utils'
include ':product:core:featuretoggle'
include ':product:core:file'
include ':product:core:navigation'
+include ':product:core:search-utils'
include ':product:core:test'
// Services
include ':product:services:ad-api'