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" /> + + + +