diff --git a/common/src/main/res/values-ku/strings.xml b/common/src/main/res/values-ku/strings.xml
index 657fd76..a92c11a 100644
--- a/common/src/main/res/values-ku/strings.xml
+++ b/common/src/main/res/values-ku/strings.xml
@@ -2763,4 +2763,32 @@
Pûana Pêbaweriyê
Serlêdan û Karên
Ji bo karên hemwelatîbûna Kurdistana Dîjîtal MiniApp\'a me ya Telegram bikar bînin.\n\nJi bo qezenckirina xelatên PEZ, divê hûn xwediyê bilêta Welatî bin û herî kêm 10 HEZ stake kiribe bin.\n\nKesên ne-hemwelatî tenê dikarin ji xelatên HEZ sûd werbigirin.
+
+
+ Serlêdana Hemwelatîbûnê
+ Nav û Paşnav
+ Navê Bav
+ Navê Bapîr
+ Navê Dayik
+ Eşîr
+ Herêm
+ Serlêdan bike
+ Serlêdana te li benda pejirandinê ye
+ Îmze bike
+ Tu jixwe hemwelatî yî
+ Di People Chain de balance têr nîne. Herî kêm 1.1 HEZ pêwîst e.
+ Serlêdan bi serkeftî hat şandin
+ Ji bo hemwelatîbûna Kurdistana Dîjîtal hevalê xwe vexwîne!\n\nNavnîşana min a referansê:\n%s
+ Lînka Referansê Parve Bike
+ Navnîşana Referansê (ne mecbûrî)
+ Referansa te serlêdana te pejirand. Ji bo temamkirina hemwelatîbûnê îmze bike.
+ Bakur
+ Başûr
+ Rojava
+ Rojhelat
+ Kurdistan a Sor
+ Diaspora
+ Navnîşana serlêder
+ Referralê bipejirîne
+ Referral bi serkeftî hat pejirandin
diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml
index 2ffe878..2d59629 100644
--- a/common/src/main/res/values-tr/strings.xml
+++ b/common/src/main/res/values-tr/strings.xml
@@ -30,4 +30,32 @@
HEZ → DOT yönü manuel işlenir, 24 saate kadar sürebilir.
HEZ → DOT yönü şu anda devre dışı.
USDT(Pez) → USDT(Pol) yönü şu anda devre dışı.
+
+
+ Vatandaşlık Başvurusu
+ Ad Soyad
+ Baba Adı
+ Dede Adı
+ Anne Adı
+ Aşiret
+ Bölge
+ Başvur
+ Başvurunuz onay bekliyor
+ İmzala
+ Zaten vatandaşsınız
+ People Chain\'de yetersiz bakiye. En az 1.1 HEZ gerekli.
+ Başvuru başarıyla gönderildi
+ Dijital Kurdistan vatandaşlığı için arkadaşını davet et!\n\nReferans adresim:\n%s
+ Referans Linkini Paylaş
+ Referans Adresi (opsiyonel)
+ Referansınız başvurunuzu onayladı. Vatandaşlığı tamamlamak için imzalayın.
+ Bakur
+ Başûr
+ Rojava
+ Rojhelat
+ Kurdistan a Sor
+ Diaspora
+ Başvuranın adresi
+ Referral Onayla
+ Referral başarıyla onaylandı
diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml
index bb61a5a..c83574a 100644
--- a/common/src/main/res/values/strings.xml
+++ b/common/src/main/res/values/strings.xml
@@ -2767,4 +2767,32 @@
Apply & Actions
Use our Telegram MiniApp for Digital Kurdistan citizenship services.\n\nTo earn PEZ rewards, you must hold a Welatî ticket and stake at least 10 HEZ.\n\nNon-citizens can only benefit from HEZ rewards.
Hejmara Kurd Le Cihane
+
+
+ Citizenship Application
+ Full Name
+ Father\'s Name
+ Grandfather\'s Name
+ Mother\'s Name
+ Tribe
+ Region
+ Apply
+ Your application is pending approval
+ Sign
+ You are already a citizen
+ Insufficient balance on People Chain. At least 1.1 HEZ required.
+ Application submitted successfully
+ Refer a friend for Digital Kurdistan citizenship!\n\nMy referrer address:\n%s
+ Share Referral Link
+ Referrer Address (optional)
+ Your referrer has approved your application. Sign to complete citizenship.
+ Bakur
+ Başûr
+ Rojava
+ Rojhelat
+ Kurdistan a Sor
+ Diaspora
+ Applicant\'s address
+ Approve Referral
+ Referral approved successfully
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/model/PezkuwiDashboardData.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/model/PezkuwiDashboardData.kt
index 8718e05..904f52f 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/model/PezkuwiDashboardData.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/model/PezkuwiDashboardData.kt
@@ -1,9 +1,11 @@
package io.novafoundation.nova.feature_assets.data.model
+import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
import java.math.BigInteger
data class PezkuwiDashboardData(
val roles: List,
val trustScore: BigInteger,
- val welatiCount: Int
+ val welatiCount: Int,
+ val citizenshipStatus: CitizenshipStatus = CitizenshipStatus.NOT_STARTED
)
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt
index b226ed7..0cfcda2 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/data/repository/PezkuwiDashboardRepository.kt
@@ -1,10 +1,16 @@
package io.novafoundation.nova.feature_assets.data.repository
+import android.util.Log
+import io.novafoundation.nova.common.data.network.runtime.binding.bindAccountInfo
import io.novafoundation.nova.common.data.network.runtime.binding.bindNumber
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
import io.novafoundation.nova.common.data.network.runtime.binding.castToList
+import io.novafoundation.nova.common.data.network.runtime.binding.castToStructOrNull
import io.novafoundation.nova.feature_assets.data.model.PezkuwiDashboardData
+import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
import io.novafoundation.nova.runtime.ext.ChainGeneses
+import io.novafoundation.nova.runtime.ext.addressOf
+import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
@@ -29,11 +35,13 @@ class PezkuwiDashboardRepository(
val roles = queryRoles(chainId, accountId)
val trustScore = queryTrustScore(chainId, accountId)
val welatiCount = fetchWelatiCount()
+ val kycStatus = runCatching { queryKycStatus(chainId, accountId) }.getOrDefault(CitizenshipStatus.NOT_STARTED)
return PezkuwiDashboardData(
roles = roles.ifEmpty { listOf("Non-Citizen") },
trustScore = trustScore,
- welatiCount = welatiCount
+ welatiCount = welatiCount,
+ citizenshipStatus = kycStatus
)
}
@@ -64,4 +72,104 @@ class PezkuwiDashboardRepository(
})
}
}.getOrDefault(BigInteger.ZERO)
+
+ suspend fun queryFreeBalance(chainId: String, accountId: AccountId): BigInteger = runCatching {
+ remoteStorageDataSource.query(chainId) {
+ val systemModule = runtime.metadata.moduleOrNull("System") ?: return@query BigInteger.ZERO
+ systemModule.storage("Account").query(accountId, binding = { decoded ->
+ decoded?.let { bindAccountInfo(it).data.free } ?: BigInteger.ZERO
+ })
+ }
+ }.getOrDefault(BigInteger.ZERO)
+
+ suspend fun getPendingApprovals(chain: Chain, referrerAccountId: AccountId): List {
+ return runCatching {
+ remoteStorageDataSource.query(chain.id) {
+ val kycModule = runtime.metadata.moduleOrNull("IdentityKyc") ?: return@query emptyList()
+ val results = mutableListOf()
+ val seenAccounts = mutableSetOf()
+
+ // 1) Confirmed referrals from Referral::Referrals (referred → referrer AccountId)
+ val referralModule = runtime.metadata.moduleOrNull("Referral")
+ if (referralModule != null) {
+ val allReferrals: Map = referralModule.storage("Referrals").entries(
+ keyExtractor = { it.component1() },
+ binding = { decoded, _ -> decoded }
+ )
+ allReferrals.forEach { (referredId, referrerValue) ->
+ val referrer = referrerValue as? ByteArray ?: return@forEach
+ if (!referrer.contentEquals(referrerAccountId)) return@forEach
+
+ val addr = chain.addressOf(referredId)
+ seenAccounts.add(addr)
+
+ val statusName = kycModule.storage("KycStatuses").query(referredId, binding = { d ->
+ d?.castToDictEnum()?.name
+ })
+ val status = mapKycStatus(statusName)
+ results.add(PendingApproval(referredId, addr, status))
+ }
+ }
+
+ // 2) Pending applications not yet in Referral pallet
+ val allApplications: Map = kycModule.storage("Applications").entries(
+ keyExtractor = { it.component1() },
+ binding = { decoded, _ -> decoded }
+ )
+ allApplications.forEach { (applicantId, decoded) ->
+ val struct = decoded?.castToStructOrNull() ?: return@forEach
+ val referrer = struct["referrer"] as? ByteArray ?: return@forEach
+ if (!referrer.contentEquals(referrerAccountId)) return@forEach
+
+ val addr = chain.addressOf(applicantId)
+ if (addr in seenAccounts) return@forEach
+
+ val statusName = kycModule.storage("KycStatuses").query(applicantId, binding = { d ->
+ d?.castToDictEnum()?.name
+ })
+ val status = mapKycStatus(statusName)
+ results.add(PendingApproval(applicantId, addr, status))
+ }
+
+ results
+ }
+ }.getOrElse { e ->
+ Log.e("PezkuwiDashboard", "getPendingApprovals failed", e)
+ emptyList()
+ }
+ }
+
+ private fun mapKycStatus(statusName: String?): CitizenshipStatus {
+ return when (statusName) {
+ "PendingReferral" -> CitizenshipStatus.PENDING_REFERRAL
+ "ReferrerApproved" -> CitizenshipStatus.REFERRER_APPROVED
+ "Approved" -> CitizenshipStatus.APPROVED
+ else -> CitizenshipStatus.NOT_STARTED
+ }
+ }
+
+ suspend fun queryKycStatus(chainId: String, accountId: AccountId): CitizenshipStatus {
+ return remoteStorageDataSource.query(chainId) {
+ val kycModule = runtime.metadata.moduleOrNull("IdentityKyc") ?: run {
+ Log.w("PezkuwiDashboard", "IdentityKyc module not found in metadata")
+ return@query CitizenshipStatus.NOT_STARTED
+ }
+ kycModule.storage("KycStatuses").query(accountId, binding = { decoded ->
+ val enumName = decoded?.castToDictEnum()?.name
+ Log.d("PezkuwiDashboard", "KYC status raw enum: '$enumName' (decoded=$decoded)")
+ when (enumName) {
+ "PendingReferral" -> CitizenshipStatus.PENDING_REFERRAL
+ "ReferrerApproved" -> CitizenshipStatus.REFERRER_APPROVED
+ "Approved" -> CitizenshipStatus.APPROVED
+ else -> CitizenshipStatus.NOT_STARTED
+ }
+ })
+ }
+ }
}
+
+data class PendingApproval(
+ val applicantAccountId: ByteArray,
+ val applicantAddress: String,
+ val status: CitizenshipStatus
+)
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt
index fd0e43a..79ba3f0 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/di/AssetsFeatureComponent.kt
@@ -35,6 +35,7 @@ import io.novafoundation.nova.feature_assets.presentation.tokens.manage.chain.di
import io.novafoundation.nova.feature_assets.presentation.tokens.manage.di.ManageTokensComponent
import io.novafoundation.nova.feature_assets.presentation.topup.TopUpAddressCommunicator
import io.novafoundation.nova.feature_assets.presentation.bridge.di.BridgeComponent
+import io.novafoundation.nova.feature_assets.presentation.citizenship.di.CitizenshipComponent
import io.novafoundation.nova.feature_assets.presentation.trade.sell.flow.asset.di.AssetSellFlowComponent
import io.novafoundation.nova.feature_assets.presentation.trade.sell.flow.network.di.NetworkSellFlowComponent
import io.novafoundation.nova.feature_assets.presentation.trade.provider.di.TradeProviderListComponent
@@ -138,6 +139,8 @@ interface AssetsFeatureComponent : AssetsFeatureApi {
fun waitingNovaCardTopUpComponentFactory(): WaitingNovaCardTopUpComponent.Factory
+ fun citizenshipComponentFactory(): CitizenshipComponent.Factory
+
fun inject(view: GoToNftsView)
@Component.Factory
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/dashboard/PezkuwiDashboardInteractor.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/dashboard/PezkuwiDashboardInteractor.kt
index eae990d..f7fb234 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/dashboard/PezkuwiDashboardInteractor.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/domain/dashboard/PezkuwiDashboardInteractor.kt
@@ -5,6 +5,7 @@ import io.novafoundation.nova.feature_assets.data.model.PezkuwiDashboardData
import io.novafoundation.nova.feature_assets.data.repository.PezkuwiDashboardRepository
import io.novafoundation.nova.runtime.ext.ChainGeneses
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
+import java.math.BigInteger
class PezkuwiDashboardInteractor(
private val repository: PezkuwiDashboardRepository,
@@ -18,4 +19,13 @@ class PezkuwiDashboardInteractor(
repository.getDashboard(accountId)
}
+
+ suspend fun hasSufficientBalance(metaAccount: MetaAccount, minPlanck: BigInteger): Boolean {
+ return runCatching {
+ val peopleChain = chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
+ val accountId = metaAccount.accountIdIn(peopleChain) ?: return false
+ val balance = repository.queryFreeBalance(ChainGeneses.PEZKUWI_PEOPLE, accountId)
+ balance >= minPlanck
+ }.getOrDefault(false)
+ }
}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt
index 2979215..105d83f 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListFragment.kt
@@ -32,8 +32,10 @@ import io.novafoundation.nova.feature_assets.presentation.balance.list.view.Asse
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.AssetsHeaderHolder
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.ManageAssetsAdapter
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.ManageAssetsHolder
+import android.content.Intent
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardAdapter
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardHolder
+import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipBottomSheet
import io.novafoundation.nova.feature_banners_api.presentation.BannerHolder
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter
import io.novafoundation.nova.feature_banners_api.presentation.bindWithAdapter
@@ -175,6 +177,18 @@ class BalanceListFragment :
viewModel.pendingOperationsCountModel.observe(headerAdapter::setPendingOperationsCountModel)
viewModel.filtersIndicatorIcon.observe(headerAdapter::setFilterIconRes)
viewModel.assetViewModeModelFlow.observe { manageAssetsAdapter.setAssetViewModeModel(it) }
+
+ viewModel.openCitizenshipEvent.observeEvent {
+ CitizenshipBottomSheet().show(childFragmentManager, "citizenship")
+ }
+
+ viewModel.shareReferralEvent.observeEvent { shareText ->
+ val intent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, shareText)
+ }
+ startActivity(Intent.createChooser(intent, null))
+ }
}
override fun assetClicked(asset: Chain.Asset) {
@@ -256,6 +270,14 @@ class BalanceListFragment :
viewModel.basvuruClicked()
}
+ override fun onSignClicked() {
+ viewModel.basvuruClicked()
+ }
+
+ override fun onShareReferralClicked() {
+ viewModel.shareReferralClicked()
+ }
+
private fun setupRecyclerViewSpacing() {
binder.balanceListAssets.addSpaceItemDecoration {
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt
index b28055b..586517f 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/BalanceListViewModel.kt
@@ -23,6 +23,7 @@ import io.novafoundation.nova.common.utils.withSafeLoading
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
+import io.novafoundation.nova.feature_account_api.domain.model.defaultSubstrateAddress
import io.novafoundation.nova.feature_assets.R
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor
@@ -64,6 +65,7 @@ import io.novafoundation.nova.feature_wallet_api.presentation.model.FractionPart
import io.novafoundation.nova.feature_wallet_connect_api.domain.sessions.WalletConnectSessionsUseCase
import io.novafoundation.nova.feature_wallet_connect_api.presentation.mapNumberOfActiveSessionsToUi
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
+import java.math.BigInteger
import java.text.NumberFormat
import kotlinx.coroutines.async
import kotlinx.coroutines.flow.combine
@@ -114,6 +116,12 @@ class BalanceListViewModel(
private val _showBalanceBreakdownEvent = MutableLiveData>()
val showBalanceBreakdownEvent: LiveData> = _showBalanceBreakdownEvent
+ private val _openCitizenshipEvent = MutableLiveData>()
+ val openCitizenshipEvent: LiveData> = _openCitizenshipEvent
+
+ private val _shareReferralEvent = MutableLiveData>()
+ val shareReferralEvent: LiveData> = _shareReferralEvent
+
val bannersMixin = promotionBannersMixinFactory.create(bannerSourceFactory.assetsSource(), viewModelScope)
private val selectedCurrency = currencyInteractor.observeSelectCurrency()
@@ -230,7 +238,8 @@ class BalanceListViewModel(
PezkuwiDashboardModel(
roles = data.roles,
trustScore = data.trustScore.toString(),
- welatiCount = NumberFormat.getIntegerInstance().format(data.welatiCount)
+ welatiCount = NumberFormat.getIntegerInstance().format(data.welatiCount),
+ citizenshipStatus = data.citizenshipStatus
)
}
.getOrNull()
@@ -400,8 +409,15 @@ class BalanceListViewModel(
}
}
- fun basvuruClicked() {
- showBrowser("https://t.me/pezkuwichainBot")
+ fun basvuruClicked() = launchUnit {
+ _openCitizenshipEvent.postValue(Event(Unit))
+ }
+
+ fun shareReferralClicked() = launchUnit {
+ val metaAccount = selectedAccountUseCase.getSelectedMetaAccount()
+ val address = metaAccount.defaultSubstrateAddress ?: return@launchUnit
+ val shareText = resourceManager.getString(R.string.citizenship_share_referral, address)
+ _shareReferralEvent.postValue(Event(shareText))
}
fun novaCardClicked() = launchUnit {
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PezkuwiDashboardModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PezkuwiDashboardModel.kt
index a312fb3..6e133c2 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PezkuwiDashboardModel.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/model/PezkuwiDashboardModel.kt
@@ -1,7 +1,10 @@
package io.novafoundation.nova.feature_assets.presentation.balance.list.model
+import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
+
data class PezkuwiDashboardModel(
val roles: List,
val trustScore: String,
- val welatiCount: String
+ val welatiCount: String,
+ val citizenshipStatus: CitizenshipStatus = CitizenshipStatus.NOT_STARTED
)
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PezkuwiDashboardAdapter.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PezkuwiDashboardAdapter.kt
index 512f04f..8508628 100644
--- a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PezkuwiDashboardAdapter.kt
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/balance/list/view/PezkuwiDashboardAdapter.kt
@@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_assets.presentation.balance.list.view
import android.content.res.ColorStateList
import android.graphics.Color
+import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
@@ -12,6 +13,7 @@ import io.novafoundation.nova.common.utils.recyclerView.WithViewType
import io.novafoundation.nova.feature_assets.R
import io.novafoundation.nova.feature_assets.databinding.ItemPezkuwiDashboardBinding
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.PezkuwiDashboardModel
+import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
class PezkuwiDashboardAdapter(
private val handler: Handler
@@ -19,6 +21,8 @@ class PezkuwiDashboardAdapter(
interface Handler {
fun onBasvuruClicked()
+ fun onSignClicked()
+ fun onShareReferralClicked()
}
private var model: PezkuwiDashboardModel? = null
@@ -53,12 +57,40 @@ class PezkuwiDashboardHolder(
init {
binder.pezkuwiDashboardBasvuruButton.setOnClickListener { handler.onBasvuruClicked() }
+ binder.pezkuwiDashboardSignButton.setOnClickListener { handler.onSignClicked() }
+ binder.pezkuwiDashboardShareButton.setOnClickListener { handler.onShareReferralClicked() }
}
fun bind(model: PezkuwiDashboardModel) {
bindRoles(model.roles)
binder.pezkuwiDashboardTrustValue.text = model.trustScore
binder.pezkuwiDashboardWelatiCount.text = model.welatiCount
+ bindButtons(model.citizenshipStatus)
+ }
+
+ private fun bindButtons(status: CitizenshipStatus) {
+ if (status == CitizenshipStatus.APPROVED) {
+ // Citizen: "Onayla" (approve referrals) + Share
+ binder.pezkuwiDashboardBasvuruButton.visibility = View.VISIBLE
+ binder.pezkuwiDashboardBasvuruButton.setText(R.string.citizenship_approve_button)
+ binder.pezkuwiDashboardSignButton.visibility = View.GONE
+ binder.pezkuwiDashboardShareButton.visibility = View.VISIBLE
+ binder.pezkuwiDashboardShareButton.isEnabled = true
+ binder.pezkuwiDashboardShareButton.alpha = 1f
+ } else {
+ // Not yet citizen: show all 3
+ binder.pezkuwiDashboardBasvuruButton.visibility = View.VISIBLE
+ binder.pezkuwiDashboardBasvuruButton.setText(R.string.pezkuwi_dashboard_basvuru)
+ binder.pezkuwiDashboardSignButton.visibility = View.VISIBLE
+ binder.pezkuwiDashboardShareButton.visibility = View.VISIBLE
+
+ val signEnabled = status == CitizenshipStatus.REFERRER_APPROVED
+ binder.pezkuwiDashboardSignButton.isEnabled = signEnabled
+ binder.pezkuwiDashboardSignButton.alpha = if (signEnabled) 1f else 0.4f
+
+ binder.pezkuwiDashboardShareButton.isEnabled = false
+ binder.pezkuwiDashboardShareButton.alpha = 0.4f
+ }
}
private fun bindRoles(roles: List) {
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/CitizenshipBottomSheet.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/CitizenshipBottomSheet.kt
new file mode 100644
index 0000000..1d616b3
--- /dev/null
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/CitizenshipBottomSheet.kt
@@ -0,0 +1,258 @@
+package io.novafoundation.nova.feature_assets.presentation.citizenship
+
+import android.content.Intent
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.LinearLayout
+import android.widget.TextView
+import com.google.android.material.button.MaterialButton
+import io.novafoundation.nova.common.base.BaseBottomSheetFragment
+import io.novafoundation.nova.common.di.FeatureUtils
+import io.novafoundation.nova.common.utils.dp
+import io.novafoundation.nova.feature_assets.R
+import io.novafoundation.nova.feature_assets.data.repository.PendingApproval
+import io.novafoundation.nova.feature_assets.databinding.FragmentCitizenshipBottomSheetBinding
+import io.novafoundation.nova.feature_assets.di.AssetsFeatureApi
+import io.novafoundation.nova.feature_assets.di.AssetsFeatureComponent
+
+class CitizenshipBottomSheet : BaseBottomSheetFragment() {
+
+ override fun createBinding() = FragmentCitizenshipBottomSheetBinding.inflate(layoutInflater)
+
+ override fun inject() {
+ FeatureUtils.getFeature(
+ requireContext(),
+ AssetsFeatureApi::class.java
+ )
+ .citizenshipComponentFactory()
+ .create(this)
+ .inject(this)
+ }
+
+ override fun initViews() {
+ setupRegionSpinner()
+
+ binder.citizenshipActionButton.setOnClickListener {
+ when (viewModel.citizenshipStatus.value) {
+ CitizenshipStatus.NOT_STARTED -> submitForm()
+ CitizenshipStatus.REFERRER_APPROVED -> viewModel.signApplication()
+ else -> {}
+ }
+ }
+
+ binder.citizenshipShareButton.setOnClickListener {
+ viewModel.shareReferralLink()
+ }
+ }
+
+ override fun subscribe(viewModel: CitizenshipViewModel) {
+ viewModel.citizenshipStatus.observe(viewLifecycleOwner) { status ->
+ updateUiForStatus(status)
+ }
+
+ viewModel.isLoading.observe(viewLifecycleOwner) { loading ->
+ binder.citizenshipProgress.visibility = if (loading) View.VISIBLE else View.GONE
+ binder.citizenshipActionButton.isEnabled = !loading
+ }
+
+ viewModel.dismissEvent.observeEvent {
+ dismiss()
+ }
+
+ viewModel.shareEvent.observeEvent { shareText ->
+ val intent = Intent(Intent.ACTION_SEND).apply {
+ type = "text/plain"
+ putExtra(Intent.EXTRA_TEXT, shareText)
+ }
+ startActivity(Intent.createChooser(intent, null))
+ }
+
+ viewModel.pendingApprovals.observe(viewLifecycleOwner) { approvals ->
+ bindInvitationsList(approvals)
+ }
+ }
+
+ private fun setupRegionSpinner() {
+ val regions = listOf(
+ getString(R.string.citizenship_region_bakur),
+ getString(R.string.citizenship_region_basur),
+ getString(R.string.citizenship_region_rojava),
+ getString(R.string.citizenship_region_rojhelat),
+ getString(R.string.citizenship_region_kurdistan),
+ getString(R.string.citizenship_region_diaspora)
+ )
+
+ val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, regions)
+ adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
+ binder.citizenshipRegionSpinner.adapter = adapter
+ }
+
+ private fun updateUiForStatus(status: CitizenshipStatus) {
+ binder.citizenshipShareButton.visibility = View.GONE
+ binder.citizenshipInvitationsHeader.visibility = View.GONE
+ binder.citizenshipInvitationsScroll.visibility = View.GONE
+
+ when (status) {
+ CitizenshipStatus.LOADING -> {
+ binder.citizenshipFormScroll.visibility = View.GONE
+ binder.citizenshipStatusText.visibility = View.GONE
+ binder.citizenshipActionButton.isEnabled = false
+ binder.citizenshipProgress.visibility = View.VISIBLE
+ }
+
+ CitizenshipStatus.NOT_STARTED -> {
+ binder.citizenshipFormScroll.visibility = View.VISIBLE
+ binder.citizenshipStatusText.visibility = View.GONE
+ binder.citizenshipActionButton.isEnabled = true
+ binder.citizenshipActionButton.visibility = View.VISIBLE
+ binder.citizenshipActionButton.text = getString(R.string.citizenship_apply)
+ binder.citizenshipProgress.visibility = View.GONE
+ }
+
+ CitizenshipStatus.PENDING_REFERRAL -> {
+ binder.citizenshipFormScroll.visibility = View.GONE
+ binder.citizenshipStatusText.visibility = View.VISIBLE
+ binder.citizenshipStatusText.text = getString(R.string.citizenship_pending)
+ binder.citizenshipActionButton.isEnabled = false
+ binder.citizenshipActionButton.visibility = View.VISIBLE
+ binder.citizenshipActionButton.text = getString(R.string.citizenship_pending)
+ binder.citizenshipProgress.visibility = View.GONE
+ }
+
+ CitizenshipStatus.REFERRER_APPROVED -> {
+ binder.citizenshipFormScroll.visibility = View.GONE
+ binder.citizenshipStatusText.visibility = View.VISIBLE
+ binder.citizenshipStatusText.text = getString(R.string.citizenship_sign_description)
+ binder.citizenshipActionButton.isEnabled = true
+ binder.citizenshipActionButton.visibility = View.VISIBLE
+ binder.citizenshipActionButton.text = getString(R.string.citizenship_sign)
+ binder.citizenshipProgress.visibility = View.GONE
+ }
+
+ CitizenshipStatus.APPROVED -> {
+ binder.citizenshipFormScroll.visibility = View.GONE
+ binder.citizenshipStatusText.visibility = View.VISIBLE
+ binder.citizenshipStatusText.text = getString(R.string.citizenship_approved)
+ binder.citizenshipActionButton.visibility = View.GONE
+ binder.citizenshipShareButton.visibility = View.VISIBLE
+ binder.citizenshipInvitationsHeader.visibility = View.VISIBLE
+ binder.citizenshipInvitationsScroll.visibility = View.VISIBLE
+ binder.citizenshipProgress.visibility = View.GONE
+ }
+ }
+ }
+
+ private fun bindInvitationsList(approvals: List) {
+ val container = binder.citizenshipInvitationsList
+ container.removeAllViews()
+
+ val header = binder.citizenshipInvitationsHeader
+ val pendingCount = approvals.count { it.status == CitizenshipStatus.PENDING_REFERRAL }
+ header.text = "My Invitations (${approvals.size})" +
+ if (pendingCount > 0) " \u2022 $pendingCount pending" else ""
+
+ if (approvals.isEmpty()) {
+ val emptyText = TextView(requireContext()).apply {
+ text = "No invitations yet"
+ setTextColor(Color.parseColor("#78909C"))
+ textSize = 13f
+ gravity = Gravity.CENTER
+ setPadding(0, 8.dp(context), 0, 8.dp(context))
+ }
+ container.addView(emptyText)
+ return
+ }
+
+ approvals.forEach { approval ->
+ container.addView(createInvitationRow(approval))
+ }
+ }
+
+ private fun createInvitationRow(approval: PendingApproval): View {
+ val row = LinearLayout(requireContext()).apply {
+ orientation = LinearLayout.HORIZONTAL
+ gravity = Gravity.CENTER_VERTICAL
+ setPadding(0, 6.dp(context), 0, 6.dp(context))
+ layoutParams = ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+
+ // Shortened address
+ val addr = approval.applicantAddress
+ val shortAddr = if (addr.length > 16) "${addr.take(8)}...${addr.takeLast(6)}" else addr
+
+ val addressText = TextView(requireContext()).apply {
+ text = shortAddr
+ setTextColor(Color.WHITE)
+ textSize = 13f
+ layoutParams = LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f)
+ }
+ row.addView(addressText)
+
+ when (approval.status) {
+ CitizenshipStatus.PENDING_REFERRAL -> {
+ val approveBtn = MaterialButton(requireContext()).apply {
+ text = getString(R.string.citizenship_approve_button)
+ textSize = 11f
+ isAllCaps = false
+ backgroundTintList = ColorStateList.valueOf(Color.parseColor("#4CAF50"))
+ setTextColor(Color.WHITE)
+ cornerRadius = 6.dp(context)
+ setPadding(12.dp(context), 0, 12.dp(context), 0)
+ minimumHeight = 36.dp(context)
+ layoutParams = LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ )
+ }
+ approveBtn.setOnClickListener {
+ viewModel.approveReferral(approval.applicantAccountId)
+ }
+ row.addView(approveBtn)
+ }
+
+ CitizenshipStatus.APPROVED, CitizenshipStatus.REFERRER_APPROVED -> {
+ val confirmedText = TextView(requireContext()).apply {
+ text = "Confirmed"
+ setTextColor(Color.parseColor("#4CAF50"))
+ textSize = 12f
+ }
+ row.addView(confirmedText)
+ }
+
+ else -> {
+ val statusText = TextView(requireContext()).apply {
+ text = approval.status.name
+ setTextColor(Color.parseColor("#78909C"))
+ textSize = 12f
+ }
+ row.addView(statusText)
+ }
+ }
+
+ return row
+ }
+
+ private fun submitForm() {
+ val name = binder.citizenshipNameInput.text?.toString().orEmpty()
+ val fatherName = binder.citizenshipFatherNameInput.text?.toString().orEmpty()
+ val grandfatherName = binder.citizenshipGrandfatherNameInput.text?.toString().orEmpty()
+ val motherName = binder.citizenshipMotherNameInput.text?.toString().orEmpty()
+ val tribe = binder.citizenshipTribeInput.text?.toString().orEmpty()
+ val region = binder.citizenshipRegionSpinner.selectedItem?.toString().orEmpty()
+ val referrer = binder.citizenshipReferrerInput.text?.toString()?.trim()?.ifBlank { null }
+
+ if (name.isBlank()) {
+ binder.citizenshipNameLayout.error = getString(R.string.common_name)
+ return
+ }
+
+ viewModel.submitApplication(name, fatherName, grandfatherName, motherName, tribe, region, referrer)
+ }
+}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/CitizenshipViewModel.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/CitizenshipViewModel.kt
new file mode 100644
index 0000000..81f4983
--- /dev/null
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/CitizenshipViewModel.kt
@@ -0,0 +1,264 @@
+package io.novafoundation.nova.feature_assets.presentation.citizenship
+
+import android.util.Log
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import io.novafoundation.nova.common.base.BaseViewModel
+import io.novafoundation.nova.common.resources.ResourceManager
+import io.novafoundation.nova.common.utils.Event
+import io.novafoundation.nova.feature_account_api.data.ethereum.transaction.TransactionOrigin
+import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
+import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
+import io.novafoundation.nova.feature_account_api.domain.model.addressIn
+import io.novafoundation.nova.feature_assets.R
+import io.novafoundation.nova.feature_assets.data.repository.PendingApproval
+import io.novafoundation.nova.feature_assets.data.repository.PezkuwiDashboardRepository
+import io.novafoundation.nova.runtime.ext.ChainGeneses
+import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
+import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
+import io.novasama.substrate_sdk_android.runtime.extrinsic.call
+import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
+import io.novasama.substrate_sdk_android.runtime.metadata.storage
+import io.novasama.substrate_sdk_android.ss58.SS58Encoder.toAccountId
+import kotlinx.coroutines.launch
+import org.bouncycastle.jcajce.provider.digest.Keccak
+import java.math.BigInteger
+
+enum class CitizenshipStatus {
+ NOT_STARTED,
+ PENDING_REFERRAL,
+ REFERRER_APPROVED,
+ APPROVED,
+ LOADING
+}
+
+class CitizenshipViewModel(
+ private val extrinsicService: ExtrinsicService,
+ private val chainRegistry: ChainRegistry,
+ private val selectedAccountUseCase: SelectedAccountUseCase,
+ private val resourceManager: ResourceManager,
+ private val pezkuwiDashboardRepository: PezkuwiDashboardRepository
+) : BaseViewModel() {
+
+ private val _citizenshipStatus = MutableLiveData(CitizenshipStatus.LOADING)
+ val citizenshipStatus: LiveData = _citizenshipStatus
+
+ private val _isLoading = MutableLiveData(false)
+ val isLoading: LiveData = _isLoading
+
+ private val _dismissEvent = MutableLiveData>()
+ val dismissEvent: LiveData> = _dismissEvent
+
+ private val _shareEvent = MutableLiveData>()
+ val shareEvent: LiveData> = _shareEvent
+
+ private val _pendingApprovals = MutableLiveData>(emptyList())
+ val pendingApprovals: LiveData> = _pendingApprovals
+
+ private var peopleChain: Chain? = null
+ private var cachedAccountId: ByteArray? = null
+
+ companion object {
+ private val MIN_BALANCE_PLANCK = BigInteger("1100000000000") // 1.1 HEZ
+ private const val TAG = "CitizenshipVM"
+ }
+
+ init {
+ loadStatus()
+ }
+
+ private fun loadStatus() {
+ launch {
+ try {
+ val chain = chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
+ peopleChain = chain
+
+ val metaAccount = selectedAccountUseCase.getSelectedMetaAccount()
+ val accountId = metaAccount.accountIdIn(chain) ?: run {
+ _citizenshipStatus.postValue(CitizenshipStatus.NOT_STARTED)
+ return@launch
+ }
+ cachedAccountId = accountId
+
+ val status = queryKycStatus(accountId)
+ Log.d(TAG, "KYC status loaded: $status")
+
+ // Balance check only for new applications — don't block sign/approve
+ if (status == CitizenshipStatus.NOT_STARTED) {
+ val freeBalance = pezkuwiDashboardRepository.queryFreeBalance(ChainGeneses.PEZKUWI_PEOPLE, accountId)
+ if (freeBalance < MIN_BALANCE_PLANCK) {
+ showError(resourceManager.getString(R.string.citizenship_insufficient_balance))
+ _dismissEvent.postValue(Event(Unit))
+ return@launch
+ }
+ }
+
+ _citizenshipStatus.postValue(status)
+
+ if (status == CitizenshipStatus.APPROVED) {
+ loadPendingApprovals(chain, accountId)
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to load KYC status", e)
+ _citizenshipStatus.postValue(CitizenshipStatus.NOT_STARTED)
+ }
+ }
+ }
+
+ private suspend fun queryKycStatus(accountId: ByteArray): CitizenshipStatus {
+ return try {
+ pezkuwiDashboardRepository.queryKycStatus(ChainGeneses.PEZKUWI_PEOPLE, accountId)
+ } catch (e: Exception) {
+ Log.e(TAG, "queryKycStatus failed", e)
+ CitizenshipStatus.NOT_STARTED
+ }
+ }
+
+ fun submitApplication(
+ name: String,
+ fatherName: String,
+ grandfatherName: String,
+ motherName: String,
+ tribe: String,
+ region: String,
+ referrerAddress: String?
+ ) {
+ launch {
+ _isLoading.postValue(true)
+ try {
+ val chain = peopleChain ?: chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
+ val accountId = cachedAccountId ?: run {
+ val metaAccount = selectedAccountUseCase.getSelectedMetaAccount()
+ metaAccount.accountIdIn(chain) ?: throw IllegalStateException("No account for People Chain")
+ }
+
+ val jsonString = """{"name":"${name.trim().lowercase()}","email":"","documents":[]}"""
+ val identityHash = keccak256(jsonString.toByteArray())
+
+ val referrerAccountId = referrerAddress?.let {
+ try {
+ it.toAccountId()
+ } catch (e: Exception) {
+ Log.w(TAG, "Invalid referrer address: $it", e)
+ null
+ }
+ }
+
+ val arguments = mutableMapOf(
+ "identity_hash" to identityHash,
+ "referrer" to referrerAccountId
+ )
+
+ val result = extrinsicService.submitExtrinsic(
+ chain = chain,
+ origin = TransactionOrigin.SelectedWallet
+ ) {
+ call(
+ moduleName = "IdentityKyc",
+ callName = "apply_for_citizenship",
+ arguments = arguments
+ )
+ }
+ result.getOrThrow()
+
+ showToast(resourceManager.getString(R.string.citizenship_success))
+ _citizenshipStatus.postValue(CitizenshipStatus.PENDING_REFERRAL)
+ _dismissEvent.postValue(Event(Unit))
+ } catch (e: Exception) {
+ Log.e(TAG, "submitApplication failed", e)
+ showError(e)
+ } finally {
+ _isLoading.postValue(false)
+ }
+ }
+ }
+
+ fun signApplication() {
+ launch {
+ _isLoading.postValue(true)
+ try {
+ val chain = peopleChain ?: chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
+
+ val result = extrinsicService.submitExtrinsic(
+ chain = chain,
+ origin = TransactionOrigin.SelectedWallet
+ ) {
+ call(
+ moduleName = "IdentityKyc",
+ callName = "confirm_citizenship",
+ arguments = emptyMap()
+ )
+ }
+ result.getOrThrow()
+
+ showToast(resourceManager.getString(R.string.citizenship_success))
+ _citizenshipStatus.postValue(CitizenshipStatus.APPROVED)
+ _dismissEvent.postValue(Event(Unit))
+ } catch (e: Exception) {
+ Log.e(TAG, "signApplication failed", e)
+ showError(e)
+ } finally {
+ _isLoading.postValue(false)
+ }
+ }
+ }
+
+ fun shareReferralLink() {
+ launch {
+ try {
+ val chain = peopleChain ?: chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
+ val metaAccount = selectedAccountUseCase.getSelectedMetaAccount()
+ val address = metaAccount.addressIn(chain) ?: return@launch
+
+ val shareText = resourceManager.getString(R.string.citizenship_share_referral, address)
+ _shareEvent.postValue(Event(shareText))
+ } catch (e: Exception) {
+ Log.e(TAG, "shareReferralLink failed", e)
+ }
+ }
+ }
+
+ fun approveReferral(applicantAccountId: ByteArray) {
+ launch {
+ _isLoading.postValue(true)
+ try {
+ val chain = peopleChain ?: chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
+
+ val result = extrinsicService.submitExtrinsic(
+ chain = chain,
+ origin = TransactionOrigin.SelectedWallet
+ ) {
+ call(
+ moduleName = "IdentityKyc",
+ callName = "approve_referral",
+ arguments = mapOf("applicant" to applicantAccountId)
+ )
+ }
+ result.getOrThrow()
+
+ showToast(resourceManager.getString(R.string.citizenship_approve_success))
+ // Reload the list
+ val accountId = cachedAccountId
+ if (accountId != null) {
+ loadPendingApprovals(chain, accountId)
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "approveReferral failed", e)
+ showError(e)
+ } finally {
+ _isLoading.postValue(false)
+ }
+ }
+ }
+
+ private suspend fun loadPendingApprovals(chain: Chain, referrerAccountId: ByteArray) {
+ val approvals = pezkuwiDashboardRepository.getPendingApprovals(chain, referrerAccountId)
+ Log.d(TAG, "Loaded ${approvals.size} pending approvals")
+ _pendingApprovals.postValue(approvals)
+ }
+
+ private fun keccak256(input: ByteArray): ByteArray {
+ val digest = Keccak.Digest256()
+ return digest.digest(input)
+ }
+}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/di/CitizenshipComponent.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/di/CitizenshipComponent.kt
new file mode 100644
index 0000000..bfa94c9
--- /dev/null
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/di/CitizenshipComponent.kt
@@ -0,0 +1,26 @@
+package io.novafoundation.nova.feature_assets.presentation.citizenship.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_assets.presentation.citizenship.CitizenshipBottomSheet
+
+@Subcomponent(
+ modules = [
+ CitizenshipModule::class
+ ]
+)
+@ScreenScope
+interface CitizenshipComponent {
+
+ @Subcomponent.Factory
+ interface Factory {
+
+ fun create(
+ @BindsInstance fragment: Fragment
+ ): CitizenshipComponent
+ }
+
+ fun inject(fragment: CitizenshipBottomSheet)
+}
diff --git a/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/di/CitizenshipModule.kt b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/di/CitizenshipModule.kt
new file mode 100644
index 0000000..dd3efe3
--- /dev/null
+++ b/feature-assets/src/main/java/io/novafoundation/nova/feature_assets/presentation/citizenship/di/CitizenshipModule.kt
@@ -0,0 +1,59 @@
+package io.novafoundation.nova.feature_assets.presentation.citizenship.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.scope.ScreenScope
+import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
+import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
+import io.novafoundation.nova.common.resources.ResourceManager
+import io.novafoundation.nova.feature_account_api.data.extrinsic.ExtrinsicService
+import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
+import io.novafoundation.nova.feature_assets.data.repository.PezkuwiDashboardRepository
+import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipViewModel
+import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
+import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
+import io.novafoundation.nova.runtime.storage.source.StorageDataSource
+import javax.inject.Named
+
+@Module(includes = [ViewModelModule::class])
+class CitizenshipModule {
+
+ @Provides
+ @ScreenScope
+ fun providePezkuwiDashboardRepository(
+ @Named(REMOTE_STORAGE_SOURCE) remoteStorageDataSource: StorageDataSource
+ ): PezkuwiDashboardRepository {
+ return PezkuwiDashboardRepository(remoteStorageDataSource)
+ }
+
+ @Provides
+ @IntoMap
+ @ViewModelKey(CitizenshipViewModel::class)
+ fun provideViewModel(
+ extrinsicService: ExtrinsicService,
+ chainRegistry: ChainRegistry,
+ selectedAccountUseCase: SelectedAccountUseCase,
+ resourceManager: ResourceManager,
+ pezkuwiDashboardRepository: PezkuwiDashboardRepository
+ ): ViewModel {
+ return CitizenshipViewModel(
+ extrinsicService = extrinsicService,
+ chainRegistry = chainRegistry,
+ selectedAccountUseCase = selectedAccountUseCase,
+ resourceManager = resourceManager,
+ pezkuwiDashboardRepository = pezkuwiDashboardRepository
+ )
+ }
+
+ @Provides
+ fun provideViewModelCreator(
+ fragment: Fragment,
+ viewModelFactory: ViewModelProvider.Factory,
+ ): CitizenshipViewModel {
+ return ViewModelProvider(fragment, viewModelFactory).get(CitizenshipViewModel::class.java)
+ }
+}
diff --git a/feature-assets/src/main/res/drawable/bg_spinner_outlined.xml b/feature-assets/src/main/res/drawable/bg_spinner_outlined.xml
new file mode 100644
index 0000000..5f92d86
--- /dev/null
+++ b/feature-assets/src/main/res/drawable/bg_spinner_outlined.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/feature-assets/src/main/res/layout/fragment_citizenship_bottom_sheet.xml b/feature-assets/src/main/res/layout/fragment_citizenship_bottom_sheet.xml
new file mode 100644
index 0000000..c10a3f8
--- /dev/null
+++ b/feature-assets/src/main/res/layout/fragment_citizenship_bottom_sheet.xml
@@ -0,0 +1,224 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/feature-assets/src/main/res/layout/item_pezkuwi_dashboard.xml b/feature-assets/src/main/res/layout/item_pezkuwi_dashboard.xml
index 7ac92be..259a33b 100644
--- a/feature-assets/src/main/res/layout/item_pezkuwi_dashboard.xml
+++ b/feature-assets/src/main/res/layout/item_pezkuwi_dashboard.xml
@@ -96,25 +96,38 @@
-
-
-
-
+
+ android:textAllCaps="false"
+ android:textColor="@android:color/white"
+ app:backgroundTint="#4CAF50"
+ app:cornerRadius="8dp" />
+
+
+
+