Skip to content

Commit c59d6ba

Browse files
committed
Validator details progress: panels and icons completed.
1 parent 764a698 commit c59d6ba

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+740
-26
lines changed

subvt/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ dependencies {
7878
implementation("androidx.compose.ui:ui")
7979
implementation("androidx.compose.ui:ui-graphics")
8080
implementation("androidx.compose.ui:ui-tooling-preview")
81-
implementation("androidx.compose.material3:material3:1.2.0-rc01")
81+
implementation("androidx.compose.material3:material3:1.2.0")
8282
implementation("androidx.navigation:navigation-compose:2.7.6")
8383
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
8484
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.7.0")

subvt/src/main/java/io/helikon/subvt/app/SubVTApp.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.helikon.subvt.app
22

33
import android.app.Application
4+
import com.google.android.filament.utils.Utils
45
import dagger.hilt.android.HiltAndroidApp
56
import io.helikon.subvt.BuildConfig
67
import timber.log.Timber
@@ -9,6 +10,7 @@ import timber.log.Timber
910
class SubVTApp : Application() {
1011
override fun onCreate() {
1112
super.onCreate()
13+
Utils.init()
1214
if (BuildConfig.DEBUG) {
1315
Timber.plant(Timber.DebugTree())
1416
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package io.helikon.subvt.data.extension
2+
3+
import android.content.Context
4+
import io.helikon.subvt.R
5+
import io.helikon.subvt.data.model.substrate.RewardDestination
6+
import io.helikon.subvt.data.model.substrate.RewardDestinationType
7+
import io.helikon.subvt.util.truncateAddress
8+
9+
fun RewardDestination.display(
10+
context: Context,
11+
prefix: Short,
12+
): String {
13+
return when (this.destinationType) {
14+
RewardDestinationType.ACCOUNT ->
15+
if (this.destination != null) {
16+
truncateAddress(this.destination!!.getAddress(prefix))
17+
} else {
18+
"-"
19+
}
20+
21+
RewardDestinationType.CONTROLLER -> context.resources.getString(R.string.reward_destination_controller)
22+
RewardDestinationType.NONE -> context.resources.getString(R.string.reward_destination_none)
23+
RewardDestinationType.STAKED -> context.resources.getString(R.string.reward_destination_staked)
24+
RewardDestinationType.STASH -> context.resources.getString(R.string.reward_destination_stash)
25+
}
26+
}

subvt/src/main/java/io/helikon/subvt/ui/screen/main/Tab.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ fun TabLayout(
107107
modifier =
108108
Modifier
109109
.fillMaxWidth()
110-
.height(104.dp)
110+
.height(dimensionResource(id = R.dimen.common_bottom_gradient_height))
111111
.zIndex(12f)
112112
.align(Alignment.BottomCenter)
113113
.background(

subvt/src/main/java/io/helikon/subvt/ui/screen/validator/details/ValidatorDetailsScreen.kt

Lines changed: 138 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package io.helikon.subvt.ui.screen.validator.details
22

3-
import androidx.compose.foundation.ExperimentalFoundationApi
43
import androidx.compose.foundation.Image
5-
import androidx.compose.foundation.LocalOverscrollConfiguration
64
import androidx.compose.foundation.background
75
import androidx.compose.foundation.isSystemInDarkTheme
86
import androidx.compose.foundation.layout.Arrangement
@@ -24,14 +22,13 @@ import androidx.compose.foundation.verticalScroll
2422
import androidx.compose.material3.Surface
2523
import androidx.compose.material3.Text
2624
import androidx.compose.runtime.Composable
27-
import androidx.compose.runtime.CompositionLocalProvider
2825
import androidx.compose.runtime.DisposableEffect
2926
import androidx.compose.runtime.getValue
3027
import androidx.compose.ui.Alignment
3128
import androidx.compose.ui.Modifier
32-
import androidx.compose.ui.draw.alpha
3329
import androidx.compose.ui.draw.clipToBounds
3430
import androidx.compose.ui.graphics.Brush
31+
import androidx.compose.ui.platform.LocalContext
3532
import androidx.compose.ui.platform.LocalLifecycleOwner
3633
import androidx.compose.ui.res.dimensionResource
3734
import androidx.compose.ui.res.painterResource
@@ -45,7 +42,9 @@ import androidx.lifecycle.LifecycleEventObserver
4542
import androidx.lifecycle.LifecycleOwner
4643
import androidx.lifecycle.compose.collectAsStateWithLifecycle
4744
import io.helikon.subvt.R
45+
import io.helikon.subvt.data.extension.display
4846
import io.helikon.subvt.data.extension.inactiveNominationTotal
47+
import io.helikon.subvt.data.extension.inactiveNominations
4948
import io.helikon.subvt.data.extension.nominationTotal
5049
import io.helikon.subvt.data.model.Network
5150
import io.helikon.subvt.data.model.app.ValidatorDetails
@@ -55,12 +54,19 @@ import io.helikon.subvt.ui.component.AnimatedBackground
5554
import io.helikon.subvt.ui.modifier.noRippleClickable
5655
import io.helikon.subvt.ui.modifier.scrollHeader
5756
import io.helikon.subvt.ui.screen.network.status.view.NetworkSelectorButton
57+
import io.helikon.subvt.ui.screen.validator.details.view.AccountAgeView
5858
import io.helikon.subvt.ui.screen.validator.details.view.BalanceView
59+
import io.helikon.subvt.ui.screen.validator.details.view.HorizontalDataView
60+
import io.helikon.subvt.ui.screen.validator.details.view.IconsView
5961
import io.helikon.subvt.ui.screen.validator.details.view.IdenticonView
6062
import io.helikon.subvt.ui.screen.validator.details.view.IdentityView
63+
import io.helikon.subvt.ui.screen.validator.details.view.NominatorListView
64+
import io.helikon.subvt.ui.screen.validator.details.view.OneKVDetailsView
65+
import io.helikon.subvt.ui.screen.validator.details.view.VerticalDataView
6166
import io.helikon.subvt.ui.style.Color
6267
import io.helikon.subvt.ui.style.Font
6368
import io.helikon.subvt.ui.util.ThemePreviews
69+
import io.helikon.subvt.util.formatDecimal
6470

6571
data class ValidatorDetailsScreenState(
6672
val serviceStatus: RPCSubscriptionServiceStatus,
@@ -107,7 +113,6 @@ fun ValidatorDetailsScreen(
107113
)
108114
}
109115

110-
@OptIn(ExperimentalFoundationApi::class)
111116
@Composable
112117
fun ValidatorDetailsScreenContent(
113118
modifier: Modifier = Modifier,
@@ -313,6 +318,7 @@ fun ValidatorDetailsScreenContent(
313318
.height(dimensionResource(id = R.dimen.validator_details_identicon_height)),
314319
)
315320
IdentityView(validator = state.validator)
321+
// Spacer(modifier = Modifier.height(dimensionResource(id = R.dimen.common_panel_padding) / 2))
316322
BalanceView(
317323
titleResourceId = R.string.validator_details_nomination_total,
318324
network = state.network,
@@ -323,28 +329,142 @@ fun ValidatorDetailsScreenContent(
323329
network = state.network,
324330
balance = state.validator?.selfStake?.activeAmount,
325331
)
326-
BalanceView(
327-
titleResourceId = R.string.validator_details_active_stake,
328-
network = state.network,
329-
balance = state.validator?.validatorStake?.totalStake,
330-
)
331-
BalanceView(
332+
state.validator?.let { validator ->
333+
validator.validatorStake?.let { validatorStake ->
334+
NominatorListView(
335+
titleResourceId = R.string.validator_details_active_stake,
336+
network = state.network,
337+
count = validatorStake.nominators.size,
338+
total = validatorStake.totalStake,
339+
nominations =
340+
validatorStake.nominators.map {
341+
Triple(it.account.address, false, it.stake)
342+
}.sortedByDescending {
343+
it.third
344+
},
345+
)
346+
}
347+
}
348+
NominatorListView(
332349
titleResourceId = R.string.validator_details_inactive_nominations,
333350
network = state.network,
334-
balance = state.validator?.inactiveNominationTotal(),
351+
count = state.validator?.inactiveNominations()?.size,
352+
total = state.validator?.inactiveNominationTotal(),
353+
nominations =
354+
state.validator?.inactiveNominations()?.map {
355+
Triple(it.stashAccount.address, false, it.stake.activeAmount)
356+
}?.sortedByDescending {
357+
it.third
358+
},
359+
)
360+
state.validator?.account?.discoveredAt?.let {
361+
AccountAgeView(discoveredAt = it)
362+
}
363+
HorizontalDataView(
364+
titleResourceId = R.string.validator_details_offline_faults,
365+
text = state.validator?.offlineOffenceCount?.toString() ?: "-",
366+
displayExclamation = (state.validator?.offlineOffenceCount ?: 0) > 0,
367+
)
368+
VerticalDataView(
369+
modifier = Modifier.fillMaxWidth(),
370+
titleResourceId = R.string.validator_details_reward_destination,
371+
text =
372+
state.validator?.rewardDestination?.display(
373+
context = LocalContext.current,
374+
prefix = state.network?.ss58Prefix?.toShort() ?: 0,
375+
) ?: "-",
376+
)
377+
Row(
378+
modifier = Modifier.fillMaxWidth(),
379+
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)),
380+
) {
381+
VerticalDataView(
382+
modifier = Modifier.weight(1.0f),
383+
titleResourceId = R.string.validator_details_commission,
384+
text =
385+
String.format(
386+
stringResource(id = R.string.percentage),
387+
formatDecimal(
388+
number =
389+
(
390+
state.validator?.preferences?.commissionPerBillion
391+
?: 0
392+
).toBigInteger(),
393+
tokenDecimalCount = 7,
394+
formatDecimalCount = 2,
395+
),
396+
),
397+
)
398+
VerticalDataView(
399+
modifier = Modifier.weight(1.0f),
400+
titleResourceId = R.string.validator_details_apr,
401+
text =
402+
String.format(
403+
stringResource(id = R.string.percentage),
404+
formatDecimal(
405+
number = (state.validator?.returnRatePerBillion ?: 0).toBigInteger(),
406+
tokenDecimalCount = 7,
407+
formatDecimalCount = 2,
408+
),
409+
),
410+
)
411+
}
412+
Row(
413+
modifier = Modifier.fillMaxWidth(),
414+
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.common_panel_padding)),
415+
) {
416+
VerticalDataView(
417+
modifier = Modifier.weight(1.0f),
418+
titleResourceId = R.string.validator_details_era_blocks,
419+
text = state.validator?.blocksAuthored?.toString() ?: "-",
420+
)
421+
VerticalDataView(
422+
modifier = Modifier.weight(1.0f),
423+
titleResourceId = R.string.validator_details_era_points,
424+
text = state.validator?.rewardPoints?.toString() ?: "-",
425+
)
426+
}
427+
state.validator?.let { validator ->
428+
if (validator.onekvCandidateRecordId != null) {
429+
OneKVDetailsView(
430+
modifier = Modifier.fillMaxWidth(),
431+
validator = validator,
432+
)
433+
}
434+
}
435+
Spacer(
436+
modifier =
437+
Modifier
438+
.navigationBarsPadding()
439+
.padding(
440+
0.dp,
441+
0.dp,
442+
0.dp,
443+
dimensionResource(id = R.dimen.common_scrollable_content_margin_bottom),
444+
),
335445
)
336-
Spacer(modifier = Modifier.navigationBarsPadding())
337446
}
338-
339-
CompositionLocalProvider(
340-
LocalOverscrollConfiguration provides null,
341-
) {
447+
state.validator?.let {
448+
IconsView(
449+
Modifier
450+
.padding(
451+
0.dp,
452+
0.dp,
453+
0.dp,
454+
dimensionResource(id = R.dimen.common_padding),
455+
)
456+
.navigationBarsPadding()
457+
.fillMaxWidth()
458+
.zIndex(8.0f)
459+
.align(Alignment.BottomCenter),
460+
validator = it,
461+
)
342462
}
343463
Box(
344464
modifier =
345465
Modifier
346466
.fillMaxWidth()
347-
.height(104.dp)
467+
.height(dimensionResource(id = R.dimen.common_bottom_gradient_height))
348468
.zIndex(7.0f)
349469
.align(Alignment.BottomCenter)
350470
.background(
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package io.helikon.subvt.ui.screen.validator.details.view
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.isSystemInDarkTheme
5+
import androidx.compose.foundation.layout.Column
6+
import androidx.compose.foundation.layout.Spacer
7+
import androidx.compose.foundation.layout.fillMaxWidth
8+
import androidx.compose.foundation.layout.height
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.shape.RoundedCornerShape
11+
import androidx.compose.material3.Text
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.res.dimensionResource
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.unit.sp
17+
import io.helikon.subvt.R
18+
import io.helikon.subvt.ui.style.Color
19+
import io.helikon.subvt.ui.style.Font
20+
import java.time.Instant
21+
import java.time.LocalDate
22+
import java.time.Period
23+
import java.time.ZoneId
24+
25+
@Composable
26+
fun AccountAgeView(
27+
modifier: Modifier = Modifier,
28+
isDark: Boolean = isSystemInDarkTheme(),
29+
discoveredAt: Long,
30+
) {
31+
val period =
32+
Period.between(
33+
Instant.ofEpochMilli(discoveredAt).atZone(ZoneId.systemDefault()).toLocalDate(),
34+
LocalDate.now(),
35+
)
36+
val periodText =
37+
if (period.years > 0) {
38+
String.format(
39+
stringResource(R.string.validator_details_period_years),
40+
period.years,
41+
)
42+
} else {
43+
""
44+
} +
45+
if (period.months > 0) {
46+
" " +
47+
String.format(
48+
stringResource(R.string.validator_details_period_months),
49+
period.months,
50+
)
51+
} else {
52+
""
53+
} +
54+
if (period.days > 0) {
55+
val days = period.days % 7
56+
val weeks = period.days / 7
57+
var weeksText = ""
58+
if (weeks > 0) {
59+
weeksText =
60+
" " +
61+
String.format(
62+
stringResource(R.string.validator_details_period_weeks),
63+
weeks,
64+
)
65+
}
66+
if (days > 0) {
67+
"$weeksText " +
68+
String.format(
69+
stringResource(R.string.validator_details_period_days),
70+
days,
71+
)
72+
} else {
73+
weeksText
74+
}
75+
} else {
76+
""
77+
}
78+
Column(
79+
modifier =
80+
modifier
81+
.background(
82+
color = Color.panelBg(isDark),
83+
shape = RoundedCornerShape(dimensionResource(id = R.dimen.common_panel_border_radius)),
84+
)
85+
.padding(dimensionResource(id = R.dimen.common_padding))
86+
.fillMaxWidth(),
87+
) {
88+
Text(
89+
text = stringResource(id = R.string.validator_details_account_age),
90+
style = Font.light(12.sp),
91+
color = Color.text(isDark),
92+
)
93+
Spacer(
94+
modifier = Modifier.height(dimensionResource(id = R.dimen.common_data_panel_content_margin_top)),
95+
)
96+
Text(
97+
text = periodText.trim(),
98+
style = Font.semiBold(20.sp),
99+
color = Color.text(isDark),
100+
)
101+
}
102+
}

0 commit comments

Comments
 (0)