mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-04-22 02:07:58 +00:00
Initial commit: Pezkuwi Wallet Android
Security hardened release: - Code obfuscation enabled (minifyEnabled=true, shrinkResources=true) - Sensitive files excluded (google-services.json, keystores) - Branch.io key moved to BuildConfig placeholder - Updated dependencies: OkHttp 4.12.0, Gson 2.10.1, BouncyCastle 1.77 - Comprehensive ProGuard rules for crypto wallet - Navigation 2.7.7, Lifecycle 2.7.0, ConstraintLayout 2.1.4
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,32 @@
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
android {
|
||||
namespace 'io.novafoundation.nova.feature_banners_impl'
|
||||
|
||||
defaultConfig {
|
||||
|
||||
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation coroutinesDep
|
||||
implementation project(":common")
|
||||
implementation project(':feature-banners-api')
|
||||
|
||||
implementation cardViewDep
|
||||
implementation recyclerViewDep
|
||||
implementation materialDep
|
||||
implementation androidDep
|
||||
|
||||
implementation daggerDep
|
||||
ksp daggerCompiler
|
||||
|
||||
implementation androidDep
|
||||
|
||||
testImplementation project(':test-shared')
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.data
|
||||
|
||||
class BannerResponse(
|
||||
val id: String,
|
||||
val background: String,
|
||||
val image: String,
|
||||
val clipsToBounds: Boolean,
|
||||
val action: String?
|
||||
)
|
||||
|
||||
class BannerLocalisationResponse(
|
||||
val title: String,
|
||||
val details: String
|
||||
)
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.data
|
||||
|
||||
import io.novafoundation.nova.core.model.Language
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Url
|
||||
|
||||
interface BannersApi {
|
||||
|
||||
companion object {
|
||||
fun getLocalisationLink(url: String, language: Language): String {
|
||||
return "$url/${language.iso639Code}.json"
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
suspend fun getBanners(@Url url: String): List<BannerResponse>
|
||||
|
||||
@GET
|
||||
suspend fun getBannersLocalisation(@Url url: String): Map<String, BannerLocalisationResponse>
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.data
|
||||
|
||||
import retrofit2.HttpException
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.resources.LanguagesHolder
|
||||
import io.novafoundation.nova.common.utils.scopeAsync
|
||||
import io.novafoundation.nova.feature_banners_api.domain.PromotionBanner
|
||||
import io.novafoundation.nova.core.model.Language
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface BannersRepository {
|
||||
suspend fun getBanners(url: String, localisationUrl: String): List<PromotionBanner>
|
||||
|
||||
fun closeBanner(id: String)
|
||||
|
||||
fun observeClosedBannerIds(): Flow<Set<String>>
|
||||
}
|
||||
|
||||
class RealBannersRepository(
|
||||
private val preferences: Preferences,
|
||||
private val bannersApi: BannersApi,
|
||||
private val languagesHolder: LanguagesHolder
|
||||
) : BannersRepository {
|
||||
|
||||
companion object {
|
||||
private const val PREFS_CLOSED_BANNERS = "closed_banners"
|
||||
}
|
||||
|
||||
override suspend fun getBanners(url: String, localisationUrl: String): List<PromotionBanner> {
|
||||
val language = preferences.getCurrentLanguage()!!
|
||||
val bannersDeferred = scopeAsync { bannersApi.getBanners(url) }
|
||||
val localisationDeferred = scopeAsync { getLocalisation(localisationUrl, language) }
|
||||
|
||||
val banners = bannersDeferred.await()
|
||||
val localisation = localisationDeferred.await()
|
||||
|
||||
return mapBanners(banners, localisation)
|
||||
}
|
||||
|
||||
override fun observeClosedBannerIds(): Flow<Set<String>> {
|
||||
return preferences.stringSetFlow(PREFS_CLOSED_BANNERS)
|
||||
.map { it.orEmpty() }
|
||||
}
|
||||
|
||||
private fun mapBanners(
|
||||
banners: List<BannerResponse>,
|
||||
localisation: Map<String, BannerLocalisationResponse>
|
||||
) = banners.mapNotNull {
|
||||
val localisationBanner = localisation[it.id] ?: return@mapNotNull null
|
||||
|
||||
PromotionBanner(
|
||||
id = it.id,
|
||||
title = localisationBanner.title,
|
||||
details = localisationBanner.details,
|
||||
backgroundUrl = it.background,
|
||||
imageUrl = it.image,
|
||||
clipToBounds = it.clipsToBounds,
|
||||
actionLink = it.action
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getLocalisation(url: String, language: Language): Map<String, BannerLocalisationResponse> {
|
||||
try {
|
||||
val localisationUrl = BannersApi.getLocalisationLink(url, language)
|
||||
return bannersApi.getBannersLocalisation(localisationUrl)
|
||||
} catch (e: HttpException) {
|
||||
val fallbackLanguage = languagesHolder.getDefaultLanguage()
|
||||
if (e.code() == 404 && language != fallbackLanguage) {
|
||||
val fallbackUrl = BannersApi.getLocalisationLink(url, fallbackLanguage)
|
||||
return bannersApi.getBannersLocalisation(fallbackUrl)
|
||||
}
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeBanner(id: String) {
|
||||
val closedBannersId = preferences.getStringSet(PREFS_CLOSED_BANNERS)
|
||||
preferences.putStringSet(PREFS_CLOSED_BANNERS, closedBannersId + id)
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.di
|
||||
|
||||
import dagger.Component
|
||||
import io.novafoundation.nova.common.di.CommonApi
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.feature_banners_api.di.BannersFeatureApi
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
BannersFeatureDependencies::class,
|
||||
],
|
||||
modules = [
|
||||
BannersFeatureModule::class
|
||||
]
|
||||
)
|
||||
@FeatureScope
|
||||
interface BannersFeatureComponent : BannersFeatureApi {
|
||||
|
||||
@Component.Factory
|
||||
interface Factory {
|
||||
|
||||
fun create(deps: BannersFeatureDependencies): BannersFeatureComponent
|
||||
}
|
||||
|
||||
@Component(
|
||||
dependencies = [
|
||||
CommonApi::class
|
||||
]
|
||||
)
|
||||
interface BannersFeatureDependenciesComponent : BannersFeatureDependencies
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.di
|
||||
|
||||
import android.content.Context
|
||||
import coil.ImageLoader
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.resources.LanguagesHolder
|
||||
import io.novafoundation.nova.common.utils.sequrity.AutomaticInteractionGate
|
||||
|
||||
interface BannersFeatureDependencies {
|
||||
|
||||
val imageLoader: ImageLoader
|
||||
|
||||
val context: Context
|
||||
|
||||
val preferences: Preferences
|
||||
|
||||
val languagesHolder: LanguagesHolder
|
||||
|
||||
val networkApiCreator: NetworkApiCreator
|
||||
|
||||
val automaticInteractionGate: AutomaticInteractionGate
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.di
|
||||
|
||||
import io.novafoundation.nova.common.di.FeatureApiHolder
|
||||
import io.novafoundation.nova.common.di.FeatureContainer
|
||||
import io.novafoundation.nova.common.di.scope.ApplicationScope
|
||||
|
||||
import javax.inject.Inject
|
||||
|
||||
@ApplicationScope
|
||||
class BannersFeatureHolder @Inject constructor(
|
||||
featureContainer: FeatureContainer
|
||||
) : FeatureApiHolder(featureContainer) {
|
||||
|
||||
override fun initializeDependencies(): Any {
|
||||
val accountFeatureDependencies = DaggerBannersFeatureComponent_BannersFeatureDependenciesComponent.builder()
|
||||
.commonApi(commonApi())
|
||||
.build()
|
||||
|
||||
return DaggerBannersFeatureComponent.factory()
|
||||
.create(deps = accountFeatureDependencies)
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.di
|
||||
|
||||
import android.content.Context
|
||||
import coil.ImageLoader
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import io.novafoundation.nova.common.data.network.NetworkApiCreator
|
||||
import io.novafoundation.nova.common.data.storage.Preferences
|
||||
import io.novafoundation.nova.common.di.scope.FeatureScope
|
||||
import io.novafoundation.nova.common.resources.LanguagesHolder
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannersMixinFactory
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSourceFactory
|
||||
import io.novafoundation.nova.feature_banners_impl.data.BannersApi
|
||||
import io.novafoundation.nova.feature_banners_impl.data.BannersRepository
|
||||
import io.novafoundation.nova.feature_banners_impl.data.RealBannersRepository
|
||||
import io.novafoundation.nova.feature_banners_impl.domain.PromotionBannersInteractor
|
||||
import io.novafoundation.nova.feature_banners_impl.domain.RealPromotionBannersInteractor
|
||||
import io.novafoundation.nova.feature_banners_impl.presentation.banner.RealPromotionBannersMixinFactory
|
||||
import io.novafoundation.nova.feature_banners_impl.presentation.banner.source.RealBannersSourceFactory
|
||||
|
||||
@Module()
|
||||
class BannersFeatureModule {
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBannersApi(networkApiCreator: NetworkApiCreator): BannersApi {
|
||||
return networkApiCreator.create(BannersApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBannersRepository(
|
||||
preferences: Preferences,
|
||||
bannersApi: BannersApi,
|
||||
languagesHolder: LanguagesHolder
|
||||
): BannersRepository {
|
||||
return RealBannersRepository(preferences, bannersApi, languagesHolder)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun provideBannersInteractor(
|
||||
repository: BannersRepository
|
||||
): PromotionBannersInteractor {
|
||||
return RealPromotionBannersInteractor(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun sourceFactory(promotionBannersInteractor: PromotionBannersInteractor): BannersSourceFactory {
|
||||
return RealBannersSourceFactory(promotionBannersInteractor)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@FeatureScope
|
||||
fun providePromotionBannersMixinFactory(
|
||||
promotionBannersInteractor: PromotionBannersInteractor,
|
||||
imageLoader: ImageLoader,
|
||||
context: Context
|
||||
): PromotionBannersMixinFactory {
|
||||
return RealPromotionBannersMixinFactory(imageLoader, context, promotionBannersInteractor)
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.domain
|
||||
|
||||
import io.novafoundation.nova.feature_banners_api.domain.PromotionBanner
|
||||
import io.novafoundation.nova.feature_banners_impl.data.BannersRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
interface PromotionBannersInteractor {
|
||||
|
||||
suspend fun observeBanners(url: String, localisationUrl: String): Flow<List<PromotionBanner>>
|
||||
|
||||
fun closeBanner(id: String)
|
||||
}
|
||||
|
||||
class RealPromotionBannersInteractor(
|
||||
private val bannersRepository: BannersRepository,
|
||||
) : PromotionBannersInteractor {
|
||||
|
||||
override suspend fun observeBanners(url: String, localisationUrl: String): Flow<List<PromotionBanner>> {
|
||||
val banners = bannersRepository.getBanners(url, localisationUrl)
|
||||
return bannersRepository.observeClosedBannerIds()
|
||||
.map { closedIds ->
|
||||
banners.filter { it.id !in closedIds }
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeBanner(id: String) {
|
||||
bannersRepository.closeBanner(id)
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.presentation.banner
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.drawable.Drawable
|
||||
import coil.ImageLoader
|
||||
import coil.request.ImageRequest
|
||||
import io.novafoundation.nova.common.utils.launchDeepLink
|
||||
import io.novafoundation.nova.feature_banners_api.domain.PromotionBanner
|
||||
import io.novafoundation.nova.common.utils.scopeAsync
|
||||
import io.novafoundation.nova.common.utils.shareInBackground
|
||||
import io.novafoundation.nova.common.utils.withSafeLoading
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.BannerPageModel
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.ClipableImage
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannersMixin
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannersMixinFactory
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSource
|
||||
import io.novafoundation.nova.feature_banners_impl.domain.PromotionBannersInteractor
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
class RealPromotionBannersMixinFactory(
|
||||
private val imageLoader: ImageLoader,
|
||||
private val context: Context,
|
||||
private val promotionBannersInteractor: PromotionBannersInteractor
|
||||
) : PromotionBannersMixinFactory {
|
||||
|
||||
override fun create(source: BannersSource, coroutineScope: CoroutineScope): PromotionBannersMixin {
|
||||
return RealPromotionBannersMixin(
|
||||
promotionBannersInteractor,
|
||||
imageLoader,
|
||||
context,
|
||||
source,
|
||||
coroutineScope
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RealPromotionBannersMixin(
|
||||
private val promotionBannersInteractor: PromotionBannersInteractor,
|
||||
private val imageLoader: ImageLoader,
|
||||
private val context: Context,
|
||||
private val bannersSource: BannersSource,
|
||||
coroutineScope: CoroutineScope
|
||||
) : PromotionBannersMixin, CoroutineScope by coroutineScope {
|
||||
|
||||
override val bannersFlow = bannersSource.observeBanners()
|
||||
.map { banners ->
|
||||
val resources = loadResources(banners)
|
||||
banners.map { mapBanner(it, resources) }
|
||||
}.withSafeLoading()
|
||||
.shareInBackground()
|
||||
|
||||
override fun closeBanner(banner: BannerPageModel) {
|
||||
promotionBannersInteractor.closeBanner(banner.id)
|
||||
}
|
||||
|
||||
override fun startBannerAction(page: BannerPageModel) {
|
||||
val url = page.actionUrl ?: return
|
||||
|
||||
context.launchDeepLink(url)
|
||||
}
|
||||
|
||||
private suspend fun loadResources(banners: List<PromotionBanner>): Map<String, Drawable> {
|
||||
val imagesSet = buildSet {
|
||||
addAll(banners.map { it.imageUrl })
|
||||
addAll(banners.map { it.backgroundUrl })
|
||||
}
|
||||
|
||||
val loadingImagesResult = imagesSet.associateWith {
|
||||
val imageRequest = ImageRequest.Builder(context)
|
||||
.data(it)
|
||||
.build()
|
||||
|
||||
scopeAsync { imageLoader.execute(imageRequest) }
|
||||
}
|
||||
|
||||
return loadingImagesResult.mapValues { (_, value) -> value.await().drawable!! }
|
||||
}
|
||||
|
||||
private fun mapBanner(
|
||||
banner: PromotionBanner,
|
||||
resources: Map<String, Drawable>
|
||||
): BannerPageModel {
|
||||
return BannerPageModel(
|
||||
id = banner.id,
|
||||
title = banner.title,
|
||||
subtitle = banner.details,
|
||||
image = ClipableImage(
|
||||
resources.getValue(banner.imageUrl),
|
||||
banner.clipToBounds
|
||||
),
|
||||
background = resources.getValue(banner.backgroundUrl),
|
||||
actionUrl = banner.actionLink
|
||||
)
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.presentation.banner.source
|
||||
|
||||
import io.novafoundation.nova.common.utils.flowOfAll
|
||||
import io.novafoundation.nova.feature_banners_api.domain.PromotionBanner
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSource
|
||||
import io.novafoundation.nova.feature_banners_impl.domain.PromotionBannersInteractor
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class RealBannersSource(
|
||||
private val bannersUrl: String,
|
||||
private val localisationUrl: String,
|
||||
private val bannersInteractor: PromotionBannersInteractor
|
||||
) : BannersSource {
|
||||
|
||||
override fun observeBanners(): Flow<List<PromotionBanner>> {
|
||||
return flowOfAll { bannersInteractor.observeBanners(bannersUrl, localisationUrl) }
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
package io.novafoundation.nova.feature_banners_impl.presentation.banner.source
|
||||
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSource
|
||||
import io.novafoundation.nova.feature_banners_api.presentation.source.BannersSourceFactory
|
||||
import io.novafoundation.nova.feature_banners_impl.domain.PromotionBannersInteractor
|
||||
|
||||
class RealBannersSourceFactory(
|
||||
private val bannersInteractor: PromotionBannersInteractor
|
||||
) : BannersSourceFactory {
|
||||
|
||||
override fun create(bannersUrl: String, localisationUrl: String): BannersSource {
|
||||
return RealBannersSource(bannersUrl, localisationUrl, bannersInteractor)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user