feat: add Pezkuwi Dashboard card with live People Chain data

- Dashboard card on Assets page showing roles, trust score, referral,
  staking, and perwerde points from People Chain pallets
- Repository queries: Tiki, Trust, Referral, StakingScore, Perwerde
- CachedStakingDetails double map query (RelayChain + AssetHub sources)
- Full i18n support across all 15 locales including new Turkish locale
- "Apply & Actions" button opens Telegram bot
- Staking improvements for split ecosystem multi-endpoint stats
This commit is contained in:
2026-02-17 00:10:23 +03:00
parent 9899bb5c40
commit 93e94cbf15
37 changed files with 741 additions and 48 deletions
@@ -0,0 +1,11 @@
package io.novafoundation.nova.feature_assets.data.model
import java.math.BigInteger
data class PezkuwiDashboardData(
val roles: List<String>,
val trustScore: BigInteger,
val totalReferrals: Int,
val stakedAmount: BigInteger,
val perwerdePoints: Int
)
@@ -0,0 +1,119 @@
package io.novafoundation.nova.feature_assets.data.repository
import io.novafoundation.nova.common.data.network.runtime.binding.bindInt
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.castToStruct
import io.novafoundation.nova.feature_assets.data.model.PezkuwiDashboardData
import io.novafoundation.nova.runtime.ext.ChainGeneses
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
import io.novasama.substrate_sdk_android.runtime.AccountId
import io.novasama.substrate_sdk_android.runtime.definitions.types.composite.DictEnum
import io.novasama.substrate_sdk_android.runtime.metadata.moduleOrNull
import io.novasama.substrate_sdk_android.runtime.metadata.storage
import java.math.BigInteger
class PezkuwiDashboardRepository(
private val remoteStorageDataSource: StorageDataSource
) {
suspend fun getDashboard(accountId: AccountId): PezkuwiDashboardData {
val chainId = ChainGeneses.PEZKUWI_PEOPLE
val roles = queryRoles(chainId, accountId)
val trustScore = queryTrustScore(chainId, accountId)
val totalReferrals = queryReferrals(chainId, accountId)
val stakedAmount = queryStakedAmount(chainId, accountId)
val perwerdePoints = queryPerwerdePoints(chainId, accountId)
return PezkuwiDashboardData(
roles = roles,
trustScore = trustScore,
totalReferrals = totalReferrals,
stakedAmount = stakedAmount,
perwerdePoints = perwerdePoints
)
}
private suspend fun queryRoles(chainId: String, accountId: AccountId): List<String> = runCatching {
remoteStorageDataSource.query(chainId) {
val tikiModule = runtime.metadata.moduleOrNull("Tiki") ?: return@query emptyList()
val result = tikiModule.storage("UserTikis").query(accountId, binding = { decoded ->
decoded?.castToList()?.map { entry ->
entry!!.castToDictEnum().name
} ?: emptyList()
})
result
}
}.getOrDefault(emptyList())
private suspend fun queryTrustScore(chainId: String, accountId: AccountId): BigInteger = runCatching {
remoteStorageDataSource.query(chainId) {
val trustModule = runtime.metadata.moduleOrNull("Trust") ?: return@query BigInteger.ZERO
trustModule.storage("TrustScores").query(accountId, binding = { decoded ->
decoded?.let { bindNumber(it) } ?: BigInteger.ZERO
})
}
}.getOrDefault(BigInteger.ZERO)
private suspend fun queryReferrals(chainId: String, accountId: AccountId): Int = runCatching {
remoteStorageDataSource.query(chainId) {
val referralModule = runtime.metadata.moduleOrNull("Referral") ?: return@query 0
referralModule.storage("ReferrerStatsStorage").query(accountId, binding = { decoded ->
decoded?.castToStruct()?.let { struct ->
bindInt(struct["total_referrals"])
} ?: 0
})
}
}.getOrDefault(0)
private suspend fun queryStakedAmount(chainId: String, accountId: AccountId): BigInteger = runCatching {
remoteStorageDataSource.query(chainId) {
val stakingModule = runtime.metadata.moduleOrNull("StakingScore") ?: return@query BigInteger.ZERO
val relayChainKey = DictEnum.Entry("RelayChain", null)
val assetHubKey = DictEnum.Entry("AssetHub", null)
val relayStaked = runCatching {
stakingModule.storage("CachedStakingDetails").query(accountId, relayChainKey, binding = { decoded ->
decoded?.castToStruct()?.let { struct ->
bindNumber(struct["staked_amount"])
} ?: BigInteger.ZERO
})
}.getOrDefault(BigInteger.ZERO)
val assetHubStaked = runCatching {
stakingModule.storage("CachedStakingDetails").query(accountId, assetHubKey, binding = { decoded ->
decoded?.castToStruct()?.let { struct ->
bindNumber(struct["staked_amount"])
} ?: BigInteger.ZERO
})
}.getOrDefault(BigInteger.ZERO)
relayStaked.add(assetHubStaked)
}
}.getOrDefault(BigInteger.ZERO)
private suspend fun queryPerwerdePoints(chainId: String, accountId: AccountId): Int = runCatching {
remoteStorageDataSource.query(chainId) {
val perwerdeModule = runtime.metadata.moduleOrNull("Perwerde") ?: return@query 0
val courseIds = perwerdeModule.storage("StudentCourses").query(accountId, binding = { decoded ->
decoded?.castToList()?.map { bindInt(it) } ?: emptyList()
})
if (courseIds.isEmpty()) return@query 0
courseIds.sumOf { courseId ->
runCatching {
perwerdeModule.storage("Enrollments").query(courseId, accountId, binding = { decoded ->
decoded?.castToStruct()?.let { struct ->
bindInt(struct["points_earned"])
} ?: 0
})
}.getOrDefault(0)
}
}
}.getOrDefault(0)
}
@@ -0,0 +1,21 @@
package io.novafoundation.nova.feature_assets.domain.dashboard
import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
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
class PezkuwiDashboardInteractor(
private val repository: PezkuwiDashboardRepository,
private val chainRegistry: ChainRegistry
) {
suspend fun getDashboard(metaAccount: MetaAccount): Result<PezkuwiDashboardData> = runCatching {
val peopleChain = chainRegistry.getChain(ChainGeneses.PEZKUWI_PEOPLE)
val accountId = metaAccount.accountIdIn(peopleChain)
?: error("No account for People chain")
repository.getDashboard(accountId)
}
}
@@ -7,6 +7,7 @@ import coil.ImageLoader
import io.novafoundation.nova.common.base.BaseFragment
import io.novafoundation.nova.common.di.FeatureUtils
import io.novafoundation.nova.common.list.EditablePlaceholderAdapter
import io.novafoundation.nova.common.mixin.impl.observeBrowserEvents
import io.novafoundation.nova.common.utils.insets.applyStatusBarInsets
import io.novafoundation.nova.common.utils.hideKeyboard
import io.novafoundation.nova.common.utils.recyclerView.expandable.ExpandableAnimationSettings
@@ -31,6 +32,8 @@ 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 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_banners_api.presentation.BannerHolder
import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter
import io.novafoundation.nova.feature_banners_api.presentation.bindWithAdapter
@@ -41,7 +44,8 @@ class BalanceListFragment :
BaseFragment<BalanceListViewModel, FragmentBalanceListBinding>(),
BalanceListAdapter.ItemAssetHandler,
AssetsHeaderAdapter.Handler,
ManageAssetsAdapter.Handler {
ManageAssetsAdapter.Handler,
PezkuwiDashboardAdapter.Handler {
override fun createBinding() = FragmentBalanceListBinding.inflate(layoutInflater)
@@ -54,6 +58,10 @@ class BalanceListFragment :
AssetsHeaderAdapter(this)
}
private val pezkuwiDashboardAdapter by lazy(LazyThreadSafetyMode.NONE) {
PezkuwiDashboardAdapter(this)
}
private val bannerAdapter: PromotionBannerAdapter by lazy(LazyThreadSafetyMode.NONE) {
PromotionBannerAdapter(closable = true)
}
@@ -74,7 +82,7 @@ class BalanceListFragment :
}
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
ConcatAdapter(headerAdapter, bannerAdapter, manageAssetsAdapter, emptyAssetsPlaceholder, assetsAdapter)
ConcatAdapter(headerAdapter, pezkuwiDashboardAdapter, bannerAdapter, manageAssetsAdapter, emptyAssetsPlaceholder, assetsAdapter)
}
override fun applyInsets(rootView: View) {
@@ -111,6 +119,16 @@ class BalanceListFragment :
override fun subscribe(viewModel: BalanceListViewModel) {
setupBuySellSelectorMixin(viewModel.buySellSelectorMixin)
observeBrowserEvents(viewModel)
viewModel.pezkuwiDashboardFlow.observe { model ->
if (model != null) {
pezkuwiDashboardAdapter.setModel(model)
pezkuwiDashboardAdapter.show(true)
} else {
pezkuwiDashboardAdapter.show(false)
}
}
viewModel.bannersMixin.bindWithAdapter(bannerAdapter) {
binder.balanceListAssets.invalidateItemDecorations()
@@ -234,8 +252,15 @@ class BalanceListFragment :
viewModel.giftClicked()
}
override fun onBasvuruClicked() {
viewModel.basvuruClicked()
}
private fun setupRecyclerViewSpacing() {
binder.balanceListAssets.addSpaceItemDecoration {
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
add(SpaceBetween(PezkuwiDashboardHolder, BannerHolder, spaceDp = 4))
add(SpaceBetween(PezkuwiDashboardHolder, ManageAssetsHolder, spaceDp = 24))
add(SpaceBetween(AssetsHeaderHolder, BannerHolder, spaceDp = 4))
add(SpaceBetween(BannerHolder, ManageAssetsHolder, spaceDp = 4))
add(SpaceBetween(AssetsHeaderHolder, ManageAssetsHolder, spaceDp = 24))
@@ -14,6 +14,7 @@ import io.novafoundation.nova.common.presentation.LoadingState
import io.novafoundation.nova.common.presentation.masking.MaskableModel
import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.common.utils.Event
import io.novafoundation.nova.common.mixin.api.Browserable
import io.novafoundation.nova.common.utils.formatting.format
import io.novafoundation.nova.common.utils.formatting.formatAsPercentage
import io.novafoundation.nova.common.utils.inBackground
@@ -25,6 +26,8 @@ import io.novafoundation.nova.feature_account_api.domain.model.MetaAccount
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
import io.novafoundation.nova.feature_assets.domain.dashboard.PezkuwiDashboardInteractor
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.PezkuwiDashboardModel
import io.novafoundation.nova.feature_assets.domain.assets.list.NftPreviews
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdown
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdownInteractor
@@ -97,8 +100,9 @@ class BalanceListViewModel(
private val multisigPendingOperationsService: MultisigPendingOperationsService,
private val novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
private val maskingModeUseCase: MaskingModeUseCase,
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin
) : BaseViewModel() {
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
private val pezkuwiDashboardInteractor: PezkuwiDashboardInteractor
) : BaseViewModel(), Browserable.Presentation by Browserable() {
private val maskableAmountFormatterFlow = maskableValueFormatterProvider.provideFormatter()
.shareInBackground()
@@ -218,6 +222,22 @@ class BalanceListViewModel(
.combine(maskableAmountFormatterFlow, ::formatPendingOperationsCount)
.shareInBackground()
val pezkuwiDashboardFlow = selectedMetaAccount
.mapLatest { metaAccount ->
pezkuwiDashboardInteractor.getDashboard(metaAccount)
.map { data ->
PezkuwiDashboardModel(
roles = data.roles,
trustScore = data.trustScore.toString(),
referralPoints = data.totalReferrals.toString(),
stakingPoints = data.stakedAmount.toString(),
perwerdePoints = data.perwerdePoints.toString()
)
}
.getOrNull()
}
.shareInBackground()
init {
selectedCurrency
.onEach { fullSync() }
@@ -381,6 +401,10 @@ class BalanceListViewModel(
}
}
fun basvuruClicked() {
showBrowser("https://t.me/pezkuwichainBot")
}
fun novaCardClicked() = launchUnit {
novaCardRestrictionCheckMixin.checkRestrictionAndDo {
router.openNovaCard()
@@ -15,10 +15,16 @@ import io.novafoundation.nova.common.resources.ResourceManager
import io.novafoundation.nova.feature_account_api.data.multisig.MultisigPendingOperationsService
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
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.domain.WalletInteractor
import io.novafoundation.nova.feature_assets.domain.assets.ExternalBalancesInteractor
import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdownInteractor
import io.novafoundation.nova.feature_assets.domain.dashboard.PezkuwiDashboardInteractor
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
import io.novafoundation.nova.feature_assets.presentation.AssetsRouter
import io.novafoundation.nova.feature_assets.presentation.balance.common.AssetListMixinFactory
import io.novafoundation.nova.feature_assets.presentation.balance.common.ExpandableAssetsMixinFactory
@@ -79,6 +85,23 @@ class BalanceListModule {
)
}
@Provides
@ScreenScope
fun providePezkuwiDashboardRepository(
@Named(REMOTE_STORAGE_SOURCE) remoteStorageDataSource: StorageDataSource
): PezkuwiDashboardRepository {
return PezkuwiDashboardRepository(remoteStorageDataSource)
}
@Provides
@ScreenScope
fun providePezkuwiDashboardInteractor(
repository: PezkuwiDashboardRepository,
chainRegistry: ChainRegistry
): PezkuwiDashboardInteractor {
return PezkuwiDashboardInteractor(repository, chainRegistry)
}
@Provides
@IntoMap
@ViewModelKey(BalanceListViewModel::class)
@@ -103,6 +126,7 @@ class BalanceListModule {
maskingModeUseCase: MaskingModeUseCase,
fiatFormatter: FiatFormatter,
giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
pezkuwiDashboardInteractor: PezkuwiDashboardInteractor,
): ViewModel {
return BalanceListViewModel(
promotionBannersMixinFactory = promotionBannersMixinFactory,
@@ -124,7 +148,8 @@ class BalanceListModule {
novaCardRestrictionCheckMixin = novaCardRestrictionCheckMixin,
maskingModeUseCase = maskingModeUseCase,
fiatFormatter = fiatFormatter,
giftsRestrictionCheckMixin = giftsRestrictionCheckMixin
giftsRestrictionCheckMixin = giftsRestrictionCheckMixin,
pezkuwiDashboardInteractor = pezkuwiDashboardInteractor
)
}
@@ -0,0 +1,9 @@
package io.novafoundation.nova.feature_assets.presentation.balance.list.model
data class PezkuwiDashboardModel(
val roles: List<String>,
val trustScore: String,
val referralPoints: String,
val stakingPoints: String,
val perwerdePoints: String
)
@@ -0,0 +1,88 @@
package io.novafoundation.nova.feature_assets.presentation.balance.list.view
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.chip.Chip
import io.novafoundation.nova.common.list.SingleItemAdapter
import io.novafoundation.nova.common.utils.dp
import io.novafoundation.nova.common.utils.inflater
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
class PezkuwiDashboardAdapter(
private val handler: Handler
) : SingleItemAdapter<PezkuwiDashboardHolder>(isShownByDefault = false) {
interface Handler {
fun onBasvuruClicked()
}
private var model: PezkuwiDashboardModel? = null
fun setModel(model: PezkuwiDashboardModel) {
this.model = model
notifyChangedIfShown()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PezkuwiDashboardHolder {
val binding = ItemPezkuwiDashboardBinding.inflate(parent.inflater(), parent, false)
return PezkuwiDashboardHolder(binding, handler)
}
override fun onBindViewHolder(holder: PezkuwiDashboardHolder, position: Int) {
model?.let { holder.bind(it) }
}
override fun getItemViewType(position: Int): Int {
return PezkuwiDashboardHolder.viewType
}
}
class PezkuwiDashboardHolder(
private val binder: ItemPezkuwiDashboardBinding,
handler: PezkuwiDashboardAdapter.Handler
) : RecyclerView.ViewHolder(binder.root) {
companion object : WithViewType {
override val viewType: Int = R.layout.item_pezkuwi_dashboard
}
init {
binder.pezkuwiDashboardBasvuruButton.setOnClickListener { handler.onBasvuruClicked() }
}
fun bind(model: PezkuwiDashboardModel) {
bindRoles(model.roles)
binder.pezkuwiDashboardTrustValue.text = model.trustScore
binder.pezkuwiDashboardReferralValue.text = model.referralPoints
binder.pezkuwiDashboardStakingValue.text = model.stakingPoints
binder.pezkuwiDashboardPerwerdeValue.text = model.perwerdePoints
}
private fun bindRoles(roles: List<String>) {
val flexbox = binder.pezkuwiDashboardRoles
flexbox.removeAllViews()
roles.forEach { role ->
val chip = Chip(flexbox.context).apply {
text = role
isClickable = false
isCheckable = false
setTextColor(Color.WHITE)
chipBackgroundColor = ColorStateList.valueOf(0x33FFFFFF)
chipStrokeWidth = 0f
val params = ViewGroup.MarginLayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
params.setMargins(0, 0, 8.dp(context), 4.dp(context))
layoutParams = params
}
flexbox.addView(chip)
}
}
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="135"
android:startColor="#FF1A237E"
android:centerColor="#FF283593"
android:endColor="#FF3949AB"
android:type="linear" />
<corners android:radius="16dp" />
</shape>
@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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:layout_marginHorizontal="16dp"
app:cardCornerRadius="16dp"
app:cardElevation="0dp"
app:strokeWidth="0dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/bg_pezkuwi_dashboard"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/pezkuwiDashboardTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pezkuwi_dashboard_title"
android:textColor="@android:color/white"
android:textSize="18sp"
android:textStyle="bold" />
<com.google.android.flexbox.FlexboxLayout
android:id="@+id/pezkuwiDashboardRoles"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:flexWrap="wrap"
app:alignItems="center"
app:justifyContent="flex_start" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pezkuwi_dashboard_trust_score"
android:textColor="#B0BEC5"
android:textSize="12sp" />
<TextView
android:id="@+id/pezkuwiDashboardTrustValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pezkuwi_dashboard_referral"
android:textColor="#B0BEC5"
android:textSize="12sp" />
<TextView
android:id="@+id/pezkuwiDashboardReferralValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pezkuwi_dashboard_staking"
android:textColor="#B0BEC5"
android:textSize="12sp" />
<TextView
android:id="@+id/pezkuwiDashboardStakingValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pezkuwi_dashboard_perwerde"
android:textColor="#B0BEC5"
android:textSize="12sp" />
<TextView
android:id="@+id/pezkuwiDashboardPerwerdeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="@android:color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/pezkuwiDashboardBasvuruButton"
style="@style/Widget.Nova.MaterialButton.Primary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/pezkuwi_dashboard_basvuru"
android:textAllCaps="false" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>