mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-23 17:45:45 +00:00
feat: add Mining Simulation card to fill empty space in Pezkuwi Trust Score card
Cosmetic, client-side-only preview of a trust-score-weighted PEZ emission share, meant to incentivize referral usage (higher trust score -> visibly faster mining). Not tied to any real distribution mechanism or backend. - MiningSimulationFormula: pure calc. Rate per minute = (92.5M PEZ / 43200 min era) * (trustScore / 500,000 reference total) - trust score is a direct multiplier so zero trust score always yields zero rate. Diamonds only accrue while a 24h session is active. - MiningSimulationRepository: local-only persistence via the existing Preferences wrapper (same pattern as BannerVisibilityRepository), keyed per accountId. Tapping the square freezes the live total as the new base and starts a fresh 24h session - never resets progress on tap. A full 43200-minute era rolling over resets the total to 0 (that era's pool is considered distributed) independently of session taps. - UI: square button in the dashboard card's previously-empty right column, red when inactive (needs a tap), gold (#FDB813, matching the existing Trust Score color) while actively mining. Small Telegram icon opens https://t.me/DKSKurdistanBot via the existing Browserable mixin.
This commit is contained in:
@@ -2777,6 +2777,7 @@
|
|||||||
<string name="pezkuwi_dashboard_start_tracking">Start Tracking</string>
|
<string name="pezkuwi_dashboard_start_tracking">Start Tracking</string>
|
||||||
<string name="pezkuwi_dashboard_tracking_success">Score tracking started!</string>
|
<string name="pezkuwi_dashboard_tracking_success">Score tracking started!</string>
|
||||||
<string name="pezkuwi_dashboard_kurds_title">Hejmara Kurd Le Cihane</string>
|
<string name="pezkuwi_dashboard_kurds_title">Hejmara Kurd Le Cihane</string>
|
||||||
|
<string name="pezkuwi_dashboard_mining_simulation_title">Mining Simulation</string>
|
||||||
|
|
||||||
<string name="pending_signatures_title">Pending Signatures</string>
|
<string name="pending_signatures_title">Pending Signatures</string>
|
||||||
<string name="pending_signatures_sign">Sign</string>
|
<string name="pending_signatures_sign">Sign</string>
|
||||||
|
|||||||
+48
@@ -0,0 +1,48 @@
|
|||||||
|
package io.novafoundation.nova.feature_assets.domain.dashboard
|
||||||
|
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cosmetic, client-side-only preview of what a user's PEZ trust-score-weighted emission share
|
||||||
|
* could look like - not tied to any real distribution mechanism or backend. Numbers loosely track
|
||||||
|
* the real era emission (92.5M PEZ per ~30-day era) without claiming accuracy; see the "referral
|
||||||
|
* incentive" mining card this backs.
|
||||||
|
*/
|
||||||
|
object MiningSimulationFormula {
|
||||||
|
|
||||||
|
private const val ERA_DAYS = 30
|
||||||
|
const val ERA_DURATION_MS = ERA_DAYS * 24 * 60 * 60 * 1000L
|
||||||
|
private const val ERA_MINUTES = ERA_DAYS * 24 * 60
|
||||||
|
private const val ERA_POOL_PEZ = 92_500_000.0
|
||||||
|
|
||||||
|
/** Assumed total trust score across the network - unknowable client-side, so this is a
|
||||||
|
* simulation-only reference point, not a real aggregate. */
|
||||||
|
private const val REFERENCE_TOTAL_TRUST = 500_000.0
|
||||||
|
|
||||||
|
const val SESSION_DURATION_MS = 24 * 60 * 60 * 1000L
|
||||||
|
|
||||||
|
private val poolPerMinute = ERA_POOL_PEZ / ERA_MINUTES
|
||||||
|
|
||||||
|
/** Zero trust score always yields zero rate - trust score is a direct multiplier, not an offset. */
|
||||||
|
fun ratePerMinute(trustScore: BigInteger): Double {
|
||||||
|
if (trustScore <= BigInteger.ZERO) return 0.0
|
||||||
|
|
||||||
|
return poolPerMinute * (trustScore.toDouble() / REFERENCE_TOTAL_TRUST)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isSessionActive(sessionActivatedAt: Long, now: Long): Boolean {
|
||||||
|
return sessionActivatedAt > 0L && now < sessionActivatedAt + SESSION_DURATION_MS
|
||||||
|
}
|
||||||
|
|
||||||
|
/** [baseDiamonds] is the frozen within-era total as of [sessionActivatedAt] (0 = no active
|
||||||
|
* session right now, in which case this is just the frozen total with nothing accruing). */
|
||||||
|
fun diamondsWithinEra(baseDiamonds: Double, sessionActivatedAt: Long, trustScore: BigInteger, now: Long): Double {
|
||||||
|
if (sessionActivatedAt <= 0L) return baseDiamonds
|
||||||
|
|
||||||
|
val sessionEnd = sessionActivatedAt + SESSION_DURATION_MS
|
||||||
|
val effectiveNow = minOf(now, sessionEnd)
|
||||||
|
val elapsedMinutes = (effectiveNow - sessionActivatedAt).coerceAtLeast(0) / 60_000.0
|
||||||
|
|
||||||
|
return baseDiamonds + elapsedMinutes * ratePerMinute(trustScore)
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
package io.novafoundation.nova.feature_assets.domain.dashboard
|
||||||
|
|
||||||
|
import io.novafoundation.nova.common.data.storage.Preferences
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
|
data class MiningSimulationState(
|
||||||
|
val eraStartedAt: Long, // 0 = era never started (no session ever activated)
|
||||||
|
val sessionActivatedAt: Long, // 0 = no active session right now
|
||||||
|
val baseDiamonds: Double, // frozen total within the current era
|
||||||
|
)
|
||||||
|
|
||||||
|
interface MiningSimulationRepository {
|
||||||
|
|
||||||
|
/** Applies any pending era rollover (see [MiningSimulationFormula.ERA_DURATION_MS]) before
|
||||||
|
* returning the current state - callers should always go through this, never read raw prefs. */
|
||||||
|
fun getState(accountId: Long, now: Long): MiningSimulationState
|
||||||
|
|
||||||
|
/** Freezes the live total (as of [now]) as the new base and starts a fresh session -
|
||||||
|
* never resets [MiningSimulationState.baseDiamonds] to zero except via an era rollover. */
|
||||||
|
fun activate(accountId: Long, trustScore: BigInteger, now: Long): MiningSimulationState
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val MICRO_SCALE = 1_000_000L
|
||||||
|
|
||||||
|
class RealMiningSimulationRepository(
|
||||||
|
private val preferences: Preferences
|
||||||
|
) : MiningSimulationRepository {
|
||||||
|
|
||||||
|
override fun getState(accountId: Long, now: Long): MiningSimulationState {
|
||||||
|
return normalize(accountId, now)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun activate(accountId: Long, trustScore: BigInteger, now: Long): MiningSimulationState {
|
||||||
|
val normalized = normalize(accountId, now)
|
||||||
|
|
||||||
|
val liveDiamonds = MiningSimulationFormula.diamondsWithinEra(
|
||||||
|
baseDiamonds = normalized.baseDiamonds,
|
||||||
|
sessionActivatedAt = normalized.sessionActivatedAt,
|
||||||
|
trustScore = trustScore,
|
||||||
|
now = now
|
||||||
|
)
|
||||||
|
|
||||||
|
val eraStartedAt = if (normalized.eraStartedAt == 0L) now else normalized.eraStartedAt
|
||||||
|
|
||||||
|
val newState = normalized.copy(
|
||||||
|
eraStartedAt = eraStartedAt,
|
||||||
|
sessionActivatedAt = now,
|
||||||
|
baseDiamonds = liveDiamonds
|
||||||
|
)
|
||||||
|
persist(accountId, newState)
|
||||||
|
return newState
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Detects and applies an era rollover (>= ERA_DURATION_MS since era start) - resets both the
|
||||||
|
* diamond total and the active session, since that era's simulated pool is "distributed". */
|
||||||
|
private fun normalize(accountId: Long, now: Long): MiningSimulationState {
|
||||||
|
val stored = read(accountId)
|
||||||
|
if (stored.eraStartedAt == 0L) return stored
|
||||||
|
|
||||||
|
val erasElapsed = (now - stored.eraStartedAt) / MiningSimulationFormula.ERA_DURATION_MS
|
||||||
|
if (erasElapsed <= 0L) return stored
|
||||||
|
|
||||||
|
val rolledOver = stored.copy(
|
||||||
|
eraStartedAt = stored.eraStartedAt + erasElapsed * MiningSimulationFormula.ERA_DURATION_MS,
|
||||||
|
sessionActivatedAt = 0L,
|
||||||
|
baseDiamonds = 0.0
|
||||||
|
)
|
||||||
|
persist(accountId, rolledOver)
|
||||||
|
return rolledOver
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun read(accountId: Long): MiningSimulationState {
|
||||||
|
return MiningSimulationState(
|
||||||
|
eraStartedAt = preferences.getLong(eraKey(accountId), 0L),
|
||||||
|
sessionActivatedAt = preferences.getLong(sessionKey(accountId), 0L),
|
||||||
|
baseDiamonds = preferences.getLong(diamondsKey(accountId), 0L) / MICRO_SCALE.toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun persist(accountId: Long, state: MiningSimulationState) {
|
||||||
|
val editor = preferences.edit()
|
||||||
|
editor.putLong(eraKey(accountId), state.eraStartedAt)
|
||||||
|
editor.putLong(sessionKey(accountId), state.sessionActivatedAt)
|
||||||
|
editor.putLong(diamondsKey(accountId), (state.baseDiamonds * MICRO_SCALE).toLong())
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun eraKey(accountId: Long) = "MiningSimulationRepository.era.$accountId"
|
||||||
|
private fun sessionKey(accountId: Long) = "MiningSimulationRepository.session.$accountId"
|
||||||
|
private fun diamondsKey(accountId: Long) = "MiningSimulationRepository.diamonds.$accountId"
|
||||||
|
}
|
||||||
+12
@@ -143,6 +143,10 @@ class BalanceListFragment :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.miningSimulationFlow.observe { model ->
|
||||||
|
pezkuwiDashboardAdapter.setMiningModel(model)
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.bannersMixin.bindWithAdapter(bannerAdapter) {
|
viewModel.bannersMixin.bindWithAdapter(bannerAdapter) {
|
||||||
binder.balanceListAssets.invalidateItemDecorations()
|
binder.balanceListAssets.invalidateItemDecorations()
|
||||||
}
|
}
|
||||||
@@ -301,6 +305,14 @@ class BalanceListFragment :
|
|||||||
viewModel.startTrackingClicked()
|
viewModel.startTrackingClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onMiningSquareClicked() {
|
||||||
|
viewModel.miningSquareClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMiningInfoClicked() {
|
||||||
|
viewModel.miningInfoClicked()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupRecyclerViewSpacing() {
|
private fun setupRecyclerViewSpacing() {
|
||||||
binder.balanceListAssets.addSpaceItemDecoration {
|
binder.balanceListAssets.addSpaceItemDecoration {
|
||||||
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
|
add(SpaceBetween(AssetsHeaderHolder, PezkuwiDashboardHolder, spaceDp = 8))
|
||||||
|
|||||||
+61
-1
@@ -27,7 +27,10 @@ import io.novafoundation.nova.feature_account_api.domain.model.defaultSubstrateA
|
|||||||
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
|
||||||
|
import io.novafoundation.nova.feature_assets.domain.dashboard.MiningSimulationFormula
|
||||||
|
import io.novafoundation.nova.feature_assets.domain.dashboard.MiningSimulationRepository
|
||||||
import io.novafoundation.nova.feature_assets.domain.dashboard.PezkuwiDashboardInteractor
|
import io.novafoundation.nova.feature_assets.domain.dashboard.PezkuwiDashboardInteractor
|
||||||
|
import io.novafoundation.nova.feature_assets.presentation.balance.list.model.MiningSimulationModel
|
||||||
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.domain.assets.list.NftPreviews
|
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.BalanceBreakdown
|
||||||
@@ -74,18 +77,22 @@ 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.extrinsic.call
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.debounce
|
import kotlinx.coroutines.flow.debounce
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.joinAll
|
import kotlinx.coroutines.joinAll
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.math.BigInteger
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
private typealias SyncAction = suspend (MetaAccount) -> Unit
|
private typealias SyncAction = suspend (MetaAccount) -> Unit
|
||||||
@@ -113,7 +120,8 @@ class BalanceListViewModel(
|
|||||||
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
||||||
private val pezkuwiDashboardInteractor: PezkuwiDashboardInteractor,
|
private val pezkuwiDashboardInteractor: PezkuwiDashboardInteractor,
|
||||||
private val extrinsicService: ExtrinsicService,
|
private val extrinsicService: ExtrinsicService,
|
||||||
private val chainRegistry: ChainRegistry
|
private val chainRegistry: ChainRegistry,
|
||||||
|
private val miningSimulationRepository: MiningSimulationRepository
|
||||||
) : BaseViewModel(), Browserable.Presentation by Browserable() {
|
) : BaseViewModel(), Browserable.Presentation by Browserable() {
|
||||||
|
|
||||||
private val maskableAmountFormatterFlow = maskableValueFormatterProvider.provideFormatter()
|
private val maskableAmountFormatterFlow = maskableValueFormatterProvider.provideFormatter()
|
||||||
@@ -139,6 +147,11 @@ class BalanceListViewModel(
|
|||||||
|
|
||||||
private val dashboardRefreshSignal = MutableStateFlow(0)
|
private val dashboardRefreshSignal = MutableStateFlow(0)
|
||||||
|
|
||||||
|
private val diamondFormatter = NumberFormat.getNumberInstance().apply {
|
||||||
|
minimumFractionDigits = 2
|
||||||
|
maximumFractionDigits = 2
|
||||||
|
}
|
||||||
|
|
||||||
val bannersMixin = promotionBannersMixinFactory.create(bannerSourceFactory.assetsSource(), viewModelScope)
|
val bannersMixin = promotionBannersMixinFactory.create(bannerSourceFactory.assetsSource(), viewModelScope)
|
||||||
|
|
||||||
private val selectedCurrency = currencyInteractor.observeSelectCurrency()
|
private val selectedCurrency = currencyInteractor.observeSelectCurrency()
|
||||||
@@ -256,6 +269,7 @@ class BalanceListViewModel(
|
|||||||
PezkuwiDashboardModel(
|
PezkuwiDashboardModel(
|
||||||
roles = data.roles,
|
roles = data.roles,
|
||||||
trustScore = data.trustScore.toString(),
|
trustScore = data.trustScore.toString(),
|
||||||
|
trustScoreRaw = data.trustScore,
|
||||||
welatiCount = NumberFormat.getIntegerInstance().format(data.welatiCount),
|
welatiCount = NumberFormat.getIntegerInstance().format(data.welatiCount),
|
||||||
citizenshipStatus = data.citizenshipStatus,
|
citizenshipStatus = data.citizenshipStatus,
|
||||||
isTrackingScore = data.isTrackingScore
|
isTrackingScore = data.isTrackingScore
|
||||||
@@ -265,6 +279,52 @@ class BalanceListViewModel(
|
|||||||
}
|
}
|
||||||
.shareInBackground()
|
.shareInBackground()
|
||||||
|
|
||||||
|
private val miningRefreshSignal = MutableStateFlow(0)
|
||||||
|
|
||||||
|
private val miningTicker = flow {
|
||||||
|
while (true) {
|
||||||
|
emit(Unit)
|
||||||
|
delay(60_000L)
|
||||||
|
}
|
||||||
|
}.onStart { emit(Unit) }
|
||||||
|
|
||||||
|
val miningSimulationFlow = combine(
|
||||||
|
pezkuwiDashboardFlow,
|
||||||
|
selectedMetaAccount,
|
||||||
|
miningTicker,
|
||||||
|
miningRefreshSignal
|
||||||
|
) { dashboard, metaAccount, _, _ ->
|
||||||
|
val trustScore = dashboard?.trustScoreRaw ?: BigInteger.ZERO
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val state = miningSimulationRepository.getState(metaAccount.id, now)
|
||||||
|
val diamonds = MiningSimulationFormula.diamondsWithinEra(state.baseDiamonds, state.sessionActivatedAt, trustScore, now)
|
||||||
|
|
||||||
|
MiningSimulationModel(
|
||||||
|
diamondsText = diamondFormatter.format(diamonds),
|
||||||
|
isActive = MiningSimulationFormula.isSessionActive(state.sessionActivatedAt, now)
|
||||||
|
)
|
||||||
|
}.shareInBackground()
|
||||||
|
|
||||||
|
fun miningSquareClicked() {
|
||||||
|
launch {
|
||||||
|
val metaAccount = selectedMetaAccount.first()
|
||||||
|
val now = System.currentTimeMillis()
|
||||||
|
val alreadyActive = MiningSimulationFormula.isSessionActive(
|
||||||
|
miningSimulationRepository.getState(metaAccount.id, now).sessionActivatedAt,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
if (alreadyActive) return@launch
|
||||||
|
|
||||||
|
val trustScore = pezkuwiDashboardFlow.first()?.trustScoreRaw ?: BigInteger.ZERO
|
||||||
|
miningSimulationRepository.activate(metaAccount.id, trustScore, now)
|
||||||
|
miningRefreshSignal.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun miningInfoClicked() {
|
||||||
|
showBrowser("https://t.me/DKSKurdistanBot")
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
selectedCurrency
|
selectedCurrency
|
||||||
.onEach { fullSync() }
|
.onEach { fullSync() }
|
||||||
|
|||||||
+12
-1
@@ -7,6 +7,7 @@ import dagger.Module
|
|||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.multibindings.IntoMap
|
import dagger.multibindings.IntoMap
|
||||||
import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
|
import io.novafoundation.nova.common.data.repository.AssetsViewModeRepository
|
||||||
|
import io.novafoundation.nova.common.data.storage.Preferences
|
||||||
import io.novafoundation.nova.common.di.scope.ScreenScope
|
import io.novafoundation.nova.common.di.scope.ScreenScope
|
||||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
import io.novafoundation.nova.common.di.viewmodel.ViewModelKey
|
||||||
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
import io.novafoundation.nova.common.di.viewmodel.ViewModelModule
|
||||||
@@ -20,7 +21,9 @@ 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.ExternalBalancesInteractor
|
||||||
import io.novafoundation.nova.feature_assets.domain.assets.list.AssetsListInteractor
|
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.breakdown.BalanceBreakdownInteractor
|
||||||
|
import io.novafoundation.nova.feature_assets.domain.dashboard.MiningSimulationRepository
|
||||||
import io.novafoundation.nova.feature_assets.domain.dashboard.PezkuwiDashboardInteractor
|
import io.novafoundation.nova.feature_assets.domain.dashboard.PezkuwiDashboardInteractor
|
||||||
|
import io.novafoundation.nova.feature_assets.domain.dashboard.RealMiningSimulationRepository
|
||||||
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
import io.novafoundation.nova.runtime.di.REMOTE_STORAGE_SOURCE
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
|
||||||
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
import io.novafoundation.nova.runtime.storage.source.StorageDataSource
|
||||||
@@ -103,6 +106,12 @@ class BalanceListModule {
|
|||||||
return PezkuwiDashboardInteractor(repository, chainRegistry)
|
return PezkuwiDashboardInteractor(repository, chainRegistry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@ScreenScope
|
||||||
|
fun provideMiningSimulationRepository(preferences: Preferences): MiningSimulationRepository {
|
||||||
|
return RealMiningSimulationRepository(preferences)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(BalanceListViewModel::class)
|
@ViewModelKey(BalanceListViewModel::class)
|
||||||
@@ -130,6 +139,7 @@ class BalanceListModule {
|
|||||||
pezkuwiDashboardInteractor: PezkuwiDashboardInteractor,
|
pezkuwiDashboardInteractor: PezkuwiDashboardInteractor,
|
||||||
extrinsicService: ExtrinsicService,
|
extrinsicService: ExtrinsicService,
|
||||||
chainRegistry: ChainRegistry,
|
chainRegistry: ChainRegistry,
|
||||||
|
miningSimulationRepository: MiningSimulationRepository,
|
||||||
): ViewModel {
|
): ViewModel {
|
||||||
return BalanceListViewModel(
|
return BalanceListViewModel(
|
||||||
promotionBannersMixinFactory = promotionBannersMixinFactory,
|
promotionBannersMixinFactory = promotionBannersMixinFactory,
|
||||||
@@ -154,7 +164,8 @@ class BalanceListModule {
|
|||||||
giftsRestrictionCheckMixin = giftsRestrictionCheckMixin,
|
giftsRestrictionCheckMixin = giftsRestrictionCheckMixin,
|
||||||
pezkuwiDashboardInteractor = pezkuwiDashboardInteractor,
|
pezkuwiDashboardInteractor = pezkuwiDashboardInteractor,
|
||||||
extrinsicService = extrinsicService,
|
extrinsicService = extrinsicService,
|
||||||
chainRegistry = chainRegistry
|
chainRegistry = chainRegistry,
|
||||||
|
miningSimulationRepository = miningSimulationRepository
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
package io.novafoundation.nova.feature_assets.presentation.balance.list.model
|
||||||
|
|
||||||
|
data class MiningSimulationModel(
|
||||||
|
val diamondsText: String,
|
||||||
|
val isActive: Boolean,
|
||||||
|
)
|
||||||
+2
@@ -1,10 +1,12 @@
|
|||||||
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
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
|
||||||
|
import java.math.BigInteger
|
||||||
|
|
||||||
data class PezkuwiDashboardModel(
|
data class PezkuwiDashboardModel(
|
||||||
val roles: List<String>,
|
val roles: List<String>,
|
||||||
val trustScore: String,
|
val trustScore: String,
|
||||||
|
val trustScoreRaw: BigInteger,
|
||||||
val welatiCount: String,
|
val welatiCount: String,
|
||||||
val citizenshipStatus: CitizenshipStatus = CitizenshipStatus.NOT_STARTED,
|
val citizenshipStatus: CitizenshipStatus = CitizenshipStatus.NOT_STARTED,
|
||||||
val isTrackingScore: Boolean = false
|
val isTrackingScore: Boolean = false
|
||||||
|
|||||||
+20
@@ -14,6 +14,7 @@ import io.novafoundation.nova.common.utils.inflater
|
|||||||
import io.novafoundation.nova.common.utils.recyclerView.WithViewType
|
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.MiningSimulationModel
|
||||||
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
|
import io.novafoundation.nova.feature_assets.presentation.citizenship.CitizenshipStatus
|
||||||
|
|
||||||
@@ -26,10 +27,13 @@ class PezkuwiDashboardAdapter(
|
|||||||
fun onSignClicked()
|
fun onSignClicked()
|
||||||
fun onShareReferralClicked()
|
fun onShareReferralClicked()
|
||||||
fun onStartTrackingClicked()
|
fun onStartTrackingClicked()
|
||||||
|
fun onMiningSquareClicked()
|
||||||
|
fun onMiningInfoClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
private var model: PezkuwiDashboardModel? = null
|
private var model: PezkuwiDashboardModel? = null
|
||||||
private var trackingLoading: Boolean = false
|
private var trackingLoading: Boolean = false
|
||||||
|
private var miningModel: MiningSimulationModel? = null
|
||||||
|
|
||||||
// Survives ViewHolder recycling (scroll) within the process, but not process restart —
|
// Survives ViewHolder recycling (scroll) within the process, but not process restart —
|
||||||
// resets to collapsed (false) whenever the app is freshly opened, by design.
|
// resets to collapsed (false) whenever the app is freshly opened, by design.
|
||||||
@@ -45,6 +49,11 @@ class PezkuwiDashboardAdapter(
|
|||||||
notifyChangedIfShown()
|
notifyChangedIfShown()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setMiningModel(model: MiningSimulationModel) {
|
||||||
|
this.miningModel = model
|
||||||
|
notifyChangedIfShown()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PezkuwiDashboardHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PezkuwiDashboardHolder {
|
||||||
val binding = ItemPezkuwiDashboardBinding.inflate(parent.inflater(), parent, false)
|
val binding = ItemPezkuwiDashboardBinding.inflate(parent.inflater(), parent, false)
|
||||||
return PezkuwiDashboardHolder(binding, handler) { expanded -> isExpanded = expanded }
|
return PezkuwiDashboardHolder(binding, handler) { expanded -> isExpanded = expanded }
|
||||||
@@ -52,6 +61,7 @@ class PezkuwiDashboardAdapter(
|
|||||||
|
|
||||||
override fun onBindViewHolder(holder: PezkuwiDashboardHolder, position: Int) {
|
override fun onBindViewHolder(holder: PezkuwiDashboardHolder, position: Int) {
|
||||||
model?.let { holder.bind(it, trackingLoading, isExpanded) }
|
model?.let { holder.bind(it, trackingLoading, isExpanded) }
|
||||||
|
miningModel?.let { holder.bindMining(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int {
|
override fun getItemViewType(position: Int): Int {
|
||||||
@@ -77,6 +87,9 @@ class PezkuwiDashboardHolder(
|
|||||||
|
|
||||||
binder.pezkuwiDashboardCollapsedBar.setOnClickListener { setExpanded(true) }
|
binder.pezkuwiDashboardCollapsedBar.setOnClickListener { setExpanded(true) }
|
||||||
binder.pezkuwiDashboardCollapseButton.setOnClickListener { setExpanded(false) }
|
binder.pezkuwiDashboardCollapseButton.setOnClickListener { setExpanded(false) }
|
||||||
|
|
||||||
|
binder.pezkuwiDashboardMiningSquare.setOnClickListener { handler.onMiningSquareClicked() }
|
||||||
|
binder.pezkuwiDashboardMiningInfoButton.setOnClickListener { handler.onMiningInfoClicked() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setExpanded(expanded: Boolean) {
|
private fun setExpanded(expanded: Boolean) {
|
||||||
@@ -86,6 +99,13 @@ class PezkuwiDashboardHolder(
|
|||||||
onExpandedChanged(expanded)
|
onExpandedChanged(expanded)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun bindMining(model: MiningSimulationModel) {
|
||||||
|
binder.pezkuwiDashboardMiningCount.text = model.diamondsText
|
||||||
|
|
||||||
|
val color = if (model.isActive) Color.parseColor("#FDB813") else Color.parseColor("#E2231A")
|
||||||
|
binder.pezkuwiDashboardMiningSquare.background.mutate().setTint(color)
|
||||||
|
}
|
||||||
|
|
||||||
fun bind(model: PezkuwiDashboardModel, trackingLoading: Boolean = false, isExpanded: Boolean = false) {
|
fun bind(model: PezkuwiDashboardModel, trackingLoading: Boolean = false, isExpanded: Boolean = false) {
|
||||||
bindRoles(model.roles)
|
bindRoles(model.roles)
|
||||||
binder.pezkuwiDashboardTrustValue.text = model.trustScore
|
binder.pezkuwiDashboardTrustValue.text = model.trustScore
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Mining Simulation square - tinted programmatically (red = inactive, gold = actively mining). -->
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
|
||||||
|
<solid android:color="@android:color/white" />
|
||||||
|
|
||||||
|
<corners android:radius="12dp" />
|
||||||
|
</shape>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#29A9EA"
|
||||||
|
android:pathData="M12,2 C6.48,2 2,6.48 2,12 C2,17.52 6.48,22 12,22 C17.52,22 22,17.52 22,12 C22,6.48 17.52,2 12,2 Z" />
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:pathData="M17.3,7.15 L15.4,17.05 C15.25,17.7 14.85,17.85 14.3,17.55 L11.4,15.4 L10,16.75 C9.85,16.9 9.7,17.05 9.4,17.05 L9.6,13.9 L15.05,9 C15.3,8.8 15,8.65 14.7,8.85 L7.95,13.2 L5.1,12.3 C4.45,12.1 4.45,11.65 5.25,11.35 L16.5,7 C17.05,6.8 17.5,7.15 17.3,7.15 Z" />
|
||||||
|
</vector>
|
||||||
@@ -130,6 +130,63 @@
|
|||||||
android:text="@string/pezkuwi_dashboard_kurds_title"
|
android:text="@string/pezkuwi_dashboard_kurds_title"
|
||||||
android:textColor="#7AFFFFFF"
|
android:textColor="#7AFFFFFF"
|
||||||
android:textSize="10sp" />
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
<!-- Mining Simulation: cosmetic, client-side-only preview of a trust-score-weighted
|
||||||
|
emission share (see MiningSimulationFormula) - fills the empty space this
|
||||||
|
right-side column otherwise leaves below the citizen-count stat. -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/pezkuwi_dashboard_mining_simulation_title"
|
||||||
|
android:textColor="#7AFFFFFF"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/pezkuwiDashboardMiningInfoButton"
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:src="@drawable/ic_telegram" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/pezkuwiDashboardMiningSquare"
|
||||||
|
android:layout_width="64dp"
|
||||||
|
android:layout_height="64dp"
|
||||||
|
android:layout_marginTop="6dp"
|
||||||
|
android:background="@drawable/bg_pezkuwi_mining_square"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pezkuwiDashboardMiningCount"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:paddingHorizontal="4dp"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</FrameLayout>
|
||||||
|
</LinearLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|||||||
Reference in New Issue
Block a user