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:
2026-02-12 05:19:41 +03:00
commit a294aa1a6b
7687 changed files with 441811 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+23
View File
@@ -0,0 +1,23 @@
apply plugin: 'kotlin-parcelize'
android {
namespace 'io.novafoundation.nova.feature_dapp_api'
buildFeatures {
viewBinding true
}
}
dependencies {
implementation project(':feature-account-api')
implementation project(":feature-external-sign-api")
implementation project(':common')
implementation project(':feature-deep-linking')
implementation shimmerDep
implementation coroutinesDep
testImplementation jUnitDep
testImplementation mockitoDep
}
View File
+21
View File
@@ -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>
@@ -0,0 +1,6 @@
package io.novafoundation.nova.feature_dapp_api.data.model
class BrowserHostSettings(
val hostUrl: String,
val isDesktopModeEnabled: Boolean
)
@@ -0,0 +1,17 @@
package io.novafoundation.nova.feature_dapp_api.data.model
import io.novafoundation.nova.common.list.GroupedList
class DApp(
val name: String,
val description: String,
val iconLink: String?,
val url: String,
val isFavourite: Boolean,
val favoriteIndex: Int?
)
data class DAppGroupedCatalog(
val popular: List<DApp>,
val categoriesWithDApps: GroupedList<DappCategory, DApp>
)
@@ -0,0 +1,29 @@
package io.novafoundation.nova.feature_dapp_api.data.model
typealias DAppUrl = String
class DappCatalog(
val popular: List<DAppUrl>,
val categories: List<DappCategory>,
val dApps: List<DappMetadata>
)
class DappMetadata(
val name: String,
val iconLink: String,
val url: DAppUrl,
val baseUrl: String,
val categories: Set<DappCategory>
)
data class DappCategory(
val iconUrl: String?,
val name: String,
val id: String
)
private const val STAKING_CATEGORY_ID = "staking"
fun DappCategory.isStaking(): Boolean {
return id == STAKING_CATEGORY_ID
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_dapp_api.data.model
class SimpleTabModel(
val tabId: String,
val title: String?,
val knownDAppIconUrl: String?,
val faviconPath: String?
)
@@ -0,0 +1,10 @@
package io.novafoundation.nova.feature_dapp_api.data.repository
import io.novafoundation.nova.feature_dapp_api.data.model.BrowserHostSettings
interface BrowserHostSettingsRepository {
suspend fun getBrowserHostSettings(url: String): BrowserHostSettings?
suspend fun saveBrowserHostSettings(settings: BrowserHostSettings)
}
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_dapp_api.data.repository
import io.novafoundation.nova.feature_dapp_api.data.model.SimpleTabModel
import kotlinx.coroutines.flow.Flow
interface BrowserTabExternalRepository {
fun observeTabsWithNames(metaId: Long): Flow<List<SimpleTabModel>>
suspend fun removeTabsForMetaAccount(metaId: Long): List<String>
}
@@ -0,0 +1,32 @@
package io.novafoundation.nova.feature_dapp_api.data.repository
import io.novafoundation.nova.feature_dapp_api.data.model.DappCatalog
import io.novafoundation.nova.feature_dapp_api.data.model.DappMetadata
import kotlinx.coroutines.flow.Flow
interface DAppMetadataRepository {
suspend fun isDAppsSynced(): Boolean
suspend fun syncDAppMetadatas()
suspend fun syncAndGetDapp(baseUrl: String): DappMetadata?
suspend fun getDAppMetadata(baseUrl: String): DappMetadata?
suspend fun findDAppMetadataByExactUrlMatch(fullUrl: String): DappMetadata?
suspend fun findDAppMetadatasByBaseUrlMatch(baseUrl: String): List<DappMetadata>
suspend fun getDAppCatalog(): DappCatalog
fun observeDAppCatalog(): Flow<DappCatalog>
}
suspend fun DAppMetadataRepository.getDAppIfSyncedOrSync(baseUrl: String): DappMetadata? {
return if (isDAppsSynced()) {
getDAppMetadata(baseUrl)
} else {
syncAndGetDapp(baseUrl)
}
}
@@ -0,0 +1,14 @@
package io.novafoundation.nova.feature_dapp_api.di
import io.novafoundation.nova.feature_dapp_api.data.repository.BrowserTabExternalRepository
import io.novafoundation.nova.feature_dapp_api.data.repository.DAppMetadataRepository
import io.novafoundation.nova.feature_dapp_api.di.deeplinks.DAppDeepLinks
interface DAppFeatureApi {
val dappMetadataRepository: DAppMetadataRepository
val browserTabsRepository: BrowserTabExternalRepository
val dAppDeepLinks: DAppDeepLinks
}
@@ -0,0 +1,5 @@
package io.novafoundation.nova.feature_dapp_api.di.deeplinks
import io.novafoundation.nova.feature_deep_linking.presentation.handling.DeepLinkHandler
class DAppDeepLinks(val deepLinkHandlers: List<DeepLinkHandler>)
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_dapp_api.presentation.addToFavorites
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
@Parcelize
class AddToFavouritesPayload(
val url: String,
val label: String?,
val iconLink: String?
) : Parcelable
@@ -0,0 +1,13 @@
package io.novafoundation.nova.feature_dapp_api.presentation.browser.main
import android.os.Parcelable
import kotlinx.android.parcel.Parcelize
interface DAppBrowserPayload : Parcelable {
@Parcelize
class Tab(val id: String) : DAppBrowserPayload
@Parcelize
class Address(val address: String) : DAppBrowserPayload
}
@@ -0,0 +1,98 @@
package io.novafoundation.nova.feature_dapp_api.presentation.view
import android.content.Context
import android.util.AttributeSet
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.constraintlayout.widget.ConstraintLayout
import coil.ImageLoader
import coil.clear
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.inflater
import io.novafoundation.nova.common.utils.makeVisible
import io.novafoundation.nova.common.utils.setImageTintRes
import io.novafoundation.nova.common.utils.setVisible
import io.novafoundation.nova.common.utils.updatePadding
import io.novafoundation.nova.feature_dapp_api.R
import io.novafoundation.nova.feature_dapp_api.databinding.ViewDappBinding
import io.novafoundation.nova.feature_external_sign_api.presentation.dapp.showDAppIcon
class DAppView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
) : ConstraintLayout(context, attrs, defStyle) {
private val binder = ViewDappBinding.inflate(inflater(), this)
companion object {
fun createUsingMathParentWidth(context: Context): DAppView {
return DAppView(context).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
}
}
}
private val imageLoader: ImageLoader by lazy(LazyThreadSafetyMode.NONE) {
FeatureUtils.getCommonApi(context).imageLoader()
}
init {
setBackgroundResource(R.drawable.bg_primary_list_item)
}
fun setTitle(name: String?) {
binder.itemDAppTitle.text = name
}
fun showTitle(show: Boolean) {
binder.itemDAppTitle.setVisible(show)
}
fun setSubtitle(url: String?) {
binder.itemDAppSubtitle.text = url
}
fun showSubtitle(show: Boolean) {
binder.itemDAppSubtitle.setVisible(show)
}
fun setIconUrl(iconUrl: String?) {
binder.itemDAppIcon.showDAppIcon(iconUrl, imageLoader)
}
fun setFavoriteIconVisible(visible: Boolean) {
binder.itemDappFavorite.setVisible(visible)
}
fun enableSubtitleIcon(): ImageView {
return binder.itemDAppSubtitleIcon.also { icon -> icon.makeVisible() }
}
fun setOnActionClickListener(listener: OnClickListener?) {
binder.itemDappAction.setOnClickListener(listener)
}
fun setActionResource(@DrawableRes iconRes: Int?, @ColorRes colorRes: Int? = null) {
if (iconRes == null) {
binder.itemDappAction.setImageDrawable(null)
} else {
binder.itemDappAction.setImageResource(iconRes)
binder.itemDappAction.setImageTintRes(colorRes)
}
}
fun setActionTintRes(@ColorRes color: Int?) {
binder.itemDappAction.setImageTintRes(color)
}
fun setActionEndPadding(rightPadding: Int) {
binder.itemDappAction.updatePadding(end = rightPadding)
}
fun clearIcon() {
binder.itemDAppIcon.clear()
}
}
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_selected="true" android:color="@color/text_primary_on_content"/>
<item android:color="@color/button_text" />
</selector>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape android:shape="rectangle"
xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="@dimen/border_radius" />
<solid android:color="@color/block_background" />
<stroke android:color="@color/container_border" android:width="0.5dp" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/button_background_secondary" />
</shape>
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="10dp" />
<solid android:color="@color/selected_dapp_category" />
</shape>
@@ -0,0 +1,22 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="44dp"
android:height="44dp"
android:viewportWidth="44"
android:viewportHeight="44">
<path
android:pathData="M22,44C34.1503,44 44,34.1503 44,22C44,9.8497 34.1503,0 22,0C9.8497,0 0,9.8497 0,22C0,34.1503 9.8497,44 22,44ZM9.2784,31.9733H14.2913V11.1192H9.2784V31.9733ZM19.2529,11.1192V31.9733H26.1136C27.9183,31.9638 29.551,31.5341 31.0119,30.6843C32.4824,29.825 33.6188,28.6409 34.4209,27.1322C35.2324,25.614 35.6382,23.8999 35.6382,21.9903V21.0307C35.6287,19.14 35.2087,17.4356 34.3779,15.9173C33.5567,14.3896 32.4108,13.2103 30.9404,12.3796C29.4699,11.5393 27.8131,11.1192 25.9703,11.1192H19.2529ZM24.2802,28.1062V15.0006H25.9703C27.4503,15.0006 28.5771,15.5115 29.3505,16.5333C30.124,17.555 30.5106,19.0731 30.5106,21.0879V21.9903C30.5106,24.0146 30.124,25.5423 29.3505,26.5737C28.5771,27.5953 27.4695,28.1062 26.0276,28.1062H24.2802Z"
android:fillType="evenOdd">
<aapt:attr name="android:fillColor">
<gradient
android:startY="0"
android:startX="22"
android:endY="44"
android:endX="22"
android:type="linear">
<item android:offset="0" android:color="#FFF50083"/>
<item android:offset="1" android:color="#FF720497"/>
</gradient>
</aapt:attr>
</path>
</vector>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<View
android:id="@+id/itemDappShimerringIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/bg_shimmering"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/itemDappShimerringTitle"
android:layout_width="96dp"
android:layout_height="12dp"
android:layout_marginStart="12dp"
android:background="@drawable/bg_shimmering"
app:layout_constraintBottom_toTopOf="@+id/itemDappShimerringSubTitle"
app:layout_constraintStart_toEndOf="@+id/itemDappShimerringIcon"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<View
android:id="@id/itemDappShimerringSubTitle"
android:layout_width="64dp"
android:layout_height="8dp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:background="@drawable/bg_shimmering"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/itemDappShimerringIcon"
app:layout_constraintTop_toBottomOf="@+id/itemDappShimerringTitle" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<View
android:layout_width="88dp"
android:layout_height="14dp"
android:layout_marginStart="16dp"
android:background="@drawable/bg_shimmering" />
<include
layout="@layout/item_dapp_shimmering"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
<include
layout="@layout/item_dapp_shimmering"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include
layout="@layout/item_dapp_shimmering"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<com.facebook.shimmer.ShimmerFrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="34dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="vertical">
<View
android:layout_width="88dp"
android:layout_height="14dp"
android:layout_marginStart="16dp"
android:background="@drawable/bg_shimmering" />
<include
layout="@layout/item_dapp_shimmering"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
<include
layout="@layout/item_dapp_shimmering"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<include
layout="@layout/item_dapp_shimmering"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
</LinearLayout>
@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_primary_list_item"
tools:background="@color/block_background"
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
<ImageView
android:id="@+id/itemDAppIcon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="@drawable/dapp_icon_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0"
tools:src="@drawable/ic_sub_id" />
<TextView
android:id="@+id/itemDAppTitle"
style="@style/TextAppearance.NovaFoundation.Regular.Body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:singleLine="true"
android:textColor="@color/text_primary"
app:layout_constraintBottom_toTopOf="@+id/itemDAppSubtitle"
app:layout_constraintEnd_toStartOf="@+id/itemDappAction"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@id/itemDAppIcon"
app:layout_constraintTop_toTopOf="@+id/itemDAppIcon"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Astar" />
<ImageView
android:id="@+id/itemDAppSubtitleIcon"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="2dp"
android:visibility="gone"
app:layout_constraintStart_toStartOf="@+id/itemDAppTitle"
app:layout_constraintTop_toBottomOf="@+id/itemDAppTitle"
tools:visibility="visible" />
<TextView
android:id="@+id/itemDAppSubtitle"
style="@style/TextAppearance.NovaFoundation.Regular.Footnote"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="24dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/text_secondary"
app:layout_constraintBottom_toBottomOf="@+id/itemDAppIcon"
app:layout_constraintEnd_toStartOf="@+id/itemDappAction"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/itemDAppSubtitleIcon"
app:layout_constraintTop_toBottomOf="@+id/itemDAppTitle"
app:layout_goneMarginStart="0dp"
tools:text="Staking" />
<ImageView
android:id="@+id/itemDappFavorite"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="40dp"
android:layout_marginBottom="38dp"
android:src="@drawable/ic_favorite_heart_filled_20"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/itemDAppIcon"
app:layout_constraintStart_toStartOf="@+id/itemDAppIcon"
app:tint="@color/mimi_icon_favorite"
tools:visibility="visible" />
<ImageView
android:id="@+id/itemDappAction"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingStart="10dp"
android:paddingEnd="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/ic_close" />
</merge>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Widget.Nova.DappCategory.Shimmering" parent="">
<item name="android:layout_width">48dp</item>
<item name="android:layout_height">10dp</item>
<item name="android:background">@drawable/bg_shimmering</item>
<item name="android:layout_marginEnd">32dp</item>
</style>
</resources>