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
+50
View File
@@ -0,0 +1,50 @@
apply plugin: 'kotlin-parcelize'
apply from: '../tests.gradle'
apply from: '../scripts/secrets.gradle'
android {
namespace 'io.novafoundation.nova.feature_vote'
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation project(':common')
implementation project(':feature-account-api')
implementation kotlinDep
implementation androidDep
implementation materialDep
implementation constraintDep
implementation coroutinesDep
implementation coroutinesAndroidDep
implementation viewModelKtxDep
implementation lifeCycleKtxDep
implementation daggerDep
ksp daggerCompiler
testImplementation jUnitDep
testImplementation mockitoDep
implementation insetterDep
implementation shimmerDep
androidTestImplementation androidTestRunnerDep
androidTestImplementation androidTestRulesDep
androidTestImplementation androidJunitDep
}
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,3 @@
package io.novafoundation.nova.feature_vote.di
interface VoteFeatureApi
@@ -0,0 +1,40 @@
package io.novafoundation.nova.feature_vote.di
import dagger.BindsInstance
import dagger.Component
import io.novafoundation.nova.common.di.CommonApi
import io.novafoundation.nova.common.di.scope.FeatureScope
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
import io.novafoundation.nova.feature_vote.presentation.vote.di.VoteComponent
@Component(
dependencies = [
VoteFeatureDependencies::class
],
modules = [
VoteFeatureModule::class,
]
)
@FeatureScope
interface VoteFeatureComponent : VoteFeatureApi {
@Component.Factory
interface Factory {
fun create(
@BindsInstance voteRouter: VoteRouter,
deps: VoteFeatureDependencies
): VoteFeatureComponent
}
fun voteComponentFactory(): VoteComponent.Factory
@Component(
dependencies = [
CommonApi::class,
AccountFeatureApi::class
]
)
interface VoteFeatureDependenciesComponent : VoteFeatureDependencies
}
@@ -0,0 +1,8 @@
package io.novafoundation.nova.feature_vote.di
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
interface VoteFeatureDependencies {
val selectedAccountUseCase: SelectedAccountUseCase
}
@@ -0,0 +1,24 @@
package io.novafoundation.nova.feature_vote.di
import io.novafoundation.nova.common.di.FeatureApiHolder
import io.novafoundation.nova.common.di.FeatureContainer
import io.novafoundation.nova.common.di.scope.ApplicationScope
import io.novafoundation.nova.feature_account_api.di.AccountFeatureApi
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
import javax.inject.Inject
@ApplicationScope
class VoteFeatureHolder @Inject constructor(
featureContainer: FeatureContainer,
private val router: VoteRouter
) : FeatureApiHolder(featureContainer) {
override fun initializeDependencies(): Any {
val dependencies = DaggerVoteFeatureComponent_VoteFeatureDependenciesComponent.builder()
.commonApi(commonApi())
.accountFeatureApi(getFeature(AccountFeatureApi::class.java))
.build()
return DaggerVoteFeatureComponent.factory()
.create(router, dependencies)
}
}
@@ -0,0 +1,6 @@
package io.novafoundation.nova.feature_vote.di
import dagger.Module
@Module
class VoteFeatureModule
@@ -0,0 +1,12 @@
package io.novafoundation.nova.feature_vote.presentation
import androidx.fragment.app.Fragment
interface VoteRouter {
fun getDemocracyFragment(): Fragment
fun getCrowdloansFragment(): Fragment
fun openSwitchWallet()
}
@@ -0,0 +1,44 @@
package io.novafoundation.nova.feature_vote.presentation.vote
import android.view.View
import io.novafoundation.nova.common.base.BaseFragment
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.utils.insets.applyStatusBarInsets
import io.novafoundation.nova.common.utils.setupWithViewPager2
import io.novafoundation.nova.feature_vote.databinding.FragmentVoteBinding
import io.novafoundation.nova.feature_vote.di.VoteFeatureApi
import io.novafoundation.nova.feature_vote.di.VoteFeatureComponent
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
import javax.inject.Inject
class VoteFragment : BaseFragment<VoteViewModel, FragmentVoteBinding>() {
override fun createBinding() = FragmentVoteBinding.inflate(layoutInflater)
@Inject
lateinit var router: VoteRouter
override fun applyInsets(rootView: View) {
binder.voteContainer.applyStatusBarInsets()
}
override fun initViews() {
val adapter = VotePagerAdapter(this, router)
binder.voteViewPager.adapter = adapter
binder.voteTabs.setupWithViewPager2(binder.voteViewPager, adapter::getPageTitle)
binder.voteAvatar.setOnClickListener { viewModel.avatarClicked() }
}
override fun inject() {
FeatureUtils.getFeature<VoteFeatureComponent>(this, VoteFeatureApi::class.java)
.voteComponentFactory()
.create(this)
.inject(this)
}
override fun subscribe(viewModel: VoteViewModel) {
viewModel.selectedWalletModel.observe(binder.voteAvatar::setModel)
}
}
@@ -0,0 +1,29 @@
package io.novafoundation.nova.feature_vote.presentation.vote
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import io.novafoundation.nova.feature_vote.R
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
class VotePagerAdapter(private val fragment: Fragment, private val router: VoteRouter) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int {
return 2
}
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> router.getDemocracyFragment()
1 -> router.getCrowdloansFragment()
else -> throw IllegalArgumentException("Invalid position")
}
}
fun getPageTitle(position: Int): CharSequence {
return when (position) {
0 -> fragment.getString(R.string.common_governance)
1 -> fragment.getString(R.string.crowdloan_crowdloan)
else -> throw IllegalArgumentException("Invalid position")
}
}
}
@@ -0,0 +1,18 @@
package io.novafoundation.nova.feature_vote.presentation.vote
import io.novafoundation.nova.common.base.BaseViewModel
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
class VoteViewModel(
private val router: VoteRouter,
private val selectedAccountUseCase: SelectedAccountUseCase,
) : BaseViewModel() {
val selectedWalletModel = selectedAccountUseCase.selectedWalletModelFlow()
.shareInBackground()
fun avatarClicked() {
router.openSwitchWallet()
}
}
@@ -0,0 +1,26 @@
package io.novafoundation.nova.feature_vote.presentation.vote.di
import androidx.fragment.app.Fragment
import dagger.BindsInstance
import dagger.Subcomponent
import io.novafoundation.nova.common.di.scope.ScreenScope
import io.novafoundation.nova.feature_vote.presentation.vote.VoteFragment
@Subcomponent(
modules = [
VoteModule::class
]
)
@ScreenScope
interface VoteComponent {
@Subcomponent.Factory
interface Factory {
fun create(
@BindsInstance fragment: Fragment
): VoteComponent
}
fun inject(fragment: VoteFragment)
}
@@ -0,0 +1,35 @@
package io.novafoundation.nova.feature_vote.presentation.vote.di
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import dagger.Module
import dagger.Provides
import dagger.multibindings.IntoMap
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
import io.novafoundation.nova.feature_vote.presentation.VoteRouter
import io.novafoundation.nova.feature_vote.presentation.vote.VoteViewModel
@Module(includes = [ViewModelModule::class])
class VoteModule {
@Provides
internal fun provideViewModel(fragment: Fragment, factory: ViewModelProvider.Factory): VoteViewModel {
return ViewModelProvider(fragment, factory).get(VoteViewModel::class.java)
}
@Provides
@IntoMap
@ViewModelKey(VoteViewModel::class)
fun provideViewModel(
voteRouter: VoteRouter,
selectedAccountUseCase: SelectedAccountUseCase
): ViewModel {
return VoteViewModel(
router = voteRouter,
selectedAccountUseCase = selectedAccountUseCase
)
}
}
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/voteContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/drawable_background_image"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/voteTitle"
style="@style/TextAppearance.NovaFoundation.Header1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="17dp"
android:layout_marginTop="25dp"
android:layout_marginEnd="16dp"
android:layout_weight="1"
android:text="@string/vote_vote"
android:textColor="@color/text_primary" />
<io.novafoundation.nova.feature_account_api.view.SelectedWalletView
android:id="@+id/voteAvatar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="25dp"
android:layout_marginEnd="16dp" />
</LinearLayout>
<io.novafoundation.nova.common.view.SegmentedTabLayout
android:id="@+id/voteTabs"
style="@style/SegmentedTab"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/voteViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
@@ -0,0 +1,17 @@
package io.novafoundation.nova.feature_vote
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}