mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-23 10:45:50 +00:00
feat: in-app citizenship application with referral system
- Add CitizenshipBottomSheet with form (name, father, grandfather, mother, tribe, region) - Submit apply_for_citizenship extrinsic with referrer parameter to People Chain - Query KYC status from IdentityKyc pallet and show appropriate UI state - Referrer approval: query Referral + Applications entries, approve pending referrals - Sign (confirm_citizenship) for applicants after referrer approval - Share referral link for citizens to invite others - Dashboard buttons adapt to citizenship status (hide Apply/Sign when approved) - Balance check (1.1 HEZ) only before new applications, not for sign/approve - i18n strings for en, tr, ku
This commit is contained in:
@@ -2763,4 +2763,32 @@
|
|||||||
<string name="pezkuwi_dashboard_trust_score">Pûana Pêbaweriyê</string>
|
<string name="pezkuwi_dashboard_trust_score">Pûana Pêbaweriyê</string>
|
||||||
<string name="pezkuwi_dashboard_basvuru">Serlêdan û Karên</string>
|
<string name="pezkuwi_dashboard_basvuru">Serlêdan û Karên</string>
|
||||||
<string name="pezkuwi_dashboard_info">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.</string>
|
<string name="pezkuwi_dashboard_info">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.</string>
|
||||||
|
|
||||||
|
<!-- Citizenship Application -->
|
||||||
|
<string name="citizenship_title">Serlêdana Hemwelatîbûnê</string>
|
||||||
|
<string name="citizenship_name">Nav û Paşnav</string>
|
||||||
|
<string name="citizenship_father_name">Navê Bav</string>
|
||||||
|
<string name="citizenship_grandfather_name">Navê Bapîr</string>
|
||||||
|
<string name="citizenship_mother_name">Navê Dayik</string>
|
||||||
|
<string name="citizenship_tribe">Eşîr</string>
|
||||||
|
<string name="citizenship_region">Herêm</string>
|
||||||
|
<string name="citizenship_apply">Serlêdan bike</string>
|
||||||
|
<string name="citizenship_pending">Serlêdana te li benda pejirandinê ye</string>
|
||||||
|
<string name="citizenship_sign">Îmze bike</string>
|
||||||
|
<string name="citizenship_approved">Tu jixwe hemwelatî yî</string>
|
||||||
|
<string name="citizenship_insufficient_balance">Di People Chain de balance têr nîne. Herî kêm 1.1 HEZ pêwîst e.</string>
|
||||||
|
<string name="citizenship_success">Serlêdan bi serkeftî hat şandin</string>
|
||||||
|
<string name="citizenship_share_referral">Ji bo hemwelatîbûna Kurdistana Dîjîtal hevalê xwe vexwîne!\n\nNavnîşana min a referansê:\n%s</string>
|
||||||
|
<string name="citizenship_share_button">Lînka Referansê Parve Bike</string>
|
||||||
|
<string name="citizenship_referrer_hint">Navnîşana Referansê (ne mecbûrî)</string>
|
||||||
|
<string name="citizenship_sign_description">Referansa te serlêdana te pejirand. Ji bo temamkirina hemwelatîbûnê îmze bike.</string>
|
||||||
|
<string name="citizenship_region_bakur">Bakur</string>
|
||||||
|
<string name="citizenship_region_basur">Başûr</string>
|
||||||
|
<string name="citizenship_region_rojava">Rojava</string>
|
||||||
|
<string name="citizenship_region_rojhelat">Rojhelat</string>
|
||||||
|
<string name="citizenship_region_kurdistan">Kurdistan a Sor</string>
|
||||||
|
<string name="citizenship_region_diaspora">Diaspora</string>
|
||||||
|
<string name="citizenship_approve_address_hint">Navnîşana serlêder</string>
|
||||||
|
<string name="citizenship_approve_button">Referralê bipejirîne</string>
|
||||||
|
<string name="citizenship_approve_success">Referral bi serkeftî hat pejirandin</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -30,4 +30,32 @@
|
|||||||
<string name="bridge_hez_to_dot_warning">HEZ → DOT yönü manuel işlenir, 24 saate kadar sürebilir.</string>
|
<string name="bridge_hez_to_dot_warning">HEZ → DOT yönü manuel işlenir, 24 saate kadar sürebilir.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">HEZ → DOT yönü şu anda devre dışı.</string>
|
<string name="bridge_hez_to_dot_blocked">HEZ → DOT yönü şu anda devre dışı.</string>
|
||||||
<string name="bridge_wusdt_to_usdt_blocked">USDT(Pez) → USDT(Pol) yönü şu anda devre dışı.</string>
|
<string name="bridge_wusdt_to_usdt_blocked">USDT(Pez) → USDT(Pol) yönü şu anda devre dışı.</string>
|
||||||
|
|
||||||
|
<!-- Citizenship Application -->
|
||||||
|
<string name="citizenship_title">Vatandaşlık Başvurusu</string>
|
||||||
|
<string name="citizenship_name">Ad Soyad</string>
|
||||||
|
<string name="citizenship_father_name">Baba Adı</string>
|
||||||
|
<string name="citizenship_grandfather_name">Dede Adı</string>
|
||||||
|
<string name="citizenship_mother_name">Anne Adı</string>
|
||||||
|
<string name="citizenship_tribe">Aşiret</string>
|
||||||
|
<string name="citizenship_region">Bölge</string>
|
||||||
|
<string name="citizenship_apply">Başvur</string>
|
||||||
|
<string name="citizenship_pending">Başvurunuz onay bekliyor</string>
|
||||||
|
<string name="citizenship_sign">İmzala</string>
|
||||||
|
<string name="citizenship_approved">Zaten vatandaşsınız</string>
|
||||||
|
<string name="citizenship_insufficient_balance">People Chain\'de yetersiz bakiye. En az 1.1 HEZ gerekli.</string>
|
||||||
|
<string name="citizenship_success">Başvuru başarıyla gönderildi</string>
|
||||||
|
<string name="citizenship_share_referral">Dijital Kurdistan vatandaşlığı için arkadaşını davet et!\n\nReferans adresim:\n%s</string>
|
||||||
|
<string name="citizenship_share_button">Referans Linkini Paylaş</string>
|
||||||
|
<string name="citizenship_referrer_hint">Referans Adresi (opsiyonel)</string>
|
||||||
|
<string name="citizenship_sign_description">Referansınız başvurunuzu onayladı. Vatandaşlığı tamamlamak için imzalayın.</string>
|
||||||
|
<string name="citizenship_region_bakur">Bakur</string>
|
||||||
|
<string name="citizenship_region_basur">Başûr</string>
|
||||||
|
<string name="citizenship_region_rojava">Rojava</string>
|
||||||
|
<string name="citizenship_region_rojhelat">Rojhelat</string>
|
||||||
|
<string name="citizenship_region_kurdistan">Kurdistan a Sor</string>
|
||||||
|
<string name="citizenship_region_diaspora">Diaspora</string>
|
||||||
|
<string name="citizenship_approve_address_hint">Başvuranın adresi</string>
|
||||||
|
<string name="citizenship_approve_button">Referral Onayla</string>
|
||||||
|
<string name="citizenship_approve_success">Referral başarıyla onaylandı</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2767,4 +2767,32 @@
|
|||||||
<string name="pezkuwi_dashboard_basvuru">Apply & Actions</string>
|
<string name="pezkuwi_dashboard_basvuru">Apply & Actions</string>
|
||||||
<string name="pezkuwi_dashboard_info">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.</string>
|
<string name="pezkuwi_dashboard_info">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.</string>
|
||||||
<string name="pezkuwi_dashboard_kurds_title">Hejmara Kurd Le Cihane</string>
|
<string name="pezkuwi_dashboard_kurds_title">Hejmara Kurd Le Cihane</string>
|
||||||
|
|
||||||
|
<!-- Citizenship Application -->
|
||||||
|
<string name="citizenship_title">Citizenship Application</string>
|
||||||
|
<string name="citizenship_name">Full Name</string>
|
||||||
|
<string name="citizenship_father_name">Father\'s Name</string>
|
||||||
|
<string name="citizenship_grandfather_name">Grandfather\'s Name</string>
|
||||||
|
<string name="citizenship_mother_name">Mother\'s Name</string>
|
||||||
|
<string name="citizenship_tribe">Tribe</string>
|
||||||
|
<string name="citizenship_region">Region</string>
|
||||||
|
<string name="citizenship_apply">Apply</string>
|
||||||
|
<string name="citizenship_pending">Your application is pending approval</string>
|
||||||
|
<string name="citizenship_sign">Sign</string>
|
||||||
|
<string name="citizenship_approved">You are already a citizen</string>
|
||||||
|
<string name="citizenship_insufficient_balance">Insufficient balance on People Chain. At least 1.1 HEZ required.</string>
|
||||||
|
<string name="citizenship_success">Application submitted successfully</string>
|
||||||
|
<string name="citizenship_share_referral">Refer a friend for Digital Kurdistan citizenship!\n\nMy referrer address:\n%s</string>
|
||||||
|
<string name="citizenship_share_button">Share Referral Link</string>
|
||||||
|
<string name="citizenship_referrer_hint">Referrer Address (optional)</string>
|
||||||
|
<string name="citizenship_sign_description">Your referrer has approved your application. Sign to complete citizenship.</string>
|
||||||
|
<string name="citizenship_region_bakur">Bakur</string>
|
||||||
|
<string name="citizenship_region_basur">Başûr</string>
|
||||||
|
<string name="citizenship_region_rojava">Rojava</string>
|
||||||
|
<string name="citizenship_region_rojhelat">Rojhelat</string>
|
||||||
|
<string name="citizenship_region_kurdistan">Kurdistan a Sor</string>
|
||||||
|
<string name="citizenship_region_diaspora">Diaspora</string>
|
||||||
|
<string name="citizenship_approve_address_hint">Applicant\'s address</string>
|
||||||
|
<string name="citizenship_approve_button">Approve Referral</string>
|
||||||
|
<string name="citizenship_approve_success">Referral approved successfully</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+3
-1
@@ -1,9 +1,11 @@
|
|||||||
package io.novafoundation.nova.feature_assets.data.model
|
package io.novafoundation.nova.feature_assets.data.model
|
||||||
|
|
||||||
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
|
|
||||||
data class PezkuwiDashboardData(
|
data class PezkuwiDashboardData(
|
||||||
val roles: List<String>,
|
val roles: List<String>,
|
||||||
val trustScore: BigInteger,
|
val trustScore: BigInteger,
|
||||||
val welatiCount: Int
|
val welatiCount: Int,
|
||||||
|
val citizenshipStatus: CitizenshipStatus = CitizenshipStatus.NOT_STARTED
|
||||||
)
|
)
|
||||||
|
|||||||
+109
-1
@@ -1,10 +1,16 @@
|
|||||||
package io.novafoundation.nova.feature_assets.data.repository
|
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.bindNumber
|
||||||
import io.novafoundation.nova.common.data.network.runtime.binding.castToDictEnum
|
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.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.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.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.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||||
import io.novasama.substrate_sdk_android.runtime.AccountId
|
import io.novasama.substrate_sdk_android.runtime.AccountId
|
||||||
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
|
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
|
||||||
@@ -29,11 +35,13 @@ class PezkuwiDashboardRepository(
|
|||||||
val roles = queryRoles(chainId, accountId)
|
val roles = queryRoles(chainId, accountId)
|
||||||
val trustScore = queryTrustScore(chainId, accountId)
|
val trustScore = queryTrustScore(chainId, accountId)
|
||||||
val welatiCount = fetchWelatiCount()
|
val welatiCount = fetchWelatiCount()
|
||||||
|
val kycStatus = runCatching { queryKycStatus(chainId, accountId) }.getOrDefault(CitizenshipStatus.NOT_STARTED)
|
||||||
|
|
||||||
return PezkuwiDashboardData(
|
return PezkuwiDashboardData(
|
||||||
roles = roles.ifEmpty { listOf("Non-Citizen") },
|
roles = roles.ifEmpty { listOf("Non-Citizen") },
|
||||||
trustScore = trustScore,
|
trustScore = trustScore,
|
||||||
welatiCount = welatiCount
|
welatiCount = welatiCount,
|
||||||
|
citizenshipStatus = kycStatus
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,4 +72,104 @@ class PezkuwiDashboardRepository(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}.getOrDefault(BigInteger.ZERO)
|
}.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<PendingApproval> {
|
||||||
|
return runCatching {
|
||||||
|
remoteStorageDataSource.query(chain.id) {
|
||||||
|
val kycModule = runtime.metadata.moduleOrNull("IdentityKyc") ?: return@query emptyList()
|
||||||
|
val results = mutableListOf<PendingApproval>()
|
||||||
|
val seenAccounts = mutableSetOf<String>()
|
||||||
|
|
||||||
|
// 1) Confirmed referrals from Referral::Referrals (referred → referrer AccountId)
|
||||||
|
val referralModule = runtime.metadata.moduleOrNull("Referral")
|
||||||
|
if (referralModule != null) {
|
||||||
|
val allReferrals: Map<ByteArray, Any?> = referralModule.storage("Referrals").entries(
|
||||||
|
keyExtractor = { it.component1<ByteArray>() },
|
||||||
|
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<ByteArray, Any?> = kycModule.storage("Applications").entries(
|
||||||
|
keyExtractor = { it.component1<ByteArray>() },
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|||||||
+3
@@ -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.tokens.manage.di.ManageTokensComponent
|
||||||
import io.novafoundation.nova.feature_assets.presentation.topup.TopUpAddressCommunicator
|
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.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.asset.di.AssetSellFlowComponent
|
||||||
import io.novafoundation.nova.feature_assets.presentation.trade.sell.flow.network.di.NetworkSellFlowComponent
|
import io.novafoundation.nova.feature_assets.presentation.trade.sell.flow.network.di.NetworkSellFlowComponent
|
||||||
import io.novafoundation.nova.feature_assets.presentation.trade.provider.di.TradeProviderListComponent
|
import io.novafoundation.nova.feature_assets.presentation.trade.provider.di.TradeProviderListComponent
|
||||||
@@ -138,6 +139,8 @@ interface AssetsFeatureComponent : AssetsFeatureApi {
|
|||||||
|
|
||||||
fun waitingNovaCardTopUpComponentFactory(): WaitingNovaCardTopUpComponent.Factory
|
fun waitingNovaCardTopUpComponentFactory(): WaitingNovaCardTopUpComponent.Factory
|
||||||
|
|
||||||
|
fun citizenshipComponentFactory(): CitizenshipComponent.Factory
|
||||||
|
|
||||||
fun inject(view: GoToNftsView)
|
fun inject(view: GoToNftsView)
|
||||||
|
|
||||||
@Component.Factory
|
@Component.Factory
|
||||||
|
|||||||
+10
@@ -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.feature_assets.data.repository.PezkuwiDashboardRepository
|
||||||
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
import io.novafoundation.nova.runtime.ext.ChainGeneses
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
class PezkuwiDashboardInteractor(
|
class PezkuwiDashboardInteractor(
|
||||||
private val repository: PezkuwiDashboardRepository,
|
private val repository: PezkuwiDashboardRepository,
|
||||||
@@ -18,4 +19,13 @@ class PezkuwiDashboardInteractor(
|
|||||||
|
|
||||||
repository.getDashboard(accountId)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
@@ -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.AssetsHeaderHolder
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.ManageAssetsAdapter
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.ManageAssetsAdapter
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.ManageAssetsHolder
|
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.PezkuwiDashboardAdapter
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.list.view.PezkuwiDashboardHolder
|
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.BannerHolder
|
||||||
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter
|
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter
|
||||||
import io.novafoundation.nova.feature_banners_api.presentation.bindWithAdapter
|
import io.novafoundation.nova.feature_banners_api.presentation.bindWithAdapter
|
||||||
@@ -175,6 +177,18 @@ class BalanceListFragment :
|
|||||||
viewModel.pendingOperationsCountModel.observe(headerAdapter::setPendingOperationsCountModel)
|
viewModel.pendingOperationsCountModel.observe(headerAdapter::setPendingOperationsCountModel)
|
||||||
viewModel.filtersIndicatorIcon.observe(headerAdapter::setFilterIconRes)
|
viewModel.filtersIndicatorIcon.observe(headerAdapter::setFilterIconRes)
|
||||||
viewModel.assetViewModeModelFlow.observe { manageAssetsAdapter.setAssetViewModeModel(it) }
|
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) {
|
override fun assetClicked(asset: Chain.Asset) {
|
||||||
@@ -256,6 +270,14 @@ class BalanceListFragment :
|
|||||||
viewModel.basvuruClicked()
|
viewModel.basvuruClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSignClicked() {
|
||||||
|
viewModel.basvuruClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onShareReferralClicked() {
|
||||||
|
viewModel.shareReferralClicked()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupRecyclerViewSpacing() {
|
private fun setupRecyclerViewSpacing() {
|
||||||
binder.balanceListAssets.addSpaceItemDecoration {
|
binder.balanceListAssets.addSpaceItemDecoration {
|
||||||
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
|
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
|
||||||
|
|||||||
+19
-3
@@ -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.data.multisig.MultisigPendingOperationsService
|
||||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.SelectedAccountUseCase
|
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.MetaAccount
|
||||||
|
import io.novafoundation.nova.feature_account_api.domain.model.defaultSubstrateAddress
|
||||||
import io.novafoundation.nova.feature_assets.R
|
import io.novafoundation.nova.feature_assets.R
|
||||||
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
import io.novafoundation.nova.feature_assets.domain.WalletInteractor
|
||||||
import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor
|
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.domain.sessions.WalletConnectSessionsUseCase
|
||||||
import io.novafoundation.nova.feature_wallet_connect_api.presentation.mapNumberOfActiveSessionsToUi
|
import io.novafoundation.nova.feature_wallet_connect_api.presentation.mapNumberOfActiveSessionsToUi
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||||
|
import java.math.BigInteger
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
@@ -114,6 +116,12 @@ class BalanceListViewModel(
|
|||||||
private val _showBalanceBreakdownEvent = MutableLiveData<Event<TotalBalanceBreakdownModel>>()
|
private val _showBalanceBreakdownEvent = MutableLiveData<Event<TotalBalanceBreakdownModel>>()
|
||||||
val showBalanceBreakdownEvent: LiveData<Event<TotalBalanceBreakdownModel>> = _showBalanceBreakdownEvent
|
val showBalanceBreakdownEvent: LiveData<Event<TotalBalanceBreakdownModel>> = _showBalanceBreakdownEvent
|
||||||
|
|
||||||
|
private val _openCitizenshipEvent = MutableLiveData<Event<Unit>>()
|
||||||
|
val openCitizenshipEvent: LiveData<Event<Unit>> = _openCitizenshipEvent
|
||||||
|
|
||||||
|
private val _shareReferralEvent = MutableLiveData<Event<String>>()
|
||||||
|
val shareReferralEvent: LiveData<Event<String>> = _shareReferralEvent
|
||||||
|
|
||||||
val bannersMixin = promotionBannersMixinFactory.create(bannerSourceFactory.assetsSource(), viewModelScope)
|
val bannersMixin = promotionBannersMixinFactory.create(bannerSourceFactory.assetsSource(), viewModelScope)
|
||||||
|
|
||||||
private val selectedCurrency = currencyInteractor.observeSelectCurrency()
|
private val selectedCurrency = currencyInteractor.observeSelectCurrency()
|
||||||
@@ -230,7 +238,8 @@ class BalanceListViewModel(
|
|||||||
PezkuwiDashboardModel(
|
PezkuwiDashboardModel(
|
||||||
roles = data.roles,
|
roles = data.roles,
|
||||||
trustScore = data.trustScore.toString(),
|
trustScore = data.trustScore.toString(),
|
||||||
welatiCount = NumberFormat.getIntegerInstance().format(data.welatiCount)
|
welatiCount = NumberFormat.getIntegerInstance().format(data.welatiCount),
|
||||||
|
citizenshipStatus = data.citizenshipStatus
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.getOrNull()
|
.getOrNull()
|
||||||
@@ -400,8 +409,15 @@ class BalanceListViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun basvuruClicked() {
|
fun basvuruClicked() = launchUnit {
|
||||||
showBrowser("https://t.me/pezkuwichainBot")
|
_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 {
|
fun novaCardClicked() = launchUnit {
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,10 @@
|
|||||||
package io.novafoundation.nova.feature_assets.presentation.balance.list.model
|
package io.novafoundation.nova.feature_assets.presentation.balance.list.model
|
||||||
|
|
||||||
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
|
||||||
|
|
||||||
data class PezkuwiDashboardModel(
|
data class PezkuwiDashboardModel(
|
||||||
val roles: List<String>,
|
val roles: List<String>,
|
||||||
val trustScore: String,
|
val trustScore: String,
|
||||||
val welatiCount: String
|
val welatiCount: String,
|
||||||
|
val citizenshipStatus: CitizenshipStatus = CitizenshipStatus.NOT_STARTED
|
||||||
)
|
)
|
||||||
|
|||||||
+32
@@ -2,6 +2,7 @@ package io.novafoundation.nova.feature_assets.presentation.balance.list.view
|
|||||||
|
|
||||||
import android.content.res.ColorStateList
|
import android.content.res.ColorStateList
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.chip.Chip
|
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.R
|
||||||
import io.novafoundation.nova.feature_assets.databinding.ItemPezkuwiDashboardBinding
|
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.balance.list.model.PezkuwiDashboardModel
|
||||||
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
|
||||||
|
|
||||||
class PezkuwiDashboardAdapter(
|
class PezkuwiDashboardAdapter(
|
||||||
private val handler: Handler
|
private val handler: Handler
|
||||||
@@ -19,6 +21,8 @@ class PezkuwiDashboardAdapter(
|
|||||||
|
|
||||||
interface Handler {
|
interface Handler {
|
||||||
fun onBasvuruClicked()
|
fun onBasvuruClicked()
|
||||||
|
fun onSignClicked()
|
||||||
|
fun onShareReferralClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var model: PezkuwiDashboardModel? = null
|
private var model: PezkuwiDashboardModel? = null
|
||||||
@@ -53,12 +57,40 @@ class PezkuwiDashboardHolder(
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
binder.pezkuwiDashboardBasvuruButton.setOnClickListener { handler.onBasvuruClicked() }
|
binder.pezkuwiDashboardBasvuruButton.setOnClickListener { handler.onBasvuruClicked() }
|
||||||
|
binder.pezkuwiDashboardSignButton.setOnClickListener { handler.onSignClicked() }
|
||||||
|
binder.pezkuwiDashboardShareButton.setOnClickListener { handler.onShareReferralClicked() }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bind(model: PezkuwiDashboardModel) {
|
fun bind(model: PezkuwiDashboardModel) {
|
||||||
bindRoles(model.roles)
|
bindRoles(model.roles)
|
||||||
binder.pezkuwiDashboardTrustValue.text = model.trustScore
|
binder.pezkuwiDashboardTrustValue.text = model.trustScore
|
||||||
binder.pezkuwiDashboardWelatiCount.text = model.welatiCount
|
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<String>) {
|
private fun bindRoles(roles: List<String>) {
|
||||||
|
|||||||
+258
@@ -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<CitizenshipViewModel, FragmentCitizenshipBottomSheetBinding>() {
|
||||||
|
|
||||||
|
override fun createBinding() = FragmentCitizenshipBottomSheetBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
override fun inject() {
|
||||||
|
FeatureUtils.getFeature<AssetsFeatureComponent>(
|
||||||
|
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<PendingApproval>) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+264
@@ -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> = _citizenshipStatus
|
||||||
|
|
||||||
|
private val _isLoading = MutableLiveData(false)
|
||||||
|
val isLoading: LiveData<Boolean> = _isLoading
|
||||||
|
|
||||||
|
private val _dismissEvent = MutableLiveData<Event<Unit>>()
|
||||||
|
val dismissEvent: LiveData<Event<Unit>> = _dismissEvent
|
||||||
|
|
||||||
|
private val _shareEvent = MutableLiveData<Event<String>>()
|
||||||
|
val shareEvent: LiveData<Event<String>> = _shareEvent
|
||||||
|
|
||||||
|
private val _pendingApprovals = MutableLiveData<List<PendingApproval>>(emptyList())
|
||||||
|
val pendingApprovals: LiveData<List<PendingApproval>> = _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<String, Any?>(
|
||||||
|
"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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+26
@@ -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)
|
||||||
|
}
|
||||||
+59
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<stroke
|
||||||
|
android:width="1dp"
|
||||||
|
android:color="#80FFFFFF" />
|
||||||
|
<corners android:radius="4dp" />
|
||||||
|
<padding
|
||||||
|
android:left="12dp"
|
||||||
|
android:right="12dp"
|
||||||
|
android:top="4dp"
|
||||||
|
android:bottom="4dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/citizenshipTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/citizenship_title"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/citizenshipStatusText"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:layout_marginBottom="12dp" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/citizenshipFormScroll"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:fillViewport="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/citizenshipFormContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/citizenshipNameLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="@string/citizenship_name"
|
||||||
|
app:boxStrokeColor="@android:color/white"
|
||||||
|
app:hintTextColor="@android:color/white">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/citizenshipNameInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:inputType="textPersonName" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/citizenshipFatherNameLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="@string/citizenship_father_name"
|
||||||
|
app:boxStrokeColor="@android:color/white"
|
||||||
|
app:hintTextColor="@android:color/white">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/citizenshipFatherNameInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:inputType="textPersonName" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/citizenshipGrandfatherNameLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="@string/citizenship_grandfather_name"
|
||||||
|
app:boxStrokeColor="@android:color/white"
|
||||||
|
app:hintTextColor="@android:color/white">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/citizenshipGrandfatherNameInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:inputType="textPersonName" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/citizenshipMotherNameLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="@string/citizenship_mother_name"
|
||||||
|
app:boxStrokeColor="@android:color/white"
|
||||||
|
app:hintTextColor="@android:color/white">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/citizenshipMotherNameInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:inputType="textPersonName" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/citizenshipTribeLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="@string/citizenship_tribe"
|
||||||
|
app:boxStrokeColor="@android:color/white"
|
||||||
|
app:hintTextColor="@android:color/white">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/citizenshipTribeInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:inputType="text" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/citizenship_region"
|
||||||
|
android:textColor="#B0BEC5"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp" />
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/citizenshipRegionSpinner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:background="@drawable/bg_spinner_outlined"
|
||||||
|
android:paddingStart="12dp"
|
||||||
|
android:paddingEnd="12dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/citizenshipReferrerLayout"
|
||||||
|
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:hint="@string/citizenship_referrer_hint"
|
||||||
|
app:boxStrokeColor="@android:color/white"
|
||||||
|
app:hintTextColor="@android:color/white">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/citizenshipReferrerInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:inputType="text"
|
||||||
|
android:textSize="12sp" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/citizenshipProgress"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/citizenshipActionButton"
|
||||||
|
style="@style/Widget.Nova.MaterialButton.Primary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/citizenship_apply"
|
||||||
|
android:textAllCaps="false" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/citizenshipShareButton"
|
||||||
|
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/citizenship_share_button"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:strokeColor="@android:color/white"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<!-- Invitations list (visible only for citizens) -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/citizenshipInvitationsHeader"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:textColor="#B0BEC5"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/citizenshipInvitationsScroll"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/citizenshipInvitationsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
</ScrollView>
|
||||||
|
</LinearLayout>
|
||||||
@@ -96,25 +96,38 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Info text -->
|
<!-- Action buttons: Kesk (green), Sor (red), Zer (yellow) -->
|
||||||
<TextView
|
|
||||||
android:id="@+id/pezkuwiDashboardInfo"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:text="@string/pezkuwi_dashboard_info"
|
|
||||||
android:textColor="#CFD8DC"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:lineSpacingExtra="2dp" />
|
|
||||||
|
|
||||||
<!-- Action button -->
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/pezkuwiDashboardBasvuruButton"
|
android:id="@+id/pezkuwiDashboardBasvuruButton"
|
||||||
style="@style/Widget.Nova.MaterialButton.Primary"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:text="@string/pezkuwi_dashboard_basvuru"
|
android:text="@string/pezkuwi_dashboard_basvuru"
|
||||||
android:textAllCaps="false" />
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:backgroundTint="#4CAF50"
|
||||||
|
app:cornerRadius="8dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pezkuwiDashboardSignButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/citizenship_sign"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:backgroundTint="#E53935"
|
||||||
|
app:cornerRadius="8dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pezkuwiDashboardShareButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:text="@string/citizenship_share_button"
|
||||||
|
android:textAllCaps="false"
|
||||||
|
android:textColor="#212121"
|
||||||
|
app:backgroundTint="#FDD835"
|
||||||
|
app:cornerRadius="8dp" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|||||||
Reference in New Issue
Block a user