mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-19 19:15:52 +00:00
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:
+2
-2
@@ -48,7 +48,7 @@ android {
|
|||||||
|
|
||||||
buildConfigField "long", "CLOUD_PROJECT_NUMBER", "171267697857L"
|
buildConfigField "long", "CLOUD_PROJECT_NUMBER", "171267697857L"
|
||||||
|
|
||||||
buildConfigField "String", "GLOBAL_CONFIG_URL", "\"https://wallet.pezkuwichain.io/config/global_config_dev.json\""
|
buildConfigField "String", "GLOBAL_CONFIG_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/staking/global_config_dev.json\""
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@@ -61,7 +61,7 @@ android {
|
|||||||
|
|
||||||
buildConfigField "long", "CLOUD_PROJECT_NUMBER", "802342409053L"
|
buildConfigField "long", "CLOUD_PROJECT_NUMBER", "802342409053L"
|
||||||
|
|
||||||
buildConfigField "String", "GLOBAL_CONFIG_URL", "\"https://wallet.pezkuwichain.io/config/global_config.json\""
|
buildConfigField "String", "GLOBAL_CONFIG_URL", "\"https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/staking/global_config.json\""
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseGithub {
|
releaseGithub {
|
||||||
|
|||||||
+16
-7
@@ -6,6 +6,7 @@ import io.novafoundation.nova.common.utils.flowOfAll
|
|||||||
import io.novafoundation.nova.common.utils.inBackground
|
import io.novafoundation.nova.common.utils.inBackground
|
||||||
import io.novafoundation.nova.common.utils.invokeOnCompletion
|
import io.novafoundation.nova.common.utils.invokeOnCompletion
|
||||||
import io.novafoundation.nova.common.utils.singleReplaySharedFlow
|
import io.novafoundation.nova.common.utils.singleReplaySharedFlow
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
@@ -13,6 +14,7 @@ import kotlinx.coroutines.cancel
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
@@ -72,18 +74,25 @@ internal class RealComputationalCache : ComputationalCache, CoroutineScope by Co
|
|||||||
cachedAction: AwaitableConstructor<T>
|
cachedAction: AwaitableConstructor<T>
|
||||||
): T {
|
): T {
|
||||||
val awaitable = mutex.withLock {
|
val awaitable = mutex.withLock {
|
||||||
if (key in memory) {
|
val existing = memory[key]
|
||||||
|
if (existing != null && existing.aggregateScope.isActive) {
|
||||||
Log.d(LOG_TAG, "Key $key requested - already present")
|
Log.d(LOG_TAG, "Key $key requested - already present")
|
||||||
|
|
||||||
val entry = memory.getValue(key)
|
existing.dependents += scope
|
||||||
|
|
||||||
entry.dependents += scope
|
existing.awaitable
|
||||||
|
|
||||||
entry.awaitable
|
|
||||||
} else {
|
} else {
|
||||||
Log.d(LOG_TAG, "Key $key requested - creating new operation")
|
if (existing != null) {
|
||||||
|
Log.d(LOG_TAG, "Key $key requested - stale (aggregateScope cancelled), recreating")
|
||||||
|
memory.remove(key)
|
||||||
|
} else {
|
||||||
|
Log.d(LOG_TAG, "Key $key requested - creating new operation")
|
||||||
|
}
|
||||||
|
|
||||||
val aggregateScope = CoroutineScope(Dispatchers.Default)
|
val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
|
||||||
|
Log.e(LOG_TAG, "Key $key - upstream error in aggregateScope", throwable)
|
||||||
|
}
|
||||||
|
val aggregateScope = CoroutineScope(Dispatchers.Default + exceptionHandler)
|
||||||
val awaitable = cachedAction(aggregateScope)
|
val awaitable = cachedAction(aggregateScope)
|
||||||
|
|
||||||
memory[key] = Entry(dependents = mutableSetOf(scope), aggregateScope, awaitable)
|
memory[key] = Entry(dependents = mutableSetOf(scope), aggregateScope, awaitable)
|
||||||
|
|||||||
@@ -2044,4 +2044,11 @@
|
|||||||
<string name="bridge_enter_amount">Ingresa la cantidad</string>
|
<string name="bridge_enter_amount">Ingresa la cantidad</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Los intercambios HEZ→DOT pueden tener disponibilidad limitada según la liquidez.</string>
|
<string name="bridge_hez_to_dot_warning">Los intercambios HEZ→DOT pueden tener disponibilidad limitada según la liquidez.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Los intercambios HEZ→DOT no están disponibles temporalmente. Inténtalo de nuevo cuando haya suficiente liquidez de DOT.</string>
|
<string name="bridge_hez_to_dot_blocked">Los intercambios HEZ→DOT no están disponibles temporalmente. Inténtalo de nuevo cuando haya suficiente liquidez de DOT.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Puntuación de confianza</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Referido</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Solicitar y Acciones</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2044,4 +2044,11 @@
|
|||||||
<string name="bridge_enter_amount">Entrez le montant</string>
|
<string name="bridge_enter_amount">Entrez le montant</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Les échanges HEZ→DOT peuvent avoir une disponibilité limitée selon la liquidité.</string>
|
<string name="bridge_hez_to_dot_warning">Les échanges HEZ→DOT peuvent avoir une disponibilité limitée selon la liquidité.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Les échanges HEZ→DOT sont temporairement indisponibles. Réessayez lorsque la liquidité DOT sera suffisante.</string>
|
<string name="bridge_hez_to_dot_blocked">Les échanges HEZ→DOT sont temporairement indisponibles. Réessayez lorsque la liquidité DOT sera suffisante.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Score de confiance</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Parrainage</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Demande et Actions</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2044,4 +2044,11 @@
|
|||||||
<string name="bridge_enter_amount">Írd be az összeget</string>
|
<string name="bridge_enter_amount">Írd be az összeget</string>
|
||||||
<string name="bridge_hez_to_dot_warning">A HEZ→DOT cserék korlátozott elérhetőségűek lehetnek a likviditástól függően.</string>
|
<string name="bridge_hez_to_dot_warning">A HEZ→DOT cserék korlátozott elérhetőségűek lehetnek a likviditástól függően.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">A HEZ→DOT cserék ideiglenesen nem elérhetők. Próbáld újra, amikor elegendő DOT likviditás áll rendelkezésre.</string>
|
<string name="bridge_hez_to_dot_blocked">A HEZ→DOT cserék ideiglenesen nem elérhetők. Próbáld újra, amikor elegendő DOT likviditás áll rendelkezésre.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Bizalmi pontszám</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Ajánlás</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Jelentkezés és Műveletek</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2030,4 +2030,11 @@
|
|||||||
<string name="bridge_enter_amount">Masukkan jumlah</string>
|
<string name="bridge_enter_amount">Masukkan jumlah</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Penukaran HEZ→DOT mungkin terbatas tergantung pada likuiditas.</string>
|
<string name="bridge_hez_to_dot_warning">Penukaran HEZ→DOT mungkin terbatas tergantung pada likuiditas.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Penukaran HEZ→DOT sementara tidak tersedia. Coba lagi saat likuiditas DOT mencukupi.</string>
|
<string name="bridge_hez_to_dot_blocked">Penukaran HEZ→DOT sementara tidak tersedia. Coba lagi saat likuiditas DOT mencukupi.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Skor Kepercayaan</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Referral</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Ajukan & Tindakan</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2044,4 +2044,11 @@
|
|||||||
<string name="bridge_enter_amount">Inserisci importo</string>
|
<string name="bridge_enter_amount">Inserisci importo</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Gli scambi HEZ→DOT potrebbero avere disponibilità limitata in base alla liquidità.</string>
|
<string name="bridge_hez_to_dot_warning">Gli scambi HEZ→DOT potrebbero avere disponibilità limitata in base alla liquidità.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Gli scambi HEZ→DOT sono temporaneamente non disponibili. Riprova quando la liquidità DOT sarà sufficiente.</string>
|
<string name="bridge_hez_to_dot_blocked">Gli scambi HEZ→DOT sono temporaneamente non disponibili. Riprova quando la liquidità DOT sarà sufficiente.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Punteggio di fiducia</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Referral</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Richiesta e Azioni</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2030,4 +2030,11 @@
|
|||||||
<string name="bridge_enter_amount">金額を入力</string>
|
<string name="bridge_enter_amount">金額を入力</string>
|
||||||
<string name="bridge_hez_to_dot_warning">HEZ→DOT交換は流動性により制限される場合があります。</string>
|
<string name="bridge_hez_to_dot_warning">HEZ→DOT交換は流動性により制限される場合があります。</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">HEZ→DOT交換は一時的に利用できません。DOTの流動性が十分になったら再試行してください。</string>
|
<string name="bridge_hez_to_dot_blocked">HEZ→DOT交換は一時的に利用できません。DOTの流動性が十分になったら再試行してください。</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">信頼スコア</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">紹介</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">ステーキング</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">申請とアクション</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2030,4 +2030,11 @@
|
|||||||
<string name="bridge_enter_amount">금액 입력</string>
|
<string name="bridge_enter_amount">금액 입력</string>
|
||||||
<string name="bridge_hez_to_dot_warning">HEZ→DOT 교환은 유동성에 따라 제한될 수 있습니다.</string>
|
<string name="bridge_hez_to_dot_warning">HEZ→DOT 교환은 유동성에 따라 제한될 수 있습니다.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">HEZ→DOT 교환은 일시적으로 사용할 수 없습니다. DOT 유동성이 충분해지면 다시 시도하세요.</string>
|
<string name="bridge_hez_to_dot_blocked">HEZ→DOT 교환은 일시적으로 사용할 수 없습니다. DOT 유동성이 충분해지면 다시 시도하세요.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">신뢰 점수</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">추천</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">스테이킹</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">신청 및 작업</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2757,4 +2757,11 @@
|
|||||||
<string name="bridge_enter_amount">Mîqdar binivîse</string>
|
<string name="bridge_enter_amount">Mîqdar binivîse</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Guherandina HEZ→DOT li gorî rewşa DOT sînordar dibe.</string>
|
<string name="bridge_hez_to_dot_warning">Guherandina HEZ→DOT li gorî rewşa DOT sînordar dibe.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Guherandina HEZ→DOT niha tune. Dema DOT têr bibe dîsa biceribîne.</string>
|
<string name="bridge_hez_to_dot_blocked">Guherandina HEZ→DOT niha tune. Dema DOT têr bibe dîsa biceribîne.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Pûana Pêbaweriyê</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Referral</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Serlêdan û Karên</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2072,4 +2072,11 @@
|
|||||||
<string name="bridge_enter_amount">Wprowadź kwotę</string>
|
<string name="bridge_enter_amount">Wprowadź kwotę</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Wymiany HEZ→DOT mogą mieć ograniczoną dostępność w zależności od płynności.</string>
|
<string name="bridge_hez_to_dot_warning">Wymiany HEZ→DOT mogą mieć ograniczoną dostępność w zależności od płynności.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Wymiany HEZ→DOT są tymczasowo niedostępne. Spróbuj ponownie, gdy płynność DOT będzie wystarczająca.</string>
|
<string name="bridge_hez_to_dot_blocked">Wymiany HEZ→DOT są tymczasowo niedostępne. Spróbuj ponownie, gdy płynność DOT będzie wystarczająca.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Wynik zaufania</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Polecenie</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Wniosek i Akcje</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2044,4 +2044,11 @@
|
|||||||
<string name="bridge_enter_amount">Digite o valor</string>
|
<string name="bridge_enter_amount">Digite o valor</string>
|
||||||
<string name="bridge_hez_to_dot_warning">As trocas HEZ→DOT podem ter disponibilidade limitada dependendo da liquidez.</string>
|
<string name="bridge_hez_to_dot_warning">As trocas HEZ→DOT podem ter disponibilidade limitada dependendo da liquidez.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">As trocas HEZ→DOT estão temporariamente indisponíveis. Tente novamente quando houver liquidez suficiente de DOT.</string>
|
<string name="bridge_hez_to_dot_blocked">As trocas HEZ→DOT estão temporariamente indisponíveis. Tente novamente quando houver liquidez suficiente de DOT.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Pontuação de confiança</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Indicação</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Candidatura e Ações</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2072,4 +2072,11 @@
|
|||||||
<string name="bridge_enter_amount">Введите сумму</string>
|
<string name="bridge_enter_amount">Введите сумму</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Обмен HEZ→DOT может быть ограничен в зависимости от ликвидности.</string>
|
<string name="bridge_hez_to_dot_warning">Обмен HEZ→DOT может быть ограничен в зависимости от ликвидности.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Обмен HEZ→DOT временно недоступен. Повторите попытку при достаточной ликвидности DOT.</string>
|
<string name="bridge_hez_to_dot_blocked">Обмен HEZ→DOT временно недоступен. Повторите попытку при достаточной ликвидности DOT.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Рейтинг доверия</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Реферал</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Стейкинг</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Заявка и Действия</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Güven Puanı</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Referans</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Başvuru ve İşlemler</string>
|
||||||
|
</resources>
|
||||||
@@ -2030,4 +2030,11 @@
|
|||||||
<string name="bridge_enter_amount">Nhập số tiền</string>
|
<string name="bridge_enter_amount">Nhập số tiền</string>
|
||||||
<string name="bridge_hez_to_dot_warning">Giao dịch HEZ→DOT có thể bị hạn chế tùy thuộc vào thanh khoản.</string>
|
<string name="bridge_hez_to_dot_warning">Giao dịch HEZ→DOT có thể bị hạn chế tùy thuộc vào thanh khoản.</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">Giao dịch HEZ→DOT tạm thời không khả dụng. Vui lòng thử lại khi thanh khoản DOT đủ.</string>
|
<string name="bridge_hez_to_dot_blocked">Giao dịch HEZ→DOT tạm thời không khả dụng. Vui lòng thử lại khi thanh khoản DOT đủ.</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Điểm tin cậy</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Giới thiệu</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Đăng ký & Hành động</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2030,4 +2030,11 @@
|
|||||||
<string name="bridge_enter_amount">输入金额</string>
|
<string name="bridge_enter_amount">输入金额</string>
|
||||||
<string name="bridge_hez_to_dot_warning">HEZ→DOT兑换可能因流动性而受限。</string>
|
<string name="bridge_hez_to_dot_warning">HEZ→DOT兑换可能因流动性而受限。</string>
|
||||||
<string name="bridge_hez_to_dot_blocked">HEZ→DOT兑换暂时不可用。请在DOT流动性充足时重试。</string>
|
<string name="bridge_hez_to_dot_blocked">HEZ→DOT兑换暂时不可用。请在DOT流动性充足时重试。</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">信任评分</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">推荐</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">质押</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">申请与操作</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -2759,4 +2759,11 @@
|
|||||||
<string name="yesterday">Yesterday</string>
|
<string name="yesterday">Yesterday</string>
|
||||||
<string name="branch_io_link_host" translatable="false">pezkuwi-wallet.app.link</string>
|
<string name="branch_io_link_host" translatable="false">pezkuwi-wallet.app.link</string>
|
||||||
<string name="branch_io_link_host_alternate" translatable="false">pezkuwi-wallet-alternate.app.link</string>
|
<string name="branch_io_link_host_alternate" translatable="false">pezkuwi-wallet-alternate.app.link</string>
|
||||||
|
|
||||||
|
<string name="pezkuwi_dashboard_title">Pezkuwi</string>
|
||||||
|
<string name="pezkuwi_dashboard_trust_score">Trust Score</string>
|
||||||
|
<string name="pezkuwi_dashboard_referral">Referral</string>
|
||||||
|
<string name="pezkuwi_dashboard_staking">Staking</string>
|
||||||
|
<string name="pezkuwi_dashboard_perwerde">Perwerde</string>
|
||||||
|
<string name="pezkuwi_dashboard_basvuru">Apply & Actions</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+11
@@ -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
|
||||||
|
)
|
||||||
+119
@@ -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)
|
||||||
|
}
|
||||||
+21
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
+27
-2
@@ -7,6 +7,7 @@ import coil.ImageLoader
|
|||||||
import io.novafoundation.nova.common.base.BaseFragment
|
import io.novafoundation.nova.common.base.BaseFragment
|
||||||
import io.novafoundation.nova.common.di.FeatureUtils
|
import io.novafoundation.nova.common.di.FeatureUtils
|
||||||
import io.novafoundation.nova.common.list.EditablePlaceholderAdapter
|
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.insets.applyStatusBarInsets
|
||||||
import io.novafoundation.nova.common.utils.hideKeyboard
|
import io.novafoundation.nova.common.utils.hideKeyboard
|
||||||
import io.novafoundation.nova.common.utils.recyclerView.expandable.ExpandableAnimationSettings
|
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.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 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.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
|
||||||
@@ -41,7 +44,8 @@ class BalanceListFragment :
|
|||||||
BaseFragment<BalanceListViewModel, FragmentBalanceListBinding>(),
|
BaseFragment<BalanceListViewModel, FragmentBalanceListBinding>(),
|
||||||
BalanceListAdapter.ItemAssetHandler,
|
BalanceListAdapter.ItemAssetHandler,
|
||||||
AssetsHeaderAdapter.Handler,
|
AssetsHeaderAdapter.Handler,
|
||||||
ManageAssetsAdapter.Handler {
|
ManageAssetsAdapter.Handler,
|
||||||
|
PezkuwiDashboardAdapter.Handler {
|
||||||
|
|
||||||
override fun createBinding() = FragmentBalanceListBinding.inflate(layoutInflater)
|
override fun createBinding() = FragmentBalanceListBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
@@ -54,6 +58,10 @@ class BalanceListFragment :
|
|||||||
AssetsHeaderAdapter(this)
|
AssetsHeaderAdapter(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val pezkuwiDashboardAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
|
PezkuwiDashboardAdapter(this)
|
||||||
|
}
|
||||||
|
|
||||||
private val bannerAdapter: PromotionBannerAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
private val bannerAdapter: PromotionBannerAdapter by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
PromotionBannerAdapter(closable = true)
|
PromotionBannerAdapter(closable = true)
|
||||||
}
|
}
|
||||||
@@ -74,7 +82,7 @@ class BalanceListFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
|
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) {
|
override fun applyInsets(rootView: View) {
|
||||||
@@ -111,6 +119,16 @@ class BalanceListFragment :
|
|||||||
|
|
||||||
override fun subscribe(viewModel: BalanceListViewModel) {
|
override fun subscribe(viewModel: BalanceListViewModel) {
|
||||||
setupBuySellSelectorMixin(viewModel.buySellSelectorMixin)
|
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) {
|
viewModel.bannersMixin.bindWithAdapter(bannerAdapter) {
|
||||||
binder.balanceListAssets.invalidateItemDecorations()
|
binder.balanceListAssets.invalidateItemDecorations()
|
||||||
@@ -234,8 +252,15 @@ class BalanceListFragment :
|
|||||||
viewModel.giftClicked()
|
viewModel.giftClicked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onBasvuruClicked() {
|
||||||
|
viewModel.basvuruClicked()
|
||||||
|
}
|
||||||
|
|
||||||
private fun setupRecyclerViewSpacing() {
|
private fun setupRecyclerViewSpacing() {
|
||||||
binder.balanceListAssets.addSpaceItemDecoration {
|
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(AssetsHeaderHolder, BannerHolder, spaceDp = 4))
|
||||||
add(SpaceBetween(BannerHolder, ManageAssetsHolder, spaceDp = 4))
|
add(SpaceBetween(BannerHolder, ManageAssetsHolder, spaceDp = 4))
|
||||||
add(SpaceBetween(AssetsHeaderHolder, ManageAssetsHolder, spaceDp = 24))
|
add(SpaceBetween(AssetsHeaderHolder, ManageAssetsHolder, spaceDp = 24))
|
||||||
|
|||||||
+26
-2
@@ -14,6 +14,7 @@ import io.novafoundation.nova.common.presentation.LoadingState
|
|||||||
import io.novafoundation.nova.common.presentation.masking.MaskableModel
|
import io.novafoundation.nova.common.presentation.masking.MaskableModel
|
||||||
import io.novafoundation.nova.common.resources.ResourceManager
|
import io.novafoundation.nova.common.resources.ResourceManager
|
||||||
import io.novafoundation.nova.common.utils.Event
|
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.format
|
||||||
import io.novafoundation.nova.common.utils.formatting.formatAsPercentage
|
import io.novafoundation.nova.common.utils.formatting.formatAsPercentage
|
||||||
import io.novafoundation.nova.common.utils.inBackground
|
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.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.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.assets.list.NftPreviews
|
||||||
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdown
|
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdown
|
||||||
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdownInteractor
|
import io.novafoundation.nova.feature_assets.domain.breakdown.BalanceBreakdownInteractor
|
||||||
@@ -97,8 +100,9 @@ class BalanceListViewModel(
|
|||||||
private val multisigPendingOperationsService: MultisigPendingOperationsService,
|
private val multisigPendingOperationsService: MultisigPendingOperationsService,
|
||||||
private val novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
|
private val novaCardRestrictionCheckMixin: NovaCardRestrictionCheckMixin,
|
||||||
private val maskingModeUseCase: MaskingModeUseCase,
|
private val maskingModeUseCase: MaskingModeUseCase,
|
||||||
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin
|
private val giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
||||||
) : BaseViewModel() {
|
private val pezkuwiDashboardInteractor: PezkuwiDashboardInteractor
|
||||||
|
) : BaseViewModel(), Browserable.Presentation by Browserable() {
|
||||||
|
|
||||||
private val maskableAmountFormatterFlow = maskableValueFormatterProvider.provideFormatter()
|
private val maskableAmountFormatterFlow = maskableValueFormatterProvider.provideFormatter()
|
||||||
.shareInBackground()
|
.shareInBackground()
|
||||||
@@ -218,6 +222,22 @@ class BalanceListViewModel(
|
|||||||
.combine(maskableAmountFormatterFlow, ::formatPendingOperationsCount)
|
.combine(maskableAmountFormatterFlow, ::formatPendingOperationsCount)
|
||||||
.shareInBackground()
|
.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 {
|
init {
|
||||||
selectedCurrency
|
selectedCurrency
|
||||||
.onEach { fullSync() }
|
.onEach { fullSync() }
|
||||||
@@ -381,6 +401,10 @@ class BalanceListViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun basvuruClicked() {
|
||||||
|
showBrowser("https://t.me/pezkuwichainBot")
|
||||||
|
}
|
||||||
|
|
||||||
fun novaCardClicked() = launchUnit {
|
fun novaCardClicked() = launchUnit {
|
||||||
novaCardRestrictionCheckMixin.checkRestrictionAndDo {
|
novaCardRestrictionCheckMixin.checkRestrictionAndDo {
|
||||||
router.openNovaCard()
|
router.openNovaCard()
|
||||||
|
|||||||
+26
-1
@@ -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.data.multisig.MultisigPendingOperationsService
|
||||||
import io.novafoundation.nova.feature_account_api.domain.interfaces.AccountRepository
|
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_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.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.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.AssetsRouter
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.common.AssetListMixinFactory
|
import io.novafoundation.nova.feature_assets.presentation.balance.common.AssetListMixinFactory
|
||||||
import io.novafoundation.nova.feature_assets.presentation.balance.common.ExpandableAssetsMixinFactory
|
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
|
@Provides
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ViewModelKey(BalanceListViewModel::class)
|
@ViewModelKey(BalanceListViewModel::class)
|
||||||
@@ -103,6 +126,7 @@ class BalanceListModule {
|
|||||||
maskingModeUseCase: MaskingModeUseCase,
|
maskingModeUseCase: MaskingModeUseCase,
|
||||||
fiatFormatter: FiatFormatter,
|
fiatFormatter: FiatFormatter,
|
||||||
giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
giftsRestrictionCheckMixin: GiftsRestrictionCheckMixin,
|
||||||
|
pezkuwiDashboardInteractor: PezkuwiDashboardInteractor,
|
||||||
): ViewModel {
|
): ViewModel {
|
||||||
return BalanceListViewModel(
|
return BalanceListViewModel(
|
||||||
promotionBannersMixinFactory = promotionBannersMixinFactory,
|
promotionBannersMixinFactory = promotionBannersMixinFactory,
|
||||||
@@ -124,7 +148,8 @@ class BalanceListModule {
|
|||||||
novaCardRestrictionCheckMixin = novaCardRestrictionCheckMixin,
|
novaCardRestrictionCheckMixin = novaCardRestrictionCheckMixin,
|
||||||
maskingModeUseCase = maskingModeUseCase,
|
maskingModeUseCase = maskingModeUseCase,
|
||||||
fiatFormatter = fiatFormatter,
|
fiatFormatter = fiatFormatter,
|
||||||
giftsRestrictionCheckMixin = giftsRestrictionCheckMixin
|
giftsRestrictionCheckMixin = giftsRestrictionCheckMixin,
|
||||||
|
pezkuwiDashboardInteractor = pezkuwiDashboardInteractor
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+9
@@ -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
|
||||||
|
)
|
||||||
+88
@@ -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>
|
||||||
+2
-1
@@ -38,6 +38,7 @@ suspend fun BagListRepository.bagListLocatorOrThrow(chainId: ChainId): BagListLo
|
|||||||
|
|
||||||
class LocalBagListRepository(
|
class LocalBagListRepository(
|
||||||
private val localStorage: StorageDataSource,
|
private val localStorage: StorageDataSource,
|
||||||
|
private val remoteStorage: StorageDataSource,
|
||||||
private val chainRegistry: ChainRegistry
|
private val chainRegistry: ChainRegistry
|
||||||
) : BagListRepository {
|
) : BagListRepository {
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ class LocalBagListRepository(
|
|||||||
|
|
||||||
override suspend fun bagListSize(chainId: ChainId): BigInteger? {
|
override suspend fun bagListSize(chainId: ChainId): BigInteger? {
|
||||||
return runCatching {
|
return runCatching {
|
||||||
localStorage.query(chainId) {
|
remoteStorage.query(chainId) {
|
||||||
runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber)
|
runtime.metadata.voterListOrNull()?.storage("CounterForListNodes")?.query(binding = ::bindNumber)
|
||||||
}
|
}
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
|
|||||||
+1
-1
@@ -346,7 +346,7 @@ class StakingRepositoryImpl(
|
|||||||
val runtime = runtimeFor(chainId)
|
val runtime = runtimeFor(chainId)
|
||||||
|
|
||||||
return runtime.metadata.staking().storageOrNull(storageName)?.let { storageEntry ->
|
return runtime.metadata.staking().storageOrNull(storageName)?.let { storageEntry ->
|
||||||
localStorage.query(
|
remoteStorage.query(
|
||||||
keyBuilder = { storageEntry.storageKey() },
|
keyBuilder = { storageEntry.storageKey() },
|
||||||
binding = { scale, _ -> scale?.let { binder(scale, runtime, storageEntry.returnType()) } },
|
binding = { scale, _ -> scale?.let { binder(scale, runtime, storageEntry.returnType()) } },
|
||||||
chainId = chainId
|
chainId = chainId
|
||||||
|
|||||||
+2
-1
@@ -225,8 +225,9 @@ class StakingFeatureModule {
|
|||||||
@FeatureScope
|
@FeatureScope
|
||||||
fun provideBagListRepository(
|
fun provideBagListRepository(
|
||||||
@Named(LOCAL_STORAGE_SOURCE) localStorageSource: StorageDataSource,
|
@Named(LOCAL_STORAGE_SOURCE) localStorageSource: StorageDataSource,
|
||||||
|
@Named(REMOTE_STORAGE_SOURCE) remoteStorageSource: StorageDataSource,
|
||||||
chainRegistry: ChainRegistry
|
chainRegistry: ChainRegistry
|
||||||
): BagListRepository = LocalBagListRepository(localStorageSource, chainRegistry)
|
): BagListRepository = LocalBagListRepository(localStorageSource, remoteStorageSource, chainRegistry)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@FeatureScope
|
@FeatureScope
|
||||||
|
|||||||
+45
-18
@@ -22,10 +22,12 @@ import io.novafoundation.nova.runtime.multiNetwork.ChainWithAsset
|
|||||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
import io.novafoundation.nova.runtime.multiNetwork.chain.model.ChainId
|
||||||
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
|
import io.novafoundation.nova.runtime.repository.TotalIssuanceRepository
|
||||||
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.emitAll
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
|
|
||||||
@@ -61,7 +63,15 @@ class StakingSharedComputation(
|
|||||||
val key = "ACTIVE_ERA:$chainId"
|
val key = "ACTIVE_ERA:$chainId"
|
||||||
|
|
||||||
return computationalCache.useSharedFlow(key, scope) {
|
return computationalCache.useSharedFlow(key, scope) {
|
||||||
stakingRepository.observeActiveEraIndex(chainId)
|
flow {
|
||||||
|
Log.d("PEZ_STAKE", "activeEraFlow: fetching remote activeEra for chainId=$chainId")
|
||||||
|
val era = stakingRepository.getActiveEraIndex(chainId)
|
||||||
|
Log.d("PEZ_STAKE", "activeEraFlow: got remote activeEra=$era")
|
||||||
|
emit(era)
|
||||||
|
|
||||||
|
Log.d("PEZ_STAKE", "activeEraFlow: starting local observation for chainId=$chainId")
|
||||||
|
emitAll(stakingRepository.observeActiveEraIndex(chainId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,7 +80,15 @@ class StakingSharedComputation(
|
|||||||
|
|
||||||
return computationalCache.useSharedFlow(key, scope) {
|
return computationalCache.useSharedFlow(key, scope) {
|
||||||
activeEraFlow(chainId, scope).map { eraIndex ->
|
activeEraFlow(chainId, scope).map { eraIndex ->
|
||||||
stakingRepository.getElectedValidatorsExposure(chainId, eraIndex) to eraIndex
|
Log.d("PEZ_STAKE", "electedExposures: fetching validators for chainId=$chainId, era=$eraIndex")
|
||||||
|
try {
|
||||||
|
val exposures = stakingRepository.getElectedValidatorsExposure(chainId, eraIndex)
|
||||||
|
Log.d("PEZ_STAKE", "electedExposures: got ${exposures.size} validators for chainId=$chainId")
|
||||||
|
exposures to eraIndex
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PEZ_STAKE", "electedExposures: FAILED for chainId=$chainId, era=$eraIndex", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,24 +97,33 @@ class StakingSharedComputation(
|
|||||||
val key = "MIN_STAKE:$chainId"
|
val key = "MIN_STAKE:$chainId"
|
||||||
|
|
||||||
return computationalCache.useSharedFlow(key, scope) {
|
return computationalCache.useSharedFlow(key, scope) {
|
||||||
val minBond = stakingRepository.minimumNominatorBond(chainId)
|
|
||||||
val bagListLocator = bagListRepository.bagListLocatorOrNull(chainId)
|
|
||||||
val totalIssuance = totalIssuanceRepository.getTotalIssuance(chainId)
|
|
||||||
val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance)
|
|
||||||
val maxElectingVoters = bagListRepository.maxElectingVotes(chainId)
|
|
||||||
val bagListSize = bagListRepository.bagListSize(chainId)
|
|
||||||
|
|
||||||
electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) ->
|
electedExposuresWithActiveEraFlow(chainId, scope).map { (exposures, activeEraIndex) ->
|
||||||
val minStake = minimumStake(
|
Log.d("PEZ_STAKE", "activeEraInfo: calculating minStake for chainId=$chainId, era=$activeEraIndex, validators=${exposures.size}")
|
||||||
exposures = exposures.values,
|
try {
|
||||||
minimumNominatorBond = minBond,
|
val minBond = stakingRepository.minimumNominatorBond(chainId)
|
||||||
bagListLocator = bagListLocator,
|
Log.d("PEZ_STAKE", "activeEraInfo: minBond=$minBond")
|
||||||
bagListScoreConverter = bagListScoreConverter,
|
val bagListLocator = bagListRepository.bagListLocatorOrNull(chainId)
|
||||||
bagListSize = bagListSize,
|
val totalIssuance = totalIssuanceRepository.getTotalIssuance(chainId)
|
||||||
maxElectingVoters = maxElectingVoters
|
val bagListScoreConverter = BagListScoreConverter.U128(totalIssuance)
|
||||||
)
|
val maxElectingVoters = bagListRepository.maxElectingVotes(chainId)
|
||||||
|
val bagListSize = bagListRepository.bagListSize(chainId)
|
||||||
|
Log.d("PEZ_STAKE", "activeEraInfo: bagListSize=$bagListSize, maxElectingVoters=$maxElectingVoters")
|
||||||
|
|
||||||
ActiveEraInfo(activeEraIndex, exposures, minStake)
|
val minStake = minimumStake(
|
||||||
|
exposures = exposures.values,
|
||||||
|
minimumNominatorBond = minBond,
|
||||||
|
bagListLocator = bagListLocator,
|
||||||
|
bagListScoreConverter = bagListScoreConverter,
|
||||||
|
bagListSize = bagListSize,
|
||||||
|
maxElectingVoters = maxElectingVoters
|
||||||
|
)
|
||||||
|
Log.d("PEZ_STAKE", "activeEraInfo: minStake=$minStake")
|
||||||
|
|
||||||
|
ActiveEraInfo(activeEraIndex, exposures, minStake)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PEZ_STAKE", "activeEraInfo: FAILED for chainId=$chainId", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
-4
@@ -33,20 +33,16 @@ class NominationPoolRewardCalculatorFactory(
|
|||||||
// For parachains, staking exposures live on the parent relay chain
|
// For parachains, staking exposures live on the parent relay chain
|
||||||
val exposureChainId = chain.parentId ?: chainId
|
val exposureChainId = chain.parentId ?: chainId
|
||||||
|
|
||||||
android.util.Log.d("PEZ_STAKING", "NomPoolRewardCalcFactory.create() chainId=${chainId.take(12)} exposureChainId=${exposureChainId.take(12)}")
|
|
||||||
|
|
||||||
val delegateOption = stakingOption.unwrapNominationPools()
|
val delegateOption = stakingOption.unwrapNominationPools()
|
||||||
|
|
||||||
val delegate = sharedStakingSharedComputation.rewardCalculator(delegateOption, sharedComputationScope)
|
val delegate = sharedStakingSharedComputation.rewardCalculator(delegateOption, sharedComputationScope)
|
||||||
val allPoolAccounts = nominationPoolSharedComputation.allBondedPoolAccounts(chainId, sharedComputationScope)
|
val allPoolAccounts = nominationPoolSharedComputation.allBondedPoolAccounts(chainId, sharedComputationScope)
|
||||||
android.util.Log.d("PEZ_STAKING", "Pool accounts: ${allPoolAccounts.size}")
|
|
||||||
|
|
||||||
val poolCommissions = nominationPoolSharedComputation.allBondedPools(chainId, sharedComputationScope)
|
val poolCommissions = nominationPoolSharedComputation.allBondedPools(chainId, sharedComputationScope)
|
||||||
.mapValues { (_, pool) -> pool.commission?.current?.perbill }
|
.mapValues { (_, pool) -> pool.commission?.current?.perbill }
|
||||||
|
|
||||||
val activeEra = stakingRepository.getActiveEraIndex(exposureChainId)
|
val activeEra = stakingRepository.getActiveEraIndex(exposureChainId)
|
||||||
val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra)
|
val exposures = stakingRepository.getElectedValidatorsExposure(exposureChainId, activeEra)
|
||||||
android.util.Log.d("PEZ_STAKING", "NomPool exposures: ${exposures.size} (era=$activeEra)")
|
|
||||||
|
|
||||||
return RealNominationPoolRewardCalculator(
|
return RealNominationPoolRewardCalculator(
|
||||||
directStakingDelegate = delegate,
|
directStakingDelegate = delegate,
|
||||||
|
|||||||
+4
-2
@@ -26,7 +26,8 @@ class RealStartMultiStakingInteractor(
|
|||||||
|
|
||||||
override suspend fun calculateFee(selection: StartMultiStakingSelection): Fee {
|
override suspend fun calculateFee(selection: StartMultiStakingSelection): Fee {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
extrinsicService.estimateFee(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) {
|
val chain = selection.stakingOption.chain
|
||||||
|
extrinsicService.estimateFee(chain, TransactionOrigin.SelectedWallet) {
|
||||||
startStaking(selection)
|
startStaking(selection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +35,8 @@ class RealStartMultiStakingInteractor(
|
|||||||
|
|
||||||
override suspend fun startStaking(selection: StartMultiStakingSelection): Result<ExtrinsicExecutionResult> {
|
override suspend fun startStaking(selection: StartMultiStakingSelection): Result<ExtrinsicExecutionResult> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
extrinsicService.submitExtrinsicAndAwaitExecution(selection.stakingOption.chain, TransactionOrigin.SelectedWallet) {
|
val chain = selection.stakingOption.chain
|
||||||
|
extrinsicService.submitExtrinsicAndAwaitExecution(chain, TransactionOrigin.SelectedWallet) {
|
||||||
startStaking(selection)
|
startStaking(selection)
|
||||||
}.requireOk()
|
}.requireOk()
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-1
@@ -29,6 +29,7 @@ import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Ba
|
|||||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
||||||
import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance
|
import io.novafoundation.nova.feature_wallet_api.domain.validation.sufficientBalance
|
||||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||||
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
|
||||||
class DirectStakingPropertiesFactory(
|
class DirectStakingPropertiesFactory(
|
||||||
@@ -101,7 +102,10 @@ private class DirectStakingProperties(
|
|||||||
private val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
|
private val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
|
||||||
|
|
||||||
override suspend fun minStake(): Balance {
|
override suspend fun minStake(): Balance {
|
||||||
return stakingSharedComputation.minStake(stakingChainId, scope)
|
Log.d("PEZ_STAKE", "DirectStaking.minStake() called, stakingChainId=$stakingChainId")
|
||||||
|
val result = stakingSharedComputation.minStake(stakingChainId, scope)
|
||||||
|
Log.d("PEZ_STAKE", "DirectStaking.minStake() returned: $result")
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun StartMultiStakingValidationSystemBuilder.noConflictingStaking() {
|
private fun StartMultiStakingValidationSystemBuilder.noConflictingStaking() {
|
||||||
|
|||||||
+24
-2
@@ -8,6 +8,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.recommendations.settin
|
|||||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection
|
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection
|
||||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.SingleStakingRecommendation
|
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.SingleStakingRecommendation
|
||||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||||
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
|
||||||
@@ -20,21 +21,42 @@ class DirectStakingRecommendation(
|
|||||||
) : SingleStakingRecommendation {
|
) : SingleStakingRecommendation {
|
||||||
|
|
||||||
private val recommendator = scope.async {
|
private val recommendator = scope.async {
|
||||||
validatorRecommenderFactory.create(scope)
|
Log.d("PEZ_STAKE", "DirectRecommendation: creating validator recommender...")
|
||||||
|
try {
|
||||||
|
val result = validatorRecommenderFactory.create(scope)
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: validator recommender created")
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PEZ_STAKE", "DirectRecommendation: validator recommender FAILED", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val recommendationSettingsProvider = scope.async {
|
private val recommendationSettingsProvider = scope.async {
|
||||||
recommendationSettingsProviderFactory.create(scope)
|
Log.d("PEZ_STAKE", "DirectRecommendation: creating settings provider...")
|
||||||
|
try {
|
||||||
|
val result = recommendationSettingsProviderFactory.create(scope)
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: settings provider created")
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PEZ_STAKE", "DirectRecommendation: settings provider FAILED", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection {
|
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection {
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: awaiting settings provider...")
|
||||||
val provider = recommendationSettingsProvider.await()
|
val provider = recommendationSettingsProvider.await()
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: got settings provider")
|
||||||
val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
|
val stakingChainId = stakingOption.chain.parentId ?: stakingOption.chain.id
|
||||||
val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingChainId, stake)
|
val maximumValidatorsPerNominator = stakingConstantsRepository.maxValidatorsPerNominator(stakingChainId, stake)
|
||||||
val recommendationSettings = provider.recommendedSettings(maximumValidatorsPerNominator)
|
val recommendationSettings = provider.recommendedSettings(maximumValidatorsPerNominator)
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: awaiting recommender...")
|
||||||
val recommendator = recommendator.await()
|
val recommendator = recommendator.await()
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: got recommender, getting recommendations...")
|
||||||
|
|
||||||
val recommendedValidators = recommendator.recommendations(recommendationSettings)
|
val recommendedValidators = recommendator.recommendations(recommendationSettings)
|
||||||
|
Log.d("PEZ_STAKE", "DirectRecommendation: got ${recommendedValidators.size} recommended validators")
|
||||||
|
|
||||||
return DirectStakingSelection(
|
return DirectStakingSelection(
|
||||||
validators = recommendedValidators,
|
validators = recommendedValidators,
|
||||||
|
|||||||
+16
-2
@@ -5,6 +5,7 @@ import io.novafoundation.nova.feature_staking_impl.domain.nominationPools.pools.
|
|||||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection
|
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.common.selection.StartMultiStakingSelection
|
||||||
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.SingleStakingRecommendation
|
import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmount.SingleStakingRecommendation
|
||||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||||
|
import android.util.Log
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
|
|
||||||
@@ -15,11 +16,24 @@ class NominationPoolRecommendation(
|
|||||||
) : SingleStakingRecommendation {
|
) : SingleStakingRecommendation {
|
||||||
|
|
||||||
private val recommendator = scope.async {
|
private val recommendator = scope.async {
|
||||||
nominationPoolRecommenderFactory.create(stakingOption, scope)
|
Log.d("PEZ_STAKE", "NomPoolRecommendation: creating recommender...")
|
||||||
|
try {
|
||||||
|
val result = nominationPoolRecommenderFactory.create(stakingOption, scope)
|
||||||
|
Log.d("PEZ_STAKE", "NomPoolRecommendation: recommender created successfully")
|
||||||
|
result
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PEZ_STAKE", "NomPoolRecommendation: recommender creation FAILED", e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection? {
|
override suspend fun recommendedSelection(stake: Balance): StartMultiStakingSelection? {
|
||||||
val recommendedPool = recommendator.await().recommendedPool() ?: return null
|
Log.d("PEZ_STAKE", "NomPoolRecommendation: awaiting recommender...")
|
||||||
|
val recommendedPool = recommendator.await().recommendedPool() ?: run {
|
||||||
|
Log.d("PEZ_STAKE", "NomPoolRecommendation: no recommended pool found")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
Log.d("PEZ_STAKE", "NomPoolRecommendation: recommended pool=${recommendedPool.id}")
|
||||||
|
|
||||||
return NominationPoolSelection(recommendedPool, stakingOption, stake)
|
return NominationPoolSelection(recommendedPool, stakingOption, stake)
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-2
@@ -13,6 +13,8 @@ import io.novafoundation.nova.feature_staking_impl.domain.staking.start.setupAmo
|
|||||||
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
import io.novafoundation.nova.feature_wallet_api.data.network.blockhain.types.Balance
|
||||||
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
|
import io.novafoundation.nova.feature_wallet_api.data.repository.BalanceLocksRepository
|
||||||
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
import io.novafoundation.nova.feature_wallet_api.domain.model.Asset
|
||||||
|
import android.util.Log
|
||||||
|
import kotlin.coroutines.cancellation.CancellationException
|
||||||
|
|
||||||
class AutomaticMultiStakingSelectionType(
|
class AutomaticMultiStakingSelectionType(
|
||||||
private val candidates: List<SingleStakingProperties>,
|
private val candidates: List<SingleStakingProperties>,
|
||||||
@@ -44,8 +46,14 @@ class AutomaticMultiStakingSelectionType(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateSelectionFor(stake: Balance) {
|
override suspend fun updateSelectionFor(stake: Balance) {
|
||||||
|
Log.d("PEZ_STAKE", "updateSelectionFor: stake=$stake")
|
||||||
val stakingProperties = typePropertiesFor(stake)
|
val stakingProperties = typePropertiesFor(stake)
|
||||||
val candidates = stakingProperties.recommendation.recommendedSelection(stake) ?: return
|
Log.d("PEZ_STAKE", "updateSelectionFor: got properties type=${stakingProperties.stakingType}")
|
||||||
|
val candidates = stakingProperties.recommendation.recommendedSelection(stake) ?: run {
|
||||||
|
Log.d("PEZ_STAKE", "updateSelectionFor: recommendedSelection returned null, returning")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log.d("PEZ_STAKE", "updateSelectionFor: got recommended selection")
|
||||||
|
|
||||||
val recommendableSelection = RecommendableMultiStakingSelection(
|
val recommendableSelection = RecommendableMultiStakingSelection(
|
||||||
source = SelectionTypeSource.Automatic,
|
source = SelectionTypeSource.Automatic,
|
||||||
@@ -54,10 +62,26 @@ class AutomaticMultiStakingSelectionType(
|
|||||||
)
|
)
|
||||||
|
|
||||||
selectionStore.updateSelection(recommendableSelection)
|
selectionStore.updateSelection(recommendableSelection)
|
||||||
|
Log.d("PEZ_STAKE", "updateSelectionFor: selection updated successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun typePropertiesFor(stake: Balance): SingleStakingProperties {
|
private suspend fun typePropertiesFor(stake: Balance): SingleStakingProperties {
|
||||||
return candidates.firstAllowingToStake(stake) ?: candidates.findWithMinimumStake()
|
Log.d("PEZ_STAKE", "typePropertiesFor: trying ${candidates.size} candidates")
|
||||||
|
for ((index, candidate) in candidates.withIndex()) {
|
||||||
|
Log.d("PEZ_STAKE", "typePropertiesFor: checking candidate $index type=${candidate.stakingType}")
|
||||||
|
try {
|
||||||
|
val minStake = candidate.minStake()
|
||||||
|
Log.d("PEZ_STAKE", "typePropertiesFor: candidate $index minStake=$minStake, stake=$stake, allows=${minStake <= stake}")
|
||||||
|
if (minStake <= stake) return candidate
|
||||||
|
} catch (e: CancellationException) {
|
||||||
|
Log.d("PEZ_STAKE", "typePropertiesFor: candidate $index cancelled, rethrowing")
|
||||||
|
throw e
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("PEZ_STAKE", "typePropertiesFor: candidate $index minStake() threw", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d("PEZ_STAKE", "typePropertiesFor: no candidate allows, finding minimum")
|
||||||
|
return candidates.findWithMinimumStake()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun List<SingleStakingProperties>.firstAllowingToStake(stake: Balance): SingleStakingProperties? {
|
private suspend fun List<SingleStakingProperties>.firstAllowingToStake(stake: Balance): SingleStakingProperties? {
|
||||||
|
|||||||
Reference in New Issue
Block a user